aboutsummaryrefslogtreecommitdiff
path: root/src/wallet
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet')
-rw-r--r--src/wallet/CMakeLists.txt4
-rw-r--r--src/wallet/api/wallet2_api.h4
-rw-r--r--src/wallet/api/wallet_manager.cpp4
-rw-r--r--src/wallet/api/wallet_manager.h2
-rw-r--r--src/wallet/wallet2.cpp1445
-rw-r--r--src/wallet/wallet2.h227
-rw-r--r--src/wallet/wallet_args.cpp5
-rw-r--r--src/wallet/wallet_args.h1
-rw-r--r--src/wallet/wallet_errors.h19
-rw-r--r--src/wallet/wallet_rpc_server.cpp683
-rw-r--r--src/wallet/wallet_rpc_server.h16
-rw-r--r--src/wallet/wallet_rpc_server_commands_defs.h188
-rw-r--r--src/wallet/wallet_rpc_server_error_codes.h9
13 files changed, 2364 insertions, 243 deletions
diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt
index 74992139d..2d664ba15 100644
--- a/src/wallet/CMakeLists.txt
+++ b/src/wallet/CMakeLists.txt
@@ -51,6 +51,7 @@ monero_add_library(wallet
${wallet_private_headers})
target_link_libraries(wallet
PUBLIC
+ multisig
common
cryptonote_core
mnemonics
@@ -82,7 +83,7 @@ target_link_libraries(wallet_rpc_server
PRIVATE
wallet
epee
- rpc
+ rpc_base
cryptonote_core
cncrypto
common
@@ -104,6 +105,7 @@ if (BUILD_GUI_DEPS)
set(libs_to_merge
wallet_api
wallet
+ multisig
cryptonote_core
cryptonote_basic
mnemonics
diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h
index b1f8369a3..8593bd1f9 100644
--- a/src/wallet/api/wallet2_api.h
+++ b/src/wallet/api/wallet2_api.h
@@ -812,10 +812,10 @@ struct WalletManager
* @brief verifyWalletPassword - check if the given filename is the wallet
* @param keys_file_name - location of keys file
* @param password - password to verify
- * @param watch_only - verify only view keys?
+ * @param no_spend_key - verify only view keys?
* @return - true if password is correct
*/
- virtual bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool watch_only) const = 0;
+ virtual bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key) const = 0;
/*!
* \brief findWallets - searches for the wallet files by given path name recursively
diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp
index ee69ec028..a6e5e551e 100644
--- a/src/wallet/api/wallet_manager.cpp
+++ b/src/wallet/api/wallet_manager.cpp
@@ -127,9 +127,9 @@ bool WalletManagerImpl::walletExists(const std::string &path)
return false;
}
-bool WalletManagerImpl::verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool watch_only) const
+bool WalletManagerImpl::verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key) const
{
- return tools::wallet2::verify_password(keys_file_name, password, watch_only);
+ return tools::wallet2::verify_password(keys_file_name, password, no_spend_key);
}
std::vector<std::string> WalletManagerImpl::findWallets(const std::string &path)
diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h
index 978a2d411..ef5b8f91b 100644
--- a/src/wallet/api/wallet_manager.h
+++ b/src/wallet/api/wallet_manager.h
@@ -50,7 +50,7 @@ public:
const std::string &spendKeyString = "");
virtual bool closeWallet(Wallet *wallet, bool store = true);
bool walletExists(const std::string &path);
- bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool watch_only) const;
+ bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key) const;
std::vector<std::string> findWallets(const std::string &path);
std::string errorString() const;
void setDaemonAddress(const std::string &address);
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index e37cbb25d..96fcf33f2 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -28,12 +28,15 @@
//
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
+#include <numeric>
#include <random>
#include <tuple>
#include <boost/format.hpp>
#include <boost/optional/optional.hpp>
#include <boost/utility/value_init.hpp>
-#include <boost/algorithm/string/join.hpp>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/trim.hpp>
+#include <boost/algorithm/string/split.hpp>
#include "include_base_utils.h"
using namespace epee;
@@ -43,20 +46,23 @@ using namespace epee;
#include "rpc/core_rpc_server_commands_defs.h"
#include "misc_language.h"
#include "cryptonote_basic/cryptonote_basic_impl.h"
+#include "multisig/multisig.h"
#include "common/boost_serialization_helper.h"
#include "common/command_line.h"
#include "common/threadpool.h"
#include "profile_tools.h"
#include "crypto/crypto.h"
#include "serialization/binary_utils.h"
-#include "cryptonote_protocol/blobdatatype.h"
+#include "cryptonote_basic/blobdatatype.h"
#include "mnemonics/electrum-words.h"
#include "common/i18n.h"
#include "common/util.h"
+#include "common/apply_permutation.h"
#include "rapidjson/document.h"
#include "rapidjson/writer.h"
#include "rapidjson/stringbuffer.h"
#include "common/json_util.h"
+#include "common/memwipe.h"
#include "common/base58.h"
#include "ringct/rctSigs.h"
@@ -83,6 +89,7 @@ using namespace cryptonote;
#define UNSIGNED_TX_PREFIX "Monero unsigned tx set\004"
#define SIGNED_TX_PREFIX "Monero signed tx set\004"
+#define MULTISIG_UNSIGNED_TX_PREFIX "Monero multisig unsigned tx set\001"
#define RECENT_OUTPUT_RATIO (0.5) // 50% of outputs are from the recent zone
#define RECENT_OUTPUT_ZONE ((time_t)(1.8 * 86400)) // last 1.8 day makes up the recent zone (taken from monerolink.pdf, Miller et al)
@@ -96,6 +103,8 @@ using namespace cryptonote;
#define KEY_IMAGE_EXPORT_FILE_MAGIC "Monero key image export\002"
+#define MULTISIG_EXPORT_FILE_MAGIC "Monero multisig export\001"
+
namespace
{
// Create on-demand to prevent static initialization order fiasco issues.
@@ -456,7 +465,7 @@ void drop_from_short_history(std::list<crypto::hash> &short_chain_history, size_
}
}
-size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra_size)
+size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof)
{
size_t size = 0;
@@ -480,7 +489,10 @@ size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra
size += 1;
// rangeSigs
- size += (2*64*32+32+64*32) * n_outputs;
+ if (bulletproof)
+ size += ((2*6 + 4 + 5)*32 + 3) * n_outputs;
+ else
+ size += (2*64*32+32+64*32) * n_outputs;
// MGs
size += n_inputs * (64 * (mixin+1) + 32);
@@ -501,14 +513,58 @@ size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra
return size;
}
-size_t estimate_tx_size(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size)
+size_t estimate_tx_size(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof)
{
if (use_rct)
- return estimate_rct_tx_size(n_inputs, mixin, n_outputs + 1, extra_size);
+ return estimate_rct_tx_size(n_inputs, mixin, n_outputs + 1, extra_size, bulletproof);
else
return n_inputs * (mixin+1) * APPROXIMATE_INPUT_BYTES + extra_size;
}
+uint8_t get_bulletproof_fork(bool testnet)
+{
+ if (testnet)
+ return 7;
+ else
+ return 255; // TODO
+}
+
+crypto::hash8 get_short_payment_id(const tools::wallet2::pending_tx &ptx)
+{
+ crypto::hash8 payment_id8 = null_hash8;
+ std::vector<tx_extra_field> tx_extra_fields;
+ if(!parse_tx_extra(ptx.tx.extra, tx_extra_fields))
+ return payment_id8;
+ cryptonote::tx_extra_nonce extra_nonce;
+ if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce))
+ {
+ if(get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8))
+ {
+ decrypt_payment_id(payment_id8, ptx.dests[0].addr.m_view_public_key, ptx.tx_key);
+ }
+ }
+ return payment_id8;
+}
+
+tools::wallet2::tx_construction_data get_construction_data_with_decrypted_short_payment_id(const tools::wallet2::pending_tx &ptx)
+{
+ tools::wallet2::tx_construction_data construction_data = ptx.construction_data;
+ crypto::hash8 payment_id = get_short_payment_id(ptx);
+ if (payment_id != null_hash8)
+ {
+ // Remove encrypted
+ remove_field_from_tx_extra(construction_data.extra, typeid(cryptonote::tx_extra_nonce));
+ // Add decrypted
+ std::string extra_nonce;
+ set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, payment_id);
+ THROW_WALLET_EXCEPTION_IF(!add_extra_nonce_to_tx_extra(construction_data.extra, extra_nonce),
+ tools::error::wallet_internal_error, "Failed to add decrypted payment id to tx extra");
+ LOG_PRINT_L1("Decrypted payment ID: " << payment_id);
+ }
+ return construction_data;
+}
+
+ //-----------------------------------------------------------------
} //namespace
namespace tools
@@ -519,6 +575,41 @@ const size_t MAX_SPLIT_ATTEMPTS = 30;
constexpr const std::chrono::seconds wallet2::rpc_timeout;
const char* wallet2::tr(const char* str) { return i18n_translate(str, "tools::wallet2"); }
+wallet2::wallet2(bool testnet, bool restricted):
+ m_multisig_rescan_info(NULL),
+ m_multisig_rescan_k(NULL),
+ 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(RefreshOptimizeCoinbase),
+ 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_subaddress_lookahead_major(SUBADDRESS_LOOKAHEAD_MAJOR),
+ m_subaddress_lookahead_minor(SUBADDRESS_LOOKAHEAD_MINOR),
+ 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)
+{
+}
+
bool wallet2::has_testnet_option(const boost::program_options::variables_map& vm)
{
return command_line::get_arg(vm, options().testnet);
@@ -602,7 +693,7 @@ bool wallet2::is_deterministic() const
return keys_deterministic;
}
//----------------------------------------------------------------------------------------------------
-bool wallet2::get_seed(std::string& electrum_words, const std::string &passphrase) const
+bool wallet2::get_seed(std::string& electrum_words, const epee::wipeable_string &passphrase) const
{
bool keys_deterministic = is_deterministic();
if (!keys_deterministic)
@@ -710,9 +801,9 @@ void wallet2::expand_subaddresses(const cryptonote::subaddress_index& index)
{
// add new accounts
cryptonote::subaddress_index index2;
- for (index2.major = m_subaddress_labels.size(); index2.major < index.major + SUBADDRESS_LOOKAHEAD_MAJOR; ++index2.major)
+ for (index2.major = m_subaddress_labels.size(); index2.major < index.major + m_subaddress_lookahead_major; ++index2.major)
{
- for (index2.minor = 0; index2.minor < (index2.major == index.major ? index.minor : 0) + SUBADDRESS_LOOKAHEAD_MINOR; ++index2.minor)
+ for (index2.minor = 0; index2.minor < (index2.major == index.major ? index.minor : 0) + m_subaddress_lookahead_minor; ++index2.minor)
{
if (m_subaddresses_inv.count(index2) == 0)
{
@@ -729,7 +820,7 @@ void wallet2::expand_subaddresses(const cryptonote::subaddress_index& index)
{
// add new subaddresses
cryptonote::subaddress_index index2 = index;
- for (index2.minor = m_subaddress_labels[index.major].size(); index2.minor < index.minor + SUBADDRESS_LOOKAHEAD_MINOR; ++index2.minor)
+ for (index2.minor = m_subaddress_labels[index.major].size(); index2.minor < index.minor + m_subaddress_lookahead_minor; ++index2.minor)
{
if (m_subaddresses_inv.count(index2) == 0)
{
@@ -759,6 +850,12 @@ void wallet2::set_subaddress_label(const cryptonote::subaddress_index& index, co
m_subaddress_labels[index.major][index.minor] = label;
}
//----------------------------------------------------------------------------------------------------
+void wallet2::set_subaddress_lookahead(size_t major, size_t minor)
+{
+ m_subaddress_lookahead_major = major;
+ m_subaddress_lookahead_minor = minor;
+}
+//----------------------------------------------------------------------------------------------------
/*!
* \brief Tells if the wallet file is deprecated.
*/
@@ -812,8 +909,10 @@ static uint64_t decodeRct(const rct::rctSig & rv, const crypto::key_derivation &
switch (rv.type)
{
case rct::RCTTypeSimple:
+ case rct::RCTTypeSimpleBulletproof:
return rct::decodeRctSimple(rv, rct::sk2rct(scalar1), i, mask);
case rct::RCTTypeFull:
+ case rct::RCTTypeFullBulletproof:
return rct::decodeRct(rv, rct::sk2rct(scalar1), i, mask);
default:
LOG_ERROR("Unsupported rct type: " << rv.type);
@@ -829,10 +928,20 @@ static uint64_t decodeRct(const rct::rctSig & rv, const crypto::key_derivation &
//----------------------------------------------------------------------------------------------------
void wallet2::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)
{
- bool r = cryptonote::generate_key_image_helper_precomp(keys, boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key, tx_scan_info.received->derivation, i, tx_scan_info.received->index, tx_scan_info.in_ephemeral, tx_scan_info.ki);
- THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image");
- THROW_WALLET_EXCEPTION_IF(tx_scan_info.in_ephemeral.pub != boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key,
- error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key");
+ THROW_WALLET_EXCEPTION_IF(i >= tx.vout.size(), error::wallet_internal_error, "Invalid vout index");
+ if (m_multisig)
+ {
+ tx_scan_info.in_ephemeral.pub = boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key;
+ tx_scan_info.in_ephemeral.sec = crypto::null_skey;
+ tx_scan_info.ki = rct::rct2ki(rct::zero());
+ }
+ else
+ {
+ bool r = cryptonote::generate_key_image_helper_precomp(keys, boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key, tx_scan_info.received->derivation, i, tx_scan_info.received->index, tx_scan_info.in_ephemeral, tx_scan_info.ki);
+ THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image");
+ THROW_WALLET_EXCEPTION_IF(tx_scan_info.in_ephemeral.pub != boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key,
+ error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key");
+ }
outs.push_back(i);
if (tx_scan_info.money_transfered == 0)
@@ -877,7 +986,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
LOG_PRINT_L0("Public key wasn't found in the transaction extra. Skipping transaction " << txid);
if(0 != m_callback)
m_callback->on_skip_transaction(height, txid, tx);
- return;
+ break;
}
int num_vouts_received = 0;
@@ -1001,7 +1110,8 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
td.m_tx = (const cryptonote::transaction_prefix&)tx;
td.m_txid = txid;
td.m_key_image = tx_scan_info[o].ki;
- td.m_key_image_known = !m_watch_only;
+ td.m_key_image_known = !m_watch_only && !m_multisig;
+ td.m_key_image_partial = m_multisig;
td.m_amount = tx.vout[o].amount;
td.m_pk_index = pk_index - 1;
td.m_subaddr_index = tx_scan_info[o].received->index;
@@ -1023,8 +1133,16 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
td.m_rct = false;
}
set_unspent(m_transfers.size()-1);
- m_key_images[td.m_key_image] = m_transfers.size()-1;
+ if (!m_multisig)
+ m_key_images[td.m_key_image] = m_transfers.size()-1;
m_pub_keys[tx_scan_info[o].in_ephemeral.pub] = m_transfers.size()-1;
+ if (m_multisig)
+ {
+ THROW_WALLET_EXCEPTION_IF(!m_multisig_rescan_k && m_multisig_rescan_info,
+ 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);
+ }
LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid);
if (0 != m_callback)
m_callback->on_money_received(height, txid, tx, td.m_amount, td.m_subaddr_index);
@@ -1073,6 +1191,13 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
td.m_mask = rct::identity();
td.m_rct = false;
}
+ if (m_multisig)
+ {
+ THROW_WALLET_EXCEPTION_IF(!m_multisig_rescan_k && m_multisig_rescan_info,
+ 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);
+ }
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");
@@ -2066,8 +2191,10 @@ void wallet2::detach_blockchain(uint64_t height)
for(size_t i = i_start; i!= m_transfers.size();i++)
{
+ if (!m_transfers[i].m_key_image_known || m_transfers[i].m_key_image_partial)
+ continue;
auto it_ki = m_key_images.find(m_transfers[i].m_key_image);
- THROW_WALLET_EXCEPTION_IF(it_ki == m_key_images.end(), error::wallet_internal_error, "key image not found");
+ THROW_WALLET_EXCEPTION_IF(it_ki == m_key_images.end(), error::wallet_internal_error, "key image not found: index " + std::to_string(i) + ", ki " + epee::string_tools::pod_to_hex(m_transfers[i].m_key_image) + ", " + std::to_string(m_key_images.size()) + " key images known");
m_key_images.erase(it_ki);
}
@@ -2137,9 +2264,10 @@ bool wallet2::clear()
* \param watch_only true to save only view key, false to save both spend and view keys
* \return Whether it was successful.
*/
-bool wallet2::store_keys(const std::string& keys_file_name, const std::string& password, bool watch_only)
+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)
@@ -2164,6 +2292,20 @@ bool wallet2::store_keys(const std::string& keys_file_name, const std::string& p
value2.SetInt(watch_only ? 1 :0); // WTF ? JSON has different true and false types, and not boolean ??
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());
+
+ 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());
@@ -2223,7 +2365,7 @@ bool wallet2::store_keys(const std::string& keys_file_name, const std::string& p
// Encrypt the entire JSON object.
crypto::chacha8_key key;
- crypto::generate_chacha8_key(password, key);
+ crypto::generate_chacha8_key(password.data(), password.size(), key);
std::string cipher;
cipher.resize(account_data.size());
keys_file_data.iv = crypto::rand<crypto::chacha8_iv>();
@@ -2253,7 +2395,7 @@ namespace
* \param keys_file_name Name of wallet file
* \param password Password of wallet file
*/
-bool wallet2::load_keys(const std::string& keys_file_name, const std::string& password)
+bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_string& password)
{
wallet2::keys_file_data keys_file_data;
std::string buf;
@@ -2264,7 +2406,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const std::string& pa
r = ::serialization::parse_binary(buf, keys_file_data);
THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "internal error: failed to deserialize \"" + keys_file_name + '\"');
crypto::chacha8_key key;
- crypto::generate_chacha8_key(password, key);
+ crypto::generate_chacha8_key(password.data(), password.size(), key);
std::string account_data;
account_data.resize(keys_file_data.account_data.size());
crypto::chacha8(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]);
@@ -2275,6 +2417,9 @@ bool wallet2::load_keys(const std::string& keys_file_name, const std::string& pa
{
is_old_file_format = true;
m_watch_only = false;
+ m_multisig = false;
+ m_multisig_threshold = 0;
+ m_multisig_signers.clear();
m_always_confirm_transfers = false;
m_print_ring_members = false;
m_default_mixin = 0;
@@ -2289,7 +2434,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const std::string& pa
m_confirm_backlog = true;
m_confirm_backlog_threshold = 0;
}
- else
+ else if(json.IsObject())
{
if (!json.HasMember("key_data"))
{
@@ -2311,6 +2456,31 @@ bool wallet2::load_keys(const std::string& keys_file_name, const std::string& pa
}
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, watch_only, int, Int, false, false);
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;
+ 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);
@@ -2368,11 +2538,16 @@ bool wallet2::load_keys(const std::string& keys_file_name, const std::string& pa
// Wallet is being opened without testnet flag but is saved as a testnet wallet.
THROW_WALLET_EXCEPTION_IF(!m_testnet && field_testnet, error::wallet_internal_error, "Testnet wallet can not be opened as mainnet wallet");
}
+ else
+ {
+ THROW_WALLET_EXCEPTION(error::wallet_internal_error, "invalid password");
+ return false;
+ }
const cryptonote::account_keys& keys = m_account.get_keys();
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);
- 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);
THROW_WALLET_EXCEPTION_IF(!r, error::invalid_password);
return true;
@@ -2388,16 +2563,16 @@ bool wallet2::load_keys(const std::string& keys_file_name, const std::string& pa
* can be used prior to rewriting wallet keys file, to ensure user has entered the correct password
*
*/
-bool wallet2::verify_password(const std::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);
}
/*!
* \brief verify password for specified wallet keys file.
* \param keys_file_name Keys file to verify password for
* \param password Password to verify
- * \param watch_only If set = only verify view keys, otherwise also spend keys
+ * \param no_spend_key If set = only verify view keys, otherwise also spend keys
* \return true if password is correct
*
* for verification only
@@ -2405,7 +2580,7 @@ bool wallet2::verify_password(const std::string& password) const
* 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 std::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;
std::string buf;
@@ -2416,7 +2591,7 @@ bool wallet2::verify_password(const std::string& keys_file_name, const std::stri
r = ::serialization::parse_binary(buf, keys_file_data);
THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "internal error: failed to deserialize \"" + keys_file_name + '\"');
crypto::chacha8_key key;
- crypto::generate_chacha8_key(password, key);
+ crypto::generate_chacha8_key(password.data(), password.size(), key);
std::string account_data;
account_data.resize(keys_file_data.account_data.size());
crypto::chacha8(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]);
@@ -2439,7 +2614,7 @@ bool wallet2::verify_password(const std::string& keys_file_name, const std::stri
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);
- if(!watch_only)
+ if(!no_spend_key)
r = r && verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key);
return r;
}
@@ -2453,20 +2628,26 @@ bool wallet2::verify_password(const std::string& keys_file_name, const std::stri
* \param two_random Whether it is a non-deterministic wallet
* \return The secret key of the generated wallet
*/
-crypto::secret_key wallet2::generate(const std::string& wallet_, const std::string& password,
+crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wipeable_string& password,
const crypto::secret_key& recovery_param, bool recover, bool two_random)
{
clear();
prepare_file_names(wallet_);
- boost::system::error_code ignored_ec;
- THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file);
- THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file);
+ if (!wallet_.empty())
+ {
+ boost::system::error_code ignored_ec;
+ THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file);
+ THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file);
+ }
crypto::secret_key retval = m_account.generate(recovery_param, recover, two_random);
m_account_public_address = m_account.get_keys().m_account_address;
m_watch_only = false;
+ m_multisig = false;
+ m_multisig_threshold = 0;
+ m_multisig_signers.clear();
// -1 month for fluctuations in block time and machine date/time setup.
// avg seconds per block
@@ -2480,18 +2661,23 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const std::stri
m_refresh_from_block_height = height >= blocks_per_month ? height - blocks_per_month : 0;
}
- bool r = store_keys(m_keys_file, password, false);
- THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file);
+ if (!wallet_.empty())
+ {
+ bool r = store_keys(m_keys_file, password, false);
+ THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file);
- r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet));
- if(!r) MERROR("String with address text not saved");
+ r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet));
+ if(!r) MERROR("String with address text not saved");
+ }
cryptonote::block b;
generate_genesis(b);
m_blockchain.push_back(get_block_hash(b));
add_subaddress_account(tr("Primary account"));
- store();
+ if (!wallet_.empty())
+ store();
+
return retval;
}
@@ -2533,33 +2719,43 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const std::stri
* \param password Password of wallet file
* \param viewkey view secret key
*/
-void wallet2::generate(const std::string& wallet_, const std::string& password,
+void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& password,
const cryptonote::account_public_address &account_public_address,
const crypto::secret_key& viewkey)
{
clear();
prepare_file_names(wallet_);
- boost::system::error_code ignored_ec;
- THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file);
- THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file);
+ if (!wallet_.empty())
+ {
+ boost::system::error_code ignored_ec;
+ THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file);
+ THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file);
+ }
m_account.create_from_viewkey(account_public_address, viewkey);
m_account_public_address = account_public_address;
m_watch_only = true;
+ m_multisig = false;
+ m_multisig_threshold = 0;
+ m_multisig_signers.clear();
- bool r = store_keys(m_keys_file, password, true);
- THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file);
+ if (!wallet_.empty())
+ {
+ bool r = store_keys(m_keys_file, password, true);
+ 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");
+ r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet));
+ if(!r) MERROR("String with address text not saved");
+ }
cryptonote::block b;
generate_genesis(b);
m_blockchain.push_back(get_block_hash(b));
add_subaddress_account(tr("Primary account"));
- store();
+ if (!wallet_.empty())
+ store();
}
/*!
@@ -2569,33 +2765,380 @@ void wallet2::generate(const std::string& wallet_, const std::string& password,
* \param spendkey spend secret key
* \param viewkey view secret key
*/
-void wallet2::generate(const std::string& wallet_, const std::string& password,
+void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& password,
const cryptonote::account_public_address &account_public_address,
const crypto::secret_key& spendkey, const crypto::secret_key& viewkey)
{
clear();
prepare_file_names(wallet_);
- boost::system::error_code ignored_ec;
- THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file);
- THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file);
+ if (!wallet_.empty())
+ {
+ boost::system::error_code ignored_ec;
+ THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file);
+ THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file);
+ }
m_account.create_from_keys(account_public_address, spendkey, viewkey);
m_account_public_address = account_public_address;
m_watch_only = false;
+ m_multisig = false;
+ m_multisig_threshold = 0;
+ m_multisig_signers.clear();
- bool r = store_keys(m_keys_file, password, false);
- THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file);
+ if (!wallet_.empty())
+ {
+ bool r = store_keys(m_keys_file, password, false);
+ THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file);
- r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet));
- if(!r) MERROR("String with address text not saved");
+ 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));
+
+ if (!wallet_.empty())
+ store();
+}
+
+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)
+{
+ 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");
+
+ std::string extra_multisig_info;
+ crypto::hash hash;
+
+ clear();
+
+ MINFO("Creating spend key...");
+ std::vector<crypto::secret_key> multisig_keys;
+ rct::key spend_pkey, spend_skey;
+ if (threshold == spend_keys.size() + 1)
+ {
+ cryptonote::generate_multisig_N_N(get_account().get_keys(), spend_keys, multisig_keys, spend_skey, spend_pkey);
+ }
+ else if (threshold == spend_keys.size())
+ {
+ cryptonote::generate_multisig_N1_N(get_account().get_keys(), spend_keys, multisig_keys, spend_skey, spend_pkey);
+
+ // 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;
+ crypto::public_key signer;
+ CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(rct::rct2sk(spend_skey), signer), "Failed to derive public spend key");
+ 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, signer, rct::rct2sk(spend_skey), signature);
+
+ extra_multisig_info = std::string("MultisigxV1") + tools::base58::encode(data);
+ }
+ else
+ {
+ 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::secret_key view_skey = cryptonote::generate_multisig_view_secret_key(get_account().get_keys().m_view_secret_key, view_keys);
+
+ MINFO("Creating multisig address...");
+ CHECK_AND_ASSERT_THROW_MES(m_account.make_multisig(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;
+ 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())
+ {
+ bool r = store_keys(m_keys_file, password, false);
+ THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file);
+
+ r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet));
+ if(!r) MERROR("String with address text not saved");
+ }
cryptonote::block b;
generate_genesis(b);
m_blockchain.push_back(get_block_hash(b));
add_subaddress_account(tr("Primary account"));
- store();
+ if (!m_wallet_file.empty())
+ store();
+
+ return extra_multisig_info;
+}
+
+std::string wallet2::make_multisig(const epee::wipeable_string &password,
+ const std::vector<std::string> &info,
+ uint32_t threshold)
+{
+ // parse all multisig info
+ std::vector<crypto::secret_key> secret_keys(info.size());
+ std::vector<crypto::public_key> public_keys(info.size());
+ for (size_t i = 0; i < info.size(); ++i)
+ {
+ THROW_WALLET_EXCEPTION_IF(!verify_multisig_info(info[i], secret_keys[i], public_keys[i]),
+ error::wallet_internal_error, "Bad multisig info: " + info[i]);
+ }
+
+ // remove duplicates
+ for (size_t i = 0; i < secret_keys.size(); ++i)
+ {
+ for (size_t j = i + 1; j < secret_keys.size(); ++j)
+ {
+ if (rct::sk2rct(secret_keys[i]) == rct::sk2rct(secret_keys[j]))
+ {
+ MDEBUG("Duplicate key found, ignoring");
+ secret_keys[j] = secret_keys.back();
+ public_keys[j] = public_keys.back();
+ secret_keys.pop_back();
+ public_keys.pop_back();
+ --j;
+ }
+ }
+ }
+
+ // people may include their own, weed it out
+ const crypto::secret_key local_skey = cryptonote::get_multisig_blinded_secret_key(get_account().get_keys().m_view_secret_key);
+ const crypto::public_key local_pkey = get_multisig_signer_public_key(get_account().get_keys().m_spend_secret_key);
+ for (size_t i = 0; i < secret_keys.size(); ++i)
+ {
+ if (secret_keys[i] == local_skey)
+ {
+ MDEBUG("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
+ {
+ THROW_WALLET_EXCEPTION_IF(public_keys[i] == local_pkey, error::wallet_internal_error,
+ "Found local spend public key, but not local view secret key - something very weird");
+ }
+ }
+
+ return make_multisig(password, secret_keys, public_keys, threshold);
+}
+
+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
+ crypto::public_key local_signer;
+ CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(get_account().get_keys().m_spend_secret_key, local_signer),
+ "Failed to derive public spend 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");
+
+ crypto::public_key spend_public_key = cryptonote::generate_multisig_N1_N_spend_public_key(std::vector<crypto::public_key>(pkeys.begin(), pkeys.end()));
+ m_account_public_address.m_spend_public_key = spend_public_key;
+ m_account.finalize_multisig(spend_public_key);
+
+ 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)); });
+
+ 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::finalize_multisig(const epee::wipeable_string &password, const std::vector<std::string> &info)
+{
+ // parse all multisig info
+ std::unordered_set<crypto::public_key> public_keys;
+ std::vector<crypto::public_key> signers(info.size(), crypto::null_pkey);
+ for (size_t i = 0; i < info.size(); ++i)
+ {
+ if (!verify_extra_multisig_info(info[i], public_keys, signers[i]))
+ {
+ MERROR("Bad multisig info");
+ return false;
+ }
+ }
+ return finalize_multisig(password, public_keys, signers);
+}
+
+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 = cryptonote::get_multisig_blinded_secret_key(get_account().get_keys().m_view_secret_key);
+ const crypto::public_key pkey = get_multisig_signer_public_key(get_account().get_keys().m_spend_secret_key);
+ crypto::hash hash;
+
+ 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::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_multisig_blinded_secret_key(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::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::signature))
+ {
+ MERROR("Multisig info is corrupt");
+ return false;
+ }
+ if ((decoded.size() - (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::signature))) / sizeof(crypto::public_key);
+ size_t offset = 0;
+ 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, signer, 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(bool *ready, uint32_t *threshold, uint32_t *total) const
+{
+ if (!m_multisig)
+ return false;
+ if (threshold)
+ *threshold = m_multisig_threshold;
+ if (total)
+ *total = m_multisig_signers.size();
+ if (ready)
+ *ready = !(get_account().get_keys().m_account_address.m_spend_public_key == rct::rct2pk(rct::identity()));
+ return true;
+}
+
+bool wallet2::has_multisig_partial_key_images() const
+{
+ if (!m_multisig)
+ return false;
+ for (const auto &td: m_transfers)
+ if (td.m_key_image_partial)
+ return true;
+ return false;
}
/*!
@@ -2603,8 +3146,10 @@ void wallet2::generate(const std::string& wallet_, const std::string& password,
* \param wallet_name Name of wallet file (should exist)
* \param password Password for wallet file
*/
-void wallet2::rewrite(const std::string& wallet_name, const std::string& password)
+void wallet2::rewrite(const std::string& wallet_name, const epee::wipeable_string& password)
{
+ if (wallet_name.empty())
+ return;
prepare_file_names(wallet_name);
boost::system::error_code ignored_ec;
THROW_WALLET_EXCEPTION_IF(!boost::filesystem::exists(m_keys_file, ignored_ec), error::file_not_found, m_keys_file);
@@ -2616,7 +3161,7 @@ void wallet2::rewrite(const std::string& wallet_name, const std::string& passwor
* \param wallet_name Base name of wallet file
* \param password Password for wallet file
*/
-void wallet2::write_watch_only_wallet(const std::string& wallet_name, const std::string& password)
+void wallet2::write_watch_only_wallet(const std::string& wallet_name, const epee::wipeable_string& password)
{
prepare_file_names(wallet_name);
boost::system::error_code ignored_ec;
@@ -2743,16 +3288,15 @@ bool wallet2::generate_chacha8_key_from_secret_keys(crypto::chacha8_key &key) co
const account_keys &keys = m_account.get_keys();
const crypto::secret_key &view_key = keys.m_view_secret_key;
const crypto::secret_key &spend_key = keys.m_spend_secret_key;
- char data[sizeof(view_key) + sizeof(spend_key) + 1];
- memcpy(data, &view_key, sizeof(view_key));
- memcpy(data + sizeof(view_key), &spend_key, sizeof(spend_key));
+ tools::scrubbed_arr<char, sizeof(view_key) + sizeof(spend_key) + 1> data;
+ memcpy(data.data(), &view_key, sizeof(view_key));
+ memcpy(data.data() + sizeof(view_key), &spend_key, sizeof(spend_key));
data[sizeof(data) - 1] = CHACHA8_KEY_TAIL;
- crypto::generate_chacha8_key(data, sizeof(data), key);
- memset(data, 0, sizeof(data));
+ crypto::generate_chacha8_key(data.data(), sizeof(data), key);
return true;
}
//----------------------------------------------------------------------------------------------------
-void wallet2::load(const std::string& wallet_, const std::string& password)
+void wallet2::load(const std::string& wallet_, const epee::wipeable_string& password)
{
clear();
prepare_file_names(wallet_);
@@ -2903,10 +3447,10 @@ std::string wallet2::path() const
//----------------------------------------------------------------------------------------------------
void wallet2::store()
{
- store_to("", "");
+ store_to("", epee::wipeable_string());
}
//----------------------------------------------------------------------------------------------------
-void wallet2::store_to(const std::string &path, const std::string &password)
+void wallet2::store_to(const std::string &path, const epee::wipeable_string &password)
{
trim_hashchain();
@@ -3179,7 +3723,7 @@ void wallet2::rescan_spent()
{
transfer_details& td = m_transfers[i];
// a view wallet may not know about key images
- if (!td.m_key_image_known)
+ if (!td.m_key_image_known || td.m_key_image_partial)
continue;
if (td.m_spent != (spent_status[i] != COMMAND_RPC_IS_KEY_IMAGE_SPENT::UNSPENT))
{
@@ -3519,23 +4063,6 @@ crypto::hash wallet2::get_payment_id(const pending_tx &ptx) const
return payment_id;
}
-crypto::hash8 wallet2::get_short_payment_id(const pending_tx &ptx) const
-{
- crypto::hash8 payment_id8 = null_hash8;
- std::vector<tx_extra_field> tx_extra_fields;
- if(!parse_tx_extra(ptx.tx.extra, tx_extra_fields))
- return payment_id8;
- cryptonote::tx_extra_nonce extra_nonce;
- if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce))
- {
- if(get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8))
- {
- decrypt_payment_id(payment_id8, ptx.dests[0].addr.m_view_public_key, ptx.tx_key);
- }
- }
- return payment_id8;
-}
-
//----------------------------------------------------------------------------------------------------
// take a pending tx and actually send it to the daemon
void wallet2::commit_tx(pending_tx& ptx)
@@ -3603,6 +4130,10 @@ void wallet2::commit_tx(pending_tx& ptx)
set_spent(idx, 0);
}
+ // tx generated, get rid of used k values
+ for (size_t idx: ptx.selected_transfers)
+ m_transfers[idx].m_multisig_k.clear();
+
//fee includes dust if dust policy specified it.
LOG_PRINT_L1("Transaction successfully sent. <" << txid << ">" << ENDL
<< "Commission: " << print_money(ptx.fee) << " (dust sent to dust addr: " << print_money((ptx.dust_added_to_fee ? 0 : ptx.dust)) << ")" << ENDL
@@ -3625,27 +4156,10 @@ bool wallet2::save_tx(const std::vector<pending_tx>& ptx_vector, const std::stri
unsigned_tx_set txs;
for (auto &tx: ptx_vector)
{
- tx_construction_data construction_data = tx.construction_data;
// Short payment id is encrypted with tx_key.
// Since sign_tx() generates new tx_keys and encrypts the payment id, we need to save the decrypted payment ID
- // Get decrypted payment id from pending_tx
- crypto::hash8 payment_id = get_short_payment_id(tx);
- if (payment_id != null_hash8)
- {
- // Remove encrypted
- remove_field_from_tx_extra(construction_data.extra, typeid(cryptonote::tx_extra_nonce));
- // Add decrypted
- std::string extra_nonce;
- set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, payment_id);
- if (!add_extra_nonce_to_tx_extra(construction_data.extra, extra_nonce))
- {
- LOG_ERROR("Failed to add decrypted payment id to tx extra");
- return false;
- }
- LOG_PRINT_L1("Decrypted payment ID: " << payment_id);
- }
// Save tx construction_data to unsigned_tx_set
- txs.txes.push_back(construction_data);
+ txs.txes.push_back(get_construction_data_with_decrypted_short_payment_id(tx));
}
txs.transfers = m_transfers;
@@ -3763,9 +4277,11 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_f
LOG_PRINT_L1(" " << (n+1) << ": " << sd.sources.size() << " inputs, ring size " << sd.sources[0].outputs.size());
signed_txes.ptx.push_back(pending_tx());
tools::wallet2::pending_tx &ptx = signed_txes.ptx.back();
+ bool bulletproof = sd.use_rct && !ptx.tx.rct_signatures.p.bulletproofs.empty();
crypto::secret_key tx_key;
std::vector<crypto::secret_key> additional_tx_keys;
- bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sd.sources, sd.splitted_dsts, sd.change_dts.addr, sd.extra, ptx.tx, sd.unlock_time, tx_key, additional_tx_keys, sd.use_rct);
+ rct::multisig_out msout;
+ bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sd.sources, sd.splitted_dsts, sd.change_dts.addr, sd.extra, ptx.tx, sd.unlock_time, tx_key, additional_tx_keys, sd.use_rct, bulletproof, m_multisig ? &msout : NULL);
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, sd.unlock_time, m_testnet);
// we don't test tx size, because we don't know the current limit, due to not having a blockchain,
// and it's a bit pointless to fail there anyway, since it'd be a (good) guess only. We sign anyway,
@@ -3810,7 +4326,7 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_f
signed_txes.key_images.resize(m_transfers.size());
for (size_t i = 0; i < m_transfers.size(); ++i)
{
- if (!m_transfers[i].m_key_image_known)
+ if (!m_transfers[i].m_key_image_known || m_transfers[i].m_key_image_partial)
LOG_PRINT_L0("WARNING: key image not known in signing wallet at index " << i);
signed_txes.key_images[i] = m_transfers[i].m_key_image;
}
@@ -3936,11 +4452,12 @@ bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wal
for (size_t i = 0; i < signed_txs.key_images.size(); ++i)
{
transfer_details &td = m_transfers[i];
- if (td.m_key_image_known && td.m_key_image != signed_txs.key_images[i])
+ if (td.m_key_image_known && !td.m_key_image_partial && td.m_key_image != signed_txs.key_images[i])
LOG_PRINT_L0("WARNING: imported key image differs from previously known key image at index " << i << ": trusting imported one");
td.m_key_image = signed_txs.key_images[i];
m_key_images[m_transfers[i].m_key_image] = i;
td.m_key_image_known = true;
+ td.m_key_image_partial = false;
m_pub_keys[m_transfers[i].get_public_key()] = i;
}
@@ -3949,6 +4466,292 @@ bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wal
return true;
}
//----------------------------------------------------------------------------------------------------
+std::string wallet2::save_multisig_tx(multisig_tx_set txs)
+{
+ LOG_PRINT_L0("saving " << txs.m_ptx.size() << " multisig transactions");
+
+ // 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.clear();
+
+ // zero out some data we don't want to share
+ for (auto &ptx: txs.m_ptx)
+ {
+ for (auto &e: ptx.construction_data.sources)
+ e.multisig_kLRki.k = rct::zero();
+ }
+
+ for (auto &ptx: txs.m_ptx)
+ {
+ // Get decrypted payment id from pending_tx
+ ptx.construction_data = get_construction_data_with_decrypted_short_payment_id(ptx);
+ }
+
+ // save as binary
+ std::ostringstream oss;
+ boost::archive::portable_binary_oarchive ar(oss);
+ try
+ {
+ ar << txs;
+ }
+ catch (...)
+ {
+ return std::string();
+ }
+ LOG_PRINT_L2("Saving multisig unsigned tx data: " << oss.str());
+ std::string ciphertext = encrypt_with_view_secret_key(oss.str());
+ return std::string(MULTISIG_UNSIGNED_TX_PREFIX) + ciphertext;
+}
+//----------------------------------------------------------------------------------------------------
+bool wallet2::save_multisig_tx(const multisig_tx_set &txs, const std::string &filename)
+{
+ std::string ciphertext = save_multisig_tx(txs);
+ if (ciphertext.empty())
+ return false;
+ return epee::file_io_utils::save_string_to_file(filename, ciphertext);
+}
+//----------------------------------------------------------------------------------------------------
+std::string wallet2::save_multisig_tx(const std::vector<pending_tx>& ptx_vector)
+{
+ multisig_tx_set txs;
+ txs.m_ptx = ptx_vector;
+
+ 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);
+}
+//----------------------------------------------------------------------------------------------------
+bool wallet2::save_multisig_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename)
+{
+ std::string ciphertext = save_multisig_tx(ptx_vector);
+ if (ciphertext.empty())
+ return false;
+ return epee::file_io_utils::save_string_to_file(filename, ciphertext);
+}
+//----------------------------------------------------------------------------------------------------
+bool wallet2::load_multisig_tx(cryptonote::blobdata s, multisig_tx_set &exported_txs, std::function<bool(const multisig_tx_set&)> accept_func)
+{
+ const size_t magiclen = strlen(MULTISIG_UNSIGNED_TX_PREFIX);
+ if (strncmp(s.c_str(), MULTISIG_UNSIGNED_TX_PREFIX, magiclen))
+ {
+ LOG_PRINT_L0("Bad magic from multisig tx data");
+ return false;
+ }
+ try
+ {
+ s = decrypt_with_view_secret_key(std::string(s, magiclen));
+ }
+ catch (const std::exception &e)
+ {
+ LOG_PRINT_L0("Failed to decrypt multisig tx data: " << e.what());
+ return false;
+ }
+ try
+ {
+ std::istringstream iss(s);
+ boost::archive::portable_binary_iarchive ar(iss);
+ ar >> exported_txs;
+ }
+ catch (...)
+ {
+ LOG_PRINT_L0("Failed to parse multisig tx data");
+ return false;
+ }
+
+ // sanity checks
+ for (const auto &ptx: exported_txs.m_ptx)
+ {
+ CHECK_AND_ASSERT_MES(ptx.selected_transfers.size() == ptx.tx.vin.size(), false, "Mismatched selected_transfers/vin sizes");
+ for (size_t idx: ptx.selected_transfers)
+ CHECK_AND_ASSERT_MES(idx < m_transfers.size(), false, "Transfer index out of range");
+ CHECK_AND_ASSERT_MES(ptx.construction_data.selected_transfers.size() == ptx.tx.vin.size(), false, "Mismatched cd selected_transfers/vin sizes");
+ for (size_t idx: ptx.construction_data.selected_transfers)
+ CHECK_AND_ASSERT_MES(idx < m_transfers.size(), false, "Transfer index out of range");
+ CHECK_AND_ASSERT_MES(ptx.construction_data.sources.size() == ptx.tx.vin.size(), false, "Mismatched sources/vin sizes");
+ }
+
+ LOG_PRINT_L1("Loaded multisig tx unsigned data from binary: " << exported_txs.m_ptx.size() << " transactions");
+ for (auto &ptx: exported_txs.m_ptx) LOG_PRINT_L0(cryptonote::obj_to_json_str(ptx.tx));
+
+ if (accept_func && !accept_func(exported_txs))
+ {
+ LOG_PRINT_L1("Transactions rejected by callback");
+ return false;
+ }
+
+ const bool is_signed = exported_txs.m_signers.size() >= m_multisig_threshold;
+ if (is_signed)
+ {
+ for (const auto &ptx: exported_txs.m_ptx)
+ {
+ const crypto::hash txid = get_transaction_hash(ptx.tx);
+ if (store_tx_info())
+ {
+ m_tx_keys.insert(std::make_pair(txid, ptx.tx_key));
+ m_additional_tx_keys.insert(std::make_pair(txid, ptx.additional_tx_keys));
+ }
+ }
+ }
+
+ return true;
+}
+//----------------------------------------------------------------------------------------------------
+bool wallet2::load_multisig_tx_from_file(const std::string &filename, multisig_tx_set &exported_txs, std::function<bool(const multisig_tx_set&)> accept_func)
+{
+ std::string s;
+ boost::system::error_code errcode;
+
+ if (!boost::filesystem::exists(filename, errcode))
+ {
+ LOG_PRINT_L0("File " << filename << " does not exist: " << errcode);
+ return false;
+ }
+ if (!epee::file_io_utils::load_file_to_string(filename.c_str(), s))
+ {
+ LOG_PRINT_L0("Failed to load from " << filename);
+ return false;
+ }
+
+ if (!load_multisig_tx(s, exported_txs, accept_func))
+ {
+ LOG_PRINT_L0("Failed to parse multisig tx data from " << filename);
+ return false;
+ }
+ return true;
+}
+//----------------------------------------------------------------------------------------------------
+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();
+
+ THROW_WALLET_EXCEPTION_IF(exported_txs.m_signers.find(local_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");
+ THROW_WALLET_EXCEPTION_IF(exported_txs.m_signers.size() == m_multisig_threshold,
+ error::wallet_internal_error, "Transaction is already fully signed");
+
+ 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.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);
+ THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, sd.unlock_time, m_testnet);
+
+ THROW_WALLET_EXCEPTION_IF(get_transaction_prefix_hash (tx) != get_transaction_prefix_hash(ptx.tx),
+ error::wallet_internal_error, "Transaction prefix does not match data");
+
+ // Tests passed, sign
+ std::vector<unsigned int> indices;
+ for (const auto &source: sources)
+ indices.push_back(source.real_output);
+
+ for (auto &sig: ptx.multisig_sigs)
+ {
+ if (sig.ignore != local_signer)
+ {
+ ptx.tx.rct_signatures = sig.sigs;
+
+ 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())
+ {
+ m_tx_keys.insert(std::make_pair(txid, ptx.tx_key));
+ m_additional_tx_keys.insert(std::make_pair(txid, ptx.additional_tx_keys));
+ }
+ txids.push_back(txid);
+ }
+ }
+
+ // 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.clear();
+
+ exported_txs.m_signers.insert(get_multisig_signer_public_key());
+
+ return true;
+}
+//----------------------------------------------------------------------------------------------------
+bool wallet2::sign_multisig_tx_to_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);
+}
+//----------------------------------------------------------------------------------------------------
+bool wallet2::sign_multisig_tx_from_file(const std::string &filename, std::vector<crypto::hash> &txids, std::function<bool(const multisig_tx_set&)> accept_func)
+{
+ multisig_tx_set exported_txs;
+ if(!load_multisig_tx_from_file(filename, exported_txs))
+ return false;
+
+ if (accept_func && !accept_func(exported_txs))
+ {
+ LOG_PRINT_L1("Transactions rejected by callback");
+ return false;
+ }
+ return sign_multisig_tx_to_file(exported_txs, filename, txids);
+}
+//----------------------------------------------------------------------------------------------------
uint64_t wallet2::get_fee_multiplier(uint32_t priority, int fee_algorithm)
{
static const uint64_t old_multipliers[3] = {1, 2, 3};
@@ -4066,7 +4869,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto
pending_tx ptx;
// loop until fee is met without increasing tx size to next KB boundary.
- const size_t estimated_tx_size = estimate_tx_size(false, unused_transfers_indices.size(), fake_outs_count, dst_vector.size(), extra.size());
+ const size_t estimated_tx_size = estimate_tx_size(false, unused_transfers_indices.size(), fake_outs_count, dst_vector.size(), extra.size(), false);
uint64_t needed_fee = calculate_fee(fee_per_kb, estimated_tx_size, fee_multiplier);
do
{
@@ -4500,6 +5303,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));
@@ -4572,6 +5377,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;
+ src.multisig_kLRki = rct::multisig_kLRki({rct::zero(), rct::zero(), rct::zero(), rct::zero()});
detail::print_source_entry(src);
++out_index;
}
@@ -4599,8 +5405,9 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
crypto::secret_key tx_key;
std::vector<crypto::secret_key> additional_tx_keys;
+ rct::multisig_out msout;
LOG_PRINT_L2("constructing tx");
- bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys);
+ bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, false, m_multisig ? &msout : NULL);
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);
@@ -4648,7 +5455,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::vector<size_t>& selected_transfers, size_t fake_outputs_count,
std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs,
- uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx)
+ uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx, bool bulletproof)
{
using namespace cryptonote;
// throw if attempting a transaction with no destinations
@@ -4669,6 +5476,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)
{
@@ -4689,6 +5526,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);
@@ -4730,6 +5568,13 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
src.real_output = it_to_replace - src.outputs.begin();
src.real_output_in_tx_index = td.m_internal_output_index;
src.mask = td.m_mask;
+ if (m_multisig)
+ {
+ 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);
++out_index;
}
@@ -4763,12 +5608,67 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
crypto::secret_key tx_key;
std::vector<crypto::secret_key> additional_tx_keys;
+ rct::multisig_out msout;
LOG_PRINT_L2("constructing tx");
- bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, true);
+ auto sources_copy = sources;
+ bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, true, bulletproof, m_multisig ? &msout : NULL);
LOG_PRINT_L2("constructed tx, r="<<r);
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, 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);
+ // work out the permutation done on sources
+ std::vector<size_t> ins_order;
+ for (size_t n = 0; n < sources.size(); ++n)
+ {
+ for (size_t idx = 0; idx < sources_copy.size(); ++idx)
+ {
+ THROW_WALLET_EXCEPTION_IF((size_t)sources_copy[idx].real_output >= sources_copy[idx].outputs.size(),
+ error::wallet_internal_error, "Invalid real_output");
+ if (sources_copy[idx].outputs[sources_copy[idx].real_output].second.dest == sources[n].outputs[sources[n].real_output].second.dest)
+ ins_order.push_back(idx);
+ }
+ }
+ 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, bulletproof, &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
@@ -4787,13 +5687,15 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
ptx.tx = tx;
ptx.change_dts = change_dts;
ptx.selected_transfers = selected_transfers;
+ tools::apply_permutation(ins_order, ptx.selected_transfers);
ptx.tx_key = tx_key;
ptx.additional_tx_keys = additional_tx_keys;
ptx.dests = dsts;
- 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 = selected_transfers;
+ ptx.construction_data.selected_transfers = ptx.selected_transfers;
ptx.construction_data.extra = tx.extra;
ptx.construction_data.unlock_time = unlock_time;
ptx.construction_data.use_rct = true;
@@ -4820,13 +5722,13 @@ std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui
for (size_t i = 0; i < m_transfers.size(); ++i)
{
const transfer_details& td = m_transfers[i];
- if (!td.m_spent && td.is_rct() && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1)
+ if (!td.m_spent && !td.m_key_image_partial && td.is_rct() && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1)
{
LOG_PRINT_L2("Considering input " << i << ", " << print_money(td.amount()));
for (size_t j = i + 1; j < m_transfers.size(); ++j)
{
const transfer_details& td2 = m_transfers[j];
- if (!td2.m_spent && td2.is_rct() && td.amount() + td2.amount() >= needed_money && is_transfer_unlocked(td2) && td2.m_subaddr_index == td.m_subaddr_index)
+ if (!td2.m_spent && !td.m_key_image_partial && td2.is_rct() && td.amount() + td2.amount() >= needed_money && is_transfer_unlocked(td2) && td2.m_subaddr_index == td.m_subaddr_index)
{
// update our picks if those outputs are less related than any we
// already found. If the same, don't update, and oldest suitable outputs
@@ -5432,6 +6334,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
uint64_t needed_fee, available_for_fee = 0;
uint64_t upper_transaction_size_limit = get_upper_transaction_size_limit();
const bool use_rct = use_fork_rules(4, 0);
+ const bool bulletproof = use_fork_rules(get_bulletproof_fork(m_testnet), 0);
const uint64_t fee_per_kb = get_per_kb_fee();
const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm());
@@ -5487,7 +6390,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
for (size_t i = 0; i < m_transfers.size(); ++i)
{
const transfer_details& td = m_transfers[i];
- if (!td.m_spent && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1)
+ if (!td.m_spent && !td.m_key_image_partial && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1)
{
const uint32_t index_minor = td.m_subaddr_index.minor;
auto find_predicate = [&index_minor](const std::pair<uint32_t, std::vector<size_t>>& x) { return x.first == index_minor; };
@@ -5567,7 +6470,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
{
// this is used to build a tx that's 1 or 2 inputs, and 2 outputs, which
// will get us a known fee.
- uint64_t estimated_fee = calculate_fee(fee_per_kb, estimate_rct_tx_size(2, fake_outs_count, 2, extra.size()), fee_multiplier);
+ uint64_t estimated_fee = calculate_fee(fee_per_kb, estimate_rct_tx_size(2, fake_outs_count, 2, extra.size(), bulletproof), fee_multiplier);
preferred_inputs = pick_preferred_rct_inputs(needed_money + estimated_fee, subaddr_account, subaddr_indices);
if (!preferred_inputs.empty())
{
@@ -5670,7 +6573,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
}
else
{
- while (!dsts.empty() && dsts[0].amount <= available_amount && estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size(), extra.size()) < TX_SIZE_TARGET(upper_transaction_size_limit))
+ while (!dsts.empty() && dsts[0].amount <= available_amount && estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size(), extra.size(), bulletproof) < TX_SIZE_TARGET(upper_transaction_size_limit))
{
// we can fully pay that destination
LOG_PRINT_L2("We can fully pay " << get_account_address_as_str(m_testnet, dsts[0].is_subaddress, dsts[0].addr) <<
@@ -5682,7 +6585,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
++original_output_index;
}
- if (available_amount > 0 && !dsts.empty() && estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size(), extra.size()) < TX_SIZE_TARGET(upper_transaction_size_limit)) {
+ if (available_amount > 0 && !dsts.empty() && estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size(), extra.size(), bulletproof) < TX_SIZE_TARGET(upper_transaction_size_limit)) {
// we can partially fill that destination
LOG_PRINT_L2("We can partially pay " << get_account_address_as_str(m_testnet, dsts[0].is_subaddress, dsts[0].addr) <<
" for " << print_money(available_amount) << "/" << print_money(dsts[0].amount));
@@ -5706,7 +6609,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
}
else
{
- const size_t estimated_rct_tx_size = estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size(), extra.size());
+ const size_t estimated_rct_tx_size = estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size(), extra.size(), bulletproof);
try_tx = dsts.empty() || (estimated_rct_tx_size >= TX_SIZE_TARGET(upper_transaction_size_limit));
}
}
@@ -5715,14 +6618,14 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
cryptonote::transaction test_tx;
pending_tx test_ptx;
- const size_t estimated_tx_size = estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size(), extra.size());
+ const size_t estimated_tx_size = estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size(), extra.size(), bulletproof);
needed_fee = calculate_fee(fee_per_kb, estimated_tx_size, fee_multiplier);
LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " outputs and " <<
tx.selected_transfers.size() << " inputs");
if (use_rct)
transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
- test_tx, test_ptx);
+ test_tx, test_ptx, bulletproof);
else
transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx);
@@ -5765,7 +6668,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
while (needed_fee > test_ptx.fee) {
if (use_rct)
transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
- test_tx, test_ptx);
+ test_tx, test_ptx, bulletproof);
else
transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx);
@@ -5863,7 +6766,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below
for (size_t i = 0; i < m_transfers.size(); ++i)
{
const transfer_details& td = m_transfers[i];
- if (!td.m_spent && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1)
+ if (!td.m_spent && !td.m_key_image_partial && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1)
{
if (below == 0 || td.amount() < below)
{
@@ -5917,6 +6820,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
std::vector<std::vector<get_outs_entry>> outs;
const bool use_rct = fake_outs_count > 0 && use_fork_rules(4, 0);
+ const bool bulletproof = use_fork_rules(get_bulletproof_fork(m_testnet), 0);
const uint64_t fee_per_kb = get_per_kb_fee();
const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm());
@@ -5955,14 +6859,14 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
// here, check if we need to sent tx and start a new one
LOG_PRINT_L2("Considering whether to create a tx now, " << tx.selected_transfers.size() << " inputs, tx limit "
<< upper_transaction_size_limit);
- const size_t estimated_rct_tx_size = estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 1, extra.size());
+ const size_t estimated_rct_tx_size = estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 1, extra.size(), bulletproof);
bool try_tx = (unused_dust_indices.empty() && unused_transfers_indices.empty()) || ( estimated_rct_tx_size >= TX_SIZE_TARGET(upper_transaction_size_limit));
if (try_tx) {
cryptonote::transaction test_tx;
pending_tx test_ptx;
- const size_t estimated_tx_size = estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size(), extra.size());
+ const size_t estimated_tx_size = estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size(), extra.size(), bulletproof);
needed_fee = calculate_fee(fee_per_kb, estimated_tx_size, fee_multiplier);
tx.dsts.push_back(tx_destination_entry(1, address, is_subaddress));
@@ -5971,7 +6875,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
tx.selected_transfers.size() << " outputs");
if (use_rct)
transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
- test_tx, test_ptx);
+ test_tx, test_ptx, bulletproof);
else
transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx);
@@ -5988,7 +6892,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
tx.dsts[0].amount = available_for_fee - needed_fee;
if (use_rct)
transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
- test_tx, test_ptx);
+ test_tx, test_ptx, bulletproof);
else
transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx);
@@ -6076,6 +6980,8 @@ std::vector<size_t> wallet2::select_available_outputs(const std::function<bool(c
{
if (i->m_spent)
continue;
+ if (i->m_key_image_partial)
+ continue;
if (!is_transfer_unlocked(*i))
continue;
if (f(*i))
@@ -7056,7 +7962,7 @@ std::vector<std::pair<crypto::key_image, crypto::signature>> wallet2::export_key
bool r = cryptonote::generate_key_image_helper(m_account.get_keys(), m_subaddresses, pkey, tx_pub_key, additional_tx_pub_keys, td.m_internal_output_index, in_ephemeral, ki);
THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image");
- THROW_WALLET_EXCEPTION_IF(td.m_key_image_known && ki != td.m_key_image,
+ THROW_WALLET_EXCEPTION_IF(td.m_key_image_known && !td.m_key_image_partial && ki != td.m_key_image,
error::wallet_internal_error, "key_image generated not matched with cached key image");
THROW_WALLET_EXCEPTION_IF(in_ephemeral.pub != pkey,
error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key");
@@ -7171,6 +8077,7 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
m_transfers[n].m_key_image = signed_key_images[n].first;
m_key_images[m_transfers[n].m_key_image] = n;
m_transfers[n].m_key_image_known = true;
+ m_transfers[n].m_key_image_partial = false;
}
if(check_spent)
@@ -7455,6 +8362,7 @@ size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_detail
THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image");
expand_subaddresses(td.m_subaddr_index);
td.m_key_image_known = true;
+ td.m_key_image_partial = false;
THROW_WALLET_EXCEPTION_IF(in_ephemeral.pub != boost::get<cryptonote::txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key,
error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key at index " + boost::lexical_cast<std::string>(i));
@@ -7466,6 +8374,287 @@ size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_detail
return m_transfers.size();
}
//----------------------------------------------------------------------------------------------------
+crypto::public_key wallet2::get_multisig_signer_public_key(const crypto::secret_key &spend_skey) const
+{
+ crypto::public_key pkey;
+ crypto::secret_key_to_public_key(get_multisig_blinded_secret_key(spend_skey), pkey);
+ return pkey;
+}
+//----------------------------------------------------------------------------------------------------
+crypto::public_key wallet2::get_multisig_signer_public_key() const
+{
+ CHECK_AND_ASSERT_THROW_MES(m_multisig, "Wallet is not multisig");
+ crypto::public_key signer;
+ CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(get_account().get_keys().m_spend_secret_key, signer), "Failed to generate signer public key");
+ return signer;
+}
+//----------------------------------------------------------------------------------------------------
+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;
+ cryptonote::generate_multisig_LR(m_transfers[n].get_public_key(), rct::rct2sk(kLRki.k), (crypto::public_key&)kLRki.L, (crypto::public_key&)kLRki.R);
+ kLRki.ki = rct::ki2rct(m_transfers[n].m_key_image);
+ return kLRki;
+}
+//----------------------------------------------------------------------------------------------------
+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
+{
+ 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];
+ const crypto::public_key tx_key = get_tx_pub_key_from_received_outs(td);
+ const std::vector<crypto::public_key> additional_tx_keys = cryptonote::get_additional_tx_pub_keys_from_extra(td.m_tx);
+ crypto::key_image ki;
+ std::vector<crypto::key_image> pkis;
+ for (const auto &info: td.m_multisig_info)
+ for (const auto &pki: info.m_partial_key_images)
+ pkis.push_back(pki);
+ bool r = cryptonote::generate_multisig_composite_key_image(get_account().get_keys(), m_subaddresses, td.get_public_key(), tx_key, additional_tx_keys, td.m_internal_output_index, pkis, ki);
+ THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image");
+ return ki;
+}
+//----------------------------------------------------------------------------------------------------
+cryptonote::blobdata 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)
+ {
+ transfer_details &td = m_transfers[n];
+ const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td.m_tx);
+ crypto::key_image ki;
+ 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 = generate_multisig_key_image(get_account().get_keys(), m, td.get_public_key(), ki);
+ 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;
+ }
+
+ std::stringstream oss;
+ boost::archive::portable_binary_oarchive ar(oss);
+ ar << info;
+
+ std::string magic(MULTISIG_EXPORT_FILE_MAGIC, strlen(MULTISIG_EXPORT_FILE_MAGIC));
+ const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address;
+ 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));
+ header += std::string((const char *)&signer, sizeof(crypto::public_key));
+ std::string ciphertext = encrypt_with_view_secret_key(header + oss.str());
+
+ return MULTISIG_EXPORT_FILE_MAGIC + ciphertext;
+}
+//----------------------------------------------------------------------------------------------------
+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");
+
+ MDEBUG("update_multisig_rescan_info: updating index " << n);
+ transfer_details &td = m_transfers[n];
+ td.m_multisig_info.clear();
+ for (const auto &pi: info)
+ {
+ CHECK_AND_ASSERT_THROW_MES(n < pi.size(), "Bad pi size");
+ td.m_multisig_info.push_back(pi[n]);
+ }
+ m_key_images.erase(td.m_key_image);
+ 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];
+ m_key_images[td.m_key_image] = n;
+}
+//----------------------------------------------------------------------------------------------------
+size_t wallet2::import_multisig(std::vector<cryptonote::blobdata> blobs)
+{
+ CHECK_AND_ASSERT_THROW_MES(m_multisig, "Wallet is not multisig");
+
+ std::vector<std::vector<tools::wallet2::multisig_info>> info;
+ std::unordered_set<crypto::public_key> seen;
+ for (cryptonote::blobdata &data: blobs)
+ {
+ const size_t magiclen = strlen(MULTISIG_EXPORT_FILE_MAGIC);
+ THROW_WALLET_EXCEPTION_IF(data.size() < magiclen || memcmp(data.data(), MULTISIG_EXPORT_FILE_MAGIC, magiclen),
+ error::wallet_internal_error, "Bad multisig info file magic in ");
+
+ data = decrypt_with_view_secret_key(std::string(data, magiclen));
+
+ const size_t headerlen = 3 * sizeof(crypto::public_key);
+ THROW_WALLET_EXCEPTION_IF(data.size() < headerlen, error::wallet_internal_error, "Bad data size");
+
+ 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::public_key &signer = *(const crypto::public_key*)&data[2*sizeof(crypto::public_key)];
+ const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address;
+ THROW_WALLET_EXCEPTION_IF(public_spend_key != keys.m_spend_public_key || public_view_key != keys.m_view_public_key,
+ error::wallet_internal_error, "Multisig info is for a different account");
+ if (get_multisig_signer_public_key() == signer)
+ {
+ MINFO("Multisig info from this wallet ignored");
+ continue;
+ }
+ if (seen.find(signer) != seen.end())
+ {
+ MINFO("Duplicate multisig info ignored");
+ continue;
+ }
+ seen.insert(signer);
+
+ std::string body(data, headerlen);
+ std::istringstream iss(body);
+ std::vector<tools::wallet2::multisig_info> i;
+ boost::archive::portable_binary_iarchive ar(iss);
+ ar >> i;
+ MINFO(boost::format("%u outputs found") % boost::lexical_cast<std::string>(i.size()));
+ info.push_back(std::move(i));
+ }
+
+ 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<std::vector<rct::key>> k;
+ k.reserve(m_transfers.size());
+ for (const auto &td: m_transfers)
+ k.push_back(td.m_multisig_k);
+
+ // how many outputs we're going to update
+ size_t n_outputs = m_transfers.size();
+ for (const auto &pi: info)
+ 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)
+ {
+ const transfer_details &td = m_transfers[n];
+ if (!td.m_key_image_partial)
+ continue;
+ MINFO("Multisig info importing from block height " << td.m_block_height);
+ detach_blockchain(td.m_block_height);
+ break;
+ }
+
+ for (size_t n = 0; n < n_outputs && n < m_transfers.size(); ++n)
+ {
+ update_multisig_rescan_info(k, info, n);
+ }
+
+ m_multisig_rescan_k = &k;
+ m_multisig_rescan_info = &info;
+ try
+ {
+ refresh();
+ }
+ catch (...) {}
+ m_multisig_rescan_info = NULL;
+ m_multisig_rescan_k = NULL;
+
+ return n_outputs;
+}
+//----------------------------------------------------------------------------------------------------
std::string wallet2::encrypt(const std::string &plaintext, const crypto::secret_key &skey, bool authenticated) const
{
crypto::chacha8_key key;
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index eeec7c338..399287c3e 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -59,8 +59,6 @@
#include "common/password.h"
#include "node_rpc_proxy.h"
-#include <iostream>
-
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "wallet.wallet2"
@@ -146,10 +144,6 @@ namespace tools
RefreshDefault = RefreshOptimizeCoinbase,
};
- private:
- wallet2(const wallet2&) : m_run(true), m_callback(0), m_testnet(false), 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_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex) {}
-
- public:
static const char* tr(const char* str);
static bool has_testnet_option(const boost::program_options::variables_map& vm);
@@ -168,9 +162,33 @@ namespace tools
//! 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 bool verify_password(const std::string& keys_file_name, const std::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);
+
+ struct multisig_info
+ {
+ struct LR
+ {
+ rct::key m_L;
+ rct::key m_R;
+
+ BEGIN_SERIALIZE_OBJECT()
+ FIELD(m_L)
+ FIELD(m_R)
+ END_SERIALIZE()
+ };
- 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) {}
+ 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_signer)
+ FIELD(m_LR)
+ FIELD(m_partial_key_images)
+ END_SERIALIZE()
+ };
struct tx_scan_info_t
{
@@ -201,6 +219,9 @@ namespace tools
bool m_key_image_known;
size_t m_pk_index;
cryptonote::subaddress_index m_subaddr_index;
+ bool m_key_image_partial;
+ 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; }
@@ -221,6 +242,9 @@ namespace tools
FIELD(m_key_image_known)
FIELD(m_pk_index)
FIELD(m_subaddr_index)
+ FIELD(m_key_image_partial)
+ FIELD(m_multisig_k)
+ FIELD(m_multisig_info)
END_SERIALIZE()
};
@@ -310,6 +334,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
@@ -324,6 +357,7 @@ namespace tools
crypto::secret_key tx_key;
std::vector<crypto::secret_key> additional_tx_keys;
std::vector<cryptonote::tx_destination_entry> dests;
+ std::vector<multisig_sig> multisig_sigs;
tx_construction_data construction_data;
@@ -339,6 +373,7 @@ namespace tools
FIELD(additional_tx_keys)
FIELD(dests)
FIELD(construction_data)
+ FIELD(multisig_sigs)
END_SERIALIZE()
};
@@ -356,6 +391,17 @@ namespace tools
std::vector<crypto::key_image> key_images;
};
+ struct multisig_tx_set
+ {
+ std::vector<pending_tx> m_ptx;
+ std::unordered_set<crypto::public_key> m_signers;
+
+ BEGIN_SERIALIZE_OBJECT()
+ FIELD(m_ptx)
+ FIELD(m_signers)
+ END_SERIALIZE()
+ };
+
struct keys_file_data
{
crypto::chacha8_iv iv;
@@ -398,7 +444,7 @@ namespace tools
* \param two_random Whether it is a non-deterministic wallet
* \return The secret key of the generated wallet
*/
- crypto::secret_key generate(const std::string& wallet, const std::string& password,
+ crypto::secret_key generate(const std::string& wallet, const epee::wipeable_string& password,
const crypto::secret_key& recovery_param = crypto::secret_key(), bool recover = false,
bool two_random = false);
/*!
@@ -408,7 +454,7 @@ namespace tools
* \param viewkey view secret key
* \param spendkey spend secret key
*/
- void generate(const std::string& wallet, const std::string& password,
+ void generate(const std::string& wallet, const epee::wipeable_string& password,
const cryptonote::account_public_address &account_public_address,
const crypto::secret_key& spendkey, const crypto::secret_key& viewkey);
/*!
@@ -417,31 +463,78 @@ namespace tools
* \param password Password of wallet file
* \param viewkey view secret key
*/
- void generate(const std::string& wallet, const std::string& password,
+ void generate(const std::string& wallet, const epee::wipeable_string& password,
const cryptonote::account_public_address &account_public_address,
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
+ */
+ std::string make_multisig(const epee::wipeable_string &password,
+ const std::vector<std::string> &info,
+ uint32_t threshold);
+ /*!
+ * \brief Creates a multisig wallet
+ * \return empty if done, non empty if we need to send another string
+ * to other participants
+ */
+ 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, const std::vector<std::string> &info);
+ /*!
+ * \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
+ */
+ 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);
+ /*!
+ * 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
+ */
+ cryptonote::blobdata export_multisig();
+ /*!
+ * Import a set of multisig info from multisig partners
+ * \return the number of inputs which were imported
+ */
+ size_t import_multisig(std::vector<cryptonote::blobdata> info);
+ /*!
* \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 password Password for wallet file
*/
- void rewrite(const std::string& wallet_name, const std::string& password);
- void write_watch_only_wallet(const std::string& wallet_name, const std::string& password);
- void load(const std::string& wallet, const std::string& password);
+ void rewrite(const std::string& wallet_name, const epee::wipeable_string& password);
+ void write_watch_only_wallet(const std::string& wallet_name, const epee::wipeable_string& password);
+ void load(const std::string& wallet, const epee::wipeable_string& password);
void store();
/*!
* \brief store_to - stores wallet to another file(s), deleting old ones
* \param path - path to the wallet file (keys and address filenames will be generated based on this filename)
* \param password - password to protect new wallet (TODO: probably better save the password in the wallet object?)
*/
- void store_to(const std::string &path, const std::string &password);
+ void store_to(const std::string &path, const epee::wipeable_string &password);
std::string path() const;
/*!
* \brief verifies given password is correct for default wallet keys file
*/
- bool verify_password(const std::string& password) const;
+ bool verify_password(const epee::wipeable_string& password) const;
cryptonote::account_base& get_account(){return m_account;}
const cryptonote::account_base& get_account()const{return m_account;}
@@ -466,7 +559,7 @@ namespace tools
* \brief Checks if deterministic wallet
*/
bool is_deterministic() const;
- bool get_seed(std::string& electrum_words, const std::string &passphrase = std::string()) const;
+ bool get_seed(std::string& electrum_words, const epee::wipeable_string &passphrase = epee::wipeable_string()) const;
/*!
* \brief Checks if light wallet. A light wallet sends view key to a server where the blockchain is scanned.
@@ -499,6 +592,7 @@ namespace tools
void expand_subaddresses(const cryptonote::subaddress_index& index);
std::string get_subaddress_label(const cryptonote::subaddress_index& index) const;
void set_subaddress_label(const cryptonote::subaddress_index &index, const std::string &label);
+ void set_subaddress_lookahead(size_t major, size_t minor);
/*!
* \brief Tells if the wallet file is deprecated.
*/
@@ -514,6 +608,8 @@ namespace tools
bool testnet() const { return m_testnet; }
bool restricted() const { return m_restricted; }
bool watch_only() const { return m_watch_only; }
+ bool multisig(bool *ready = NULL, uint32_t *threshold = NULL, uint32_t *total = NULL) const;
+ bool has_multisig_partial_key_images() const;
// locked & unlocked balance of given or current subaddress account
uint64_t balance(uint32_t subaddr_index_major) const;
@@ -536,11 +632,15 @@ namespace tools
uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx);
void transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::vector<size_t>& selected_transfers, size_t fake_outputs_count,
std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs,
- uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx);
+ uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx, bool bulletproof);
void commit_tx(pending_tx& ptx_vector);
void commit_tx(std::vector<pending_tx>& ptx_vector);
bool save_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename);
+ std::string save_multisig_tx(multisig_tx_set txs);
+ bool save_multisig_tx(const multisig_tx_set &txs, const std::string &filename);
+ std::string save_multisig_tx(const std::vector<pending_tx>& ptx_vector);
+ bool save_multisig_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename);
// load unsigned tx from file and sign it. Takes confirmation callback as argument. Used by the cli wallet
bool sign_tx(const std::string &unsigned_filename, const std::string &signed_filename, std::vector<wallet2::pending_tx> &ptx, std::function<bool(const unsigned_tx_set&)> accept_func = NULL, bool export_raw = false);
// sign unsigned tx. Takes unsigned_tx_set as argument. Used by GUI
@@ -553,6 +653,11 @@ namespace tools
std::vector<wallet2::pending_tx> create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, bool trusted_daemon);
std::vector<wallet2::pending_tx> create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon);
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(cryptonote::blobdata blob, multisig_tx_set &exported_txs, std::function<bool(const multisig_tx_set&)> accept_func = NULL);
+ 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, std::vector<crypto::hash> &txids);
+ bool sign_multisig_tx_to_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;
@@ -726,6 +831,7 @@ namespace tools
bool delete_address_book_row(std::size_t row_id);
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;
void get_hard_fork_info(uint8_t version, uint64_t &earliest_height);
@@ -831,6 +937,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.
@@ -839,13 +950,13 @@ namespace tools
* \param watch_only true to save only view key, false to save both spend and view keys
* \return Whether it was successful.
*/
- bool store_keys(const std::string& keys_file_name, const std::string& password, bool watch_only = false);
+ bool store_keys(const std::string& keys_file_name, const epee::wipeable_string& password, bool watch_only = false);
/*!
* \brief Load wallet information from wallet file.
* \param keys_file_name Name of wallet file
* \param password Password of wallet file
*/
- bool load_keys(const std::string& keys_file_name, const std::string& password);
+ bool load_keys(const std::string& keys_file_name, const epee::wipeable_string& password);
void process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen);
void process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const crypto::hash& bl_id, uint64_t height, const cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices &o_indices);
void detach_blockchain(uint64_t height);
@@ -866,7 +977,6 @@ namespace tools
void check_genesis(const crypto::hash& genesis_hash) const; //throws
bool generate_chacha8_key_from_secret_keys(crypto::chacha8_key &key) const;
crypto::hash get_payment_id(const pending_tx &ptx) const;
- crypto::hash8 get_short_payment_id(const pending_tx &ptx) const;
void check_acc_out_precomp(const cryptonote::tx_out &o, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, size_t i, tx_scan_info_t &tx_scan_info) const;
void parse_block_round(const cryptonote::blobdata &blob, cryptonote::block &bl, crypto::hash &bl_id, bool &error) const;
uint64_t get_upper_transaction_size_limit();
@@ -878,12 +988,16 @@ 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(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);
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();
+ 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;
@@ -912,6 +1026,8 @@ namespace tools
std::unordered_map<std::string, std::string> m_attributes;
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<std::vector<rct::key>> *m_multisig_rescan_k;
std::atomic<bool> m_run;
@@ -923,6 +1039,9 @@ namespace tools
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 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;
+ 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 */
@@ -941,6 +1060,7 @@ namespace tools
bool m_is_initialized;
NodeRPCProxy m_node_rpc_proxy;
std::unordered_set<crypto::hash> m_scanned_pool_txs[2];
+ size_t m_subaddress_lookahead_major, m_subaddress_lookahead_minor;
// Light wallet
bool m_light_wallet; /* sends view key to daemon for scanning */
@@ -958,7 +1078,10 @@ namespace tools
};
}
BOOST_CLASS_VERSION(tools::wallet2, 22)
-BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 8)
+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)
BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 7)
@@ -967,7 +1090,8 @@ BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 17)
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, 2)
+BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 3)
+BOOST_CLASS_VERSION(tools::wallet2::multisig_sig, 0)
namespace boost
{
@@ -1005,6 +1129,12 @@ namespace boost
{
x.m_subaddr_index = {};
}
+ if (ver < 9)
+ {
+ x.m_key_image_partial = false;
+ x.m_multisig_k.clear();
+ x.m_multisig_info.clear();
+ }
}
template <class Archive>
@@ -1078,6 +1208,36 @@ namespace boost
return;
}
a & x.m_subaddr_index;
+ if (ver < 9)
+ {
+ initialize_transfer_details(a, x, ver);
+ return;
+ }
+ a & x.m_multisig_info;
+ a & x.m_multisig_k;
+ a & x.m_key_image_partial;
+ }
+
+ template <class Archive>
+ inline void serialize(Archive &a, tools::wallet2::multisig_info::LR &x, const boost::serialization::version_type ver)
+ {
+ 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)
+ {
+ a & x.m_ptx;
+ a & x.m_signers;
}
template <class Archive>
@@ -1256,6 +1416,16 @@ namespace boost
}
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)
{
a & x.tx;
@@ -1283,6 +1453,9 @@ namespace boost
if (ver < 2)
return;
a & x.selected_transfers;
+ if (ver < 3)
+ return;
+ a & x.multisig_sigs;
}
}
}
@@ -1358,6 +1531,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;
@@ -1460,6 +1635,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;
+ src.multisig_kLRki = rct::multisig_kLRki({rct::zero(), rct::zero(), rct::zero(), rct::zero()});
detail::print_source_entry(src);
++i;
}
@@ -1486,7 +1662,8 @@ namespace tools
crypto::secret_key tx_key;
std::vector<crypto::secret_key> additional_tx_keys;
- bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys);
+ rct::multisig_out msout;
+ bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, false, m_multisig ? &msout : NULL);
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);
diff --git a/src/wallet/wallet_args.cpp b/src/wallet/wallet_args.cpp
index e665042d4..2273f14ad 100644
--- a/src/wallet/wallet_args.cpp
+++ b/src/wallet/wallet_args.cpp
@@ -28,6 +28,7 @@
#include "wallet/wallet_args.h"
#include <boost/filesystem/path.hpp>
+#include <boost/filesystem/operations.hpp>
#include <boost/format.hpp>
#include "common/i18n.h"
#include "common/util.h"
@@ -84,6 +85,7 @@ namespace wallet_args
boost::optional<boost::program_options::variables_map> main(
int argc, char** argv,
const char* const usage,
+ const char* const notice,
boost::program_options::options_description desc_params,
const boost::program_options::positional_options_description& positional_options,
const std::function<void(const std::string&, bool)> &print,
@@ -178,6 +180,9 @@ namespace wallet_args
mlog_set_log(command_line::get_arg(vm, arg_log_level).c_str());
}
+ if (notice)
+ Print(print) << notice << ENDL;
+
if (!command_line::is_arg_defaulted(vm, arg_max_concurrency))
tools::set_max_concurrency(command_line::get_arg(vm, arg_max_concurrency));
diff --git a/src/wallet/wallet_args.h b/src/wallet/wallet_args.h
index 8974098ad..212958988 100644
--- a/src/wallet/wallet_args.h
+++ b/src/wallet/wallet_args.h
@@ -48,6 +48,7 @@ namespace wallet_args
boost::optional<boost::program_options::variables_map> main(
int argc, char** argv,
const char* const usage,
+ const char* const notice,
boost::program_options::options_description desc_params,
const boost::program_options::positional_options_description& positional_options,
const std::function<void(const std::string&, bool)> &print,
diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h
index 48fce40dd..234c22d85 100644
--- a/src/wallet/wallet_errors.h
+++ b/src/wallet/wallet_errors.h
@@ -50,6 +50,8 @@ namespace tools
// wallet_internal_error
// unexpected_txin_type
// wallet_not_initialized
+ // multisig_export_needed
+ // multisig_import_needed
// std::logic_error
// wallet_logic_error *
// file_exists
@@ -186,7 +188,22 @@ namespace tools
{
}
};
-
+ //----------------------------------------------------------------------------------------------------
+ struct multisig_export_needed : public wallet_runtime_error
+ {
+ explicit multisig_export_needed(std::string&& loc)
+ : wallet_runtime_error(std::move(loc), "This signature was made with stale data: export fresh multisig data, which other participants must then use")
+ {
+ }
+ };
+ //----------------------------------------------------------------------------------------------------
+ struct multisig_import_needed : public wallet_runtime_error
+ {
+ explicit multisig_import_needed(std::string&& loc)
+ : wallet_runtime_error(std::move(loc), "Not enough multisig data was found to sign: import multisig data from more other participants")
+ {
+ }
+ };
//----------------------------------------------------------------------------------------------------
const char* const file_error_messages[] = {
"file already exists",
diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp
index c315684de..0482b9dd6 100644
--- a/src/wallet/wallet_rpc_server.cpp
+++ b/src/wallet/wallet_rpc_server.cpp
@@ -41,6 +41,7 @@ using namespace epee;
#include "common/i18n.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "cryptonote_basic/account.h"
+#include "multisig/multisig.h"
#include "wallet_rpc_server_commands_defs.h"
#include "misc_language.h"
#include "string_coding.h"
@@ -206,7 +207,8 @@ namespace tools
}
std::fputs(http_login->username.c_str(), rpc_login_file.handle());
std::fputc(':', rpc_login_file.handle());
- std::fputs(http_login->password.c_str(), rpc_login_file.handle());
+ const epee::wipeable_string password = http_login->password;
+ std::fwrite(password.data(), 1, password.size(), rpc_login_file.handle());
std::fflush(rpc_login_file.handle());
if (std::ferror(rpc_login_file.handle()))
{
@@ -323,6 +325,7 @@ namespace tools
{
res.balance = m_wallet->balance(req.account_index);
res.unlocked_balance = m_wallet->unlocked_balance(req.account_index);
+ res.multisig_import_needed = m_wallet->multisig() && m_wallet->has_multisig_partial_key_images();
std::map<uint32_t, uint64_t> balance_per_subaddress = m_wallet->balance_per_subaddress(req.account_index);
std::map<uint32_t, uint64_t> unlocked_balance_per_subaddress = m_wallet->unlocked_balance_per_subaddress(req.account_index);
std::vector<tools::wallet2::transfer_details> transfers;
@@ -607,11 +610,6 @@ namespace tools
return false;
}
- if (!req.do_not_relay)
- m_wallet->commit_tx(ptx_vector);
-
- // populate response with tx hash
- res.tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx_vector.back().tx));
if (req.get_tx_key)
{
res.tx_key = epee::string_tools::pod_to_hex(ptx_vector.back().tx_key);
@@ -620,18 +618,45 @@ namespace tools
}
res.fee = ptx_vector.back().fee;
- if (req.get_tx_hex)
+ if (m_wallet->multisig())
{
- cryptonote::blobdata blob;
- tx_to_blob(ptx_vector.back().tx, blob);
- res.tx_blob = epee::string_tools::buff_to_hex_nodelimer(blob);
+ res.multisig_txset = epee::string_tools::buff_to_hex_nodelimer(m_wallet->save_multisig_tx(ptx_vector));
+ if (res.multisig_txset.empty())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "Failed to save multisig tx set after creation";
+ return false;
+ }
}
- if (req.get_tx_metadata)
+ else
{
- std::ostringstream oss;
- binary_archive<true> ar(oss);
- ::serialization::serialize(ar, ptx_vector.back());
- res.tx_metadata = epee::string_tools::buff_to_hex_nodelimer(oss.str());
+ if (!req.do_not_relay)
+ m_wallet->commit_tx(ptx_vector);
+
+ // populate response with tx hash
+ res.tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx_vector.back().tx));
+ if (req.get_tx_hex)
+ {
+ cryptonote::blobdata blob;
+ tx_to_blob(ptx_vector.back().tx, blob);
+ res.tx_blob = epee::string_tools::buff_to_hex_nodelimer(blob);
+ }
+ if (req.get_tx_metadata)
+ {
+ std::ostringstream oss;
+ boost::archive::portable_binary_oarchive ar(oss);
+ try
+ {
+ ar << ptx_vector.back();
+ }
+ catch (...)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "Failed to save multisig tx set after creation";
+ return false;
+ }
+ res.tx_metadata = epee::string_tools::buff_to_hex_nodelimer(oss.str());
+ }
}
return true;
}
@@ -672,17 +697,9 @@ namespace tools
ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, req.priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon);
LOG_PRINT_L2("on_transfer_split called create_transactions_2");
- if (!req.do_not_relay)
- {
- LOG_PRINT_L2("on_transfer_split calling commit_tx");
- m_wallet->commit_tx(ptx_vector);
- LOG_PRINT_L2("on_transfer_split called commit_tx");
- }
-
// populate response with tx hashes
for (const auto & ptx : ptx_vector)
{
- res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)));
if (req.get_tx_keys)
{
res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key));
@@ -696,19 +713,56 @@ namespace tools
res.amount_list.push_back(ptx_amount);
res.fee_list.push_back(ptx.fee);
+ }
- if (req.get_tx_hex)
+ if (m_wallet->multisig())
+ {
+ res.multisig_txset = epee::string_tools::buff_to_hex_nodelimer(m_wallet->save_multisig_tx(ptx_vector));
+ if (res.multisig_txset.empty())
{
- cryptonote::blobdata blob;
- tx_to_blob(ptx.tx, blob);
- res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob));
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "Failed to save multisig tx set after creation";
+ return false;
}
- if (req.get_tx_metadata)
+ }
+
+ // populate response with tx hashes
+ for (const auto & ptx : ptx_vector)
+ {
+ if (!req.do_not_relay)
{
- std::ostringstream oss;
- binary_archive<true> ar(oss);
- ::serialization::serialize(ar, const_cast<tools::wallet2::pending_tx&>(ptx));
- res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str()));
+ LOG_PRINT_L2("on_transfer_split calling commit_tx");
+ m_wallet->commit_tx(ptx_vector);
+ LOG_PRINT_L2("on_transfer_split called commit_tx");
+ }
+
+ // populate response with tx hashes
+ for (auto & ptx : ptx_vector)
+ {
+ res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)));
+
+ if (req.get_tx_hex)
+ {
+ cryptonote::blobdata blob;
+ tx_to_blob(ptx.tx, blob);
+ res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob));
+ }
+ if (req.get_tx_metadata)
+ {
+ std::ostringstream oss;
+ boost::archive::portable_binary_oarchive ar(oss);
+ try
+ {
+ ar << ptx;
+ }
+ catch (...)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "Failed to save multisig tx set after creation";
+ return false;
+ }
+ res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str()));
+ }
}
}
@@ -736,30 +790,65 @@ namespace tools
{
std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_unmixable_sweep_transactions(m_trusted_daemon);
- if (!req.do_not_relay)
- m_wallet->commit_tx(ptx_vector);
-
- // populate response with tx hashes
for (const auto & ptx : ptx_vector)
{
- res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)));
if (req.get_tx_keys)
{
res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key));
}
res.fee_list.push_back(ptx.fee);
- if (req.get_tx_hex)
+ }
+
+ if (m_wallet->multisig())
+ {
+ for (tools::wallet2::pending_tx &ptx: ptx_vector)
{
- cryptonote::blobdata blob;
- tx_to_blob(ptx.tx, blob);
- res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob));
+ std::ostringstream oss;
+ boost::archive::portable_binary_oarchive ar(oss);
+ try
+ {
+ ar << ptx;
+ }
+ catch (...)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "Failed to save multisig tx set after creation";
+ return false;
+ }
+ res.multisig_txset.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str()));
}
- if (req.get_tx_metadata)
+ }
+ else
+ {
+ if (!req.do_not_relay)
+ m_wallet->commit_tx(ptx_vector);
+
+ // populate response with tx hashes
+ for (auto & ptx : ptx_vector)
{
- std::ostringstream oss;
- binary_archive<true> ar(oss);
- ::serialization::serialize(ar, const_cast<tools::wallet2::pending_tx&>(ptx));
- res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str()));
+ res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)));
+ if (req.get_tx_hex)
+ {
+ cryptonote::blobdata blob;
+ tx_to_blob(ptx.tx, blob);
+ res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob));
+ }
+ if (req.get_tx_metadata)
+ {
+ std::ostringstream oss;
+ boost::archive::portable_binary_oarchive ar(oss);
+ try
+ {
+ ar << ptx;
+ }
+ catch (...)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "Failed to save multisig tx set after creation";
+ return false;
+ }
+ res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str()));
+ }
}
}
@@ -801,29 +890,64 @@ namespace tools
uint64_t mixin = m_wallet->adjust_mixin(req.mixin);
std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_all(req.below_amount, dsts[0].addr, dsts[0].is_subaddress, mixin, req.unlock_time, req.priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon);
- if (!req.do_not_relay)
- m_wallet->commit_tx(ptx_vector);
-
- // populate response with tx hashes
for (const auto & ptx : ptx_vector)
{
- res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)));
if (req.get_tx_keys)
{
res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key));
}
- if (req.get_tx_hex)
+ }
+
+ if (m_wallet->multisig())
+ {
+ for (tools::wallet2::pending_tx &ptx: ptx_vector)
{
- cryptonote::blobdata blob;
- tx_to_blob(ptx.tx, blob);
- res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob));
+ std::ostringstream oss;
+ boost::archive::portable_binary_oarchive ar(oss);
+ try
+ {
+ ar << ptx;
+ }
+ catch (...)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "Failed to save multisig tx set after creation";
+ return false;
+ }
+ res.multisig_txset.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str()));
}
- if (req.get_tx_metadata)
+ }
+ else
+ {
+ if (!req.do_not_relay)
+ m_wallet->commit_tx(ptx_vector);
+
+ // populate response with tx hashes
+ for (auto & ptx : ptx_vector)
{
- std::ostringstream oss;
- binary_archive<true> ar(oss);
- ::serialization::serialize(ar, const_cast<tools::wallet2::pending_tx&>(ptx));
- res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str()));
+ res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)));
+ if (req.get_tx_hex)
+ {
+ cryptonote::blobdata blob;
+ tx_to_blob(ptx.tx, blob);
+ res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob));
+ }
+ if (req.get_tx_metadata)
+ {
+ std::ostringstream oss;
+ boost::archive::portable_binary_oarchive ar(oss);
+ try
+ {
+ ar << ptx;
+ }
+ catch (...)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "Failed to save multisig tx set after creation";
+ return false;
+ }
+ res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str()));
+ }
}
}
@@ -885,37 +1009,59 @@ namespace tools
er.message = "Multiple transactions are created, which is not supposed to happen";
return false;
}
- if (ptx_vector[0].selected_transfers.size() > 1)
+ const wallet2::pending_tx &ptx = ptx_vector[0];
+ if (ptx.selected_transfers.size() > 1)
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
er.message = "The transaction uses multiple inputs, which is not supposed to happen";
return false;
}
- if (!req.do_not_relay)
- m_wallet->commit_tx(ptx_vector);
-
- // populate response with tx hashes
- const wallet2::pending_tx &ptx = ptx_vector[0];
- res.tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx));
if (req.get_tx_key)
{
res.tx_key = epee::string_tools::pod_to_hex(ptx.tx_key);
}
- if (req.get_tx_hex)
+
+ if (m_wallet->multisig())
{
- cryptonote::blobdata blob;
- tx_to_blob(ptx.tx, blob);
- res.tx_blob = epee::string_tools::buff_to_hex_nodelimer(blob);
+ res.multisig_txset = epee::string_tools::buff_to_hex_nodelimer(m_wallet->save_multisig_tx(ptx_vector));
+ if (res.multisig_txset.empty())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "Failed to save multisig tx set after creation";
+ return false;
+ }
}
- if (req.get_tx_metadata)
+ else
{
- std::ostringstream oss;
- binary_archive<true> ar(oss);
- ::serialization::serialize(ar, const_cast<tools::wallet2::pending_tx&>(ptx));
- res.tx_metadata = epee::string_tools::buff_to_hex_nodelimer(oss.str());
- }
+ if (!req.do_not_relay)
+ m_wallet->commit_tx(ptx_vector);
+ // populate response with tx hashes
+ res.tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx));
+ if (req.get_tx_hex)
+ {
+ cryptonote::blobdata blob;
+ tx_to_blob(ptx.tx, blob);
+ res.tx_blob = epee::string_tools::buff_to_hex_nodelimer(blob);
+ }
+ if (req.get_tx_metadata)
+ {
+ std::ostringstream oss;
+ boost::archive::portable_binary_oarchive ar(oss);
+ try
+ {
+ ar << ptx;
+ }
+ catch (...)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "Failed to save multisig tx set after creation";
+ return false;
+ }
+ res.tx_metadata = epee::string_tools::buff_to_hex_nodelimer(oss.str());
+ }
+ }
return true;
}
catch (const tools::error::daemon_busy& e)
@@ -951,13 +1097,14 @@ namespace tools
return false;
}
- std::stringstream ss;
- ss << blob;
- binary_archive<false> ba(ss);
-
tools::wallet2::pending_tx ptx;
- bool r = ::serialization::serialize(ba, ptx);
- if (!r)
+ try
+ {
+ std::istringstream iss(blob);
+ boost::archive::portable_binary_iarchive ar(iss);
+ ar >> ptx;
+ }
+ catch (...)
{
er.code = WALLET_RPC_ERROR_CODE_BAD_TX_METADATA;
er.message = "Failed to parse tx metadata.";
@@ -2337,6 +2484,375 @@ namespace tools
}
}
//------------------------------------------------------------------------------------------------------------------------------
+ bool wallet_rpc_server::on_is_multisig(const wallet_rpc::COMMAND_RPC_IS_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_IS_MULTISIG::response& res, epee::json_rpc::error& er)
+ {
+ if (!m_wallet) return not_open(er);
+ res.multisig = m_wallet->multisig(&res.ready, &res.threshold, &res.total);
+ return true;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
+ bool wallet_rpc_server::on_prepare_multisig(const wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_PREPARE_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;
+ }
+ if (m_wallet->multisig())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_ALREADY_MULTISIG;
+ er.message = "This wallet is already multisig";
+ return false;
+ }
+ if (m_wallet->watch_only())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY;
+ er.message = "wallet is watch-only and cannot be made multisig";
+ return false;
+ }
+
+ res.multisig_info = m_wallet->get_multisig_info();
+ return true;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
+ bool wallet_rpc_server::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)
+ {
+ 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;
+ }
+ if (m_wallet->multisig())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_ALREADY_MULTISIG;
+ er.message = "This wallet is already multisig";
+ return false;
+ }
+ if (m_wallet->watch_only())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY;
+ er.message = "wallet is watch-only and cannot be made multisig";
+ return false;
+ }
+
+ try
+ {
+ res.multisig_info = m_wallet->make_multisig(req.password, req.multisig_info, req.threshold);
+ res.address = m_wallet->get_account().get_public_address_str(m_wallet->testnet());
+ }
+ catch (const std::exception &e)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = e.what();
+ return false;
+ }
+
+ return true;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
+ bool wallet_rpc_server::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)
+ {
+ 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;
+ }
+ bool ready;
+ if (!m_wallet->multisig(&ready))
+ {
+ er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG;
+ er.message = "This wallet is not multisig";
+ return false;
+ }
+ if (!ready)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG;
+ er.message = "This wallet is multisig, but not yet finalized";
+ return false;
+ }
+
+ cryptonote::blobdata info;
+ try
+ {
+ info = m_wallet->export_multisig();
+ }
+ catch (const std::exception &e)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = e.what();
+ return false;
+ }
+
+ res.info = epee::string_tools::buff_to_hex_nodelimer(info);
+
+ return true;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
+ bool wallet_rpc_server::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)
+ {
+ 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;
+ }
+ bool ready;
+ uint32_t threshold, total;
+ if (!m_wallet->multisig(&ready, &threshold, &total))
+ {
+ er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG;
+ er.message = "This wallet is not multisig";
+ return false;
+ }
+ if (!ready)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG;
+ er.message = "This wallet is multisig, but not yet finalized";
+ return false;
+ }
+
+ if (req.info.size() < threshold - 1)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED;
+ er.message = "Needs multisig export info from more participants";
+ return false;
+ }
+
+ std::vector<cryptonote::blobdata> info;
+ info.resize(req.info.size());
+ for (size_t n = 0; n < info.size(); ++n)
+ {
+ if (!epee::string_tools::parse_hexstr_to_binbuff(req.info[n], info[n]))
+ {
+ er.code = WALLET_RPC_ERROR_CODE_BAD_HEX;
+ er.message = "Failed to parse hex.";
+ return false;
+ }
+ }
+
+ try
+ {
+ res.n_outputs = m_wallet->import_multisig(info);
+ }
+ catch (const std::exception &e)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "Error calling import_multisig";
+ return false;
+ }
+
+ if (m_trusted_daemon)
+ {
+ try
+ {
+ m_wallet->rescan_spent();
+ }
+ catch (const std::exception &e)
+ {
+ er.message = std::string("Success, but failed to update spent status after import multisig info: ") + e.what();
+ }
+ }
+ else
+ {
+ er.message = "Success, but cannot update spent status after import multisig info as dameon is untrusted";
+ }
+
+ 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;
+ }
+ bool ready;
+ uint32_t threshold, total;
+ if (!m_wallet->multisig(&ready, &threshold, &total))
+ {
+ er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG;
+ er.message = "This wallet is not multisig";
+ return false;
+ }
+ if (ready)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_ALREADY_MULTISIG;
+ er.message = "This wallet is multisig, and already finalized";
+ 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;
+ }
+
+ try
+ {
+ if (!m_wallet->finalize_multisig(req.password, req.multisig_info))
+ {
+ 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;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
+ bool wallet_rpc_server::on_sign_multisig(const wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SIGN_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;
+ }
+ bool ready;
+ uint32_t threshold, total;
+ if (!m_wallet->multisig(&ready, &threshold, &total))
+ {
+ er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG;
+ er.message = "This wallet is not multisig";
+ return false;
+ }
+ if (!ready)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG;
+ er.message = "This wallet is multisig, but not yet finalized";
+ return false;
+ }
+
+ cryptonote::blobdata blob;
+ if (!epee::string_tools::parse_hexstr_to_binbuff(req.tx_data_hex, blob))
+ {
+ er.code = WALLET_RPC_ERROR_CODE_BAD_HEX;
+ er.message = "Failed to parse hex.";
+ return false;
+ }
+
+ tools::wallet2::multisig_tx_set txs;
+ bool r = m_wallet->load_multisig_tx(blob, txs, NULL);
+ if (!r)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_BAD_MULTISIG_TX_DATA;
+ er.message = "Failed to parse multisig tx data.";
+ return false;
+ }
+
+ std::vector<crypto::hash> txids;
+ try
+ {
+ bool r = m_wallet->sign_multisig_tx(txs, txids);
+ if (!r)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_MULTISIG_SIGNATURE;
+ er.message = "Failed to sign multisig tx";
+ return false;
+ }
+ }
+ catch (const std::exception &e)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_MULTISIG_SIGNATURE;
+ er.message = std::string("Failed to sign multisig tx: ") + e.what();
+ return false;
+ }
+
+ res.tx_data_hex = epee::string_tools::buff_to_hex_nodelimer(m_wallet->save_multisig_tx(txs));
+ if (!txids.empty())
+ {
+ for (const crypto::hash &txid: txids)
+ res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(txid));
+ }
+
+ return true;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
+ bool wallet_rpc_server::on_submit_multisig(const wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SUBMIT_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;
+ }
+ bool ready;
+ uint32_t threshold, total;
+ if (!m_wallet->multisig(&ready, &threshold, &total))
+ {
+ er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG;
+ er.message = "This wallet is not multisig";
+ return false;
+ }
+ if (!ready)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG;
+ er.message = "This wallet is multisig, but not yet finalized";
+ return false;
+ }
+
+ cryptonote::blobdata blob;
+ if (!epee::string_tools::parse_hexstr_to_binbuff(req.tx_data_hex, blob))
+ {
+ er.code = WALLET_RPC_ERROR_CODE_BAD_HEX;
+ er.message = "Failed to parse hex.";
+ return false;
+ }
+
+ tools::wallet2::multisig_tx_set txs;
+ bool r = m_wallet->load_multisig_tx(blob, txs, NULL);
+ if (!r)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_BAD_MULTISIG_TX_DATA;
+ er.message = "Failed to parse multisig tx data.";
+ return false;
+ }
+
+ if (txs.m_signers.size() < threshold)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED;
+ er.message = "Not enough signers signed this transaction.";
+ return false;
+ }
+
+ try
+ {
+ for (auto &ptx: txs.m_ptx)
+ {
+ m_wallet->commit_tx(ptx);
+ res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)));
+ }
+ }
+ catch (const std::exception &e)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_MULTISIG_SUBMISSION;
+ er.message = std::string("Failed to submit multisig tx: ") + e.what();
+ return false;
+ }
+
+ return true;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
}
int main(int argc, char** argv) {
@@ -2359,6 +2875,7 @@ int main(int argc, char** argv) {
const auto vm = wallet_args::main(
argc, argv,
"monero-wallet-rpc [--wallet-file=<file>|--generate-from-json=<file>|--wallet-dir=<directory>] [--rpc-bind-port=<port>]",
+ tools::wallet_rpc_server::tr("This is the RPC monero wallet. It needs to connect to a monero\ndaemon to work correctly."),
desc_params,
po::positional_options_description(),
[](const std::string &s, bool emphasis){ epee::set_console_color(emphasis ? epee::console_color_white : epee::console_color_default, true); std::cout << s << std::endl; if (emphasis) epee::reset_console_color(); },
diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h
index 9455c4769..79f589623 100644
--- a/src/wallet/wallet_rpc_server.h
+++ b/src/wallet/wallet_rpc_server.h
@@ -117,6 +117,14 @@ namespace tools
MAP_JON_RPC_WE("get_languages", on_get_languages, wallet_rpc::COMMAND_RPC_GET_LANGUAGES)
MAP_JON_RPC_WE("create_wallet", on_create_wallet, wallet_rpc::COMMAND_RPC_CREATE_WALLET)
MAP_JON_RPC_WE("open_wallet", on_open_wallet, wallet_rpc::COMMAND_RPC_OPEN_WALLET)
+ MAP_JON_RPC_WE("is_multisig", on_is_multisig, wallet_rpc::COMMAND_RPC_IS_MULTISIG)
+ MAP_JON_RPC_WE("prepare_multisig", on_prepare_multisig, wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG)
+ MAP_JON_RPC_WE("make_multisig", on_make_multisig, wallet_rpc::COMMAND_RPC_MAKE_MULTISIG)
+ MAP_JON_RPC_WE("export_multisig_info", on_export_multisig, wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG)
+ MAP_JON_RPC_WE("import_multisig_info", on_import_multisig, wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG)
+ MAP_JON_RPC_WE("finalize_multisig", on_finalize_multisig, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG)
+ MAP_JON_RPC_WE("sign_multisig", on_sign_multisig, wallet_rpc::COMMAND_RPC_SIGN_MULTISIG)
+ MAP_JON_RPC_WE("submit_multisig", on_submit_multisig, wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG)
END_JSON_RPC_MAP()
END_URI_MAP2()
@@ -171,6 +179,14 @@ namespace tools
bool on_get_languages(const wallet_rpc::COMMAND_RPC_GET_LANGUAGES::request& req, wallet_rpc::COMMAND_RPC_GET_LANGUAGES::response& res, epee::json_rpc::error& er);
bool on_create_wallet(const wallet_rpc::COMMAND_RPC_CREATE_WALLET::request& req, wallet_rpc::COMMAND_RPC_CREATE_WALLET::response& res, epee::json_rpc::error& er);
bool on_open_wallet(const wallet_rpc::COMMAND_RPC_OPEN_WALLET::request& req, wallet_rpc::COMMAND_RPC_OPEN_WALLET::response& res, epee::json_rpc::error& er);
+ bool on_is_multisig(const wallet_rpc::COMMAND_RPC_IS_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_IS_MULTISIG::response& res, epee::json_rpc::error& er);
+ bool on_prepare_multisig(const wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG::response& res, epee::json_rpc::error& er);
+ 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);
+ bool on_sign_multisig(const wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::response& res, epee::json_rpc::error& er);
+ bool on_submit_multisig(const wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SUBMIT_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);
diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h
index e084d9e6d..57cc01e27 100644
--- a/src/wallet/wallet_rpc_server_commands_defs.h
+++ b/src/wallet/wallet_rpc_server_commands_defs.h
@@ -78,11 +78,13 @@ namespace wallet_rpc
{
uint64_t balance;
uint64_t unlocked_balance;
+ bool multisig_import_needed;
std::vector<per_subaddress_info> per_subaddress;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(balance)
KV_SERIALIZE(unlocked_balance)
+ KV_SERIALIZE(multisig_import_needed)
KV_SERIALIZE(per_subaddress)
END_KV_SERIALIZE_MAP()
};
@@ -316,6 +318,7 @@ namespace wallet_rpc
uint64_t fee;
std::string tx_blob;
std::string tx_metadata;
+ std::string multisig_txset;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(tx_hash)
@@ -324,6 +327,7 @@ namespace wallet_rpc
KV_SERIALIZE(fee)
KV_SERIALIZE(tx_blob)
KV_SERIALIZE(tx_metadata)
+ KV_SERIALIZE(multisig_txset)
END_KV_SERIALIZE_MAP()
};
};
@@ -376,6 +380,7 @@ namespace wallet_rpc
std::list<uint64_t> fee_list;
std::list<std::string> tx_blob_list;
std::list<std::string> tx_metadata_list;
+ std::string multisig_txset;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(tx_hash_list)
@@ -384,6 +389,7 @@ namespace wallet_rpc
KV_SERIALIZE(fee_list)
KV_SERIALIZE(tx_blob_list)
KV_SERIALIZE(tx_metadata_list)
+ KV_SERIALIZE(multisig_txset)
END_KV_SERIALIZE_MAP()
};
};
@@ -421,6 +427,7 @@ namespace wallet_rpc
std::list<uint64_t> fee_list;
std::list<std::string> tx_blob_list;
std::list<std::string> tx_metadata_list;
+ std::list<std::string> multisig_txset;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(tx_hash_list)
@@ -428,6 +435,7 @@ namespace wallet_rpc
KV_SERIALIZE(fee_list)
KV_SERIALIZE(tx_blob_list)
KV_SERIALIZE(tx_metadata_list)
+ KV_SERIALIZE(multisig_txset)
END_KV_SERIALIZE_MAP()
};
};
@@ -481,6 +489,7 @@ namespace wallet_rpc
std::list<uint64_t> fee_list;
std::list<std::string> tx_blob_list;
std::list<std::string> tx_metadata_list;
+ std::list<std::string> multisig_txset;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(tx_hash_list)
@@ -488,6 +497,7 @@ namespace wallet_rpc
KV_SERIALIZE(fee_list)
KV_SERIALIZE(tx_blob_list)
KV_SERIALIZE(tx_metadata_list)
+ KV_SERIALIZE(multisig_txset)
END_KV_SERIALIZE_MAP()
};
};
@@ -528,6 +538,7 @@ namespace wallet_rpc
uint64_t fee;
std::string tx_blob;
std::string tx_metadata;
+ std::string multisig_txset;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(tx_hash)
@@ -535,6 +546,7 @@ namespace wallet_rpc
KV_SERIALIZE(fee)
KV_SERIALIZE(tx_blob)
KV_SERIALIZE(tx_metadata)
+ KV_SERIALIZE(multisig_txset)
END_KV_SERIALIZE_MAP()
};
};
@@ -1485,5 +1497,181 @@ namespace wallet_rpc
END_KV_SERIALIZE_MAP()
};
};
+
+ struct COMMAND_RPC_IS_MULTISIG
+ {
+ struct request
+ {
+ BEGIN_KV_SERIALIZE_MAP()
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct response
+ {
+ bool multisig;
+ bool ready;
+ uint32_t threshold;
+ uint32_t total;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(multisig)
+ KV_SERIALIZE(ready)
+ KV_SERIALIZE(threshold)
+ KV_SERIALIZE(total)
+ END_KV_SERIALIZE_MAP()
+ };
+ };
+
+ struct COMMAND_RPC_PREPARE_MULTISIG
+ {
+ struct request
+ {
+ BEGIN_KV_SERIALIZE_MAP()
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct response
+ {
+ std::string multisig_info;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(multisig_info)
+ END_KV_SERIALIZE_MAP()
+ };
+ };
+
+ struct COMMAND_RPC_MAKE_MULTISIG
+ {
+ struct request
+ {
+ std::vector<std::string> multisig_info;
+ uint32_t threshold;
+ std::string password;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(multisig_info)
+ KV_SERIALIZE(threshold)
+ KV_SERIALIZE(password)
+ END_KV_SERIALIZE_MAP()
+ };
+
+ 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 COMMAND_RPC_EXPORT_MULTISIG
+ {
+ struct request
+ {
+ BEGIN_KV_SERIALIZE_MAP()
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct response
+ {
+ std::string info;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(info)
+ END_KV_SERIALIZE_MAP()
+ };
+ };
+
+ struct COMMAND_RPC_IMPORT_MULTISIG
+ {
+ struct request
+ {
+ std::vector<std::string> info;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(info)
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct response
+ {
+ uint64_t n_outputs;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(n_outputs)
+ 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()
+ };
+ };
+
+ struct COMMAND_RPC_SIGN_MULTISIG
+ {
+ struct request
+ {
+ std::string tx_data_hex;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(tx_data_hex)
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct response
+ {
+ std::string tx_data_hex;
+ std::list<std::string> tx_hash_list;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(tx_data_hex)
+ KV_SERIALIZE(tx_hash_list)
+ END_KV_SERIALIZE_MAP()
+ };
+ };
+
+ struct COMMAND_RPC_SUBMIT_MULTISIG
+ {
+ struct request
+ {
+ std::string tx_data_hex;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(tx_data_hex)
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct response
+ {
+ std::list<std::string> tx_hash_list;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(tx_hash_list)
+ END_KV_SERIALIZE_MAP()
+ };
+ };
+
}
}
diff --git a/src/wallet/wallet_rpc_server_error_codes.h b/src/wallet/wallet_rpc_server_error_codes.h
index c3f3e20d1..578413e38 100644
--- a/src/wallet/wallet_rpc_server_error_codes.h
+++ b/src/wallet/wallet_rpc_server_error_codes.h
@@ -58,3 +58,12 @@
#define WALLET_RPC_ERROR_CODE_WRONG_KEY -25
#define WALLET_RPC_ERROR_CODE_BAD_HEX -26
#define WALLET_RPC_ERROR_CODE_BAD_TX_METADATA -27
+#define WALLET_RPC_ERROR_CODE_ALREADY_MULTISIG -28
+#define WALLET_RPC_ERROR_CODE_WATCH_ONLY -29
+#define WALLET_RPC_ERROR_CODE_BAD_MULTISIG_INFO -30
+#define WALLET_RPC_ERROR_CODE_NOT_MULTISIG -31
+#define WALLET_RPC_ERROR_CODE_WRONG_LR -32
+#define WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED -33
+#define WALLET_RPC_ERROR_CODE_BAD_MULTISIG_TX_DATA -34
+#define WALLET_RPC_ERROR_CODE_MULTISIG_SIGNATURE -35
+#define WALLET_RPC_ERROR_CODE_MULTISIG_SUBMISSION -36