aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/blockchain_db/blockchain_db.h16
-rw-r--r--src/blockchain_db/lmdb/db_lmdb.cpp126
-rw-r--r--src/blockchain_db/lmdb/db_lmdb.h1
-rw-r--r--src/blocks/checkpoints.datbin272772 -> 332676 bytes
-rw-r--r--src/checkpoints/checkpoints.cpp1
-rw-r--r--src/common/dns_utils.cpp3
-rw-r--r--src/common/threadpool.h6
-rw-r--r--src/common/util.cpp19
-rw-r--r--src/common/util.h20
-rw-r--r--src/crypto/hash-extra-jh.c6
-rw-r--r--src/crypto/hash-extra-skein.c6
-rw-r--r--src/crypto/keccak.c2
-rw-r--r--src/cryptonote_basic/account.h2
-rw-r--r--src/cryptonote_basic/cryptonote_format_utils.cpp24
-rw-r--r--src/cryptonote_basic/cryptonote_format_utils.h2
-rw-r--r--src/cryptonote_basic/hardfork.h5
-rw-r--r--src/cryptonote_config.h3
-rw-r--r--src/cryptonote_core/CMakeLists.txt1
-rw-r--r--src/cryptonote_core/blockchain.cpp6
-rw-r--r--src/cryptonote_core/blockchain.h14
-rw-r--r--src/cryptonote_core/cryptonote_core.cpp61
-rw-r--r--src/cryptonote_core/cryptonote_core.h14
-rw-r--r--src/cryptonote_core/cryptonote_tx_utils.cpp27
-rw-r--r--src/cryptonote_core/cryptonote_tx_utils.h4
-rw-r--r--src/cryptonote_core/tx_pool.cpp114
-rw-r--r--src/cryptonote_core/tx_pool.h17
-rw-r--r--src/cryptonote_protocol/cryptonote_protocol_handler.h18
-rw-r--r--src/cryptonote_protocol/cryptonote_protocol_handler.inl70
-rw-r--r--src/daemon/main.cpp2
-rw-r--r--src/daemon/rpc_command_executor.cpp2
-rw-r--r--src/device/device.hpp1
-rw-r--r--src/device/device_default.hpp2
-rw-r--r--src/device/device_ledger.cpp91
-rw-r--r--src/device/device_ledger.hpp7
-rw-r--r--src/device/log.cpp4
-rw-r--r--src/device/log.hpp1
-rw-r--r--src/device_trezor/device_trezor.cpp40
-rw-r--r--src/device_trezor/trezor/protocol.cpp225
-rw-r--r--src/device_trezor/trezor/protocol.hpp49
-rw-r--r--src/gen_multisig/gen_multisig.cpp12
-rw-r--r--src/hardforks/hardforks.cpp6
-rw-r--r--src/multisig/CMakeLists.txt5
-rw-r--r--src/multisig/multisig_account.cpp16
-rw-r--r--src/multisig/multisig_account.h21
-rw-r--r--src/multisig/multisig_account_kex_impl.cpp153
-rw-r--r--src/multisig/multisig_clsag_context.cpp257
-rw-r--r--src/multisig/multisig_clsag_context.h137
-rw-r--r--src/multisig/multisig_tx_builder_ringct.cpp1059
-rw-r--r--src/multisig/multisig_tx_builder_ringct.h120
-rw-r--r--src/p2p/net_node.inl18
-rw-r--r--src/ringct/rctSigs.cpp170
-rw-r--r--src/ringct/rctSigs.h19
-rw-r--r--src/rpc/core_rpc_server.cpp60
-rw-r--r--src/rpc/core_rpc_server.h4
-rw-r--r--src/rpc/core_rpc_server_commands_defs.h21
-rw-r--r--src/serialization/tuple.h169
-rw-r--r--src/simplewallet/simplewallet.cpp178
-rw-r--r--src/simplewallet/simplewallet.h8
-rw-r--r--src/version.cpp.in4
-rw-r--r--src/wallet/api/wallet.cpp66
-rw-r--r--src/wallet/api/wallet.h3
-rw-r--r--src/wallet/api/wallet2_api.h10
-rw-r--r--src/wallet/node_rpc_proxy.cpp32
-rw-r--r--src/wallet/node_rpc_proxy.h4
-rw-r--r--src/wallet/wallet2.cpp768
-rw-r--r--src/wallet/wallet2.h148
-rw-r--r--src/wallet/wallet_errors.h10
-rw-r--r--src/wallet/wallet_rpc_payments.cpp2
-rw-r--r--src/wallet/wallet_rpc_server.cpp46
-rw-r--r--src/wallet/wallet_rpc_server_commands_defs.h11
-rw-r--r--src/wallet/wallet_rpc_server_error_codes.h1
71 files changed, 3561 insertions, 989 deletions
diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h
index 263948fa2..a163ef98c 100644
--- a/src/blockchain_db/blockchain_db.h
+++ b/src/blockchain_db/blockchain_db.h
@@ -1883,16 +1883,18 @@ public:
}
virtual ~db_txn_guard()
{
- if (active)
- stop();
+ stop();
}
void stop()
{
- if (readonly)
- db->block_rtxn_stop();
- else
- db->block_wtxn_stop();
- active = false;
+ if (active)
+ {
+ if (readonly)
+ db->block_rtxn_stop();
+ else
+ db->block_wtxn_stop();
+ active = false;
+ }
}
void abort()
{
diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp
index e2ac9df0b..f80013d02 100644
--- a/src/blockchain_db/lmdb/db_lmdb.cpp
+++ b/src/blockchain_db/lmdb/db_lmdb.cpp
@@ -25,13 +25,6 @@
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-#ifndef _WIN32
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/mman.h>
-#include <fcntl.h>
-#endif
-
#include "db_lmdb.h"
#include <boost/filesystem.hpp>
@@ -472,6 +465,32 @@ void mdb_txn_safe::increment_txns(int i)
num_active_txns += i;
}
+#define TXN_PREFIX(flags); \
+ mdb_txn_safe auto_txn; \
+ mdb_txn_safe* txn_ptr = &auto_txn; \
+ if (m_batch_active) \
+ txn_ptr = m_write_txn; \
+ else \
+ { \
+ if (auto mdb_res = lmdb_txn_begin(m_env, NULL, flags, auto_txn)) \
+ throw0(DB_ERROR(lmdb_error(std::string("Failed to create a transaction for the db in ")+__FUNCTION__+": ", mdb_res).c_str())); \
+ } \
+
+#define TXN_PREFIX_RDONLY() \
+ MDB_txn *m_txn; \
+ mdb_txn_cursors *m_cursors; \
+ mdb_txn_safe auto_txn; \
+ bool my_rtxn = block_rtxn_start(&m_txn, &m_cursors); \
+ if (my_rtxn) auto_txn.m_tinfo = m_tinfo.get(); \
+ else auto_txn.uncheck()
+#define TXN_POSTFIX_RDONLY()
+
+#define TXN_POSTFIX_SUCCESS() \
+ do { \
+ if (! m_batch_active) \
+ auto_txn.commit(); \
+ } while(0)
+
void lmdb_resized(MDB_env *env, int isactive)
{
mdb_txn_safe::prevent_new_txns();
@@ -720,21 +739,20 @@ uint64_t BlockchainLMDB::get_estimated_batch_size(uint64_t batch_num_blocks, uin
}
else
{
- MDB_txn *rtxn;
- mdb_txn_cursors *rcurs;
- bool my_rtxn = block_rtxn_start(&rtxn, &rcurs);
- for (uint64_t block_num = block_start; block_num <= block_stop; ++block_num)
{
- // we have access to block weight, which will be greater or equal to block size,
- // so use this as a proxy. If it's too much off, we might have to check actual size,
- // which involves reading more data, so is not really wanted
- size_t block_weight = get_block_weight(block_num);
- total_block_size += block_weight;
- // Track number of blocks being totalled here instead of assuming, in case
- // some blocks were to be skipped for being outliers.
- ++num_blocks_used;
+ TXN_PREFIX_RDONLY();
+ for (uint64_t block_num = block_start; block_num <= block_stop; ++block_num)
+ {
+ // we have access to block weight, which will be greater or equal to block size,
+ // so use this as a proxy. If it's too much off, we might have to check actual size,
+ // which involves reading more data, so is not really wanted
+ size_t block_weight = get_block_weight(block_num);
+ total_block_size += block_weight;
+ // Track number of blocks being totalled here instead of assuming, in case
+ // some blocks were to be skipped for being outliers.
+ ++num_blocks_used;
+ }
}
- if (my_rtxn) block_rtxn_stop();
avg_block_size = total_block_size / (num_blocks_used ? num_blocks_used : 1);
MDEBUG("average block size across recent " << num_blocks_used << " blocks: " << avg_block_size);
}
@@ -1303,26 +1321,6 @@ BlockchainLMDB::BlockchainLMDB(bool batch_transactions): BlockchainDB()
m_hardfork = nullptr;
}
-void BlockchainLMDB::check_mmap_support()
-{
-#ifndef _WIN32
- const boost::filesystem::path mmap_test_file = m_folder / boost::filesystem::unique_path();
- int mmap_test_fd = ::open(mmap_test_file.string().c_str(), O_RDWR | O_CREAT, 0600);
- if (mmap_test_fd < 0)
- throw0(DB_ERROR((std::string("Failed to check for mmap support: open failed: ") + strerror(errno)).c_str()));
- epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([mmap_test_fd, &mmap_test_file]() {
- ::close(mmap_test_fd);
- boost::filesystem::remove(mmap_test_file.string());
- });
- if (write(mmap_test_fd, "mmaptest", 8) != 8)
- throw0(DB_ERROR((std::string("Failed to check for mmap support: write failed: ") + strerror(errno)).c_str()));
- void *mmap_res = mmap(NULL, 8, PROT_READ, MAP_SHARED, mmap_test_fd, 0);
- if (mmap_res == MAP_FAILED)
- throw0(DB_ERROR("This filesystem does not support mmap: use --data-dir to place the blockchain on a filesystem which does"));
- munmap(mmap_res, 8);
-#endif
-}
-
void BlockchainLMDB::open(const std::string& filename, const int db_flags)
{
int result;
@@ -1334,14 +1332,8 @@ void BlockchainLMDB::open(const std::string& filename, const int db_flags)
throw0(DB_OPEN_FAILURE("Attempted to open db, but it's already open"));
boost::filesystem::path direc(filename);
- if (boost::filesystem::exists(direc))
- {
- if (!boost::filesystem::is_directory(direc))
- throw0(DB_OPEN_FAILURE("LMDB needs a directory path, but a file was passed"));
- }
- else
- {
- if (!boost::filesystem::create_directories(direc))
+ if (!boost::filesystem::exists(direc) &&
+ !boost::filesystem::create_directories(direc)) {
throw0(DB_OPEN_FAILURE(std::string("Failed to create directory ").append(filename).c_str()));
}
@@ -1364,9 +1356,6 @@ void BlockchainLMDB::open(const std::string& filename, const int db_flags)
m_folder = filename;
- try { check_mmap_support(); }
- catch(...) { MERROR("Failed to check for mmap support, proceeding"); }
-
#ifdef __OpenBSD__
if ((mdb_flags & MDB_WRITEMAP) == 0) {
MCLOG_RED(el::Level::Info, "global", "Running on OpenBSD: forcing WRITEMAP");
@@ -1714,32 +1703,6 @@ void BlockchainLMDB::unlock()
check_open();
}
-#define TXN_PREFIX(flags); \
- mdb_txn_safe auto_txn; \
- mdb_txn_safe* txn_ptr = &auto_txn; \
- if (m_batch_active) \
- txn_ptr = m_write_txn; \
- else \
- { \
- if (auto mdb_res = lmdb_txn_begin(m_env, NULL, flags, auto_txn)) \
- throw0(DB_ERROR(lmdb_error(std::string("Failed to create a transaction for the db in ")+__FUNCTION__+": ", mdb_res).c_str())); \
- } \
-
-#define TXN_PREFIX_RDONLY() \
- MDB_txn *m_txn; \
- mdb_txn_cursors *m_cursors; \
- mdb_txn_safe auto_txn; \
- bool my_rtxn = block_rtxn_start(&m_txn, &m_cursors); \
- if (my_rtxn) auto_txn.m_tinfo = m_tinfo.get(); \
- else auto_txn.uncheck()
-#define TXN_POSTFIX_RDONLY()
-
-#define TXN_POSTFIX_SUCCESS() \
- do { \
- if (! m_batch_active) \
- auto_txn.commit(); \
- } while(0)
-
// The below two macros are for DB access within block add/remove, whether
// regular batch txn is in use or not. m_write_txn is used as a batch txn, even
@@ -3959,13 +3922,20 @@ void BlockchainLMDB::block_rtxn_stop() const
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
mdb_txn_reset(m_tinfo->m_ti_rtxn);
memset(&m_tinfo->m_ti_rflags, 0, sizeof(m_tinfo->m_ti_rflags));
+ /* cancel out the increment from rtxn_start */
+ mdb_txn_safe::increment_txns(-1);
}
bool BlockchainLMDB::block_rtxn_start() const
{
MDB_txn *mtxn;
mdb_txn_cursors *mcur;
- return block_rtxn_start(&mtxn, &mcur);
+ /* auto_txn is only used for the create gate */
+ mdb_txn_safe auto_txn;
+ bool ret = block_rtxn_start(&mtxn, &mcur);
+ if (ret)
+ auto_txn.increment_txns(1); /* remember there is an active readtxn */
+ return ret;
}
void BlockchainLMDB::block_wtxn_start()
diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h
index 20edab2e9..bdae44948 100644
--- a/src/blockchain_db/lmdb/db_lmdb.h
+++ b/src/blockchain_db/lmdb/db_lmdb.h
@@ -359,7 +359,6 @@ public:
static int compare_string(const MDB_val *a, const MDB_val *b);
private:
- void check_mmap_support();
void do_resize(uint64_t size_increase=0);
bool need_resize(uint64_t threshold_size=0) const;
diff --git a/src/blocks/checkpoints.dat b/src/blocks/checkpoints.dat
index e75e379f2..2ed1d630f 100644
--- a/src/blocks/checkpoints.dat
+++ b/src/blocks/checkpoints.dat
Binary files differ
diff --git a/src/checkpoints/checkpoints.cpp b/src/checkpoints/checkpoints.cpp
index 27e77cae8..330e3653c 100644
--- a/src/checkpoints/checkpoints.cpp
+++ b/src/checkpoints/checkpoints.cpp
@@ -239,6 +239,7 @@ namespace cryptonote
ADD_CHECKPOINT2(2046000, "5e867f0b8baefed9244a681df97fc885d8ab36c3dfcd24c7a3abf3b8ac8b8314", "0x9cb8b6ff2978c6");
ADD_CHECKPOINT2(2092500, "c4e00820c9c7989b49153d5e90ae095a18a11d990e82fcc3be54e6ed785472b5", "0xb4e585a31369cb");
ADD_CHECKPOINT2(2182500, "0d22b5f81982eff21d094af9e821dc2007e6342069e3b1a37b15d97646353124", "0xead4a874083492");
+ ADD_CHECKPOINT2(2661600, "41c9060e8426012238e8a26da26fcb90797436896cc70886a894c2c560bcccf2", "0x2e0d87526ff161f");
return true;
}
diff --git a/src/common/dns_utils.cpp b/src/common/dns_utils.cpp
index 6ab6ff4fe..e00421f87 100644
--- a/src/common/dns_utils.cpp
+++ b/src/common/dns_utils.cpp
@@ -102,7 +102,6 @@ get_builtin_ds(void)
{
static const char * const ds[] =
{
- ". IN DS 19036 8 2 49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5\n",
". IN DS 20326 8 2 E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D\n",
NULL
};
@@ -521,7 +520,7 @@ bool load_txt_records_from_dns(std::vector<std::string> &good_records, const std
// send all requests in parallel
std::deque<bool> avail(dns_urls.size(), false), valid(dns_urls.size(), false);
- tools::threadpool& tpool = tools::threadpool::getInstance();
+ tools::threadpool& tpool = tools::threadpool::getInstanceForIO();
tools::threadpool::waiter waiter(tpool);
for (size_t n = 0; n < dns_urls.size(); ++n)
{
diff --git a/src/common/threadpool.h b/src/common/threadpool.h
index ce1bc5799..53421e18b 100644
--- a/src/common/threadpool.h
+++ b/src/common/threadpool.h
@@ -42,10 +42,14 @@ namespace tools
class threadpool
{
public:
- static threadpool& getInstance() {
+ static threadpool& getInstanceForCompute() {
static threadpool instance;
return instance;
}
+ static threadpool& getInstanceForIO() {
+ static threadpool instance(8);
+ return instance;
+ }
static threadpool *getNewForUnitTests(unsigned max_threads = 0) {
return new threadpool(max_threads);
}
diff --git a/src/common/util.cpp b/src/common/util.cpp
index 89dcf4fef..f0de73a06 100644
--- a/src/common/util.cpp
+++ b/src/common/util.cpp
@@ -85,7 +85,7 @@ using namespace epee;
#include <boost/algorithm/string.hpp>
#include <boost/asio.hpp>
#include <boost/format.hpp>
-#include <openssl/sha.h>
+#include <openssl/evp.h>
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "util"
@@ -941,14 +941,7 @@ std::string get_nix_version_display_string()
bool sha256sum(const uint8_t *data, size_t len, crypto::hash &hash)
{
- SHA256_CTX ctx;
- if (!SHA256_Init(&ctx))
- return false;
- if (!SHA256_Update(&ctx, data, len))
- return false;
- if (!SHA256_Final((unsigned char*)hash.data, &ctx))
- return false;
- return true;
+ return EVP_Digest(data, len, (unsigned char*) hash.data, NULL, EVP_sha256(), NULL) != 0;
}
bool sha256sum(const std::string &filename, crypto::hash &hash)
@@ -961,8 +954,8 @@ std::string get_nix_version_display_string()
if (!f)
return false;
std::ifstream::pos_type file_size = f.tellg();
- SHA256_CTX ctx;
- if (!SHA256_Init(&ctx))
+ std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)> ctx(EVP_MD_CTX_new(), &EVP_MD_CTX_free);
+ if (!EVP_DigestInit_ex(ctx.get(), EVP_sha256(), nullptr))
return false;
size_t size_left = file_size;
f.seekg(0, std::ios::beg);
@@ -973,12 +966,12 @@ std::string get_nix_version_display_string()
f.read(buf, read_size);
if (!f || !f.good())
return false;
- if (!SHA256_Update(&ctx, buf, read_size))
+ if (!EVP_DigestUpdate(ctx.get(), buf, read_size))
return false;
size_left -= read_size;
}
f.close();
- if (!SHA256_Final((unsigned char*)hash.data, &ctx))
+ if (!EVP_DigestFinal_ex(ctx.get(), (unsigned char*)hash.data, nullptr))
return false;
return true;
}
diff --git a/src/common/util.h b/src/common/util.h
index 25f5ceb47..f489594e8 100644
--- a/src/common/util.h
+++ b/src/common/util.h
@@ -231,7 +231,27 @@ namespace tools
bool is_privacy_preserving_network(const std::string &address);
int vercmp(const char *v0, const char *v1); // returns < 0, 0, > 0, similar to strcmp, but more human friendly than lexical - does not attempt to validate
+ /**
+ * \brief Creates a SHA-256 digest of a data buffer
+ *
+ * \param[in] data pointer to the buffer
+ * \param[in] len size of the buffer in bytes
+ * \param[out] hash where message digest will be written to
+ *
+ * \returns true if successful, false otherwise
+ */
bool sha256sum(const uint8_t *data, size_t len, crypto::hash &hash);
+
+ /**
+ * \brief Creates a SHA-256 digest of a file's contents, equivalent to the sha256sum command in Linux
+ *
+ * \param[in] filename path to target file
+ * \param[out] hash where message digest will be written to
+ *
+ * \returns true if successful, false if the file can not be opened or there is an OpenSSL failure
+ *
+ * \throws ios_base::failure if after the file is successfully opened, an error occurs during reading
+ */
bool sha256sum(const std::string &filename, crypto::hash &hash);
boost::optional<bool> is_hdd(const char *path);
diff --git a/src/crypto/hash-extra-jh.c b/src/crypto/hash-extra-jh.c
index 4d7481c07..52efd4ae3 100644
--- a/src/crypto/hash-extra-jh.c
+++ b/src/crypto/hash-extra-jh.c
@@ -36,7 +36,9 @@
#include "jh.h"
#include "hash-ops.h"
+#define JH_HASH_BITLEN HASH_SIZE * 8
+
void hash_extra_jh(const void *data, size_t length, char *hash) {
- int r = jh_hash(HASH_SIZE * 8, data, 8 * length, (uint8_t*)hash);
- assert(SUCCESS == r);
+ // No need to check for failure b/c jh_hash only fails for invalid hash size
+ jh_hash(JH_HASH_BITLEN, data, 8 * length, (uint8_t*)hash);
}
diff --git a/src/crypto/hash-extra-skein.c b/src/crypto/hash-extra-skein.c
index 9ea9c4faa..3eacaba58 100644
--- a/src/crypto/hash-extra-skein.c
+++ b/src/crypto/hash-extra-skein.c
@@ -34,7 +34,9 @@
#include "hash-ops.h"
#include "skein.h"
+#define SKEIN_HASH_BITLEN HASH_SIZE * 8
+
void hash_extra_skein(const void *data, size_t length, char *hash) {
- int r = skein_hash(8 * HASH_SIZE, data, 8 * length, (uint8_t*)hash);
- assert(SKEIN_SUCCESS == r);
+ // No need to check for failure b/c skein_hash only fails for invalid hash size
+ skein_hash(SKEIN_HASH_BITLEN, data, 8 * length, (uint8_t*)hash);
}
diff --git a/src/crypto/keccak.c b/src/crypto/keccak.c
index f098cbdf0..6616d3530 100644
--- a/src/crypto/keccak.c
+++ b/src/crypto/keccak.c
@@ -123,7 +123,7 @@ void keccak(const uint8_t *in, size_t inlen, uint8_t *md, int mdlen)
size_t i, rsiz, rsizw;
static_assert(HASH_DATA_AREA <= sizeof(temp), "Bad keccak preconditions");
- if (mdlen <= 0 || (mdlen > 100 && sizeof(st) != (size_t)mdlen))
+ if (mdlen <= 0 || (mdlen >= 100 && sizeof(st) != (size_t)mdlen))
{
local_abort("Bad keccak use");
}
diff --git a/src/cryptonote_basic/account.h b/src/cryptonote_basic/account.h
index 6e887db6d..2ee9545d4 100644
--- a/src/cryptonote_basic/account.h
+++ b/src/cryptonote_basic/account.h
@@ -55,8 +55,6 @@ namespace cryptonote
KV_SERIALIZE_VAL_POD_AS_BLOB_OPT(m_encryption_iv, default_iv)
END_KV_SERIALIZE_MAP()
- account_keys& operator=(account_keys const&) = default;
-
void encrypt(const crypto::chacha_key &key);
void decrypt(const crypto::chacha_key &key);
void encrypt_viewkey(const crypto::chacha_key &key);
diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp
index f101f10c5..829e5fc70 100644
--- a/src/cryptonote_basic/cryptonote_format_utils.cpp
+++ b/src/cryptonote_basic/cryptonote_format_utils.cpp
@@ -989,7 +989,7 @@ namespace cryptonote
return true;
}
//---------------------------------------------------------------
- bool out_can_be_to_acc(const boost::optional<crypto::view_tag>& view_tag_opt, const crypto::key_derivation& derivation, const size_t output_index)
+ bool out_can_be_to_acc(const boost::optional<crypto::view_tag>& view_tag_opt, const crypto::key_derivation& derivation, const size_t output_index, hw::device* hwdev)
{
// If there is no view tag to check, the output can possibly belong to the account.
// Will need to derive the output pub key to be certain whether or not the output belongs to the account.
@@ -1002,7 +1002,15 @@ namespace cryptonote
// Therefore can fail out early to avoid expensive crypto ops needlessly deriving output public key to
// determine if output belongs to the account.
crypto::view_tag derived_view_tag;
- crypto::derive_view_tag(derivation, output_index, derived_view_tag);
+ if (hwdev != nullptr)
+ {
+ bool r = hwdev->derive_view_tag(derivation, output_index, derived_view_tag);
+ CHECK_AND_ASSERT_MES(r, false, "Failed to derive view tag");
+ }
+ else
+ {
+ crypto::derive_view_tag(derivation, output_index, derived_view_tag);
+ }
return view_tag == derived_view_tag;
}
//---------------------------------------------------------------
@@ -1012,7 +1020,7 @@ namespace cryptonote
bool r = acc.get_device().generate_key_derivation(tx_pub_key, acc.m_view_secret_key, derivation);
CHECK_AND_ASSERT_MES(r, false, "Failed to generate key derivation");
crypto::public_key pk;
- if (out_can_be_to_acc(view_tag_opt, derivation, output_index))
+ if (out_can_be_to_acc(view_tag_opt, derivation, output_index, &acc.get_device()))
{
r = acc.get_device().derive_public_key(derivation, output_index, acc.m_account_address.m_spend_public_key, pk);
CHECK_AND_ASSERT_MES(r, false, "Failed to derive public key");
@@ -1026,7 +1034,7 @@ namespace cryptonote
CHECK_AND_ASSERT_MES(output_index < additional_tx_pub_keys.size(), false, "wrong number of additional tx pubkeys");
r = acc.get_device().generate_key_derivation(additional_tx_pub_keys[output_index], acc.m_view_secret_key, derivation);
CHECK_AND_ASSERT_MES(r, false, "Failed to generate key derivation");
- if (out_can_be_to_acc(view_tag_opt, derivation, output_index))
+ if (out_can_be_to_acc(view_tag_opt, derivation, output_index, &acc.get_device()))
{
r = acc.get_device().derive_public_key(derivation, output_index, acc.m_account_address.m_spend_public_key, pk);
CHECK_AND_ASSERT_MES(r, false, "Failed to derive public key");
@@ -1040,9 +1048,9 @@ namespace cryptonote
{
// try the shared tx pubkey
crypto::public_key subaddress_spendkey;
- if (out_can_be_to_acc(view_tag_opt, derivation, output_index))
+ if (out_can_be_to_acc(view_tag_opt, derivation, output_index, &hwdev))
{
- hwdev.derive_subaddress_public_key(out_key, derivation, output_index, subaddress_spendkey);
+ CHECK_AND_ASSERT_MES(hwdev.derive_subaddress_public_key(out_key, derivation, output_index, subaddress_spendkey), boost::none, "Failed to derive subaddress public key");
auto found = subaddresses.find(subaddress_spendkey);
if (found != subaddresses.end())
return subaddress_receive_info{ found->second, derivation };
@@ -1052,9 +1060,9 @@ namespace cryptonote
if (!additional_derivations.empty())
{
CHECK_AND_ASSERT_MES(output_index < additional_derivations.size(), boost::none, "wrong number of additional derivations");
- if (out_can_be_to_acc(view_tag_opt, additional_derivations[output_index], output_index))
+ if (out_can_be_to_acc(view_tag_opt, additional_derivations[output_index], output_index, &hwdev))
{
- hwdev.derive_subaddress_public_key(out_key, additional_derivations[output_index], output_index, subaddress_spendkey);
+ CHECK_AND_ASSERT_MES(hwdev.derive_subaddress_public_key(out_key, additional_derivations[output_index], output_index, subaddress_spendkey), boost::none, "Failed to derive subaddress public key");
auto found = subaddresses.find(subaddress_spendkey);
if (found != subaddresses.end())
return subaddress_receive_info{ found->second, additional_derivations[output_index] };
diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h
index 8f5459ca7..b97c9d499 100644
--- a/src/cryptonote_basic/cryptonote_format_utils.h
+++ b/src/cryptonote_basic/cryptonote_format_utils.h
@@ -91,7 +91,7 @@ namespace cryptonote
bool get_encrypted_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash8& payment_id);
void set_tx_out(const uint64_t amount, const crypto::public_key& output_public_key, const bool use_view_tags, const crypto::view_tag& view_tag, tx_out& out);
bool check_output_types(const transaction& tx, const uint8_t hf_version);
- bool out_can_be_to_acc(const boost::optional<crypto::view_tag>& view_tag_opt, const crypto::key_derivation& derivation, const size_t output_index);
+ bool out_can_be_to_acc(const boost::optional<crypto::view_tag>& view_tag_opt, const crypto::key_derivation& derivation, const size_t output_index, hw::device *hwdev = nullptr);
bool is_out_to_acc(const account_keys& acc, const crypto::public_key& output_public_key, const crypto::public_key& tx_pub_key, const std::vector<crypto::public_key>& additional_tx_public_keys, size_t output_index, const boost::optional<crypto::view_tag>& view_tag_opt = boost::optional<crypto::view_tag>());
struct subaddress_receive_info
{
diff --git a/src/cryptonote_basic/hardfork.h b/src/cryptonote_basic/hardfork.h
index 32f669c71..f43710f4f 100644
--- a/src/cryptonote_basic/hardfork.h
+++ b/src/cryptonote_basic/hardfork.h
@@ -231,6 +231,11 @@ namespace cryptonote
*/
uint64_t get_window_size() const { return window_size; }
+ /**
+ * @brief returns info for all known hard forks
+ */
+ const std::vector<hardfork_t>& get_hardforks() const { return heights; }
+
private:
uint8_t get_block_version(uint64_t height) const;
diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h
index f2a8e9b79..2ec194ef8 100644
--- a/src/cryptonote_config.h
+++ b/src/cryptonote_config.h
@@ -239,12 +239,15 @@ namespace config
const unsigned char HASH_KEY_MEMORY = 'k';
const unsigned char HASH_KEY_MULTISIG[] = {'M', 'u', 'l', 't' , 'i', 's', 'i', 'g', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
const unsigned char HASH_KEY_MULTISIG_KEY_AGGREGATION[] = "Multisig_key_agg";
+ const unsigned char HASH_KEY_CLSAG_ROUND_MULTISIG[] = "CLSAG_round_ms_merge_factor";
const unsigned char HASH_KEY_TXPROOF_V2[] = "TXPROOF_V2";
const unsigned char HASH_KEY_CLSAG_ROUND[] = "CLSAG_round";
const unsigned char HASH_KEY_CLSAG_AGG_0[] = "CLSAG_agg_0";
const unsigned char HASH_KEY_CLSAG_AGG_1[] = "CLSAG_agg_1";
const char HASH_KEY_MESSAGE_SIGNING[] = "MoneroMessageSignature";
const unsigned char HASH_KEY_MM_SLOT = 'm';
+ const constexpr char HASH_KEY_MULTISIG_TX_PRIVKEYS_SEED[] = "multisig_tx_privkeys_seed";
+ const constexpr char HASH_KEY_MULTISIG_TX_PRIVKEYS[] = "multisig_tx_privkeys";
// Multisig
const uint32_t MULTISIG_MAX_SIGNERS{16};
diff --git a/src/cryptonote_core/CMakeLists.txt b/src/cryptonote_core/CMakeLists.txt
index e272b94f0..69411e379 100644
--- a/src/cryptonote_core/CMakeLists.txt
+++ b/src/cryptonote_core/CMakeLists.txt
@@ -49,7 +49,6 @@ target_link_libraries(cryptonote_core
common
cncrypto
blockchain_db
- multisig
ringct
device
hardforks
diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp
index 5b7b4353d..657db311b 100644
--- a/src/cryptonote_core/blockchain.cpp
+++ b/src/cryptonote_core/blockchain.cpp
@@ -3434,7 +3434,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
std::vector < uint64_t > results;
results.resize(tx.vin.size(), 0);
- tools::threadpool& tpool = tools::threadpool::getInstance();
+ tools::threadpool& tpool = tools::threadpool::getInstanceForCompute();
tools::threadpool::waiter waiter(tpool);
int threads = tpool.get_max_concurrency();
@@ -5137,7 +5137,7 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::vector<block_complete
return true;
bool blocks_exist = false;
- tools::threadpool& tpool = tools::threadpool::getInstance();
+ tools::threadpool& tpool = tools::threadpool::getInstanceForCompute();
unsigned threads = tpool.get_max_concurrency();
blocks.resize(blocks_entry.size());
@@ -5604,7 +5604,7 @@ void Blockchain::cancel()
}
#if defined(PER_BLOCK_CHECKPOINT)
-static const char expected_block_hashes_hash[] = "8da80ca560793f252d1d4ed449c85d75c74867f3f86b8832c8e3f88b1cbb6ae3";
+static const char expected_block_hashes_hash[] = "e9371004b9f6be59921b27bc81e28b4715845ade1c6d16891d5c455f72e21365";
void Blockchain::load_compiled_in_block_hashes(const GetCheckpointsCallback& get_checkpoints)
{
if (get_checkpoints == nullptr || !m_fast_sync)
diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h
index 7a94f6358..4795fc55c 100644
--- a/src/cryptonote_core/blockchain.h
+++ b/src/cryptonote_core/blockchain.h
@@ -159,6 +159,13 @@ namespace cryptonote
bool deinit();
/**
+ * @brief get a set of blockchain checkpoint hashes
+ *
+ * @return set of blockchain checkpoint hashes
+ */
+ const checkpoints& get_checkpoints() const { return m_checkpoints; }
+
+ /**
* @brief assign a set of blockchain checkpoint hashes
*
* @param chk_pts the set of checkpoints to assign
@@ -893,6 +900,13 @@ namespace cryptonote
uint64_t get_earliest_ideal_height_for_version(uint8_t version) const { return m_hardfork->get_earliest_ideal_height_for_version(version); }
/**
+ * @brief returns info for all known hard forks
+ *
+ * @return the hardforks
+ */
+ const std::vector<hardfork_t>& get_hardforks() const { return m_hardfork->get_hardforks(); }
+
+ /**
* @brief get information about hardfork voting for a version
*
* @param version the version in question
diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp
index a78f5d673..d8c782f78 100644
--- a/src/cryptonote_core/cryptonote_core.cpp
+++ b/src/cryptonote_core/cryptonote_core.cpp
@@ -252,6 +252,10 @@ namespace cryptonote
m_pprotocol = &m_protocol_stub;
}
//-----------------------------------------------------------------------------------
+ const checkpoints& core::get_checkpoints() const
+ {
+ return m_blockchain_storage.get_checkpoints();
+ }
void core::set_checkpoints(checkpoints&& chk_pts)
{
m_blockchain_storage.set_checkpoints(std::move(chk_pts));
@@ -1009,7 +1013,7 @@ namespace cryptonote
CRITICAL_REGION_LOCAL(m_incoming_tx_lock);
- tools::threadpool& tpool = tools::threadpool::getInstance();
+ tools::threadpool& tpool = tools::threadpool::getInstanceForCompute();
tools::threadpool::waiter waiter(tpool);
epee::span<tx_blob_entry>::const_iterator it = tx_blobs.begin();
for (size_t i = 0; i < tx_blobs.size(); i++, ++it) {
@@ -1406,21 +1410,66 @@ namespace cryptonote
return true;
}
//-----------------------------------------------------------------------------------------------
+ bool core::notify_txpool_event(const epee::span<const cryptonote::blobdata> tx_blobs, epee::span<const crypto::hash> tx_hashes, epee::span<const cryptonote::transaction> txs, const std::vector<bool> &just_broadcasted) const
+ {
+ if (!m_zmq_pub)
+ return true;
+
+ if (tx_blobs.size() != tx_hashes.size() || tx_blobs.size() != txs.size() || tx_blobs.size() != just_broadcasted.size())
+ return false;
+
+ /* Publish txs via ZMQ that are "just broadcasted" by the daemon. This is
+ done here in addition to `handle_incoming_txs` in order to guarantee txs
+ are pub'd via ZMQ when we know the daemon has/will broadcast to other
+ nodes & *after* the tx is visible in the pool. This should get called
+ when the user submits a tx to a daemon in the "fluff" epoch relaying txs
+ via a public network. */
+ if (std::count(just_broadcasted.begin(), just_broadcasted.end(), true) == 0)
+ return true;
+
+ std::vector<txpool_event> results{};
+ results.resize(tx_blobs.size());
+ for (std::size_t i = 0; i < results.size(); ++i)
+ {
+ results[i].tx = std::move(txs[i]);
+ results[i].hash = std::move(tx_hashes[i]);
+ results[i].blob_size = tx_blobs[i].size();
+ results[i].weight = results[i].tx.pruned ? get_pruned_transaction_weight(results[i].tx) : get_transaction_weight(results[i].tx, results[i].blob_size);
+ results[i].res = just_broadcasted[i];
+ }
+
+ m_zmq_pub(std::move(results));
+
+ return true;
+ }
+ //-----------------------------------------------------------------------------------------------
void core::on_transactions_relayed(const epee::span<const cryptonote::blobdata> tx_blobs, const relay_method tx_relay)
{
+ // lock ensures duplicate txs aren't pub'd via zmq
+ CRITICAL_REGION_LOCAL(m_incoming_tx_lock);
+
std::vector<crypto::hash> tx_hashes{};
tx_hashes.resize(tx_blobs.size());
+ std::vector<cryptonote::transaction> txs{};
+ txs.resize(tx_blobs.size());
+
for (std::size_t i = 0; i < tx_blobs.size(); ++i)
{
- cryptonote::transaction tx{};
- if (!parse_and_validate_tx_from_blob(tx_blobs[i], tx, tx_hashes[i]))
+ if (!parse_and_validate_tx_from_blob(tx_blobs[i], txs[i], tx_hashes[i]))
{
LOG_ERROR("Failed to parse relayed transaction");
return;
}
}
- m_mempool.set_relayed(epee::to_span(tx_hashes), tx_relay);
+
+ std::vector<bool> just_broadcasted{};
+ just_broadcasted.reserve(tx_hashes.size());
+
+ m_mempool.set_relayed(epee::to_span(tx_hashes), tx_relay, just_broadcasted);
+
+ if (m_zmq_pub && matches_category(tx_relay, relay_category::legacy))
+ notify_txpool_event(tx_blobs, epee::to_span(tx_hashes), epee::to_span(txs), just_broadcasted);
}
//-----------------------------------------------------------------------------------------------
bool core::get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce, uint64_t &seed_height, crypto::hash &seed_hash)
@@ -1607,10 +1656,6 @@ namespace cryptonote
if (((size_t)-1) <= 0xffffffff && block_blob.size() >= 0x3fffffff)
MWARNING("This block's size is " << block_blob.size() << ", closing on the 32 bit limit");
- // load json & DNS checkpoints every 10min/hour respectively,
- // and verify them with respect to what blocks we already have
- CHECK_AND_ASSERT_MES(update_checkpoints(), false, "One or more checkpoints loaded from json or dns conflicted with existing checkpoints.");
-
block lb;
if (!b)
{
diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h
index 0b36730b6..6dc513570 100644
--- a/src/cryptonote_core/cryptonote_core.h
+++ b/src/cryptonote_core/cryptonote_core.h
@@ -437,6 +437,13 @@ namespace cryptonote
void set_cryptonote_protocol(i_cryptonote_protocol* pprotocol);
/**
+ * @copydoc Blockchain::get_checkpoints
+ *
+ * @note see Blockchain::get_checkpoints()
+ */
+ const checkpoints& get_checkpoints() const;
+
+ /**
* @copydoc Blockchain::set_checkpoints
*
* @note see Blockchain::set_checkpoints()
@@ -1036,6 +1043,13 @@ namespace cryptonote
bool relay_txpool_transactions();
/**
+ * @brief sends notification of txpool events to subscribers
+ *
+ * @return true on success, false otherwise
+ */
+ bool notify_txpool_event(const epee::span<const cryptonote::blobdata> tx_blobs, epee::span<const crypto::hash> tx_hashes, epee::span<const cryptonote::transaction> txs, const std::vector<bool> &just_broadcasted) const;
+
+ /**
* @brief checks DNS versions
*
* @return true on success, false otherwise
diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp
index 1d2024a05..472026217 100644
--- a/src/cryptonote_core/cryptonote_tx_utils.cpp
+++ b/src/cryptonote_core/cryptonote_tx_utils.cpp
@@ -203,7 +203,7 @@ namespace cryptonote
return addr.m_view_public_key;
}
//---------------------------------------------------------------
- bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct, const rct::RCTConfig &rct_config, rct::multisig_out *msout, bool shuffle_outs, bool use_view_tags)
+ bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct, const rct::RCTConfig &rct_config, bool shuffle_outs, bool use_view_tags)
{
hw::device &hwdev = sender_account_keys.get_device();
@@ -216,10 +216,6 @@ namespace cryptonote
std::vector<rct::key> amount_keys;
tx.set_null();
amount_keys.clear();
- if (msout)
- {
- msout->c.clear();
- }
tx.version = rct ? 2 : 1;
tx.unlock_time = unlock_time;
@@ -333,8 +329,8 @@ namespace cryptonote
return false;
}
- //check that derivated key is equal with real output key (if non multisig)
- if(!msout && !(in_ephemeral.pub == src_entr.outputs[src_entr.real_output].second.dest) )
+ //check that derivated key is equal with real output key
+ if(!(in_ephemeral.pub == src_entr.outputs[src_entr.real_output].second.dest) )
{
LOG_ERROR("derived public key mismatch with output public key at index " << idx << ", real out " << src_entr.real_output << "! "<< ENDL << "derived_key:"
<< string_tools::pod_to_hex(in_ephemeral.pub) << ENDL << "real output_public_key:"
@@ -347,7 +343,7 @@ namespace cryptonote
//put key image into tx input
txin_to_key input_to_key;
input_to_key.amount = src_entr.amount;
- input_to_key.k_image = msout ? rct::rct2ki(src_entr.multisig_kLRki.ki) : img;
+ input_to_key.k_image = img;
//fill outputs array and use relative offsets
for(const tx_source_entry::output_entry& out_entry: src_entr.outputs)
@@ -529,7 +525,6 @@ namespace cryptonote
rct::keyV destinations;
std::vector<uint64_t> inamounts, outamounts;
std::vector<unsigned int> index;
- std::vector<rct::multisig_kLRki> kLRki;
for (size_t i = 0; i < sources.size(); ++i)
{
rct::ctkey ctkey;
@@ -543,10 +538,6 @@ namespace cryptonote
memwipe(&ctkey, sizeof(rct::ctkey));
// inPk: (public key, commitment)
// will be done when filling in mixRing
- if (msout)
- {
- kLRki.push_back(sources[i].multisig_kLRki);
- }
}
for (size_t i = 0; i < tx.vout.size(); ++i)
{
@@ -598,9 +589,9 @@ namespace cryptonote
get_transaction_prefix_hash(tx, tx_prefix_hash, hwdev);
rct::ctkeyV outSk;
if (use_simple_rct)
- tx.rct_signatures = rct::genRctSimple(rct::hash2rct(tx_prefix_hash), inSk, destinations, inamounts, outamounts, amount_in - amount_out, mixRing, amount_keys, msout ? &kLRki : NULL, msout, index, outSk, rct_config, hwdev);
+ tx.rct_signatures = rct::genRctSimple(rct::hash2rct(tx_prefix_hash), inSk, destinations, inamounts, outamounts, amount_in - amount_out, mixRing, amount_keys, index, outSk, rct_config, hwdev);
else
- tx.rct_signatures = rct::genRct(rct::hash2rct(tx_prefix_hash), inSk, destinations, outamounts, mixRing, amount_keys, msout ? &kLRki[0] : NULL, msout, sources[0].real_output, outSk, rct_config, hwdev); // same index assumption
+ tx.rct_signatures = rct::genRct(rct::hash2rct(tx_prefix_hash), inSk, destinations, outamounts, mixRing, amount_keys, sources[0].real_output, outSk, rct_config, hwdev); // same index assumption
memwipe(inSk.data(), inSk.size() * sizeof(rct::ctkey));
CHECK_AND_ASSERT_MES(tx.vout.size() == outSk.size(), false, "outSk size does not match vout");
@@ -613,7 +604,7 @@ namespace cryptonote
return true;
}
//---------------------------------------------------------------
- bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct, const rct::RCTConfig &rct_config, rct::multisig_out *msout, bool use_view_tags)
+ bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct, const rct::RCTConfig &rct_config, bool use_view_tags)
{
hw::device &hwdev = sender_account_keys.get_device();
hwdev.open_tx(tx_key);
@@ -634,7 +625,7 @@ namespace cryptonote
}
bool shuffle_outs = true;
- bool r = construct_tx_with_tx_key(sender_account_keys, subaddresses, sources, destinations, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, rct, rct_config, msout, shuffle_outs, use_view_tags);
+ bool r = construct_tx_with_tx_key(sender_account_keys, subaddresses, sources, destinations, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, rct, rct_config, shuffle_outs, use_view_tags);
hwdev.close_tx();
return r;
} catch(...) {
@@ -650,7 +641,7 @@ namespace cryptonote
crypto::secret_key tx_key;
std::vector<crypto::secret_key> additional_tx_keys;
std::vector<tx_destination_entry> destinations_copy = destinations;
- return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations_copy, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, { rct::RangeProofBorromean, 0}, NULL, false);
+ return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations_copy, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, { rct::RangeProofBorromean, 0});
}
//---------------------------------------------------------------
bool generate_genesis_block(
diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h
index f4ffb98ff..12d6b8ce5 100644
--- a/src/cryptonote_core/cryptonote_tx_utils.h
+++ b/src/cryptonote_core/cryptonote_tx_utils.h
@@ -119,8 +119,8 @@ namespace cryptonote
//---------------------------------------------------------------
crypto::public_key get_destination_view_key_pub(const std::vector<tx_destination_entry> &destinations, const boost::optional<cryptonote::account_public_address>& change_addr);
bool construct_tx(const account_keys& sender_account_keys, std::vector<tx_source_entry> &sources, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time);
- bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, const rct::RCTConfig &rct_config = { rct::RangeProofBorromean, 0 }, rct::multisig_out *msout = NULL, bool shuffle_outs = true, bool use_view_tags = false);
- bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, const rct::RCTConfig &rct_config = { rct::RangeProofBorromean, 0 }, rct::multisig_out *msout = NULL, bool use_view_tags = false);
+ bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, const rct::RCTConfig &rct_config = { rct::RangeProofBorromean, 0 }, bool shuffle_outs = true, bool use_view_tags = false);
+ bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, const rct::RCTConfig &rct_config = { rct::RangeProofBorromean, 0 }, bool use_view_tags = false);
bool generate_output_ephemeral_keys(const size_t tx_version, const cryptonote::account_keys &sender_account_keys, const crypto::public_key &txkey_pub, const crypto::secret_key &tx_key,
const cryptonote::tx_destination_entry &dst_entr, const boost::optional<cryptonote::account_public_address> &change_addr, const size_t output_index,
const bool &need_additional_txkeys, const std::vector<crypto::secret_key> &additional_tx_keys,
diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp
index c27261860..2a514ceae 100644
--- a/src/cryptonote_core/tx_pool.cpp
+++ b/src/cryptonote_core/tx_pool.cpp
@@ -97,9 +97,9 @@ namespace cryptonote
constexpr const std::chrono::seconds forward_delay_average{CRYPTONOTE_FORWARD_DELAY_AVERAGE};
// a kind of increasing backoff within min/max bounds
- uint64_t get_relay_delay(time_t now, time_t received)
+ uint64_t get_relay_delay(time_t last_relay, time_t received)
{
- time_t d = (now - received + MIN_RELAY_TIME) / MIN_RELAY_TIME * MIN_RELAY_TIME;
+ time_t d = (last_relay - received + MIN_RELAY_TIME) / MIN_RELAY_TIME * MIN_RELAY_TIME;
if (d > MAX_RELAY_TIME)
d = MAX_RELAY_TIME;
return d;
@@ -402,6 +402,19 @@ namespace cryptonote
m_txpool_max_weight = bytes;
}
//---------------------------------------------------------------------------------
+ void tx_memory_pool::reduce_txpool_weight(size_t weight)
+ {
+ if (weight > m_txpool_weight)
+ {
+ MERROR("Underflow in txpool weight");
+ m_txpool_weight = 0;
+ }
+ else
+ {
+ m_txpool_weight -= weight;
+ }
+ }
+ //---------------------------------------------------------------------------------
void tx_memory_pool::prune(size_t bytes)
{
CRITICAL_REGION_LOCAL(m_transactions_lock);
@@ -423,8 +436,14 @@ namespace cryptonote
txpool_tx_meta_t meta;
if (!m_blockchain.get_txpool_tx_meta(txid, meta))
{
- MERROR("Failed to find tx_meta in txpool");
- return;
+ static bool warned = false;
+ if (!warned)
+ {
+ MERROR("Failed to find tx_meta in txpool (will only print once)");
+ warned = true;
+ }
+ --it;
+ continue;
}
// don't prune the kept_by_block ones, they're likely added because we're adding a block with those
if (meta.kept_by_block)
@@ -442,7 +461,7 @@ namespace cryptonote
// remove first, in case this throws, so key images aren't removed
MINFO("Pruning tx " << txid << " from txpool: weight: " << meta.weight << ", fee/byte: " << it->first.first);
m_blockchain.remove_txpool_tx(txid);
- m_txpool_weight -= meta.weight;
+ reduce_txpool_weight(meta.weight);
remove_transaction_keyimages(tx, txid);
MINFO("Pruned tx " << txid << " from txpool: weight: " << meta.weight << ", fee/byte: " << it->first.first);
m_txs_by_fee_and_receive_time.erase(it--);
@@ -562,7 +581,7 @@ namespace cryptonote
// remove first, in case this throws, so key images aren't removed
m_blockchain.remove_txpool_tx(id);
- m_txpool_weight -= tx_weight;
+ reduce_txpool_weight(tx_weight);
remove_transaction_keyimages(tx, id);
lock.commit();
}
@@ -725,7 +744,7 @@ namespace cryptonote
{
// remove first, so we only remove key images if the tx removal succeeds
m_blockchain.remove_txpool_tx(txid);
- m_txpool_weight -= entry.second;
+ reduce_txpool_weight(entry.second);
remove_transaction_keyimages(tx, txid);
}
}
@@ -779,7 +798,7 @@ namespace cryptonote
case relay_method::local:
case relay_method::fluff:
case relay_method::block:
- if (now - meta.last_relayed_time <= get_relay_delay(now, meta.receive_time))
+ if (now - meta.last_relayed_time <= get_relay_delay(meta.last_relayed_time, meta.receive_time))
return true; // continue to next tx
break;
}
@@ -812,7 +831,7 @@ namespace cryptonote
function is only called every ~2 minutes, so this resetting should be
unnecessary, but is primarily a precaution against potential changes
to the callback routines. */
- elem.second.last_relayed_time = now + get_relay_delay(now, elem.second.receive_time);
+ elem.second.last_relayed_time = now + get_relay_delay(elem.second.last_relayed_time, elem.second.receive_time);
m_blockchain.update_txpool_tx(elem.first, elem.second);
}
@@ -820,8 +839,10 @@ namespace cryptonote
return true;
}
//---------------------------------------------------------------------------------
- void tx_memory_pool::set_relayed(const epee::span<const crypto::hash> hashes, const relay_method method)
+ void tx_memory_pool::set_relayed(const epee::span<const crypto::hash> hashes, const relay_method method, std::vector<bool> &just_broadcasted)
{
+ just_broadcasted.clear();
+
crypto::random_poisson_seconds embargo_duration{dandelionpp_embargo_average};
const auto now = std::chrono::system_clock::now();
uint64_t next_relay = uint64_t{std::numeric_limits<time_t>::max()};
@@ -831,12 +852,14 @@ namespace cryptonote
LockedTXN lock(m_blockchain.get_db());
for (const auto& hash : hashes)
{
+ bool was_just_broadcasted = false;
try
{
txpool_tx_meta_t meta;
if (m_blockchain.get_txpool_tx_meta(hash, meta))
{
// txes can be received as "stem" or "fluff" in either order
+ const bool already_broadcasted = meta.matches(relay_category::broadcasted);
meta.upgrade_relay_method(method);
meta.relayed = true;
@@ -849,6 +872,9 @@ namespace cryptonote
meta.last_relayed_time = std::chrono::system_clock::to_time_t(now);
m_blockchain.update_txpool_tx(hash, meta);
+
+ // wait until db update succeeds to ensure tx is visible in the pool
+ was_just_broadcasted = !already_broadcasted && meta.matches(relay_category::broadcasted);
}
}
catch (const std::exception &e)
@@ -856,6 +882,7 @@ namespace cryptonote
MERROR("Failed to update txpool transaction metadata: " << e.what());
// continue
}
+ just_broadcasted.emplace_back(was_just_broadcasted);
}
lock.commit();
set_if_less(m_next_check, time_t(next_relay));
@@ -917,26 +944,61 @@ namespace cryptonote
{
CRITICAL_REGION_LOCAL(m_transactions_lock);
CRITICAL_REGION_LOCAL1(m_blockchain);
- const relay_category category = include_sensitive ? relay_category::all : relay_category::broadcasted;
- backlog.reserve(m_blockchain.get_txpool_tx_count(include_sensitive));
- txpool_tx_meta_t tmp_meta;
- m_blockchain.for_all_txpool_txes([this, &backlog, &tmp_meta](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata_ref *bd){
- transaction tx;
- if (!(meta.pruned ? parse_and_validate_tx_base_from_blob(*bd, tx) : parse_and_validate_tx_from_blob(*bd, tx)))
+
+ std::vector<tx_block_template_backlog_entry> tmp;
+ uint64_t total_weight = 0;
+
+ // First get everything from the mempool, filter it later
+ m_blockchain.for_all_txpool_txes([&tmp, &total_weight](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata_ref*){
+ tmp.emplace_back(tx_block_template_backlog_entry{txid, meta.weight, meta.fee});
+ total_weight += meta.weight;
+ return true;
+ }, false, include_sensitive ? relay_category::all : relay_category::broadcasted);
+
+ // Limit backlog to 112.5% of current median weight. This is enough to mine a full block with the optimal block reward
+ const uint64_t median_weight = m_blockchain.get_current_cumulative_block_weight_median();
+ const uint64_t max_backlog_weight = median_weight + (median_weight / 8);
+
+ // If the total weight is too high, choose the best paying transactions
+ if (total_weight > max_backlog_weight)
+ std::sort(tmp.begin(), tmp.end(), [](const auto& a, const auto& b){ return a.fee * b.weight > b.fee * a.weight; });
+
+ backlog.clear();
+ uint64_t w = 0;
+
+ std::unordered_set<crypto::key_image> k_images;
+
+ for (const tx_block_template_backlog_entry& e : tmp)
+ {
+ try
{
- MERROR("Failed to parse tx from txpool");
- // continue
- return true;
- }
- tx.set_hash(txid);
+ txpool_tx_meta_t meta;
+ if (!m_blockchain.get_txpool_tx_meta(e.id, meta))
+ continue;
- tmp_meta = meta;
+ cryptonote::blobdata txblob;
+ if (!m_blockchain.get_txpool_tx_blob(e.id, txblob, relay_category::all))
+ continue;
- if (is_transaction_ready_to_go(tmp_meta, txid, *bd, tx))
- backlog.push_back({txid, meta.weight, meta.fee});
+ cryptonote::transaction tx;
+ if (is_transaction_ready_to_go(meta, e.id, txblob, tx))
+ {
+ if (have_key_images(k_images, tx))
+ continue;
+ append_key_images(k_images, tx);
- return true;
- }, true, category);
+ backlog.push_back(e);
+ w += e.weight;
+ if (w > max_backlog_weight)
+ break;
+ }
+ }
+ catch (const std::exception &e)
+ {
+ MERROR("Failed to check transaction readiness: " << e.what());
+ // continue, not fatal
+ }
+ }
}
//------------------------------------------------------------------
void tx_memory_pool::get_transaction_stats(struct txpool_stats& stats, bool include_sensitive) const
diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h
index 62bef6c06..45623fd14 100644
--- a/src/cryptonote_core/tx_pool.h
+++ b/src/cryptonote_core/tx_pool.h
@@ -266,7 +266,11 @@ namespace cryptonote
void get_transaction_backlog(std::vector<tx_backlog_entry>& backlog, bool include_sensitive = false) const;
/**
- * @brief get (hash, weight, fee) for all transactions in the pool - the minimum required information to create a block template
+ * @brief get (hash, weight, fee) for transactions in the pool - the minimum required information to create a block template
+ *
+ * Not all transactions in the pool will be returned for performance reasons
+ * If there are too many transactions in the pool, only the highest-paying transactions
+ * will be returned - but enough for the miner to create a full block
*
* @param backlog return-by-reference that data
* @param include_sensitive return stempool, anonymity-pool, and unrelayed txes
@@ -353,8 +357,10 @@ namespace cryptonote
*
* @param hashes list of tx hashes that are about to be relayed
* @param tx_relay update how the tx left this node
+ * @param just_broadcasted true if a tx was just broadcasted
+ *
*/
- void set_relayed(epee::span<const crypto::hash> hashes, relay_method tx_relay);
+ void set_relayed(epee::span<const crypto::hash> hashes, relay_method tx_relay, std::vector<bool> &just_broadcasted);
/**
* @brief get the total number of transactions in the pool
@@ -406,6 +412,13 @@ namespace cryptonote
*/
void set_txpool_max_weight(size_t bytes);
+ /**
+ * @brief reduce the cumulative txpool weight by the weight provided
+ *
+ * @param weight the weight to reduce the total txpool weight by
+ */
+ void reduce_txpool_weight(size_t weight);
+
#define CURRENT_MEMPOOL_ARCHIVE_VER 11
#define CURRENT_MEMPOOL_TX_DETAILS_ARCHIVE_VER 13
diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.h b/src/cryptonote_protocol/cryptonote_protocol_handler.h
index a1e4df563..515b78c94 100644
--- a/src/cryptonote_protocol/cryptonote_protocol_handler.h
+++ b/src/cryptonote_protocol/cryptonote_protocol_handler.h
@@ -113,12 +113,23 @@ namespace cryptonote
const block_queue &get_block_queue() const { return m_block_queue; }
void stop();
void on_connection_close(cryptonote_connection_context &context);
- void set_max_out_peers(unsigned int max) { m_max_out_peers = max; }
+ void set_max_out_peers(epee::net_utils::zone zone, unsigned int max) { CRITICAL_REGION_LOCAL(m_max_out_peers_lock); m_max_out_peers[zone] = max; }
+ unsigned int get_max_out_peers(epee::net_utils::zone zone) const
+ {
+ CRITICAL_REGION_LOCAL(m_max_out_peers_lock);
+ const auto it = m_max_out_peers.find(zone);
+ if (it == m_max_out_peers.end())
+ {
+ MWARNING(epee::net_utils::zone_to_string(zone) << " max out peers not set, using default");
+ return P2P_DEFAULT_CONNECTIONS_COUNT;
+ }
+ return it->second;
+ }
bool no_sync() const { return m_no_sync; }
void set_no_sync(bool value) { m_no_sync = value; }
std::string get_peers_overview() const;
std::pair<uint32_t, uint32_t> get_next_needed_pruning_stripe() const;
- bool needs_new_sync_connections() const;
+ bool needs_new_sync_connections(epee::net_utils::zone zone) const;
bool is_busy_syncing();
private:
@@ -171,7 +182,8 @@ namespace cryptonote
epee::math_helper::once_a_time_milliseconds<100> m_standby_checker;
epee::math_helper::once_a_time_seconds<101> m_sync_search_checker;
epee::math_helper::once_a_time_seconds<43> m_bad_peer_checker;
- std::atomic<unsigned int> m_max_out_peers;
+ std::unordered_map<epee::net_utils::zone, unsigned int> m_max_out_peers;
+ mutable epee::critical_section m_max_out_peers_lock;
tools::PerformanceTimer m_sync_timer, m_add_timer;
uint64_t m_last_add_end_time;
uint64_t m_sync_spans_downloaded, m_sync_old_spans_downloaded, m_sync_bad_spans_downloaded;
diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl
index 891ee109d..bd2f8ce85 100644
--- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl
+++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl
@@ -537,6 +537,10 @@ namespace cryptonote
MLOG_PEER_STATE("requesting chain");
}
+ // load json & DNS checkpoints every 10min/hour respectively,
+ // and verify them with respect to what blocks we already have
+ CHECK_AND_ASSERT_MES(m_core.update_checkpoints(), 1, "One or more checkpoints loaded from json or dns conflicted with existing checkpoints.");
+
return 1;
}
//------------------------------------------------------------------------------------------------------------------------
@@ -819,6 +823,10 @@ namespace cryptonote
post_notify<NOTIFY_REQUEST_CHAIN>(r, context);
MLOG_PEER_STATE("requesting chain");
}
+
+ // load json & DNS checkpoints every 10min/hour respectively,
+ // and verify them with respect to what blocks we already have
+ CHECK_AND_ASSERT_MES(m_core.update_checkpoints(), 1, "One or more checkpoints loaded from json or dns conflicted with existing checkpoints.");
}
}
else
@@ -1776,33 +1784,49 @@ skip:
return true;
MTRACE("Checking for outgoing syncing peers...");
- unsigned n_syncing = 0, n_synced = 0;
- boost::uuids::uuid last_synced_peer_id(boost::uuids::nil_uuid());
+ std::unordered_map<epee::net_utils::zone, unsigned> n_syncing, n_synced;
+ std::unordered_map<epee::net_utils::zone, boost::uuids::uuid> last_synced_peer_id;
+ std::vector<epee::net_utils::zone> zones;
m_p2p->for_each_connection([&](cryptonote_connection_context& context, nodetool::peerid_type peer_id, uint32_t support_flags)->bool
{
if (!peer_id || context.m_is_income) // only consider connected outgoing peers
return true;
+
+ const epee::net_utils::zone zone = context.m_remote_address.get_zone();
+ if (n_syncing.find(zone) == n_syncing.end())
+ {
+ n_syncing[zone] = 0;
+ n_synced[zone] = 0;
+ last_synced_peer_id[zone] = boost::uuids::nil_uuid();
+ zones.push_back(zone);
+ }
+
if (context.m_state == cryptonote_connection_context::state_synchronizing)
- ++n_syncing;
+ ++n_syncing[zone];
if (context.m_state == cryptonote_connection_context::state_normal)
{
- ++n_synced;
+ ++n_synced[zone];
if (!context.m_anchor)
- last_synced_peer_id = context.m_connection_id;
+ last_synced_peer_id[zone] = context.m_connection_id;
}
return true;
});
- MTRACE(n_syncing << " syncing, " << n_synced << " synced");
- // if we're at max out peers, and not enough are syncing
- if (n_synced + n_syncing >= m_max_out_peers && n_syncing < P2P_DEFAULT_SYNC_SEARCH_CONNECTIONS_COUNT && last_synced_peer_id != boost::uuids::nil_uuid())
+ for (const auto& zone : zones)
{
- if (!m_p2p->for_connection(last_synced_peer_id, [&](cryptonote_connection_context& ctx, nodetool::peerid_type peer_id, uint32_t f)->bool{
- MINFO(ctx << "dropping synced peer, " << n_syncing << " syncing, " << n_synced << " synced");
- drop_connection(ctx, false, false);
- return true;
- }))
- MDEBUG("Failed to find peer we wanted to drop");
+ const unsigned int max_out_peers = get_max_out_peers(zone);
+ MTRACE("[" << epee::net_utils::zone_to_string(zone) << "] " << n_syncing[zone] << " syncing, " << n_synced[zone] << " synced, " << max_out_peers << " max out peers");
+
+ // if we're at max out peers, and not enough are syncing, drop the last sync'd non-anchor
+ if (n_synced[zone] + n_syncing[zone] >= max_out_peers && n_syncing[zone] < P2P_DEFAULT_SYNC_SEARCH_CONNECTIONS_COUNT && last_synced_peer_id[zone] != boost::uuids::nil_uuid())
+ {
+ if (!m_p2p->for_connection(last_synced_peer_id[zone], [&](cryptonote_connection_context& ctx, nodetool::peerid_type peer_id, uint32_t f)->bool{
+ MINFO(ctx << "dropping synced peer, " << n_syncing[zone] << " syncing, " << n_synced[zone] << " synced, " << max_out_peers << " max out peers");
+ drop_connection(ctx, false, false);
+ return true;
+ }))
+ MDEBUG("Failed to find peer we wanted to drop");
+ }
}
return true;
@@ -1987,11 +2011,13 @@ skip:
++n_peers_on_next_stripe;
return true;
});
+ // TODO: investigate tallying by zone and comparing to max out peers by zone
+ const unsigned int max_out_peers = get_max_out_peers(epee::net_utils::zone::public_);
const uint32_t distance = (peer_stripe + (1<<CRYPTONOTE_PRUNING_LOG_STRIPES) - next_stripe) % (1<<CRYPTONOTE_PRUNING_LOG_STRIPES);
- if ((n_out_peers >= m_max_out_peers && n_peers_on_next_stripe == 0) || (distance > 1 && n_peers_on_next_stripe <= 2) || distance > 2)
+ if ((n_out_peers >= max_out_peers && n_peers_on_next_stripe == 0) || (distance > 1 && n_peers_on_next_stripe <= 2) || distance > 2)
{
MDEBUG(context << "we want seed " << next_stripe << ", and either " << n_out_peers << " is at max out peers ("
- << m_max_out_peers << ") or distance " << distance << " from " << next_stripe << " to " << peer_stripe <<
+ << max_out_peers << ") or distance " << distance << " from " << next_stripe << " to " << peer_stripe <<
" is too large and we have only " << n_peers_on_next_stripe << " peers on next seed, dropping connection to make space");
return true;
}
@@ -2812,11 +2838,13 @@ skip:
}
return true;
});
- const bool use_next = (n_next > m_max_out_peers / 2 && n_subsequent <= 1) || (n_next > 2 && n_subsequent == 0);
+ // TODO: investigate tallying by zone and comparing to max out peers by zone
+ const unsigned int max_out_peers = get_max_out_peers(epee::net_utils::zone::public_);
+ const bool use_next = (n_next > max_out_peers / 2 && n_subsequent <= 1) || (n_next > 2 && n_subsequent == 0);
const uint32_t ret_stripe = use_next ? subsequent_pruning_stripe: next_pruning_stripe;
MIDEBUG(const std::string po = get_peers_overview(), "get_next_needed_pruning_stripe: want height " << want_height << " (" <<
want_height_from_blockchain << " from blockchain, " << want_height_from_block_queue << " from block queue), stripe " <<
- next_pruning_stripe << " (" << n_next << "/" << m_max_out_peers << " on it and " << n_subsequent << " on " <<
+ next_pruning_stripe << " (" << n_next << "/" << max_out_peers << " on it and " << n_subsequent << " on " <<
subsequent_pruning_stripe << ", " << n_others << " others) -> " << ret_stripe << " (+" <<
(ret_stripe - next_pruning_stripe + (1 << CRYPTONOTE_PRUNING_LOG_STRIPES)) % (1 << CRYPTONOTE_PRUNING_LOG_STRIPES) <<
"), current peers " << po);
@@ -2824,7 +2852,7 @@ skip:
}
//------------------------------------------------------------------------------------------------------------------------
template<class t_core>
- bool t_cryptonote_protocol_handler<t_core>::needs_new_sync_connections() const
+ bool t_cryptonote_protocol_handler<t_core>::needs_new_sync_connections(epee::net_utils::zone zone) const
{
const uint64_t target = m_core.get_target_blockchain_height();
const uint64_t height = m_core.get_current_blockchain_height();
@@ -2832,11 +2860,11 @@ skip:
return false;
size_t n_out_peers = 0;
m_p2p->for_each_connection([&](cryptonote_connection_context& ctx, nodetool::peerid_type peer_id, uint32_t support_flags)->bool{
- if (!ctx.m_is_income)
+ if (!ctx.m_is_income && ctx.m_remote_address.get_zone() == zone)
++n_out_peers;
return true;
});
- if (n_out_peers >= m_max_out_peers)
+ if (n_out_peers >= get_max_out_peers(zone))
return false;
return true;
}
diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp
index 73d9ebce1..3d90e0855 100644
--- a/src/daemon/main.cpp
+++ b/src/daemon/main.cpp
@@ -83,7 +83,7 @@ uint16_t parse_public_rpc_port(const po::variables_map &vm)
}
uint16_t rpc_port;
- if (!string_tools::get_xtype_from_string(rpc_port, rpc_port_str))
+ if (!epee::string_tools::get_xtype_from_string(rpc_port, rpc_port_str))
{
throw std::runtime_error("invalid RPC port " + rpc_port_str);
}
diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp
index b6364ff77..0d3688c76 100644
--- a/src/daemon/rpc_command_executor.cpp
+++ b/src/daemon/rpc_command_executor.cpp
@@ -1063,7 +1063,7 @@ bool t_rpc_command_executor::print_transaction(crypto::hash transaction_hash,
cryptonote::blobdata blob;
std::string source = as_hex.empty() ? pruned_as_hex + prunable_as_hex : as_hex;
bool pruned = !pruned_as_hex.empty() && prunable_as_hex.empty();
- if (!string_tools::parse_hexstr_to_binbuff(source, blob))
+ if (!epee::string_tools::parse_hexstr_to_binbuff(source, blob))
{
tools::fail_msg_writer() << "Failed to parse tx to get json format";
}
diff --git a/src/device/device.hpp b/src/device/device.hpp
index eca91006f..392703a24 100644
--- a/src/device/device.hpp
+++ b/src/device/device.hpp
@@ -177,6 +177,7 @@ namespace hw {
virtual bool derive_public_key(const crypto::key_derivation &derivation, const std::size_t output_index, const crypto::public_key &pub, crypto::public_key &derived_pub) = 0;
virtual bool secret_key_to_public_key(const crypto::secret_key &sec, crypto::public_key &pub) = 0;
virtual bool generate_key_image(const crypto::public_key &pub, const crypto::secret_key &sec, crypto::key_image &image) = 0;
+ virtual bool derive_view_tag(const crypto::key_derivation &derivation, const std::size_t output_index, crypto::view_tag &view_tag) = 0;
// alternative prototypes available in libringct
rct::key scalarmultKey(const rct::key &P, const rct::key &a)
diff --git a/src/device/device_default.hpp b/src/device/device_default.hpp
index 7d3543652..58149cdbf 100644
--- a/src/device/device_default.hpp
+++ b/src/device/device_default.hpp
@@ -101,7 +101,7 @@ namespace hw {
bool derive_public_key(const crypto::key_derivation &derivation, const std::size_t output_index, const crypto::public_key &pub, crypto::public_key &derived_pub) override;
bool secret_key_to_public_key(const crypto::secret_key &sec, crypto::public_key &pub) override;
bool generate_key_image(const crypto::public_key &pub, const crypto::secret_key &sec, crypto::key_image &image) override;
- bool derive_view_tag(const crypto::key_derivation &derivation, const std::size_t output_index, crypto::view_tag &view_tag);
+ bool derive_view_tag(const crypto::key_derivation &derivation, const std::size_t output_index, crypto::view_tag &view_tag) override;
/* ======================================================================= */
diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp
index 51e65dfa5..22a1fd986 100644
--- a/src/device/device_ledger.cpp
+++ b/src/device/device_ledger.cpp
@@ -43,6 +43,10 @@ namespace hw {
#ifdef WITH_DEVICE_LEDGER
+ namespace {
+ bool apdu_verbose =true;
+ }
+
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "device.ledger"
@@ -266,6 +270,7 @@ namespace hw {
#define INS_DERIVE_PUBLIC_KEY 0x36
#define INS_DERIVE_SECRET_KEY 0x38
#define INS_GEN_KEY_IMAGE 0x3A
+ #define INS_DERIVE_VIEW_TAG 0x3B
#define INS_SECRET_KEY_ADD 0x3C
#define INS_SECRET_KEY_SUB 0x3E
#define INS_GENERATE_KEYPAIR 0x40
@@ -694,7 +699,8 @@ namespace hw {
log_hexbuffer("derive_subaddress_public_key: [[IN]] pub ", pub_x.data, 32);
log_hexbuffer("derive_subaddress_public_key: [[IN]] derivation", derivation_x.data, 32);
log_message ("derive_subaddress_public_key: [[IN]] index ", std::to_string((int)output_index_x));
- this->controle_device->derive_subaddress_public_key(pub_x, derivation_x,output_index_x,derived_pub_x);
+ if (!this->controle_device->derive_subaddress_public_key(pub_x, derivation_x,output_index_x,derived_pub_x))
+ return false;
log_hexbuffer("derive_subaddress_public_key: [[OUT]] derived_pub", derived_pub_x.data, 32);
#endif
@@ -702,7 +708,8 @@ namespace hw {
//If we are in TRANSACTION_PARSE, the given derivation has been retrieved uncrypted (wihtout the help
//of the device), so continue that way.
MDEBUG( "derive_subaddress_public_key : PARSE mode with known viewkey");
- crypto::derive_subaddress_public_key(pub, derivation, output_index,derived_pub);
+ if (!crypto::derive_subaddress_public_key(pub, derivation, output_index,derived_pub))
+ return false;
} else {
AUTO_LOCK_CMD();
int offset = set_command_header_noopt(INS_DERIVE_SUBADDRESS_PUBLIC_KEY);
@@ -1052,7 +1059,8 @@ namespace hw {
crypto::key_derivation derivation_x;
log_hexbuffer("generate_key_derivation: [[IN]] pub ", pub_x.data, 32);
log_hexbuffer("generate_key_derivation: [[IN]] sec ", sec_x.data, 32);
- this->controle_device->generate_key_derivation(pub_x, sec_x, derivation_x);
+ if (!this->controle_device->generate_key_derivation(pub_x, sec_x, derivation_x))
+ return false;
log_hexbuffer("generate_key_derivation: [[OUT]] derivation", derivation_x.data, 32);
#endif
@@ -1207,7 +1215,8 @@ namespace hw {
log_hexbuffer("derive_public_key: [[IN]] derivation ", derivation_x.data, 32);
log_message ("derive_public_key: [[IN]] output_index", std::to_string(output_index_x));
log_hexbuffer("derive_public_key: [[IN]] pub ", pub_x.data, 32);
- this->controle_device->derive_public_key(derivation_x, output_index_x, pub_x, derived_pub_x);
+ if (!this->controle_device->derive_public_key(derivation_x, output_index_x, pub_x, derived_pub_x))
+ return false;
log_hexbuffer("derive_public_key: [[OUT]] derived_pub ", derived_pub_x.data, 32);
#endif
@@ -1304,6 +1313,54 @@ namespace hw {
return true;
}
+ bool device_ledger::derive_view_tag(const crypto::key_derivation &derivation, const std::size_t output_index, crypto::view_tag &view_tag){
+ #ifdef DEBUG_HWDEVICE
+ crypto::key_derivation derivation_x;
+ if ((this->mode == TRANSACTION_PARSE) && has_view_key) {
+ derivation_x = derivation;
+ } else {
+ derivation_x = hw::ledger::decrypt(derivation);
+ }
+ const std::size_t output_index_x = output_index;
+ crypto::view_tag view_tag_x;
+ log_hexbuffer("derive_view_tag: [[IN]] derivation ", derivation_x.data, 32);
+ log_message ("derive_view_tag: [[IN]] output_index", std::to_string(output_index_x));
+ this->controle_device->derive_view_tag(derivation_x, output_index_x, view_tag_x);
+ log_hexbuffer("derive_view_tag: [[OUT]] view_tag ", &view_tag_x.data, 1);
+ #endif
+
+ if ((this->mode == TRANSACTION_PARSE) && has_view_key) {
+ //If we are in TRANSACTION_PARSE, the given derivation has been retrieved uncrypted (wihtout the help
+ //of the device), so continue that way.
+ MDEBUG( "derive_view_tag : PARSE mode with known viewkey");
+ crypto::derive_view_tag(derivation, output_index, view_tag);
+ } else {
+ AUTO_LOCK_CMD();
+ int offset = set_command_header_noopt(INS_DERIVE_VIEW_TAG);
+ //derivation
+ this->send_secret((unsigned char*)derivation.data, offset);
+ //index
+ this->buffer_send[offset+0] = output_index>>24;
+ this->buffer_send[offset+1] = output_index>>16;
+ this->buffer_send[offset+2] = output_index>>8;
+ this->buffer_send[offset+3] = output_index>>0;
+ offset += 4;
+
+ this->buffer_send[4] = offset-5;
+ this->length_send = offset;
+ this->exchange();
+
+ //view tag
+ memmove(&view_tag.data, &this->buffer_recv[0], 1);
+ }
+
+ #ifdef DEBUG_HWDEVICE
+ hw::ledger::check1("derive_view_tag", "view_tag", &view_tag_x.data, &view_tag.data);
+ #endif
+
+ return true;
+ }
+
/* ======================================================================= */
/* TRANSACTION */
/* ======================================================================= */
@@ -1544,7 +1601,6 @@ namespace hw {
const size_t output_index_x = output_index;
const bool need_additional_txkeys_x = need_additional_txkeys;
const bool use_view_tags_x = use_view_tags;
- const crypto::view_tag view_tag_x = view_tag;
std::vector<crypto::secret_key> additional_tx_keys_x;
for (const auto &k: additional_tx_keys) {
@@ -1554,6 +1610,7 @@ namespace hw {
std::vector<crypto::public_key> additional_tx_public_keys_x;
std::vector<rct::key> amount_keys_x;
crypto::public_key out_eph_public_key_x;
+ crypto::view_tag view_tag_x;
log_message ("generate_output_ephemeral_keys: [[IN]] tx_version", std::to_string(tx_version_x));
//log_hexbuffer("generate_output_ephemeral_keys: [[IN]] sender_account_keys.view", sender_account_keys.m_sview_secret_key.data, 32);
@@ -1571,11 +1628,15 @@ namespace hw {
if(need_additional_txkeys_x) {
log_hexbuffer("generate_output_ephemeral_keys: [[IN]] additional_tx_keys[oi]", additional_tx_keys_x[output_index].data, 32);
}
+ log_message ("generate_output_ephemeral_keys: [[IN]] use_view_tags", std::to_string(use_view_tags_x));
this->controle_device->generate_output_ephemeral_keys(tx_version_x, sender_account_keys_x, txkey_pub_x, tx_key_x, dst_entr_x, change_addr_x, output_index_x, need_additional_txkeys_x, additional_tx_keys_x,
additional_tx_public_keys_x, amount_keys_x, out_eph_public_key_x, use_view_tags_x, view_tag_x);
if(need_additional_txkeys_x) {
log_hexbuffer("additional_tx_public_keys_x: [[OUT]] additional_tx_public_keys_x", additional_tx_public_keys_x.back().data, 32);
}
+ if(use_view_tags_x) {
+ log_hexbuffer("generate_output_ephemeral_keys: [[OUT]] view_tag", &view_tag_x.data, 1);
+ }
log_hexbuffer("generate_output_ephemeral_keys: [[OUT]] amount_keys ", (char*)amount_keys_x.back().bytes, 32);
log_hexbuffer("generate_output_ephemeral_keys: [[OUT]] out_eph_public_key ", out_eph_public_key_x.data, 32);
#endif
@@ -1629,6 +1690,9 @@ namespace hw {
memset(&this->buffer_send[offset], 0, 32);
offset += 32;
}
+ //use_view_tags
+ this->buffer_send[offset] = use_view_tags;
+ offset++;
this->buffer_send[4] = offset-5;
this->length_send = offset;
@@ -1659,6 +1723,14 @@ namespace hw {
recv_len -= 32;
}
+ if (use_view_tags)
+ {
+ ASSERT_X(recv_len>=1, "Not enough data from device");
+ memmove(&view_tag.data, &this->buffer_recv[offset], 1);
+ offset++;
+ recv_len -= 1;
+ }
+
// add ABPkeys
this->add_output_key_mapping(dst_entr.addr.m_view_public_key, dst_entr.addr.m_spend_public_key, dst_entr.is_subaddress, is_change,
need_additional_txkeys, output_index,
@@ -1671,6 +1743,9 @@ namespace hw {
hw::ledger::check32("generate_output_ephemeral_keys", "additional_tx_key", additional_tx_public_keys_x.back().data, additional_tx_public_keys.back().data);
}
hw::ledger::check32("generate_output_ephemeral_keys", "out_eph_public_key", out_eph_public_key_x.data, out_eph_public_key.data);
+ if (use_view_tags) {
+ hw::ledger::check1("generate_output_ephemeral_keys", "view_tag", &view_tag_x.data, &view_tag.data);
+ }
#endif
return true;
@@ -1856,7 +1931,7 @@ namespace hw {
// ====== Aout, Bout, AKout, C, v, k ======
kv_offset = data_offset;
- if (type==rct::RCTTypeBulletproof2 || type==rct::RCTTypeCLSAG) {
+ if (type==rct::RCTTypeBulletproof2 || type==rct::RCTTypeCLSAG || type==rct::RCTTypeBulletproofPlus) {
C_offset = kv_offset+ (8)*outputs_size;
} else {
C_offset = kv_offset+ (32+32)*outputs_size;
@@ -1873,7 +1948,7 @@ namespace hw {
offset = set_command_header(INS_VALIDATE, 0x02, i+1);
//options
this->buffer_send[offset] = (i==outputs_size-1)? 0x00:0x80 ;
- this->buffer_send[offset] |= (type==rct::RCTTypeBulletproof2 || type==rct::RCTTypeCLSAG)?0x02:0x00;
+ this->buffer_send[offset] |= (type==rct::RCTTypeBulletproof2 || type==rct::RCTTypeCLSAG || type==rct::RCTTypeBulletproofPlus)?0x02:0x00;
offset += 1;
//is_subaddress
this->buffer_send[offset] = outKeys.is_subaddress;
@@ -1894,7 +1969,7 @@ namespace hw {
memmove(this->buffer_send+offset, data+C_offset,32);
offset += 32;
C_offset += 32;
- if (type==rct::RCTTypeBulletproof2 || type==rct::RCTTypeCLSAG) {
+ if (type==rct::RCTTypeBulletproof2 || type==rct::RCTTypeCLSAG || type==rct::RCTTypeBulletproofPlus) {
//k
memset(this->buffer_send+offset, 0, 32);
offset += 32;
diff --git a/src/device/device_ledger.hpp b/src/device/device_ledger.hpp
index 074bfaa8d..5a1e7d75c 100644
--- a/src/device/device_ledger.hpp
+++ b/src/device/device_ledger.hpp
@@ -44,7 +44,7 @@ namespace hw {
/* Minimal supported version */
#define MINIMAL_APP_VERSION_MAJOR 1
- #define MINIMAL_APP_VERSION_MINOR 6
+ #define MINIMAL_APP_VERSION_MINOR 8
#define MINIMAL_APP_VERSION_MICRO 0
#define VERSION(M,m,u) ((M)<<16|(m)<<8|(u))
@@ -87,10 +87,6 @@ namespace hw {
#define SW_PROTOCOL_NOT_SUPPORTED 0x6e00
#define SW_UNKNOWN 0x6f00
- namespace {
- bool apdu_verbose =true;
- }
-
void set_apdu_verbose(bool verbose);
class ABPkeys {
@@ -249,6 +245,7 @@ namespace hw {
bool derive_public_key(const crypto::key_derivation &derivation, const std::size_t output_index, const crypto::public_key &pub, crypto::public_key &derived_pub) override;
bool secret_key_to_public_key(const crypto::secret_key &sec, crypto::public_key &pub) override;
bool generate_key_image(const crypto::public_key &pub, const crypto::secret_key &sec, crypto::key_image &image) override;
+ bool derive_view_tag(const crypto::key_derivation &derivation, const size_t output_index, crypto::view_tag &view_tag) override;
/* ======================================================================= */
/* TRANSACTION */
diff --git a/src/device/log.cpp b/src/device/log.cpp
index 9b882b784..b159632d9 100644
--- a/src/device/log.cpp
+++ b/src/device/log.cpp
@@ -165,6 +165,10 @@ namespace hw {
void check8(const std::string &msg, const std::string &info, const char *h, const char *d, bool crypted) {
check(msg, info, h, d, 8, crypted);
}
+
+ void check1(const std::string &msg, const std::string &info, const char *h, const char *d, bool crypted) {
+ check(msg, info, h, d, 1, crypted);
+ }
#endif
}
diff --git a/src/device/log.hpp b/src/device/log.hpp
index 660adc63e..a0c89ec71 100644
--- a/src/device/log.hpp
+++ b/src/device/log.hpp
@@ -75,6 +75,7 @@ namespace hw {
void check32(const std::string &msg, const std::string &info, const char *h, const char *d, bool crypted=false);
void check8(const std::string &msg, const std::string &info, const char *h, const char *d, bool crypted=false);
+ void check1(const std::string &msg, const std::string &info, const char *h, const char *d, bool crypted=false);
void set_check_verbose(bool verbose);
#endif
diff --git a/src/device_trezor/device_trezor.cpp b/src/device_trezor/device_trezor.cpp
index 6f7ae9a6b..3f7b10be4 100644
--- a/src/device_trezor/device_trezor.cpp
+++ b/src/device_trezor/device_trezor.cpp
@@ -324,8 +324,8 @@ namespace trezor {
std::vector<protocol::ki::MoneroTransferDetails> mtds;
std::vector<protocol::ki::MoneroExportedKeyImage> kis;
- protocol::ki::key_image_data(wallet, transfers, mtds, client_version() <= 1);
- protocol::ki::generate_commitment(mtds, transfers, req, client_version() <= 1);
+ protocol::ki::key_image_data(wallet, transfers, mtds);
+ protocol::ki::generate_commitment(mtds, transfers, req);
EVENT_PROGRESS(0.);
this->set_msg_addr<messages::monero::MoneroKeyImageExportInitRequest>(req.get());
@@ -511,7 +511,7 @@ namespace trezor {
tools::wallet2::signed_tx_set & signed_tx,
hw::tx_aux_data & aux_data)
{
- CHECK_AND_ASSERT_THROW_MES(unsigned_tx.transfers.first == 0, "Unsuported non zero offset");
+ CHECK_AND_ASSERT_THROW_MES(std::get<0>(unsigned_tx.transfers) == 0, "Unsuported non zero offset");
TREZOR_AUTO_LOCK_CMD();
require_connected();
@@ -522,7 +522,7 @@ namespace trezor {
const size_t num_tx = unsigned_tx.txes.size();
m_num_transations_to_sign = num_tx;
signed_tx.key_images.clear();
- signed_tx.key_images.resize(unsigned_tx.transfers.second.size());
+ signed_tx.key_images.resize(std::get<2>(unsigned_tx.transfers).size());
for(size_t tx_idx = 0; tx_idx < num_tx; ++tx_idx) {
std::shared_ptr<protocol::tx::Signer> signer;
@@ -566,8 +566,8 @@ namespace trezor {
cpend.key_images = key_images;
// KI sync
- for(size_t cidx=0, trans_max=unsigned_tx.transfers.second.size(); cidx < trans_max; ++cidx){
- signed_tx.key_images[cidx] = unsigned_tx.transfers.second[cidx].m_key_image;
+ for(size_t cidx=0, trans_max=std::get<2>(unsigned_tx.transfers).size(); cidx < trans_max; ++cidx){
+ signed_tx.key_images[cidx] = std::get<2>(unsigned_tx.transfers)[cidx].m_key_image;
}
size_t num_sources = cdata.tx_data.sources.size();
@@ -579,9 +579,9 @@ namespace trezor {
CHECK_AND_ASSERT_THROW_MES(src_idx < cdata.tx.vin.size(), "Invalid idx_mapped");
size_t idx_map_src = cdata.tx_data.selected_transfers[idx_mapped];
- CHECK_AND_ASSERT_THROW_MES(idx_map_src >= unsigned_tx.transfers.first, "Invalid offset");
+ CHECK_AND_ASSERT_THROW_MES(idx_map_src >= std::get<0>(unsigned_tx.transfers), "Invalid offset");
- idx_map_src -= unsigned_tx.transfers.first;
+ idx_map_src -= std::get<0>(unsigned_tx.transfers);
CHECK_AND_ASSERT_THROW_MES(idx_map_src < signed_tx.key_images.size(), "Invalid key image index");
const auto vini = boost::get<cryptonote::txin_to_key>(cdata.tx.vin[src_idx]);
@@ -635,11 +635,7 @@ namespace trezor {
}
// Step: sort
- auto perm_req = signer->step_permutation();
- if (perm_req){
- auto perm_ack = this->client_exchange<messages::monero::MoneroTransactionInputsPermutationAck>(perm_req);
- signer->step_permutation_ack(perm_ack);
- }
+ signer->sort_ki();
EVENT_PROGRESS(3, 1, 1);
// Step: input_vini
@@ -697,13 +693,13 @@ namespace trezor {
unsigned device_trezor::client_version()
{
auto trezor_version = get_version();
- if (trezor_version <= pack_version(2, 0, 10)){
- throw exc::TrezorException("Trezor firmware 2.0.10 and lower are not supported. Please update.");
+ if (trezor_version < pack_version(2, 4, 3)){
+ throw exc::TrezorException("Minimal Trezor firmware version is 2.4.3. Please update.");
}
- unsigned client_version = 1;
- if (trezor_version >= pack_version(2, 3, 1)){
- client_version = 3;
+ unsigned client_version = 3;
+ if (trezor_version >= pack_version(2, 5, 2)){
+ client_version = 4;
}
#ifdef WITH_TREZOR_DEBUGGING
@@ -739,14 +735,6 @@ namespace trezor {
CHECK_AND_ASSERT_THROW_MES(init_msg, "TransactionInitRequest is empty");
CHECK_AND_ASSERT_THROW_MES(init_msg->has_tsx_data(), "TransactionInitRequest has no transaction data");
CHECK_AND_ASSERT_THROW_MES(m_features, "Device state not initialized"); // make sure the caller did not reset features
- const bool nonce_required = init_msg->tsx_data().has_payment_id() && init_msg->tsx_data().payment_id().size() > 0;
-
- if (nonce_required && init_msg->tsx_data().payment_id().size() == 8){
- // Versions 2.0.9 and lower do not support payment ID
- if (get_version() <= pack_version(2, 0, 9)) {
- throw exc::TrezorException("Trezor firmware 2.0.9 and lower does not support transactions with short payment IDs or integrated addresses. Please update.");
- }
- }
}
void device_trezor::transaction_check(const protocol::tx::TData & tdata, const hw::tx_aux_data & aux_data)
diff --git a/src/device_trezor/trezor/protocol.cpp b/src/device_trezor/trezor/protocol.cpp
index a400e82c7..0e59a16ba 100644
--- a/src/device_trezor/trezor/protocol.cpp
+++ b/src/device_trezor/trezor/protocol.cpp
@@ -38,6 +38,7 @@
#include <crypto/hmac-keccak.h>
#include <ringct/rctSigs.h>
#include <ringct/bulletproofs.h>
+#include <ringct/bulletproofs_plus.h>
#include "cryptonote_config.h"
#include <sodium.h>
#include <sodium/crypto_verify_32.h>
@@ -145,8 +146,7 @@ namespace ki {
bool key_image_data(wallet_shim * wallet,
const std::vector<tools::wallet2::transfer_details> & transfers,
- std::vector<MoneroTransferDetails> & res,
- bool need_all_additionals)
+ std::vector<MoneroTransferDetails> & res)
{
for(auto & td : transfers){
::crypto::public_key tx_pub_key = wallet->get_tx_pub_key_from_received_outs(td);
@@ -159,11 +159,7 @@ namespace ki {
cres.set_internal_output_index(td.m_internal_output_index);
cres.set_sub_addr_major(td.m_subaddr_index.major);
cres.set_sub_addr_minor(td.m_subaddr_index.minor);
- if (need_all_additionals) {
- for (auto &aux : additional_tx_pub_keys) {
- cres.add_additional_tx_pub_keys(key_to_string(aux));
- }
- } else if (!additional_tx_pub_keys.empty() && additional_tx_pub_keys.size() > td.m_internal_output_index) {
+ if (!additional_tx_pub_keys.empty() && additional_tx_pub_keys.size() > td.m_internal_output_index) {
cres.add_additional_tx_pub_keys(key_to_string(additional_tx_pub_keys[td.m_internal_output_index]));
}
}
@@ -194,8 +190,7 @@ namespace ki {
void generate_commitment(std::vector<MoneroTransferDetails> & mtds,
const std::vector<tools::wallet2::transfer_details> & transfers,
- std::shared_ptr<messages::monero::MoneroKeyImageExportInitRequest> & req,
- bool need_subaddr_indices)
+ std::shared_ptr<messages::monero::MoneroKeyImageExportInitRequest> & req)
{
req = std::make_shared<messages::monero::MoneroKeyImageExportInitRequest>();
@@ -219,16 +214,6 @@ namespace ki {
auto & st = search.first->second;
st.insert(cur.m_subaddr_index.minor);
}
-
- if (need_subaddr_indices) {
- for (auto &x: sub_indices) {
- auto subs = req->add_subs();
- subs->set_account(x.first);
- for (auto minor : x.second) {
- subs->add_minor_indices(minor);
- }
- }
- }
}
void live_refresh_ack(const ::crypto::secret_key & view_key_priv,
@@ -399,7 +384,7 @@ namespace tx {
m_tx_idx = tx_idx;
m_ct.tx_data = cur_src_tx();
m_multisig = false;
- m_client_version = 1;
+ m_client_version = 3;
}
void Signer::extract_payment_id(){
@@ -474,25 +459,19 @@ namespace tx {
auto & cur = src.outputs[i];
auto out = dst->add_outputs();
- if (i == src.real_output || need_ring_indices || client_version() <= 1) {
+ if (i == src.real_output || need_ring_indices) {
out->set_idx(cur.first);
}
- if (i == src.real_output || need_ring_keys || client_version() <= 1) {
+ if (i == src.real_output || need_ring_keys) {
translate_rct_key(out->mutable_key(), &(cur.second));
}
}
dst->set_real_out_tx_key(key_to_string(src.real_out_tx_key));
dst->set_real_output_in_tx_index(src.real_output_in_tx_index);
-
- if (client_version() <= 1) {
- for (auto &cur : src.real_out_additional_tx_keys) {
- dst->add_real_out_additional_tx_keys(key_to_string(cur));
- }
- } else if (!src.real_out_additional_tx_keys.empty()) {
+ if (!src.real_out_additional_tx_keys.empty()) {
dst->add_real_out_additional_tx_keys(key_to_string(src.real_out_additional_tx_keys.at(src.real_output_in_tx_index)));
}
-
dst->set_amount(src.amount);
dst->set_rct(src.rct);
dst->set_mask(key_to_string(src.mask));
@@ -532,7 +511,7 @@ namespace tx {
m_ct.tx.version = 2;
m_ct.tx.unlock_time = tx.unlock_time;
- m_client_version = (m_aux_data->client_version ? m_aux_data->client_version.get() : 1);
+ m_client_version = (m_aux_data->client_version ? m_aux_data->client_version.get() : 3);
tsx_data.set_version(1);
tsx_data.set_client_version(client_version());
@@ -543,18 +522,13 @@ namespace tx {
tsx_data.set_monero_version(std::string(MONERO_VERSION) + "|" + MONERO_VERSION_TAG);
tsx_data.set_hard_fork(m_aux_data->hard_fork ? m_aux_data->hard_fork.get() : 0);
- if (client_version() <= 1){
- assign_to_repeatable(tsx_data.mutable_minor_indices(), tx.subaddr_indices.begin(), tx.subaddr_indices.end());
- }
-
// Rsig decision
auto rsig_data = tsx_data.mutable_rsig_data();
m_ct.rsig_type = get_rsig_type(tx.rct_config, tx.splitted_dsts.size());
rsig_data->set_rsig_type(m_ct.rsig_type);
- if (tx.rct_config.range_proof_type != rct::RangeProofBorromean){
- m_ct.bp_version = (m_aux_data->bp_version ? m_aux_data->bp_version.get() : 1);
- rsig_data->set_bp_version((uint32_t) m_ct.bp_version);
- }
+ CHECK_AND_ASSERT_THROW_MES(tx.rct_config.range_proof_type != rct::RangeProofBorromean, "Borromean rsig not supported");
+ m_ct.bp_version = (m_aux_data->bp_version ? m_aux_data->bp_version.get() : 1);
+ rsig_data->set_bp_version((uint32_t) m_ct.bp_version);
generate_rsig_batch_sizes(m_ct.grouping_vct, m_ct.rsig_type, tx.splitted_dsts.size());
assign_to_repeatable(rsig_data->mutable_grouping(), m_ct.grouping_vct.begin(), m_ct.grouping_vct.end());
@@ -652,22 +626,6 @@ namespace tx {
});
}
- std::shared_ptr<messages::monero::MoneroTransactionInputsPermutationRequest> Signer::step_permutation(){
- sort_ki();
- if (client_version() >= 2){
- return nullptr;
- }
-
- auto res = std::make_shared<messages::monero::MoneroTransactionInputsPermutationRequest>();
- assign_to_repeatable(res->mutable_perm(), m_ct.source_permutation.begin(), m_ct.source_permutation.end());
-
- return res;
- }
-
- void Signer::step_permutation_ack(std::shared_ptr<const messages::monero::MoneroTransactionInputsPermutationAck> ack){
-
- }
-
std::shared_ptr<messages::monero::MoneroTransactionInputViniRequest> Signer::step_set_vini_input(size_t idx){
CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx_data.sources.size(), "Invalid transaction index");
CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx.vin.size(), "Invalid transaction index");
@@ -711,8 +669,10 @@ namespace tx {
}
void Signer::step_set_output_ack(std::shared_ptr<const messages::monero::MoneroTransactionSetOutputAck> ack){
+ CHECK_AND_ASSERT_THROW_MES(is_req_bulletproof(), "Borromean rsig not supported");
cryptonote::tx_out tx_out;
rct::Bulletproof bproof{};
+ rct::BulletproofPlus bproof_plus{};
rct::ctkey out_pk{};
rct::ecdhTuple ecdh{};
@@ -727,7 +687,7 @@ namespace tx {
rsig_buff = rsig_data.rsig();
}
- if (client_version() >= 1 && rsig_data.has_mask()){
+ if (rsig_data.has_mask()){
rct::key cmask{};
string_to_key(cmask, rsig_data.mask());
m_ct.rsig_gamma.emplace_back(cmask);
@@ -751,22 +711,32 @@ namespace tx {
memcpy(ecdh.amount.bytes, ack->ecdh_info().data(), 8);
}
- if (has_rsig && is_req_bulletproof() && !cn_deserialize(rsig_buff, bproof)){
- throw exc::ProtocolException("Cannot deserialize bulletproof rangesig");
- }
-
m_ct.tx.vout.emplace_back(tx_out);
m_ct.tx_out_hmacs.push_back(ack->vouti_hmac());
m_ct.tx_out_pk.emplace_back(out_pk);
m_ct.tx_out_ecdh.emplace_back(ecdh);
- // ClientV0, if no rsig was generated on Trezor, do not continue.
- // ClientV1+ generates BP after all masks in the current batch are generated
- if (!has_rsig || (client_version() >= 1 && is_offloading())){
+ rsig_v bp_obj{};
+ if (has_rsig) {
+ bool deserialize_success;
+ if (is_req_bulletproof_plus()) {
+ deserialize_success = cn_deserialize(rsig_buff, bproof_plus);
+ bp_obj = bproof_plus;
+ } else {
+ deserialize_success = cn_deserialize(rsig_buff, bproof);
+ bp_obj = bproof;
+ }
+ if (!deserialize_success) {
+ throw exc::ProtocolException("Cannot deserialize bulletproof rangesig");
+ }
+ }
+
+ // Generates BP after all masks in the current batch are generated
+ if (!has_rsig || is_offloading()){
return;
}
- process_bproof(bproof);
+ process_bproof(bp_obj);
m_ct.cur_batch_idx += 1;
m_ct.cur_output_in_batch_idx = 0;
}
@@ -791,13 +761,21 @@ namespace tx {
masks.push_back(m_ct.rsig_gamma[bidx]);
}
- auto bp = bulletproof_PROVE(amounts, masks);
- auto serRsig = cn_serialize(bp);
- m_ct.tx_out_rsigs.emplace_back(bp);
+ std::string serRsig;
+ if (is_req_bulletproof_plus()) {
+ auto bp = bulletproof_plus_PROVE(amounts, masks);
+ serRsig = cn_serialize(bp);
+ m_ct.tx_out_rsigs.emplace_back(bp);
+ } else {
+ auto bp = bulletproof_PROVE(amounts, masks);
+ serRsig = cn_serialize(bp);
+ m_ct.tx_out_rsigs.emplace_back(bp);
+ }
+
rsig_data.set_rsig(serRsig);
}
- void Signer::process_bproof(rct::Bulletproof & bproof){
+ void Signer::process_bproof(rsig_v & bproof){
CHECK_AND_ASSERT_THROW_MES(m_ct.cur_batch_idx < m_ct.grouping_vct.size(), "Invalid batch index");
auto batch_size = m_ct.grouping_vct[m_ct.cur_batch_idx];
for (size_t i = 0; i < batch_size; ++i){
@@ -806,12 +784,22 @@ namespace tx {
rct::key commitment = m_ct.tx_out_pk[bidx].mask;
commitment = rct::scalarmultKey(commitment, rct::INV_EIGHT);
- bproof.V.push_back(commitment);
+ if (is_req_bulletproof_plus()) {
+ boost::get<rct::BulletproofPlus>(bproof).V.push_back(commitment);
+ } else {
+ boost::get<rct::Bulletproof>(bproof).V.push_back(commitment);
+ }
}
m_ct.tx_out_rsigs.emplace_back(bproof);
- if (!rct::bulletproof_VERIFY(boost::get<rct::Bulletproof>(m_ct.tx_out_rsigs.back()))) {
- throw exc::ProtocolException("Returned range signature is invalid");
+ if (is_req_bulletproof_plus()) {
+ if (!rct::bulletproof_plus_VERIFY(boost::get<rct::BulletproofPlus>(m_ct.tx_out_rsigs.back()))) {
+ throw exc::ProtocolException("Returned range signature is invalid");
+ }
+ } else {
+ if (!rct::bulletproof_VERIFY(boost::get<rct::Bulletproof>(m_ct.tx_out_rsigs.back()))) {
+ throw exc::ProtocolException("Returned range signature is invalid");
+ }
}
}
@@ -840,6 +828,7 @@ namespace tx {
}
void Signer::step_all_outs_set_ack(std::shared_ptr<const messages::monero::MoneroTransactionAllOutSetAck> ack, hw::device &hwdev){
+ CHECK_AND_ASSERT_THROW_MES(is_req_bulletproof(), "Borromean rsig not supported");
m_ct.rv = std::make_shared<rct::rctSig>();
m_ct.rv->txnFee = ack->rv().txn_fee();
m_ct.rv->type = static_cast<uint8_t>(ack->rv().rv_type());
@@ -864,24 +853,15 @@ namespace tx {
// RctSig
auto num_sources = m_ct.tx_data.sources.size();
- if (is_simple() || is_req_bulletproof()){
- auto dst = &m_ct.rv->pseudoOuts;
- if (is_bulletproof()){
- dst = &m_ct.rv->p.pseudoOuts;
- }
-
- dst->clear();
- for (const auto &pseudo_out : m_ct.pseudo_outs) {
- dst->emplace_back();
- string_to_key(dst->back(), pseudo_out);
- }
-
- m_ct.rv->mixRing.resize(num_sources);
- } else {
- m_ct.rv->mixRing.resize(m_ct.tsx_data.mixin());
- m_ct.rv->mixRing[0].resize(num_sources);
+ auto dst = &m_ct.rv->p.pseudoOuts;
+ dst->clear();
+ for (const auto &pseudo_out : m_ct.pseudo_outs) {
+ dst->emplace_back();
+ string_to_key(dst->back(), pseudo_out);
}
+ m_ct.rv->mixRing.resize(num_sources);
+
CHECK_AND_ASSERT_THROW_MES(m_ct.tx_out_pk.size() == m_ct.tx_out_ecdh.size(), "Invalid vector sizes");
for(size_t i = 0; i < m_ct.tx_out_ecdh.size(); ++i){
m_ct.rv->outPk.push_back(m_ct.tx_out_pk[i]);
@@ -889,10 +869,10 @@ namespace tx {
}
for(size_t i = 0; i < m_ct.tx_out_rsigs.size(); ++i){
- if (is_bulletproof()){
- m_ct.rv->p.bulletproofs.push_back(boost::get<rct::Bulletproof>(m_ct.tx_out_rsigs[i]));
+ if (is_req_bulletproof_plus()) {
+ m_ct.rv->p.bulletproofs_plus.push_back(boost::get<rct::BulletproofPlus>(m_ct.tx_out_rsigs[i]));
} else {
- m_ct.rv->p.rangeSigs.push_back(boost::get<rct::rangeSig>(m_ct.tx_out_rsigs[i]));
+ m_ct.rv->p.bulletproofs.push_back(boost::get<rct::Bulletproof>(m_ct.tx_out_rsigs[i]));
}
}
@@ -936,8 +916,8 @@ namespace tx {
void Signer::step_sign_input_ack(std::shared_ptr<const messages::monero::MoneroTransactionSignInputAck> ack){
m_ct.signatures.push_back(ack->signature());
- // Sync updated pseudo_outputs, client_version>=1, HF10+
- if (client_version() >= 1 && ack->has_pseudo_out()){
+ // Sync updated pseudo_outputs
+ if (ack->has_pseudo_out()){
CHECK_AND_ASSERT_THROW_MES(m_ct.cur_input_idx < m_ct.pseudo_outs.size(), "Invalid pseudo-out index");
m_ct.pseudo_outs[m_ct.cur_input_idx] = ack->pseudo_out();
if (is_bulletproof()){
@@ -955,6 +935,8 @@ namespace tx {
}
void Signer::step_final_ack(std::shared_ptr<const messages::monero::MoneroTransactionFinalAck> ack){
+ CHECK_AND_ASSERT_THROW_MES(is_clsag(), "Only CLSAGs signatures are supported");
+
if (m_multisig){
auto & cout_key = ack->cout_key();
for(auto & cur : m_ct.couts){
@@ -975,47 +957,34 @@ namespace tx {
m_ct.enc_keys = ack->tx_enc_keys();
// Opening the sealed signatures
- if (client_version() >= 3){
- if(!ack->has_opening_key()){
- throw exc::ProtocolException("Client version 3+ requires sealed signatures");
- }
+ if(!ack->has_opening_key()){
+ throw exc::ProtocolException("Client version 3+ requires sealed signatures");
+ }
- for(size_t i = 0; i < m_ct.signatures.size(); ++i){
- CHECK_AND_ASSERT_THROW_MES(m_ct.signatures[i].size() > crypto::chacha::TAG_SIZE, "Invalid signature size");
- std::string nonce = compute_sealing_key(ack->opening_key(), i, true);
- std::string key = compute_sealing_key(ack->opening_key(), i, false);
- size_t plen = m_ct.signatures[i].size() - crypto::chacha::TAG_SIZE;
- std::unique_ptr<uint8_t[]> plaintext(new uint8_t[plen]);
- uint8_t * buff = plaintext.get();
-
- protocol::crypto::chacha::decrypt(
- m_ct.signatures[i].data(),
- m_ct.signatures[i].size(),
- reinterpret_cast<const uint8_t *>(key.data()),
- reinterpret_cast<const uint8_t *>(nonce.data()),
- reinterpret_cast<char *>(buff), &plen);
- m_ct.signatures[i].assign(reinterpret_cast<const char *>(buff), plen);
- }
+ for(size_t i = 0; i < m_ct.signatures.size(); ++i){
+ CHECK_AND_ASSERT_THROW_MES(m_ct.signatures[i].size() > crypto::chacha::TAG_SIZE, "Invalid signature size");
+ std::string nonce = compute_sealing_key(ack->opening_key(), i, true);
+ std::string key = compute_sealing_key(ack->opening_key(), i, false);
+ size_t plen = m_ct.signatures[i].size() - crypto::chacha::TAG_SIZE;
+ std::unique_ptr<uint8_t[]> plaintext(new uint8_t[plen]);
+ uint8_t * buff = plaintext.get();
+
+ protocol::crypto::chacha::decrypt(
+ m_ct.signatures[i].data(),
+ m_ct.signatures[i].size(),
+ reinterpret_cast<const uint8_t *>(key.data()),
+ reinterpret_cast<const uint8_t *>(nonce.data()),
+ reinterpret_cast<char *>(buff), &plen);
+ m_ct.signatures[i].assign(reinterpret_cast<const char *>(buff), plen);
}
- if (m_ct.rv->type == rct::RCTTypeCLSAG){
- m_ct.rv->p.CLSAGs.reserve(m_ct.signatures.size());
- for (size_t i = 0; i < m_ct.signatures.size(); ++i) {
- rct::clsag clsag;
- if (!cn_deserialize(m_ct.signatures[i], clsag)) {
- throw exc::ProtocolException("Cannot deserialize clsag[i]");
- }
- m_ct.rv->p.CLSAGs.push_back(clsag);
- }
- } else {
- m_ct.rv->p.MGs.reserve(m_ct.signatures.size());
- for (size_t i = 0; i < m_ct.signatures.size(); ++i) {
- rct::mgSig mg;
- if (!cn_deserialize(m_ct.signatures[i], mg)) {
- throw exc::ProtocolException("Cannot deserialize mg[i]");
- }
- m_ct.rv->p.MGs.push_back(mg);
+ m_ct.rv->p.CLSAGs.reserve(m_ct.signatures.size());
+ for (size_t i = 0; i < m_ct.signatures.size(); ++i) {
+ rct::clsag clsag;
+ if (!cn_deserialize(m_ct.signatures[i], clsag)) {
+ throw exc::ProtocolException("Cannot deserialize clsag[i]");
}
+ m_ct.rv->p.CLSAGs.push_back(clsag);
}
m_ct.tx.rct_signatures = *(m_ct.rv);
diff --git a/src/device_trezor/trezor/protocol.hpp b/src/device_trezor/trezor/protocol.hpp
index 858db1520..7ffadd9aa 100644
--- a/src/device_trezor/trezor/protocol.hpp
+++ b/src/device_trezor/trezor/protocol.hpp
@@ -116,8 +116,7 @@ namespace ki {
*/
bool key_image_data(wallet_shim * wallet,
const std::vector<tools::wallet2::transfer_details> & transfers,
- std::vector<MoneroTransferDetails> & res,
- bool need_all_additionals=false);
+ std::vector<MoneroTransferDetails> & res);
/**
* Computes a hash over MoneroTransferDetails. Commitment used in the KI sync.
@@ -129,8 +128,7 @@ namespace ki {
*/
void generate_commitment(std::vector<MoneroTransferDetails> & mtds,
const std::vector<tools::wallet2::transfer_details> & transfers,
- std::shared_ptr<messages::monero::MoneroKeyImageExportInitRequest> & req,
- bool need_subaddr_indices=false);
+ std::shared_ptr<messages::monero::MoneroKeyImageExportInitRequest> & req);
/**
* Processes Live refresh step response, parses KI, checks the signature
@@ -166,7 +164,7 @@ namespace tx {
::crypto::secret_key compute_enc_key(const ::crypto::secret_key & private_view_key, const std::string & aux, const std::string & salt);
std::string compute_sealing_key(const std::string & master_key, size_t idx, bool is_iv=false);
- typedef boost::variant<rct::rangeSig, rct::Bulletproof> rsig_v;
+ typedef boost::variant<rct::Bulletproof, rct::BulletproofPlus> rsig_v;
/**
* Transaction signer state holder.
@@ -232,8 +230,8 @@ namespace tx {
}
const tools::wallet2::transfer_details & get_transfer(size_t idx) const {
- CHECK_AND_ASSERT_THROW_MES(idx < m_unsigned_tx->transfers.second.size() + m_unsigned_tx->transfers.first && idx >= m_unsigned_tx->transfers.first, "Invalid transfer index");
- return m_unsigned_tx->transfers.second[idx - m_unsigned_tx->transfers.first];
+ CHECK_AND_ASSERT_THROW_MES(idx < std::get<2>(m_unsigned_tx->transfers).size() + std::get<0>(m_unsigned_tx->transfers) && idx >= std::get<0>(m_unsigned_tx->transfers), "Invalid transfer index");
+ return std::get<2>(m_unsigned_tx->transfers)[idx - std::get<0>(m_unsigned_tx->transfers)];
}
const tools::wallet2::transfer_details & get_source_transfer(size_t idx) const {
@@ -247,7 +245,7 @@ namespace tx {
void compute_integrated_indices(TsxData * tsx_data);
bool should_compute_bp_now() const;
void compute_bproof(messages::monero::MoneroTransactionRsigData & rsig_data);
- void process_bproof(rct::Bulletproof & bproof);
+ void process_bproof(rsig_v & bproof);
void set_tx_input(MoneroTransactionSourceEntry * dst, size_t idx, bool need_ring_keys=false, bool need_ring_indices=false);
public:
@@ -260,8 +258,6 @@ namespace tx {
void step_set_input_ack(std::shared_ptr<const messages::monero::MoneroTransactionSetInputAck> ack);
void sort_ki();
- std::shared_ptr<messages::monero::MoneroTransactionInputsPermutationRequest> step_permutation();
- void step_permutation_ack(std::shared_ptr<const messages::monero::MoneroTransactionInputsPermutationAck> ack);
std::shared_ptr<messages::monero::MoneroTransactionInputViniRequest> step_set_vini_input(size_t idx);
void step_set_vini_input_ack(std::shared_ptr<const messages::monero::MoneroTransactionInputViniAck> ack);
@@ -290,11 +286,15 @@ namespace tx {
return m_client_version;
}
- bool is_simple() const {
+ uint8_t get_rv_type() const {
if (!m_ct.rv){
throw std::invalid_argument("RV not initialized");
}
- auto tp = m_ct.rv->type;
+ return m_ct.rv->type;
+ }
+
+ bool is_simple() const {
+ auto tp = get_rv_type();
return tp == rct::RCTTypeSimple;
}
@@ -302,12 +302,27 @@ namespace tx {
return m_ct.tx_data.rct_config.range_proof_type != rct::RangeProofBorromean;
}
+ bool is_req_clsag() const {
+ return is_req_bulletproof() && m_ct.tx_data.rct_config.bp_version >= 3;
+ }
+
+ bool is_req_bulletproof_plus() const {
+ return is_req_bulletproof() && m_ct.tx_data.rct_config.bp_version == 4; // rct::genRctSimple
+ }
+
bool is_bulletproof() const {
- if (!m_ct.rv){
- throw std::invalid_argument("RV not initialized");
- }
- auto tp = m_ct.rv->type;
- return tp == rct::RCTTypeBulletproof || tp == rct::RCTTypeBulletproof2 || tp == rct::RCTTypeCLSAG;
+ auto tp = get_rv_type();
+ return rct::is_rct_bulletproof(tp) || rct::is_rct_bulletproof_plus(tp);
+ }
+
+ bool is_bulletproof_plus() const {
+ auto tp = get_rv_type();
+ return rct::is_rct_bulletproof_plus(tp);
+ }
+
+ bool is_clsag() const {
+ auto tp = get_rv_type();
+ return rct::is_rct_clsag(tp);
}
bool is_offloading() const {
diff --git a/src/gen_multisig/gen_multisig.cpp b/src/gen_multisig/gen_multisig.cpp
index f13e74b0f..eedd1511d 100644
--- a/src/gen_multisig/gen_multisig.cpp
+++ b/src/gen_multisig/gen_multisig.cpp
@@ -50,7 +50,6 @@
using namespace std;
using namespace epee;
using namespace cryptonote;
-using boost::lexical_cast;
namespace po = boost::program_options;
#undef MONERO_DEFAULT_LOG_CATEGORY
@@ -84,6 +83,9 @@ static bool generate_multisig(uint32_t threshold, uint32_t total, const std::str
try
{
+ if (total == 0)
+ throw std::runtime_error("Signer group of size 0 is not allowed.");
+
// create M wallets first
std::vector<boost::shared_ptr<tools::wallet2>> wallets(total);
for (size_t n = 0; n < total; ++n)
@@ -118,13 +120,17 @@ static bool generate_multisig(uint32_t threshold, uint32_t total, const std::str
ss << " " << name << std::endl;
}
- //exchange keys unless exchange_multisig_keys returns no extra info
- while (!kex_msgs_intermediate[0].empty())
+ // exchange keys until the wallets are done
+ bool ready{false};
+ wallets[0]->multisig(&ready);
+ while (!ready)
{
for (size_t n = 0; n < total; ++n)
{
kex_msgs_intermediate[n] = wallets[n]->exchange_multisig_keys(pwd_container->password(), kex_msgs_intermediate);
}
+
+ wallets[0]->multisig(&ready);
}
std::string address = wallets[0]->get_account().get_public_address_str(wallets[0]->nettype());
diff --git a/src/hardforks/hardforks.cpp b/src/hardforks/hardforks.cpp
index 0dd0cf5f0..336ef6519 100644
--- a/src/hardforks/hardforks.cpp
+++ b/src/hardforks/hardforks.cpp
@@ -71,8 +71,8 @@ const hardfork_t mainnet_hard_forks[] = {
{ 13, 2210000, 0, 1598180817 },
{ 14, 2210720, 0, 1598180818 },
- { 15, 8000000, 0, 1608223241 }, // temp so tests test with these consensus rules
- { 16, 8000001, 0, 1608223242 }, // temp so tests test with these consensus rules
+ { 15, 2688888, 0, 1656629117 },
+ { 16, 2689608, 0, 1656629118 },
};
const size_t num_mainnet_hard_forks = sizeof(mainnet_hard_forks) / sizeof(mainnet_hard_forks[0]);
const uint64_t mainnet_hard_fork_version_1_till = 1009826;
@@ -122,5 +122,7 @@ const hardfork_t stagenet_hard_forks[] = {
{ 12, 454721, 0, 1571419280 },
{ 13, 675405, 0, 1598180817 },
{ 14, 676125, 0, 1598180818 },
+ { 15, 1151000, 0, 1656629117 },
+ { 16, 1151720, 0, 1656629118 },
};
const size_t num_stagenet_hard_forks = sizeof(stagenet_hard_forks) / sizeof(stagenet_hard_forks[0]);
diff --git a/src/multisig/CMakeLists.txt b/src/multisig/CMakeLists.txt
index 294a1721f..61e658a39 100644
--- a/src/multisig/CMakeLists.txt
+++ b/src/multisig/CMakeLists.txt
@@ -30,7 +30,9 @@ set(multisig_sources
multisig.cpp
multisig_account.cpp
multisig_account_kex_impl.cpp
- multisig_kex_msg.cpp)
+ multisig_clsag_context.cpp
+ multisig_kex_msg.cpp
+ multisig_tx_builder_ringct.cpp)
set(multisig_headers)
@@ -48,6 +50,7 @@ target_link_libraries(multisig
PUBLIC
ringct
cryptonote_basic
+ cryptonote_core
common
cncrypto
PRIVATE
diff --git a/src/multisig/multisig_account.cpp b/src/multisig/multisig_account.cpp
index 9bdcf2dbc..401c6f1fe 100644
--- a/src/multisig/multisig_account.cpp
+++ b/src/multisig/multisig_account.cpp
@@ -127,7 +127,7 @@ namespace multisig
bool multisig_account::multisig_is_ready() const
{
if (main_kex_rounds_done())
- return m_kex_rounds_complete >= multisig_kex_rounds_required(m_signers.size(), m_threshold) + 1;
+ return m_kex_rounds_complete >= multisig_setup_rounds_required(m_signers.size(), m_threshold);
else
return false;
}
@@ -175,19 +175,20 @@ namespace multisig
// only mutate account if update succeeds
multisig_account temp_account{*this};
temp_account.set_multisig_config(threshold, std::move(signers));
- temp_account.kex_update_impl(expanded_msgs_rnd1);
+ temp_account.kex_update_impl(expanded_msgs_rnd1, false);
*this = std::move(temp_account);
}
//----------------------------------------------------------------------------------------------------------------------
// multisig_account: EXTERNAL
//----------------------------------------------------------------------------------------------------------------------
- void multisig_account::kex_update(const std::vector<multisig_kex_msg> &expanded_msgs)
+ void multisig_account::kex_update(const std::vector<multisig_kex_msg> &expanded_msgs,
+ const bool force_update_use_with_caution /*= false*/)
{
CHECK_AND_ASSERT_THROW_MES(account_is_active(), "multisig account: tried to update kex, but kex isn't initialized yet.");
CHECK_AND_ASSERT_THROW_MES(!multisig_is_ready(), "multisig account: tried to update kex, but kex is already complete.");
multisig_account temp_account{*this};
- temp_account.kex_update_impl(expanded_msgs);
+ temp_account.kex_update_impl(expanded_msgs, force_update_use_with_caution);
*this = std::move(temp_account);
}
//----------------------------------------------------------------------------------------------------------------------
@@ -200,4 +201,11 @@ namespace multisig
return num_signers - threshold + 1;
}
//----------------------------------------------------------------------------------------------------------------------
+ // EXTERNAL
+ //----------------------------------------------------------------------------------------------------------------------
+ std::uint32_t multisig_setup_rounds_required(const std::uint32_t num_signers, const std::uint32_t threshold)
+ {
+ return multisig_kex_rounds_required(num_signers, threshold) + 1;
+ }
+ //----------------------------------------------------------------------------------------------------------------------
} //namespace multisig
diff --git a/src/multisig/multisig_account.h b/src/multisig/multisig_account.h
index 7b372bbff..9cd0942d4 100644
--- a/src/multisig/multisig_account.h
+++ b/src/multisig/multisig_account.h
@@ -169,12 +169,20 @@ namespace multisig
* - The main interface for multisig key exchange, this handles all the work of processing input messages,
* creating new messages for new rounds, and finalizing the multisig shared public key when kex is complete.
* param: expanded_msgs - kex messages corresponding to the account's 'in progress' round
+ * param: force_update_use_with_caution - try to force the account to update with messages from an incomplete signer set.
+ * - If this is the post-kex verification round, only require one input message.
+ * - Force updating here should only be done if we can safely assume an honest signer subgroup of size 'threshold'
+ * will complete the account.
+ * - If this is an intermediate round, only require messages from 'num signers - 1 - (round - 1)' other signers.
+ * - If force updating with maliciously-crafted messages, the resulting account will be invalid (either unable
+ * to complete signatures, or a 'hostage' to the malicious signer [i.e. can't sign without his participation]).
*/
- void kex_update(const std::vector<multisig_kex_msg> &expanded_msgs);
+ void kex_update(const std::vector<multisig_kex_msg> &expanded_msgs,
+ const bool force_update_use_with_caution = false);
private:
// implementation of kex_update() (non-transactional)
- void kex_update_impl(const std::vector<multisig_kex_msg> &expanded_msgs);
+ void kex_update_impl(const std::vector<multisig_kex_msg> &expanded_msgs, const bool incomplete_signer_set);
/**
* brief: initialize_kex_update - Helper for kex_update_impl()
* - Collect the local signer's shared keys to ignore in incoming messages, build the aggregate ancillary key
@@ -245,4 +253,13 @@ namespace multisig
* return: number of kex rounds required
*/
std::uint32_t multisig_kex_rounds_required(const std::uint32_t num_signers, const std::uint32_t threshold);
+
+ /**
+ * brief: multisig_setup_rounds_required - The number of setup rounds required to produce an M-of-N shared key.
+ * - A participant must complete all kex rounds and 1 initialization round.
+ * param: num_signers - number of participants in multisig (N)
+ * param: threshold - threshold of multisig (M)
+ * return: number of setup rounds required
+ */
+ std::uint32_t multisig_setup_rounds_required(const std::uint32_t num_signers, const std::uint32_t threshold);
} //namespace multisig
diff --git a/src/multisig/multisig_account_kex_impl.cpp b/src/multisig/multisig_account_kex_impl.cpp
index be9ed9cb2..243058b81 100644
--- a/src/multisig/multisig_account_kex_impl.cpp
+++ b/src/multisig/multisig_account_kex_impl.cpp
@@ -74,7 +74,7 @@ namespace multisig
"Multisig threshold may not be larger than number of signers.");
CHECK_AND_ASSERT_THROW_MES(threshold > 0, "Multisig threshold must be > 0.");
CHECK_AND_ASSERT_THROW_MES(round > 0, "Multisig kex round must be > 0.");
- CHECK_AND_ASSERT_THROW_MES(round <= multisig_kex_rounds_required(num_signers, threshold) + 1,
+ CHECK_AND_ASSERT_THROW_MES(round <= multisig_setup_rounds_required(num_signers, threshold),
"Trying to process multisig kex for an invalid round.");
}
//----------------------------------------------------------------------------------------------------------------------
@@ -181,7 +181,8 @@ namespace multisig
* Key aggregation via aggregation coefficients prevents key cancellation attacks.
* See: https://www.getmonero.org/resources/research-lab/pubs/MRL-0009.pdf
* param: final_keys - address components (public keys) obtained from other participants (not shared with local)
- * param: privkeys_inout - private keys of address components known by local; each key will be multiplied by an aggregation coefficient (return by reference)
+ * param: privkeys_inout - private keys of address components known by local; each key will be multiplied by an aggregation
+ * coefficient (return by reference)
* return: final multisig public spend key for the account
*/
//----------------------------------------------------------------------------------------------------------------------
@@ -199,7 +200,8 @@ namespace multisig
for (std::size_t multisig_keys_index{0}; multisig_keys_index < privkeys_inout.size(); ++multisig_keys_index)
{
crypto::public_key pubkey;
- CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(privkeys_inout[multisig_keys_index], pubkey), "Failed to derive public key");
+ CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(privkeys_inout[multisig_keys_index], pubkey),
+ "Failed to derive public key");
own_keys_mapping[pubkey] = multisig_keys_index;
@@ -307,8 +309,7 @@ namespace multisig
* INTERNAL
*
* brief: multisig_kex_msgs_sanitize_pubkeys - Sanitize multisig kex messages.
- * - Removes duplicates from msg pubkeys, ignores pubkeys equal to the local account's signing key,
- * ignores messages signed by the local account, ignores keys found in input 'exclusion set',
+ * - Removes duplicates from msg pubkeys, ignores keys found in input 'exclusion set',
* constructs map of pubkey:origins.
* - Requires that all input msgs have the same round number.
*
@@ -316,15 +317,13 @@ namespace multisig
*
* - If the messages' round numbers are all '1', then only the message signing pubkey is considered
* 'recommended'. Furthermore, the 'exclusion set' is ignored.
- * param: own_pubkey - local account's signing key (key used to sign multisig messages)
* param: expanded_msgs - set of multisig kex messages to process
* param: exclude_pubkeys - pubkeys to exclude from output set
* outparam: sanitized_pubkeys_out - processed pubkeys obtained from msgs, mapped to their origins
* return: round number shared by all input msgs
*/
//----------------------------------------------------------------------------------------------------------------------
- static std::uint32_t multisig_kex_msgs_sanitize_pubkeys(const crypto::public_key &own_pubkey,
- const std::vector<multisig_kex_msg> &expanded_msgs,
+ static std::uint32_t multisig_kex_msgs_sanitize_pubkeys(const std::vector<multisig_kex_msg> &expanded_msgs,
const std::vector<crypto::public_key> &exclude_pubkeys,
multisig_keyset_map_memsafe_t &sanitized_pubkeys_out)
{
@@ -339,10 +338,6 @@ namespace multisig
// - origins = all the signing pubkeys that recommended a given msg pubkey
for (const auto &expanded_msg : expanded_msgs)
{
- // ignore messages from self
- if (expanded_msg.get_signing_pubkey() == own_pubkey)
- continue;
-
// in round 1, only the signing pubkey is treated as a msg pubkey
if (round == 1)
{
@@ -355,10 +350,6 @@ namespace multisig
// copy all pubkeys from message into list
for (const auto &pubkey : expanded_msg.get_msg_pubkeys())
{
- // ignore own pubkey
- if (pubkey == own_pubkey)
- continue;
-
// ignore pubkeys in 'ignore' set
if (std::find(exclude_pubkeys.begin(), exclude_pubkeys.end(), pubkey) != exclude_pubkeys.end())
continue;
@@ -375,6 +366,31 @@ namespace multisig
/**
* INTERNAL
*
+ * brief: remove_key_from_mapped_sets - Remove a specified key from the mapped sets in a multisig keyset map.
+ * param: key_to_remove - specified key to remove
+ * inoutparam: keyset_inout - keyset to update
+ */
+ //----------------------------------------------------------------------------------------------------------------------
+ static void remove_key_from_mapped_sets(const crypto::public_key &key_to_remove,
+ multisig_keyset_map_memsafe_t &keyset_inout)
+ {
+ // remove specified key from each mapped set
+ for (auto keyset_it = keyset_inout.begin(); keyset_it != keyset_inout.end();)
+ {
+ // remove specified key from this set
+ keyset_it->second.erase(key_to_remove);
+
+ // remove empty keyset positions or increment iterator
+ if (keyset_it->second.size() == 0)
+ keyset_it = keyset_inout.erase(keyset_it);
+ else
+ ++keyset_it;
+ }
+ }
+ //----------------------------------------------------------------------------------------------------------------------
+ /**
+ * INTERNAL
+ *
* brief: evaluate_multisig_kex_round_msgs - Evaluate pubkeys from a kex round in order to prepare for the next round.
* - Sanitizes input msgs.
* - Require uniqueness in: 'exclude_pubkeys'.
@@ -392,6 +408,8 @@ namespace multisig
* param: signers - expected participants in multisig kex
* param: expanded_msgs - set of multisig kex messages to process
* param: exclude_pubkeys - derivations held by the local account corresponding to round 'expected_round'
+ * param: incomplete_signer_set - only require the minimum number of signers to complete this round
+ * minimum = num_signers - (round num - 1) (including local signer)
* return: fully sanitized and validated pubkey:origins map for building the account's next kex round message
*/
//----------------------------------------------------------------------------------------------------------------------
@@ -400,7 +418,8 @@ namespace multisig
const std::uint32_t expected_round,
const std::vector<crypto::public_key> &signers,
const std::vector<multisig_kex_msg> &expanded_msgs,
- const std::vector<crypto::public_key> &exclude_pubkeys)
+ const std::vector<crypto::public_key> &exclude_pubkeys,
+ const bool incomplete_signer_set)
{
// exclude_pubkeys should all be unique
for (auto it = exclude_pubkeys.begin(); it != exclude_pubkeys.end(); ++it)
@@ -410,21 +429,31 @@ namespace multisig
}
// sanitize input messages
- multisig_keyset_map_memsafe_t pubkey_origins_map;
- const std::uint32_t round = multisig_kex_msgs_sanitize_pubkeys(base_pubkey, expanded_msgs, exclude_pubkeys, pubkey_origins_map);
+ multisig_keyset_map_memsafe_t pubkey_origins_map; //map: [pubkey : [origins]]
+ const std::uint32_t round = multisig_kex_msgs_sanitize_pubkeys(expanded_msgs, exclude_pubkeys, pubkey_origins_map);
CHECK_AND_ASSERT_THROW_MES(round == expected_round,
"Kex messages were for round [" << round << "], but expected round is [" << expected_round << "]");
+ // remove the local signer from each origins set in the sanitized pubkey map
+ // note: intermediate kex rounds only need keys from other signers to make progress (keys from self are useless)
+ remove_key_from_mapped_sets(base_pubkey, pubkey_origins_map);
+
// evaluate pubkeys collected
- std::unordered_map<crypto::public_key, std::unordered_set<crypto::public_key>> origin_pubkeys_map;
+ std::unordered_map<crypto::public_key, std::unordered_set<crypto::public_key>> origin_pubkeys_map; //map: [origin: [pubkeys]]
// 1. each pubkey should be recommended by a precise number of signers
+ const std::size_t num_recommendations_per_pubkey_required{
+ incomplete_signer_set
+ ? 1
+ : round
+ };
+
for (const auto &pubkey_and_origins : pubkey_origins_map)
{
// expected amount = round_num
// With each successive round, pubkeys are shared by incrementally larger groups,
// starting at 1 in round 1 (i.e. the local multisig key to start kex with).
- CHECK_AND_ASSERT_THROW_MES(pubkey_and_origins.second.size() == round,
+ CHECK_AND_ASSERT_THROW_MES(pubkey_and_origins.second.size() >= num_recommendations_per_pubkey_required,
"A pubkey recommended by multisig kex messages had an unexpected number of recommendations.");
// map (sanitized) pubkeys back to origins
@@ -433,8 +462,18 @@ namespace multisig
}
// 2. the number of unique signers recommending pubkeys should equal the number of signers passed in (minus the local signer)
- CHECK_AND_ASSERT_THROW_MES(origin_pubkeys_map.size() == signers.size() - 1,
- "Number of unique other signers does not equal number of other signers that recommended pubkeys.");
+ // - if an incomplete set is allowed, then we need at least one signer to represent each subgroup in this round that
+ // doesn't include the local signer
+ const std::size_t num_signers_required{
+ incomplete_signer_set
+ ? signers.size() - 1 - (round - 1)
+ : signers.size() - 1
+ };
+
+ CHECK_AND_ASSERT_THROW_MES(origin_pubkeys_map.size() >= num_signers_required,
+ "Number of unique other signers recommending pubkeys does not equal number of required other signers "
+ "(kex round: " << round << ", num signers found: " << origin_pubkeys_map.size() << ", num signers required: " <<
+ num_signers_required << ").");
// 3. each origin should recommend a precise number of pubkeys
@@ -461,19 +500,20 @@ namespace multisig
// other signers: (N - 2) choose (msg_round_num - 1)
// - Each signer recommends keys they share with other signers.
- // - In each round, a signer shares a key with 'round num - 1' other signers.
- // - Since 'origins pubkey map' excludes keys shared with the local account,
- // only keys shared with participants 'other than local and self' will be in the map (e.g. N - 2 signers).
- // - So other signers will recommend (N - 2) choose (msg_round_num - 1) pubkeys (after removing keys shared with local).
- // - Each origin should have a shared key with each group of size 'round - 1'.
- // Note: Keys shared with local are ignored to facilitate kex round boosting, where one or more signers may
+ // - In each round, every group of size 'round num' will have a key. From a single signer's perspective,
+ // they will share a key with every group of size 'round num - 1' of other signers.
+ // - Since 'origins pubkey map' excludes keys shared with the local account, only keys shared with participants
+ // 'other than local and self' will be in the map (e.g. N - 2 signers).
+ // - Other signers will recommend (N - 2) choose (msg_round_num - 1) pubkeys (after removing keys shared with local).
+ // Note: Keys shared with local are filtered out to facilitate kex round boosting, where one or more signers may
// have boosted the local signer (implying they didn't have access to the local signer's previous round msg).
const std::uint32_t expected_recommendations_others = n_choose_k_f(signers.size() - 2, round - 1);
// local: (N - 1) choose (msg_round_num - 1)
const std::uint32_t expected_recommendations_self = n_choose_k_f(signers.size() - 1, round - 1);
- // note: expected_recommendations_others would be 0 in the last round of 1-of-N, but we return early for that case
+ // note: expected_recommendations_others would be 0 in the last round of 1-of-N, but we don't call this function for
+ // that case
CHECK_AND_ASSERT_THROW_MES(expected_recommendations_self > 0 && expected_recommendations_others > 0,
"Bad num signers or round num (possibly numerical limits exceeded).");
@@ -485,7 +525,7 @@ namespace multisig
for (const auto &origin_and_pubkeys : origin_pubkeys_map)
{
CHECK_AND_ASSERT_THROW_MES(origin_and_pubkeys.second.size() == expected_recommendations_others,
- "A pubkey recommended by multisig kex messages had an unexpected number of recommendations.");
+ "A multisig signer recommended an unexpected number of pubkeys.");
// 2 (continued). only expected signers should be recommending keys
CHECK_AND_ASSERT_THROW_MES(std::find(signers.begin(), signers.end(), origin_and_pubkeys.first) != signers.end(),
@@ -507,6 +547,7 @@ namespace multisig
* param: expected_round - expected kex round of input messages
* param: signers - expected participants in multisig kex
* param: expanded_msgs - set of multisig kex messages to process
+ * param: incomplete_signer_set - only require the minimum amount of messages to complete this round (1 message)
* return: sanitized and validated pubkey:origins map
*/
//----------------------------------------------------------------------------------------------------------------------
@@ -514,15 +555,20 @@ namespace multisig
const crypto::public_key &base_pubkey,
const std::uint32_t expected_round,
const std::vector<crypto::public_key> &signers,
- const std::vector<multisig_kex_msg> &expanded_msgs)
+ const std::vector<multisig_kex_msg> &expanded_msgs,
+ const bool incomplete_signer_set)
{
// sanitize input messages
const std::vector<crypto::public_key> dummy;
- multisig_keyset_map_memsafe_t pubkey_origins_map;
- const std::uint32_t round = multisig_kex_msgs_sanitize_pubkeys(base_pubkey, expanded_msgs, dummy, pubkey_origins_map);
+ multisig_keyset_map_memsafe_t pubkey_origins_map; //map: [pubkey : [origins]]
+ const std::uint32_t round = multisig_kex_msgs_sanitize_pubkeys(expanded_msgs, dummy, pubkey_origins_map);
CHECK_AND_ASSERT_THROW_MES(round == expected_round,
"Kex messages were for round [" << round << "], but expected round is [" << expected_round << "]");
+ // note: do NOT remove the local signer from the pubkey origins map, since the post-kex round can be force-updated with
+ // just the local signer's post-kex message (if the local signer were removed, then the post-kex message's pubkeys
+ // would be completely lost)
+
// evaluate pubkeys collected
// 1) there should only be two pubkeys
@@ -533,17 +579,26 @@ namespace multisig
CHECK_AND_ASSERT_THROW_MES(pubkey_origins_map.begin()->second == (++(pubkey_origins_map.begin()))->second,
"Multisig post-kex round messages from other signers did not all recommend the same pubkey pair.");
- // 3) all signers should be present in the recommendation list
+ // 3) all signers should be present in the recommendation list (unless an incomplete list is permitted)
auto origins = pubkey_origins_map.begin()->second;
- origins.insert(base_pubkey); //add self
+ origins.insert(base_pubkey); //add self if missing
- CHECK_AND_ASSERT_THROW_MES(origins.size() == signers.size(),
- "Multisig post-kex round message origins don't line up with multisig signer set.");
+ const std::size_t num_signers_required{
+ incomplete_signer_set
+ ? 1
+ : signers.size()
+ };
+
+ CHECK_AND_ASSERT_THROW_MES(origins.size() >= num_signers_required,
+ "Multisig post-kex round message origins don't line up with multisig signer set "
+ "(num signers found: " << origins.size() << ", num signers required: " << num_signers_required << ").");
- for (const crypto::public_key &signer : signers)
+ for (const crypto::public_key &origin : origins)
{
- CHECK_AND_ASSERT_THROW_MES(origins.find(signer) != origins.end(),
- "Could not find an expected signer in multisig post-kex round messages (all signers expected).");
+ // note: if num_signers_required == signers.size(), then this test will ensure all signers are present in 'origins',
+ // which contains only unique pubkeys
+ CHECK_AND_ASSERT_THROW_MES(std::find(signers.begin(), signers.end(), origin) != signers.end(),
+ "An unknown origin recommended a multisig post-kex verification messsage.");
}
return pubkey_origins_map;
@@ -564,6 +619,7 @@ namespace multisig
* param: expanded_msgs - set of multisig kex messages to process
* param: exclude_pubkeys - keys held by the local account corresponding to round 'current_round'
* - If 'current_round' is the final round, these are the local account's shares of the final aggregate key.
+ * param: incomplete_signer_set - allow messages from an incomplete signer set
* outparam: keys_to_origins_map_out - map between round keys and identity keys
* - If in the final round, these are key shares recommended by other signers for the final aggregate key.
* - Otherwise, these are the local account's DH derivations for the next round.
@@ -578,6 +634,7 @@ namespace multisig
const std::vector<crypto::public_key> &signers,
const std::vector<multisig_kex_msg> &expanded_msgs,
const std::vector<crypto::public_key> &exclude_pubkeys,
+ const bool incomplete_signer_set,
multisig_keyset_map_memsafe_t &keys_to_origins_map_out)
{
check_multisig_config(current_round, threshold, signers.size());
@@ -598,7 +655,8 @@ namespace multisig
current_round,
signers,
expanded_msgs,
- exclude_pubkeys);
+ exclude_pubkeys,
+ incomplete_signer_set);
}
else //(current_round == kex_rounds_required + 1)
{
@@ -606,7 +664,8 @@ namespace multisig
evaluated_pubkeys = evaluate_multisig_post_kex_round_msgs(base_pubkey,
current_round,
signers,
- expanded_msgs);
+ expanded_msgs,
+ incomplete_signer_set);
}
// prepare keys-to-origins map for updating the multisig account
@@ -693,9 +752,9 @@ namespace multisig
{
// post-kex verification round: check that the multisig pubkey and common pubkey were recommended by other signers
CHECK_AND_ASSERT_THROW_MES(result_keys_to_origins_map.count(m_multisig_pubkey) > 0,
- "Multisig post-kex round: expected multisig pubkey wasn't found in other signers' messages.");
+ "Multisig post-kex round: expected multisig pubkey wasn't found in input messages.");
CHECK_AND_ASSERT_THROW_MES(result_keys_to_origins_map.count(m_common_pubkey) > 0,
- "Multisig post-kex round: expected common pubkey wasn't found in other signers' messages.");
+ "Multisig post-kex round: expected common pubkey wasn't found in input messages.");
// save keys that should be recommended to other signers
// - for convenience, re-recommend the post-kex verification message once an account is complete
@@ -790,7 +849,8 @@ namespace multisig
//----------------------------------------------------------------------------------------------------------------------
// multisig_account: INTERNAL
//----------------------------------------------------------------------------------------------------------------------
- void multisig_account::kex_update_impl(const std::vector<multisig_kex_msg> &expanded_msgs)
+ void multisig_account::kex_update_impl(const std::vector<multisig_kex_msg> &expanded_msgs,
+ const bool incomplete_signer_set)
{
// check messages are for the expected kex round
check_messages_round(expanded_msgs, m_kex_rounds_complete + 1);
@@ -816,6 +876,7 @@ namespace multisig
m_signers,
expanded_msgs,
exclude_pubkeys,
+ incomplete_signer_set,
result_keys_to_origins_map);
// finish account update
diff --git a/src/multisig/multisig_clsag_context.cpp b/src/multisig/multisig_clsag_context.cpp
new file mode 100644
index 000000000..e3417b896
--- /dev/null
+++ b/src/multisig/multisig_clsag_context.cpp
@@ -0,0 +1,257 @@
+// Copyright (c) 2021, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "multisig_clsag_context.h"
+
+#include "int-util.h"
+
+#include "crypto/crypto.h"
+#include "cryptonote_config.h"
+#include "ringct/rctOps.h"
+#include "ringct/rctTypes.h"
+
+#include <cstring>
+#include <string>
+#include <vector>
+
+#undef MONERO_DEFAULT_LOG_CATEGORY
+#define MONERO_DEFAULT_LOG_CATEGORY "multisig"
+
+namespace multisig {
+
+namespace signing {
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+template<std::size_t N>
+static rct::key string_to_key(const unsigned char (&str)[N]) {
+ rct::key tmp{};
+ static_assert(sizeof(tmp.bytes) >= N, "");
+ std::memcpy(tmp.bytes, str, N);
+ return tmp;
+}
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+static void encode_int_to_key_le(const unsigned int i, rct::key &k_out)
+{
+ static_assert(sizeof(unsigned int) <= sizeof(std::uint64_t), "unsigned int max too large");
+ static_assert(sizeof(std::uint64_t) <= sizeof(rct::key), "");
+ std::uint64_t temp_i{SWAP64LE(i)};
+ std::memcpy(k_out.bytes, &temp_i, sizeof(temp_i));
+}
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+bool CLSAG_context_t::init(
+ const rct::keyV& P,
+ const rct::keyV& C_nonzero,
+ const rct::key& C_offset,
+ const rct::key& message,
+ const rct::key& I,
+ const rct::key& D,
+ const unsigned int l,
+ const rct::keyV& s,
+ const std::size_t num_alpha_components
+)
+{
+ initialized = false;
+
+ n = P.size();
+ if (n <= 0)
+ return false;
+ if (C_nonzero.size() != n)
+ return false;
+ if (s.size() != n)
+ return false;
+ if (l >= n)
+ return false;
+
+ c_params.clear();
+ c_params.reserve(n * 2 + 5);
+ b_params.clear();
+ b_params.reserve(n * 3 + 2 * num_alpha_components + 7);
+
+ c_params.push_back(string_to_key(config::HASH_KEY_CLSAG_ROUND));
+ b_params.push_back(string_to_key(config::HASH_KEY_CLSAG_ROUND_MULTISIG));
+ c_params.insert(c_params.end(), P.begin(), P.end());
+ b_params.insert(b_params.end(), P.begin(), P.end());
+ c_params.insert(c_params.end(), C_nonzero.begin(), C_nonzero.end());
+ b_params.insert(b_params.end(), C_nonzero.begin(), C_nonzero.end());
+ c_params.emplace_back(C_offset);
+ b_params.emplace_back(C_offset);
+ c_params.emplace_back(message);
+ b_params.emplace_back(message);
+ c_params_L_offset = c_params.size();
+ b_params_L_offset = b_params.size();
+ c_params.resize(c_params.size() + 1); //this is where L will be inserted later
+ b_params.resize(b_params.size() + num_alpha_components); //multisig aggregate public nonces for L will be inserted here later
+ c_params_R_offset = c_params.size();
+ b_params_R_offset = b_params.size();
+ c_params.resize(c_params.size() + 1); //this is where R will be inserted later
+ b_params.resize(b_params.size() + num_alpha_components); //multisig aggregate public nonces for R will be inserted here later
+ b_params.emplace_back(I);
+ b_params.emplace_back(D);
+ b_params.insert(b_params.end(), s.begin(), s.begin() + l); //fake responses before 'l'
+ b_params.insert(b_params.end(), s.begin() + l + 1, s.end()); //fake responses after 'l'
+ b_params.emplace_back();
+ encode_int_to_key_le(l, b_params.back()); //real signing index 'l'
+ b_params.emplace_back();
+ encode_int_to_key_le(num_alpha_components, b_params.back()); //number of parallel nonces
+ b_params.emplace_back();
+ encode_int_to_key_le(n, b_params.back()); //number of ring members
+
+ rct::keyV mu_P_params;
+ rct::keyV mu_C_params;
+ mu_P_params.reserve(n * 2 + 4);
+ mu_C_params.reserve(n * 2 + 4);
+
+ mu_P_params.push_back(string_to_key(config::HASH_KEY_CLSAG_AGG_0));
+ mu_C_params.push_back(string_to_key(config::HASH_KEY_CLSAG_AGG_1));
+ mu_P_params.insert(mu_P_params.end(), P.begin(), P.end());
+ mu_C_params.insert(mu_C_params.end(), P.begin(), P.end());
+ mu_P_params.insert(mu_P_params.end(), C_nonzero.begin(), C_nonzero.end());
+ mu_C_params.insert(mu_C_params.end(), C_nonzero.begin(), C_nonzero.end());
+ mu_P_params.emplace_back(I);
+ mu_C_params.emplace_back(I);
+ mu_P_params.emplace_back(scalarmultKey(D, rct::INV_EIGHT));
+ mu_C_params.emplace_back(mu_P_params.back());
+ mu_P_params.emplace_back(C_offset);
+ mu_C_params.emplace_back(C_offset);
+ mu_P = hash_to_scalar(mu_P_params);
+ mu_C = hash_to_scalar(mu_C_params);
+
+ rct::geDsmp I_precomp;
+ rct::geDsmp D_precomp;
+ rct::precomp(I_precomp.k, I);
+ rct::precomp(D_precomp.k, D);
+ rct::key wH_l;
+ rct::addKeys3(wH_l, mu_P, I_precomp.k, mu_C, D_precomp.k);
+ rct::precomp(wH_l_precomp.k, wH_l);
+ W_precomp.resize(n);
+ H_precomp.resize(n);
+ for (std::size_t i = 0; i < n; ++i) {
+ rct::geDsmp P_precomp;
+ rct::geDsmp C_precomp;
+ rct::key C;
+ rct::subKeys(C, C_nonzero[i], C_offset);
+ rct::precomp(P_precomp.k, P[i]);
+ rct::precomp(C_precomp.k, C);
+ rct::key W;
+ rct::addKeys3(W, mu_P, P_precomp.k, mu_C, C_precomp.k);
+ rct::precomp(W_precomp[i].k, W);
+ ge_p3 Hi_p3;
+ rct::hash_to_p3(Hi_p3, P[i]);
+ ge_dsm_precomp(H_precomp[i].k, &Hi_p3);
+ }
+ rct::precomp(G_precomp.k, rct::G);
+ this->l = l;
+ this->s = s;
+ this->num_alpha_components = num_alpha_components;
+
+ initialized = true;
+ return true;
+}
+//----------------------------------------------------------------------------------------------------------------------
+bool CLSAG_context_t::combine_alpha_and_compute_challenge(
+ const rct::keyV& total_alpha_G,
+ const rct::keyV& total_alpha_H,
+ const rct::keyV& alpha,
+ rct::key& alpha_combined,
+ rct::key& c_0,
+ rct::key& c
+)
+{
+ if (not initialized)
+ return false;
+
+ if (num_alpha_components != total_alpha_G.size())
+ return false;
+ if (num_alpha_components != total_alpha_H.size())
+ return false;
+ if (num_alpha_components != alpha.size())
+ return false;
+
+ // insert aggregate public nonces for L and R components
+ for (std::size_t i = 0; i < num_alpha_components; ++i) {
+ b_params[b_params_L_offset + i] = total_alpha_G[i];
+ b_params[b_params_R_offset + i] = total_alpha_H[i];
+ }
+
+ // musig2-style combination factor 'b'
+ const rct::key b = rct::hash_to_scalar(b_params);
+
+ // 1) store combined public nonces in the 'L' and 'R' slots for computing the initial challenge
+ // - L = sum_i(b^i total_alpha_G[i])
+ // - R = sum_i(b^i total_alpha_H[i])
+ // 2) compute the local signer's combined private nonce
+ // - alpha_combined = sum_i(b^i * alpha[i])
+ rct::key& L_l = c_params[c_params_L_offset];
+ rct::key& R_l = c_params[c_params_R_offset];
+ rct::key b_i = rct::identity();
+ L_l = rct::identity();
+ R_l = rct::identity();
+ alpha_combined = rct::zero();
+ for (std::size_t i = 0; i < num_alpha_components; ++i) {
+ rct::addKeys(L_l, L_l, rct::scalarmultKey(total_alpha_G[i], b_i));
+ rct::addKeys(R_l, R_l, rct::scalarmultKey(total_alpha_H[i], b_i));
+ sc_muladd(alpha_combined.bytes, alpha[i].bytes, b_i.bytes, alpha_combined.bytes);
+ sc_mul(b_i.bytes, b_i.bytes, b.bytes);
+ }
+
+ // compute initial challenge from real spend components
+ c = rct::hash_to_scalar(c_params);
+
+ // 1) c_0: find the CLSAG's challenge for index '0', which will be stored in the proof
+ // note: in the CLSAG implementation in ringct/rctSigs, c_0 is denoted 'c1' (a notation error)
+ // 2) c: find the final challenge for the multisig signers to respond to
+ for (std::size_t i = (l + 1) % n; i != l; i = (i + 1) % n) {
+ if (i == 0)
+ c_0 = c;
+ rct::addKeys3(c_params[c_params_L_offset], s[i], G_precomp.k, c, W_precomp[i].k);
+ rct::addKeys3(c_params[c_params_R_offset], s[i], H_precomp[i].k, c, wH_l_precomp.k);
+ c = rct::hash_to_scalar(c_params);
+ }
+ if (l == 0)
+ c_0 = c;
+
+ return true;
+}
+//----------------------------------------------------------------------------------------------------------------------
+bool CLSAG_context_t::get_mu(
+ rct::key& mu_P,
+ rct::key& mu_C
+) const
+{
+ if (not initialized)
+ return false;
+ mu_P = this->mu_P;
+ mu_C = this->mu_C;
+ return true;
+}
+//----------------------------------------------------------------------------------------------------------------------
+} //namespace signing
+
+} //namespace multisig
diff --git a/src/multisig/multisig_clsag_context.h b/src/multisig/multisig_clsag_context.h
new file mode 100644
index 000000000..5017e8688
--- /dev/null
+++ b/src/multisig/multisig_clsag_context.h
@@ -0,0 +1,137 @@
+// Copyright (c) 2021, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+////
+// References
+// - CLSAG (base signature scheme): https://eprint.iacr.org/2019/654
+// - MuSig2 (style for multisig signing): https://eprint.iacr.org/2020/1261
+///
+
+
+#pragma once
+
+#include "ringct/rctTypes.h"
+
+#include <vector>
+
+
+namespace multisig {
+
+namespace signing {
+
+class CLSAG_context_t final {
+private:
+ // is the CLSAG context initialized?
+ bool initialized;
+ // challenge components: c = H(domain-separator, {P}, {C}, C_offset, message, L, R)
+ rct::keyV c_params;
+ // indices in c_params where L and R will be
+ std::size_t c_params_L_offset;
+ std::size_t c_params_R_offset;
+ // musig2-style nonce combination factor components for multisig signing
+ // b = H(domain-separator, {P}, {C}, C_offset, message, {L_combined_alphas}, {R_combined_alphas}, I, D, {s_non_l}, l, k, n)
+ // - {P} = ring of one-time addresses
+ // - {C} = ring of amount commitments (1:1 with one-time addresses)
+ // - C_offset = pseudo-output commitment to offset all amount commitments with
+ // - message = message the CLSAG will sign
+ // - {L_combined_alphas} = set of summed-together public nonces from all multisig signers for this CLSAG's L component
+ // - {R_combined_alphas} = set of summed-together public nonces from all multisig signers for this CLSAG's R component
+ // - I = key image for one-time address at {P}[l]
+ // - D = auxiliary key image for the offsetted amount commitment '{C}[l] - C_offset'
+ // - {s_non_l} = fake responses for this proof
+ // - l = real signing index in {P} and '{C} - C_offset'
+ // - k = number of parallel nonces that each participant provides
+ // - n = number of ring members
+ rct::keyV b_params;
+ // indices in b_params where L and R 'alpha' components will be
+ std::size_t b_params_L_offset;
+ std::size_t b_params_R_offset;
+ // CLSAG 'concise' coefficients for {P} and '{C} - C_offset'
+ // mu_x = H(domain-separator, {P}, {C}, I, (1/8)*D, C_offset)
+ // - note: 'D' is stored in the form '(1/8)*D' in transaction data
+ rct::key mu_P;
+ rct::key mu_C;
+ // ring size
+ std::size_t n;
+ // aggregate key image: mu_P*I + mu_C*D
+ rct::geDsmp wH_l_precomp;
+ // aggregate ring members: mu_P*P_i + mu_C*(C_i - C_offset)
+ std::vector<rct::geDsmp> W_precomp;
+ // key image component base keys: H_p(P_i)
+ std::vector<rct::geDsmp> H_precomp;
+ // cache for later: generator 'G' in 'precomp' representation
+ rct::geDsmp G_precomp;
+ // real signing index in this CLSAG
+ std::size_t l;
+ // signature responses
+ rct::keyV s;
+ // number of signing nonces expected per signer
+ std::size_t num_alpha_components;
+public:
+ CLSAG_context_t() : initialized{false} {}
+
+ // prepare CLSAG challenge context
+ bool init(
+ const rct::keyV& P,
+ const rct::keyV& C_nonzero,
+ const rct::key& C_offset,
+ const rct::key& message,
+ const rct::key& I,
+ const rct::key& D,
+ const unsigned int l,
+ const rct::keyV& s,
+ const std::size_t num_alpha_components
+ );
+
+ // get the local signer's combined musig2-style private nonce and compute the CLSAG challenge
+ bool combine_alpha_and_compute_challenge(
+ // set of summed-together musig2-style public nonces from all multisig signers for this CLSAG's L component
+ const rct::keyV& total_alpha_G,
+ // set of summed-together musig2-style public nonces from all multisig signers for this CLSAG's R component
+ const rct::keyV& total_alpha_H,
+ // local signer's private musig2-style nonces
+ const rct::keyV& alpha,
+ // local signer's final private nonce, using musig2-style combination with factor 'b'
+ // alpha_combined = sum_i(b^i * alpha[i])
+ rct::key& alpha_combined,
+ // CLSAG challenge to store in the proof
+ rct::key& c_0,
+ // final CLSAG challenge to respond to (need this to make multisig partial signatures)
+ rct::key& c
+ );
+
+ // getter for CLSAG 'concise' coefficients
+ bool get_mu(
+ rct::key& mu_P,
+ rct::key& mu_C
+ ) const;
+};
+
+} //namespace signing
+
+} //namespace multisig
diff --git a/src/multisig/multisig_tx_builder_ringct.cpp b/src/multisig/multisig_tx_builder_ringct.cpp
new file mode 100644
index 000000000..e5c9ac483
--- /dev/null
+++ b/src/multisig/multisig_tx_builder_ringct.cpp
@@ -0,0 +1,1059 @@
+// Copyright (c) 2021, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "multisig_tx_builder_ringct.h"
+
+#include "int-util.h"
+#include "memwipe.h"
+
+#include "cryptonote_basic/cryptonote_basic.h"
+#include "cryptonote_basic/account.h"
+#include "cryptonote_basic/cryptonote_format_utils.h"
+#include "cryptonote_config.h"
+#include "cryptonote_core/cryptonote_tx_utils.h"
+#include "device/device.hpp"
+#include "multisig_clsag_context.h"
+#include "ringct/bulletproofs.h"
+#include "ringct/bulletproofs_plus.h"
+#include "ringct/rctSigs.h"
+
+#include <boost/multiprecision/cpp_int.hpp>
+
+#include <algorithm>
+#include <cstring>
+#include <limits>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#undef MONERO_DEFAULT_LOG_CATEGORY
+#define MONERO_DEFAULT_LOG_CATEGORY "multisig"
+
+namespace multisig {
+
+namespace signing {
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+bool view_tag_required(const int bp_version)
+{
+ // view tags were introduced at the same time as BP+, so they are needed after BP+ (v4 and later)
+ if (bp_version <= 3)
+ return false;
+ else
+ return true;
+}
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+static void sort_sources(
+ std::vector<cryptonote::tx_source_entry>& sources
+)
+{
+ std::sort(sources.begin(), sources.end(), [](const auto& lhs, const auto& rhs){
+ const rct::key& ki0 = lhs.multisig_kLRki.ki;
+ const rct::key& ki1 = rhs.multisig_kLRki.ki;
+ return memcmp(&ki0, &ki1, sizeof(rct::key)) > 0;
+ });
+}
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+static bool compute_keys_for_sources(
+ const cryptonote::account_keys& account_keys,
+ const std::vector<cryptonote::tx_source_entry>& sources,
+ const std::uint32_t subaddr_account,
+ const std::set<std::uint32_t>& subaddr_minor_indices,
+ rct::keyV& input_secret_keys
+)
+{
+ const std::size_t num_sources = sources.size();
+ hw::device& hwdev = account_keys.get_device();
+ std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses;
+ for (const std::uint32_t minor_index: subaddr_minor_indices) {
+ subaddresses[hwdev.get_subaddress_spend_public_key(
+ account_keys,
+ {subaddr_account, minor_index}
+ )] = {subaddr_account, minor_index};
+ }
+ input_secret_keys.resize(num_sources);
+ for (std::size_t i = 0; i < num_sources; ++i) {
+ const auto& src = sources[i];
+ crypto::key_image tmp_key_image;
+ cryptonote::keypair tmp_keys;
+ if (src.real_output >= src.outputs.size())
+ return false;
+ if (not cryptonote::generate_key_image_helper(
+ account_keys,
+ subaddresses,
+ rct::rct2pk(src.outputs[src.real_output].second.dest),
+ src.real_out_tx_key,
+ src.real_out_additional_tx_keys,
+ src.real_output_in_tx_index,
+ tmp_keys,
+ tmp_key_image,
+ hwdev
+ )) {
+ return false;
+ }
+ input_secret_keys[i] = rct::sk2rct(tmp_keys.sec);
+ }
+ return true;
+}
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+static void shuffle_destinations(
+ std::vector<cryptonote::tx_destination_entry>& destinations
+)
+{
+ std::shuffle(destinations.begin(), destinations.end(), crypto::random_device{});
+}
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+static bool set_tx_extra(
+ const cryptonote::account_keys& account_keys,
+ const std::vector<cryptonote::tx_destination_entry>& destinations,
+ const cryptonote::tx_destination_entry& change,
+ const crypto::secret_key& tx_secret_key,
+ const crypto::public_key& tx_public_key,
+ const std::vector<crypto::public_key>& tx_aux_public_keys,
+ const std::vector<std::uint8_t>& extra,
+ cryptonote::transaction& tx
+)
+{
+ hw::device &hwdev = account_keys.get_device();
+ tx.extra = extra;
+ // if we have a stealth payment id, find it and encrypt it with the tx key now
+ std::vector<cryptonote::tx_extra_field> tx_extra_fields;
+ if (cryptonote::parse_tx_extra(tx.extra, tx_extra_fields))
+ {
+ bool add_dummy_payment_id = true;
+ cryptonote::tx_extra_nonce extra_nonce;
+ if (cryptonote::find_tx_extra_field_by_type(tx_extra_fields, extra_nonce))
+ {
+ crypto::hash payment_id = crypto::null_hash;
+ crypto::hash8 payment_id8 = crypto::null_hash8;
+ if (cryptonote::get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8))
+ {
+ LOG_PRINT_L2("Encrypting payment id " << payment_id8);
+ crypto::public_key view_key_pub = cryptonote::get_destination_view_key_pub(destinations, change.addr);
+ if (view_key_pub == crypto::null_pkey)
+ {
+ // valid combinations:
+ // - 1 output with encrypted payment ID, dummy change output (0 amount)
+ // - 0 outputs, 1 change output with encrypted payment ID
+ // - 1 output with encrypted payment ID, 1 change output
+ LOG_ERROR("Destinations have to have exactly one output to support encrypted payment ids");
+ return false;
+ }
+
+ if (!hwdev.encrypt_payment_id(payment_id8, view_key_pub, tx_secret_key))
+ {
+ LOG_ERROR("Failed to encrypt payment id");
+ return false;
+ }
+
+ std::string extra_nonce_updated;
+ cryptonote::set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce_updated, payment_id8);
+ cryptonote::remove_field_from_tx_extra(tx.extra, typeid(cryptonote::tx_extra_nonce));
+ if (!cryptonote::add_extra_nonce_to_tx_extra(tx.extra, extra_nonce_updated))
+ {
+ LOG_ERROR("Failed to add encrypted payment id to tx extra");
+ return false;
+ }
+ LOG_PRINT_L1("Encrypted payment ID: " << payment_id8);
+ add_dummy_payment_id = false;
+ }
+ else if (cryptonote::get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id))
+ {
+ add_dummy_payment_id = false;
+ }
+ }
+
+ // we don't add one if we've got more than the usual 1 destination plus change
+ if (destinations.size() > 2)
+ add_dummy_payment_id = false;
+
+ if (add_dummy_payment_id)
+ {
+ // if we have neither long nor short payment id, add a dummy short one,
+ // this should end up being the vast majority of txes as time goes on
+ std::string extra_nonce_updated;
+ crypto::hash8 payment_id8 = crypto::null_hash8;
+ crypto::public_key view_key_pub = cryptonote::get_destination_view_key_pub(destinations, change.addr);
+ if (view_key_pub == crypto::null_pkey)
+ {
+ LOG_ERROR("Failed to get key to encrypt dummy payment id with");
+ }
+ else
+ {
+ hwdev.encrypt_payment_id(payment_id8, view_key_pub, tx_secret_key);
+ cryptonote::set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce_updated, payment_id8);
+ if (!cryptonote::add_extra_nonce_to_tx_extra(tx.extra, extra_nonce_updated))
+ {
+ LOG_ERROR("Failed to add dummy encrypted payment id to tx extra");
+ // continue anyway
+ }
+ }
+ }
+ }
+ else
+ {
+ MWARNING("Failed to parse tx extra");
+ tx_extra_fields.clear();
+ }
+
+ cryptonote::remove_field_from_tx_extra(tx.extra, typeid(cryptonote::tx_extra_pub_key));
+ cryptonote::add_tx_pub_key_to_extra(tx.extra, tx_public_key);
+ cryptonote::remove_field_from_tx_extra(tx.extra, typeid(cryptonote::tx_extra_additional_pub_keys));
+ LOG_PRINT_L2("tx pubkey: " << tx_public_key);
+ if (tx_aux_public_keys.size())
+ {
+ LOG_PRINT_L2("additional tx pubkeys: ");
+ for (size_t i = 0; i < tx_aux_public_keys.size(); ++i)
+ LOG_PRINT_L2(tx_aux_public_keys[i]);
+ cryptonote::add_additional_tx_pub_keys_to_extra(tx.extra, tx_aux_public_keys);
+ }
+ if (not cryptonote::sort_tx_extra(tx.extra, tx.extra))
+ return false;
+ return true;
+}
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+static void make_tx_secret_key_seed(const crypto::secret_key& tx_secret_key_entropy,
+ const std::vector<cryptonote::tx_source_entry>& sources,
+ crypto::secret_key& tx_secret_key_seed)
+{
+ // seed = H(H("domain separator"), entropy, {KI})
+ static const std::string domain_separator{config::HASH_KEY_MULTISIG_TX_PRIVKEYS_SEED};
+
+ rct::keyV hash_context;
+ hash_context.reserve(2 + sources.size());
+ auto hash_context_wiper = epee::misc_utils::create_scope_leave_handler([&]{
+ memwipe(hash_context.data(), hash_context.size());
+ });
+ hash_context.emplace_back();
+ rct::cn_fast_hash(hash_context.back(), domain_separator.data(), domain_separator.size()); //domain sep
+ hash_context.emplace_back(rct::sk2rct(tx_secret_key_entropy)); //entropy
+
+ for (const cryptonote::tx_source_entry& source : sources)
+ hash_context.emplace_back(source.multisig_kLRki.ki); //{KI}
+
+ // set the seed
+ tx_secret_key_seed = rct::rct2sk(rct::cn_fast_hash(hash_context));
+}
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+static void make_tx_secret_keys(const crypto::secret_key& tx_secret_key_seed,
+ const std::size_t num_tx_keys,
+ std::vector<crypto::secret_key>& tx_secret_keys)
+{
+ // make tx secret keys as a hash chain of the seed
+ // h1 = H_n(seed || H("domain separator"))
+ // h2 = H_n(seed || h1)
+ // h3 = H_n(seed || h2)
+ // ...
+ static const std::string domain_separator{config::HASH_KEY_MULTISIG_TX_PRIVKEYS};
+
+ rct::keyV hash_context;
+ hash_context.resize(2);
+ auto hash_context_wiper = epee::misc_utils::create_scope_leave_handler([&]{
+ memwipe(hash_context.data(), hash_context.size());
+ });
+ hash_context[0] = rct::sk2rct(tx_secret_key_seed);
+ rct::cn_fast_hash(hash_context[1], domain_separator.data(), domain_separator.size());
+
+ tx_secret_keys.clear();
+ tx_secret_keys.resize(num_tx_keys);
+
+ for (crypto::secret_key& tx_secret_key : tx_secret_keys)
+ {
+ // advance the hash chain
+ hash_context[1] = rct::hash_to_scalar(hash_context);
+
+ // set this key
+ tx_secret_key = rct::rct2sk(hash_context[1]);
+ }
+}
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+static bool collect_tx_secret_keys(const std::vector<crypto::secret_key>& tx_secret_keys,
+ crypto::secret_key& tx_secret_key,
+ std::vector<crypto::secret_key>& tx_aux_secret_keys)
+{
+ if (tx_secret_keys.size() == 0)
+ return false;
+
+ tx_secret_key = tx_secret_keys[0];
+ tx_aux_secret_keys.clear();
+ tx_aux_secret_keys.reserve(tx_secret_keys.size() - 1);
+ for (std::size_t tx_key_index{1}; tx_key_index < tx_secret_keys.size(); ++tx_key_index)
+ tx_aux_secret_keys.emplace_back(tx_secret_keys[tx_key_index]);
+
+ return true;
+}
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+static bool compute_keys_for_destinations(
+ const cryptonote::account_keys& account_keys,
+ const std::uint32_t subaddr_account,
+ const std::vector<cryptonote::tx_destination_entry>& destinations,
+ const cryptonote::tx_destination_entry& change,
+ const std::vector<std::uint8_t>& extra,
+ const bool use_view_tags,
+ const bool reconstruction,
+ const crypto::secret_key& tx_secret_key_seed,
+ crypto::secret_key& tx_secret_key,
+ std::vector<crypto::secret_key>& tx_aux_secret_keys,
+ rct::keyV& output_public_keys,
+ rct::keyV& output_amount_secret_keys,
+ std::vector<crypto::view_tag>& view_tags,
+ cryptonote::transaction& unsigned_tx
+)
+{
+ hw::device &hwdev = account_keys.get_device();
+
+ // check non-zero change amount case
+ if (change.amount > 0)
+ {
+ // the change output must be directed to the local account
+ if (change.addr != hwdev.get_subaddress(account_keys, {subaddr_account}))
+ return false;
+
+ // expect the change destination to be in the destination set
+ if (std::find_if(destinations.begin(), destinations.end(),
+ [&change](const auto &destination) -> bool
+ {
+ return destination.addr == change.addr;
+ }) == destinations.end())
+ return false;
+ }
+
+ // collect non-change recipients into normal/subaddress buckets
+ std::unordered_set<cryptonote::account_public_address> unique_subbaddr_recipients;
+ std::unordered_set<cryptonote::account_public_address> unique_std_recipients;
+ for(const auto& dst_entr: destinations) {
+ if (dst_entr.addr == change.addr)
+ continue;
+ if (dst_entr.is_subaddress)
+ unique_subbaddr_recipients.insert(dst_entr.addr);
+ else
+ unique_std_recipients.insert(dst_entr.addr);
+ }
+
+ // figure out how many tx secret keys are needed
+ // - tx aux keys: add if there are > 1 non-change recipients, with at least one to a subaddress
+ const std::size_t num_destinations = destinations.size();
+ const bool need_tx_aux_keys = unique_subbaddr_recipients.size() + bool(unique_std_recipients.size()) > 1;
+
+ const std::size_t num_tx_keys = 1 + (need_tx_aux_keys ? num_destinations : 0);
+
+ // make tx secret keys
+ std::vector<crypto::secret_key> all_tx_secret_keys;
+ make_tx_secret_keys(tx_secret_key_seed, num_tx_keys, all_tx_secret_keys);
+
+ // split up tx secret keys
+ crypto::secret_key tx_secret_key_temp;
+ std::vector<crypto::secret_key> tx_aux_secret_keys_temp;
+ if (not collect_tx_secret_keys(all_tx_secret_keys, tx_secret_key_temp, tx_aux_secret_keys_temp))
+ return false;
+
+ if (reconstruction)
+ {
+ // when reconstructing, the tx secret keys should be reproducible from input seed
+ if (!(tx_secret_key == tx_secret_key_temp))
+ return false;
+ if (!(tx_aux_secret_keys == tx_aux_secret_keys_temp))
+ return false;
+ }
+ else
+ {
+ tx_secret_key = tx_secret_key_temp;
+ tx_aux_secret_keys = std::move(tx_aux_secret_keys_temp);
+ }
+
+ // tx pub key: R
+ crypto::public_key tx_public_key;
+ if (unique_std_recipients.empty() && unique_subbaddr_recipients.size() == 1) {
+ // if there is exactly 1 non-change recipient, and it's to a subaddress, then the tx pubkey = r*Ksi_nonchange_recipient
+ tx_public_key = rct::rct2pk(
+ hwdev.scalarmultKey(
+ rct::pk2rct(unique_subbaddr_recipients.begin()->m_spend_public_key),
+ rct::sk2rct(tx_secret_key)
+ ));
+ }
+ else {
+ // otherwise, the tx pub key = r*G
+ // - if there are > 1 non-change recipients, with at least one to a subaddress, then the tx pubkey is not used
+ // (additional tx keys will be used instead)
+ // - if all non-change recipients are to normal addresses, then the tx pubkey will be used by all recipients
+ // (including change recipient, even if change is to a subaddress)
+ tx_public_key = rct::rct2pk(hwdev.scalarmultBase(rct::sk2rct(tx_secret_key)));
+ }
+
+ // additional tx pubkeys: R_t
+ output_public_keys.resize(num_destinations);
+ view_tags.resize(num_destinations);
+ std::vector<crypto::public_key> tx_aux_public_keys;
+ crypto::public_key temp_output_public_key;
+
+ for (std::size_t i = 0; i < num_destinations; ++i) {
+ if (not hwdev.generate_output_ephemeral_keys(
+ unsigned_tx.version,
+ account_keys,
+ tx_public_key,
+ tx_secret_key,
+ destinations[i],
+ change.addr,
+ i,
+ need_tx_aux_keys,
+ tx_aux_secret_keys,
+ tx_aux_public_keys,
+ output_amount_secret_keys,
+ temp_output_public_key,
+ use_view_tags,
+ view_tags[i] //unused variable if use_view_tags is not set
+ )) {
+ return false;
+ }
+ output_public_keys[i] = rct::pk2rct(temp_output_public_key);
+ }
+
+ if (num_destinations != output_amount_secret_keys.size())
+ return false;
+
+ CHECK_AND_ASSERT_MES(
+ tx_aux_public_keys.size() == tx_aux_secret_keys.size(),
+ false,
+ "Internal error creating additional public keys"
+ );
+
+ if (not set_tx_extra(account_keys, destinations, change, tx_secret_key, tx_public_key, tx_aux_public_keys, extra, unsigned_tx))
+ return false;
+
+ return true;
+}
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+static void set_tx_inputs(
+ const std::vector<cryptonote::tx_source_entry>& sources,
+ cryptonote::transaction& unsigned_tx
+)
+{
+ const std::size_t num_sources = sources.size();
+ unsigned_tx.vin.resize(num_sources);
+ for (std::size_t i = 0; i < num_sources; ++i) {
+ std::vector<std::uint64_t> offsets;
+ offsets.reserve(sources[i].outputs.size());
+ for (const auto& e: sources[i].outputs)
+ offsets.emplace_back(e.first);
+ unsigned_tx.vin[i] = cryptonote::txin_to_key{
+ .amount = 0,
+ .key_offsets = cryptonote::absolute_output_offsets_to_relative(offsets),
+ .k_image = rct::rct2ki(sources[i].multisig_kLRki.ki),
+ };
+ }
+}
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+static bool onetime_addresses_are_unique(const rct::keyV& output_public_keys)
+{
+ for (auto addr_it = output_public_keys.begin(); addr_it != output_public_keys.end(); ++addr_it)
+ {
+ if (std::find(output_public_keys.begin(), addr_it, *addr_it) != addr_it)
+ return false;
+ }
+
+ return true;
+}
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+static bool set_tx_outputs(const rct::keyV& output_public_keys, cryptonote::transaction& unsigned_tx)
+{
+ // sanity check: all onetime addresses should be unique
+ if (not onetime_addresses_are_unique(output_public_keys))
+ return false;
+
+ // set the tx outputs
+ const std::size_t num_destinations = output_public_keys.size();
+ unsigned_tx.vout.resize(num_destinations);
+ for (std::size_t i = 0; i < num_destinations; ++i)
+ cryptonote::set_tx_out(0, rct::rct2pk(output_public_keys[i]), false, crypto::view_tag{}, unsigned_tx.vout[i]);
+
+ return true;
+}
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+static bool set_tx_outputs_with_view_tags(
+ const rct::keyV& output_public_keys,
+ const std::vector<crypto::view_tag>& view_tags,
+ cryptonote::transaction& unsigned_tx
+)
+{
+ // sanity check: all onetime addresses should be unique
+ if (not onetime_addresses_are_unique(output_public_keys))
+ return false;
+
+ // set the tx outputs (with view tags)
+ const std::size_t num_destinations = output_public_keys.size();
+ CHECK_AND_ASSERT_MES(view_tags.size() == num_destinations, false,
+ "multisig signing protocol: internal error, view tag size mismatch.");
+ unsigned_tx.vout.resize(num_destinations);
+ for (std::size_t i = 0; i < num_destinations; ++i)
+ cryptonote::set_tx_out(0, rct::rct2pk(output_public_keys[i]), true, view_tags[i], unsigned_tx.vout[i]);
+
+ return true;
+}
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+static void make_new_range_proofs(const int bp_version,
+ const std::vector<std::uint64_t>& output_amounts,
+ const rct::keyV& output_amount_masks,
+ rct::rctSigPrunable& sigs)
+{
+ sigs.bulletproofs.clear();
+ sigs.bulletproofs_plus.clear();
+
+ if (bp_version == 3)
+ sigs.bulletproofs.push_back(rct::bulletproof_PROVE(output_amounts, output_amount_masks));
+ else if (bp_version == 4)
+ sigs.bulletproofs_plus.push_back(rct::bulletproof_plus_PROVE(output_amounts, output_amount_masks));
+}
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+static bool try_reconstruct_range_proofs(const int bp_version,
+ const rct::rctSigPrunable& original_sigs,
+ const std::size_t num_destinations,
+ const rct::ctkeyV& output_public_keys,
+ rct::rctSigPrunable& reconstructed_sigs)
+{
+ auto try_reconstruct_range_proofs =
+ [&](const auto &original_range_proofs, auto &new_range_proofs) -> bool
+ {
+ if (original_range_proofs.size() != 1)
+ return false;
+
+ new_range_proofs = original_range_proofs;
+ new_range_proofs[0].V.resize(num_destinations);
+ for (std::size_t i = 0; i < num_destinations; ++i)
+ new_range_proofs[0].V[i] = rct::scalarmultKey(output_public_keys[i].mask, rct::INV_EIGHT);
+
+ return true;
+ };
+
+ if (bp_version == 3)
+ {
+ if (not try_reconstruct_range_proofs(original_sigs.bulletproofs, reconstructed_sigs.bulletproofs))
+ return false;
+ return rct::bulletproof_VERIFY(reconstructed_sigs.bulletproofs);
+ }
+ else if (bp_version == 4)
+ {
+ if (not try_reconstruct_range_proofs(original_sigs.bulletproofs_plus, reconstructed_sigs.bulletproofs_plus))
+ return false;
+ return rct::bulletproof_plus_VERIFY(reconstructed_sigs.bulletproofs_plus);
+ }
+
+ return false;
+}
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+static bool set_tx_rct_signatures(
+ const std::uint64_t fee,
+ const std::vector<cryptonote::tx_source_entry>& sources,
+ const std::vector<cryptonote::tx_destination_entry>& destinations,
+ const rct::keyV& input_secret_keys,
+ const rct::keyV& output_public_keys,
+ const rct::keyV& output_amount_secret_keys,
+ const rct::RCTConfig& rct_config,
+ const bool reconstruction,
+ cryptonote::transaction& unsigned_tx,
+ std::vector<CLSAG_context_t>& CLSAG_contexts,
+ rct::keyV& cached_w
+)
+{
+ if (rct_config.bp_version != 3 &&
+ rct_config.bp_version != 4)
+ return false;
+ if (rct_config.range_proof_type != rct::RangeProofPaddedBulletproof)
+ return false;
+
+ const std::size_t num_destinations = destinations.size();
+ const std::size_t num_sources = sources.size();
+
+ // rct_signatures component of tx
+ rct::rctSig rv{};
+
+ // set misc. fields
+ if (rct_config.bp_version == 3)
+ rv.type = rct::RCTTypeCLSAG;
+ else if (rct_config.bp_version == 4)
+ rv.type = rct::RCTTypeBulletproofPlus;
+ else
+ return false;
+ rv.txnFee = fee;
+ rv.message = rct::hash2rct(cryptonote::get_transaction_prefix_hash(unsigned_tx));
+
+ // define outputs
+ std::vector<std::uint64_t> output_amounts(num_destinations);
+ rct::keyV output_amount_masks(num_destinations);
+ rv.ecdhInfo.resize(num_destinations);
+ rv.outPk.resize(num_destinations);
+ for (std::size_t i = 0; i < num_destinations; ++i) {
+ rv.outPk[i].dest = output_public_keys[i];
+ output_amounts[i] = destinations[i].amount;
+ output_amount_masks[i] = genCommitmentMask(output_amount_secret_keys[i]);
+ rv.ecdhInfo[i].amount = rct::d2h(output_amounts[i]);
+ rct::addKeys2(
+ rv.outPk[i].mask,
+ output_amount_masks[i],
+ rv.ecdhInfo[i].amount,
+ rct::H
+ );
+ rct::ecdhEncode(rv.ecdhInfo[i], output_amount_secret_keys[i], true);
+ }
+
+ // output range proofs
+ if (not reconstruction) {
+ make_new_range_proofs(rct_config.bp_version, output_amounts, output_amount_masks, rv.p);
+ }
+ else {
+ if (not try_reconstruct_range_proofs(rct_config.bp_version,
+ unsigned_tx.rct_signatures.p,
+ num_destinations,
+ rv.outPk,
+ rv.p))
+ return false;
+ }
+
+ // prepare rings for input CLSAGs
+ rv.mixRing.resize(num_sources);
+ for (std::size_t i = 0; i < num_sources; ++i) {
+ const std::size_t ring_size = sources[i].outputs.size();
+ rv.mixRing[i].resize(ring_size);
+ for (std::size_t j = 0; j < ring_size; ++j) {
+ rv.mixRing[i][j].dest = sources[i].outputs[j].second.dest;
+ rv.mixRing[i][j].mask = sources[i].outputs[j].second.mask;
+ }
+ }
+
+ // make pseudo-output commitments
+ rct::keyV a; //pseudo-output commitment blinding factors
+ auto a_wiper = epee::misc_utils::create_scope_leave_handler([&]{
+ memwipe(static_cast<rct::key *>(a.data()), a.size() * sizeof(rct::key));
+ });
+ if (not reconstruction) {
+ a.resize(num_sources);
+ rv.p.pseudoOuts.resize(num_sources);
+ a[num_sources - 1] = rct::zero();
+ for (std::size_t i = 0; i < num_destinations; ++i) {
+ sc_add(
+ a[num_sources - 1].bytes,
+ a[num_sources - 1].bytes,
+ output_amount_masks[i].bytes
+ );
+ }
+ for (std::size_t i = 0; i < num_sources - 1; ++i) {
+ rct::skGen(a[i]);
+ sc_sub(
+ a[num_sources - 1].bytes,
+ a[num_sources - 1].bytes,
+ a[i].bytes
+ );
+ rct::genC(rv.p.pseudoOuts[i], a[i], sources[i].amount);
+ }
+ rct::genC(
+ rv.p.pseudoOuts[num_sources - 1],
+ a[num_sources - 1],
+ sources[num_sources - 1].amount
+ );
+ }
+ // check balance if reconstructing the tx
+ else {
+ rv.p.pseudoOuts = unsigned_tx.rct_signatures.p.pseudoOuts;
+ if (num_sources != rv.p.pseudoOuts.size())
+ return false;
+ rct::key balance_accumulator = rct::scalarmultH(rct::d2h(fee));
+ for (const auto& e: rv.outPk)
+ rct::addKeys(balance_accumulator, balance_accumulator, e.mask);
+ for (const auto& pseudoOut: rv.p.pseudoOuts)
+ rct::subKeys(balance_accumulator, balance_accumulator, pseudoOut);
+ if (not (balance_accumulator == rct::identity()))
+ return false;
+ }
+
+ // prepare input CLSAGs for signing
+ const rct::key message = get_pre_mlsag_hash(rv, hw::get_device("default"));
+
+ rv.p.CLSAGs.resize(num_sources);
+ if (reconstruction) {
+ if (num_sources != unsigned_tx.rct_signatures.p.CLSAGs.size())
+ return false;
+ }
+
+ CLSAG_contexts.resize(num_sources);
+ if (not reconstruction)
+ cached_w.resize(num_sources);
+
+ for (std::size_t i = 0; i < num_sources; ++i) {
+ const std::size_t ring_size = rv.mixRing[i].size();
+ const rct::key& I = sources[i].multisig_kLRki.ki;
+ const std::size_t l = sources[i].real_output;
+ if (l >= ring_size)
+ return false;
+ rct::keyV& s = rv.p.CLSAGs[i].s;
+ const rct::key& C_offset = rv.p.pseudoOuts[i];
+ rct::keyV P(ring_size);
+ rct::keyV C_nonzero(ring_size);
+
+ if (not reconstruction) {
+ s.resize(ring_size);
+ for (std::size_t j = 0; j < ring_size; ++j) {
+ if (j != l)
+ s[j] = rct::skGen(); //make fake responses
+ }
+ }
+ else {
+ if (ring_size != unsigned_tx.rct_signatures.p.CLSAGs[i].s.size())
+ return false;
+ s = unsigned_tx.rct_signatures.p.CLSAGs[i].s;
+ }
+
+ for (std::size_t j = 0; j < ring_size; ++j) {
+ P[j] = rv.mixRing[i][j].dest;
+ C_nonzero[j] = rv.mixRing[i][j].mask;
+ }
+
+ rct::key D;
+ rct::key z;
+ auto z_wiper = epee::misc_utils::create_scope_leave_handler([&]{
+ memwipe(static_cast<rct::key *>(&z), sizeof(rct::key));
+ });
+ if (not reconstruction) {
+ sc_sub(z.bytes, sources[i].mask.bytes, a[i].bytes); //commitment to zero privkey
+ ge_p3 H_p3;
+ rct::hash_to_p3(H_p3, rv.mixRing[i][l].dest);
+ rct::key H_l;
+ ge_p3_tobytes(H_l.bytes, &H_p3);
+ D = rct::scalarmultKey(H_l, z); //auxilliary key image (for commitment to zero)
+ rv.p.CLSAGs[i].D = rct::scalarmultKey(D, rct::INV_EIGHT);
+ rv.p.CLSAGs[i].I = I;
+ }
+ else {
+ rv.p.CLSAGs[i].D = unsigned_tx.rct_signatures.p.CLSAGs[i].D;
+ rv.p.CLSAGs[i].I = I;
+ D = rct::scalarmultKey(rv.p.CLSAGs[i].D, rct::EIGHT);
+ }
+
+ if (not CLSAG_contexts[i].init(P, C_nonzero, C_offset, message, I, D, l, s, kAlphaComponents))
+ return false;
+
+ if (not reconstruction) {
+ rct::key mu_P;
+ rct::key mu_C;
+ if (not CLSAG_contexts[i].get_mu(mu_P, mu_C))
+ return false;
+ sc_mul(cached_w[i].bytes, mu_P.bytes, input_secret_keys[i].bytes);
+ sc_muladd(cached_w[i].bytes, mu_C.bytes, z.bytes, cached_w[i].bytes);
+ }
+ }
+ unsigned_tx.rct_signatures = std::move(rv);
+ return true;
+}
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+static bool compute_tx_fee(
+ const std::vector<cryptonote::tx_source_entry>& sources,
+ const std::vector<cryptonote::tx_destination_entry>& destinations,
+ std::uint64_t& fee
+)
+{
+ boost::multiprecision::uint128_t in_amount = 0;
+ for (const auto& src: sources)
+ in_amount += src.amount;
+
+ boost::multiprecision::uint128_t out_amount = 0;
+ for (const auto& dst: destinations)
+ out_amount += dst.amount;
+
+ if (out_amount > in_amount)
+ return false;
+
+ if (in_amount - out_amount > std::numeric_limits<std::uint64_t>::max())
+ return false;
+
+ fee = static_cast<std::uint64_t>(in_amount - out_amount);
+ return true;
+}
+//----------------------------------------------------------------------------------------------------------------------
+tx_builder_ringct_t::tx_builder_ringct_t(): initialized(false) {}
+//----------------------------------------------------------------------------------------------------------------------
+tx_builder_ringct_t::~tx_builder_ringct_t()
+{
+ memwipe(static_cast<rct::key *>(cached_w.data()), cached_w.size() * sizeof(rct::key));
+}
+//----------------------------------------------------------------------------------------------------------------------
+bool tx_builder_ringct_t::init(
+ const cryptonote::account_keys& account_keys,
+ const std::vector<std::uint8_t>& extra,
+ const std::uint64_t unlock_time,
+ const std::uint32_t subaddr_account,
+ const std::set<std::uint32_t>& subaddr_minor_indices,
+ std::vector<cryptonote::tx_source_entry>& sources,
+ std::vector<cryptonote::tx_destination_entry>& destinations,
+ const cryptonote::tx_destination_entry& change,
+ const rct::RCTConfig& rct_config,
+ const bool use_rct,
+ const bool reconstruction,
+ crypto::secret_key& tx_secret_key,
+ std::vector<crypto::secret_key>& tx_aux_secret_keys,
+ crypto::secret_key& tx_secret_key_entropy,
+ cryptonote::transaction& unsigned_tx
+)
+{
+ initialized = false;
+ this->reconstruction = reconstruction;
+ if (not use_rct)
+ return false;
+ if (sources.empty())
+ return false;
+
+ if (not reconstruction)
+ unsigned_tx.set_null();
+
+ std::uint64_t fee;
+ if (not compute_tx_fee(sources, destinations, fee))
+ return false;
+
+ // decide if view tags are needed
+ const bool use_view_tags{view_tag_required(rct_config.bp_version)};
+
+ // misc. fields
+ unsigned_tx.version = 2; //rct = 2
+ unsigned_tx.unlock_time = unlock_time;
+
+ // sort inputs
+ sort_sources(sources);
+
+ // prepare tx secret key seed (must be AFTER sorting sources)
+ // - deriving the seed from sources plus entropy ensures uniqueness for every new tx attempt
+ // - the goal is that two multisig txs added to the chain will never have outputs with the same onetime addresses,
+ // which would burn funds (embedding the inputs' key images guarantees this)
+ // - it is acceptable if two tx attempts use the same input set and entropy (only a malicious tx proposer will do
+ // that, but all it can accomplish is leaking information about the recipients - which a malicious proposer can
+ // easily do outside the signing ritual anyway)
+ if (not reconstruction)
+ tx_secret_key_entropy = rct::rct2sk(rct::skGen());
+
+ // expect not null (note: wallet serialization code may set this to null if handling an old partial tx)
+ if (tx_secret_key_entropy == crypto::null_skey)
+ return false;
+
+ crypto::secret_key tx_secret_key_seed;
+ make_tx_secret_key_seed(tx_secret_key_entropy, sources, tx_secret_key_seed);
+
+ // get secret keys for signing input CLSAGs (multisig: or for the initial partial signature)
+ rct::keyV input_secret_keys;
+ auto input_secret_keys_wiper = epee::misc_utils::create_scope_leave_handler([&]{
+ memwipe(static_cast<rct::key *>(input_secret_keys.data()), input_secret_keys.size() * sizeof(rct::key));
+ });
+ if (not compute_keys_for_sources(account_keys, sources, subaddr_account, subaddr_minor_indices, input_secret_keys))
+ return false;
+
+ // randomize output order
+ if (not reconstruction)
+ shuffle_destinations(destinations);
+
+ // prepare outputs
+ rct::keyV output_public_keys;
+ rct::keyV output_amount_secret_keys;
+ std::vector<crypto::view_tag> view_tags;
+ auto output_amount_secret_keys_wiper = epee::misc_utils::create_scope_leave_handler([&]{
+ memwipe(static_cast<rct::key *>(output_amount_secret_keys.data()), output_amount_secret_keys.size() * sizeof(rct::key));
+ });
+ if (not compute_keys_for_destinations(account_keys,
+ subaddr_account,
+ destinations,
+ change,
+ extra,
+ use_view_tags,
+ reconstruction,
+ tx_secret_key_seed,
+ tx_secret_key,
+ tx_aux_secret_keys,
+ output_public_keys,
+ output_amount_secret_keys,
+ view_tags,
+ unsigned_tx))
+ return false;
+
+ // add inputs to tx
+ set_tx_inputs(sources, unsigned_tx);
+
+ // add output one-time addresses to tx
+ bool set_tx_outputs_result{false};
+ if (use_view_tags)
+ set_tx_outputs_result = set_tx_outputs_with_view_tags(output_public_keys, view_tags, unsigned_tx);
+ else
+ set_tx_outputs_result = set_tx_outputs(output_public_keys, unsigned_tx);
+
+ if (not set_tx_outputs_result)
+ return false;
+
+ // prepare input signatures
+ if (not set_tx_rct_signatures(fee, sources, destinations, input_secret_keys, output_public_keys, output_amount_secret_keys,
+ rct_config, reconstruction, unsigned_tx, CLSAG_contexts, cached_w))
+ return false;
+
+ initialized = true;
+ return true;
+}
+//----------------------------------------------------------------------------------------------------------------------
+bool tx_builder_ringct_t::first_partial_sign(
+ const std::size_t source,
+ const rct::keyV& total_alpha_G,
+ const rct::keyV& total_alpha_H,
+ const rct::keyV& alpha,
+ rct::key& c_0,
+ rct::key& s
+)
+{
+ if (not initialized or reconstruction)
+ return false;
+ const std::size_t num_sources = CLSAG_contexts.size();
+ if (source >= num_sources)
+ return false;
+ rct::key c;
+ rct::key alpha_combined;
+ auto alpha_combined_wiper = epee::misc_utils::create_scope_leave_handler([&]{
+ memwipe(static_cast<rct::key *>(&alpha_combined), sizeof(rct::key));
+ });
+ if (not CLSAG_contexts[source].combine_alpha_and_compute_challenge(
+ total_alpha_G,
+ total_alpha_H,
+ alpha,
+ alpha_combined,
+ c_0,
+ c
+ )) {
+ return false;
+ }
+
+ // initial partial response:
+ // s = alpha_combined_local - challenge*[mu_P*(local keys and sender-receiver secret and subaddress material) +
+ // mu_C*(commitment-to-zero secret)]
+ sc_mulsub(s.bytes, c.bytes, cached_w[source].bytes, alpha_combined.bytes);
+ return true;
+}
+//----------------------------------------------------------------------------------------------------------------------
+bool tx_builder_ringct_t::next_partial_sign(
+ const rct::keyM& total_alpha_G,
+ const rct::keyM& total_alpha_H,
+ const rct::keyM& alpha,
+ const rct::key& x,
+ rct::keyV& c_0,
+ rct::keyV& s
+)
+{
+ if (not initialized or not reconstruction)
+ return false;
+ const std::size_t num_sources = CLSAG_contexts.size();
+ if (num_sources != total_alpha_G.size())
+ return false;
+ if (num_sources != total_alpha_H.size())
+ return false;
+ if (num_sources != alpha.size())
+ return false;
+ if (num_sources != c_0.size())
+ return false;
+ if (num_sources != s.size())
+ return false;
+ for (std::size_t i = 0; i < num_sources; ++i) {
+ rct::key c;
+ rct::key alpha_combined;
+ auto alpha_combined_wiper = epee::misc_utils::create_scope_leave_handler([&]{
+ memwipe(static_cast<rct::key *>(&alpha_combined), sizeof(rct::key));
+ });
+ if (not CLSAG_contexts[i].combine_alpha_and_compute_challenge(
+ total_alpha_G[i],
+ total_alpha_H[i],
+ alpha[i],
+ alpha_combined,
+ c_0[i],
+ c
+ )) {
+ return false;
+ }
+ rct::key mu_P;
+ rct::key mu_C;
+ if (not CLSAG_contexts[i].get_mu(mu_P, mu_C))
+ return false;
+ rct::key w;
+ auto w_wiper = epee::misc_utils::create_scope_leave_handler([&]{
+ memwipe(static_cast<rct::key *>(&w), sizeof(rct::key));
+ });
+ sc_mul(w.bytes, mu_P.bytes, x.bytes);
+
+ // include local signer's response:
+ // s += alpha_combined_local - challenge*[mu_P*(local keys)]
+ sc_add(s[i].bytes, s[i].bytes, alpha_combined.bytes);
+ sc_mulsub(s[i].bytes, c.bytes, w.bytes, s[i].bytes);
+ }
+ return true;
+}
+//----------------------------------------------------------------------------------------------------------------------
+bool tx_builder_ringct_t::finalize_tx(
+ const std::vector<cryptonote::tx_source_entry>& sources,
+ const rct::keyV& c_0,
+ const rct::keyV& s,
+ cryptonote::transaction& unsigned_tx
+)
+{
+ // checks
+ const std::size_t num_sources = sources.size();
+ if (num_sources != unsigned_tx.rct_signatures.p.CLSAGs.size())
+ return false;
+ if (num_sources != c_0.size())
+ return false;
+ if (num_sources != s.size())
+ return false;
+
+ // finalize tx signatures
+ for (std::size_t i = 0; i < num_sources; ++i) {
+ const std::size_t ring_size = unsigned_tx.rct_signatures.p.CLSAGs[i].s.size();
+ if (sources[i].real_output >= ring_size)
+ return false;
+ unsigned_tx.rct_signatures.p.CLSAGs[i].s[sources[i].real_output] = s[i];
+ unsigned_tx.rct_signatures.p.CLSAGs[i].c1 = c_0[i];
+ }
+
+ return true;
+}
+//----------------------------------------------------------------------------------------------------------------------
+} //namespace signing
+
+} //namespace multisig
diff --git a/src/multisig/multisig_tx_builder_ringct.h b/src/multisig/multisig_tx_builder_ringct.h
new file mode 100644
index 000000000..853934659
--- /dev/null
+++ b/src/multisig/multisig_tx_builder_ringct.h
@@ -0,0 +1,120 @@
+// Copyright (c) 2021, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+#pragma once
+
+#include "ringct/rctTypes.h"
+
+#include <set>
+#include <vector>
+
+namespace cryptonote {
+
+class transaction;
+struct tx_source_entry;
+struct tx_destination_entry;
+struct account_keys;
+
+}
+
+namespace multisig {
+
+namespace signing {
+
+class CLSAG_context_t;
+
+// number of parallel signing nonces to use per signer (2 nonces as in musig2 and FROST)
+constexpr std::size_t kAlphaComponents = 2;
+
+class tx_builder_ringct_t final {
+private:
+ // the tx builder has been initialized
+ bool initialized;
+ // the tx builder is 'reconstructing' a tx that has already been created using this object
+ bool reconstruction;
+ // cached: mu_P*(local keys and sender-receiver secret and subaddress material) + mu_C*(commitment-to-zero secret)
+ // - these are only used for the initial building of a tx (not reconstructions)
+ rct::keyV cached_w;
+ // contexts for making CLSAG challenges with multisig nonces
+ std::vector<CLSAG_context_t> CLSAG_contexts;
+public:
+ tx_builder_ringct_t();
+ ~tx_builder_ringct_t();
+
+ // prepare an unsigned transaction (and get tx privkeys for outputs)
+ bool init(
+ const cryptonote::account_keys& account_keys,
+ const std::vector<std::uint8_t>& extra,
+ const std::uint64_t unlock_time,
+ const std::uint32_t subaddr_account,
+ const std::set<std::uint32_t>& subaddr_minor_indices,
+ std::vector<cryptonote::tx_source_entry>& sources,
+ std::vector<cryptonote::tx_destination_entry>& destinations,
+ const cryptonote::tx_destination_entry& change,
+ const rct::RCTConfig& rct_config,
+ const bool use_rct,
+ const bool reconstruction,
+ crypto::secret_key& tx_secret_key,
+ std::vector<crypto::secret_key>& tx_aux_secret_keys,
+ crypto::secret_key& tx_secret_key_entropy,
+ cryptonote::transaction& unsigned_tx
+ );
+
+ // get the first partial signature for the specified input ('source')
+ bool first_partial_sign(
+ const std::size_t source,
+ const rct::keyV& total_alpha_G,
+ const rct::keyV& total_alpha_H,
+ const rct::keyV& alpha,
+ rct::key& c_0,
+ rct::key& s
+ );
+
+ // get intermediate partial signatures for all the inputs
+ bool next_partial_sign(
+ const rct::keyM& total_alpha_G,
+ const rct::keyM& total_alpha_H,
+ const rct::keyM& alpha,
+ const rct::key& x,
+ rct::keyV& c_0,
+ rct::keyV& s
+ );
+
+ // finalize an unsigned transaction (add challenges and real responses to incomplete CLSAG signatures)
+ static bool finalize_tx(
+ const std::vector<cryptonote::tx_source_entry>& sources,
+ const rct::keyV& c_0,
+ const rct::keyV& s,
+ cryptonote::transaction& unsigned_tx
+ );
+};
+
+} //namespace signing
+
+} //namespace multisig
diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl
index a3bc3bf24..f33ce977d 100644
--- a/src/p2p/net_node.inl
+++ b/src/p2p/net_node.inl
@@ -538,7 +538,7 @@ namespace nodetool
if ( !set_max_out_peers(public_zone, command_line::get_arg(vm, arg_out_peers) ) )
return false;
else
- m_payload_handler.set_max_out_peers(public_zone.m_config.m_net_config.max_out_connection_count);
+ m_payload_handler.set_max_out_peers(epee::net_utils::zone::public_, public_zone.m_config.m_net_config.max_out_connection_count);
if ( !set_max_in_peers(public_zone, command_line::get_arg(vm, arg_in_peers) ) )
@@ -575,6 +575,8 @@ namespace nodetool
if (!set_max_out_peers(zone, proxy.max_connections))
return false;
+ else
+ m_payload_handler.set_max_out_peers(proxy.zone, proxy.max_connections);
epee::byte_slice this_noise = nullptr;
if (proxy.noise)
@@ -2462,8 +2464,12 @@ namespace nodetool
const epee::net_utils::zone zone_type = context.m_remote_address.get_zone();
network_zone& zone = m_network_zones.at(zone_type);
+ //will add self to peerlist if in same zone as outgoing later in this function
+ const bool outgoing_to_same_zone = !context.m_is_income && zone.m_our_address.get_zone() == zone_type;
+ const uint32_t max_peerlist_size = P2P_DEFAULT_PEERS_IN_HANDSHAKE - (outgoing_to_same_zone ? 1 : 0);
+
std::vector<peerlist_entry> local_peerlist_new;
- zone.m_peerlist.get_peerlist_head(local_peerlist_new, true, P2P_DEFAULT_PEERS_IN_HANDSHAKE);
+ zone.m_peerlist.get_peerlist_head(local_peerlist_new, true, max_peerlist_size);
//only include out peers we did not already send
rsp.local_peerlist_new.reserve(local_peerlist_new.size());
@@ -2483,7 +2489,7 @@ namespace nodetool
etc., because someone could give faulty addresses over Tor/I2P to get the
real peer with that identity banned/blacklisted. */
- if(!context.m_is_income && zone.m_our_address.get_zone() == zone_type)
+ if(outgoing_to_same_zone)
rsp.local_peerlist_new.push_back(peerlist_entry{zone.m_our_address, zone.m_config.m_peer_id, std::time(nullptr)});
LOG_DEBUG_CC(context, "COMMAND_TIMED_SYNC");
@@ -2758,7 +2764,7 @@ namespace nodetool
public_zone->second.m_config.m_net_config.max_out_connection_count = count;
if(current > count)
public_zone->second.m_net_server.get_config_object().del_out_connections(current - count);
- m_payload_handler.set_max_out_peers(count);
+ m_payload_handler.set_max_out_peers(epee::net_utils::zone::public_, count);
}
}
@@ -2887,10 +2893,12 @@ namespace nodetool
{
if (m_offline) return true;
if (!m_exclusive_peers.empty()) return true;
- if (m_payload_handler.needs_new_sync_connections()) return true;
for (auto& zone : m_network_zones)
{
+ if (m_payload_handler.needs_new_sync_connections(zone.first))
+ continue;
+
if (zone.second.m_net_server.is_stop_signal_sent())
return false;
diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp
index bd67778ec..477a7907d 100644
--- a/src/ringct/rctSigs.cpp
+++ b/src/ringct/rctSigs.cpp
@@ -238,14 +238,12 @@ namespace rct {
// P[l] == p*G
// C[l] == z*G
// C[i] == C_nonzero[i] - C_offset (for hashing purposes) for all i
- clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const keyV & C_nonzero, const key & C_offset, const unsigned int l, const multisig_kLRki *kLRki, key *mscout, key *mspout, hw::device &hwdev) {
+ clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const keyV & C_nonzero, const key & C_offset, const unsigned int l, hw::device &hwdev) {
clsag sig;
size_t n = P.size(); // ring size
CHECK_AND_ASSERT_THROW_MES(n == C.size(), "Signing and commitment key vector sizes must match!");
CHECK_AND_ASSERT_THROW_MES(n == C_nonzero.size(), "Signing and commitment key vector sizes must match!");
CHECK_AND_ASSERT_THROW_MES(l < n, "Signing index out of range!");
- CHECK_AND_ASSERT_THROW_MES((kLRki && mscout) || (!kLRki && !mscout), "Only one of kLRki/mscout is present");
- CHECK_AND_ASSERT_THROW_MES((mscout && mspout) || !kLRki, "Multisig pointers are not all present");
// Key images
ge_p3 H_p3;
@@ -260,16 +258,7 @@ namespace rct {
key aG;
key aH;
- // Multisig
- if (kLRki)
- {
- sig.I = kLRki->ki;
- scalarmultKey(D,H,z);
- }
- else
- {
- hwdev.clsag_prepare(p,z,sig.I,D,H,a,aG,aH);
- }
+ hwdev.clsag_prepare(p,z,sig.I,D,H,a,aG,aH);
geDsmp I_precomp;
geDsmp D_precomp;
@@ -317,18 +306,9 @@ namespace rct {
c_to_hash[2*n+1] = C_offset;
c_to_hash[2*n+2] = message;
- // Multisig data is present
- if (kLRki)
- {
- a = kLRki->k;
- c_to_hash[2*n+3] = kLRki->L;
- c_to_hash[2*n+4] = kLRki->R;
- }
- else
- {
- c_to_hash[2*n+3] = aG;
- c_to_hash[2*n+4] = aH;
- }
+ c_to_hash[2*n+3] = aG;
+ c_to_hash[2*n+4] = aH;
+
hwdev.clsag_hash(c_to_hash,c);
size_t i;
@@ -380,16 +360,11 @@ namespace rct {
hwdev.clsag_sign(c,a,p,z,mu_P,mu_C,sig.s[l]);
memwipe(&a, sizeof(key));
- if (mscout)
- *mscout = c;
- if (mspout)
- *mspout = mu_P;
-
return sig;
}
clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const keyV & C_nonzero, const key & C_offset, const unsigned int l) {
- return CLSAG_Gen(message, P, p, C, z, C_nonzero, C_offset, l, NULL, NULL, NULL, hw::get_device("default"));
+ return CLSAG_Gen(message, P, p, C, z, C_nonzero, C_offset, l, hw::get_device("default"));
}
// MLSAG signatures
@@ -397,7 +372,7 @@ namespace rct {
// This generalization allows for some dimensions not to require linkability;
// this is used in practice for commitment data within signatures
// Note that using more than one linkable dimension is not recommended.
- mgSig MLSAG_Gen(const key &message, const keyM & pk, const keyV & xx, const multisig_kLRki *kLRki, key *mscout, const unsigned int index, size_t dsRows, hw::device &hwdev) {
+ mgSig MLSAG_Gen(const key &message, const keyM & pk, const keyV & xx, const unsigned int index, size_t dsRows, hw::device &hwdev) {
mgSig rv;
size_t cols = pk.size();
CHECK_AND_ASSERT_THROW_MES(cols >= 2, "Error! What is c if cols = 1!");
@@ -409,8 +384,6 @@ namespace rct {
}
CHECK_AND_ASSERT_THROW_MES(xx.size() == rows, "Bad xx size");
CHECK_AND_ASSERT_THROW_MES(dsRows <= rows, "Bad dsRows size");
- CHECK_AND_ASSERT_THROW_MES((kLRki && mscout) || (!kLRki && !mscout), "Only one of kLRki/mscout is present");
- CHECK_AND_ASSERT_THROW_MES(!kLRki || dsRows == 1, "Multisig requires exactly 1 dsRows");
size_t i = 0, j = 0, ii = 0;
key c, c_old, L, R, Hi;
@@ -428,20 +401,11 @@ namespace rct {
DP("here1");
for (i = 0; i < dsRows; i++) {
toHash[3 * i + 1] = pk[index][i];
- if (kLRki) {
- // multisig
- alpha[i] = kLRki->k;
- toHash[3 * i + 2] = kLRki->L;
- toHash[3 * i + 3] = kLRki->R;
- rv.II[i] = kLRki->ki;
- }
- else {
- hash_to_p3(Hi_p3, pk[index][i]);
- ge_p3_tobytes(Hi.bytes, &Hi_p3);
- hwdev.mlsag_prepare(Hi, xx[i], alpha[i] , aG[i] , aHP[i] , rv.II[i]);
- toHash[3 * i + 2] = aG[i];
- toHash[3 * i + 3] = aHP[i];
- }
+ hash_to_p3(Hi_p3, pk[index][i]);
+ ge_p3_tobytes(Hi.bytes, &Hi_p3);
+ hwdev.mlsag_prepare(Hi, xx[i], alpha[i] , aG[i] , aHP[i] , rv.II[i]);
+ toHash[3 * i + 2] = aG[i];
+ toHash[3 * i + 3] = aHP[i];
precomp(Ip[i].k, rv.II[i]);
}
size_t ndsRows = 3 * dsRows; //non Double Spendable Rows (see identity chains paper)
@@ -485,8 +449,6 @@ namespace rct {
}
}
hwdev.mlsag_sign(c, xx, alpha, rows, dsRows, rv.ss[index]);
- if (mscout)
- *mscout = c;
return rv;
}
@@ -722,7 +684,7 @@ namespace rct {
// this shows that sum inputs = sum outputs
//Ver:
// verifies the above sig is created corretly
- mgSig proveRctMG(const key &message, const ctkeyM & pubs, const ctkeyV & inSk, const ctkeyV &outSk, const ctkeyV & outPk, const multisig_kLRki *kLRki, key *mscout, unsigned int index, const key &txnFeeKey, hw::device &hwdev) {
+ mgSig proveRctMG(const key &message, const ctkeyM & pubs, const ctkeyV & inSk, const ctkeyV &outSk, const ctkeyV & outPk, unsigned int index, const key &txnFeeKey, hw::device &hwdev) {
//setup vars
size_t cols = pubs.size();
CHECK_AND_ASSERT_THROW_MES(cols >= 1, "Empty pubs");
@@ -733,7 +695,6 @@ namespace rct {
}
CHECK_AND_ASSERT_THROW_MES(inSk.size() == rows, "Bad inSk size");
CHECK_AND_ASSERT_THROW_MES(outSk.size() == outPk.size(), "Bad outSk/outPk size");
- CHECK_AND_ASSERT_THROW_MES((kLRki && mscout) || (!kLRki && !mscout), "Only one of kLRki/mscout is present");
keyV sk(rows + 1);
keyV tmp(rows + 1);
@@ -766,7 +727,7 @@ namespace rct {
for (size_t j = 0; j < outPk.size(); j++) {
sc_sub(sk[rows].bytes, sk[rows].bytes, outSk[j].mask.bytes); //subtract output masks in last row..
}
- mgSig result = MLSAG_Gen(message, M, sk, kLRki, mscout, index, rows, hwdev);
+ mgSig result = MLSAG_Gen(message, M, sk, index, rows, hwdev);
memwipe(sk.data(), sk.size() * sizeof(key));
return result;
}
@@ -779,12 +740,11 @@ namespace rct {
// inSk is x, a_in corresponding to signing index
// a_out, Cout is for the output commitment
// index is the signing index..
- mgSig proveRctMGSimple(const key &message, const ctkeyV & pubs, const ctkey & inSk, const key &a , const key &Cout, const multisig_kLRki *kLRki, key *mscout, unsigned int index, hw::device &hwdev) {
+ mgSig proveRctMGSimple(const key &message, const ctkeyV & pubs, const ctkey & inSk, const key &a , const key &Cout, unsigned int index, hw::device &hwdev) {
//setup vars
size_t rows = 1;
size_t cols = pubs.size();
CHECK_AND_ASSERT_THROW_MES(cols >= 1, "Empty pubs");
- CHECK_AND_ASSERT_THROW_MES((kLRki && mscout) || (!kLRki && !mscout), "Only one of kLRki/mscout is present");
keyV tmp(rows + 1);
keyV sk(rows + 1);
size_t i;
@@ -796,17 +756,16 @@ namespace rct {
M[i][0] = pubs[i].dest;
subKeys(M[i][1], pubs[i].mask, Cout);
}
- mgSig result = MLSAG_Gen(message, M, sk, kLRki, mscout, index, rows, hwdev);
+ mgSig result = MLSAG_Gen(message, M, sk, index, rows, hwdev);
memwipe(sk.data(), sk.size() * sizeof(key));
return result;
}
- clsag proveRctCLSAGSimple(const key &message, const ctkeyV &pubs, const ctkey &inSk, const key &a, const key &Cout, const multisig_kLRki *kLRki, key *mscout, key *mspout, unsigned int index, hw::device &hwdev) {
+ clsag proveRctCLSAGSimple(const key &message, const ctkeyV &pubs, const ctkey &inSk, const key &a, const key &Cout, unsigned int index, hw::device &hwdev) {
//setup vars
size_t rows = 1;
size_t cols = pubs.size();
CHECK_AND_ASSERT_THROW_MES(cols >= 1, "Empty pubs");
- CHECK_AND_ASSERT_THROW_MES((kLRki && mscout) || (!kLRki && !mscout), "Only one of kLRki/mscout is present");
keyV tmp(rows + 1);
keyV sk(rows + 1);
keyM M(cols, tmp);
@@ -826,7 +785,7 @@ namespace rct {
sk[0] = copy(inSk.dest);
sc_sub(sk[1].bytes, inSk.mask.bytes, a.bytes);
- clsag result = CLSAG_Gen(message, P, sk[0], C, sk[1], C_nonzero, Cout, index, kLRki, mscout, mspout, hwdev);
+ clsag result = CLSAG_Gen(message, P, sk[0], C, sk[1], C_nonzero, Cout, index, hwdev);
memwipe(sk.data(), sk.size() * sizeof(key));
return result;
}
@@ -1084,14 +1043,13 @@ namespace rct {
// must know the destination private key to find the correct amount, else will return a random number
// Note: For txn fees, the last index in the amounts vector should contain that
// Thus the amounts vector will be "one" longer than the destinations vectort
- rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> & amounts, const ctkeyM &mixRing, const keyV &amount_keys, const multisig_kLRki *kLRki, multisig_out *msout, unsigned int index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev) {
+ rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> & amounts, const ctkeyM &mixRing, const keyV &amount_keys, unsigned int index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev) {
CHECK_AND_ASSERT_THROW_MES(amounts.size() == destinations.size() || amounts.size() == destinations.size() + 1, "Different number of amounts/destinations");
CHECK_AND_ASSERT_THROW_MES(amount_keys.size() == destinations.size(), "Different number of amount_keys/destinations");
CHECK_AND_ASSERT_THROW_MES(index < mixRing.size(), "Bad index into mixRing");
for (size_t n = 0; n < mixRing.size(); ++n) {
CHECK_AND_ASSERT_THROW_MES(mixRing[n].size() == inSk.size(), "Bad mixRing size");
}
- CHECK_AND_ASSERT_THROW_MES((kLRki && msout) || (!kLRki && !msout), "Only one of kLRki/msout is present");
CHECK_AND_ASSERT_THROW_MES(inSk.size() < 2, "genRct is not suitable for 2+ rings");
rctSig rv;
@@ -1130,23 +1088,21 @@ namespace rct {
key txnFeeKey = scalarmultH(d2h(rv.txnFee));
rv.mixRing = mixRing;
- if (msout)
- msout->c.resize(1);
- rv.p.MGs.push_back(proveRctMG(get_pre_mlsag_hash(rv, hwdev), rv.mixRing, inSk, outSk, rv.outPk, kLRki, msout ? &msout->c[0] : NULL, index, txnFeeKey,hwdev));
+ rv.p.MGs.push_back(proveRctMG(get_pre_mlsag_hash(rv, hwdev), rv.mixRing, inSk, outSk, rv.outPk, index, txnFeeKey,hwdev));
return rv;
}
- rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> & amounts, const keyV &amount_keys, const multisig_kLRki *kLRki, multisig_out *msout, const int mixin, const RCTConfig &rct_config, hw::device &hwdev) {
+ rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> & amounts, const keyV &amount_keys, const int mixin, const RCTConfig &rct_config, hw::device &hwdev) {
unsigned int index;
ctkeyM mixRing;
ctkeyV outSk;
tie(mixRing, index) = populateFromBlockchain(inPk, mixin);
- return genRct(message, inSk, destinations, amounts, mixRing, amount_keys, kLRki, msout, index, outSk, rct_config, hwdev);
+ return genRct(message, inSk, destinations, amounts, mixRing, amount_keys, index, outSk, rct_config, hwdev);
}
//RCT simple
//for post-rct only
- rctSig genRctSimple(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> &inamounts, const vector<xmr_amount> &outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<multisig_kLRki> *kLRki, multisig_out *msout, const std::vector<unsigned int> & index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev) {
+ rctSig genRctSimple(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> &inamounts, const vector<xmr_amount> &outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<unsigned int> & index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev) {
const bool bulletproof_or_plus = rct_config.range_proof_type > RangeProofBorromean;
CHECK_AND_ASSERT_THROW_MES(inamounts.size() > 0, "Empty inamounts");
CHECK_AND_ASSERT_THROW_MES(inamounts.size() == inSk.size(), "Different number of inamounts/inSk");
@@ -1157,10 +1113,6 @@ namespace rct {
for (size_t n = 0; n < mixRing.size(); ++n) {
CHECK_AND_ASSERT_THROW_MES(index[n] < mixRing[n].size(), "Bad index into mixRing");
}
- CHECK_AND_ASSERT_THROW_MES((kLRki && msout) || (!kLRki && !msout), "Only one of kLRki/msout is present");
- if (kLRki && msout) {
- CHECK_AND_ASSERT_THROW_MES(kLRki->size() == inamounts.size(), "Mismatched kLRki/inamounts sizes");
- }
rctSig rv;
if (bulletproof_or_plus)
@@ -1322,11 +1274,7 @@ namespace rct {
DP(pseudoOuts[i]);
key full_message = get_pre_mlsag_hash(rv,hwdev);
- if (msout)
- {
- msout->c.resize(inamounts.size());
- msout->mu_p.resize(is_rct_clsag(rv.type) ? inamounts.size() : 0);
- }
+
for (i = 0 ; i < inamounts.size(); i++)
{
if (is_rct_clsag(rv.type))
@@ -1334,17 +1282,17 @@ namespace rct {
if (hwdev.get_mode() == hw::device::TRANSACTION_CREATE_FAKE)
rv.p.CLSAGs[i] = make_dummy_clsag(rv.mixRing[i].size());
else
- rv.p.CLSAGs[i] = proveRctCLSAGSimple(full_message, rv.mixRing[i], inSk[i], a[i], pseudoOuts[i], kLRki ? &(*kLRki)[i]: NULL, msout ? &msout->c[i] : NULL, msout ? &msout->mu_p[i] : NULL, index[i], hwdev);
+ rv.p.CLSAGs[i] = proveRctCLSAGSimple(full_message, rv.mixRing[i], inSk[i], a[i], pseudoOuts[i], index[i], hwdev);
}
else
{
- rv.p.MGs[i] = proveRctMGSimple(full_message, rv.mixRing[i], inSk[i], a[i], pseudoOuts[i], kLRki ? &(*kLRki)[i]: NULL, msout ? &msout->c[i] : NULL, index[i], hwdev);
+ rv.p.MGs[i] = proveRctMGSimple(full_message, rv.mixRing[i], inSk[i], a[i], pseudoOuts[i], index[i], hwdev);
}
}
return rv;
}
- rctSig genRctSimple(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> &inamounts, const vector<xmr_amount> &outamounts, const keyV &amount_keys, const std::vector<multisig_kLRki> *kLRki, multisig_out *msout, xmr_amount txnFee, unsigned int mixin, const RCTConfig &rct_config, hw::device &hwdev) {
+ rctSig genRctSimple(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> &inamounts, const vector<xmr_amount> &outamounts, const keyV &amount_keys, xmr_amount txnFee, unsigned int mixin, const RCTConfig &rct_config, hw::device &hwdev) {
std::vector<unsigned int> index;
index.resize(inPk.size());
ctkeyM mixRing;
@@ -1354,7 +1302,7 @@ namespace rct {
mixRing[i].resize(mixin+1);
index[i] = populateFromBlockchainSimple(mixRing[i], inPk[i], mixin);
}
- return genRctSimple(message, inSk, destinations, inamounts, outamounts, txnFee, mixRing, amount_keys, kLRki, msout, index, outSk, rct_config, hwdev);
+ return genRctSimple(message, inSk, destinations, inamounts, outamounts, txnFee, mixRing, amount_keys, index, outSk, rct_config, hwdev);
}
//RingCT protocol
@@ -1385,7 +1333,7 @@ namespace rct {
try
{
if (semantics) {
- tools::threadpool& tpool = tools::threadpool::getInstance();
+ tools::threadpool& tpool = tools::threadpool::getInstanceForCompute();
tools::threadpool::waiter waiter(tpool);
std::deque<bool> results(rv.outPk.size(), false);
DP("range proofs verified?");
@@ -1435,7 +1383,7 @@ namespace rct {
{
PERF_TIMER(verRctSemanticsSimple);
- tools::threadpool& tpool = tools::threadpool::getInstance();
+ tools::threadpool& tpool = tools::threadpool::getInstanceForCompute();
tools::threadpool::waiter waiter(tpool);
std::deque<bool> results;
std::vector<const Bulletproof*> bp_proofs;
@@ -1588,7 +1536,7 @@ namespace rct {
const size_t threads = std::max(rv.outPk.size(), rv.mixRing.size());
std::deque<bool> results(threads);
- tools::threadpool& tpool = tools::threadpool::getInstance();
+ tools::threadpool& tpool = tools::threadpool::getInstanceForCompute();
tools::threadpool::waiter waiter(tpool);
const keyV &pseudoOuts = bulletproof || bulletproof_plus ? rv.p.pseudoOuts : rv.pseudoOuts;
@@ -1700,60 +1648,4 @@ namespace rct {
key mask;
return decodeRctSimple(rv, sk, i, mask, hwdev);
}
-
- bool signMultisigMLSAG(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key) {
- CHECK_AND_ASSERT_MES(rv.type == RCTTypeFull || rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2,
- false, "unsupported rct type");
- CHECK_AND_ASSERT_MES(!is_rct_clsag(rv.type), false, "CLSAG signature type in MLSAG signature function");
- CHECK_AND_ASSERT_MES(indices.size() == k.size(), false, "Mismatched k/indices sizes");
- CHECK_AND_ASSERT_MES(k.size() == rv.p.MGs.size(), false, "Mismatched k/MGs size");
- CHECK_AND_ASSERT_MES(k.size() == msout.c.size(), false, "Mismatched k/msout.c size");
- CHECK_AND_ASSERT_MES(rv.p.CLSAGs.empty(), false, "CLSAGs not empty for MLSAGs");
- if (rv.type == RCTTypeFull)
- {
- CHECK_AND_ASSERT_MES(rv.p.MGs.size() == 1, false, "MGs not a single element");
- }
- for (size_t n = 0; n < indices.size(); ++n) {
- CHECK_AND_ASSERT_MES(indices[n] < rv.p.MGs[n].ss.size(), false, "Index out of range");
- CHECK_AND_ASSERT_MES(!rv.p.MGs[n].ss[indices[n]].empty(), false, "empty ss line");
- }
-
- // MLSAG: each player contributes a share to the secret-index ss: k - cc*secret_key_share
- // cc: msout.c[n], secret_key_share: secret_key
- for (size_t n = 0; n < indices.size(); ++n) {
- rct::key diff;
- sc_mulsub(diff.bytes, msout.c[n].bytes, secret_key.bytes, k[n].bytes);
- sc_add(rv.p.MGs[n].ss[indices[n]][0].bytes, rv.p.MGs[n].ss[indices[n]][0].bytes, diff.bytes);
- }
- return true;
- }
-
- bool signMultisigCLSAG(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key) {
- CHECK_AND_ASSERT_MES(is_rct_clsag(rv.type), false, "unsupported rct type");
- CHECK_AND_ASSERT_MES(indices.size() == k.size(), false, "Mismatched k/indices sizes");
- CHECK_AND_ASSERT_MES(k.size() == rv.p.CLSAGs.size(), false, "Mismatched k/CLSAGs size");
- CHECK_AND_ASSERT_MES(k.size() == msout.c.size(), false, "Mismatched k/msout.c size");
- CHECK_AND_ASSERT_MES(rv.p.MGs.empty(), false, "MGs not empty for CLSAGs");
- CHECK_AND_ASSERT_MES(msout.c.size() == msout.mu_p.size(), false, "Bad mu_p size");
- for (size_t n = 0; n < indices.size(); ++n) {
- CHECK_AND_ASSERT_MES(indices[n] < rv.p.CLSAGs[n].s.size(), false, "Index out of range");
- }
-
- // CLSAG: each player contributes a share to the secret-index ss: k - cc*mu_p*secret_key_share
- // cc: msout.c[n], mu_p, msout.mu_p[n], secret_key_share: secret_key
- for (size_t n = 0; n < indices.size(); ++n) {
- rct::key diff, sk;
- sc_mul(sk.bytes, msout.mu_p[n].bytes, secret_key.bytes);
- sc_mulsub(diff.bytes, msout.c[n].bytes, sk.bytes, k[n].bytes);
- sc_add(rv.p.CLSAGs[n].s[indices[n]].bytes, rv.p.CLSAGs[n].s[indices[n]].bytes, diff.bytes);
- }
- return true;
- }
-
- bool signMultisig(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key) {
- if (is_rct_clsag(rv.type))
- return signMultisigCLSAG(rv, indices, k, msout, secret_key);
- else
- return signMultisigMLSAG(rv, indices, k, msout, secret_key);
- }
}
diff --git a/src/ringct/rctSigs.h b/src/ringct/rctSigs.h
index a0346b34e..17cfd77b9 100644
--- a/src/ringct/rctSigs.h
+++ b/src/ringct/rctSigs.h
@@ -74,12 +74,12 @@ namespace rct {
// Gen creates a signature which proves that for some column in the keymatrix "pk"
// the signer knows a secret key for each row in that column
// Ver verifies that the MG sig was created correctly
- mgSig MLSAG_Gen(const key &message, const keyM & pk, const keyV & xx, const multisig_kLRki *kLRki, key *mscout, const unsigned int index, size_t dsRows, hw::device &hwdev);
+ mgSig MLSAG_Gen(const key &message, const keyM & pk, const keyV & xx, const unsigned int index, size_t dsRows, hw::device &hwdev);
bool MLSAG_Ver(const key &message, const keyM &pk, const mgSig &sig, size_t dsRows);
- clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const keyV & C_nonzero, const key & C_offset, const unsigned int l, const multisig_kLRki *kLRki, key *mscout, key *mspout, hw::device &hwdev);
+ clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const keyV & C_nonzero, const key & C_offset, const unsigned int l, hw::device &hwdev);
clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const keyV & C_nonzero, const key & C_offset, const unsigned int l);
- clsag proveRctCLSAGSimple(const key &, const ctkeyV &, const ctkey &, const key &, const key &, const multisig_kLRki *, key *, key *, unsigned int, hw::device &);
+ clsag proveRctCLSAGSimple(const key &, const ctkeyV &, const ctkey &, const key &, const key &, unsigned int, hw::device &);
bool verRctCLSAGSimple(const key &, const clsag &, const ctkeyV &, const key &);
//proveRange and verRange
@@ -100,8 +100,8 @@ namespace rct {
// this shows that sum inputs = sum outputs
//Ver:
// verifies the above sig is created corretly
- mgSig proveRctMG(const ctkeyM & pubs, const ctkeyV & inSk, const keyV &outMasks, const ctkeyV & outPk, const multisig_kLRki *kLRki, key *mscout, unsigned int index, const key &txnFee, const key &message, hw::device &hwdev);
- mgSig proveRctMGSimple(const key & message, const ctkeyV & pubs, const ctkey & inSk, const key &a , const key &Cout, const multisig_kLRki *kLRki, key *mscout, unsigned int index, hw::device &hwdev);
+ mgSig proveRctMG(const ctkeyM & pubs, const ctkeyV & inSk, const keyV &outMasks, const ctkeyV & outPk, unsigned int index, const key &txnFee, const key &message, hw::device &hwdev);
+ mgSig proveRctMGSimple(const key & message, const ctkeyV & pubs, const ctkey & inSk, const key &a , const key &Cout, unsigned int index, hw::device &hwdev);
bool verRctMG(const mgSig &mg, const ctkeyM & pubs, const ctkeyV & outPk, const key &txnFee, const key &message);
bool verRctMGSimple(const key &message, const mgSig &mg, const ctkeyV & pubs, const key & C);
@@ -123,10 +123,10 @@ namespace rct {
//decodeRct: (c.f. https://eprint.iacr.org/2015/1098 section 5.1.1)
// uses the attached ecdh info to find the amounts represented by each output commitment
// must know the destination private key to find the correct amount, else will return a random number
- rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const std::vector<xmr_amount> & amounts, const ctkeyM &mixRing, const keyV &amount_keys, const multisig_kLRki *kLRki, multisig_out *msout, unsigned int index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev);
- rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const std::vector<xmr_amount> & amounts, const keyV &amount_keys, const multisig_kLRki *kLRki, multisig_out *msout, const int mixin, const RCTConfig &rct_config, hw::device &hwdev);
- rctSig genRctSimple(const key & message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const std::vector<xmr_amount> & inamounts, const std::vector<xmr_amount> & outamounts, const keyV &amount_keys, const std::vector<multisig_kLRki> *kLRki, multisig_out *msout, xmr_amount txnFee, unsigned int mixin, const RCTConfig &rct_config, hw::device &hwdev);
- rctSig genRctSimple(const key & message, const ctkeyV & inSk, const keyV & destinations, const std::vector<xmr_amount> & inamounts, const std::vector<xmr_amount> & outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<multisig_kLRki> *kLRki, multisig_out *msout, const std::vector<unsigned int> & index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev);
+ rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const std::vector<xmr_amount> & amounts, const ctkeyM &mixRing, const keyV &amount_keys, unsigned int index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev);
+ rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const std::vector<xmr_amount> & amounts, const keyV &amount_keys, const int mixin, const RCTConfig &rct_config, hw::device &hwdev);
+ rctSig genRctSimple(const key & message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const std::vector<xmr_amount> & inamounts, const std::vector<xmr_amount> & outamounts, const keyV &amount_keys, xmr_amount txnFee, unsigned int mixin, const RCTConfig &rct_config, hw::device &hwdev);
+ rctSig genRctSimple(const key & message, const ctkeyV & inSk, const keyV & destinations, const std::vector<xmr_amount> & inamounts, const std::vector<xmr_amount> & outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<unsigned int> & index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev);
bool verRct(const rctSig & rv, bool semantics);
static inline bool verRct(const rctSig & rv) { return verRct(rv, true) && verRct(rv, false); }
bool verRctSemanticsSimple(const rctSig & rv);
@@ -138,7 +138,6 @@ namespace rct {
xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i, key & mask, hw::device &hwdev);
xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i, hw::device &hwdev);
key get_pre_mlsag_hash(const rctSig &rv, hw::device &hwdev);
- bool signMultisig(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key);
}
#endif /* RCTSIGS_H */
diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp
index 0fe28465f..16bcf2c04 100644
--- a/src/rpc/core_rpc_server.cpp
+++ b/src/rpc/core_rpc_server.cpp
@@ -492,7 +492,6 @@ namespace cryptonote
}
CHECK_PAYMENT_MIN1(req, res, COST_PER_GET_INFO, false);
- db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db());
const bool restricted = m_restricted && ctx;
@@ -598,7 +597,6 @@ namespace cryptonote
}
CHECK_PAYMENT(req, res, 1);
- db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db());
// quick check for noop
if (!req.block_ids.empty())
@@ -609,7 +607,7 @@ namespace cryptonote
if (last_block_hash == req.block_ids.front())
{
res.start_height = 0;
- res.current_height = last_block_height + 1;
+ res.current_height = m_core.get_current_blockchain_height();
res.status = CORE_RPC_STATUS_OK;
return true;
}
@@ -730,7 +728,6 @@ namespace cryptonote
res.blocks.clear();
res.blocks.reserve(req.heights.size());
CHECK_PAYMENT_MIN1(req, res, req.heights.size() * COST_PER_BLOCK, false);
- db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db());
for (uint64_t height : req.heights)
{
block blk;
@@ -1592,7 +1589,6 @@ namespace cryptonote
return r;
CHECK_PAYMENT(req, res, 1);
- db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db());
const bool restricted = m_restricted && ctx;
const bool request_has_rpc_origin = ctx != NULL;
@@ -1617,7 +1613,6 @@ namespace cryptonote
return r;
CHECK_PAYMENT(req, res, 1);
- db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db());
const bool restricted = m_restricted && ctx;
const bool request_has_rpc_origin = ctx != NULL;
@@ -1720,14 +1715,11 @@ namespace cryptonote
error_resp.message = "Wrong parameters, expected height";
return false;
}
- db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db());
uint64_t h = req[0];
- uint64_t blockchain_height = m_core.get_current_blockchain_height();
- if(blockchain_height <= h)
+ if(m_core.get_current_blockchain_height() <= h)
{
error_resp.code = CORE_RPC_ERROR_CODE_TOO_BIG_HEIGHT;
- error_resp.message = std::string("Requested block height: ") + std::to_string(h) + " greater than current top block height: " + std::to_string(blockchain_height - 1);
- return false;
+ error_resp.message = std::string("Requested block height: ") + std::to_string(h) + " greater than current top block height: " + std::to_string(m_core.get_current_blockchain_height() - 1);
}
res = string_tools::pod_to_hex(m_core.get_block_id_by_height(h));
return true;
@@ -1877,7 +1869,6 @@ namespace cryptonote
return false;
}
}
- db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db());
crypto::hash seed_hash, next_seed_hash;
if (!get_block_template(info.address, req.prev_block.empty() ? NULL : &prev_block, blob_reserve, reserved_offset, wdiff, res.height, res.expected_reward, b, res.seed_height, seed_hash, next_seed_hash, error_resp))
return false;
@@ -2299,6 +2290,12 @@ namespace cryptonote
return m_bootstrap_daemon->handle_result(false, {});
}
+ if (bootstrap_daemon_height < m_core.get_checkpoints().get_max_height())
+ {
+ MINFO("Bootstrap daemon height is lower than the latest checkpoint");
+ return m_bootstrap_daemon->handle_result(false, {});
+ }
+
if (!m_p2p.get_payload_object().no_sync())
{
uint64_t top_height = m_core.get_current_blockchain_height();
@@ -2351,7 +2348,6 @@ namespace cryptonote
CHECK_CORE_READY();
CHECK_PAYMENT_MIN1(req, res, COST_PER_BLOCK_HEADER, false);
- db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db());
uint64_t last_block_height;
crypto::hash last_block_hash;
m_core.get_blockchain_top(last_block_height, last_block_hash);
@@ -2392,8 +2388,6 @@ namespace cryptonote
return false;
}
- db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db());
-
auto get = [this](const std::string &hash, bool fill_pow_hash, block_header_response &block_header, bool restricted, epee::json_rpc::error& error_resp) -> bool {
crypto::hash block_hash;
bool hash_parsed = parse_hash256(hash, block_hash);
@@ -2453,6 +2447,13 @@ namespace cryptonote
if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_BLOCK_HEADERS_RANGE>(invoke_http_mode::JON_RPC, "getblockheadersrange", req, res, r))
return r;
+ const uint64_t bc_height = m_core.get_current_blockchain_height();
+ if (req.start_height >= bc_height || req.end_height >= bc_height || req.start_height > req.end_height)
+ {
+ error_resp.code = CORE_RPC_ERROR_CODE_TOO_BIG_HEIGHT;
+ error_resp.message = "Invalid start/end heights.";
+ return false;
+ }
const bool restricted = m_restricted && ctx;
if (restricted && req.end_height - req.start_height > RESTRICTED_BLOCK_HEADER_RANGE)
{
@@ -2462,16 +2463,6 @@ namespace cryptonote
}
CHECK_PAYMENT_MIN1(req, res, (req.end_height - req.start_height + 1) * COST_PER_BLOCK_HEADER, false);
- db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db());
-
- const uint64_t bc_height = m_core.get_current_blockchain_height();
- if (req.start_height >= bc_height || req.end_height >= bc_height || req.start_height > req.end_height)
- {
- error_resp.code = CORE_RPC_ERROR_CODE_TOO_BIG_HEIGHT;
- error_resp.message = "Invalid start/end heights.";
- return false;
- }
-
for (uint64_t h = req.start_height; h <= req.end_height; ++h)
{
crypto::hash block_hash = m_core.get_block_id_by_height(h);
@@ -2516,12 +2507,10 @@ namespace cryptonote
if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT>(invoke_http_mode::JON_RPC, "getblockheaderbyheight", req, res, r))
return r;
- db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db());
- uint64_t blockchain_height = m_core.get_current_blockchain_height();
- if(blockchain_height <= req.height)
+ if(m_core.get_current_blockchain_height() <= req.height)
{
error_resp.code = CORE_RPC_ERROR_CODE_TOO_BIG_HEIGHT;
- error_resp.message = std::string("Requested block height: ") + std::to_string(req.height) + " greater than current top block height: " + std::to_string(blockchain_height - 1);
+ error_resp.message = std::string("Requested block height: ") + std::to_string(req.height) + " greater than current top block height: " + std::to_string(m_core.get_current_blockchain_height() - 1);
return false;
}
CHECK_PAYMENT_MIN1(req, res, COST_PER_BLOCK_HEADER, false);
@@ -2554,7 +2543,6 @@ namespace cryptonote
return r;
CHECK_PAYMENT_MIN1(req, res, COST_PER_BLOCK, false);
- db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db());
crypto::hash block_hash;
if (!req.hash.empty())
@@ -2569,11 +2557,10 @@ namespace cryptonote
}
else
{
- uint64_t blockchain_height = m_core.get_current_blockchain_height();
- if(blockchain_height <= req.height)
+ if(m_core.get_current_blockchain_height() <= req.height)
{
error_resp.code = CORE_RPC_ERROR_CODE_TOO_BIG_HEIGHT;
- error_resp.message = std::string("Requested block height: ") + std::to_string(req.height) + " greater than current top block height: " + std::to_string(blockchain_height - 1);
+ error_resp.message = std::string("Requested block height: ") + std::to_string(req.height) + " greater than current top block height: " + std::to_string(m_core.get_current_blockchain_height() - 1);
return false;
}
block_hash = m_core.get_block_id_by_height(req.height);
@@ -2874,6 +2861,10 @@ namespace cryptonote
res.version = CORE_RPC_VERSION;
res.release = MONERO_VERSION_IS_RELEASE;
+ res.current_height = m_core.get_current_blockchain_height();
+ res.target_height = m_p2p.get_payload_object().is_synchronized() ? 0 : m_core.get_target_blockchain_height();
+ for (const auto &hf : m_core.get_blockchain_storage().get_hardforks())
+ res.hard_forks.push_back({hf.version, hf.height});
res.status = CORE_RPC_STATUS_OK;
return true;
}
@@ -2881,7 +2872,6 @@ namespace cryptonote
bool core_rpc_server::on_get_coinbase_tx_sum(const COMMAND_RPC_GET_COINBASE_TX_SUM::request& req, COMMAND_RPC_GET_COINBASE_TX_SUM::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx)
{
RPC_TRACKER(get_coinbase_tx_sum);
- db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db());
const uint64_t bc_height = m_core.get_current_blockchain_height();
if (req.height >= bc_height || req.count > bc_height)
{
@@ -2923,7 +2913,6 @@ namespace cryptonote
bool core_rpc_server::on_get_alternate_chains(const COMMAND_RPC_GET_ALTERNATE_CHAINS::request& req, COMMAND_RPC_GET_ALTERNATE_CHAINS::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx)
{
RPC_TRACKER(get_alternate_chains);
- db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db());
try
{
std::vector<std::pair<Blockchain::block_extended_info, std::vector<crypto::hash>>> chains = m_core.get_blockchain_storage().get_alternative_chains();
@@ -3226,7 +3215,6 @@ namespace cryptonote
bool r;
if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG>(invoke_http_mode::JON_RPC, "get_txpool_backlog", req, res, r))
return r;
- db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db());
size_t n_txes = m_core.get_pool_transactions_count();
CHECK_PAYMENT_MIN1(req, res, COST_PER_TX_POOL_STATS * n_txes, false);
diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h
index 0274f4db8..b87412ca6 100644
--- a/src/rpc/core_rpc_server.h
+++ b/src/rpc/core_rpc_server.h
@@ -47,10 +47,6 @@
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "daemon.rpc"
-// yes, epee doesn't properly use its full namespace when calling its
-// functions from macros. *sigh*
-using namespace epee;
-
namespace cryptonote
{
/************************************************************************/
diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h
index 1be2610ff..e1222f6eb 100644
--- a/src/rpc/core_rpc_server_commands_defs.h
+++ b/src/rpc/core_rpc_server_commands_defs.h
@@ -88,7 +88,7 @@ namespace cryptonote
// advance which version they will stop working with
// Don't go over 32767 for any of these
#define CORE_RPC_VERSION_MAJOR 3
-#define CORE_RPC_VERSION_MINOR 10
+#define CORE_RPC_VERSION_MINOR 11
#define MAKE_CORE_RPC_VERSION(major,minor) (((major)<<16)|(minor))
#define CORE_RPC_VERSION MAKE_CORE_RPC_VERSION(CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR)
@@ -2123,15 +2123,34 @@ namespace cryptonote
};
typedef epee::misc_utils::struct_init<request_t> request;
+ struct hf_entry
+ {
+ uint8_t hf_version;
+ uint64_t height;
+
+ bool operator==(const hf_entry& hfe) const { return hf_version == hfe.hf_version && height == hfe.height; }
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(hf_version)
+ KV_SERIALIZE(height)
+ END_KV_SERIALIZE_MAP()
+ };
+
struct response_t: public rpc_response_base
{
uint32_t version;
bool release;
+ uint64_t current_height;
+ uint64_t target_height;
+ std::vector<hf_entry> hard_forks;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE_PARENT(rpc_response_base)
KV_SERIALIZE(version)
KV_SERIALIZE(release)
+ KV_SERIALIZE_OPT(current_height, (uint64_t)0)
+ KV_SERIALIZE_OPT(target_height, (uint64_t)0)
+ KV_SERIALIZE_OPT(hard_forks, std::vector<hf_entry>())
END_KV_SERIALIZE_MAP()
};
typedef epee::misc_utils::struct_init<response_t> response;
diff --git a/src/serialization/tuple.h b/src/serialization/tuple.h
new file mode 100644
index 000000000..6d98e05b0
--- /dev/null
+++ b/src/serialization/tuple.h
@@ -0,0 +1,169 @@
+// Copyright (c) 2014-2020, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
+
+#pragma once
+#include <memory>
+#include "serialization.h"
+
+namespace serialization
+{
+ namespace detail
+ {
+ template <typename Archive, class T>
+ bool serialize_tuple_element(Archive& ar, T& e)
+ {
+ return ::do_serialize(ar, e);
+ }
+
+ template <typename Archive>
+ bool serialize_tuple_element(Archive& ar, uint64_t& e)
+ {
+ ar.serialize_varint(e);
+ return true;
+ }
+ }
+}
+
+template <template <bool> class Archive, class E0, class E1, class E2>
+inline bool do_serialize(Archive<false>& ar, std::tuple<E0,E1,E2>& p)
+{
+ size_t cnt;
+ ar.begin_array(cnt);
+ if (!ar.good())
+ return false;
+ if (cnt != 3)
+ return false;
+
+ if (!::serialization::detail::serialize_tuple_element(ar, std::get<0>(p)))
+ return false;
+ if (!ar.good())
+ return false;
+ ar.delimit_array();
+ if (!::serialization::detail::serialize_tuple_element(ar, std::get<1>(p)))
+ return false;
+ if (!ar.good())
+ return false;
+ ar.delimit_array();
+ if (!::serialization::detail::serialize_tuple_element(ar, std::get<2>(p)))
+ return false;
+ if (!ar.good())
+ return false;
+
+ ar.end_array();
+ return true;
+}
+
+template <template <bool> class Archive, class E0, class E1, class E2>
+inline bool do_serialize(Archive<true>& ar, std::tuple<E0,E1,E2>& p)
+{
+ ar.begin_array(3);
+ if (!ar.good())
+ return false;
+ if(!::serialization::detail::serialize_tuple_element(ar, std::get<0>(p)))
+ return false;
+ if (!ar.good())
+ return false;
+ ar.delimit_array();
+ if(!::serialization::detail::serialize_tuple_element(ar, std::get<1>(p)))
+ return false;
+ if (!ar.good())
+ return false;
+ ar.delimit_array();
+ if(!::serialization::detail::serialize_tuple_element(ar, std::get<2>(p)))
+ return false;
+ if (!ar.good())
+ return false;
+ ar.end_array();
+ return true;
+}
+
+template <template <bool> class Archive, class E0, class E1, class E2, class E3>
+inline bool do_serialize(Archive<false>& ar, std::tuple<E0,E1,E2,E3>& p)
+{
+ size_t cnt;
+ ar.begin_array(cnt);
+ if (!ar.good())
+ return false;
+ if (cnt != 4)
+ return false;
+
+ if (!::serialization::detail::serialize_tuple_element(ar, std::get<0>(p)))
+ return false;
+ if (!ar.good())
+ return false;
+ ar.delimit_array();
+ if (!::serialization::detail::serialize_tuple_element(ar, std::get<1>(p)))
+ return false;
+ if (!ar.good())
+ return false;
+ ar.delimit_array();
+ if (!::serialization::detail::serialize_tuple_element(ar, std::get<2>(p)))
+ return false;
+ if (!ar.good())
+ return false;
+ ar.delimit_array();
+ if (!::serialization::detail::serialize_tuple_element(ar, std::get<3>(p)))
+ return false;
+ if (!ar.good())
+ return false;
+
+ ar.end_array();
+ return true;
+}
+
+template <template <bool> class Archive, class E0, class E1, class E2, class E3>
+inline bool do_serialize(Archive<true>& ar, std::tuple<E0,E1,E2,E3>& p)
+{
+ ar.begin_array(4);
+ if (!ar.good())
+ return false;
+ if(!::serialization::detail::serialize_tuple_element(ar, std::get<0>(p)))
+ return false;
+ if (!ar.good())
+ return false;
+ ar.delimit_array();
+ if(!::serialization::detail::serialize_tuple_element(ar, std::get<1>(p)))
+ return false;
+ if (!ar.good())
+ return false;
+ ar.delimit_array();
+ if(!::serialization::detail::serialize_tuple_element(ar, std::get<2>(p)))
+ return false;
+ if (!ar.good())
+ return false;
+ ar.delimit_array();
+ if(!::serialization::detail::serialize_tuple_element(ar, std::get<3>(p)))
+ return false;
+ if (!ar.good())
+ return false;
+ ar.end_array();
+ return true;
+}
+
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp
index d3e40ab74..f59af575e 100644
--- a/src/simplewallet/simplewallet.cpp
+++ b/src/simplewallet/simplewallet.cpp
@@ -142,6 +142,19 @@ typedef cryptonote::simple_wallet sw;
#define MIN_PAYMENT_RATE 0.01f // per hash
#define MAX_MNEW_ADDRESSES 1000
+#define CHECK_MULTISIG_ENABLED() \
+ do \
+ { \
+ if (!m_wallet->is_multisig_enabled()) \
+ { \
+ fail_msg_writer() << tr("Multisig is disabled."); \
+ fail_msg_writer() << tr("Multisig is an experimental feature and may have bugs. Things that could go wrong include: funds sent to a multisig wallet can't be spent at all, can only be spent with the participation of a malicious group member, or can be stolen by a malicious group member."); \
+ fail_msg_writer() << tr("You can enable it with:"); \
+ fail_msg_writer() << tr(" set enable-multisig-experimental 1"); \
+ return false; \
+ } \
+ } while(0)
+
enum TransferType {
Transfer,
TransferLocked,
@@ -168,7 +181,6 @@ namespace
const command_line::arg_descriptor<bool> arg_restore_from_seed = {"restore-from-seed", sw::tr("alias for --restore-deterministic-wallet"), false};
const command_line::arg_descriptor<bool> arg_restore_multisig_wallet = {"restore-multisig-wallet", sw::tr("Recover multisig wallet using Electrum-style mnemonic seed"), false};
const command_line::arg_descriptor<bool> arg_non_deterministic = {"non-deterministic", sw::tr("Generate non-deterministic view and spend keys"), false};
- const command_line::arg_descriptor<bool> arg_allow_mismatched_daemon_version = {"allow-mismatched-daemon-version", sw::tr("Allow communicating with a daemon that uses a different RPC version"), false};
const command_line::arg_descriptor<uint64_t> arg_restore_height = {"restore-height", sw::tr("Restore from specific blockchain height"), 0};
const command_line::arg_descriptor<std::string> arg_restore_date = {"restore-date", sw::tr("Restore from estimated blockchain height on specified date"), ""};
const command_line::arg_descriptor<bool> arg_do_not_relay = {"do-not-relay", sw::tr("The newly created transaction will not be relayed to the monero network"), false};
@@ -231,7 +243,7 @@ namespace
const char* USAGE_IMPORT_OUTPUTS("import_outputs <filename>");
const char* USAGE_SHOW_TRANSFER("show_transfer <txid>");
const char* USAGE_MAKE_MULTISIG("make_multisig <threshold> <string1> [<string>...]");
- const char* USAGE_EXCHANGE_MULTISIG_KEYS("exchange_multisig_keys <string> [<string>...]");
+ const char* USAGE_EXCHANGE_MULTISIG_KEYS("exchange_multisig_keys [force-update-use-with-caution] <string> [<string>...]");
const char* USAGE_EXPORT_MULTISIG_INFO("export_multisig_info <filename>");
const char* USAGE_IMPORT_MULTISIG_INFO("import_multisig_info <filename> [<filename>...]");
const char* USAGE_SIGN_MULTISIG("sign_multisig <filename>");
@@ -986,12 +998,14 @@ bool simple_wallet::print_fee_info(const std::vector<std::string> &args/* = std:
bool simple_wallet::prepare_multisig(const std::vector<std::string> &args)
{
+ CHECK_MULTISIG_ENABLED();
prepare_multisig_main(args, false);
return true;
}
bool simple_wallet::prepare_multisig_main(const std::vector<std::string> &args, bool called_by_mms)
{
+ CHECK_MULTISIG_ENABLED();
if (m_wallet->key_on_device())
{
fail_msg_writer() << tr("command not supported by HW wallet");
@@ -1031,12 +1045,14 @@ bool simple_wallet::prepare_multisig_main(const std::vector<std::string> &args,
bool simple_wallet::make_multisig(const std::vector<std::string> &args)
{
+ CHECK_MULTISIG_ENABLED();
make_multisig_main(args, false);
return true;
}
bool simple_wallet::make_multisig_main(const std::vector<std::string> &args, bool called_by_mms)
{
+ CHECK_MULTISIG_ENABLED();
if (m_wallet->key_on_device())
{
fail_msg_writer() << tr("command not supported by HW wallet");
@@ -1121,11 +1137,24 @@ bool simple_wallet::make_multisig_main(const std::vector<std::string> &args, boo
bool simple_wallet::exchange_multisig_keys(const std::vector<std::string> &args)
{
- exchange_multisig_keys_main(args, false);
+ CHECK_MULTISIG_ENABLED();
+ bool force_update_use_with_caution = false;
+
+ auto local_args = args;
+ if (args.size() >= 1 && local_args[0] == "force-update-use-with-caution")
+ {
+ force_update_use_with_caution = true;
+ local_args.erase(local_args.begin());
+ }
+
+ exchange_multisig_keys_main(local_args, force_update_use_with_caution, false);
return true;
}
-bool simple_wallet::exchange_multisig_keys_main(const std::vector<std::string> &args, bool called_by_mms) {
+bool simple_wallet::exchange_multisig_keys_main(const std::vector<std::string> &args,
+ const bool force_update_use_with_caution,
+ const bool called_by_mms) {
+ CHECK_MULTISIG_ENABLED();
bool ready;
if (m_wallet->key_on_device())
{
@@ -1150,15 +1179,9 @@ bool simple_wallet::exchange_multisig_keys_main(const std::vector<std::string> &
return false;
}
- if (args.size() < 1)
- {
- PRINT_USAGE(USAGE_EXCHANGE_MULTISIG_KEYS);
- return false;
- }
-
try
{
- std::string multisig_extra_info = m_wallet->exchange_multisig_keys(orig_pwd_container->password(), args);
+ std::string multisig_extra_info = m_wallet->exchange_multisig_keys(orig_pwd_container->password(), args, force_update_use_with_caution);
bool ready;
m_wallet->multisig(&ready);
if (!ready)
@@ -1189,12 +1212,14 @@ bool simple_wallet::exchange_multisig_keys_main(const std::vector<std::string> &
bool simple_wallet::export_multisig(const std::vector<std::string> &args)
{
+ CHECK_MULTISIG_ENABLED();
export_multisig_main(args, false);
return true;
}
bool simple_wallet::export_multisig_main(const std::vector<std::string> &args, bool called_by_mms)
{
+ CHECK_MULTISIG_ENABLED();
bool ready;
if (m_wallet->key_on_device())
{
@@ -1254,12 +1279,14 @@ bool simple_wallet::export_multisig_main(const std::vector<std::string> &args, b
bool simple_wallet::import_multisig(const std::vector<std::string> &args)
{
+ CHECK_MULTISIG_ENABLED();
import_multisig_main(args, false);
return true;
}
bool simple_wallet::import_multisig_main(const std::vector<std::string> &args, bool called_by_mms)
{
+ CHECK_MULTISIG_ENABLED();
bool ready;
uint32_t threshold, total;
if (m_wallet->key_on_device())
@@ -1349,12 +1376,14 @@ bool simple_wallet::accept_loaded_tx(const tools::wallet2::multisig_tx_set &txs)
bool simple_wallet::sign_multisig(const std::vector<std::string> &args)
{
+ CHECK_MULTISIG_ENABLED();
sign_multisig_main(args, false);
return true;
}
bool simple_wallet::sign_multisig_main(const std::vector<std::string> &args, bool called_by_mms)
{
+ CHECK_MULTISIG_ENABLED();
bool ready;
if (m_wallet->key_on_device())
{
@@ -1464,12 +1493,14 @@ bool simple_wallet::sign_multisig_main(const std::vector<std::string> &args, boo
bool simple_wallet::submit_multisig(const std::vector<std::string> &args)
{
+ CHECK_MULTISIG_ENABLED();
submit_multisig_main(args, false);
return true;
}
bool simple_wallet::submit_multisig_main(const std::vector<std::string> &args, bool called_by_mms)
{
+ CHECK_MULTISIG_ENABLED();
bool ready;
uint32_t threshold;
if (m_wallet->key_on_device())
@@ -1551,6 +1582,7 @@ bool simple_wallet::submit_multisig_main(const std::vector<std::string> &args, b
bool simple_wallet::export_raw_multisig(const std::vector<std::string> &args)
{
+ CHECK_MULTISIG_ENABLED();
bool ready;
uint32_t threshold;
if (m_wallet->key_on_device())
@@ -3074,6 +3106,25 @@ bool simple_wallet::set_load_deprecated_formats(const std::vector<std::string> &
return true;
}
+bool simple_wallet::set_enable_multisig(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
+{
+ if (args.size() < 2)
+ {
+ fail_msg_writer() << tr("Value not specified");
+ return true;
+ }
+
+ const auto pwd_container = get_and_verify_password();
+ if (pwd_container)
+ {
+ parse_bool_and_use(args[1], [&](bool r) {
+ m_wallet->enable_multisig(r);
+ m_wallet->rewrite(m_wallet_file, pwd_container->password());
+ });
+ }
+ return true;
+}
+
bool simple_wallet::help(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
{
if(args.empty())
@@ -3186,8 +3237,7 @@ bool simple_wallet::scan_tx(const std::vector<std::string> &args)
}
simple_wallet::simple_wallet()
- : m_allow_mismatched_daemon_version(false)
- , m_refresh_progress_reporter(*this)
+ : m_refresh_progress_reporter(*this)
, m_idle_run(true)
, m_auto_refresh_enabled(false)
, m_auto_refresh_refreshing(false)
@@ -3391,6 +3441,8 @@ simple_wallet::simple_wallet()
" The RPC payment credits balance to target (0 for default).\n "
"show-wallet-name-when-locked <1|0>\n "
" Set this if you would like to display the wallet name when locked.\n "
+ "enable-multisig-experimental <1|0>\n "
+ " Set this to allow multisig commands. Multisig may currently be exploitable if parties do not trust each other.\n "
"inactivity-lock-timeout <unsigned int>\n "
" How many seconds to wait before locking the wallet (0 to disable)."));
m_cmd_binder.set_handler("encrypted_seed",
@@ -3806,6 +3858,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
success_msg_writer() << "auto-mine-for-rpc-payment-threshold = " << m_wallet->auto_mine_for_rpc_payment_threshold();
success_msg_writer() << "credits-target = " << m_wallet->credits_target();
success_msg_writer() << "load-deprecated-formats = " << m_wallet->load_deprecated_formats();
+ success_msg_writer() << "enable-multisig-experimental = " << m_wallet->is_multisig_enabled();
return true;
}
else
@@ -3872,6 +3925,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
CHECK_SIMPLE_VARIABLE("persistent-rpc-client-id", set_persistent_rpc_client_id, tr("0 or 1"));
CHECK_SIMPLE_VARIABLE("auto-mine-for-rpc-payment-threshold", set_auto_mine_for_rpc_payment_threshold, tr("floating point >= 0"));
CHECK_SIMPLE_VARIABLE("credits-target", set_credits_target, tr("unsigned integer"));
+ CHECK_SIMPLE_VARIABLE("enable-multisig-experimental", set_enable_multisig, tr("0 or 1"));
}
fail_msg_writer() << tr("set: unrecognized argument(s)");
return true;
@@ -4067,6 +4121,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
epee::wipeable_string multisig_keys;
epee::wipeable_string password;
+ epee::wipeable_string seed_pass;
if (!handle_command_line(vm))
return false;
@@ -4083,6 +4138,17 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
if(!ask_wallet_create_if_needed()) return false;
}
+ bool enable_multisig = false;
+ if (m_restore_multisig_wallet) {
+ fail_msg_writer() << tr("Multisig is disabled.");
+ fail_msg_writer() << tr("Multisig is an experimental feature and may have bugs. Things that could go wrong include: funds sent to a multisig wallet can't be spent at all, can only be spent with the participation of a malicious group member, or can be stolen by a malicious group member.");
+ if (!command_line::is_yes(input_line("Do you want to continue restoring a multisig wallet?", true))) {
+ message_writer() << tr("You have canceled restoring a multisig wallet.");
+ return false;
+ }
+ enable_multisig = true;
+ }
+
if (!m_generate_new.empty() || m_restoring)
{
if (!m_subaddress_lookahead.empty() && !parse_subaddress_lookahead(m_subaddress_lookahead))
@@ -4162,19 +4228,9 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
auto pwd_container = password_prompter(tr("Enter seed offset passphrase, empty if none"), false);
if (std::cin.eof() || !pwd_container)
return false;
- epee::wipeable_string seed_pass = pwd_container->password();
- if (!seed_pass.empty())
- {
- if (m_restore_multisig_wallet)
- {
- crypto::secret_key key;
- crypto::cn_slow_hash(seed_pass.data(), seed_pass.size(), (crypto::hash&)key);
- sc_reduce32((unsigned char*)key.data);
- multisig_keys = m_wallet->decrypt<epee::wipeable_string>(std::string(multisig_keys.data(), multisig_keys.size()), key, true);
- }
- else
- m_recovery_key = cryptonote::decrypt_key(m_recovery_key, seed_pass);
- }
+ seed_pass = pwd_container->password();
+ if (!seed_pass.empty() && !m_restore_multisig_wallet)
+ m_recovery_key = cryptonote::decrypt_key(m_recovery_key, seed_pass);
}
if (!m_generate_from_view_key.empty())
{
@@ -4517,7 +4573,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
m_wallet_file = m_generate_new;
boost::optional<epee::wipeable_string> r;
if (m_restore_multisig_wallet)
- r = new_wallet(vm, multisig_keys, old_language);
+ r = new_wallet(vm, multisig_keys, seed_pass, old_language);
else
r = new_wallet(vm, m_recovery_key, m_restore_deterministic_wallet, m_non_deterministic, old_language);
CHECK_AND_ASSERT_MES(r, false, tr("account creation failed"));
@@ -4616,6 +4672,8 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
}
m_wallet->set_refresh_from_block_height(m_restore_height);
}
+ if (enable_multisig)
+ m_wallet->enable_multisig(true);
m_wallet->rewrite(m_wallet_file, password);
}
else
@@ -4704,7 +4762,6 @@ bool simple_wallet::handle_command_line(const boost::program_options::variables_
m_restore_deterministic_wallet = command_line::get_arg(vm, arg_restore_deterministic_wallet) || command_line::get_arg(vm, arg_restore_from_seed);
m_restore_multisig_wallet = command_line::get_arg(vm, arg_restore_multisig_wallet);
m_non_deterministic = command_line::get_arg(vm, arg_non_deterministic);
- m_allow_mismatched_daemon_version = command_line::get_arg(vm, arg_allow_mismatched_daemon_version);
m_restore_height = command_line::get_arg(vm, arg_restore_height);
m_restore_date = command_line::get_arg(vm, arg_restore_date);
m_do_not_relay = command_line::get_arg(vm, arg_do_not_relay);
@@ -4735,12 +4792,20 @@ bool simple_wallet::try_connect_to_daemon(bool silent, uint32_t* version)
uint32_t version_ = 0;
if (!version)
version = &version_;
- if (!m_wallet->check_connection(version))
+ bool wallet_is_outdated = false, daemon_is_outdated = false;
+ if (!m_wallet->check_connection(version, NULL, 200000, &wallet_is_outdated, &daemon_is_outdated))
{
if (!silent)
{
if (m_wallet->is_offline())
fail_msg_writer() << tr("wallet failed to connect to daemon, because it is set to offline mode");
+ else if (wallet_is_outdated)
+ fail_msg_writer() << tr("wallet failed to connect to daemon, because it is not up to date. ") <<
+ tr("Please make sure you are running the latest wallet.");
+ else if (daemon_is_outdated)
+ fail_msg_writer() << tr("wallet failed to connect to daemon: ") << m_wallet->get_daemon_address() << ". " <<
+ tr("Daemon is not up to date. "
+ "Please make sure the daemon is running the latest version or change the daemon address using the 'set_daemon' command.");
else
fail_msg_writer() << tr("wallet failed to connect to daemon: ") << m_wallet->get_daemon_address() << ". " <<
tr("Daemon either is not started or wrong port was passed. "
@@ -4748,7 +4813,7 @@ bool simple_wallet::try_connect_to_daemon(bool silent, uint32_t* version)
}
return false;
}
- if (!m_allow_mismatched_daemon_version && ((*version >> 16) != CORE_RPC_VERSION_MAJOR))
+ if (!m_wallet->is_mismatched_daemon_version_allowed() && ((*version >> 16) != CORE_RPC_VERSION_MAJOR))
{
if (!silent)
fail_msg_writer() << boost::format(tr("Daemon uses a different RPC major version (%u) than the wallet (%u): %s. Either update one of them, or use --allow-mismatched-daemon-version.")) % (*version>>16) % CORE_RPC_VERSION_MAJOR % m_wallet->get_daemon_address();
@@ -5006,7 +5071,7 @@ boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::pr
}
//----------------------------------------------------------------------------------------------------
boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::program_options::variables_map& vm,
- const epee::wipeable_string &multisig_keys, const std::string &old_language)
+ const epee::wipeable_string &multisig_keys, const epee::wipeable_string &seed_pass, const std::string &old_language)
{
std::pair<std::unique_ptr<tools::wallet2>, tools::password_container> rc;
try { rc = tools::wallet2::make_new(vm, false, password_prompter); }
@@ -5040,7 +5105,16 @@ boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::pr
try
{
- m_wallet->generate(m_wallet_file, std::move(rc.second).password(), multisig_keys, create_address_file);
+ if (seed_pass.empty())
+ m_wallet->generate(m_wallet_file, std::move(rc.second).password(), multisig_keys, create_address_file);
+ else
+ {
+ crypto::secret_key key;
+ crypto::cn_slow_hash(seed_pass.data(), seed_pass.size(), (crypto::hash&)key);
+ sc_reduce32((unsigned char*)key.data);
+ const epee::wipeable_string &msig_keys = m_wallet->decrypt<epee::wipeable_string>(std::string(multisig_keys.data(), multisig_keys.size()), key, true);
+ m_wallet->generate(m_wallet_file, std::move(rc.second).password(), msig_keys, create_address_file);
+ }
bool ready;
uint32_t threshold, total;
if (!m_wallet->multisig(&ready, &threshold, &total) || !ready)
@@ -5622,14 +5696,18 @@ void simple_wallet::on_new_block(uint64_t height, const cryptonote::block& block
m_refresh_progress_reporter.update(height, false);
}
//----------------------------------------------------------------------------------------------------
-void simple_wallet::on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time)
+void simple_wallet::on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, uint64_t burnt, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time)
{
if (m_locked)
return;
+ std::stringstream burn;
+ if (burnt != 0) {
+ burn << " (" << print_money(amount) << " yet " << print_money(burnt) << " was burnt)";
+ }
message_writer(console_color_green, false) << "\r" <<
tr("Height ") << height << ", " <<
tr("txid ") << txid << ", " <<
- print_money(amount) << ", " <<
+ print_money(amount - burnt) << burn.str() << ", " <<
tr("idx ") << subaddr_index;
const uint64_t warn_height = m_wallet->nettype() == TESTNET ? 1000000 : m_wallet->nettype() == STAGENET ? 50000 : 1650000;
@@ -6560,7 +6638,6 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
vector<cryptonote::address_parse_info> dsts_info;
vector<cryptonote::tx_destination_entry> dsts;
- size_t num_subaddresses = 0;
for (size_t i = 0; i < local_args.size(); )
{
dsts_info.emplace_back();
@@ -6619,7 +6696,6 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
de.addr = info.address;
de.is_subaddress = info.is_subaddress;
de.is_integrated = info.has_payment_id;
- num_subaddresses += info.is_subaddress;
if (info.has_payment_id || !payment_id_uri.empty())
{
@@ -6911,18 +6987,33 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
//----------------------------------------------------------------------------------------------------
bool simple_wallet::transfer(const std::vector<std::string> &args_)
{
+ if (args_.size() < 1)
+ {
+ PRINT_USAGE(USAGE_TRANSFER);
+ return true;
+ }
transfer_main(Transfer, args_, false);
return true;
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::locked_transfer(const std::vector<std::string> &args_)
{
+ if (args_.size() < 1)
+ {
+ PRINT_USAGE(USAGE_LOCKED_TRANSFER);
+ return true;
+ }
transfer_main(TransferLocked, args_, false);
return true;
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::locked_sweep_all(const std::vector<std::string> &args_)
{
+ if (args_.size() < 1)
+ {
+ PRINT_USAGE(USAGE_LOCKED_SWEEP_ALL);
+ return true;
+ }
sweep_main(m_current_subaddress_account, 0, true, args_);
return true;
}
@@ -6980,6 +7071,7 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_)
// actually commit the transactions
if (m_wallet->multisig())
{
+ CHECK_MULTISIG_ENABLED();
bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx");
if (!r)
{
@@ -7284,6 +7376,7 @@ bool simple_wallet::sweep_main(uint32_t account, uint64_t below, bool locked, co
// actually commit the transactions
if (m_wallet->multisig())
{
+ CHECK_MULTISIG_ENABLED();
bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx");
if (!r)
{
@@ -7518,6 +7611,7 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_)
// actually commit the transactions
if (m_wallet->multisig())
{
+ CHECK_MULTISIG_ENABLED();
bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx");
if (!r)
{
@@ -7618,6 +7712,7 @@ bool simple_wallet::sweep_below(const std::vector<std::string> &args_)
if (args_.size() < 1)
{
fail_msg_writer() << tr("missing threshold amount");
+ PRINT_USAGE(USAGE_SWEEP_BELOW);
return true;
}
if (!cryptonote::parse_amount(below, args_[0]))
@@ -7840,8 +7935,10 @@ bool simple_wallet::accept_loaded_tx(const std::function<size_t()> get_num_txes,
bool simple_wallet::accept_loaded_tx(const tools::wallet2::unsigned_tx_set &txs)
{
std::string extra_message;
- if (!txs.transfers.second.empty())
- extra_message = (boost::format("%u outputs to import. ") % (unsigned)txs.transfers.second.size()).str();
+ if (!std::get<2>(txs.new_transfers).empty())
+ extra_message = (boost::format("%u outputs to import. ") % (unsigned)std::get<2>(txs.new_transfers).size()).str();
+ else if (!std::get<2>(txs.transfers).empty())
+ extra_message = (boost::format("%u outputs to import. ") % (unsigned)std::get<2>(txs.transfers).size()).str();
return accept_loaded_tx([&txs](){return txs.txes.size();}, [&txs](size_t n)->const tools::wallet2::tx_construction_data&{return txs.txes[n];}, extra_message);
}
//----------------------------------------------------------------------------------------------------
@@ -10555,7 +10652,6 @@ int main(int argc, char* argv[])
command_line::add_arg(desc_params, arg_restore_multisig_wallet );
command_line::add_arg(desc_params, arg_non_deterministic );
command_line::add_arg(desc_params, arg_electrum_seed );
- command_line::add_arg(desc_params, arg_allow_mismatched_daemon_version);
command_line::add_arg(desc_params, arg_restore_height);
command_line::add_arg(desc_params, arg_restore_date);
command_line::add_arg(desc_params, arg_do_not_relay);
@@ -11085,7 +11181,8 @@ void simple_wallet::mms_next(const std::vector<std::string> &args)
mms::message m = ms.get_message_by_id(data.message_ids[i]);
sig_args[i] = m.content;
}
- command_successful = exchange_multisig_keys_main(sig_args, true);
+ // todo: update mms to enable 'key exchange force updating'
+ command_successful = exchange_multisig_keys_main(sig_args, false, true);
break;
}
@@ -11549,6 +11646,7 @@ void simple_wallet::mms_auto_config(const std::vector<std::string> &args)
bool simple_wallet::mms(const std::vector<std::string> &args)
{
+ CHECK_MULTISIG_ENABLED();
try
{
m_wallet->get_multisig_wallet_state();
diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h
index 4c005c53a..7c45d45e8 100644
--- a/src/simplewallet/simplewallet.h
+++ b/src/simplewallet/simplewallet.h
@@ -101,7 +101,7 @@ namespace cryptonote
boost::optional<epee::wipeable_string> new_wallet(const boost::program_options::variables_map& vm, const cryptonote::account_public_address& address,
const boost::optional<crypto::secret_key>& spendkey, const crypto::secret_key& viewkey);
boost::optional<epee::wipeable_string> new_wallet(const boost::program_options::variables_map& vm,
- const epee::wipeable_string &multisig_keys, const std::string &old_language);
+ const epee::wipeable_string &multisig_keys, const epee::wipeable_string &seed_pass, const std::string &old_language);
boost::optional<epee::wipeable_string> new_wallet(const boost::program_options::variables_map& vm);
boost::optional<epee::wipeable_string> open_wallet(const boost::program_options::variables_map& vm);
bool close_wallet();
@@ -153,6 +153,7 @@ namespace cryptonote
bool set_device_name(const std::vector<std::string> &args = std::vector<std::string>());
bool set_export_format(const std::vector<std::string> &args = std::vector<std::string>());
bool set_load_deprecated_formats(const std::vector<std::string> &args = std::vector<std::string>());
+ bool set_enable_multisig(const std::vector<std::string> &args = std::vector<std::string>());
bool set_persistent_rpc_client_id(const std::vector<std::string> &args = std::vector<std::string>());
bool set_auto_mine_for_rpc_payment_threshold(const std::vector<std::string> &args = std::vector<std::string>());
bool set_credits_target(const std::vector<std::string> &args = std::vector<std::string>());
@@ -234,7 +235,7 @@ namespace cryptonote
bool make_multisig(const std::vector<std::string>& args);
bool make_multisig_main(const std::vector<std::string>& args, bool called_by_mms);
bool exchange_multisig_keys(const std::vector<std::string> &args);
- bool exchange_multisig_keys_main(const std::vector<std::string> &args, bool called_by_mms);
+ bool exchange_multisig_keys_main(const std::vector<std::string> &args, const bool force_update_use_with_caution, const bool called_by_mms);
bool export_multisig(const std::vector<std::string>& args);
bool export_multisig_main(const std::vector<std::string>& args, bool called_by_mms);
bool import_multisig(const std::vector<std::string>& args);
@@ -345,7 +346,7 @@ namespace cryptonote
//----------------- i_wallet2_callback ---------------------
virtual void on_new_block(uint64_t height, const cryptonote::block& block);
- virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time);
+ virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, uint64_t burnt, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time);
virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index);
virtual void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx, const cryptonote::subaddress_index& subaddr_index);
virtual void on_skip_transaction(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx);
@@ -428,7 +429,6 @@ namespace cryptonote
bool m_restore_deterministic_wallet; // recover flag
bool m_restore_multisig_wallet; // recover flag
bool m_non_deterministic; // old 2-random generation
- bool m_allow_mismatched_daemon_version;
bool m_restoring; // are we restoring, by whatever method?
uint64_t m_restore_height; // optional
bool m_do_not_relay;
diff --git a/src/version.cpp.in b/src/version.cpp.in
index 9f6ffd97b..91fdc9902 100644
--- a/src/version.cpp.in
+++ b/src/version.cpp.in
@@ -1,6 +1,6 @@
#define DEF_MONERO_VERSION_TAG "@VERSIONTAG@"
-#define DEF_MONERO_VERSION "0.17.0.0"
-#define DEF_MONERO_RELEASE_NAME "Oxygen Orion"
+#define DEF_MONERO_VERSION "0.18.1.0"
+#define DEF_MONERO_RELEASE_NAME "Fluorine Fermi"
#define DEF_MONERO_VERSION_FULL DEF_MONERO_VERSION "-" DEF_MONERO_VERSION_TAG
#define DEF_MONERO_VERSION_IS_RELEASE @VERSION_IS_RELEASE@
diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp
index 7cd8656e1..c4d3856d4 100644
--- a/src/wallet/api/wallet.cpp
+++ b/src/wallet/api/wallet.cpp
@@ -63,8 +63,8 @@ namespace {
static const int MAX_REFRESH_INTERVAL_MILLIS = 1000 * 60 * 1;
// Default refresh interval when connected to remote node
static const int DEFAULT_REMOTE_NODE_REFRESH_INTERVAL_MILLIS = 1000 * 10;
- // Connection timeout 30 sec
- static const int DEFAULT_CONNECTION_TIMEOUT_MILLIS = 1000 * 30;
+ // Connection timeout 20 sec
+ static const int DEFAULT_CONNECTION_TIMEOUT_MILLIS = 1000 * 20;
std::string get_default_ringdb_path(cryptonote::network_type nettype)
{
@@ -154,18 +154,20 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback
}
}
- virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time)
+ virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, uint64_t burnt, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time)
{
std::string tx_hash = epee::string_tools::pod_to_hex(txid);
LOG_PRINT_L3(__FUNCTION__ << ": money received. height: " << height
<< ", tx: " << tx_hash
- << ", amount: " << print_money(amount)
+ << ", amount: " << print_money(amount - burnt)
+ << ", burnt: " << print_money(burnt)
+ << ", raw_output_value: " << print_money(amount)
<< ", idx: " << subaddr_index);
// do not signal on received tx if wallet is not syncronized completely
if (m_listener && m_wallet->synchronized()) {
- m_listener->moneyReceived(tx_hash, amount);
+ m_listener->moneyReceived(tx_hash, amount - burnt);
m_listener->updated();
}
}
@@ -1144,8 +1146,8 @@ UnsignedTransaction *WalletImpl::loadUnsignedTx(const std::string &unsigned_file
// Check tx data and construct confirmation message
std::string extra_message;
- if (!transaction->m_unsigned_tx_set.transfers.second.empty())
- extra_message = (boost::format("%u outputs to import. ") % (unsigned)transaction->m_unsigned_tx_set.transfers.second.size()).str();
+ if (!std::get<2>(transaction->m_unsigned_tx_set.transfers).empty())
+ extra_message = (boost::format("%u outputs to import. ") % (unsigned)std::get<2>(transaction->m_unsigned_tx_set.transfers).size()).str();
transaction->checkLoadedTx([&transaction](){return transaction->m_unsigned_tx_set.txes.size();}, [&transaction](size_t n)->const tools::wallet2::tx_construction_data&{return transaction->m_unsigned_tx_set.txes[n];}, extra_message);
setStatus(transaction->status(), transaction->errorString());
@@ -1280,6 +1282,42 @@ bool WalletImpl::importOutputs(const string &filename)
return true;
}
+bool WalletImpl::scanTransactions(const std::vector<std::string> &txids)
+{
+ if (txids.empty())
+ {
+ setStatusError(string(tr("Failed to scan transactions: no transaction ids provided.")));
+ return false;
+ }
+
+ // Parse and dedup args
+ std::unordered_set<crypto::hash> txids_u;
+ for (const auto &s : txids)
+ {
+ crypto::hash txid;
+ if (!epee::string_tools::hex_to_pod(s, txid))
+ {
+ setStatusError(string(tr("Invalid txid specified: ")) + s);
+ return false;
+ }
+ txids_u.insert(txid);
+ }
+ std::vector<crypto::hash> txids_v(txids_u.begin(), txids_u.end());
+
+ try
+ {
+ m_wallet->scan_tx(txids_v);
+ }
+ catch (const std::exception &e)
+ {
+ LOG_ERROR("Failed to scan transaction: " << e.what());
+ setStatusError(string(tr("Failed to scan transaction: ")) + e.what());
+ return false;
+ }
+
+ return true;
+}
+
void WalletImpl::addSubaddressAccount(const std::string& label)
{
m_wallet->add_subaddress_account(label);
@@ -1358,12 +1396,12 @@ string WalletImpl::makeMultisig(const vector<string>& info, const uint32_t thres
return string();
}
-std::string WalletImpl::exchangeMultisigKeys(const std::vector<std::string> &info) {
+std::string WalletImpl::exchangeMultisigKeys(const std::vector<std::string> &info, const bool force_update_use_with_caution /*= false*/) {
try {
clearStatus();
checkMultisigWalletNotReady(m_wallet);
- return m_wallet->exchange_multisig_keys(epee::wipeable_string(m_password), info);
+ return m_wallet->exchange_multisig_keys(epee::wipeable_string(m_password), info, force_update_use_with_caution);
} catch (const exception& e) {
LOG_ERROR("Error on exchanging multisig keys: " << e.what());
setStatusError(string(tr("Failed to exchange multisig keys: ")) + e.what());
@@ -2135,9 +2173,15 @@ bool WalletImpl::connectToDaemon()
Wallet::ConnectionStatus WalletImpl::connected() const
{
uint32_t version = 0;
- m_is_connected = m_wallet->check_connection(&version, NULL, DEFAULT_CONNECTION_TIMEOUT_MILLIS);
+ bool wallet_is_outdated = false, daemon_is_outdated = false;
+ m_is_connected = m_wallet->check_connection(&version, NULL, DEFAULT_CONNECTION_TIMEOUT_MILLIS, &wallet_is_outdated, &daemon_is_outdated);
if (!m_is_connected)
- return Wallet::ConnectionStatus_Disconnected;
+ {
+ if (!m_wallet->light_wallet() && (wallet_is_outdated || daemon_is_outdated))
+ return Wallet::ConnectionStatus_WrongVersion;
+ else
+ return Wallet::ConnectionStatus_Disconnected;
+ }
// Version check is not implemented in light wallets nodes/wallets
if (!m_wallet->light_wallet() && (version >> 16) != CORE_RPC_VERSION_MAJOR)
return Wallet::ConnectionStatus_WrongVersion;
diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h
index 0e61ee330..ec2d7e9b3 100644
--- a/src/wallet/api/wallet.h
+++ b/src/wallet/api/wallet.h
@@ -146,7 +146,7 @@ public:
MultisigState multisig() const override;
std::string getMultisigInfo() const override;
std::string makeMultisig(const std::vector<std::string>& info, uint32_t threshold) override;
- std::string exchangeMultisigKeys(const std::vector<std::string> &info) override;
+ std::string exchangeMultisigKeys(const std::vector<std::string> &info, const bool force_update_use_with_caution = false) override;
bool exportMultisigImages(std::string& images) override;
size_t importMultisigImages(const std::vector<std::string>& images) override;
bool hasMultisigPartialKeyImages() const override;
@@ -169,6 +169,7 @@ public:
bool importKeyImages(const std::string &filename) override;
bool exportOutputs(const std::string &filename, bool all = false) override;
bool importOutputs(const std::string &filename) override;
+ bool scanTransactions(const std::vector<std::string> &txids) override;
virtual void disposeTransaction(PendingTransaction * t) override;
virtual uint64_t estimateTransactionFee(const std::vector<std::pair<std::string, uint64_t>> &destinations,
diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h
index c6f81f0e4..0ae84adb9 100644
--- a/src/wallet/api/wallet2_api.h
+++ b/src/wallet/api/wallet2_api.h
@@ -796,9 +796,10 @@ struct Wallet
/**
* @brief exchange_multisig_keys - provides additional key exchange round for arbitrary multisig schemes (like N-1/N, M/N)
* @param info - base58 encoded key derivations returned by makeMultisig or exchangeMultisigKeys function call
+ * @param force_update_use_with_caution - force multisig account to update even if not all signers contribute round messages
* @return new info string if more rounds required or an empty string if wallet creation is done
*/
- virtual std::string exchangeMultisigKeys(const std::vector<std::string> &info) = 0;
+ virtual std::string exchangeMultisigKeys(const std::vector<std::string> &info, const bool force_update_use_with_caution) = 0;
/**
* @brief exportMultisigImages - exports transfers' key images
* @param images - output paramter for hex encoded array of images
@@ -927,6 +928,13 @@ struct Wallet
*/
virtual bool importOutputs(const std::string &filename) = 0;
+ /*!
+ * \brief scanTransactions - scan a list of transaction ids, this operation may reveal the txids to the remote node and affect your privacy
+ * \param txids - list of transaction ids
+ * \return - true on success
+ */
+ virtual bool scanTransactions(const std::vector<std::string> &txids) = 0;
+
virtual TransactionHistory * history() = 0;
virtual AddressBook * addressBook() = 0;
virtual Subaddress * subaddress() = 0;
diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp
index 7810abdd2..0a9ea8f7b 100644
--- a/src/wallet/node_rpc_proxy.cpp
+++ b/src/wallet/node_rpc_proxy.cpp
@@ -82,18 +82,21 @@ void NodeRPCProxy::invalidate()
m_rpc_payment_seed_hash = crypto::null_hash;
m_rpc_payment_next_seed_hash = crypto::null_hash;
m_height_time = 0;
+ m_target_height_time = 0;
m_rpc_payment_diff = 0;
m_rpc_payment_credits_per_hash_found = 0;
m_rpc_payment_height = 0;
m_rpc_payment_cookie = 0;
+ m_daemon_hard_forks.clear();
}
-boost::optional<std::string> NodeRPCProxy::get_rpc_version(uint32_t &rpc_version)
+boost::optional<std::string> NodeRPCProxy::get_rpc_version(uint32_t &rpc_version, std::vector<std::pair<uint8_t, uint64_t>> &daemon_hard_forks, uint64_t &height, uint64_t &target_height)
{
if (m_offline)
return boost::optional<std::string>("offline");
if (m_rpc_version == 0)
{
+ const time_t now = time(NULL);
cryptonote::COMMAND_RPC_GET_VERSION::request req_t = AUTO_VAL_INIT(req_t);
cryptonote::COMMAND_RPC_GET_VERSION::response resp_t = AUTO_VAL_INIT(resp_t);
{
@@ -101,9 +104,28 @@ boost::optional<std::string> NodeRPCProxy::get_rpc_version(uint32_t &rpc_version
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_version", req_t, resp_t, m_http_client, rpc_timeout);
RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "get_version");
}
+
m_rpc_version = resp_t.version;
+ m_daemon_hard_forks.clear();
+ for (const auto &hf : resp_t.hard_forks)
+ m_daemon_hard_forks.push_back(std::make_pair(hf.hf_version, hf.height));
+ if (resp_t.current_height > 0 || resp_t.target_height > 0)
+ {
+ m_height = resp_t.current_height;
+ m_target_height = resp_t.target_height;
+ m_height_time = now;
+ m_target_height_time = now;
+ }
}
+
rpc_version = m_rpc_version;
+ daemon_hard_forks = m_daemon_hard_forks;
+ boost::optional<std::string> result = get_height(height);
+ if (result)
+ return result;
+ result = get_target_height(target_height);
+ if (result)
+ return result;
return boost::optional<std::string>();
}
@@ -138,6 +160,7 @@ boost::optional<std::string> NodeRPCProxy::get_info()
m_adjusted_time = resp_t.adjusted_time;
m_get_info_time = now;
m_height_time = now;
+ m_target_height_time = now;
}
return boost::optional<std::string>();
}
@@ -160,6 +183,13 @@ boost::optional<std::string> NodeRPCProxy::get_height(uint64_t &height)
boost::optional<std::string> NodeRPCProxy::get_target_height(uint64_t &height)
{
+ const time_t now = time(NULL);
+ if (now < m_target_height_time + 30) // re-cache every 30 seconds
+ {
+ height = m_target_height;
+ return boost::optional<std::string>();
+ }
+
auto res = get_info();
if (res)
return res;
diff --git a/src/wallet/node_rpc_proxy.h b/src/wallet/node_rpc_proxy.h
index 07675cdb0..e320565ac 100644
--- a/src/wallet/node_rpc_proxy.h
+++ b/src/wallet/node_rpc_proxy.h
@@ -48,7 +48,7 @@ public:
void invalidate();
void set_offline(bool offline) { m_offline = offline; }
- boost::optional<std::string> get_rpc_version(uint32_t &version);
+ boost::optional<std::string> get_rpc_version(uint32_t &rpc_version, std::vector<std::pair<uint8_t, uint64_t>> &daemon_hard_forks, uint64_t &height, uint64_t &target_height);
boost::optional<std::string> get_height(uint64_t &height);
void set_height(uint64_t h);
boost::optional<std::string> get_target_height(uint64_t &height);
@@ -103,6 +103,8 @@ private:
crypto::hash m_rpc_payment_next_seed_hash;
uint32_t m_rpc_payment_cookie;
time_t m_height_time;
+ time_t m_target_height_time;
+ std::vector<std::pair<uint8_t, uint64_t>> m_daemon_hard_forks;
};
}
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index 9efbcfac6..a1a51f7de 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -47,6 +47,7 @@
using namespace epee;
#include "cryptonote_config.h"
+#include "hardforks/hardforks.h"
#include "cryptonote_core/tx_sanity_check.h"
#include "wallet_rpc_helpers.h"
#include "wallet2.h"
@@ -62,6 +63,7 @@ using namespace epee;
#include "multisig/multisig.h"
#include "multisig/multisig_account.h"
#include "multisig/multisig_kex_msg.h"
+#include "multisig/multisig_tx_builder_ringct.h"
#include "common/boost_serialization_helper.h"
#include "common/command_line.h"
#include "common/threadpool.h"
@@ -274,6 +276,7 @@ struct options {
const command_line::arg_descriptor<bool> no_dns = {"no-dns", tools::wallet2::tr("Do not use DNS"), false};
const command_line::arg_descriptor<bool> offline = {"offline", tools::wallet2::tr("Do not connect to a daemon, nor use DNS"), false};
const command_line::arg_descriptor<std::string> extra_entropy = {"extra-entropy", tools::wallet2::tr("File containing extra entropy to initialize the PRNG (any data, aim for 256 bits of entropy to be useful, which typically means more than 256 bits of data)")};
+ const command_line::arg_descriptor<bool> allow_mismatched_daemon_version = {"allow-mismatched-daemon-version", tools::wallet2::tr("Allow communicating with a daemon that uses a different version"), false};
};
void do_prepare_file_names(const std::string& file_path, std::string& keys_file, std::string& wallet_file, std::string &mms_file)
@@ -483,6 +486,9 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl
add_extra_entropy_thread_safe(data.data(), data.size());
}
+ if (command_line::has_arg(vm, opts.allow_mismatched_daemon_version))
+ wallet->allow_mismatched_daemon_version(true);
+
try
{
if (!command_line::is_arg_defaulted(vm, opts.tx_notify))
@@ -1216,7 +1222,10 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std
m_rpc_version(0),
m_export_format(ExportFormat::Binary),
m_load_deprecated_formats(false),
- m_credits_target(0)
+ m_credits_target(0),
+ m_enable_multisig(false),
+ m_has_ever_refreshed_from_node(false),
+ m_allow_mismatched_daemon_version(false)
{
set_rpc_client_secret_key(rct::rct2sk(rct::skGen()));
}
@@ -1276,6 +1285,7 @@ void wallet2::init_options(boost::program_options::options_description& desc_par
command_line::add_arg(desc_params, opts.no_dns);
command_line::add_arg(desc_params, opts.offline);
command_line::add_arg(desc_params, opts.extra_entropy);
+ command_line::add_arg(desc_params, opts.allow_mismatched_daemon_version);
}
std::pair<std::unique_ptr<wallet2>, tools::password_container> wallet2::make_from_json(const boost::program_options::variables_map& vm, bool unattended, const std::string& json_file, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter)
@@ -1336,6 +1346,7 @@ bool wallet2::set_daemon(std::string daemon_address, boost::optional<epee::net_u
}
m_rpc_payment_state.expected_spent = 0;
m_rpc_payment_state.discrepancy = 0;
+ m_rpc_version = 0;
m_node_rpc_proxy.invalidate();
}
@@ -2207,7 +2218,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
}
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, spends_one_of_ours(tx), td.m_tx.unlock_time);
+ m_callback->on_money_received(height, txid, tx, td.m_amount, 0, td.m_subaddr_index, spends_one_of_ours(tx), td.m_tx.unlock_time);
}
total_received_1 += amount;
notify = true;
@@ -2241,7 +2252,8 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
tx_money_got_in_outs[tx_scan_info[o].received->index] -= m_transfers[kit->second].amount();
uint64_t amount = tx.vout[o].amount ? tx.vout[o].amount : tx_scan_info[o].amount;
- uint64_t extra_amount = amount - m_transfers[kit->second].amount();
+ uint64_t burnt = m_transfers[kit->second].amount();
+ uint64_t extra_amount = amount - burnt;
if (!pool)
{
transfer_details &td = m_transfers[kit->second];
@@ -2284,7 +2296,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
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, spends_one_of_ours(tx), td.m_tx.unlock_time);
+ m_callback->on_money_received(height, txid, tx, td.m_amount, burnt, td.m_subaddr_index, spends_one_of_ours(tx), td.m_tx.unlock_time);
}
total_received_1 += extra_amount;
notify = true;
@@ -2713,7 +2725,7 @@ void wallet2::process_parsed_blocks(uint64_t start_height, const std::vector<cry
THROW_WALLET_EXCEPTION_IF(blocks.size() != parsed_blocks.size(), error::wallet_internal_error, "size mismatch");
THROW_WALLET_EXCEPTION_IF(!m_blockchain.is_in_bounds(current_index), error::out_of_hashchain_bounds_error);
- tools::threadpool& tpool = tools::threadpool::getInstance();
+ tools::threadpool& tpool = tools::threadpool::getInstanceForCompute();
tools::threadpool::waiter waiter(tpool);
size_t num_txes = 0;
@@ -2882,6 +2894,11 @@ void wallet2::process_parsed_blocks(uint64_t start_height, const std::vector<cry
" (height " + std::to_string(start_height) + "), local block id at this height: " +
string_tools::pod_to_hex(m_blockchain[current_index]));
+ const uint64_t reorg_depth = m_blockchain.size() - current_index;
+ THROW_WALLET_EXCEPTION_IF(reorg_depth > m_max_reorg_depth, error::reorg_depth_error,
+ tr("reorg exceeds maximum allowed depth, use 'set max-reorg-depth N' to allow it, reorg depth: ") +
+ std::to_string(reorg_depth));
+
detach_blockchain(current_index, output_tracker_cache);
process_new_blockchain_entry(bl, blocks[i], parsed_blocks[i], bl_id, current_index, tx_cache_data, tx_cache_data_offset, output_tracker_cache);
}
@@ -2906,6 +2923,26 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
refresh(trusted_daemon, start_height, blocks_fetched, received_money);
}
//----------------------------------------------------------------------------------------------------
+void check_block_hard_fork_version(cryptonote::network_type nettype, uint8_t hf_version, uint64_t height, bool &wallet_is_outdated, bool &daemon_is_outdated)
+{
+ const size_t wallet_num_hard_forks = nettype == TESTNET ? num_testnet_hard_forks
+ : nettype == STAGENET ? num_stagenet_hard_forks : num_mainnet_hard_forks;
+ const hardfork_t *wallet_hard_forks = nettype == TESTNET ? testnet_hard_forks
+ : nettype == STAGENET ? stagenet_hard_forks : mainnet_hard_forks;
+
+ wallet_is_outdated = static_cast<size_t>(hf_version) > wallet_num_hard_forks;
+ if (wallet_is_outdated)
+ return;
+
+ // check block's height falls within wallet's expected range for block's given version
+ uint64_t start_height = hf_version == 1 ? 0 : wallet_hard_forks[hf_version - 1].height;
+ uint64_t end_height = static_cast<size_t>(hf_version) + 1 > wallet_num_hard_forks
+ ? std::numeric_limits<uint64_t>::max()
+ : wallet_hard_forks[hf_version].height;
+
+ daemon_is_outdated = height < start_height || height >= end_height;
+}
+//----------------------------------------------------------------------------------------------------
void wallet2::pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::vector<cryptonote::block_complete_entry> &prev_blocks, const std::vector<parsed_block> &prev_parsed_blocks, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<parsed_block> &parsed_blocks, bool &last, bool &error, std::exception_ptr &exception)
{
error = false;
@@ -2931,7 +2968,7 @@ void wallet2::pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks
pull_blocks(start_height, blocks_start_height, short_chain_history, blocks, o_indices, current_height);
THROW_WALLET_EXCEPTION_IF(blocks.size() != o_indices.size(), error::wallet_internal_error, "Mismatched sizes of blocks and o_indices");
- tools::threadpool& tpool = tools::threadpool::getInstance();
+ tools::threadpool& tpool = tools::threadpool::getInstanceForCompute();
tools::threadpool::waiter waiter(tpool);
parsed_blocks.resize(blocks.size());
for (size_t i = 0; i < blocks.size(); ++i)
@@ -2947,6 +2984,23 @@ void wallet2::pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks
error = true;
break;
}
+
+ if (!m_allow_mismatched_daemon_version)
+ {
+ // make sure block's hard fork version is expected at the block's height
+ uint8_t hf_version = parsed_blocks[i].block.major_version;
+ uint64_t height = blocks_start_height + i;
+ bool wallet_is_outdated = false;
+ bool daemon_is_outdated = false;
+ check_block_hard_fork_version(m_nettype, hf_version, height, wallet_is_outdated, daemon_is_outdated);
+ THROW_WALLET_EXCEPTION_IF(wallet_is_outdated || daemon_is_outdated, error::incorrect_fork_version,
+ "Unexpected hard fork version v" + std::to_string(hf_version) + " at height " + std::to_string(height) + ". " +
+ (wallet_is_outdated
+ ? "Make sure your wallet is up to date"
+ : "Make sure the node you are connected to is running the latest version")
+ );
+ }
+
parsed_blocks[i].o_indices = std::move(o_indices[i]);
}
@@ -3153,14 +3207,18 @@ void wallet2::update_pool_state(std::vector<std::tuple<cryptonote::transaction,
}
}
- // get those txes
- if (!txids.empty())
+ // get_transaction_pool_hashes.bin may return more transactions than we're allowed to request in restricted mode
+ const size_t SLICE_SIZE = 100; // RESTRICTED_TRANSACTIONS_COUNT as defined in rpc/core_rpc_server.cpp
+ for (size_t offset = 0; offset < txids.size(); offset += SLICE_SIZE)
{
cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request req;
cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response res;
- for (const auto &p: txids)
- req.txs_hashes.push_back(epee::string_tools::pod_to_hex(p.first));
- MDEBUG("asking for " << txids.size() << " transactions");
+
+ const size_t n_txids = std::min<size_t>(SLICE_SIZE, txids.size() - offset);
+ for (size_t n = offset; n < (offset + n_txids); ++n) {
+ req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txids.at(n).first));
+ }
+ MDEBUG("asking for " << req.txs_hashes.size() << " transactions");
req.decode_as_json = false;
req.prune = true;
@@ -3177,7 +3235,7 @@ void wallet2::update_pool_state(std::vector<std::tuple<cryptonote::transaction,
MDEBUG("Got " << r << " and " << res.status);
if (r && res.status == CORE_RPC_STATUS_OK)
{
- if (res.txs.size() == txids.size())
+ if (res.txs.size() == req.txs_hashes.size())
{
for (const auto &tx_entry: res.txs)
{
@@ -3213,7 +3271,7 @@ void wallet2::update_pool_state(std::vector<std::tuple<cryptonote::transaction,
}
else
{
- LOG_PRINT_L0("Expected " << txids.size() << " tx(es), got " << res.txs.size());
+ LOG_PRINT_L0("Expected " << n_txids << " out of " << txids.size() << " tx(es), got " << res.txs.size());
}
}
else
@@ -3407,11 +3465,15 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
size_t try_count = 0;
crypto::hash last_tx_hash_id = m_transfers.size() ? m_transfers.back().m_txid : null_hash;
std::list<crypto::hash> short_chain_history;
- tools::threadpool& tpool = tools::threadpool::getInstance();
+ tools::threadpool& tpool = tools::threadpool::getInstanceForCompute();
tools::threadpool::waiter waiter(tpool);
uint64_t blocks_start_height;
std::vector<cryptonote::block_complete_entry> blocks;
std::vector<parsed_block> parsed_blocks;
+ // TODO moneromooo-monero says this about the "refreshed" variable:
+ // "I had to reorder some code to fix... a timing info leak IIRC. In turn, this undid something I had fixed before, ... a subtle race condition with the txpool.
+ // It was pretty subtle IIRC, and so I needed time to think about how to refix it after the move, and I never got to it."
+ // https://github.com/monero-project/monero/pull/6097
bool refreshed = false;
std::shared_ptr<std::map<std::pair<uint64_t, uint64_t>, size_t>> output_tracker_cache;
hw::device &hwdev = m_account.get_device();
@@ -3523,6 +3585,8 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
throw std::runtime_error("proxy exception in refresh thread");
}
+ m_has_ever_refreshed_from_node = true;
+
if(!first && blocks_start_height == next_blocks_start_height)
{
m_node_rpc_proxy.set_height(m_blockchain.size());
@@ -3532,15 +3596,6 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
first = false;
- if (!next_blocks.empty())
- {
- const uint64_t expected_start_height = std::max(static_cast<uint64_t>(m_blockchain.size()), uint64_t(1)) - 1;
- const uint64_t reorg_depth = expected_start_height - std::min(expected_start_height, next_blocks_start_height);
- THROW_WALLET_EXCEPTION_IF(reorg_depth > m_max_reorg_depth, error::reorg_depth_error,
- tr("reorg exceeds maximum allowed depth, use 'set max-reorg-depth N' to allow it, reorg depth: ") +
- std::to_string(reorg_depth));
- }
-
// if we've got at least 10 blocks to refresh, assume we're starting
// a long refresh, and setup a tracking output cache if we need to
if (m_track_uses && (!output_tracker_cache || output_tracker_cache->empty()) && next_blocks.size() >= 10)
@@ -3568,6 +3623,11 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
THROW_WALLET_EXCEPTION_IF(!waiter.wait(), error::wallet_internal_error, "Exception in thread pool");
throw;
}
+ catch (const error::incorrect_fork_version&)
+ {
+ THROW_WALLET_EXCEPTION_IF(!waiter.wait(), error::wallet_internal_error, "Exception in thread pool");
+ throw;
+ }
catch (const std::exception&)
{
blocks_fetched += added_blocks;
@@ -3625,32 +3685,7 @@ bool wallet2::refresh(bool trusted_daemon, uint64_t & blocks_fetched, bool& rece
//----------------------------------------------------------------------------------------------------
bool wallet2::get_rct_distribution(uint64_t &start_height, std::vector<uint64_t> &distribution)
{
- uint32_t rpc_version;
- boost::optional<std::string> result = m_node_rpc_proxy.get_rpc_version(rpc_version);
- // no error
- if (!!result)
- {
- // empty string -> not connection
- THROW_WALLET_EXCEPTION_IF(result->empty(), tools::error::no_connection_to_daemon, "getversion");
- THROW_WALLET_EXCEPTION_IF(*result == CORE_RPC_STATUS_BUSY, tools::error::daemon_busy, "getversion");
- if (*result != CORE_RPC_STATUS_OK)
- {
- MDEBUG("Cannot determine daemon RPC version, not requesting rct distribution");
- return false;
- }
- }
- else
- {
- if (rpc_version >= MAKE_CORE_RPC_VERSION(1, 19))
- {
- MDEBUG("Daemon is recent enough, requesting rct distribution");
- }
- else
- {
- MDEBUG("Daemon is too old, not requesting rct distribution");
- return false;
- }
- }
+ MDEBUG("Requesting rct distribution");
cryptonote::COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::request req = AUTO_VAL_INIT(req);
cryptonote::COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::response res = AUTO_VAL_INIT(res);
@@ -4051,6 +4086,9 @@ boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee:
value2.SetUint64(m_credits_target);
json.AddMember("credits_target", value2, json.GetAllocator());
+ value2.SetInt(m_enable_multisig ? 1 : 0);
+ json.AddMember("enable_multisig", value2, json.GetAllocator());
+
// Serialize the JSON object
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
@@ -4199,6 +4237,8 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st
m_persistent_rpc_client_id = false;
m_auto_mine_for_rpc_payment_threshold = -1.0f;
m_credits_target = 0;
+ m_enable_multisig = false;
+ m_allow_mismatched_daemon_version = false;
}
else if(json.IsObject())
{
@@ -4431,6 +4471,8 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st
m_auto_mine_for_rpc_payment_threshold = field_auto_mine_for_rpc_payment;
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, credits_target, uint64_t, Uint64, false, 0);
m_credits_target = field_credits_target;
+ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, enable_multisig, int, Int, false, false);
+ m_enable_multisig = field_enable_multisig;
}
else
{
@@ -4700,7 +4742,8 @@ void wallet2::init_type(hw::device::device_type device_type)
}
/*!
- * \brief Generates a wallet or restores one.
+ * \brief Generates a wallet or restores one. Assumes the multisig setup
+ * has already completed for the provided multisig info.
* \param wallet_ Name of wallet file
* \param password Password of wallet file
* \param multisig_data The multisig restore info and keys
@@ -4759,11 +4802,6 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string&
crypto::public_key local_signer;
THROW_WALLET_EXCEPTION_IF(!crypto::secret_key_to_public_key(spend_secret_key, local_signer), error::invalid_multisig_seed);
THROW_WALLET_EXCEPTION_IF(std::find(multisig_signers.begin(), multisig_signers.end(), local_signer) == multisig_signers.end(), error::invalid_multisig_seed);
- rct::key skey = rct::zero();
- for (const auto &msk: multisig_keys)
- sc_add(skey.bytes, skey.bytes, rct::sk2rct(msk).bytes);
- THROW_WALLET_EXCEPTION_IF(!(rct::rct2sk(skey) == spend_secret_key), error::invalid_multisig_seed);
- memwipe(&skey, sizeof(rct::key));
m_account.make_multisig(view_secret_key, spend_secret_key, spend_public_key, multisig_keys);
@@ -4774,6 +4812,8 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string&
m_multisig = true;
m_multisig_threshold = threshold;
m_multisig_signers = multisig_signers;
+ // wallet is assumed already finalized
+ m_multisig_rounds_passed = multisig::multisig_setup_rounds_required(m_multisig_signers.size(), m_multisig_threshold);
setup_keys(password);
create_keys_file(wallet_, false, password, m_nettype != MAINNET || create_address_file);
@@ -5067,7 +5107,6 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password,
m_multisig_rounds_passed = 1;
// derivations stored (should be empty in last round)
- // TODO: make use of the origins map for aggregation-style signing (instead of round-robin)
m_multisig_derivations.clear();
m_multisig_derivations.reserve(multisig_account.get_kex_keys_to_origins_map().size());
@@ -5092,12 +5131,11 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password,
}
//----------------------------------------------------------------------------------------------------
std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &password,
- const std::vector<std::string> &kex_messages)
+ const std::vector<std::string> &kex_messages,
+ const bool force_update_use_with_caution /*= false*/)
{
bool ready{false};
CHECK_AND_ASSERT_THROW_MES(multisig(&ready), "The wallet is not multisig");
- CHECK_AND_ASSERT_THROW_MES(!ready, "Multisig wallet creation process has already been finished");
- CHECK_AND_ASSERT_THROW_MES(kex_messages.size() > 0, "No key exchange messages passed in.");
// decrypt account keys
epee::misc_utils::auto_scope_leave_caller keys_reencryptor;
@@ -5116,15 +5154,7 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor
);
}
- // open kex messages
- std::vector<multisig::multisig_kex_msg> expanded_msgs;
- expanded_msgs.reserve(kex_messages.size());
-
- for (const auto &msg : kex_messages)
- expanded_msgs.emplace_back(msg);
-
// reconstruct multisig account
- crypto::public_key dummy;
multisig::multisig_keyset_map_memsafe_t kex_origins_map;
for (const auto &derivation : m_multisig_derivations)
@@ -5144,8 +5174,25 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor
""
};
+ // KLUDGE: early return if there are no kex messages and main kex is complete (will return the post-kex verification round
+ // message) (it's a kludge because this behavior would be more appropriate for a standalone wallet method)
+ if (kex_messages.size() == 0)
+ {
+ CHECK_AND_ASSERT_THROW_MES(multisig_account.main_kex_rounds_done(),
+ "Exchange multisig keys: there are no kex messages but the main kex rounds are not done.");
+
+ return multisig_account.get_next_kex_round_msg();
+ }
+
+ // open kex messages
+ std::vector<multisig::multisig_kex_msg> expanded_msgs;
+ expanded_msgs.reserve(kex_messages.size());
+
+ for (const auto &msg : kex_messages)
+ expanded_msgs.emplace_back(msg);
+
// update multisig kex
- multisig_account.kex_update(expanded_msgs);
+ multisig_account.kex_update(expanded_msgs, force_update_use_with_caution);
// update wallet state
@@ -5160,7 +5207,6 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor
"Failed to update multisig wallet account due to bad keys");
// derivations stored (should be empty in last round)
- // TODO: make use of the origins map for aggregation-style signing (instead of round-robin)
m_multisig_derivations.clear();
m_multisig_derivations.reserve(multisig_account.get_kex_keys_to_origins_map().size());
@@ -5227,7 +5273,7 @@ bool wallet2::multisig(bool *ready, uint32_t *threshold, uint32_t *total) const
if (ready)
{
*ready = !(get_account().get_keys().m_account_address.m_spend_public_key == rct::rct2pk(rct::identity())) &&
- (m_multisig_rounds_passed == multisig::multisig_kex_rounds_required(m_multisig_signers.size(), m_multisig_threshold) + 1);
+ (m_multisig_rounds_passed == multisig::multisig_setup_rounds_required(m_multisig_signers.size(), m_multisig_threshold));
}
return true;
}
@@ -5343,7 +5389,7 @@ bool wallet2::prepare_file_names(const std::string& file_path)
return true;
}
//----------------------------------------------------------------------------------------------------
-bool wallet2::check_connection(uint32_t *version, bool *ssl, uint32_t timeout)
+bool wallet2::check_connection(uint32_t *version, bool *ssl, uint32_t timeout, bool *wallet_is_outdated, bool *daemon_is_outdated)
{
THROW_WALLET_EXCEPTION_IF(!m_is_initialized, error::wallet_not_initialized);
@@ -5380,20 +5426,99 @@ bool wallet2::check_connection(uint32_t *version, bool *ssl, uint32_t timeout)
}
}
- if (!m_rpc_version)
+ if (!m_rpc_version && !check_version(version, wallet_is_outdated, daemon_is_outdated))
+ return false;
+ if (version)
+ *version = m_rpc_version;
+
+ return true;
+}
+//----------------------------------------------------------------------------------------------------
+bool wallet2::check_version(uint32_t *version, bool *wallet_is_outdated, bool *daemon_is_outdated)
+{
+ uint32_t rpc_version;
+ std::vector<std::pair<uint8_t, uint64_t>> daemon_hard_forks;
+ uint64_t height;
+ uint64_t target_height;
+ if (m_node_rpc_proxy.get_rpc_version(rpc_version, daemon_hard_forks, height, target_height))
{
- cryptonote::COMMAND_RPC_GET_VERSION::request req_t = AUTO_VAL_INIT(req_t);
- cryptonote::COMMAND_RPC_GET_VERSION::response resp_t = AUTO_VAL_INIT(resp_t);
- bool r = invoke_http_json_rpc("/json_rpc", "get_version", req_t, resp_t);
- if(!r || resp_t.status != CORE_RPC_STATUS_OK) {
- if(version)
- *version = 0;
+ if(version)
+ *version = 0;
+ return false;
+ }
+
+ // check wallet compatibility with daemon's hard fork version
+ if (!m_allow_mismatched_daemon_version)
+ if (!check_hard_fork_version(m_nettype, daemon_hard_forks, height, target_height, wallet_is_outdated, daemon_is_outdated))
return false;
+
+ m_rpc_version = rpc_version;
+ return true;
+}
+//----------------------------------------------------------------------------------------------------
+bool wallet2::check_hard_fork_version(cryptonote::network_type nettype, const std::vector<std::pair<uint8_t, uint64_t>> &daemon_hard_forks, const uint64_t height, const uint64_t target_height, bool *wallet_is_outdated, bool *daemon_is_outdated)
+{
+ const size_t wallet_num_hard_forks = nettype == TESTNET ? num_testnet_hard_forks
+ : nettype == STAGENET ? num_stagenet_hard_forks : num_mainnet_hard_forks;
+ const hardfork_t *wallet_hard_forks = nettype == TESTNET ? testnet_hard_forks
+ : nettype == STAGENET ? stagenet_hard_forks : mainnet_hard_forks;
+
+ // First check if wallet or daemon is outdated (whether either are unaware of
+ // a hard fork). Then check if fork has passed rendering versions incompatible
+ if (daemon_hard_forks.size() > 0)
+ {
+ bool daemon_outdated = daemon_hard_forks.size() < wallet_num_hard_forks;
+ bool wallet_outdated = daemon_hard_forks.size() > wallet_num_hard_forks;
+
+ if (daemon_is_outdated)
+ *daemon_is_outdated = daemon_outdated;
+ if (wallet_is_outdated)
+ *wallet_is_outdated = wallet_outdated;
+
+ if (daemon_outdated)
+ {
+ uint64_t daemon_missed_fork_height = wallet_hard_forks[daemon_hard_forks.size()].height;
+
+ // If the daemon missed the fork, then technically it is no longer part of
+ // the Monero network. Don't connect.
+ bool daemon_missed_fork = height >= daemon_missed_fork_height || target_height >= daemon_missed_fork_height;
+ if (daemon_missed_fork)
+ return false;
+ }
+ else if (wallet_outdated)
+ {
+ uint64_t wallet_missed_fork_height = daemon_hard_forks[wallet_num_hard_forks].second;
+
+ // If the wallet missed the fork, then technically it is no longer able
+ // to communicate with the Monero network. Don't connect.
+ bool wallet_missed_fork = height >= wallet_missed_fork_height || target_height >= wallet_missed_fork_height;
+ if (wallet_missed_fork)
+ return false;
}
- m_rpc_version = resp_t.version;
}
- if (version)
- *version = m_rpc_version;
+ else
+ {
+ // Non-updated daemons won't return daemon_hard_forks in response to
+ // get_version. Fall back to extra call to get_hard_fork_info by version.
+ uint64_t daemon_fork_height;
+ get_hard_fork_info(wallet_num_hard_forks-1/* wallet expects "double fork" pattern */, daemon_fork_height);
+ bool daemon_outdated = daemon_fork_height == std::numeric_limits<uint64_t>::max();
+
+ if (daemon_is_outdated)
+ *daemon_is_outdated = daemon_outdated;
+
+ if (daemon_outdated)
+ {
+ uint64_t daemon_missed_fork_height = wallet_hard_forks[wallet_num_hard_forks-2].height;
+ bool daemon_missed_fork = height >= daemon_missed_fork_height || target_height >= daemon_missed_fork_height;
+ if (daemon_missed_fork)
+ return false;
+ }
+
+ // Don't need to check if wallet is outdated here because the daemons updated
+ // for a future hard fork will serve daemon_hard_forks above. The check for
+ // an outdated wallet is done above using daemon_hard_forks.
+ }
return true;
}
@@ -6623,9 +6748,9 @@ bool wallet2::sign_tx(const std::string &unsigned_filename, const std::string &s
//----------------------------------------------------------------------------------------------------
bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pending_tx> &txs, signed_tx_set &signed_txes)
{
- if (!exported_txs.new_transfers.second.empty())
+ if (!std::get<2>(exported_txs.new_transfers).empty())
import_outputs(exported_txs.new_transfers);
- else
+ else if (!std::get<2>(exported_txs.transfers).empty())
import_outputs(exported_txs.transfers);
// sign the transactions
@@ -6639,8 +6764,7 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pendin
rct::RCTConfig rct_config = sd.rct_config;
crypto::secret_key tx_key;
std::vector<crypto::secret_key> additional_tx_keys;
- 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, rct_config, m_multisig ? &msout : NULL, sd.use_view_tags);
+ 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_config, sd.use_view_tags);
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, sd.unlock_time, m_nettype);
// 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,
@@ -7148,77 +7272,114 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto
txids.clear();
- // sign the transactions
+ // The 'exported_txs' contains a set of different transactions for the multisig group to try to sign. Each of those
+ // transactions has a set of 'signing attempts' corresponding to all the possible signing groups within the multisig.
+ // - Here, we will partially sign as many of those signing attempts as possible, for each proposed transaction.
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) <<
+ const tools::wallet2::tx_construction_data &sd = ptx.construction_data;
+ LOG_PRINT_L1(" " << (n+1) << ": " << sd.sources.size() << " inputs, ring size " << (sd.sources[0].outputs.size()) <<
", 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;
- rct::RCTConfig rct_config = sd.rct_config;
- bool shuffle_outs = false;
- 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, rct_config, &msout, shuffle_outs, sd.use_view_tags);
- THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, sd.unlock_time, m_nettype);
- 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);
+ // reconstruct the partially-signed transaction attempt to verify we are signing something that at least looks like a transaction
+ // note: the caller should further verify that the tx details are acceptable (inputs/outputs/memos/tx type)
+ multisig::signing::tx_builder_ringct_t multisig_tx_builder;
+ THROW_WALLET_EXCEPTION_IF(
+ not multisig_tx_builder.init(
+ m_account.get_keys(),
+ ptx.construction_data.extra,
+ ptx.construction_data.unlock_time,
+ ptx.construction_data.subaddr_account,
+ ptx.construction_data.subaddr_indices,
+ ptx.construction_data.sources,
+ ptx.construction_data.splitted_dsts,
+ ptx.construction_data.change_dts,
+ ptx.construction_data.rct_config,
+ ptx.construction_data.use_rct,
+ true, //true = we are reconstructing the tx (it was first constructed by the tx proposer)
+ ptx.tx_key,
+ ptx.additional_tx_keys,
+ ptx.multisig_tx_key_entropy,
+ ptx.tx
+ ),
+ error::wallet_internal_error,
+ "error: multisig::signing::tx_builder_ringct_t::init"
+ );
+ // go through each signing attempt for this transaction (each signing attempt corresponds to some subgroup of signers
+ // of size 'threshold')
for (auto &sig: ptx.multisig_sigs)
{
+ // skip this partial tx if it's intended for a subgroup of signers that doesn't include the local signer
+ // note: this check can only weed out signers who provided multisig_infos to the multisig tx proposer's
+ // (initial author's) last call to import_multisig() before making this tx proposal; all other signers
+ // will encounter a 'need to export multisig' wallet error in get_multisig_k() below
+ // note2: the 'need to export multisig' wallet error can also appear if a bad/buggy tx proposer adds duplicate
+ // 'used_L' to the set of tx attempts, or if two different tx proposals use the same 'used_L' values and the
+ // local signer calls this function on both of them
if (sig.ignore.find(local_signer) == sig.ignore.end())
{
- ptx.tx.rct_signatures = sig.sigs;
-
- rct::keyV k;
+ rct::keyM local_nonces_k(sd.selected_transfers.size(), rct::keyV(multisig::signing::kAlphaComponents));
rct::key skey = rct::zero();
- auto wiper = epee::misc_utils::create_scope_leave_handler([&](){ memwipe(k.data(), k.size() * sizeof(k[0])); memwipe(&skey, sizeof(skey)); });
-
- for (size_t idx: sd.selected_transfers)
- k.push_back(get_multisig_k(idx, sig.used_L));
+ auto wiper = epee::misc_utils::create_scope_leave_handler([&]{
+ for (auto& e: local_nonces_k)
+ memwipe(e.data(), e.size() * sizeof(rct::key));
+ memwipe(&skey, sizeof(rct::key));
+ });
+
+ // get local signer's nonces for this transaction attempt's inputs
+ // note: whoever created 'exported_txs' has full power to match proposed tx inputs (selected_transfers)
+ // with the public nonces of the multisig signers who call this function (via 'used_L' as identifiers), however
+ // the local signer will only use a given nonce exactly once (even if a used_L is repeated)
+ for (std::size_t i = 0; i < local_nonces_k.size(); ++i) {
+ for (std::size_t j = 0; j < multisig::signing::kAlphaComponents; ++j) {
+ get_multisig_k(sd.selected_transfers[i], sig.used_L, local_nonces_k[i][j]);
+ }
+ }
- for (const auto &msk: get_account().get_multisig_keys())
+ // round-robin signing: sign with all local multisig key shares that other signers have not signed with yet
+ for (const auto &multisig_skey: get_account().get_multisig_keys())
{
- crypto::public_key pmsk = get_multisig_signing_public_key(msk);
+ crypto::public_key multisig_pkey = get_multisig_signing_public_key(multisig_skey);
- if (sig.signing_keys.find(pmsk) == sig.signing_keys.end())
+ if (sig.signing_keys.find(multisig_pkey) == sig.signing_keys.end())
{
- sc_add(skey.bytes, skey.bytes, rct::sk2rct(msk).bytes);
- sig.signing_keys.insert(pmsk);
+ sc_add(skey.bytes, skey.bytes, rct::sk2rct(multisig_skey).bytes);
+ sig.signing_keys.insert(multisig_pkey);
}
}
- 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;
+ THROW_WALLET_EXCEPTION_IF(
+ not multisig_tx_builder.next_partial_sign(sig.total_alpha_G, sig.total_alpha_H, local_nonces_k, skey, sig.c_0, sig.s),
+ error::wallet_internal_error,
+ "error: multisig::signing::tx_builder_ringct_t::next_partial_sign"
+ );
}
}
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
+ // if there are signatures from enough signers (assuming the local signer signed 1+ tx attempts), find the tx
+ // attempt with a full set of signatures so this tx can be finalized
bool found = false;
for (const auto &sig: ptx.multisig_sigs)
{
if (sig.ignore.find(local_signer) == sig.ignore.end() && !keys_intersect(sig.ignore, exported_txs.m_signers))
{
THROW_WALLET_EXCEPTION_IF(found, error::wallet_internal_error, "More than one transaction is final");
- ptx.tx.rct_signatures = sig.sigs;
+ THROW_WALLET_EXCEPTION_IF(
+ not multisig_tx_builder.finalize_tx(ptx.construction_data.sources, sig.c_0, sig.s, ptx.tx),
+ error::wallet_internal_error,
+ "error: multisig::signing::tx_builder_ringct_t::finalize_tx"
+ );
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");
+ "Unable to finalize the transaction: the ignore sets for these tx attempts seem to be malformed.");
const crypto::hash txid = get_transaction_hash(ptx.tx);
if (store_tx_info())
{
@@ -7229,7 +7390,8 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto
}
}
- // txes generated, get rid of used k values
+ // signatures generated, get rid of any unused k values (must do export_multisig() to make more tx attempts with the
+ // inputs in the transactions worked on here)
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)
memwipe(m_transfers[idx].m_multisig_k.data(), m_transfers[idx].m_multisig_k.size() * sizeof(m_transfers[idx].m_multisig_k[0]));
@@ -8046,8 +8208,13 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
has_rct = true;
max_rct_index = std::max(max_rct_index, m_transfers[idx].m_global_output_index);
}
- const bool has_rct_distribution = has_rct && (!rct_offsets.empty() || get_rct_distribution(rct_start_height, rct_offsets));
- if (has_rct_distribution)
+
+ if (has_rct && rct_offsets.empty()) {
+ THROW_WALLET_EXCEPTION_IF(!get_rct_distribution(rct_start_height, rct_offsets),
+ error::get_output_distribution, "Could not obtain output distribution.");
+ }
+
+ if (has_rct)
{
// check we're clear enough of rct start, to avoid corner cases below
THROW_WALLET_EXCEPTION_IF(rct_offsets.size() <= CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE,
@@ -8059,11 +8226,11 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
// get histogram for the amounts we need
cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request req_t = AUTO_VAL_INIT(req_t);
cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response resp_t = AUTO_VAL_INIT(resp_t);
- // request histogram for all outputs, except 0 if we have the rct distribution
+ // request histogram for all pre-rct outputs
req_t.amounts.reserve(selected_transfers.size());
for(size_t idx: selected_transfers)
- if (!m_transfers[idx].is_rct() || !has_rct_distribution)
- req_t.amounts.push_back(m_transfers[idx].is_rct() ? 0 : m_transfers[idx].amount());
+ if (!m_transfers[idx].is_rct())
+ req_t.amounts.push_back(m_transfers[idx].amount());
if (!req_t.amounts.empty())
{
std::sort(req_t.amounts.begin(), req_t.amounts.end());
@@ -8163,7 +8330,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
COMMAND_RPC_GET_OUTPUTS_BIN::response daemon_resp = AUTO_VAL_INIT(daemon_resp);
std::unique_ptr<gamma_picker> gamma;
- if (has_rct_distribution)
+ if (has_rct)
gamma.reset(new gamma_picker(rct_offsets));
size_t num_selected_transfers = 0;
@@ -8178,7 +8345,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
// request more for rct in base recent (locked) coinbases are picked, since they're locked for longer
size_t requested_outputs_count = base_requested_outputs_count + (td.is_rct() ? CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0);
size_t start = req.outputs.size();
- bool use_histogram = amount != 0 || !has_rct_distribution;
+ bool use_histogram = amount != 0;
const bool output_is_pre_fork = td.m_block_height < segregation_fork_height;
uint64_t num_outs = 0, num_recent_outs = 0;
@@ -8365,7 +8532,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
uint64_t i;
const char *type = "";
- if (amount == 0 && has_rct_distribution)
+ if (amount == 0)
{
THROW_WALLET_EXCEPTION_IF(!gamma, error::wallet_internal_error, "No gamma picker");
// gamma distribution
@@ -8527,7 +8694,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
break;
}
}
- bool use_histogram = amount != 0 || !has_rct_distribution;
+ bool use_histogram = amount != 0;
if (!use_histogram)
num_outs = rct_offsets[rct_offsets.size() - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE];
@@ -8755,9 +8922,8 @@ 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, false, {}, m_multisig ? &msout : NULL, use_view_tags);
+ 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, {}, use_view_tags);
LOG_PRINT_L2("constructed tx, r="<<r);
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_nettype);
THROW_WALLET_EXCEPTION_IF(upper_transaction_weight_limit <= get_transaction_weight(tx), error::tx_too_big, tx, upper_transaction_weight_limit);
@@ -8839,6 +9005,10 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
// At this step we need to define set of participants available for signature,
// i.e. those of them who exchanged with multisig info's
+ // note: The oldest unspent owned output's multisig info (in m_transfers) will contain the most recent result of
+ // 'import_multisig()', which means only 'fresh' multisig infos (public nonces) will be used to make tx attempts.
+ // - If a signer's info was missing from the latest call to 'import_multisig()', then they won't be able to participate!
+ // - If a newly-acquired output doesn't have enouch nonces from multisig infos, then it can't be spent!
for (const crypto::public_key &signer: m_multisig_signers)
{
if (signer == local_signer)
@@ -8906,7 +9076,6 @@ 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);
@@ -8949,10 +9118,8 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
src.real_output_in_tx_index = td.m_internal_output_index;
src.mask = td.m_mask;
if (m_multisig)
- {
- auto ignore_set = ignore_sets.empty() ? std::unordered_set<crypto::public_key>() : ignore_sets.front();
- src.multisig_kLRki = get_multisig_composite_kLRki(idx, ignore_set, used_L, used_L);
- }
+ // note: multisig_kLRki is a legacy struct, currently only used as a key image shuttle into the multisig tx builder
+ src.multisig_kLRki = {.k = {}, .L = {}, .R = {}, .ki = rct::ki2rct(td.m_key_image)};
else
src.multisig_kLRki = rct::multisig_kLRki({rct::zero(), rct::zero(), rct::zero(), rct::zero()});
detail::print_source_entry(src);
@@ -8989,12 +9156,43 @@ 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;
+ crypto::secret_key multisig_tx_key_entropy;
LOG_PRINT_L2("constructing tx");
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, rct_config, m_multisig ? &msout : NULL, use_view_tags);
- LOG_PRINT_L2("constructed tx, r="<<r);
- THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, dsts, unlock_time, m_nettype);
+ multisig::signing::tx_builder_ringct_t multisig_tx_builder;
+ if (m_multisig) {
+ // prepare the core part of a multisig tx (many tx attempts for different signer groups can be spun off this core piece)
+ std::set<std::uint32_t> subaddr_minor_indices;
+ for (size_t idx: selected_transfers) {
+ subaddr_minor_indices.insert(m_transfers[idx].m_subaddr_index.minor);
+ }
+ THROW_WALLET_EXCEPTION_IF(
+ not multisig_tx_builder.init(m_account.get_keys(),
+ extra,
+ unlock_time,
+ subaddr_account,
+ subaddr_minor_indices,
+ sources,
+ splitted_dsts,
+ change_dts,
+ rct_config,
+ true,
+ false,
+ tx_key,
+ additional_tx_keys,
+ multisig_tx_key_entropy,
+ tx
+ ),
+ error::wallet_internal_error,
+ "error: multisig::signing::tx_builder_ringct_t::init"
+ );
+ }
+ else {
+ // make a normal 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, rct_config, use_view_tags);
+ LOG_PRINT_L2("constructed tx, r="<<r);
+ THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, dsts, unlock_time, m_nettype);
+ }
THROW_WALLET_EXCEPTION_IF(upper_transaction_weight_limit <= get_transaction_weight(tx), error::tx_too_big, tx, upper_transaction_weight_limit);
// work out the permutation done on sources
@@ -9012,42 +9210,77 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
THROW_WALLET_EXCEPTION_IF(ins_order.size() != sources.size(), error::wallet_internal_error, "Failed to work out sources permutation");
std::vector<tools::wallet2::multisig_sig> multisig_sigs;
- if (m_multisig)
- {
- auto ignore = ignore_sets.empty() ? std::unordered_set<crypto::public_key>() : ignore_sets.front();
- multisig_sigs.push_back({tx.rct_signatures, ignore, used_L, std::unordered_set<crypto::public_key>(), 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 ignore_index = 1; ignore_index < ignore_sets.size(); ++ignore_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_copy[src_idx];
- src.multisig_kLRki = get_multisig_composite_kLRki(idx, ignore_sets[ignore_index], used_L, new_used_L);
- ++src_idx;
+ if (m_multisig) {
+ if (ignore_sets.empty())
+ ignore_sets.emplace_back();
+ const std::size_t num_multisig_attempts = ignore_sets.size();
+ multisig_sigs.resize(num_multisig_attempts);
+ std::unordered_set<rct::key> all_used_L;
+ std::unordered_set<crypto::public_key> signing_keys;
+ for (const crypto::secret_key &multisig_skey: get_account().get_multisig_keys())
+ signing_keys.insert(get_multisig_signing_public_key(multisig_skey));
+ const std::size_t num_sources = sources.size();
+ const std::size_t num_alpha_components = multisig::signing::kAlphaComponents;
+
+ // initiate a multisig tx attempt for each unique set of signers that
+ // a) includes the local signer
+ // b) includes other signers who most recently sent the local signer LR public nonces via 'export_multisig() -> import_multisig()'
+ for (std::size_t i = 0; i < num_multisig_attempts; ++i) {
+ multisig_sig& sig = multisig_sigs[i];
+ sig.total_alpha_G.resize(num_sources, rct::keyV(num_alpha_components));
+ sig.total_alpha_H.resize(num_sources, rct::keyV(num_alpha_components));
+ sig.s.resize(num_sources);
+ sig.c_0.resize(num_sources);
+
+ // for each tx input, get public musig2-style nonces from
+ // a) temporary local-generated private nonces (used to make the local partial signatures on each tx attempt)
+ // b) other signers' public nonces, sent to the local signer via 'export_multisig() -> import_multisig()'
+ // - WARNING: If two multisig players initiate multisig tx attempts separately, but spend the same funds (and hence rely on the same LR public nonces),
+ // then if two signers partially sign different tx attempt sets, then all attempts that require both signers will become garbage,
+ // because LR nonces can only be used for one tx attempt.
+ for (std::size_t j = 0; j < num_sources; ++j) {
+ rct::keyV alpha(num_alpha_components);
+ auto alpha_wiper = epee::misc_utils::create_scope_leave_handler([&]{
+ memwipe(static_cast<rct::key *>(alpha.data()), alpha.size() * sizeof(rct::key));
+ });
+ for (std::size_t m = 0; m < num_alpha_components; ++m) {
+ const rct::multisig_kLRki kLRki = get_multisig_composite_kLRki(
+ selected_transfers[ins_order[j]],
+ ignore_sets[i],
+ all_used_L, //collect all public L nonces used by this tx proposal (set of tx attempts) to avoid duplicates
+ sig.used_L //record the public L nonces used by this tx input to this tx attempt, for coordination with other signers
+ );
+ alpha[m] = kLRki.k;
+ sig.total_alpha_G[j][m] = kLRki.L;
+ sig.total_alpha_H[j][m] = kLRki.R;
}
- LOG_PRINT_L2("Creating supplementary multisig transaction");
- cryptonote::transaction ms_tx;
- auto sources_copy_copy = sources_copy;
- bool shuffle_outs = false;
- 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, rct_config, &msout, shuffle_outs, use_view_tags);
- LOG_PRINT_L2("constructed tx, r="<<r);
- THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_nettype);
- THROW_WALLET_EXCEPTION_IF(upper_transaction_weight_limit <= get_transaction_weight(tx), error::tx_too_big, tx, upper_transaction_weight_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, ignore_sets[ignore_index], new_used_L, std::unordered_set<crypto::public_key>(), 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");
- }
+ // local signer: initial partial signature on this tx input for this tx attempt
+ // note: sign here with sender-receiver secret component, subaddress component, and ALL of the local signer's multisig key shares
+ // (this ultimately occurs deep in generate_key_image_helper_precomp())
+ THROW_WALLET_EXCEPTION_IF(
+ not multisig_tx_builder.first_partial_sign(j, sig.total_alpha_G[j], sig.total_alpha_H[j], alpha, sig.c_0[j], sig.s[j]),
+ error::wallet_internal_error,
+ "error: multisig::signing::tx_builder_ringct_t::first_partial_sign"
+ );
+ }
+
+ // note: record the ignore set so when other signers go to add their signatures (sign_multisig_tx()), they
+ // can skip this tx attempt if they aren't supposed to sign it; this only works for signers who provided
+ // multisig_infos to the last 'import_multisig()' call by the local signer, all 'other signers' will encounter
+ // a 'need to export multisig_info' wallet error if they try to sign this partial tx, which means if they want to sign a tx
+ // they need to export_multisig() -> send to the local signer -> local signer calls import_multisig() with fresh
+ // multisig_infos from all signers -> local signer makes completely new tx attempts (or a different signer makes tx attempts)
+ sig.ignore = ignore_sets[i];
+ sig.signing_keys = signing_keys; //the local signer signed with ALL of their multisig key shares, record their pubkeys for reference by other signers
+ }
+ if (m_multisig_threshold <= 1) {
+ // local signer: finish signing the tx inputs if we are the only signer (ignore all but the first 'attempt')
+ THROW_WALLET_EXCEPTION_IF(
+ not multisig_tx_builder.finalize_tx(sources, multisig_sigs[0].c_0, multisig_sigs[0].s, tx),
+ error::wallet_internal_error,
+ "error: multisig::signing::tx_builder_ringct_t::finalize_tx"
+ );
}
}
@@ -9074,6 +9307,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
ptx.additional_tx_keys = additional_tx_keys;
ptx.dests = dsts;
ptx.multisig_sigs = multisig_sigs;
+ ptx.multisig_tx_key_entropy = multisig_tx_key_entropy;
ptx.construction_data.sources = sources_copy;
ptx.construction_data.change_dts = change_dts;
ptx.construction_data.splitted_dsts = splitted_dsts;
@@ -10710,7 +10944,7 @@ void wallet2::cold_sign_tx(const std::vector<pending_tx>& ptx_vector, signed_tx_
{
txs.txes.push_back(get_construction_data_with_decrypted_short_payment_id(tx, m_account.get_device()));
}
- txs.transfers = std::make_pair(0, m_transfers);
+ txs.transfers = std::make_tuple(0, m_transfers.size(), m_transfers);
auto dev_cold = dynamic_cast<::hw::device_cold*>(&hwdev);
CHECK_AND_ASSERT_THROW_MES(dev_cold, "Device does not implement cold signing interface");
@@ -12087,7 +12321,8 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr
crypto::key_derivation derivation;
THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(proof.shared_secret, rct::rct2sk(rct::I), derivation), error::wallet_internal_error, "Failed to generate key derivation");
crypto::public_key subaddr_spendkey;
- crypto::derive_subaddress_public_key(output_public_key, derivation, proof.index_in_tx, subaddr_spendkey);
+ THROW_WALLET_EXCEPTION_IF(!crypto::derive_subaddress_public_key(output_public_key, derivation, proof.index_in_tx, subaddr_spendkey),
+ error::wallet_internal_error, "Failed to derive subaddress public key");
THROW_WALLET_EXCEPTION_IF(subaddr_spendkeys.count(subaddr_spendkey) == 0, error::wallet_internal_error,
"The address doesn't seem to have received the fund");
@@ -13012,18 +13247,29 @@ void wallet2::import_blockchain(const std::tuple<size_t, crypto::hash, std::vect
m_last_block_reward = cryptonote::get_outs_money_amount(genesis.miner_tx);
}
//----------------------------------------------------------------------------------------------------
-std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> wallet2::export_outputs(bool all) const
+std::tuple<uint64_t, uint64_t, std::vector<tools::wallet2::exported_transfer_details>> wallet2::export_outputs(bool all, uint32_t start, uint32_t count) const
{
PERF_TIMER(export_outputs);
std::vector<tools::wallet2::exported_transfer_details> outs;
+ // invalid cases
+ THROW_WALLET_EXCEPTION_IF(count == 0, error::wallet_internal_error, "Nothing requested");
+ THROW_WALLET_EXCEPTION_IF(!all && start > 0, error::wallet_internal_error, "Incremental mode is incompatible with non-zero start");
+
+ // valid cases:
+ // all: all outputs, subject to start/count
+ // !all: incremental, subject to count
+ // for convenience, start/count are allowed to go past the valid range, then nothing is returned
+
size_t offset = 0;
if (!all)
while (offset < m_transfers.size() && (m_transfers[offset].m_key_image_known && !m_transfers[offset].m_key_image_request))
++offset;
+ else
+ offset = start;
outs.reserve(m_transfers.size() - offset);
- for (size_t n = offset; n < m_transfers.size(); ++n)
+ for (size_t n = offset; n < m_transfers.size() && n - offset < count; ++n)
{
const transfer_details &td = m_transfers[n];
@@ -13041,20 +13287,22 @@ std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> wall
etd.m_flags.m_key_image_partial = td.m_key_image_partial;
etd.m_amount = td.m_amount;
etd.m_additional_tx_keys = get_additional_tx_pub_keys_from_extra(td.m_tx);
+ etd.m_subaddr_index_major = td.m_subaddr_index.major;
+ etd.m_subaddr_index_minor = td.m_subaddr_index.minor;
outs.push_back(etd);
}
- return std::make_pair(offset, outs);
+ return std::make_tuple(offset, m_transfers.size(), outs);
}
//----------------------------------------------------------------------------------------------------
-std::string wallet2::export_outputs_to_str(bool all) const
+std::string wallet2::export_outputs_to_str(bool all, uint32_t start, uint32_t count) const
{
PERF_TIMER(export_outputs_to_str);
std::stringstream oss;
binary_archive<true> ar(oss);
- auto outputs = export_outputs(all);
+ auto outputs = export_outputs(all, start, count);
THROW_WALLET_EXCEPTION_IF(!::serialization::serialize(ar, outputs), error::wallet_internal_error, "Failed to serialize output data");
std::string magic(OUTPUT_EXPORT_FILE_MAGIC, strlen(OUTPUT_EXPORT_FILE_MAGIC));
@@ -13067,21 +13315,35 @@ std::string wallet2::export_outputs_to_str(bool all) const
return magic + ciphertext;
}
//----------------------------------------------------------------------------------------------------
-size_t wallet2::import_outputs(const std::pair<uint64_t, std::vector<tools::wallet2::transfer_details>> &outputs)
+size_t wallet2::import_outputs(const std::tuple<uint64_t, uint64_t, std::vector<tools::wallet2::transfer_details>> &outputs)
{
PERF_TIMER(import_outputs);
- THROW_WALLET_EXCEPTION_IF(outputs.first > m_transfers.size(), error::wallet_internal_error,
+ THROW_WALLET_EXCEPTION_IF(m_has_ever_refreshed_from_node, error::wallet_internal_error,
+ "Hot wallets cannot import outputs");
+
+ // we can now import piecemeal
+ const size_t offset = std::get<0>(outputs);
+ const size_t num_outputs = std::get<1>(outputs);
+ const std::vector<tools::wallet2::transfer_details> &output_array = std::get<2>(outputs);
+
+ THROW_WALLET_EXCEPTION_IF(offset > m_transfers.size(), error::wallet_internal_error,
"Imported outputs omit more outputs that we know of");
- const size_t offset = outputs.first;
+ THROW_WALLET_EXCEPTION_IF(offset >= num_outputs, error::wallet_internal_error,
+ "Offset is larger than total outputs");
+ THROW_WALLET_EXCEPTION_IF(output_array.size() > num_outputs - offset, error::wallet_internal_error,
+ "Offset is larger than total outputs");
+
const size_t original_size = m_transfers.size();
- m_transfers.resize(offset + outputs.second.size());
- for (size_t i = 0; i < offset; ++i)
- m_transfers[i].m_key_image_request = false;
- for (size_t i = 0; i < outputs.second.size(); ++i)
+ if (offset + output_array.size() > m_transfers.size())
+ m_transfers.resize(offset + output_array.size());
+ else if (num_outputs < m_transfers.size())
+ m_transfers.resize(num_outputs);
+
+ for (size_t i = 0; i < output_array.size(); ++i)
{
- transfer_details td = outputs.second[i];
+ transfer_details td = output_array[i];
// skip those we've already imported, or which have different data
if (i + offset < original_size)
@@ -13115,6 +13377,8 @@ process:
THROW_WALLET_EXCEPTION_IF(td.m_internal_output_index >= td.m_tx.vout.size(),
error::wallet_internal_error, "Internal index is out of range");
crypto::public_key out_key = td.get_public_key();
+ if (should_expand(td.m_subaddr_index))
+ create_one_off_subaddress(td.m_subaddr_index);
bool r = cryptonote::generate_key_image_helper(m_account.get_keys(), m_subaddresses, out_key, tx_pub_key, additional_tx_pub_keys, td.m_internal_output_index, in_ephemeral, td.m_key_image, m_account.get_device());
THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image");
if (should_expand(td.m_subaddr_index))
@@ -13133,24 +13397,38 @@ process:
return m_transfers.size();
}
//----------------------------------------------------------------------------------------------------
-size_t wallet2::import_outputs(const std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> &outputs)
+size_t wallet2::import_outputs(const std::tuple<uint64_t, uint64_t, std::vector<tools::wallet2::exported_transfer_details>> &outputs)
{
PERF_TIMER(import_outputs);
- THROW_WALLET_EXCEPTION_IF(outputs.first > m_transfers.size(), error::wallet_internal_error,
+ THROW_WALLET_EXCEPTION_IF(m_has_ever_refreshed_from_node, error::wallet_internal_error,
+ "Hot wallets cannot import outputs");
+
+ // we can now import piecemeal
+ const size_t offset = std::get<0>(outputs);
+ const size_t num_outputs = std::get<1>(outputs);
+ const std::vector<tools::wallet2::exported_transfer_details> &output_array = std::get<2>(outputs);
+
+ THROW_WALLET_EXCEPTION_IF(offset > m_transfers.size(), error::wallet_internal_error,
"Imported outputs omit more outputs that we know of. Try using export_outputs all.");
- const size_t offset = outputs.first;
+ THROW_WALLET_EXCEPTION_IF(offset >= num_outputs, error::wallet_internal_error,
+ "Offset is larger than total outputs");
+ THROW_WALLET_EXCEPTION_IF(output_array.size() > num_outputs - offset, error::wallet_internal_error,
+ "Offset is larger than total outputs");
+
const size_t original_size = m_transfers.size();
- m_transfers.resize(offset + outputs.second.size());
- for (size_t i = 0; i < offset; ++i)
- m_transfers[i].m_key_image_request = false;
- for (size_t i = 0; i < outputs.second.size(); ++i)
+ if (offset + output_array.size() > m_transfers.size())
+ m_transfers.resize(offset + output_array.size());
+ else if (num_outputs < m_transfers.size())
+ m_transfers.resize(num_outputs);
+
+ for (size_t i = 0; i < output_array.size(); ++i)
{
- exported_transfer_details etd = outputs.second[i];
+ exported_transfer_details etd = output_array[i];
transfer_details &td = m_transfers[i + offset];
- // setup td with "cheao" loaded data
+ // setup td with "cheap" loaded data
td.m_block_height = 0;
td.m_txid = crypto::null_hash;
td.m_global_output_index = etd.m_global_output_index;
@@ -13163,6 +13441,8 @@ size_t wallet2::import_outputs(const std::pair<uint64_t, std::vector<tools::wall
td.m_key_image_known = etd.m_flags.m_key_image_known;
td.m_key_image_request = etd.m_flags.m_key_image_request;
td.m_key_image_partial = false;
+ td.m_subaddr_index.major = etd.m_subaddr_index_major;
+ td.m_subaddr_index.minor = etd.m_subaddr_index_minor;
// skip those we've already imported, or which have different data
if (i + offset < original_size)
@@ -13203,6 +13483,8 @@ size_t wallet2::import_outputs(const std::pair<uint64_t, std::vector<tools::wall
const crypto::public_key &tx_pub_key = etd.m_tx_pubkey;
const std::vector<crypto::public_key> &additional_tx_pub_keys = etd.m_additional_tx_keys;
const crypto::public_key& out_key = etd.m_pubkey;
+ if (should_expand(td.m_subaddr_index))
+ create_one_off_subaddress(td.m_subaddr_index);
bool r = cryptonote::generate_key_image_helper(m_account.get_keys(), m_subaddresses, out_key, tx_pub_key, additional_tx_pub_keys, td.m_internal_output_index, in_ephemeral, td.m_key_image, m_account.get_device());
THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image");
if (should_expand(td.m_subaddr_index))
@@ -13259,7 +13541,7 @@ size_t wallet2::import_outputs_from_str(const std::string &outputs_st)
{
std::string body(data, headerlen);
- std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> new_outputs;
+ std::tuple<uint64_t, uint64_t, std::vector<tools::wallet2::exported_transfer_details>> new_outputs;
try
{
binary_archive<false> ar{epee::strspan<std::uint8_t>(body)};
@@ -13269,9 +13551,9 @@ size_t wallet2::import_outputs_from_str(const std::string &outputs_st)
}
catch (...) {}
if (!loaded)
- new_outputs.second.clear();
+ std::get<2>(new_outputs).clear();
- std::pair<uint64_t, std::vector<tools::wallet2::transfer_details>> outputs;
+ std::tuple<uint64_t, uint64_t, std::vector<tools::wallet2::transfer_details>> outputs;
if (!loaded) try
{
binary_archive<false> ar{epee::strspan<std::uint8_t>(body)};
@@ -13296,15 +13578,16 @@ size_t wallet2::import_outputs_from_str(const std::string &outputs_st)
if (!loaded)
{
- outputs.first = 0;
- outputs.second = {};
+ std::get<0>(outputs) = 0;
+ std::get<1>(outputs) = 0;
+ std::get<2>(outputs) = {};
}
- imported_outputs = new_outputs.second.empty() ? import_outputs(outputs) : import_outputs(new_outputs);
+ imported_outputs = !std::get<2>(new_outputs).empty() ? import_outputs(new_outputs) : !std::get<2>(outputs).empty() ? import_outputs(outputs) : 0;
}
catch (const std::exception &e)
{
- THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Failed to import outputs") + e.what());
+ THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Failed to import outputs: ") + e.what());
}
return imported_outputs;
@@ -13333,19 +13616,26 @@ crypto::public_key wallet2::get_multisig_signing_public_key(size_t idx) const
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
+void wallet2::get_multisig_k(size_t idx, const std::unordered_set<rct::key> &used_L, rct::key &nonce)
{
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)
+ for (auto &k: m_transfers[idx].m_multisig_k)
{
+ if (k == rct::zero())
+ continue;
+
+ // decide whether or not to return a nonce just based on if its pubkey 'L = k*G' is attached to the transfer 'idx'
rct::key L;
rct::scalarmultBase(L, k);
if (used_L.find(L) != used_L.end())
- return k;
+ {
+ nonce = k;
+ memwipe(static_cast<rct::key *>(&k), sizeof(rct::key)); //CRITICAL: a nonce may only be used once!
+ return;
+ }
}
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
@@ -13411,15 +13701,23 @@ cryptonote::blobdata wallet2::export_multisig()
const crypto::public_key signer = get_multisig_signer_public_key();
+ // For each transfer (output owned by the multisig wallet):
+ // 1) Record the output's partial key image (from the local signer), so other signers can assemble the output's full key image.
+ // 2) Prepare enough signing nonces for one signing attempt with each possible combination of 'threshold' signers
+ // from the multisig group (only groups that include the local signer).
+ // - Calling this function will reset any nonces recorded by the previous call to this function. Doing so will
+ // invalidate any in-progress signing attempts that rely on the previous output of this function.
info.resize(m_transfers.size());
for (size_t n = 0; n < m_transfers.size(); ++n)
{
transfer_details &td = m_transfers[n];
crypto::key_image ki;
- memwipe(td.m_multisig_k.data(), td.m_multisig_k.size() * sizeof(td.m_multisig_k[0]));
+ if (td.m_multisig_k.size())
+ memwipe(td.m_multisig_k.data(), td.m_multisig_k.size() * sizeof(td.m_multisig_k[0]));
info[n].m_LR.clear();
info[n].m_partial_key_images.clear();
+ // record the partial key images
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
@@ -13432,6 +13730,15 @@ cryptonote::blobdata wallet2::export_multisig()
// if we have 2/4 wallet with signers: A, B, C, D and A is a transaction creator it will need to pick up 1 signer from 3 wallets left.
// That means counting combinations for excluding 2-of-3 wallets (k = total signers count - threshold, n = total signers count - 1).
size_t nlr = tools::combinations_count(m_multisig_signers.size() - m_multisig_threshold, m_multisig_signers.size() - 1);
+
+ // 'td.m_multisig_k' is an expansion of [{alpha_0, alpha_1, ...}, {alpha_0, alpha_1, ...}, {alpha_0, alpha_1, ...}],
+ // - A '{alpha_0, alpha_1, ...}' tuple contains a set of 'kAlphaComponents' nonces, which can be used for one
+ // signing attempt. Each output will gain 'nlr' tuples, so that every signing group can make one signing attempt.
+ // - All tuples are always cleared after 1+ of them is used to sign a tx attempt (in sign_multisig_tx()), so
+ // in practice, a call to this function only allows _one_ multisig signing cycle for each output (which can
+ // include signing attempts for multiple signer groups).
+ nlr *= multisig::signing::kAlphaComponents;
+
for (size_t m = 0; m < nlr; ++m)
{
td.m_multisig_k.push_back(rct::skGen());
@@ -14019,43 +14326,6 @@ uint64_t wallet2::get_segregation_fork_height() const
if (m_segregation_height > 0)
return m_segregation_height;
- if (m_use_dns && !m_offline)
- {
- // All four MoneroPulse domains have DNSSEC on and valid
- static const std::vector<std::string> dns_urls = {
- "segheights.moneropulse.org",
- "segheights.moneropulse.net",
- "segheights.moneropulse.co",
- "segheights.moneropulse.se"
- };
-
- const uint64_t current_height = get_blockchain_current_height();
- uint64_t best_diff = std::numeric_limits<uint64_t>::max(), best_height = 0;
- std::vector<std::string> records;
- if (tools::dns_utils::load_txt_records_from_dns(records, dns_urls))
- {
- for (const auto& record : records)
- {
- std::vector<std::string> fields;
- boost::split(fields, record, boost::is_any_of(":"));
- if (fields.size() != 2)
- continue;
- uint64_t height;
- if (!string_tools::get_xtype_from_string(height, fields[1]))
- continue;
-
- MINFO("Found segregation height via DNS: " << fields[0] << " fork height at " << height);
- uint64_t diff = height > current_height ? height - current_height : current_height - height;
- if (diff < best_diff)
- {
- best_diff = diff;
- best_height = height;
- }
- }
- if (best_height)
- return best_height;
- }
- }
return SEGREGATION_FORK_HEIGHT;
}
//----------------------------------------------------------------------------------------------------
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index 2b2e50410..3ee40a5f0 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -63,6 +63,7 @@
#include "serialization/crypto.h"
#include "serialization/string.h"
#include "serialization/pair.h"
+#include "serialization/tuple.h"
#include "serialization/containers.h"
#include "wallet_errors.h"
@@ -137,7 +138,7 @@ private:
public:
// Full wallet callbacks
virtual void on_new_block(uint64_t height, const cryptonote::block& block) {}
- virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time) {}
+ virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, uint64_t burnt, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time) {}
virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index) {}
virtual void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx, const cryptonote::subaddress_index& subaddr_index) {}
virtual void on_skip_transaction(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx) {}
@@ -349,6 +350,8 @@ private:
uint64_t amount() const { return m_amount; }
const crypto::public_key get_public_key() const {
crypto::public_key output_public_key;
+ THROW_WALLET_EXCEPTION_IF(m_tx.vout.size() <= m_internal_output_index,
+ error::wallet_internal_error, "Too few outputs, outputs may be corrupted");
THROW_WALLET_EXCEPTION_IF(!get_output_public_key(m_tx.vout[m_internal_output_index], output_public_key),
error::wallet_internal_error, "Unable to get output public key from output");
return output_public_key;
@@ -399,9 +402,13 @@ private:
} m_flags;
uint64_t m_amount;
std::vector<crypto::public_key> m_additional_tx_keys;
+ uint32_t m_subaddr_index_major;
+ uint32_t m_subaddr_index_minor;
BEGIN_SERIALIZE_OBJECT()
- VERSION_FIELD(0)
+ VERSION_FIELD(1)
+ if (version < 1)
+ return false;
FIELD(m_pubkey)
VARINT_FIELD(m_internal_output_index)
VARINT_FIELD(m_global_output_index)
@@ -409,6 +416,8 @@ private:
FIELD(m_flags.flags)
VARINT_FIELD(m_amount)
FIELD(m_additional_tx_keys)
+ VARINT_FIELD(m_subaddr_index_major)
+ VARINT_FIELD(m_subaddr_index_minor)
END_SERIALIZE()
};
@@ -492,6 +501,7 @@ private:
struct confirmed_transfer_details
{
+ cryptonote::transaction_prefix m_tx;
uint64_t m_amount_in;
uint64_t m_amount_out;
uint64_t m_change;
@@ -506,10 +516,12 @@ private:
confirmed_transfer_details(): m_amount_in(0), m_amount_out(0), m_change((uint64_t)-1), m_block_height(0), m_payment_id(crypto::null_hash), m_timestamp(0), m_unlock_time(0), m_subaddr_account((uint32_t)-1) {}
confirmed_transfer_details(const unconfirmed_transfer_details &utd, uint64_t height):
- m_amount_in(utd.m_amount_in), m_amount_out(utd.m_amount_out), m_change(utd.m_change), m_block_height(height), m_dests(utd.m_dests), m_payment_id(utd.m_payment_id), m_timestamp(utd.m_timestamp), m_unlock_time(utd.m_tx.unlock_time), m_subaddr_account(utd.m_subaddr_account), m_subaddr_indices(utd.m_subaddr_indices), m_rings(utd.m_rings) {}
+ m_tx(utd.m_tx), m_amount_in(utd.m_amount_in), m_amount_out(utd.m_amount_out), m_change(utd.m_change), m_block_height(height), m_dests(utd.m_dests), m_payment_id(utd.m_payment_id), m_timestamp(utd.m_timestamp), m_unlock_time(utd.m_tx.unlock_time), m_subaddr_account(utd.m_subaddr_account), m_subaddr_indices(utd.m_subaddr_indices), m_rings(utd.m_rings) {}
BEGIN_SERIALIZE_OBJECT()
- VERSION_FIELD(0)
+ VERSION_FIELD(1)
+ if (version >= 1)
+ FIELD(m_tx)
VARINT_FIELD(m_amount_in)
VARINT_FIELD(m_amount_out)
VARINT_FIELD(m_change)
@@ -594,13 +606,24 @@ private:
std::unordered_set<crypto::public_key> signing_keys;
rct::multisig_out msout;
+ rct::keyM total_alpha_G;
+ rct::keyM total_alpha_H;
+ rct::keyV c_0;
+ rct::keyV s;
+
BEGIN_SERIALIZE_OBJECT()
- VERSION_FIELD(0)
+ VERSION_FIELD(1)
+ if (version < 1)
+ return false;
FIELD(sigs)
FIELD(ignore)
FIELD(used_L)
FIELD(signing_keys)
FIELD(msout)
+ FIELD(total_alpha_G)
+ FIELD(total_alpha_H)
+ FIELD(c_0)
+ FIELD(s)
END_SERIALIZE()
};
@@ -619,10 +642,12 @@ private:
std::vector<crypto::secret_key> additional_tx_keys;
std::vector<cryptonote::tx_destination_entry> dests;
std::vector<multisig_sig> multisig_sigs;
+ crypto::secret_key multisig_tx_key_entropy;
tx_construction_data construction_data;
BEGIN_SERIALIZE_OBJECT()
+ VERSION_FIELD(1)
FIELD(tx)
FIELD(dust)
FIELD(fee)
@@ -635,6 +660,12 @@ private:
FIELD(dests)
FIELD(construction_data)
FIELD(multisig_sigs)
+ if (version < 1)
+ {
+ multisig_tx_key_entropy = crypto::null_skey;
+ return true;
+ }
+ FIELD(multisig_tx_key_entropy)
END_SERIALIZE()
};
@@ -643,16 +674,32 @@ private:
struct unsigned_tx_set
{
std::vector<tx_construction_data> txes;
- std::pair<size_t, wallet2::transfer_container> transfers;
- std::pair<size_t, std::vector<wallet2::exported_transfer_details>> new_transfers;
+ std::tuple<uint64_t, uint64_t, wallet2::transfer_container> transfers;
+ std::tuple<uint64_t, uint64_t, std::vector<wallet2::exported_transfer_details>> new_transfers;
BEGIN_SERIALIZE_OBJECT()
- VERSION_FIELD(1)
+ VERSION_FIELD(2)
FIELD(txes)
- if (version >= 1)
- FIELD(new_transfers)
- else
- FIELD(transfers)
+ if (version == 0)
+ {
+ std::pair<size_t, wallet2::transfer_container> v0_transfers;
+ FIELD(v0_transfers);
+ std::get<0>(transfers) = std::get<0>(v0_transfers);
+ std::get<1>(transfers) = std::get<0>(v0_transfers) + std::get<1>(v0_transfers).size();
+ std::get<2>(transfers) = std::get<1>(v0_transfers);
+ return true;
+ }
+ if (version == 1)
+ {
+ std::pair<size_t, std::vector<wallet2::exported_transfer_details>> v1_transfers;
+ FIELD(v1_transfers);
+ std::get<0>(new_transfers) = std::get<0>(v1_transfers);
+ std::get<1>(new_transfers) = std::get<0>(v1_transfers) + std::get<1>(v1_transfers).size();
+ std::get<2>(new_transfers) = std::get<1>(v1_transfers);
+ return true;
+ }
+
+ FIELD(new_transfers)
END_SERIALIZE()
};
@@ -770,7 +817,8 @@ private:
};
/*!
- * \brief Generates a wallet or restores one.
+ * \brief Generates a wallet or restores one. Assumes the multisig setup
+ * has already completed for the provided multisig info.
* \param wallet_ Name of wallet file
* \param password Password of wallet file
* \param multisig_data The multisig restore info and keys
@@ -838,7 +886,8 @@ private:
* to other participants
*/
std::string exchange_multisig_keys(const epee::wipeable_string &password,
- const std::vector<std::string> &kex_messages);
+ const std::vector<std::string> &kex_messages,
+ const bool force_update_use_with_caution = false);
/*!
* \brief Get initial message to start multisig key exchange (before 'make_multisig()' is called)
* \return string to send to other participants
@@ -1044,7 +1093,9 @@ private:
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();
void discard_unmixable_outputs();
- bool check_connection(uint32_t *version = NULL, bool *ssl = NULL, uint32_t timeout = 200000);
+ bool check_connection(uint32_t *version = NULL, bool *ssl = NULL, uint32_t timeout = 200000, bool *wallet_is_outdated = NULL, bool *daemon_is_outdated = NULL);
+ bool check_version(uint32_t *version, bool *wallet_is_outdated, bool *daemon_is_outdated);
+ bool check_hard_fork_version(cryptonote::network_type nettype, const std::vector<std::pair<uint8_t, uint64_t>> &daemon_hard_forks, const uint64_t height, const uint64_t target_height, bool *wallet_is_outdated, bool *daemon_is_outdated);
void get_transfers(wallet2::transfer_container& incoming_transfers) const;
void get_payments(const crypto::hash& payment_id, std::list<wallet2::payment_details>& payments, uint64_t min_height = 0, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const;
void get_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& payments, uint64_t min_height, uint64_t max_height = (uint64_t)-1, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const;
@@ -1182,11 +1233,17 @@ private:
if(ver < 29)
return;
a & m_rpc_client_secret_key;
+ if(ver < 30)
+ {
+ m_has_ever_refreshed_from_node = false;
+ return;
+ }
+ a & m_has_ever_refreshed_from_node;
}
BEGIN_SERIALIZE_OBJECT()
MAGIC_FIELD("monero wallet cache")
- VERSION_FIELD(0)
+ VERSION_FIELD(1)
FIELD(m_blockchain)
FIELD(m_transfers)
FIELD(m_account_public_address)
@@ -1212,6 +1269,12 @@ private:
FIELD(m_device_last_key_image_sync)
FIELD(m_cold_key_images)
FIELD(m_rpc_client_secret_key)
+ if (version < 1)
+ {
+ m_has_ever_refreshed_from_node = false;
+ return true;
+ }
+ FIELD(m_has_ever_refreshed_from_node)
END_SERIALIZE()
/*!
@@ -1297,6 +1360,10 @@ private:
void set_rpc_client_secret_key(const crypto::secret_key &key) { m_rpc_client_secret_key = key; m_node_rpc_proxy.set_client_secret_key(key); }
uint64_t credits_target() const { return m_credits_target; }
void credits_target(uint64_t threshold) { m_credits_target = threshold; }
+ bool is_multisig_enabled() const { return m_enable_multisig; }
+ void enable_multisig(bool enable) { m_enable_multisig = enable; }
+ bool is_mismatched_daemon_version_allowed() const { return m_allow_mismatched_daemon_version; }
+ void allow_mismatched_daemon_version(bool allow_mismatch) { m_allow_mismatched_daemon_version = allow_mismatch; }
bool get_tx_key_cached(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys) const;
void set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, const boost::optional<cryptonote::account_public_address> &single_destination_subaddress = boost::none);
@@ -1421,10 +1488,10 @@ private:
bool verify_with_public_key(const std::string &data, const crypto::public_key &public_key, const std::string &signature) const;
// Import/Export wallet data
- std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> export_outputs(bool all = false) const;
- std::string export_outputs_to_str(bool all = false) const;
- size_t import_outputs(const std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> &outputs);
- size_t import_outputs(const std::pair<uint64_t, std::vector<tools::wallet2::transfer_details>> &outputs);
+ std::tuple<uint64_t, uint64_t, std::vector<tools::wallet2::exported_transfer_details>> export_outputs(bool all = false, uint32_t start = 0, uint32_t count = 0xffffffff) const;
+ std::string export_outputs_to_str(bool all = false, uint32_t start = 0, uint32_t count = 0xffffffff) const;
+ size_t import_outputs(const std::tuple<uint64_t, uint64_t, std::vector<tools::wallet2::exported_transfer_details>> &outputs);
+ size_t import_outputs(const std::tuple<uint64_t, uint64_t, std::vector<tools::wallet2::transfer_details>> &outputs);
size_t import_outputs_from_str(const std::string &outputs_st);
payment_container export_payments() const;
void import_payments(const payment_container &payments);
@@ -1677,7 +1744,7 @@ private:
crypto::key_image get_multisig_composite_key_image(size_t n) const;
rct::multisig_kLRki get_multisig_composite_kLRki(size_t n, const std::unordered_set<crypto::public_key> &ignore_set, 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 get_multisig_k(size_t idx, const std::unordered_set<rct::key> &used_L, rct::key &nonce);
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);
bool add_rings(const crypto::chacha_key &key, const cryptonote::transaction_prefix &tx);
bool add_rings(const cryptonote::transaction_prefix &tx);
@@ -1813,6 +1880,8 @@ private:
crypto::secret_key m_rpc_client_secret_key;
rpc_payment_state_t m_rpc_payment_state;
uint64_t m_credits_target;
+ bool m_enable_multisig;
+ bool m_allow_mismatched_daemon_version;
// Aux transaction data from device
serializable_unordered_map<crypto::hash, std::string> m_tx_device;
@@ -1856,11 +1925,13 @@ private:
ExportFormat m_export_format;
bool m_load_deprecated_formats;
+ bool m_has_ever_refreshed_from_node;
+
static boost::mutex default_daemon_address_lock;
static std::string default_daemon_address;
};
}
-BOOST_CLASS_VERSION(tools::wallet2, 29)
+BOOST_CLASS_VERSION(tools::wallet2, 30)
BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 12)
BOOST_CLASS_VERSION(tools::wallet2::multisig_info, 1)
BOOST_CLASS_VERSION(tools::wallet2::multisig_info::LR, 0)
@@ -1871,16 +1942,27 @@ BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 8)
BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 6)
BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 18)
BOOST_CLASS_VERSION(tools::wallet2::reserve_proof_entry, 0)
-BOOST_CLASS_VERSION(tools::wallet2::unsigned_tx_set, 0)
+BOOST_CLASS_VERSION(tools::wallet2::unsigned_tx_set, 1)
BOOST_CLASS_VERSION(tools::wallet2::signed_tx_set, 1)
BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 4)
BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 3)
-BOOST_CLASS_VERSION(tools::wallet2::multisig_sig, 0)
+BOOST_CLASS_VERSION(tools::wallet2::multisig_sig, 1)
namespace boost
{
namespace serialization
{
+ template<class Archive, class F, class S, class T>
+ inline void serialize(
+ Archive & ar,
+ std::tuple<F, S, T> & t,
+ const unsigned int /* file_version */
+ ){
+ ar & boost::serialization::make_nvp("f", std::get<0>(t));
+ ar & boost::serialization::make_nvp("s", std::get<1>(t));
+ ar & boost::serialization::make_nvp("t", std::get<2>(t));
+ }
+
template <class Archive>
inline typename std::enable_if<!Archive::is_loading::value, void>::type initialize_transfer_details(Archive &a, tools::wallet2::transfer_details &x, const boost::serialization::version_type ver)
{
@@ -2241,7 +2323,17 @@ namespace boost
inline void serialize(Archive &a, tools::wallet2::unsigned_tx_set &x, const boost::serialization::version_type ver)
{
a & x.txes;
- a & x.transfers;
+ if (ver == 0)
+ {
+ // load old version
+ std::pair<size_t, tools::wallet2::transfer_container> old_transfers;
+ a & old_transfers;
+ std::get<0>(x.transfers) = std::get<0>(old_transfers);
+ std::get<1>(x.transfers) = std::get<0>(old_transfers) + std::get<1>(old_transfers).size();
+ std::get<2>(x.transfers) = std::get<1>(old_transfers);
+ return;
+ }
+ throw std::runtime_error("Boost serialization not supported for newest unsigned_tx_set");
}
template <class Archive>
@@ -2313,6 +2405,12 @@ namespace boost
a & x.used_L;
a & x.signing_keys;
a & x.msout;
+ if (ver < 1)
+ return;
+ a & x.total_alpha_G;
+ a & x.total_alpha_H;
+ a & x.c_0;
+ a & x.s;
}
template <class Archive>
diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h
index df594aa21..0b8512163 100644
--- a/src/wallet/wallet_errors.h
+++ b/src/wallet/wallet_errors.h
@@ -439,6 +439,16 @@ namespace tools
std::string to_string() const { return refresh_error::to_string(); }
};
//----------------------------------------------------------------------------------------------------
+ struct incorrect_fork_version : public refresh_error
+ {
+ explicit incorrect_fork_version(std::string&& loc, const std::string& message)
+ : refresh_error(std::move(loc), message)
+ {
+ }
+
+ std::string to_string() const { return refresh_error::to_string(); }
+ };
+ //----------------------------------------------------------------------------------------------------
struct signature_check_failed : public wallet_logic_error
{
explicit signature_check_failed(std::string&& loc, const std::string& message)
diff --git a/src/wallet/wallet_rpc_payments.cpp b/src/wallet/wallet_rpc_payments.cpp
index 61eaa8070..8474fb568 100644
--- a/src/wallet/wallet_rpc_payments.cpp
+++ b/src/wallet/wallet_rpc_payments.cpp
@@ -144,7 +144,7 @@ bool wallet2::search_for_rpc_payment(uint64_t credits_target, uint32_t n_threads
n_threads = boost::thread::hardware_concurrency();
std::vector<crypto::hash> hash(n_threads);
- tools::threadpool& tpool = tools::threadpool::getInstance();
+ tools::threadpool& tpool = tools::threadpool::getInstanceForCompute();
tools::threadpool::waiter waiter(tpool);
const uint32_t local_nonce = nonce += n_threads; // wrapping's OK
diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp
index 32f79aa7f..0f3ef928a 100644
--- a/src/wallet/wallet_rpc_server.cpp
+++ b/src/wallet/wallet_rpc_server.cpp
@@ -62,6 +62,17 @@ using namespace epee;
#define DEFAULT_AUTO_REFRESH_PERIOD 20 // seconds
#define REFRESH_INFICATIVE_BLOCK_CHUNK_SIZE 256 // just to split refresh in separate calls to play nicer with other threads
+#define CHECK_MULTISIG_ENABLED() \
+ do \
+ { \
+ if (m_wallet->multisig() && !m_wallet->is_multisig_enabled()) \
+ { \
+ er.code = WALLET_RPC_ERROR_CODE_DISABLED; \
+ er.message = "This wallet is multisig, and multisig is disabled. Multisig is an experimental feature and may have bugs. Things that could go wrong include: funds sent to a multisig wallet can't be spent at all, can only be spent with the participation of a malicious group member, or can be stolen by a malicious group member. You can enable it by running this once in monero-wallet-cli: set enable-multisig-experimental 1"; \
+ return false; \
+ } \
+ } while(0)
+
namespace
{
const command_line::arg_descriptor<std::string, true> arg_rpc_bind_port = {"rpc-bind-port", "Sets bind port for server"};
@@ -1064,6 +1075,8 @@ namespace tools
return false;
}
+ CHECK_MULTISIG_ENABLED();
+
// validate the transfer requested and populate dsts & extra
if (!validate_transfer(req.destinations, req.payment_id, dsts, extra, true, er))
{
@@ -1116,6 +1129,8 @@ namespace tools
return false;
}
+ CHECK_MULTISIG_ENABLED();
+
// validate the transfer requested and populate dsts & extra; RPC_TRANSFER::request and RPC_TRANSFER_SPLIT::request are identical types.
if (!validate_transfer(req.destinations, req.payment_id, dsts, extra, true, er))
{
@@ -1170,6 +1185,8 @@ namespace tools
return false;
}
+ CHECK_MULTISIG_ENABLED();
+
cryptonote::blobdata blob;
if (!epee::string_tools::parse_hexstr_to_binbuff(req.unsigned_txset, blob))
{
@@ -1518,6 +1535,8 @@ namespace tools
return false;
}
+ CHECK_MULTISIG_ENABLED();
+
try
{
std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_unmixable_sweep_transactions();
@@ -1546,6 +1565,8 @@ namespace tools
return false;
}
+ CHECK_MULTISIG_ENABLED();
+
// validate the transfer requested and populate dsts & extra
std::list<wallet_rpc::transfer_destination> destination;
destination.push_back(wallet_rpc::transfer_destination());
@@ -1611,6 +1632,8 @@ namespace tools
return false;
}
+ CHECK_MULTISIG_ENABLED();
+
// validate the transfer requested and populate dsts & extra
std::list<wallet_rpc::transfer_destination> destination;
destination.push_back(wallet_rpc::transfer_destination());
@@ -2776,7 +2799,7 @@ namespace tools
try
{
- res.outputs_data_hex = epee::string_tools::buff_to_hex_nodelimer(m_wallet->export_outputs_to_str(req.all));
+ res.outputs_data_hex = epee::string_tools::buff_to_hex_nodelimer(m_wallet->export_outputs_to_str(req.all, req.start, req.count));
}
catch (const std::exception &e)
{
@@ -3238,7 +3261,7 @@ namespace tools
if (!m_wallet) return not_open(er);
cryptonote::COMMAND_RPC_STOP_MINING::request daemon_req;
cryptonote::COMMAND_RPC_STOP_MINING::response daemon_res;
- bool r = m_wallet->invoke_http_json("/stop_mining", daemon_req, daemon_res);
+ bool r = m_wallet->invoke_http_json("/stop_mining", daemon_req, daemon_res, std::chrono::seconds(60)); // this waits till stopped, and if randomx has just started initializing its dataset, it might be a while
if (!r || daemon_res.status != CORE_RPC_STATUS_OK)
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
@@ -3940,6 +3963,9 @@ namespace tools
er.message = "This wallet is already multisig";
return false;
}
+ if (req.enable_multisig_experimental)
+ m_wallet->enable_multisig(true);
+ CHECK_MULTISIG_ENABLED();
if (m_wallet->watch_only())
{
er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY;
@@ -3966,6 +3992,7 @@ namespace tools
er.message = "This wallet is already multisig";
return false;
}
+ CHECK_MULTISIG_ENABLED();
if (m_wallet->watch_only())
{
er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY;
@@ -4010,6 +4037,7 @@ namespace tools
er.message = "This wallet is multisig, but not yet finalized";
return false;
}
+ CHECK_MULTISIG_ENABLED();
cryptonote::blobdata info;
try
@@ -4051,6 +4079,7 @@ namespace tools
er.message = "This wallet is multisig, but not yet finalized";
return false;
}
+ CHECK_MULTISIG_ENABLED();
if (req.info.size() < threshold - 1)
{
@@ -4103,6 +4132,7 @@ namespace tools
//------------------------------------------------------------------------------------------------------------------------------
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, const connection_context *ctx)
{
+ CHECK_MULTISIG_ENABLED();
return false;
}
//------------------------------------------------------------------------------------------------------------------------------
@@ -4123,13 +4153,7 @@ namespace tools
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;
- }
+ CHECK_MULTISIG_ENABLED();
if (req.multisig_info.size() + 1 < total)
{
@@ -4140,7 +4164,7 @@ namespace tools
try
{
- res.multisig_info = m_wallet->exchange_multisig_keys(req.password, req.multisig_info);
+ res.multisig_info = m_wallet->exchange_multisig_keys(req.password, req.multisig_info, req.force_update_use_with_caution);
m_wallet->multisig(&ready);
if (ready)
{
@@ -4179,6 +4203,7 @@ namespace tools
er.message = "This wallet is multisig, but not yet finalized";
return false;
}
+ CHECK_MULTISIG_ENABLED();
cryptonote::blobdata blob;
if (!epee::string_tools::parse_hexstr_to_binbuff(req.tx_data_hex, blob))
@@ -4248,6 +4273,7 @@ namespace tools
er.message = "This wallet is multisig, but not yet finalized";
return false;
}
+ CHECK_MULTISIG_ENABLED();
cryptonote::blobdata blob;
if (!epee::string_tools::parse_hexstr_to_binbuff(req.tx_data_hex, blob))
diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h
index fe53e293f..60df6296f 100644
--- a/src/wallet/wallet_rpc_server_commands_defs.h
+++ b/src/wallet/wallet_rpc_server_commands_defs.h
@@ -47,7 +47,7 @@
// advance which version they will stop working with
// Don't go over 32767 for any of these
#define WALLET_RPC_VERSION_MAJOR 1
-#define WALLET_RPC_VERSION_MINOR 25
+#define WALLET_RPC_VERSION_MINOR 26
#define MAKE_WALLET_RPC_VERSION(major,minor) (((major)<<16)|(minor))
#define WALLET_RPC_VERSION MAKE_WALLET_RPC_VERSION(WALLET_RPC_VERSION_MAJOR, WALLET_RPC_VERSION_MINOR)
namespace tools
@@ -1785,9 +1785,13 @@ namespace wallet_rpc
struct request_t
{
bool all;
+ uint32_t start;
+ uint32_t count;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(all)
+ KV_SERIALIZE_OPT(start, 0u)
+ KV_SERIALIZE_OPT(count, 0xffffffffu)
END_KV_SERIALIZE_MAP()
};
typedef epee::misc_utils::struct_init<request_t> request;
@@ -2416,7 +2420,10 @@ namespace wallet_rpc
{
struct request_t
{
+ bool enable_multisig_experimental;
+
BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE_OPT(enable_multisig_experimental, false)
END_KV_SERIALIZE_MAP()
};
typedef epee::misc_utils::struct_init<request_t> request;
@@ -2528,10 +2535,12 @@ namespace wallet_rpc
{
std::string password;
std::vector<std::string> multisig_info;
+ bool force_update_use_with_caution;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(password)
KV_SERIALIZE(multisig_info)
+ KV_SERIALIZE_OPT(force_update_use_with_caution, false)
END_KV_SERIALIZE_MAP()
};
typedef epee::misc_utils::struct_init<request_t> request;
diff --git a/src/wallet/wallet_rpc_server_error_codes.h b/src/wallet/wallet_rpc_server_error_codes.h
index 914939573..734229380 100644
--- a/src/wallet/wallet_rpc_server_error_codes.h
+++ b/src/wallet/wallet_rpc_server_error_codes.h
@@ -78,3 +78,4 @@
#define WALLET_RPC_ERROR_CODE_ATTRIBUTE_NOT_FOUND -45
#define WALLET_RPC_ERROR_CODE_ZERO_AMOUNT -46
#define WALLET_RPC_ERROR_CODE_INVALID_SIGNATURE_TYPE -47
+#define WALLET_RPC_ERROR_CODE_DISABLED -48