aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/build.yml7
-rw-r--r--.github/workflows/depends.yml7
-rw-r--r--.gitignore2
-rw-r--r--contrib/epee/include/net/levin_base.h4
-rw-r--r--contrib/epee/include/net/levin_client.inl8
-rw-r--r--contrib/epee/include/net/levin_client_async.h6
-rw-r--r--contrib/epee/include/net/levin_helper.h4
-rw-r--r--contrib/epee/include/net/levin_protocol_handler.h2
-rw-r--r--contrib/epee/include/storages/portable_storage_from_bin.h12
-rw-r--r--docs/LEVIN_PROTOCOL.md4
-rw-r--r--src/blockchain_db/blockchain_db.cpp9
-rw-r--r--src/crypto/crypto-ops.c50
-rw-r--r--src/crypto/crypto-ops.h2
-rw-r--r--src/crypto/crypto.h11
-rw-r--r--src/crypto/rx-slow-hash.c5
-rw-r--r--src/cryptonote_basic/account.cpp5
-rw-r--r--src/cryptonote_basic/account.h1
-rw-r--r--src/cryptonote_basic/cryptonote_boost_serialization.h32
-rw-r--r--src/cryptonote_basic/cryptonote_format_utils.cpp68
-rw-r--r--src/cryptonote_config.h8
-rw-r--r--src/cryptonote_core/blockchain.cpp35
-rw-r--r--src/cryptonote_core/cryptonote_core.cpp23
-rw-r--r--src/cryptonote_core/cryptonote_tx_utils.cpp1
-rw-r--r--src/gen_multisig/gen_multisig.cpp42
-rw-r--r--src/hardforks/hardforks.cpp3
-rw-r--r--src/multisig/CMakeLists.txt9
-rw-r--r--src/multisig/multisig.cpp168
-rw-r--r--src/multisig/multisig.h70
-rw-r--r--src/multisig/multisig_account.cpp184
-rw-r--r--src/multisig/multisig_account.h246
-rw-r--r--src/multisig/multisig_account_kex_impl.cpp726
-rw-r--r--src/multisig/multisig_kex_msg.cpp290
-rw-r--r--src/multisig/multisig_kex_msg.h109
-rw-r--r--src/multisig/multisig_kex_msg_serialization.h78
-rw-r--r--src/p2p/net_node.inl2
-rw-r--r--src/ringct/CMakeLists.txt6
-rw-r--r--src/ringct/bulletproofs.cc24
-rw-r--r--src/ringct/bulletproofs_plus.cc1121
-rw-r--r--src/ringct/bulletproofs_plus.h49
-rw-r--r--src/ringct/multiexp.cc4
-rw-r--r--src/ringct/rctSigs.cpp219
-rw-r--r--src/ringct/rctTypes.cpp87
-rw-r--r--src/ringct/rctTypes.h80
-rw-r--r--src/rpc/core_rpc_server.cpp51
-rw-r--r--src/rpc/core_rpc_server_commands_defs.h4
-rw-r--r--src/rpc/rpc_args.cpp6
-rw-r--r--src/serialization/json_object.cpp42
-rw-r--r--src/serialization/json_object.h3
-rw-r--r--src/simplewallet/simplewallet.cpp154
-rw-r--r--src/simplewallet/simplewallet.h2
-rw-r--r--src/wallet/api/wallet.cpp25
-rw-r--r--src/wallet/api/wallet.h1
-rw-r--r--src/wallet/api/wallet2_api.h8
-rw-r--r--src/wallet/wallet2.cpp835
-rw-r--r--src/wallet/wallet2.h96
-rw-r--r--src/wallet/wallet_rpc_server.cpp60
-rw-r--r--src/wallet/wallet_rpc_server_commands_defs.h15
-rw-r--r--tests/core_tests/CMakeLists.txt2
-rw-r--r--tests/core_tests/bulletproof_plus.cpp373
-rw-r--r--tests/core_tests/bulletproof_plus.h206
-rw-r--r--tests/core_tests/bulletproofs.cpp41
-rw-r--r--tests/core_tests/bulletproofs.h6
-rw-r--r--tests/core_tests/chaingen.h2
-rw-r--r--tests/core_tests/chaingen_main.cpp18
-rw-r--r--tests/core_tests/chaingen_tests_list.h1
-rw-r--r--tests/core_tests/multisig.cpp141
-rw-r--r--tests/core_tests/multisig.h2
-rw-r--r--tests/core_tests/rct.cpp2
-rw-r--r--tests/core_tests/rct2.cpp2
-rw-r--r--tests/crypto/crypto-tests.h2
-rw-r--r--tests/crypto/crypto.cpp46
-rw-r--r--tests/crypto/main.cpp10
-rw-r--r--tests/crypto/tests.txt6
-rwxr-xr-xtests/functional_tests/multisig.py29
-rwxr-xr-xtests/functional_tests/transfer.py7
-rw-r--r--tests/performance_tests/CMakeLists.txt1
-rw-r--r--tests/performance_tests/bulletproof_plus.h99
-rw-r--r--tests/performance_tests/main.cpp21
-rw-r--r--tests/unit_tests/CMakeLists.txt1
-rw-r--r--tests/unit_tests/bulletproofs.cpp2
-rw-r--r--tests/unit_tests/bulletproofs_plus.cpp169
-rw-r--r--tests/unit_tests/multisig.cpp234
-rw-r--r--utils/python-rpc/framework/wallet.py6
-rw-r--r--utils/systemd/monerod.service17
84 files changed, 5363 insertions, 1208 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index b97be58b9..bd2e26484 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,6 +1,11 @@
name: ci/gh-actions/cli
-on: [push, pull_request]
+on:
+ push:
+ pull_request:
+ paths-ignore:
+ - 'docs/**'
+ - '**/README.md'
# The below variables reduce repetitions across similar targets
env:
diff --git a/.github/workflows/depends.yml b/.github/workflows/depends.yml
index 74d0ceabc..9385338de 100644
--- a/.github/workflows/depends.yml
+++ b/.github/workflows/depends.yml
@@ -1,6 +1,11 @@
name: ci/gh-actions/depends
-on: [push, pull_request]
+on:
+ push:
+ pull_request:
+ paths-ignore:
+ - 'docs/**'
+ - '**/README.md'
env:
APT_SET_CONF: |
diff --git a/.gitignore b/.gitignore
index 049fc2562..cf7da3a04 100644
--- a/.gitignore
+++ b/.gitignore
@@ -110,3 +110,5 @@ nbproject
/testnet
__pycache__/
+*.pyc
+*.log
diff --git a/contrib/epee/include/net/levin_base.h b/contrib/epee/include/net/levin_base.h
index df59a6c44..b680691ad 100644
--- a/contrib/epee/include/net/levin_base.h
+++ b/contrib/epee/include/net/levin_base.h
@@ -48,7 +48,7 @@ namespace levin
{
uint64_t m_signature;
uint64_t m_cb;
- bool m_have_to_return_data;
+ uint8_t m_have_to_return_data;
uint32_t m_command;
int32_t m_return_code;
uint32_t m_reservedA; //probably some flags in future
@@ -63,7 +63,7 @@ namespace levin
{
uint64_t m_signature;
uint64_t m_cb;
- bool m_have_to_return_data;
+ uint8_t m_have_to_return_data;
uint32_t m_command;
int32_t m_return_code;
uint32_t m_flags;
diff --git a/contrib/epee/include/net/levin_client.inl b/contrib/epee/include/net/levin_client.inl
index 2f048b027..177dd8967 100644
--- a/contrib/epee/include/net/levin_client.inl
+++ b/contrib/epee/include/net/levin_client.inl
@@ -82,7 +82,7 @@ int levin_client_impl::invoke(int command, const epee::span<const uint8_t> in_bu
bucket_head head = {0};
head.m_signature = SWAP64LE(LEVIN_SIGNATURE);
head.m_cb = SWAP64LE(in_buff.size());
- head.m_have_to_return_data = true;
+ head.m_have_to_return_data = 1;
head.m_command = SWAP32LE(command);
if(!m_transport.send(&head, sizeof(head)))
return -1;
@@ -118,7 +118,7 @@ int levin_client_impl::notify(int command, const std::string& in_buff)
bucket_head head = {0};
head.m_signature = SWAP64LE(LEVIN_SIGNATURE);
head.m_cb = SWAP64LE(in_buff.size());
- head.m_have_to_return_data = false;
+ head.m_have_to_return_data = 0;
head.m_command = SWAP32LE(command);
if(!m_transport.send((const char*)&head, sizeof(head)))
@@ -141,7 +141,7 @@ inline
bucket_head2 head = {0};
head.m_signature = SWAP64LE(LEVIN_SIGNATURE);
head.m_cb = SWAP64LE(in_buff.size());
- head.m_have_to_return_data = true;
+ head.m_have_to_return_data = 1;
head.m_command = SWAP32LE(command);
head.m_return_code = SWAP32LE(0);
head.m_flags = SWAP32LE(LEVIN_PACKET_REQUEST);
@@ -179,7 +179,7 @@ inline
bucket_head2 head = {0};
head.m_signature = SWAP64LE(LEVIN_SIGNATURE);
head.m_cb = SWAP64LE(in_buff.size());
- head.m_have_to_return_data = false;
+ head.m_have_to_return_data = 0;
head.m_command = SWAP32LE(command);
head.m_return_code = SWAP32LE(0);
head.m_flags = SWAP32LE(LEVIN_PACKET_REQUEST);
diff --git a/contrib/epee/include/net/levin_client_async.h b/contrib/epee/include/net/levin_client_async.h
index ed92f4b95..067707edf 100644
--- a/contrib/epee/include/net/levin_client_async.h
+++ b/contrib/epee/include/net/levin_client_async.h
@@ -242,7 +242,7 @@ namespace levin
bucket_head head = {0};
head.m_signature = LEVIN_SIGNATURE;
head.m_cb = in_buff.size();
- head.m_have_to_return_data = true;
+ head.m_have_to_return_data = 1;
head.m_id = target;
#ifdef TRACE_LEVIN_PACKETS_BY_GUIDS
::UuidCreate(&head.m_id);
@@ -320,7 +320,7 @@ namespace levin
bucket_head head = {0};
head.m_signature = LEVIN_SIGNATURE;
head.m_cb = in_buff.size();
- head.m_have_to_return_data = false;
+ head.m_have_to_return_data = 0;
head.m_id = target;
#ifdef TRACE_LEVIN_PACKETS_BY_GUIDS
::UuidCreate(&head.m_id);
@@ -510,7 +510,7 @@ namespace levin
head.m_cb = return_buff.size();
- head.m_have_to_return_data = false;
+ head.m_have_to_return_data = 0;
head.m_protocol_version = LEVIN_PROTOCOL_VER_1;
head.m_flags = LEVIN_PACKET_RESPONSE;
diff --git a/contrib/epee/include/net/levin_helper.h b/contrib/epee/include/net/levin_helper.h
index da926a914..541e0948d 100644
--- a/contrib/epee/include/net/levin_helper.h
+++ b/contrib/epee/include/net/levin_helper.h
@@ -46,7 +46,7 @@ namespace levin
levin::bucket_head& head = *(levin::bucket_head*)(&buff[0]);
head.m_signature = SWAP64LE(LEVIN_SIGNATURE);
head.m_cb = 0;
- head.m_have_to_return_data = true;
+ head.m_have_to_return_data = 1;
head.m_command = SWAP32LE(command_id);
head.m_return_code = SWAP32LE(1);
head.m_reservedA = rand(); //probably some flags in future
@@ -68,7 +68,7 @@ namespace levin
levin::bucket_head& head = *(levin::bucket_head*)(&buff[0]);
head.m_signature = SWAP64LE(LEVIN_SIGNATURE);
head.m_cb = 0;
- head.m_have_to_return_data = true;
+ head.m_have_to_return_data = 1;
head.m_command = SWAP32LE(command_id);
head.m_return_code = SWAP32LE(1);
head.m_reservedA = rand(); //probably some flags in future
diff --git a/contrib/epee/include/net/levin_protocol_handler.h b/contrib/epee/include/net/levin_protocol_handler.h
index c510cfd79..fa7d1a5ab 100644
--- a/contrib/epee/include/net/levin_protocol_handler.h
+++ b/contrib/epee/include/net/levin_protocol_handler.h
@@ -156,7 +156,7 @@ namespace levin
std::string return_buff;
m_current_head.m_return_code = m_config.m_pcommands_handler->invoke(m_current_head.m_command, buff_to_invoke, return_buff, m_conn_context);
m_current_head.m_cb = return_buff.size();
- m_current_head.m_have_to_return_data = false;
+ m_current_head.m_have_to_return_data = 0;
return_buff.insert(0, (const char*)&m_current_head, sizeof(m_current_head));
if(!m_psnd_hndlr->do_send(byte_slice{std::move(return_buff)}))
diff --git a/contrib/epee/include/storages/portable_storage_from_bin.h b/contrib/epee/include/storages/portable_storage_from_bin.h
index 9e7b6ec34..6f081dbc7 100644
--- a/contrib/epee/include/storages/portable_storage_from_bin.h
+++ b/contrib/epee/include/storages/portable_storage_from_bin.h
@@ -157,6 +157,18 @@ namespace epee
pod_val = CONVERT_POD(pod_val);
}
+ template<>
+ void throwable_buffer_reader::read<bool>(bool& pod_val)
+ {
+ RECURSION_LIMITATION();
+ static_assert(std::is_pod<bool>::value, "POD type expected");
+ static_assert(sizeof(bool) == sizeof(uint8_t), "We really shouldn't use bool directly in serialization code. Replace it with uint8_t if this assert triggers!");
+ uint8_t t;
+ read(&t, sizeof(t));
+ CHECK_AND_ASSERT_THROW_MES(t <= 1, "Invalid bool value " << t);
+ pod_val = (t != 0);
+ }
+
template<class t_type>
t_type throwable_buffer_reader::read()
{
diff --git a/docs/LEVIN_PROTOCOL.md b/docs/LEVIN_PROTOCOL.md
index 81f6f52ee..2a5dacb84 100644
--- a/docs/LEVIN_PROTOCOL.md
+++ b/docs/LEVIN_PROTOCOL.md
@@ -1,5 +1,5 @@
# Levin Protocol
-This is a document explaining the current design of the levin protocol, as
+This is a document explaining the current design of the Levin protocol, as
used by Monero. The protocol is largely inherited from cryptonote, but has
undergone some changes.
@@ -9,7 +9,7 @@ extensibility.
One of the goals of this document is to clearly indicate what is being sent
"on the wire" to identify metadata that could de-anonymize users over I2P/Tor.
-These issues will be addressed as they are found. See `ANONMITY_NETWORKS.md` in
+These issues will be addressed as they are found. See `ANONYMITY_NETWORKS.md` in
the `docs` folder for any outstanding issues.
> This document does not currently list all data being sent by the monero
diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp
index a84a4148d..8e68abbe5 100644
--- a/src/blockchain_db/blockchain_db.cpp
+++ b/src/blockchain_db/blockchain_db.cpp
@@ -241,8 +241,15 @@ void BlockchainDB::add_transaction(const crypto::hash& blk_hash, const std::pair
}
else
{
+ rct::key commitment;
+ if (tx.version > 1)
+ {
+ commitment = tx.rct_signatures.outPk[i].mask;
+ if (rct::is_rct_bulletproof_plus(tx.rct_signatures.type))
+ commitment = rct::scalarmult8(commitment);
+ }
amount_output_indices[i] = add_output(tx_hash, tx.vout[i], i, tx.unlock_time,
- tx.version > 1 ? &tx.rct_signatures.outPk[i].mask : NULL);
+ tx.version > 1 ? &commitment : NULL);
}
}
add_tx_amount_output_indices(tx_id, amount_output_indices);
diff --git a/src/crypto/crypto-ops.c b/src/crypto/crypto-ops.c
index 508709280..54ca39775 100644
--- a/src/crypto/crypto-ops.c
+++ b/src/crypto/crypto-ops.c
@@ -3830,15 +3830,51 @@ int sc_isnonzero(const unsigned char *s) {
s[27] | s[28] | s[29] | s[30] | s[31]) - 1) >> 8) + 1;
}
-int ge_p3_is_point_at_infinity(const ge_p3 *p) {
- // X = 0 and Y == Z
- int n;
- for (n = 0; n < 10; ++n)
+int ge_p3_is_point_at_infinity_vartime(const ge_p3 *p) {
+ // https://eprint.iacr.org/2008/522
+ // X == T == 0 and Y/Z == 1
+ // note: convert all pieces to canonical bytes in case rounding is required (i.e. an element is > q)
+ // note2: even though T = XY/Z is true for valid point representations (implying it isn't necessary to
+ // test T == 0), the input to this function might NOT be valid, so we must test T == 0
+ char result_X_bytes[32];
+ fe_tobytes((unsigned char*)&result_X_bytes, p->X);
+
+ // X != 0
+ for (int i = 0; i < 32; ++i)
{
- if (p->X[n] | p->T[n])
+ if (result_X_bytes[i])
return 0;
- if (p->Y[n] != p->Z[n])
+ }
+
+ char result_T_bytes[32];
+ fe_tobytes((unsigned char*)&result_T_bytes, p->T);
+
+ // T != 0
+ for (int i = 0; i < 32; ++i)
+ {
+ if (result_T_bytes[i])
+ return 0;
+ }
+
+ char result_Y_bytes[32];
+ char result_Z_bytes[32];
+ fe_tobytes((unsigned char*)&result_Y_bytes, p->Y);
+ fe_tobytes((unsigned char*)&result_Z_bytes, p->Z);
+
+ // Y != Z
+ for (int i = 0; i < 32; ++i)
+ {
+ if (result_Y_bytes[i] != result_Z_bytes[i])
return 0;
}
- return 1;
+
+ // is Y nonzero? then Y/Z == 1
+ for (int i = 0; i < 32; ++i)
+ {
+ if (result_Y_bytes[i] != 0)
+ return 1;
+ }
+
+ // Y/Z = 0/0
+ return 0;
}
diff --git a/src/crypto/crypto-ops.h b/src/crypto/crypto-ops.h
index 22f76974b..96da16cbd 100644
--- a/src/crypto/crypto-ops.h
+++ b/src/crypto/crypto-ops.h
@@ -162,4 +162,4 @@ void fe_add(fe h, const fe f, const fe g);
void fe_tobytes(unsigned char *, const fe);
void fe_invert(fe out, const fe z);
-int ge_p3_is_point_at_infinity(const ge_p3 *p);
+int ge_p3_is_point_at_infinity_vartime(const ge_p3 *p);
diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h
index 7ddc0150f..599ae4f91 100644
--- a/src/crypto/crypto.h
+++ b/src/crypto/crypto.h
@@ -64,6 +64,11 @@ namespace crypto {
friend class crypto_ops;
};
+ POD_CLASS public_key_memsafe : epee::mlocked<tools::scrubbed<public_key>> {
+ public_key_memsafe() = default;
+ public_key_memsafe(const public_key &original) { memcpy(this->data, original.data, 32); }
+ };
+
using secret_key = epee::mlocked<tools::scrubbed<ec_scalar>>;
POD_CLASS public_keyV {
@@ -100,7 +105,7 @@ namespace crypto {
void random32_unbiased(unsigned char *bytes);
static_assert(sizeof(ec_point) == 32 && sizeof(ec_scalar) == 32 &&
- sizeof(public_key) == 32 && sizeof(secret_key) == 32 &&
+ sizeof(public_key) == 32 && sizeof(public_key_memsafe) == 32 && sizeof(secret_key) == 32 &&
sizeof(key_derivation) == 32 && sizeof(key_image) == 32 &&
sizeof(signature) == 64, "Invalid structure size");
@@ -310,9 +315,13 @@ namespace crypto {
const extern crypto::public_key null_pkey;
const extern crypto::secret_key null_skey;
+
+ inline bool operator<(const public_key &p1, const public_key &p2) { return memcmp(&p1, &p2, sizeof(public_key)) < 0; }
+ inline bool operator>(const public_key &p1, const public_key &p2) { return p2 < p1; }
}
CRYPTO_MAKE_HASHABLE(public_key)
CRYPTO_MAKE_HASHABLE_CONSTANT_TIME(secret_key)
+CRYPTO_MAKE_HASHABLE_CONSTANT_TIME(public_key_memsafe)
CRYPTO_MAKE_HASHABLE(key_image)
CRYPTO_MAKE_COMPARABLE(signature)
diff --git a/src/crypto/rx-slow-hash.c b/src/crypto/rx-slow-hash.c
index 801987e37..247c9032f 100644
--- a/src/crypto/rx-slow-hash.c
+++ b/src/crypto/rx-slow-hash.c
@@ -63,6 +63,7 @@ static rx_state rx_s[2] = {{CTHR_MUTEX_INIT,{0},0,0},{CTHR_MUTEX_INIT,{0},0,0}};
static randomx_dataset *rx_dataset;
static int rx_dataset_nomem;
+static int rx_dataset_nolp;
static uint64_t rx_dataset_height;
static THREADV randomx_vm *rx_vm = NULL;
@@ -316,10 +317,11 @@ void rx_slow_hash(const uint64_t mainheight, const uint64_t seedheight, const ch
}
CTHR_MUTEX_UNLOCK(rx_dataset_mutex);
}
- if (!(disabled_flags() & RANDOMX_FLAG_LARGE_PAGES)) {
+ if (!(disabled_flags() & RANDOMX_FLAG_LARGE_PAGES) && !rx_dataset_nolp) {
rx_vm = randomx_create_vm(flags | RANDOMX_FLAG_LARGE_PAGES, rx_sp->rs_cache, rx_dataset);
if(rx_vm == NULL) { //large pages failed
mdebug(RX_LOGCAT, "Couldn't use largePages for RandomX VM");
+ rx_dataset_nolp = 1;
}
}
if (rx_vm == NULL)
@@ -370,5 +372,6 @@ void rx_stop_mining(void) {
randomx_release_dataset(rd);
}
rx_dataset_nomem = 0;
+ rx_dataset_nolp = 0;
CTHR_MUTEX_UNLOCK(rx_dataset_mutex);
}
diff --git a/src/cryptonote_basic/account.cpp b/src/cryptonote_basic/account.cpp
index 36ff41684..9927351a9 100644
--- a/src/cryptonote_basic/account.cpp
+++ b/src/cryptonote_basic/account.cpp
@@ -253,11 +253,6 @@ DISABLE_VS_WARNINGS(4244 4345)
return crypto::secret_key_to_public_key(view_secret_key, m_keys.m_account_address.m_view_public_key);
}
//-----------------------------------------------------------------
- void account_base::finalize_multisig(const crypto::public_key &spend_public_key)
- {
- m_keys.m_account_address.m_spend_public_key = spend_public_key;
- }
- //-----------------------------------------------------------------
const account_keys& account_base::get_keys() const
{
return m_keys;
diff --git a/src/cryptonote_basic/account.h b/src/cryptonote_basic/account.h
index 5288b9b04..96b024c3c 100644
--- a/src/cryptonote_basic/account.h
+++ b/src/cryptonote_basic/account.h
@@ -82,7 +82,6 @@ namespace cryptonote
void create_from_keys(const cryptonote::account_public_address& address, const crypto::secret_key& spendkey, const crypto::secret_key& viewkey);
void create_from_viewkey(const cryptonote::account_public_address& address, const crypto::secret_key& viewkey);
bool make_multisig(const crypto::secret_key &view_secret_key, const crypto::secret_key &spend_secret_key, const crypto::public_key &spend_public_key, const std::vector<crypto::secret_key> &multisig_keys);
- void finalize_multisig(const crypto::public_key &spend_public_key);
const account_keys& get_keys() const;
std::string get_public_address_str(network_type nettype) const;
std::string get_public_integrated_address_str(const crypto::hash8 &payment_id, network_type nettype) const;
diff --git a/src/cryptonote_basic/cryptonote_boost_serialization.h b/src/cryptonote_basic/cryptonote_boost_serialization.h
index c6b81b094..24d452083 100644
--- a/src/cryptonote_basic/cryptonote_boost_serialization.h
+++ b/src/cryptonote_basic/cryptonote_boost_serialization.h
@@ -228,6 +228,20 @@ namespace boost
}
template <class Archive>
+ inline void serialize(Archive &a, rct::BulletproofPlus &x, const boost::serialization::version_type ver)
+ {
+ a & x.V;
+ a & x.A;
+ a & x.A1;
+ a & x.B;
+ a & x.r1;
+ a & x.s1;
+ a & x.d1;
+ a & x.L;
+ a & x.R;
+ }
+
+ template <class Archive>
inline void serialize(Archive &a, rct::boroSig &x, const boost::serialization::version_type ver)
{
a & x.s0;
@@ -305,7 +319,7 @@ namespace boost
a & x.type;
if (x.type == rct::RCTTypeNull)
return;
- if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple && x.type != rct::RCTTypeBulletproof && x.type != rct::RCTTypeBulletproof2 && x.type != rct::RCTTypeCLSAG)
+ if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple && x.type != rct::RCTTypeBulletproof && x.type != rct::RCTTypeBulletproof2 && x.type != rct::RCTTypeCLSAG && x.type != rct::RCTTypeBulletproofPlus)
throw boost::archive::archive_exception(boost::archive::archive_exception::other_exception, "Unsupported rct type");
// a & x.message; message is not serialized, as it can be reconstructed from the tx data
// a & x.mixRing; mixRing is not serialized, as it can be reconstructed from the offsets
@@ -321,7 +335,11 @@ namespace boost
{
a & x.rangeSigs;
if (x.rangeSigs.empty())
+ {
a & x.bulletproofs;
+ if (ver >= 2u)
+ a & x.bulletproofs_plus;
+ }
a & x.MGs;
if (ver >= 1u)
a & x.CLSAGs;
@@ -335,7 +353,7 @@ namespace boost
a & x.type;
if (x.type == rct::RCTTypeNull)
return;
- if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple && x.type != rct::RCTTypeBulletproof && x.type != rct::RCTTypeBulletproof2 && x.type != rct::RCTTypeCLSAG)
+ if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple && x.type != rct::RCTTypeBulletproof && x.type != rct::RCTTypeBulletproof2 && x.type != rct::RCTTypeCLSAG && x.type != rct::RCTTypeBulletproofPlus)
throw boost::archive::archive_exception(boost::archive::archive_exception::other_exception, "Unsupported rct type");
// a & x.message; message is not serialized, as it can be reconstructed from the tx data
// a & x.mixRing; mixRing is not serialized, as it can be reconstructed from the offsets
@@ -347,11 +365,15 @@ namespace boost
//--------------
a & x.p.rangeSigs;
if (x.p.rangeSigs.empty())
+ {
a & x.p.bulletproofs;
+ if (ver >= 2u)
+ a & x.p.bulletproofs_plus;
+ }
a & x.p.MGs;
if (ver >= 1u)
a & x.p.CLSAGs;
- if (x.type == rct::RCTTypeBulletproof || x.type == rct::RCTTypeBulletproof2 || x.type == rct::RCTTypeCLSAG)
+ if (x.type == rct::RCTTypeBulletproof || x.type == rct::RCTTypeBulletproof2 || x.type == rct::RCTTypeCLSAG || x.type == rct::RCTTypeBulletproofPlus)
a & x.p.pseudoOuts;
}
@@ -392,6 +414,6 @@ namespace boost
}
}
-BOOST_CLASS_VERSION(rct::rctSigPrunable, 1)
-BOOST_CLASS_VERSION(rct::rctSig, 1)
+BOOST_CLASS_VERSION(rct::rctSigPrunable, 2)
+BOOST_CLASS_VERSION(rct::rctSig, 2)
BOOST_CLASS_VERSION(rct::multisig_out, 1)
diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp
index 17adcdc35..cedc6f546 100644
--- a/src/cryptonote_basic/cryptonote_format_utils.cpp
+++ b/src/cryptonote_basic/cryptonote_format_utils.cpp
@@ -105,7 +105,9 @@ namespace cryptonote
uint64_t get_transaction_weight_clawback(const transaction &tx, size_t n_padded_outputs)
{
- const uint64_t bp_base = 368;
+ const rct::rctSig &rv = tx.rct_signatures;
+ const bool plus = rv.type == rct::RCTTypeBulletproofPlus;
+ const uint64_t bp_base = (32 * ((plus ? 6 : 9) + 7 * 2)) / 2; // notional size of a 2 output proof, normalized to 1 proof (ie, divided by 2)
const size_t n_outputs = tx.vout.size();
if (n_padded_outputs <= 2)
return 0;
@@ -113,7 +115,7 @@ namespace cryptonote
while ((1u << nlr) < n_padded_outputs)
++nlr;
nlr += 6;
- const size_t bp_size = 32 * (9 + 2 * nlr);
+ const size_t bp_size = 32 * ((plus ? 6 : 9) + 2 * nlr);
CHECK_AND_ASSERT_THROW_MES_L1(n_outputs <= BULLETPROOF_MAX_OUTPUTS, "maximum number of outputs is " + std::to_string(BULLETPROOF_MAX_OUTPUTS) + " per transaction");
CHECK_AND_ASSERT_THROW_MES_L1(bp_base * n_padded_outputs >= bp_size, "Invalid bulletproof clawback: bp_base " + std::to_string(bp_base) + ", n_padded_outputs "
+ std::to_string(n_padded_outputs) + ", bp_size " + std::to_string(bp_size));
@@ -164,7 +166,32 @@ namespace cryptonote
if (!base_only)
{
const bool bulletproof = rct::is_rct_bulletproof(rv.type);
- if (bulletproof)
+ const bool bulletproof_plus = rct::is_rct_bulletproof_plus(rv.type);
+ if (bulletproof_plus)
+ {
+ if (rv.p.bulletproofs_plus.size() != 1)
+ {
+ LOG_PRINT_L1("Failed to parse transaction from blob, bad bulletproofs_plus size in tx " << get_transaction_hash(tx));
+ return false;
+ }
+ if (rv.p.bulletproofs_plus[0].L.size() < 6)
+ {
+ LOG_PRINT_L1("Failed to parse transaction from blob, bad bulletproofs_plus L size in tx " << get_transaction_hash(tx));
+ return false;
+ }
+ const size_t max_outputs = rct::n_bulletproof_plus_max_amounts(rv.p.bulletproofs_plus[0]);
+ if (max_outputs < tx.vout.size())
+ {
+ LOG_PRINT_L1("Failed to parse transaction from blob, bad bulletproofs_plus max outputs in tx " << get_transaction_hash(tx));
+ return false;
+ }
+ const size_t n_amounts = tx.vout.size();
+ CHECK_AND_ASSERT_MES(n_amounts == rv.outPk.size(), false, "Internal error filling out V");
+ rv.p.bulletproofs_plus[0].V.resize(n_amounts);
+ for (size_t i = 0; i < n_amounts; ++i)
+ rv.p.bulletproofs_plus[0].V[i] = rv.outPk[i].mask;
+ }
+ else if (bulletproof)
{
if (rv.p.bulletproofs.size() != 1)
{
@@ -306,7 +333,26 @@ namespace cryptonote
{
// derive secret key with subaddress - step 1: original CN derivation
crypto::secret_key scalar_step1;
- hwdev.derive_secret_key(recv_derivation, real_output_index, ack.m_spend_secret_key, scalar_step1); // computes Hs(a*R || idx) + b
+ crypto::secret_key spend_skey = crypto::null_skey;
+
+ if (ack.m_multisig_keys.empty())
+ {
+ // if not multisig, use normal spend skey
+ spend_skey = ack.m_spend_secret_key;
+ }
+ else
+ {
+ // if multisig, use sum of multisig privkeys (local account's share of aggregate spend key)
+ for (const auto &multisig_key : ack.m_multisig_keys)
+ {
+ sc_add((unsigned char*)spend_skey.data,
+ (const unsigned char*)multisig_key.data,
+ (const unsigned char*)spend_skey.data);
+ }
+ }
+
+ // computes Hs(a*R || idx) + b
+ hwdev.derive_secret_key(recv_derivation, real_output_index, spend_skey, scalar_step1);
// step 2: add Hs(a || index_major || index_minor)
crypto::secret_key subaddr_sk;
@@ -400,9 +446,11 @@ namespace cryptonote
if (tx.version < 2)
return blob_size;
const rct::rctSig &rv = tx.rct_signatures;
- if (!rct::is_rct_bulletproof(rv.type))
+ const bool bulletproof = rct::is_rct_bulletproof(rv.type);
+ const bool bulletproof_plus = rct::is_rct_bulletproof_plus(rv.type);
+ if (!bulletproof && !bulletproof_plus)
return blob_size;
- const size_t n_padded_outputs = rct::n_bulletproof_max_amounts(rv.p.bulletproofs);
+ const size_t n_padded_outputs = bulletproof_plus ? rct::n_bulletproof_plus_max_amounts(rv.p.bulletproofs_plus) : rct::n_bulletproof_max_amounts(rv.p.bulletproofs);
uint64_t bp_clawback = get_transaction_weight_clawback(tx, n_padded_outputs);
CHECK_AND_ASSERT_THROW_MES_L1(bp_clawback <= std::numeric_limits<uint64_t>::max() - blob_size, "Weight overflow");
return blob_size + bp_clawback;
@@ -412,8 +460,8 @@ namespace cryptonote
{
CHECK_AND_ASSERT_MES(tx.pruned, std::numeric_limits<uint64_t>::max(), "get_pruned_transaction_weight does not support non pruned txes");
CHECK_AND_ASSERT_MES(tx.version >= 2, std::numeric_limits<uint64_t>::max(), "get_pruned_transaction_weight does not support v1 txes");
- CHECK_AND_ASSERT_MES(tx.rct_signatures.type >= rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG,
- std::numeric_limits<uint64_t>::max(), "get_pruned_transaction_weight does not support older range proof types");
+ CHECK_AND_ASSERT_MES(tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG || tx.rct_signatures.type == rct::RCTTypeBulletproofPlus,
+ std::numeric_limits<uint64_t>::max(), "Unsupported rct_signatures type in get_pruned_transaction_weight");
CHECK_AND_ASSERT_MES(!tx.vin.empty(), std::numeric_limits<uint64_t>::max(), "empty vin");
CHECK_AND_ASSERT_MES(tx.vin[0].type() == typeid(cryptonote::txin_to_key), std::numeric_limits<uint64_t>::max(), "empty vin");
@@ -431,12 +479,12 @@ namespace cryptonote
while ((n_padded_outputs = (1u << nrl)) < tx.vout.size())
++nrl;
nrl += 6;
- extra = 32 * (9 + 2 * nrl) + 2;
+ extra = 32 * ((rct::is_rct_bulletproof_plus(tx.rct_signatures.type) ? 6 : 9) + 2 * nrl) + 2;
weight += extra;
// calculate deterministic CLSAG/MLSAG data size
const size_t ring_size = boost::get<cryptonote::txin_to_key>(tx.vin[0]).key_offsets.size();
- if (tx.rct_signatures.type == rct::RCTTypeCLSAG)
+ if (rct::is_rct_clsag(tx.rct_signatures.type))
extra = tx.vin.size() * (ring_size + 2) * 32;
else
extra = tx.vin.size() * (ring_size * (1 + 1) * 32 + 32 /* cc */);
diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h
index 915835d1b..ff61fc036 100644
--- a/src/cryptonote_config.h
+++ b/src/cryptonote_config.h
@@ -182,6 +182,7 @@
#define HF_VERSION_EXACT_COINBASE 13
#define HF_VERSION_CLSAG 13
#define HF_VERSION_DETERMINISTIC_UNLOCK_TIME 13
+#define HF_VERSION_BULLETPROOF_PLUS 15
#define PER_KB_FEE_QUANTIZATION_DECIMALS 8
@@ -190,6 +191,7 @@
#define DEFAULT_TXPOOL_MAX_WEIGHT 648000000ull // 3 days at 300000, in bytes
#define BULLETPROOF_MAX_OUTPUTS 16
+#define BULLETPROOF_PLUS_MAX_OUTPUTS 16
#define CRYPTONOTE_PRUNING_STRIPE_SIZE 4096 // the smaller, the smoother the increase
#define CRYPTONOTE_PRUNING_LOG_STRIPES 3 // the higher, the more space saved
@@ -221,6 +223,8 @@ namespace config
// Hash domain separators
const char HASH_KEY_BULLETPROOF_EXPONENT[] = "bulletproof";
+ const char HASH_KEY_BULLETPROOF_PLUS_EXPONENT[] = "bulletproof_plus";
+ const char HASH_KEY_BULLETPROOF_PLUS_TRANSCRIPT[] = "bulletproof_plus_transcript";
const char HASH_KEY_RINGDB[] = "ringdsb";
const char HASH_KEY_SUBADDRESS[] = "SubAddr";
const unsigned char HASH_KEY_ENCRYPTED_PAYMENT_ID = 0x8d;
@@ -229,6 +233,7 @@ namespace config
const unsigned char HASH_KEY_RPC_PAYMENT_NONCE = 0x58;
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_TXPROOF_V2[] = "TXPROOF_V2";
const unsigned char HASH_KEY_CLSAG_ROUND[] = "CLSAG_round";
const unsigned char HASH_KEY_CLSAG_AGG_0[] = "CLSAG_agg_0";
@@ -236,6 +241,9 @@ namespace config
const char HASH_KEY_MESSAGE_SIGNING[] = "MoneroMessageSignature";
const unsigned char HASH_KEY_MM_SLOT = 'm';
+ // Multisig
+ const uint32_t MULTISIG_MAX_SIGNERS{16};
+
namespace testnet
{
uint64_t const CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX = 53;
diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp
index 34031fb7c..cd9972d1e 100644
--- a/src/cryptonote_core/blockchain.cpp
+++ b/src/cryptonote_core/blockchain.cpp
@@ -3143,6 +3143,32 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context
}
}
+ // from v15, allow bulletproofs plus
+ if (hf_version < HF_VERSION_BULLETPROOF_PLUS) {
+ if (tx.version >= 2) {
+ const bool bulletproof_plus = rct::is_rct_bulletproof_plus(tx.rct_signatures.type);
+ if (bulletproof_plus || !tx.rct_signatures.p.bulletproofs_plus.empty())
+ {
+ MERROR_VER("Bulletproofs plus are not allowed before v" << std::to_string(HF_VERSION_BULLETPROOF_PLUS));
+ tvc.m_invalid_output = true;
+ return false;
+ }
+ }
+ }
+
+ // from v16, forbid bulletproofs
+ if (hf_version > HF_VERSION_BULLETPROOF_PLUS) {
+ if (tx.version >= 2) {
+ const bool bulletproof = rct::is_rct_bulletproof(tx.rct_signatures.type);
+ if (bulletproof)
+ {
+ MERROR_VER("Bulletproof range proofs are not allowed after v" + std::to_string(HF_VERSION_BULLETPROOF_PLUS));
+ tvc.m_invalid_output = true;
+ return false;
+ }
+ }
+ }
+
return true;
}
//------------------------------------------------------------------
@@ -3183,7 +3209,7 @@ bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_pr
}
}
}
- else if (rv.type == rct::RCTTypeSimple || rv.type == rct::RCTTypeBulletproof || rv.type == rct::RCTTypeBulletproof2 || rv.type == rct::RCTTypeCLSAG)
+ else if (rv.type == rct::RCTTypeSimple || rv.type == rct::RCTTypeBulletproof || rv.type == rct::RCTTypeBulletproof2 || rv.type == rct::RCTTypeCLSAG || rv.type == rct::RCTTypeBulletproofPlus)
{
CHECK_AND_ASSERT_MES(!pubkeys.empty() && !pubkeys[0].empty(), false, "empty pubkeys");
rv.mixRing.resize(pubkeys.size());
@@ -3224,7 +3250,7 @@ bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_pr
}
}
}
- else if (rv.type == rct::RCTTypeCLSAG)
+ else if (rv.type == rct::RCTTypeCLSAG || rv.type == rct::RCTTypeBulletproofPlus)
{
if (!tx.pruned)
{
@@ -3516,6 +3542,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
case rct::RCTTypeBulletproof:
case rct::RCTTypeBulletproof2:
case rct::RCTTypeCLSAG:
+ case rct::RCTTypeBulletproofPlus:
{
// check all this, either reconstructed (so should really pass), or not
{
@@ -3551,7 +3578,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
}
}
- const size_t n_sigs = rv.type == rct::RCTTypeCLSAG ? rv.p.CLSAGs.size() : rv.p.MGs.size();
+ const size_t n_sigs = rct::is_rct_clsag(rv.type) ? rv.p.CLSAGs.size() : rv.p.MGs.size();
if (n_sigs != tx.vin.size())
{
MERROR_VER("Failed to check ringct signatures: mismatched MGs/vin sizes");
@@ -3560,7 +3587,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
for (size_t n = 0; n < tx.vin.size(); ++n)
{
bool error;
- if (rv.type == rct::RCTTypeCLSAG)
+ if (rct::is_rct_clsag(rv.type))
error = memcmp(&boost::get<txin_to_key>(tx.vin[n]).k_image, &rv.p.CLSAGs[n].I, 32);
else
error = rv.p.MGs[n].II.empty() || memcmp(&boost::get<txin_to_key>(tx.vin[n]).k_image, &rv.p.MGs[n].II[0], 32);
diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp
index 4c6536318..1da4e2d41 100644
--- a/src/cryptonote_core/cryptonote_core.cpp
+++ b/src/cryptonote_core/cryptonote_core.cpp
@@ -879,6 +879,16 @@ namespace cryptonote
return true;
}
//-----------------------------------------------------------------------------------------------
+ static bool is_canonical_bulletproof_plus_layout(const std::vector<rct::BulletproofPlus> &proofs)
+ {
+ if (proofs.size() != 1)
+ return false;
+ const size_t sz = proofs[0].V.size();
+ if (sz == 0 || sz > BULLETPROOF_PLUS_MAX_OUTPUTS)
+ return false;
+ return true;
+ }
+ //-----------------------------------------------------------------------------------------------
bool core::handle_incoming_tx_accumulated_batch(std::vector<tx_verification_batch_info> &tx_info, bool keeped_by_block)
{
bool ret = true;
@@ -943,6 +953,17 @@ namespace cryptonote
}
rvv.push_back(&rv); // delayed batch verification
break;
+ case rct::RCTTypeBulletproofPlus:
+ if (!is_canonical_bulletproof_plus_layout(rv.p.bulletproofs_plus))
+ {
+ MERROR_VER("Bulletproof_plus does not have canonical form");
+ set_semantics_failed(tx_info[n].tx_hash);
+ tx_info[n].tvc.m_verifivation_failed = true;
+ tx_info[n].result = false;
+ break;
+ }
+ rvv.push_back(&rv); // delayed batch verification
+ break;
default:
MERROR_VER("Unknown rct type: " << rv.type);
set_semantics_failed(tx_info[n].tx_hash);
@@ -960,7 +981,7 @@ namespace cryptonote
{
if (!tx_info[n].result)
continue;
- if (tx_info[n].tx->rct_signatures.type != rct::RCTTypeBulletproof && tx_info[n].tx->rct_signatures.type != rct::RCTTypeBulletproof2 && tx_info[n].tx->rct_signatures.type != rct::RCTTypeCLSAG)
+ if (tx_info[n].tx->rct_signatures.type != rct::RCTTypeBulletproof && tx_info[n].tx->rct_signatures.type != rct::RCTTypeBulletproof2 && tx_info[n].tx->rct_signatures.type != rct::RCTTypeCLSAG && tx_info[n].tx->rct_signatures.type != rct::RCTTypeBulletproofPlus)
continue;
if (assumed_bad || !rct::verRctSemanticsSimple(tx_info[n].tx->rct_signatures))
{
diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp
index f6061b803..a50ebb550 100644
--- a/src/cryptonote_core/cryptonote_tx_utils.cpp
+++ b/src/cryptonote_core/cryptonote_tx_utils.cpp
@@ -43,7 +43,6 @@ using namespace epee;
#include "crypto/crypto.h"
#include "crypto/hash.h"
#include "ringct/rctSigs.h"
-#include "multisig/multisig.h"
using namespace crypto;
diff --git a/src/gen_multisig/gen_multisig.cpp b/src/gen_multisig/gen_multisig.cpp
index 4aa21b149..87de3c351 100644
--- a/src/gen_multisig/gen_multisig.cpp
+++ b/src/gen_multisig/gen_multisig.cpp
@@ -95,55 +95,35 @@ static bool generate_multisig(uint32_t threshold, uint32_t total, const std::str
}
// gather the keys
- std::vector<crypto::secret_key> sk(total);
- std::vector<crypto::public_key> pk(total);
+ std::vector<std::string> first_round_msgs;
+ first_round_msgs.reserve(total);
for (size_t n = 0; n < total; ++n)
{
wallets[n]->decrypt_keys(pwd_container->password());
- if (!tools::wallet2::verify_multisig_info(wallets[n]->get_multisig_info(), sk[n], pk[n]))
- {
- tools::fail_msg_writer() << genms::tr("Failed to verify multisig info");
- return false;
- }
+
+ first_round_msgs.emplace_back(wallets[n]->get_multisig_first_kex_msg());
+
wallets[n]->encrypt_keys(pwd_container->password());
}
// make the wallets multisig
- std::vector<std::string> extra_info(total);
+ std::vector<std::string> kex_msgs_intermediate(total);
std::stringstream ss;
for (size_t n = 0; n < total; ++n)
{
std::string name = basename + "-" + std::to_string(n + 1);
- std::vector<crypto::secret_key> skn;
- std::vector<crypto::public_key> pkn;
- for (size_t k = 0; k < total; ++k)
- {
- if (k != n)
- {
- skn.push_back(sk[k]);
- pkn.push_back(pk[k]);
- }
- }
- extra_info[n] = wallets[n]->make_multisig(pwd_container->password(), skn, pkn, threshold);
+
+ kex_msgs_intermediate[n] = wallets[n]->make_multisig(pwd_container->password(), first_round_msgs, threshold);
+
ss << " " << name << std::endl;
}
//exchange keys unless exchange_multisig_keys returns no extra info
- while (!extra_info[0].empty())
+ while (!kex_msgs_intermediate[0].empty())
{
- std::unordered_set<crypto::public_key> pkeys;
- std::vector<crypto::public_key> signers(total);
- for (size_t n = 0; n < total; ++n)
- {
- if (!tools::wallet2::verify_extra_multisig_info(extra_info[n], pkeys, signers[n]))
- {
- tools::fail_msg_writer() << genms::tr("Error verifying multisig extra info");
- return false;
- }
- }
for (size_t n = 0; n < total; ++n)
{
- extra_info[n] = wallets[n]->exchange_multisig_keys(pwd_container->password(), pkeys, signers);
+ kex_msgs_intermediate[n] = wallets[n]->exchange_multisig_keys(pwd_container->password(), kex_msgs_intermediate);
}
}
diff --git a/src/hardforks/hardforks.cpp b/src/hardforks/hardforks.cpp
index 9055b92e3..45db59a67 100644
--- a/src/hardforks/hardforks.cpp
+++ b/src/hardforks/hardforks.cpp
@@ -70,6 +70,9 @@ 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
};
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;
diff --git a/src/multisig/CMakeLists.txt b/src/multisig/CMakeLists.txt
index eaa2c6f71..14099e64a 100644
--- a/src/multisig/CMakeLists.txt
+++ b/src/multisig/CMakeLists.txt
@@ -27,12 +27,17 @@
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
set(multisig_sources
- multisig.cpp)
+ multisig.cpp
+ multisig_account.cpp
+ multisig_account_kex_impl.cpp
+ multisig_kex_msg.cpp)
set(multisig_headers)
set(multisig_private_headers
- multisig.h)
+ multisig.h
+ multisig_account.h
+ multisig_kex_msg.h)
monero_private_headers(multisig
${multisig_private_headers})
diff --git a/src/multisig/multisig.cpp b/src/multisig/multisig.cpp
index 272de73b2..85c45bc31 100644
--- a/src/multisig/multisig.cpp
+++ b/src/multisig/multisig.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2020, The Monero Project
+// Copyright (c) 2017-2021, The Monero Project
//
// All rights reserved.
//
@@ -26,29 +26,34 @@
// 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 <unordered_set>
-#include "include_base_utils.h"
#include "crypto/crypto.h"
-#include "ringct/rctOps.h"
#include "cryptonote_basic/account.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
-#include "multisig.h"
#include "cryptonote_config.h"
+#include "include_base_utils.h"
+#include "multisig.h"
+#include "ringct/rctOps.h"
+
+#include <algorithm>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "multisig"
-using namespace std;
-
-namespace cryptonote
+namespace multisig
{
- //-----------------------------------------------------------------
+ //----------------------------------------------------------------------------------------------------------------------
crypto::secret_key get_multisig_blinded_secret_key(const crypto::secret_key &key)
{
+ CHECK_AND_ASSERT_THROW_MES(key != crypto::null_skey, "Unexpected null secret key (danger!).");
+
rct::key multisig_salt;
static_assert(sizeof(rct::key) == sizeof(config::HASH_KEY_MULTISIG), "Hash domain separator is an unexpected size");
memcpy(multisig_salt.bytes, config::HASH_KEY_MULTISIG, sizeof(rct::key));
+ // private key = H(key, domain-sep)
rct::keyV data;
data.reserve(2);
data.push_back(rct::sk2rct(key));
@@ -57,134 +62,79 @@ namespace cryptonote
memwipe(&data[0], sizeof(rct::key));
return result;
}
- //-----------------------------------------------------------------
- void generate_multisig_N_N(const account_keys &keys, const std::vector<crypto::public_key> &spend_keys, std::vector<crypto::secret_key> &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey)
- {
- // the multisig spend public key is the sum of all spend public keys
- multisig_keys.clear();
- const crypto::secret_key spend_secret_key = get_multisig_blinded_secret_key(keys.m_spend_secret_key);
- CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(spend_secret_key, (crypto::public_key&)spend_pkey), "Failed to derive public key");
- for (const auto &k: spend_keys)
- rct::addKeys(spend_pkey, spend_pkey, rct::pk2rct(k));
- multisig_keys.push_back(spend_secret_key);
- spend_skey = rct::sk2rct(spend_secret_key);
- }
- //-----------------------------------------------------------------
- void generate_multisig_N1_N(const account_keys &keys, const std::vector<crypto::public_key> &spend_keys, std::vector<crypto::secret_key> &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey)
- {
- multisig_keys.clear();
- spend_pkey = rct::identity();
- spend_skey = rct::zero();
-
- // create all our composite private keys
- crypto::secret_key blinded_skey = get_multisig_blinded_secret_key(keys.m_spend_secret_key);
- for (const auto &k: spend_keys)
- {
- rct::key sk = rct::scalarmultKey(rct::pk2rct(k), rct::sk2rct(blinded_skey));
- crypto::secret_key msk = get_multisig_blinded_secret_key(rct::rct2sk(sk));
- memwipe(&sk, sizeof(sk));
- multisig_keys.push_back(msk);
- sc_add(spend_skey.bytes, spend_skey.bytes, (const unsigned char*)msk.data);
- }
- }
- //-----------------------------------------------------------------
- std::vector<crypto::public_key> generate_multisig_derivations(const account_keys &keys, const std::vector<crypto::public_key> &derivations)
- {
- std::vector<crypto::public_key> multisig_keys;
- crypto::secret_key blinded_skey = get_multisig_blinded_secret_key(keys.m_spend_secret_key);
- for (const auto &k: derivations)
- {
- rct::key d = rct::scalarmultKey(rct::pk2rct(k), rct::sk2rct(blinded_skey));
- multisig_keys.push_back(rct::rct2pk(d));
- }
-
- return multisig_keys;
- }
- //-----------------------------------------------------------------
- crypto::secret_key calculate_multisig_signer_key(const std::vector<crypto::secret_key>& multisig_keys)
- {
- rct::key secret_key = rct::zero();
- for (const auto &k: multisig_keys)
- {
- sc_add(secret_key.bytes, secret_key.bytes, (const unsigned char*)k.data);
- }
-
- return rct::rct2sk(secret_key);
- }
- //-----------------------------------------------------------------
- std::vector<crypto::secret_key> calculate_multisig_keys(const std::vector<crypto::public_key>& derivations)
- {
- std::vector<crypto::secret_key> multisig_keys;
- multisig_keys.reserve(derivations.size());
-
- for (const auto &k: derivations)
- {
- multisig_keys.emplace_back(get_multisig_blinded_secret_key(rct::rct2sk(rct::pk2rct(k))));
- }
-
- return multisig_keys;
- }
- //-----------------------------------------------------------------
- crypto::secret_key generate_multisig_view_secret_key(const crypto::secret_key &skey, const std::vector<crypto::secret_key> &skeys)
- {
- crypto::secret_key view_skey = get_multisig_blinded_secret_key(skey);
- for (const auto &k: skeys)
- sc_add((unsigned char*)&view_skey, rct::sk2rct(view_skey).bytes, rct::sk2rct(k).bytes);
- return view_skey;
- }
- //-----------------------------------------------------------------
- crypto::public_key generate_multisig_M_N_spend_public_key(const std::vector<crypto::public_key> &pkeys)
- {
- rct::key spend_public_key = rct::identity();
- for (const auto &pk: pkeys)
- {
- rct::addKeys(spend_public_key, spend_public_key, rct::pk2rct(pk));
- }
- return rct::rct2pk(spend_public_key);
- }
- //-----------------------------------------------------------------
- bool generate_multisig_key_image(const account_keys &keys, size_t multisig_key_index, const crypto::public_key& out_key, crypto::key_image& ki)
+ //----------------------------------------------------------------------------------------------------------------------
+ bool generate_multisig_key_image(const cryptonote::account_keys &keys,
+ std::size_t multisig_key_index,
+ const crypto::public_key& out_key,
+ crypto::key_image& ki)
{
if (multisig_key_index >= keys.m_multisig_keys.size())
return false;
crypto::generate_key_image(out_key, keys.m_multisig_keys[multisig_key_index], ki);
return true;
}
- //-----------------------------------------------------------------
- void generate_multisig_LR(const crypto::public_key pkey, const crypto::secret_key &k, crypto::public_key &L, crypto::public_key &R)
+ //----------------------------------------------------------------------------------------------------------------------
+ void generate_multisig_LR(const crypto::public_key pkey,
+ const crypto::secret_key &k,
+ crypto::public_key &L,
+ crypto::public_key &R)
{
rct::scalarmultBase((rct::key&)L, rct::sk2rct(k));
crypto::generate_key_image(pkey, k, (crypto::key_image&)R);
}
- //-----------------------------------------------------------------
- bool generate_multisig_composite_key_image(const account_keys &keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::public_key &tx_public_key, const std::vector<crypto::public_key>& additional_tx_public_keys, size_t real_output_index, const std::vector<crypto::key_image> &pkis, crypto::key_image &ki)
+ //----------------------------------------------------------------------------------------------------------------------
+ bool generate_multisig_composite_key_image(const cryptonote::account_keys &keys,
+ const std::unordered_map<crypto::public_key, cryptonote::subaddress_index> &subaddresses,
+ const crypto::public_key &out_key,
+ const crypto::public_key &tx_public_key,
+ const std::vector<crypto::public_key> &additional_tx_public_keys,
+ std::size_t real_output_index,
+ const std::vector<crypto::key_image> &pkis,
+ crypto::key_image &ki)
{
+ // create a multisig partial key image
+ // KI_partial = ([view key component] + [subaddress component] + [multisig privkeys]) * Hp(output one-time address)
+ // - the 'multisig priv keys' here are those held by the local account
+ // - later, we add in the components held by other participants
cryptonote::keypair in_ephemeral;
if (!cryptonote::generate_key_image_helper(keys, subaddresses, out_key, tx_public_key, additional_tx_public_keys, real_output_index, in_ephemeral, ki, keys.get_device()))
return false;
std::unordered_set<crypto::key_image> used;
- for (size_t m = 0; m < keys.m_multisig_keys.size(); ++m)
+
+ // create a key image component for each of the local account's multisig private keys
+ for (std::size_t m = 0; m < keys.m_multisig_keys.size(); ++m)
{
crypto::key_image pki;
- bool r = cryptonote::generate_multisig_key_image(keys, m, out_key, pki);
+ // pki = keys.m_multisig_keys[m] * Hp(out_key)
+ // pki = key image component
+ // out_key = one-time address of an output owned by the multisig group
+ bool r = generate_multisig_key_image(keys, m, out_key, pki);
if (!r)
return false;
+
+ // this KI component is 'used' because it was included in the partial key image 'ki' above
used.insert(pki);
}
+
+ // add the KI components from other participants to the partial KI
+ // if they not included yet
for (const auto &pki: pkis)
{
if (used.find(pki) == used.end())
{
+ // ignore components that have already been 'used'
used.insert(pki);
+
+ // KI_partial = KI_partial + KI_component[...]
rct::addKeys((rct::key&)ki, rct::ki2rct(ki), rct::ki2rct(pki));
}
}
+
+ // at the end, 'ki' will hold the true key image for our output if inputs were sufficient
+ // - if 'pkis' (the other participants' KI components) is missing some components
+ // then 'ki' will not be complete
+
return true;
}
- //-----------------------------------------------------------------
- uint32_t multisig_rounds_required(uint32_t participants, uint32_t threshold)
- {
- CHECK_AND_ASSERT_THROW_MES(participants >= threshold, "participants must be greater or equal than threshold");
- return participants - threshold + 1;
- }
-}
+ //----------------------------------------------------------------------------------------------------------------------
+} //namespace multisig
diff --git a/src/multisig/multisig.h b/src/multisig/multisig.h
index eab32187c..e041ea670 100644
--- a/src/multisig/multisig.h
+++ b/src/multisig/multisig.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2020, The Monero Project
+// Copyright (c) 2017-2021, The Monero Project
//
// All rights reserved.
//
@@ -28,44 +28,42 @@
#pragma once
-#include <vector>
-#include <unordered_map>
#include "crypto/crypto.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "ringct/rctTypes.h"
-namespace cryptonote
-{
- struct account_keys;
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
- crypto::secret_key get_multisig_blinded_secret_key(const crypto::secret_key &key);
- void generate_multisig_N_N(const account_keys &keys, const std::vector<crypto::public_key> &spend_keys, std::vector<crypto::secret_key> &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey);
- void generate_multisig_N1_N(const account_keys &keys, const std::vector<crypto::public_key> &spend_keys, std::vector<crypto::secret_key> &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey);
- /**
- * @brief generate_multisig_derivations performs common DH key derivation.
- * Each middle round in M/N scheme is DH exchange of public multisig keys of other participants multiplied by secret spend key of current participant.
- * this functions does the following: new multisig key = secret spend * public multisig key
- * @param keys - current wallet's keys
- * @param derivations - public multisig keys of other participants
- * @return new public multisig keys derived from previous round. This data needs to be exchange with other participants
- */
- std::vector<crypto::public_key> generate_multisig_derivations(const account_keys &keys, const std::vector<crypto::public_key> &derivations);
- crypto::secret_key calculate_multisig_signer_key(const std::vector<crypto::secret_key>& derivations);
- /**
- * @brief calculate_multisig_keys. Calculates secret multisig keys from others' participants ones as follows: mi = H(Mi)
- * @param derivations - others' participants public multisig keys.
- * @return vector of current wallet's multisig secret keys
- */
- std::vector<crypto::secret_key> calculate_multisig_keys(const std::vector<crypto::public_key>& derivations);
- crypto::secret_key generate_multisig_view_secret_key(const crypto::secret_key &skey, const std::vector<crypto::secret_key> &skeys);
+namespace cryptonote { struct account_keys; }
+
+namespace multisig
+{
/**
- * @brief generate_multisig_M_N_spend_public_key calculates multisig wallet's spend public key by summing all of public multisig keys
- * @param pkeys unique public multisig keys
- * @return multisig wallet's spend public key
- */
- crypto::public_key generate_multisig_M_N_spend_public_key(const std::vector<crypto::public_key> &pkeys);
- bool generate_multisig_key_image(const account_keys &keys, size_t multisig_key_index, const crypto::public_key& out_key, crypto::key_image& ki);
- void generate_multisig_LR(const crypto::public_key pkey, const crypto::secret_key &k, crypto::public_key &L, crypto::public_key &R);
- bool generate_multisig_composite_key_image(const account_keys &keys, const std::unordered_map<crypto::public_key, cryptonote::subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::public_key &tx_public_key, const std::vector<crypto::public_key>& additional_tx_public_keys, size_t real_output_index, const std::vector<crypto::key_image> &pkis, crypto::key_image &ki);
- uint32_t multisig_rounds_required(uint32_t participants, uint32_t threshold);
-}
+ * @brief get_multisig_blinded_secret_key - converts an input private key into a blinded multisig private key
+ * Use 1a: converts account private spend key into multisig private key, which is used for key exchange and message signing
+ * Use 1b: converts account private view key into ancillary private key share, for the composite multisig private view key
+ * Use 2: converts DH shared secrets (curve points) into private keys, which are intermediate private keys in multisig key exchange
+ * @param key - private key to transform
+ * @return transformed private key
+ */
+ crypto::secret_key get_multisig_blinded_secret_key(const crypto::secret_key &key);
+
+ bool generate_multisig_key_image(const cryptonote::account_keys &keys,
+ std::size_t multisig_key_index,
+ const crypto::public_key& out_key,
+ crypto::key_image& ki);
+ void generate_multisig_LR(const crypto::public_key pkey,
+ const crypto::secret_key &k,
+ crypto::public_key &L,
+ crypto::public_key &R);
+ bool generate_multisig_composite_key_image(const cryptonote::account_keys &keys,
+ const std::unordered_map<crypto::public_key, cryptonote::subaddress_index> &subaddresses,
+ const crypto::public_key &out_key,
+ const crypto::public_key &tx_public_key,
+ const std::vector<crypto::public_key> &additional_tx_public_keys,
+ std::size_t real_output_index,
+ const std::vector<crypto::key_image> &pkis,
+ crypto::key_image &ki);
+} //namespace multisig
diff --git a/src/multisig/multisig_account.cpp b/src/multisig/multisig_account.cpp
new file mode 100644
index 000000000..b7298c4b6
--- /dev/null
+++ b/src/multisig/multisig_account.cpp
@@ -0,0 +1,184 @@
+// 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_account.h"
+
+#include "crypto/crypto.h"
+#include "cryptonote_config.h"
+#include "include_base_utils.h"
+#include "multisig.h"
+#include "multisig_kex_msg.h"
+#include "ringct/rctOps.h"
+#include "ringct/rctTypes.h"
+
+#include <cstdint>
+#include <utility>
+#include <vector>
+
+
+#undef MONERO_DEFAULT_LOG_CATEGORY
+#define MONERO_DEFAULT_LOG_CATEGORY "multisig"
+
+namespace multisig
+{
+ //----------------------------------------------------------------------------------------------------------------------
+ // multisig_account: EXTERNAL
+ //----------------------------------------------------------------------------------------------------------------------
+ multisig_account::multisig_account(const crypto::secret_key &base_privkey,
+ const crypto::secret_key &base_common_privkey) :
+ m_base_privkey{base_privkey},
+ m_base_common_privkey{base_common_privkey},
+ m_multisig_pubkey{rct::rct2pk(rct::identity())},
+ m_common_pubkey{rct::rct2pk(rct::identity())},
+ m_kex_rounds_complete{0},
+ m_next_round_kex_message{multisig_kex_msg{1, base_privkey, std::vector<crypto::public_key>{}, base_common_privkey}.get_msg()}
+ {
+ CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(m_base_privkey, m_base_pubkey),
+ "Failed to derive public key");
+ }
+ //----------------------------------------------------------------------------------------------------------------------
+ // multisig_account: EXTERNAL
+ //----------------------------------------------------------------------------------------------------------------------
+ multisig_account::multisig_account(const std::uint32_t threshold,
+ std::vector<crypto::public_key> signers,
+ const crypto::secret_key &base_privkey,
+ const crypto::secret_key &base_common_privkey,
+ std::vector<crypto::secret_key> multisig_privkeys,
+ const crypto::secret_key &common_privkey,
+ const crypto::public_key &multisig_pubkey,
+ const crypto::public_key &common_pubkey,
+ const std::uint32_t kex_rounds_complete,
+ kex_origins_map_t kex_origins_map,
+ std::string next_round_kex_message) :
+ m_base_privkey{base_privkey},
+ m_base_common_privkey{base_common_privkey},
+ m_multisig_privkeys{std::move(multisig_privkeys)},
+ m_common_privkey{common_privkey},
+ m_multisig_pubkey{multisig_pubkey},
+ m_common_pubkey{common_pubkey},
+ m_kex_rounds_complete{kex_rounds_complete},
+ m_kex_keys_to_origins_map{std::move(kex_origins_map)},
+ m_next_round_kex_message{std::move(next_round_kex_message)}
+ {
+ CHECK_AND_ASSERT_THROW_MES(kex_rounds_complete > 0, "multisig account: can't reconstruct account if its kex wasn't initialized");
+ CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(m_base_privkey, m_base_pubkey),
+ "Failed to derive public key");
+ set_multisig_config(threshold, std::move(signers));
+ }
+ //----------------------------------------------------------------------------------------------------------------------
+ // multisig_account: EXTERNAL
+ //----------------------------------------------------------------------------------------------------------------------
+ bool multisig_account::account_is_active() const
+ {
+ return m_kex_rounds_complete > 0;
+ }
+ //----------------------------------------------------------------------------------------------------------------------
+ // multisig_account: EXTERNAL
+ //----------------------------------------------------------------------------------------------------------------------
+ bool multisig_account::multisig_is_ready() const
+ {
+ if (account_is_active())
+ return multisig_kex_rounds_required(m_signers.size(), m_threshold) == m_kex_rounds_complete;
+ else
+ return false;
+ }
+ //----------------------------------------------------------------------------------------------------------------------
+ // multisig_account: INTERNAL
+ //----------------------------------------------------------------------------------------------------------------------
+ void multisig_account::set_multisig_config(const std::size_t threshold, std::vector<crypto::public_key> signers)
+ {
+ // validate
+ CHECK_AND_ASSERT_THROW_MES(threshold > 0 && threshold <= signers.size(), "multisig account: tried to set invalid threshold.");
+ CHECK_AND_ASSERT_THROW_MES(signers.size() >= 2 && signers.size() <= config::MULTISIG_MAX_SIGNERS,
+ "multisig account: tried to set invalid number of signers.");
+
+ for (auto signer_it = signers.begin(); signer_it != signers.end(); ++signer_it)
+ {
+ // signers should all be unique
+ CHECK_AND_ASSERT_THROW_MES(std::find(signers.begin(), signer_it, *signer_it) == signer_it,
+ "multisig account: tried to set signers, but found a duplicate signer unexpectedly.");
+
+ // signer pubkeys must be in main subgroup, and not identity
+ CHECK_AND_ASSERT_THROW_MES(rct::isInMainSubgroup(rct::pk2rct(*signer_it)) && !(*signer_it == rct::rct2pk(rct::identity())),
+ "multisig account: tried to set signers, but a signer pubkey is invalid.");
+ }
+
+ // own pubkey should be in signers list
+ CHECK_AND_ASSERT_THROW_MES(std::find(signers.begin(), signers.end(), m_base_pubkey) != signers.end(),
+ "multisig account: tried to set signers, but did not find the account's base pubkey in signer list.");
+
+ // sort signers
+ std::sort(signers.begin(), signers.end(),
+ [](const crypto::public_key &key1, const crypto::public_key &key2) -> bool
+ {
+ return memcmp(&key1, &key2, sizeof(crypto::public_key)) < 0;
+ }
+ );
+
+ // set
+ m_threshold = threshold;
+ m_signers = std::move(signers);
+ }
+ //----------------------------------------------------------------------------------------------------------------------
+ // multisig_account: EXTERNAL
+ //----------------------------------------------------------------------------------------------------------------------
+ void multisig_account::initialize_kex(const std::uint32_t threshold,
+ std::vector<crypto::public_key> signers,
+ const std::vector<multisig_kex_msg> &expanded_msgs_rnd1)
+ {
+ CHECK_AND_ASSERT_THROW_MES(!account_is_active(), "multisig account: tried to initialize kex, but already initialized");
+
+ // 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);
+ *this = std::move(temp_account);
+ }
+ //----------------------------------------------------------------------------------------------------------------------
+ // multisig_account: EXTERNAL
+ //----------------------------------------------------------------------------------------------------------------------
+ void multisig_account::kex_update(const std::vector<multisig_kex_msg> &expanded_msgs)
+ {
+ 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);
+ *this = std::move(temp_account);
+ }
+ //----------------------------------------------------------------------------------------------------------------------
+ // EXTERNAL
+ //----------------------------------------------------------------------------------------------------------------------
+ std::uint32_t multisig_kex_rounds_required(const std::uint32_t num_signers, const std::uint32_t threshold)
+ {
+ CHECK_AND_ASSERT_THROW_MES(num_signers >= threshold, "num_signers must be >= threshold");
+ CHECK_AND_ASSERT_THROW_MES(threshold >= 1, "threshold must be >= 1");
+ return num_signers - threshold + 1;
+ }
+ //----------------------------------------------------------------------------------------------------------------------
+} //namespace multisig
diff --git a/src/multisig/multisig_account.h b/src/multisig/multisig_account.h
new file mode 100644
index 000000000..b01ae6c88
--- /dev/null
+++ b/src/multisig/multisig_account.h
@@ -0,0 +1,246 @@
+// 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 "crypto/crypto.h"
+#include "multisig_kex_msg.h"
+
+#include <cstdint>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+
+namespace multisig
+{
+ /**
+ * multisig account:
+ *
+ * - handles account keys for an M-of-N multisig participant (M <= N; M >= 1; N >= 2)
+ * - encapsulates multisig account construction process (via key exchange [kex])
+ * - TODO: encapsulates key preparation for aggregation-style signing
+ *
+ * :: multisig pubkey: the private key is split, M group participants are required to reassemble (e.g. to sign something)
+ * - in cryptonote, this is the multisig spend key
+ * :: multisig common pubkey: the private key is known to all participants (e.g. for authenticating as a group member)
+ * - in cryptonote, this is the multisig view key
+ *
+ *
+ * multisig key exchange:
+ *
+ * An 'M-of-N' (M <= N; M >= 1; N >= 2) multisignature key is a public key where at least 'M' out of 'N'
+ * possible co-signers must collaborate in order to create a signature.
+ *
+ * Constructing a multisig key involves a series of Diffie-Hellman exchanges between participants.
+ * At the end of key exchange (kex), each participant will hold a number of private keys. Each private
+ * key is shared by a group of (N - M + 1) participants. This way if (N - M) co-signers are missing, every
+ * private key will be held by at least one of the remaining M people.
+ *
+ * Note on MULTISIG_MAX_SIGNERS: During key exchange, participants will have up to '(N - 1) choose (N - M)'
+ * key shares. If N is large, then the max number of key shares (when M = (N-1)/2) can be huge. A limit of N <= 16 was
+ * arbitrarily chosen as a power of 2 that can accomodate the vast majority of practical use-cases. To increase the
+ * limit, FROST-style key aggregation should be used instead (it is more efficient than DH-based key generation
+ * when N - M > 1).
+ *
+ * - Further reading
+ * - MRL-0009: https://www.getmonero.org/resources/research-lab/pubs/MRL-0009.pdf
+ * - MuSig2: https://eprint.iacr.org/2020/1261
+ * - ZtM2: https://web.getmonero.org/library/Zero-to-Monero-2-0-0.pdf Ch. 9, especially Section 9.6.3
+ * - FROST: https://eprint.iacr.org/2018/417
+ */
+ class multisig_account final
+ {
+ public:
+ //member types
+ using kex_origins_map_t = std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>>;
+
+ //constructors
+ // default constructor
+ multisig_account() = default;
+
+ /**
+ * construct from base privkeys
+ *
+ * - prepares a kex msg for the first round of multisig key construction.
+ * - the local account's kex msgs are signed with the base_privkey
+ * - the first kex msg transmits the local base_common_privkey to other participants, for creating the group's common_privkey
+ */
+ multisig_account(const crypto::secret_key &base_privkey,
+ const crypto::secret_key &base_common_privkey);
+
+ // reconstruct from full account details (not recommended)
+ multisig_account(const std::uint32_t threshold,
+ std::vector<crypto::public_key> signers,
+ const crypto::secret_key &base_privkey,
+ const crypto::secret_key &base_common_privkey,
+ std::vector<crypto::secret_key> multisig_privkeys,
+ const crypto::secret_key &common_privkey,
+ const crypto::public_key &multisig_pubkey,
+ const crypto::public_key &common_pubkey,
+ const std::uint32_t kex_rounds_complete,
+ kex_origins_map_t kex_origins_map,
+ std::string next_round_kex_message);
+
+ // copy constructor: default
+
+ //destructor: default
+ ~multisig_account() = default;
+
+ //overloaded operators: none
+
+ //getters
+ // get threshold
+ std::uint32_t get_threshold() const { return m_threshold; }
+ // get signers
+ const std::vector<crypto::public_key>& get_signers() const { return m_signers; }
+ // get base privkey
+ const crypto::secret_key& get_base_privkey() const { return m_base_privkey; }
+ // get base pubkey
+ const crypto::public_key& get_base_pubkey() const { return m_base_pubkey; }
+ // get base common privkey
+ const crypto::secret_key& get_base_common_privkey() const { return m_base_common_privkey; }
+ // get multisig privkeys
+ const std::vector<crypto::secret_key>& get_multisig_privkeys() const { return m_multisig_privkeys; }
+ // get common privkey
+ const crypto::secret_key& get_common_privkey() const { return m_common_privkey; }
+ // get multisig pubkey
+ const crypto::public_key& get_multisig_pubkey() const { return m_multisig_pubkey; }
+ // get common pubkey
+ const crypto::public_key& get_common_pubkey() const { return m_common_pubkey; }
+ // get kex rounds complete
+ std::uint32_t get_kex_rounds_complete() const { return m_kex_rounds_complete; }
+ // get kex keys to origins map
+ const kex_origins_map_t& get_kex_keys_to_origins_map() const { return m_kex_keys_to_origins_map; }
+ // get the kex msg for the next round
+ const std::string& get_next_kex_round_msg() const { return m_next_round_kex_message; }
+
+ //account status functions
+ // account has been intialized, and the account holder can use the 'common' key
+ bool account_is_active() const;
+ // account is ready to make multisig signatures
+ bool multisig_is_ready() const;
+
+ //account helpers
+ private:
+ // set the threshold (M) and signers (N)
+ void set_multisig_config(const std::size_t threshold, std::vector<crypto::public_key> signers);
+
+ //account mutators: key exchange to set up account
+ public:
+ /**
+ * brief: initialize_kex - initialize key exchange
+ * - Updates the account with a 'transactional' model. This account will only be mutated if the update succeeds.
+ */
+ void initialize_kex(const std::uint32_t threshold,
+ std::vector<crypto::public_key> signers,
+ const std::vector<multisig_kex_msg> &expanded_msgs_rnd1);
+ /**
+ * brief: kex_update - Complete the 'in progress' kex round and set the kex message for the next round.
+ * - Updates the account with a 'transactional' model. This account will only be mutated if the update succeeds.
+ * - 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
+ */
+ void kex_update(const std::vector<multisig_kex_msg> &expanded_msgs);
+
+ private:
+ // implementation of kex_update() (non-transactional)
+ void kex_update_impl(const std::vector<multisig_kex_msg> &expanded_msgs);
+ /**
+ * 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
+ * if appropriate.
+ * param: expanded_msgs - set of multisig kex messages to process
+ * param: rounds_required - number of rounds required for kex
+ * outparam: exclude_pubkeys_out - 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.
+ */
+ void initialize_kex_update(const std::vector<multisig_kex_msg> &expanded_msgs,
+ const std::uint32_t rounds_required,
+ std::vector<crypto::public_key> &exclude_pubkeys_out);
+ /**
+ * brief: finalize_kex_update - Helper for kex_update_impl()
+ * param: rounds_required - number of rounds required for kex
+ * param: result_keys_to_origins_map - map between keys for the next round and the other participants they correspond to
+ * inoutparam: temp_account_inout - account to perform last update steps on
+ */
+ void finalize_kex_update(const std::uint32_t rounds_required,
+ kex_origins_map_t result_keys_to_origins_map);
+
+ //member variables
+ private:
+ /// misc. account details
+ // [M] minimum number of co-signers to sign a message with the aggregate pubkey
+ std::uint32_t m_threshold{0};
+ // [N] base keys of all participants in the multisig (used to initiate key exchange, and as participant ids for msg signing)
+ std::vector<crypto::public_key> m_signers;
+
+ /// local participant's personal keys
+ // base keypair of the participant
+ // - used for signing messages, as the initial base key for key exchange, and to make DH derivations for key exchange
+ crypto::secret_key m_base_privkey;
+ crypto::public_key m_base_pubkey;
+ // common base privkey, used to produce the aggregate common privkey
+ crypto::secret_key m_base_common_privkey;
+
+ /// core multisig account keys
+ // the account's private key shares of the multisig address
+ // TODO: also record which other signers have these privkeys, to enable aggregation signing (instead of round-robin)
+ std::vector<crypto::secret_key> m_multisig_privkeys;
+ // a privkey owned by all multisig participants (e.g. a cryptonote view key)
+ crypto::secret_key m_common_privkey;
+ // the multisig public key (e.g. a cryptonote spend key)
+ crypto::public_key m_multisig_pubkey;
+ // the common public key (e.g. a view spend key)
+ crypto::public_key m_common_pubkey;
+
+ /// kex variables
+ // number of key exchange rounds that have been completed (all messages for the round collected and processed)
+ std::uint32_t m_kex_rounds_complete{0};
+ // this account's pubkeys for the in-progress key exchange round
+ // - either DH derivations (intermediate rounds), H(derivation)*G (final round), empty (when kex is done)
+ kex_origins_map_t m_kex_keys_to_origins_map;
+ // the account's message for the in-progress key exchange round
+ std::string m_next_round_kex_message;
+ };
+
+ /**
+ * brief: multisig_kex_rounds_required - The number of key exchange rounds required to produce an M-of-N shared key.
+ * - Key exchange (kex) is a synchronous series of 'rounds'. In an 'active round', participants send messages
+ * to each other.
+ * - A participant considers a round 'complete' when they have collected sufficient messages
+ * from other participants, processed those messages, and updated their multisig account state.
+ * - Typically (as implemented in this module), completing a round coincides with making a message for the next round.
+ * param: num_signers - number of participants in multisig (N)
+ * param: threshold - threshold of multisig (M)
+ * return: number of kex rounds required
+ */
+ std::uint32_t multisig_kex_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
new file mode 100644
index 000000000..0a0ca7bdc
--- /dev/null
+++ b/src/multisig/multisig_account_kex_impl.cpp
@@ -0,0 +1,726 @@
+// 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_account.h"
+
+#include "crypto/crypto.h"
+#include "cryptonote_config.h"
+#include "include_base_utils.h"
+#include "multisig.h"
+#include "multisig_kex_msg.h"
+#include "ringct/rctOps.h"
+
+#include <boost/math/special_functions/binomial.hpp>
+
+#include <algorithm>
+#include <cmath>
+#include <cstdint>
+#include <limits>
+#include <memory>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+
+#undef MONERO_DEFAULT_LOG_CATEGORY
+#define MONERO_DEFAULT_LOG_CATEGORY "multisig"
+
+namespace multisig
+{
+ //----------------------------------------------------------------------------------------------------------------------
+ /**
+ * INTERNAL
+ *
+ * brief: calculate_multisig_keypair_from_derivation - wrapper on calculate_multisig_keypair() for an input public key
+ * Converts an input public key into a crypto private key (type cast, does not change serialization),
+ * then passes it to get_multisig_blinded_secret_key().
+ *
+ * Result:
+ * - privkey = H(derivation)
+ * - pubkey = privkey * G
+ * param: derivation - a curve point
+ * outparam: derived_pubkey_out - public key of the resulting privkey
+ * return: multisig private key
+ */
+ //----------------------------------------------------------------------------------------------------------------------
+ static crypto::secret_key calculate_multisig_keypair_from_derivation(const crypto::public_key_memsafe &derivation,
+ crypto::public_key &derived_pubkey_out)
+ {
+ crypto::secret_key blinded_skey = get_multisig_blinded_secret_key(rct::rct2sk(rct::pk2rct(derivation)));
+ CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(blinded_skey, derived_pubkey_out), "Failed to derive public key");
+
+ return blinded_skey;
+ }
+ //----------------------------------------------------------------------------------------------------------------------
+ /**
+ * INTERNAL
+ *
+ * brief: make_multisig_common_privkey - Create the 'common' multisig privkey, owned by all multisig participants.
+ * - common privkey = H(sorted base common privkeys)
+ * param: participant_base_common_privkeys - Base common privkeys contributed by multisig participants.
+ * outparam: common_privkey_out - result
+ */
+ //----------------------------------------------------------------------------------------------------------------------
+ static void make_multisig_common_privkey(std::vector<crypto::secret_key> participant_base_common_privkeys,
+ crypto::secret_key &common_privkey_out)
+ {
+ // sort the privkeys for consistency
+ //TODO: need a constant-time operator< for sorting secret keys
+ std::sort(participant_base_common_privkeys.begin(), participant_base_common_privkeys.end(),
+ [](const crypto::secret_key &key1, const crypto::secret_key &key2) -> bool
+ {
+ return memcmp(&key1, &key2, sizeof(crypto::secret_key)) < 0;
+ }
+ );
+
+ // privkey = H(sorted ancillary base privkeys)
+ crypto::hash_to_scalar(participant_base_common_privkeys.data(),
+ participant_base_common_privkeys.size()*sizeof(crypto::secret_key),
+ common_privkey_out);
+
+ CHECK_AND_ASSERT_THROW_MES(common_privkey_out != crypto::null_skey, "Unexpected null secret key (danger!).");
+ }
+ //----------------------------------------------------------------------------------------------------------------------
+ /**
+ * INTERNAL
+ *
+ * brief: compute_multisig_aggregation_coefficient - creates aggregation coefficient for a specific public key in a set
+ * of public keys
+ *
+ * WARNING: The coefficient will only be deterministic if...
+ * 1) input keys are pre-sorted
+ * - tested here
+ * 2) input keys are in canonical form (compressed points in the prime-order subgroup of Ed25519)
+ * - untested here for performance
+ * param: sorted_keys - set of component public keys that will be merged into a multisig public spend key
+ * param: aggregation_key - one of the component public keys
+ * return: aggregation coefficient
+ */
+ //----------------------------------------------------------------------------------------------------------------------
+ static rct::key compute_multisig_aggregation_coefficient(const std::vector<crypto::public_key> &sorted_keys,
+ const crypto::public_key &aggregation_key)
+ {
+ CHECK_AND_ASSERT_THROW_MES(std::is_sorted(sorted_keys.begin(), sorted_keys.end()),
+ "Keys for aggregation coefficient aren't sorted.");
+
+ // aggregation key must be in sorted_keys
+ CHECK_AND_ASSERT_THROW_MES(std::find(sorted_keys.begin(), sorted_keys.end(), aggregation_key) != sorted_keys.end(),
+ "Aggregation key expected to be in input keyset.");
+
+ // aggregation coefficient salt
+ rct::key salt = rct::zero();
+ static_assert(sizeof(rct::key) >= sizeof(config::HASH_KEY_MULTISIG_KEY_AGGREGATION), "Hash domain separator is too big.");
+ memcpy(salt.bytes, config::HASH_KEY_MULTISIG_KEY_AGGREGATION, sizeof(config::HASH_KEY_MULTISIG_KEY_AGGREGATION));
+
+ // coeff = H(aggregation_key, sorted_keys, domain-sep)
+ rct::keyV data;
+ data.reserve(sorted_keys.size() + 2);
+ data.push_back(rct::pk2rct(aggregation_key));
+ for (const auto &key : sorted_keys)
+ data.push_back(rct::pk2rct(key));
+ data.push_back(salt);
+
+ // note: coefficient is considered public knowledge, no need to memwipe data
+ return rct::hash_to_scalar(data);
+ }
+ //----------------------------------------------------------------------------------------------------------------------
+ /**
+ * INTERNAL
+ *
+ * brief: generate_multisig_aggregate_key - generates a multisig public spend key via key aggregation
+ * 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)
+ * return: final multisig public spend key for the account
+ */
+ //----------------------------------------------------------------------------------------------------------------------
+ static crypto::public_key generate_multisig_aggregate_key(std::vector<crypto::public_key> final_keys,
+ std::vector<crypto::secret_key> &privkeys_inout)
+ {
+ // collect all public keys that will go into the spend key (these don't need to be memsafe)
+ final_keys.reserve(final_keys.size() + privkeys_inout.size());
+
+ // 1. convert local multisig private keys to pub keys
+ // 2. insert to final keyset if not there yet
+ // 3. save the corresponding index of input priv key set for later reference
+ std::unordered_map<crypto::public_key, std::size_t> own_keys_mapping;
+
+ 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");
+
+ own_keys_mapping[pubkey] = multisig_keys_index;
+
+ final_keys.push_back(pubkey);
+ }
+
+ // sort input final keys for computing aggregation coefficients (lowest to highest)
+ // note: input should be sanitized (no duplicates)
+ std::sort(final_keys.begin(), final_keys.end());
+ CHECK_AND_ASSERT_THROW_MES(std::adjacent_find(final_keys.begin(), final_keys.end()) == final_keys.end(),
+ "Unexpected duplicate found in input list.");
+
+ // key aggregation
+ rct::key aggregate_key = rct::identity();
+
+ for (const crypto::public_key &key : final_keys)
+ {
+ // get aggregation coefficient
+ rct::key coeff = compute_multisig_aggregation_coefficient(final_keys, key);
+
+ // convert private key if possible
+ // note: retain original priv key index in input list, in case order matters upstream
+ auto found_key = own_keys_mapping.find(key);
+ if (found_key != own_keys_mapping.end())
+ {
+ // k_agg = coeff*k_base
+ sc_mul((unsigned char*)&(privkeys_inout[found_key->second]),
+ coeff.bytes,
+ (const unsigned char*)&(privkeys_inout[found_key->second]));
+
+ CHECK_AND_ASSERT_THROW_MES(privkeys_inout[found_key->second] != crypto::null_skey,
+ "Multisig privkey with aggregation coefficient unexpectedly null.");
+ }
+
+ // convert public key (pre-merge operation)
+ // K_agg = coeff*K_base
+ rct::key converted_pubkey = rct::scalarmultKey(rct::pk2rct(key), coeff);
+
+ // build aggregate key (merge operation)
+ rct::addKeys(aggregate_key, aggregate_key, converted_pubkey);
+ }
+
+ return rct::rct2pk(aggregate_key);
+ }
+ //----------------------------------------------------------------------------------------------------------------------
+ /**
+ * INTERNAL
+ *
+ * brief: multisig_kex_make_next_msg - Construct a kex msg for any round > 1 of multisig key construction.
+ * - Involves DH exchanges with pubkeys provided by other participants.
+ * - Conserves mapping [pubkey -> DH derivation] : [origin keys of participants that share this secret with you].
+ * param: base_privkey - account's base private key, for performing DH exchanges and signing messages
+ * param: round - the round of the message that should be produced
+ * param: threshold - threshold for multisig (M in M-of-N)
+ * param: num_signers - number of participants in multisig (N)
+ * param: pubkey_origins_map - map between pubkeys to produce DH derivations with and identity keys of
+ * participants who will share each derivation with you
+ * outparam: derivation_origins_map_out - map between DH derivations (shared secrets) and identity keys
+ * - If msg is not for the last round, then these derivations are also stored in the output message
+ * so they can be sent to other participants, who will make more DH derivations for the next kex round.
+ * - If msg is for the last round, then these derivations won't be sent to other participants.
+ * Instead, they are converted to share secrets (i.e. s = H(derivation)) and multiplied by G.
+ * The keys s*G are sent to other participants in the message, so they can be used to produce the final
+ * multisig key via generate_multisig_spend_public_key().
+ * - The values s are the local account's shares of the final multisig key's private key. The caller can
+ * compute those values with calculate_multisig_keypair_from_derivation() (or compute them directly).
+ * return: multisig kex message for the specified round
+ */
+ //----------------------------------------------------------------------------------------------------------------------
+ static multisig_kex_msg multisig_kex_make_next_msg(const crypto::secret_key &base_privkey,
+ const std::uint32_t round,
+ const std::uint32_t threshold,
+ const std::uint32_t num_signers,
+ const std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> &pubkey_origins_map,
+ std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> &derivation_origins_map_out)
+ {
+ CHECK_AND_ASSERT_THROW_MES(num_signers > 1, "Must be at least one other multisig signer.");
+ CHECK_AND_ASSERT_THROW_MES(num_signers <= config::MULTISIG_MAX_SIGNERS,
+ "Too many multisig signers specified (limit = 16 to prevent dangerous combinatorial explosion during key exchange).");
+ CHECK_AND_ASSERT_THROW_MES(num_signers >= threshold,
+ "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 > 1, "Round for next msg must be > 1.");
+ CHECK_AND_ASSERT_THROW_MES(round <= multisig_kex_rounds_required(num_signers, threshold),
+ "Trying to make key exchange message for an invalid round.");
+
+ // make shared secrets with input pubkeys
+ std::vector<crypto::public_key> msg_pubkeys;
+ msg_pubkeys.reserve(pubkey_origins_map.size());
+ derivation_origins_map_out.clear();
+
+ for (const auto &pubkey_and_origins : pubkey_origins_map)
+ {
+ // D = 8 * k_base * K_pubkey
+ // note: must be mul8 (cofactor), otherwise it is possible to leak to a malicious participant if the local
+ // base_privkey is a multiple of 8 or not
+ // note2: avoid making temporaries that won't be memwiped
+ rct::key derivation_rct;
+ auto a_wiper = epee::misc_utils::create_scope_leave_handler([&]{
+ memwipe(&derivation_rct, sizeof(rct::key));
+ });
+
+ rct::scalarmultKey(derivation_rct, rct::pk2rct(pubkey_and_origins.first), rct::sk2rct(base_privkey));
+ rct::scalarmultKey(derivation_rct, derivation_rct, rct::EIGHT);
+
+ crypto::public_key_memsafe derivation{rct::rct2pk(derivation_rct)};
+
+ // retain mapping between pubkey's origins and the DH derivation
+ // note: if msg for last round, then caller must know how to handle these derivations properly
+ derivation_origins_map_out[derivation] = pubkey_and_origins.second;
+
+ // if the last round, convert derivations to public keys for the output message
+ if (round == multisig_kex_rounds_required(num_signers, threshold))
+ {
+ // derived_pubkey = H(derivation)*G
+ crypto::public_key derived_pubkey;
+ calculate_multisig_keypair_from_derivation(derivation, derived_pubkey);
+ msg_pubkeys.push_back(derived_pubkey);
+ }
+ // otherwise, put derivations in message directly, so other signers can in turn create derivations (shared secrets)
+ // with them for the next round
+ else
+ msg_pubkeys.push_back(derivation);
+ }
+
+ return multisig_kex_msg{round, base_privkey, std::move(msg_pubkeys)};
+ }
+ //----------------------------------------------------------------------------------------------------------------------
+ /**
+ * 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',
+ * constructs map of pubkey:origins.
+ * - Requires that all input msgs have the same round number.
+ *
+ * origins = all the signing pubkeys that recommended a given pubkey found in input msgs
+ *
+ * - 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,
+ const std::vector<crypto::public_key> &exclude_pubkeys,
+ std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> &sanitized_pubkeys_out)
+ {
+ CHECK_AND_ASSERT_THROW_MES(expanded_msgs.size() > 0, "At least one input message expected.");
+
+ std::uint32_t round = expanded_msgs[0].get_round();
+ sanitized_pubkeys_out.clear();
+
+ // get all pubkeys from input messages, add them to pubkey:origins map
+ // - origins = all the signing pubkeys that recommended a given msg pubkey
+ for (const auto &expanded_msg : expanded_msgs)
+ {
+ CHECK_AND_ASSERT_THROW_MES(expanded_msg.get_round() == round, "All messages must have the same kex round number.");
+
+ // 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)
+ {
+ // note: ignores duplicates
+ sanitized_pubkeys_out[expanded_msg.get_signing_pubkey()].insert(expanded_msg.get_signing_pubkey());
+ }
+ // in other rounds, only the msg pubkeys are treated as msg pubkeys
+ else
+ {
+ // 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;
+
+ // note: ignores duplicates
+ sanitized_pubkeys_out[pubkey].insert(expanded_msg.get_signing_pubkey());
+ }
+ }
+ }
+
+ return round;
+ }
+ //----------------------------------------------------------------------------------------------------------------------
+ /**
+ * 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: 'signers', 'exclude_pubkeys'.
+ * - Requires each input pubkey be recommended by 'num_recommendations = expected_round' msg signers.
+ * - For a final multisig key to be truly 'M-of-N', each of the the private key's components must be
+ * shared by (N - M + 1) signers.
+ * - Requires that msgs are signed by only keys in 'signers'.
+ * - Requires that each key in 'signers' recommends [num_signers - 2 CHOOSE (expected_round - 1)] pubkeys.
+ * - These should be derivations each signer recommends for round 'expected_round', excluding derivations shared
+ * with the local account.
+ * - Requires that 'exclude_pubkeys' has [num_signers - 1 CHOOSE (expected_round - 1)] pubkeys.
+ * - These should be derivations the local account has corresponding to round 'expected_round'.
+ * param: base_privkey - multisig account's base private key
+ * param: expected_round - expected kex round of input messages
+ * param: threshold - threshold for multisig (M in M-of-N)
+ * 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'
+ * return: fully sanitized and validated pubkey:origins map for building the account's next kex round message
+ */
+ //----------------------------------------------------------------------------------------------------------------------
+ static std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> evaluate_multisig_kex_round_msgs(
+ const crypto::public_key &base_pubkey,
+ const std::uint32_t expected_round,
+ const std::uint32_t threshold,
+ const std::vector<crypto::public_key> &signers,
+ const std::vector<multisig_kex_msg> &expanded_msgs,
+ const std::vector<crypto::public_key> &exclude_pubkeys)
+ {
+ CHECK_AND_ASSERT_THROW_MES(signers.size() > 1, "Must be at least one other multisig signer.");
+ CHECK_AND_ASSERT_THROW_MES(signers.size() <= config::MULTISIG_MAX_SIGNERS,
+ "Too many multisig signers specified (limit = 16 to prevent dangerous combinatorial explosion during key exchange).");
+ CHECK_AND_ASSERT_THROW_MES(signers.size() >= threshold, "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(expected_round > 0, "Expected round must be > 0.");
+ CHECK_AND_ASSERT_THROW_MES(expected_round <= multisig_kex_rounds_required(signers.size(), threshold),
+ "Expecting key exchange messages for an invalid round.");
+
+ std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> pubkey_origins_map;
+
+ // leave early in the last round of 1-of-N, where all signers share a key so the local signer doesn't care about
+ // recommendations from other signers
+ if (threshold == 1 && expected_round == multisig_kex_rounds_required(signers.size(), threshold))
+ return pubkey_origins_map;
+
+ // exclude_pubkeys should all be unique
+ for (auto it = exclude_pubkeys.begin(); it != exclude_pubkeys.end(); ++it)
+ {
+ CHECK_AND_ASSERT_THROW_MES(std::find(exclude_pubkeys.begin(), it, *it) == it,
+ "Found duplicate pubkeys for exclusion unexpectedly.");
+ }
+
+ // sanitize input messages
+ std::uint32_t round = multisig_kex_msgs_sanitize_pubkeys(base_pubkey, 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 << "]");
+
+ // evaluate pubkeys collected
+ std::unordered_map<crypto::public_key, std::unordered_set<crypto::public_key>> origin_pubkeys_map;
+
+ // 1. each pubkey should be recommended by a precise number of signers
+ 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,
+ "A pubkey recommended by multisig kex messages had an unexpected number of recommendations.");
+
+ // map (sanitized) pubkeys back to origins
+ for (const auto &origin : pubkey_and_origins.second)
+ origin_pubkeys_map[origin].insert(pubkey_and_origins.first);
+ }
+
+ // 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.");
+
+ // 3. each origin should recommend a precise number of pubkeys
+
+ // TODO: move to a 'math' library, with unit tests
+ auto n_choose_k_f =
+ [](const std::uint32_t n, const std::uint32_t k) -> std::uint32_t
+ {
+ static_assert(std::numeric_limits<std::int32_t>::digits <= std::numeric_limits<double>::digits,
+ "n_choose_k requires no rounding issues when converting between int32 <-> double.");
+
+ if (n < k)
+ return 0;
+
+ double fp_result = boost::math::binomial_coefficient<double>(n, k);
+
+ if (fp_result < 0)
+ return 0;
+
+ if (fp_result > std::numeric_limits<std::int32_t>::max()) // note: std::round() returns std::int32_t
+ return 0;
+
+ return static_cast<std::uint32_t>(std::round(fp_result));
+ };
+
+ // 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
+ // have boosted the local signer (implying they didn't have access to the local signer's previous round msg).
+ std::uint32_t expected_recommendations_others = n_choose_k_f(signers.size() - 2, round - 1);
+
+ // local: (N - 1) choose (msg_round_num - 1)
+ 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
+ CHECK_AND_ASSERT_THROW_MES(expected_recommendations_self > 0 && expected_recommendations_others > 0,
+ "Bad num signers or round num (possibly numerical limits exceeded).");
+
+ // check that local account recommends expected number of keys
+ CHECK_AND_ASSERT_THROW_MES(exclude_pubkeys.size() == expected_recommendations_self,
+ "Local account did not recommend expected number of multisig keys.");
+
+ // check that other signers recommend expected number of keys
+ 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.");
+
+ // 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(),
+ "Multisig kex message with unexpected signer encountered.");
+ }
+
+ // note: above tests implicitly detect if the total number of recommended keys is correct or not
+ return pubkey_origins_map;
+ }
+ //----------------------------------------------------------------------------------------------------------------------
+ /**
+ * INTERNAL
+ *
+ * brief: multisig_kex_process_round - Process kex messages for the active kex round.
+ * - A wrapper around evaluate_multisig_kex_round_msgs() -> multisig_kex_make_next_msg().
+ * - In other words, evaluate the input messages and try to make a message for the next round.
+ * - Note: Must be called on the final round's msgs to evaluate the final key components
+ * recommended by other participants.
+ * param: base_privkey - multisig account's base private key
+ * param: current_round - round of kex the input messages should be designed for
+ * param: threshold - threshold for multisig (M in M-of-N)
+ * param: signers - expected participants in multisig kex
+ * 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.
+ * 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.
+ * - See multisig_kex_make_next_msg() for an explanation.
+ * return: multisig kex message for next round, or empty message if 'current_round' is the final round
+ */
+ //----------------------------------------------------------------------------------------------------------------------
+ static multisig_kex_msg multisig_kex_process_round(const crypto::secret_key &base_privkey,
+ const crypto::public_key &base_pubkey,
+ const std::uint32_t current_round,
+ const std::uint32_t threshold,
+ const std::vector<crypto::public_key> &signers,
+ const std::vector<multisig_kex_msg> &expanded_msgs,
+ const std::vector<crypto::public_key> &exclude_pubkeys,
+ std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> &keys_to_origins_map_out)
+ {
+ // evaluate messages
+ std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> evaluated_pubkeys =
+ evaluate_multisig_kex_round_msgs(base_pubkey, current_round, threshold, signers, expanded_msgs, exclude_pubkeys);
+
+ // produce message for next round (if there is one)
+ if (current_round < multisig_kex_rounds_required(signers.size(), threshold))
+ {
+ return multisig_kex_make_next_msg(base_privkey,
+ current_round + 1,
+ threshold,
+ signers.size(),
+ evaluated_pubkeys,
+ keys_to_origins_map_out);
+ }
+ else
+ {
+ // no more rounds, so collect the key shares recommended by other signers for the final aggregate key
+ keys_to_origins_map_out.clear();
+ keys_to_origins_map_out = std::move(evaluated_pubkeys);
+
+ return multisig_kex_msg{};
+ }
+ }
+ //----------------------------------------------------------------------------------------------------------------------
+ // multisig_account: INTERNAL
+ //----------------------------------------------------------------------------------------------------------------------
+ void multisig_account::initialize_kex_update(const std::vector<multisig_kex_msg> &expanded_msgs,
+ const std::uint32_t rounds_required,
+ std::vector<crypto::public_key> &exclude_pubkeys_out)
+ {
+ if (m_kex_rounds_complete == 0)
+ {
+ // the first round of kex msgs will contain each participant's base pubkeys and ancillary privkeys
+
+ // collect participants' base common privkey shares
+ // note: duplicate privkeys are acceptable, and duplicates due to duplicate signers
+ // will be blocked by duplicate-signer errors after this function is called
+ std::vector<crypto::secret_key> participant_base_common_privkeys;
+ participant_base_common_privkeys.reserve(expanded_msgs.size() + 1);
+
+ // add local ancillary base privkey
+ participant_base_common_privkeys.emplace_back(m_base_common_privkey);
+
+ // add other signers' base common privkeys
+ for (const auto &expanded_msg : expanded_msgs)
+ {
+ if (expanded_msg.get_signing_pubkey() != m_base_pubkey)
+ {
+ participant_base_common_privkeys.emplace_back(expanded_msg.get_msg_privkey());
+ }
+ }
+
+ // make common privkey
+ make_multisig_common_privkey(std::move(participant_base_common_privkeys), m_common_privkey);
+
+ // set common pubkey
+ CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(m_common_privkey, m_common_pubkey),
+ "Failed to derive public key");
+
+ // if N-of-N, then the base privkey will be used directly to make the account's share of the final key
+ if (rounds_required == 1)
+ {
+ m_multisig_privkeys.clear();
+ m_multisig_privkeys.emplace_back(m_base_privkey);
+ }
+
+ // exclude all keys the local account recommends
+ // - in the first round, only the local pubkey is recommended by the local signer
+ exclude_pubkeys_out.emplace_back(m_base_pubkey);
+ }
+ else
+ {
+ // in other rounds, kex msgs will contain participants' shared keys
+
+ // ignore shared keys the account helped create for this round
+ for (const auto &shared_key_with_origins : m_kex_keys_to_origins_map)
+ {
+ exclude_pubkeys_out.emplace_back(shared_key_with_origins.first);
+ }
+ }
+ }
+ //----------------------------------------------------------------------------------------------------------------------
+ // multisig_account: INTERNAL
+ //----------------------------------------------------------------------------------------------------------------------
+ void multisig_account::finalize_kex_update(const std::uint32_t rounds_required,
+ std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> result_keys_to_origins_map)
+ {
+ // prepare for next round (or complete the multisig account fully)
+ if (rounds_required == m_kex_rounds_complete + 1)
+ {
+ // finished (have set of msgs to complete address)
+
+ // when 'completing the final round', result keys are other signers' shares of the final key
+ std::vector<crypto::public_key> result_keys;
+ result_keys.reserve(result_keys_to_origins_map.size());
+
+ for (const auto &result_key_and_origins : result_keys_to_origins_map)
+ {
+ result_keys.emplace_back(result_key_and_origins.first);
+ }
+
+ // compute final aggregate key, update local multisig privkeys with aggregation coefficients applied
+ m_multisig_pubkey = generate_multisig_aggregate_key(std::move(result_keys), m_multisig_privkeys);
+
+ // no longer need the account's pubkeys saved for this round (they were only used to build exclude_pubkeys)
+ // TODO: record [pre-aggregation pubkeys : origins] map for aggregation-style signing
+ m_kex_keys_to_origins_map.clear();
+ }
+ else if (rounds_required == m_kex_rounds_complete + 2)
+ {
+ // one more round (must send/receive one more set of kex msgs)
+ // - at this point, have local signer's pre-aggregation private key shares of the final address
+
+ // result keys are the local signer's DH derivations for the next round
+
+ // derivations are shared secrets between each group of N - M + 1 signers of which the local account is a member
+ // - convert them to private keys: multisig_key = H(derivation)
+ // - note: shared key = multisig_key[i]*G is recorded in the kex msg for sending to other participants
+ // instead of the original 'derivation' value (which MUST be kept secret!)
+ m_multisig_privkeys.clear();
+ m_multisig_privkeys.reserve(result_keys_to_origins_map.size());
+
+ m_kex_keys_to_origins_map.clear();
+
+ for (const auto &derivation_and_origins : result_keys_to_origins_map)
+ {
+ // multisig_privkey = H(derivation)
+ // derived pubkey = multisig_key * G
+ crypto::public_key_memsafe derived_pubkey;
+ m_multisig_privkeys.push_back(
+ calculate_multisig_keypair_from_derivation(derivation_and_origins.first, derived_pubkey));
+
+ // save the account's kex key mappings for this round [derived pubkey : other signers who will have the same key]
+ m_kex_keys_to_origins_map[derived_pubkey] = std::move(derivation_and_origins.second);
+ }
+ }
+ else
+ {
+ // next round is an 'intermediate' key exchange round, so there is nothing special to do here
+
+ // save the account's kex keys for this round [DH derivation : other signers who will have the same derivation]
+ m_kex_keys_to_origins_map = std::move(result_keys_to_origins_map);
+ }
+
+ // a full set of msgs has been collected and processed, so the 'round is complete'
+ ++m_kex_rounds_complete;
+ }
+ //----------------------------------------------------------------------------------------------------------------------
+ // multisig_account: INTERNAL
+ //----------------------------------------------------------------------------------------------------------------------
+ void multisig_account::kex_update_impl(const std::vector<multisig_kex_msg> &expanded_msgs)
+ {
+ CHECK_AND_ASSERT_THROW_MES(expanded_msgs.size() > 0, "No key exchange messages passed in.");
+
+ const std::uint32_t rounds_required = multisig_kex_rounds_required(m_signers.size(), m_threshold);
+ CHECK_AND_ASSERT_THROW_MES(rounds_required > 0, "Multisig kex rounds required unexpectedly 0.");
+
+ // initialize account update
+ std::vector<crypto::public_key> exclude_pubkeys;
+ initialize_kex_update(expanded_msgs, rounds_required, exclude_pubkeys);
+
+ // evaluate messages and get this account's kex msg for the next round
+ std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> result_keys_to_origins_map;
+
+ m_next_round_kex_message = multisig_kex_process_round(
+ m_base_privkey,
+ m_base_pubkey,
+ m_kex_rounds_complete + 1,
+ m_threshold,
+ m_signers,
+ expanded_msgs,
+ exclude_pubkeys,
+ result_keys_to_origins_map).get_msg();
+
+ // finish account update
+ finalize_kex_update(rounds_required, std::move(result_keys_to_origins_map));
+ }
+ //----------------------------------------------------------------------------------------------------------------------
+} //namespace multisig
diff --git a/src/multisig/multisig_kex_msg.cpp b/src/multisig/multisig_kex_msg.cpp
new file mode 100644
index 000000000..2bbceb19d
--- /dev/null
+++ b/src/multisig/multisig_kex_msg.cpp
@@ -0,0 +1,290 @@
+// 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_kex_msg.h"
+#include "multisig_kex_msg_serialization.h"
+
+#include "common/base58.h"
+#include "crypto/crypto.h"
+extern "C"
+{
+#include "crypto/crypto-ops.h"
+}
+#include "cryptonote_basic/cryptonote_format_utils.h"
+#include "include_base_utils.h"
+#include "ringct/rctOps.h"
+#include "serialization/binary_archive.h"
+#include "serialization/serialization.h"
+
+#include <boost/utility/string_ref.hpp>
+
+#include <sstream>
+#include <utility>
+#include <vector>
+
+
+#undef MONERO_DEFAULT_LOG_CATEGORY
+#define MONERO_DEFAULT_LOG_CATEGORY "multisig"
+
+const boost::string_ref MULTISIG_KEX_V1_MAGIC{"MultisigV1"};
+const boost::string_ref MULTISIG_KEX_MSG_V1_MAGIC{"MultisigxV1"};
+const boost::string_ref MULTISIG_KEX_MSG_V2_MAGIC_1{"MultisigxV2R1"}; //round 1
+const boost::string_ref MULTISIG_KEX_MSG_V2_MAGIC_N{"MultisigxV2Rn"}; //round n > 1
+
+namespace multisig
+{
+ //----------------------------------------------------------------------------------------------------------------------
+ // multisig_kex_msg: EXTERNAL
+ //----------------------------------------------------------------------------------------------------------------------
+ multisig_kex_msg::multisig_kex_msg(const std::uint32_t round,
+ const crypto::secret_key &signing_privkey,
+ std::vector<crypto::public_key> msg_pubkeys,
+ const crypto::secret_key &msg_privkey) :
+ m_kex_round{round}
+ {
+ CHECK_AND_ASSERT_THROW_MES(round > 0, "Kex round must be > 0.");
+ CHECK_AND_ASSERT_THROW_MES(sc_check((const unsigned char*)&signing_privkey) == 0 &&
+ signing_privkey != crypto::null_skey, "Invalid msg signing key.");
+
+ if (round == 1)
+ {
+ CHECK_AND_ASSERT_THROW_MES(sc_check((const unsigned char*)&msg_privkey) == 0 &&
+ msg_privkey != crypto::null_skey, "Invalid msg privkey.");
+
+ m_msg_privkey = msg_privkey;
+ }
+ else
+ {
+ for (const auto &pubkey : msg_pubkeys)
+ {
+ CHECK_AND_ASSERT_THROW_MES(pubkey != crypto::null_pkey && pubkey != rct::rct2pk(rct::identity()),
+ "Pubkey for message was invalid.");
+ CHECK_AND_ASSERT_THROW_MES((rct::scalarmultKey(rct::pk2rct(pubkey), rct::curveOrder()) == rct::identity()),
+ "Pubkey for message was not in prime subgroup.");
+ }
+
+ m_msg_pubkeys = std::move(msg_pubkeys);
+ }
+ CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(signing_privkey, m_signing_pubkey),
+ "Failed to derive public key");
+
+ // sets message and signing pub key
+ construct_msg(signing_privkey);
+ }
+ //----------------------------------------------------------------------------------------------------------------------
+ // multisig_kex_msg: EXTERNAL
+ //----------------------------------------------------------------------------------------------------------------------
+ multisig_kex_msg::multisig_kex_msg(std::string msg) : m_msg{std::move(msg)}
+ {
+ parse_and_validate_msg();
+ }
+ //----------------------------------------------------------------------------------------------------------------------
+ // multisig_kex_msg: INTERNAL
+ //----------------------------------------------------------------------------------------------------------------------
+ crypto::hash multisig_kex_msg::get_msg_to_sign() const
+ {
+ ////
+ // msg_content = kex_round | signing_pubkey | expand(msg_pubkeys) | OPTIONAL msg_privkey
+ // sign_msg = versioning-domain-sep | msg_content
+ ///
+
+ std::string data;
+ CHECK_AND_ASSERT_THROW_MES(MULTISIG_KEX_MSG_V2_MAGIC_1.size() == MULTISIG_KEX_MSG_V2_MAGIC_N.size(),
+ "Multisig kex msg magic inconsistency.");
+ data.reserve(MULTISIG_KEX_MSG_V2_MAGIC_1.size() + 4 + 32*(1 + (m_kex_round == 1 ? 1 : 0) + m_msg_pubkeys.size()));
+
+ // versioning domain-sep
+ if (m_kex_round == 1)
+ data.append(MULTISIG_KEX_MSG_V2_MAGIC_1.data(), MULTISIG_KEX_MSG_V2_MAGIC_1.size());
+ else
+ data.append(MULTISIG_KEX_MSG_V2_MAGIC_N.data(), MULTISIG_KEX_MSG_V2_MAGIC_N.size());
+
+ // kex_round as little-endian bytes
+ for (std::size_t i{0}; i < 4; ++i)
+ {
+ data += static_cast<char>(m_kex_round >> i*8);
+ }
+
+ // signing pubkey
+ data.append((const char *)&m_signing_pubkey, sizeof(crypto::public_key));
+
+ // add msg privkey if kex_round == 1
+ if (m_kex_round == 1)
+ data.append((const char *)&m_msg_privkey, sizeof(crypto::secret_key));
+ else
+ {
+ // only add pubkeys if not round 1
+
+ // msg pubkeys
+ for (const auto &key : m_msg_pubkeys)
+ data.append((const char *)&key, sizeof(crypto::public_key));
+ }
+
+ // message to sign
+ crypto::hash hash;
+ crypto::cn_fast_hash(data.data(), data.size(), hash);
+
+ return hash;
+ }
+ //----------------------------------------------------------------------------------------------------------------------
+ // multisig_kex_msg: INTERNAL
+ //----------------------------------------------------------------------------------------------------------------------
+ void multisig_kex_msg::construct_msg(const crypto::secret_key &signing_privkey)
+ {
+ ////
+ // msg_content = kex_round | signing_pubkey | expand(msg_pubkeys) | OPTIONAL msg_privkey
+ // sign_msg = versioning-domain-sep | msg_content
+ // msg = versioning-domain-sep | serialize(msg_content | crypto_sig[signing_privkey](sign_msg))
+ ///
+
+ // sign the message
+ crypto::signature msg_signature;
+ crypto::hash msg_to_sign{get_msg_to_sign()};
+ crypto::generate_signature(msg_to_sign, m_signing_pubkey, signing_privkey, msg_signature);
+
+ // assemble the message
+ m_msg.clear();
+
+ std::stringstream serialized_msg_ss;
+ binary_archive<true> b_archive(serialized_msg_ss);
+
+ if (m_kex_round == 1)
+ {
+ m_msg.append(MULTISIG_KEX_MSG_V2_MAGIC_1.data(), MULTISIG_KEX_MSG_V2_MAGIC_1.size());
+
+ multisig_kex_msg_serializable_round1 msg_serializable;
+ msg_serializable.msg_privkey = m_msg_privkey;
+ msg_serializable.signing_pubkey = m_signing_pubkey;
+ msg_serializable.signature = msg_signature;
+
+ CHECK_AND_ASSERT_THROW_MES(::serialization::serialize(b_archive, msg_serializable),
+ "Failed to serialize multisig kex msg");
+ }
+ else
+ {
+ m_msg.append(MULTISIG_KEX_MSG_V2_MAGIC_N.data(), MULTISIG_KEX_MSG_V2_MAGIC_N.size());
+
+ multisig_kex_msg_serializable_general msg_serializable;
+ msg_serializable.kex_round = m_kex_round;
+ msg_serializable.msg_pubkeys = m_msg_pubkeys;
+ msg_serializable.signing_pubkey = m_signing_pubkey;
+ msg_serializable.signature = msg_signature;
+
+ CHECK_AND_ASSERT_THROW_MES(::serialization::serialize(b_archive, msg_serializable),
+ "Failed to serialize multisig kex msg");
+ }
+
+ m_msg.append(tools::base58::encode(serialized_msg_ss.str()));
+ }
+ //----------------------------------------------------------------------------------------------------------------------
+ // multisig_kex_msg: INTERNAL
+ //----------------------------------------------------------------------------------------------------------------------
+ void multisig_kex_msg::parse_and_validate_msg()
+ {
+ // check message type
+ CHECK_AND_ASSERT_THROW_MES(m_msg.size() > 0, "Kex message unexpectedly empty.");
+ CHECK_AND_ASSERT_THROW_MES(m_msg.substr(0, MULTISIG_KEX_V1_MAGIC.size()) != MULTISIG_KEX_V1_MAGIC,
+ "V1 multisig kex messages are deprecated (unsafe).");
+ CHECK_AND_ASSERT_THROW_MES(m_msg.substr(0, MULTISIG_KEX_MSG_V1_MAGIC.size()) != MULTISIG_KEX_MSG_V1_MAGIC,
+ "V1 multisig kex messages are deprecated (unsafe).");
+
+ // deserialize the message
+ std::string msg_no_magic;
+ CHECK_AND_ASSERT_THROW_MES(MULTISIG_KEX_MSG_V2_MAGIC_1.size() == MULTISIG_KEX_MSG_V2_MAGIC_N.size(),
+ "Multisig kex msg magic inconsistency.");
+ CHECK_AND_ASSERT_THROW_MES(tools::base58::decode(m_msg.substr(MULTISIG_KEX_MSG_V2_MAGIC_1.size()), msg_no_magic),
+ "Multisig kex msg decoding error.");
+ binary_archive<false> b_archive{epee::strspan<std::uint8_t>(msg_no_magic)};
+ crypto::signature msg_signature;
+
+ if (m_msg.substr(0, MULTISIG_KEX_MSG_V2_MAGIC_1.size()) == MULTISIG_KEX_MSG_V2_MAGIC_1)
+ {
+ // try round 1 message
+ multisig_kex_msg_serializable_round1 kex_msg_rnd1;
+
+ if (::serialization::serialize(b_archive, kex_msg_rnd1))
+ {
+ // in round 1 the message stores a private ancillary key component for the multisig account
+ // that will be shared by all participants (e.g. a shared private view key)
+ m_kex_round = 1;
+ m_msg_privkey = kex_msg_rnd1.msg_privkey;
+ m_signing_pubkey = kex_msg_rnd1.signing_pubkey;
+ msg_signature = kex_msg_rnd1.signature;
+ }
+ else
+ {
+ CHECK_AND_ASSERT_THROW_MES(false, "Deserializing kex msg failed.");
+ }
+ }
+ else if (m_msg.substr(0, MULTISIG_KEX_MSG_V2_MAGIC_N.size()) == MULTISIG_KEX_MSG_V2_MAGIC_N)
+ {
+ // try general message
+ multisig_kex_msg_serializable_general kex_msg_general;
+
+ if (::serialization::serialize(b_archive, kex_msg_general))
+ {
+ m_kex_round = kex_msg_general.kex_round;
+ m_msg_privkey = crypto::null_skey;
+ m_msg_pubkeys = std::move(kex_msg_general.msg_pubkeys);
+ m_signing_pubkey = kex_msg_general.signing_pubkey;
+ msg_signature = kex_msg_general.signature;
+
+ CHECK_AND_ASSERT_THROW_MES(m_kex_round > 1, "Invalid kex message round (must be > 1 for the general msg type).");
+ }
+ else
+ {
+ CHECK_AND_ASSERT_THROW_MES(false, "Deserializing kex msg failed.");
+ }
+ }
+ else
+ {
+ // unknown message type
+ CHECK_AND_ASSERT_THROW_MES(false, "Only v2 multisig kex messages are supported.");
+ }
+
+ // checks
+ for (const auto &pubkey: m_msg_pubkeys)
+ {
+ CHECK_AND_ASSERT_THROW_MES(pubkey != crypto::null_pkey && pubkey != rct::rct2pk(rct::identity()),
+ "Pubkey from message was invalid.");
+ CHECK_AND_ASSERT_THROW_MES(rct::isInMainSubgroup(rct::pk2rct(pubkey)),
+ "Pubkey from message was not in prime subgroup.");
+ }
+
+ CHECK_AND_ASSERT_THROW_MES(m_signing_pubkey != crypto::null_pkey && m_signing_pubkey != rct::rct2pk(rct::identity()),
+ "Message signing key was invalid.");
+ CHECK_AND_ASSERT_THROW_MES(rct::isInMainSubgroup(rct::pk2rct(m_signing_pubkey)),
+ "Message signing key was not in prime subgroup.");
+
+ // validate signature
+ crypto::hash signed_msg{get_msg_to_sign()};
+ CHECK_AND_ASSERT_THROW_MES(crypto::check_signature(signed_msg, m_signing_pubkey, msg_signature),
+ "Multisig kex msg signature invalid.");
+ }
+ //----------------------------------------------------------------------------------------------------------------------
+} //namespace multisig
diff --git a/src/multisig/multisig_kex_msg.h b/src/multisig/multisig_kex_msg.h
new file mode 100644
index 000000000..23e3042f2
--- /dev/null
+++ b/src/multisig/multisig_kex_msg.h
@@ -0,0 +1,109 @@
+// 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 "crypto/crypto.h"
+
+#include <cstdint>
+#include <vector>
+
+
+namespace multisig
+{
+ ////
+ // multisig key exchange message
+ // - can parse and validate an input message
+ // - can construct and sign a new message
+ //
+ // msg_content = kex_round | signing_pubkey | expand(msg_pubkeys) | OPTIONAL msg_privkey
+ // msg_to_sign = versioning-domain-sep | msg_content
+ // msg = versioning-domain-sep | b58(msg_content | crypto_sig[signing_privkey](msg_to_sign))
+ //
+ // note: round 1 messages will contain a private key (e.g. for the aggregate multisig private view key)
+ ///
+ class multisig_kex_msg final
+ {
+ //member types: none
+
+ //constructors
+ public:
+ // default constructor
+ multisig_kex_msg() = default;
+
+ // construct from info
+ multisig_kex_msg(const std::uint32_t round,
+ const crypto::secret_key &signing_privkey,
+ std::vector<crypto::public_key> msg_pubkeys,
+ const crypto::secret_key &msg_privkey = crypto::null_skey);
+
+ // construct from string
+ multisig_kex_msg(std::string msg);
+
+ // copy constructor: default
+
+ //destructor: default
+ ~multisig_kex_msg() = default;
+
+ //overloaded operators: none
+
+ //member functions
+ // get msg string
+ const std::string& get_msg() const { return m_msg; }
+ // get kex round
+ std::uint32_t get_round() const { return m_kex_round; }
+ // get msg pubkeys
+ const std::vector<crypto::public_key>& get_msg_pubkeys() const { return m_msg_pubkeys; }
+ // get msg privkey
+ const crypto::secret_key& get_msg_privkey() const { return m_msg_privkey; }
+ // get msg signing pubkey
+ const crypto::public_key& get_signing_pubkey() const { return m_signing_pubkey; }
+
+ private:
+ // msg_to_sign = versioning-domain-sep | kex_round | signing_pubkey | expand(msg_pubkeys) | OPTIONAL msg_privkey
+ crypto::hash get_msg_to_sign() const;
+ // set: msg string based on msg contents, signing pubkey based on input privkey
+ void construct_msg(const crypto::secret_key &signing_privkey);
+ // parse msg string into parts, validate contents and signature
+ void parse_and_validate_msg();
+
+ //member variables
+ private:
+ // message as string
+ std::string m_msg;
+
+ // key exchange round this msg was produced for
+ std::uint32_t m_kex_round;
+ // pubkeys stored in msg
+ std::vector<crypto::public_key> m_msg_pubkeys;
+ // privkey stored in msg (if kex round 1)
+ crypto::secret_key m_msg_privkey;
+ // pubkey used to sign this msg
+ crypto::public_key m_signing_pubkey;
+ };
+} //namespace multisig
diff --git a/src/multisig/multisig_kex_msg_serialization.h b/src/multisig/multisig_kex_msg_serialization.h
new file mode 100644
index 000000000..9c7b993a7
--- /dev/null
+++ b/src/multisig/multisig_kex_msg_serialization.h
@@ -0,0 +1,78 @@
+// 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 "crypto/crypto.h"
+#include "serialization/containers.h"
+#include "serialization/crypto.h"
+#include "serialization/serialization.h"
+
+#include <cstdint>
+#include <vector>
+
+
+namespace multisig
+{
+ /// round 1 kex message
+ struct multisig_kex_msg_serializable_round1
+ {
+ // privkey stored in msg
+ crypto::secret_key msg_privkey;
+ // pubkey used to sign this msg
+ crypto::public_key signing_pubkey;
+ // message signature
+ crypto::signature signature;
+
+ BEGIN_SERIALIZE()
+ FIELD(msg_privkey)
+ FIELD(signing_pubkey)
+ FIELD(signature)
+ END_SERIALIZE()
+ };
+
+ /// general kex message (if round > 1)
+ struct multisig_kex_msg_serializable_general
+ {
+ // key exchange round this msg was produced for
+ std::uint32_t kex_round;
+ // pubkeys stored in msg
+ std::vector<crypto::public_key> msg_pubkeys;
+ // pubkey used to sign this msg
+ crypto::public_key signing_pubkey;
+ // message signature
+ crypto::signature signature;
+
+ BEGIN_SERIALIZE()
+ VARINT_FIELD(kex_round)
+ FIELD(msg_pubkeys)
+ FIELD(signing_pubkey)
+ FIELD(signature)
+ END_SERIALIZE()
+ };
+} //namespace multisig
diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl
index 86c29b4db..199601d00 100644
--- a/src/p2p/net_node.inl
+++ b/src/p2p/net_node.inl
@@ -717,7 +717,7 @@ namespace nodetool
full_addrs.insert("176.9.0.187:18080");
full_addrs.insert("88.198.163.90:18080");
full_addrs.insert("95.217.25.101:18080");
- full_addrs.insert("209.250.243.248:18080");
+ full_addrs.insert("136.244.105.131:18080");
full_addrs.insert("104.238.221.81:18080");
full_addrs.insert("66.85.74.134:18080");
full_addrs.insert("88.99.173.38:18080");
diff --git a/src/ringct/CMakeLists.txt b/src/ringct/CMakeLists.txt
index 40b2dfd55..32da0f5f5 100644
--- a/src/ringct/CMakeLists.txt
+++ b/src/ringct/CMakeLists.txt
@@ -31,13 +31,15 @@ set(ringct_basic_sources
rctTypes.cpp
rctCryptoOps.c
multiexp.cc
- bulletproofs.cc)
+ bulletproofs.cc
+ bulletproofs_plus.cc)
set(ringct_basic_private_headers
rctOps.h
rctTypes.h
multiexp.h
- bulletproofs.h)
+ bulletproofs.h
+ bulletproofs_plus.h)
monero_private_headers(ringct_basic
${crypto_private_headers})
diff --git a/src/ringct/bulletproofs.cc b/src/ringct/bulletproofs.cc
index a6e12c9b3..1689e5463 100644
--- a/src/ringct/bulletproofs.cc
+++ b/src/ringct/bulletproofs.cc
@@ -70,13 +70,12 @@ static rct::key inner_product(const rct::keyV &a, const rct::keyV &b);
static constexpr size_t maxN = 64;
static constexpr size_t maxM = BULLETPROOF_MAX_OUTPUTS;
-static rct::key Hi[maxN*maxM], Gi[maxN*maxM];
static ge_p3 Hi_p3[maxN*maxM], Gi_p3[maxN*maxM];
static std::shared_ptr<straus_cached_data> straus_HiGi_cache;
static std::shared_ptr<pippenger_cached_data> pippenger_HiGi_cache;
-static const rct::key TWO = { {0x02, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } };
-static const rct::key MINUS_ONE = { { 0xec, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 } };
-static const rct::key MINUS_INV_EIGHT = { { 0x74, 0xa4, 0x19, 0x7a, 0xf0, 0x7d, 0x0b, 0xf7, 0x05, 0xc2, 0xda, 0x25, 0x2b, 0x5c, 0x0b, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a } };
+static const constexpr rct::key TWO = { {0x02, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } };
+static const constexpr rct::key MINUS_ONE = { { 0xec, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 } };
+static const constexpr rct::key MINUS_INV_EIGHT = { { 0x74, 0xa4, 0x19, 0x7a, 0xf0, 0x7d, 0x0b, 0xf7, 0x05, 0xc2, 0xda, 0x25, 0x2b, 0x5c, 0x0b, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a } };
static const rct::keyV oneN = vector_dup(rct::identity(), maxN);
static const rct::keyV twoN = vector_powers(TWO, maxN);
static const rct::key ip12 = inner_product(oneN, twoN);
@@ -100,8 +99,7 @@ static inline bool is_reduced(const rct::key &scalar)
static rct::key get_exponent(const rct::key &base, size_t idx)
{
- static const std::string domain_separator(config::HASH_KEY_BULLETPROOF_EXPONENT);
- std::string hashed = std::string((const char*)base.bytes, sizeof(base)) + domain_separator + tools::get_varint_data(idx);
+ std::string hashed = std::string((const char*)base.bytes, sizeof(base)) + config::HASH_KEY_BULLETPROOF_EXPONENT + tools::get_varint_data(idx);
rct::key e;
ge_p3 e_p3;
rct::hash_to_p3(e_p3, rct::hash2rct(crypto::cn_fast_hash(hashed.data(), hashed.size())));
@@ -121,10 +119,10 @@ static void init_exponents()
data.reserve(maxN*maxM*2);
for (size_t i = 0; i < maxN*maxM; ++i)
{
- Hi[i] = get_exponent(rct::H, i * 2);
- CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&Hi_p3[i], Hi[i].bytes) == 0, "ge_frombytes_vartime failed");
- Gi[i] = get_exponent(rct::H, i * 2 + 1);
- CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&Gi_p3[i], Gi[i].bytes) == 0, "ge_frombytes_vartime failed");
+ const rct::key Hi = get_exponent(rct::H, i * 2);
+ CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&Hi_p3[i], Hi.bytes) == 0, "ge_frombytes_vartime failed");
+ const rct::key Gi = get_exponent(rct::H, i * 2 + 1);
+ CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&Gi_p3[i], Gi.bytes) == 0, "ge_frombytes_vartime failed");
data.push_back({rct::zero(), Gi_p3[i]});
data.push_back({rct::zero(), Hi_p3[i]});
@@ -133,11 +131,10 @@ static void init_exponents()
straus_HiGi_cache = straus_init_cache(data, STRAUS_SIZE_LIMIT);
pippenger_HiGi_cache = pippenger_init_cache(data, 0, PIPPENGER_SIZE_LIMIT);
- MINFO("Hi/Gi cache size: " << (sizeof(Hi)+sizeof(Gi))/1024 << " kB");
MINFO("Hi_p3/Gi_p3 cache size: " << (sizeof(Hi_p3)+sizeof(Gi_p3))/1024 << " kB");
MINFO("Straus cache size: " << straus_get_cache_size(straus_HiGi_cache)/1024 << " kB");
MINFO("Pippenger cache size: " << pippenger_get_cache_size(pippenger_HiGi_cache)/1024 << " kB");
- size_t cache_size = (sizeof(Hi)+sizeof(Hi_p3))*2 + straus_get_cache_size(straus_HiGi_cache) + pippenger_get_cache_size(pippenger_HiGi_cache);
+ size_t cache_size = straus_get_cache_size(straus_HiGi_cache) + pippenger_get_cache_size(pippenger_HiGi_cache);
MINFO("Total cache size: " << cache_size/1024 << "kB");
init_done = true;
}
@@ -895,7 +892,8 @@ bool bulletproof_VERIFY(const std::vector<const Bulletproof*> &proofs)
multiexp_data.resize(2 * maxMN);
PERF_TIMER_START_BP(VERIFY_line_24_25_invert);
- const std::vector<rct::key> inverses = invert(to_invert);
+ const std::vector<rct::key> inverses = invert(std::move(to_invert));
+ to_invert.clear();
PERF_TIMER_STOP_BP(VERIFY_line_24_25_invert);
// setup weighted aggregates
diff --git a/src/ringct/bulletproofs_plus.cc b/src/ringct/bulletproofs_plus.cc
new file mode 100644
index 000000000..3d27849c1
--- /dev/null
+++ b/src/ringct/bulletproofs_plus.cc
@@ -0,0 +1,1121 @@
+// Copyright (c) 2017-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.
+
+// Implements the Bulletproofs+ prover and verifier algorithms
+//
+// Preprint: https://eprint.iacr.org/2020/735, version 17 Jun 2020
+//
+// NOTE ON NOTATION:
+// In the signature constructions used in Monero, commitments to zero are treated as
+// public keys against the curve group generator `G`. This means that amount
+// commitments must use another generator `H` for values in order to show balance.
+// The result is that the roles of `g` and `h` in the preprint are effectively swapped
+// in this code, taking on the roles of `H` and `G`, respectively. Read carefully!
+
+#include <stdlib.h>
+#include <boost/thread/mutex.hpp>
+#include <boost/thread/lock_guard.hpp>
+#include "misc_log_ex.h"
+#include "span.h"
+#include "cryptonote_config.h"
+extern "C"
+{
+#include "crypto/crypto-ops.h"
+}
+#include "rctOps.h"
+#include "multiexp.h"
+#include "bulletproofs_plus.h"
+
+#undef MONERO_DEFAULT_LOG_CATEGORY
+#define MONERO_DEFAULT_LOG_CATEGORY "bulletproof_plus"
+
+#define STRAUS_SIZE_LIMIT 232
+#define PIPPENGER_SIZE_LIMIT 0
+
+namespace rct
+{
+ // Vector functions
+ static rct::key vector_exponent(const rct::keyV &a, const rct::keyV &b);
+ static rct::keyV vector_of_scalar_powers(const rct::key &x, size_t n);
+
+ // Proof bounds
+ static constexpr size_t maxN = 64; // maximum number of bits in range
+ static constexpr size_t maxM = BULLETPROOF_PLUS_MAX_OUTPUTS; // maximum number of outputs to aggregate into a single proof
+
+ // Cached public generators
+ static ge_p3 Hi_p3[maxN*maxM], Gi_p3[maxN*maxM];
+ static std::shared_ptr<straus_cached_data> straus_HiGi_cache;
+ static std::shared_ptr<pippenger_cached_data> pippenger_HiGi_cache;
+
+ // Useful scalar constants
+ static const constexpr rct::key ZERO = { {0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; // 0
+ static const constexpr rct::key ONE = { {0x01, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; // 1
+ static const constexpr rct::key TWO = { {0x02, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; // 2
+ static const constexpr rct::key MINUS_ONE = { { 0xec, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 } }; // -1
+ static const constexpr rct::key MINUS_INV_EIGHT = { { 0x74, 0xa4, 0x19, 0x7a, 0xf0, 0x7d, 0x0b, 0xf7, 0x05, 0xc2, 0xda, 0x25, 0x2b, 0x5c, 0x0b, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a } }; // -(8**(-1))
+ static rct::key TWO_SIXTY_FOUR_MINUS_ONE; // 2**64 - 1
+
+ // Initial transcript hash
+ static rct::key initial_transcript;
+
+ static boost::mutex init_mutex;
+
+ // Use the generator caches to compute a multiscalar multiplication
+ static inline rct::key multiexp(const std::vector<MultiexpData> &data, size_t HiGi_size)
+ {
+ if (HiGi_size > 0)
+ {
+ static_assert(232 <= STRAUS_SIZE_LIMIT, "Straus in precalc mode can only be calculated till STRAUS_SIZE_LIMIT");
+ return HiGi_size <= 232 && data.size() == HiGi_size ? straus(data, straus_HiGi_cache, 0) : pippenger(data, pippenger_HiGi_cache, HiGi_size, get_pippenger_c(data.size()));
+ }
+ else
+ {
+ return data.size() <= 95 ? straus(data, NULL, 0) : pippenger(data, NULL, 0, get_pippenger_c(data.size()));
+ }
+ }
+
+ // Confirm that a scalar is properly reduced
+ static inline bool is_reduced(const rct::key &scalar)
+ {
+ return sc_check(scalar.bytes) == 0;
+ }
+
+ // Use hashed values to produce indexed public generators
+ static ge_p3 get_exponent(const rct::key &base, size_t idx)
+ {
+ std::string hashed = std::string((const char*)base.bytes, sizeof(base)) + config::HASH_KEY_BULLETPROOF_PLUS_EXPONENT + tools::get_varint_data(idx);
+ rct::key generator;
+ ge_p3 generator_p3;
+ rct::hash_to_p3(generator_p3, rct::hash2rct(crypto::cn_fast_hash(hashed.data(), hashed.size())));
+ ge_p3_tobytes(generator.bytes, &generator_p3);
+ CHECK_AND_ASSERT_THROW_MES(!(generator == rct::identity()), "Exponent is point at infinity");
+ return generator_p3;
+ }
+
+ // Construct public generators
+ static void init_exponents()
+ {
+ boost::lock_guard<boost::mutex> lock(init_mutex);
+
+ // Only needs to be done once
+ static bool init_done = false;
+ if (init_done)
+ return;
+
+ std::vector<MultiexpData> data;
+ data.reserve(maxN*maxM*2);
+ for (size_t i = 0; i < maxN*maxM; ++i)
+ {
+ Hi_p3[i] = get_exponent(rct::H, i * 2);
+ Gi_p3[i] = get_exponent(rct::H, i * 2 + 1);
+
+ data.push_back({rct::zero(), Gi_p3[i]});
+ data.push_back({rct::zero(), Hi_p3[i]});
+ }
+
+ straus_HiGi_cache = straus_init_cache(data, STRAUS_SIZE_LIMIT);
+ pippenger_HiGi_cache = pippenger_init_cache(data, 0, PIPPENGER_SIZE_LIMIT);
+
+ // Compute 2**64 - 1 for later use in simplifying verification
+ TWO_SIXTY_FOUR_MINUS_ONE = TWO;
+ for (size_t i = 0; i < 6; i++)
+ {
+ sc_mul(TWO_SIXTY_FOUR_MINUS_ONE.bytes, TWO_SIXTY_FOUR_MINUS_ONE.bytes, TWO_SIXTY_FOUR_MINUS_ONE.bytes);
+ }
+ sc_sub(TWO_SIXTY_FOUR_MINUS_ONE.bytes, TWO_SIXTY_FOUR_MINUS_ONE.bytes, ONE.bytes);
+
+ // Generate the initial Fiat-Shamir transcript hash, which is constant across all proofs
+ const std::string domain_separator(config::HASH_KEY_BULLETPROOF_PLUS_TRANSCRIPT);
+ ge_p3 initial_transcript_p3;
+ rct::hash_to_p3(initial_transcript_p3, rct::hash2rct(crypto::cn_fast_hash(domain_separator.data(), domain_separator.size())));
+ ge_p3_tobytes(initial_transcript.bytes, &initial_transcript_p3);
+
+ init_done = true;
+ }
+
+ // Given two scalar arrays, construct a vector pre-commitment:
+ //
+ // a = (a_0, ..., a_{n-1})
+ // b = (b_0, ..., b_{n-1})
+ //
+ // Outputs a_0*Gi_0 + ... + a_{n-1}*Gi_{n-1} +
+ // b_0*Hi_0 + ... + b_{n-1}*Hi_{n-1}
+ static rct::key vector_exponent(const rct::keyV &a, const rct::keyV &b)
+ {
+ CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b");
+ CHECK_AND_ASSERT_THROW_MES(a.size() <= maxN*maxM, "Incompatible sizes of a and maxN");
+
+ std::vector<MultiexpData> multiexp_data;
+ multiexp_data.reserve(a.size()*2);
+ for (size_t i = 0; i < a.size(); ++i)
+ {
+ multiexp_data.emplace_back(a[i], Gi_p3[i]);
+ multiexp_data.emplace_back(b[i], Hi_p3[i]);
+ }
+ return multiexp(multiexp_data, 2 * a.size());
+ }
+
+ // Helper function used to compute the L and R terms used in the inner-product round function
+ static rct::key compute_LR(size_t size, const rct::key &y, const std::vector<ge_p3> &G, size_t G0, const std::vector<ge_p3> &H, size_t H0, const rct::keyV &a, size_t a0, const rct::keyV &b, size_t b0, const rct::key &c, const rct::key &d)
+ {
+ CHECK_AND_ASSERT_THROW_MES(size + G0 <= G.size(), "Incompatible size for G");
+ CHECK_AND_ASSERT_THROW_MES(size + H0 <= H.size(), "Incompatible size for H");
+ CHECK_AND_ASSERT_THROW_MES(size + a0 <= a.size(), "Incompatible size for a");
+ CHECK_AND_ASSERT_THROW_MES(size + b0 <= b.size(), "Incompatible size for b");
+ CHECK_AND_ASSERT_THROW_MES(size <= maxN*maxM, "size is too large");
+
+ std::vector<MultiexpData> multiexp_data;
+ multiexp_data.resize(size*2 + 2);
+ rct::key temp;
+ for (size_t i = 0; i < size; ++i)
+ {
+ sc_mul(temp.bytes, a[a0+i].bytes, y.bytes);
+ sc_mul(multiexp_data[i*2].scalar.bytes, temp.bytes, INV_EIGHT.bytes);
+ multiexp_data[i*2].point = G[G0+i];
+
+ sc_mul(multiexp_data[i*2+1].scalar.bytes, b[b0+i].bytes, INV_EIGHT.bytes);
+ multiexp_data[i*2+1].point = H[H0+i];
+ }
+
+ sc_mul(multiexp_data[2*size].scalar.bytes, c.bytes, INV_EIGHT.bytes);
+ ge_p3 H_p3;
+ ge_frombytes_vartime(&H_p3, rct::H.bytes);
+ multiexp_data[2*size].point = H_p3;
+
+ sc_mul(multiexp_data[2*size+1].scalar.bytes, d.bytes, INV_EIGHT.bytes);
+ ge_p3 G_p3;
+ ge_frombytes_vartime(&G_p3, rct::G.bytes);
+ multiexp_data[2*size+1].point = G_p3;
+
+ return multiexp(multiexp_data, 0);
+ }
+
+ // Given a scalar, construct a vector of its powers:
+ //
+ // Output (1,x,x**2,...,x**{n-1})
+ static rct::keyV vector_of_scalar_powers(const rct::key &x, size_t n)
+ {
+ CHECK_AND_ASSERT_THROW_MES(n != 0, "Need n > 0");
+
+ rct::keyV res(n);
+ res[0] = rct::identity();
+ if (n == 1)
+ return res;
+ res[1] = x;
+ for (size_t i = 2; i < n; ++i)
+ {
+ sc_mul(res[i].bytes, res[i-1].bytes, x.bytes);
+ }
+ return res;
+ }
+
+ // Given a scalar, construct the sum of its powers from 2 to n (where n is a power of 2):
+ //
+ // Output x**2 + x**4 + x**6 + ... + x**n
+ static rct::key sum_of_even_powers(const rct::key &x, size_t n)
+ {
+ CHECK_AND_ASSERT_THROW_MES((n & (n - 1)) == 0, "Need n to be a power of 2");
+ CHECK_AND_ASSERT_THROW_MES(n != 0, "Need n > 0");
+
+ rct::key x1 = copy(x);
+ sc_mul(x1.bytes, x1.bytes, x1.bytes);
+
+ rct::key res = copy(x1);
+ while (n > 2)
+ {
+ sc_muladd(res.bytes, x1.bytes, res.bytes, res.bytes);
+ sc_mul(x1.bytes, x1.bytes, x1.bytes);
+ n /= 2;
+ }
+
+ return res;
+ }
+
+ // Given a scalar, return the sum of its powers from 1 to n
+ //
+ // Output x**1 + x**2 + x**3 + ... + x**n
+ static rct::key sum_of_scalar_powers(const rct::key &x, size_t n)
+ {
+ CHECK_AND_ASSERT_THROW_MES(n != 0, "Need n > 0");
+
+ rct::key res = ONE;
+ if (n == 1)
+ return x;
+
+ n += 1;
+ rct::key x1 = copy(x);
+
+ const bool is_power_of_2 = (n & (n - 1)) == 0;
+ if (is_power_of_2)
+ {
+ sc_add(res.bytes, res.bytes, x1.bytes);
+ while (n > 2)
+ {
+ sc_mul(x1.bytes, x1.bytes, x1.bytes);
+ sc_muladd(res.bytes, x1.bytes, res.bytes, res.bytes);
+ n /= 2;
+ }
+ }
+ else
+ {
+ rct::key prev = x1;
+ for (size_t i = 1; i < n; ++i)
+ {
+ if (i > 1)
+ sc_mul(prev.bytes, prev.bytes, x1.bytes);
+ sc_add(res.bytes, res.bytes, prev.bytes);
+ }
+ }
+ sc_sub(res.bytes, res.bytes, ONE.bytes);
+
+ return res;
+ }
+
+ // Given two scalar arrays, construct the weighted inner product against another scalar
+ //
+ // Output a_0*b_0*y**1 + a_1*b_1*y**2 + ... + a_{n-1}*b_{n-1}*y**n
+ static rct::key weighted_inner_product(const epee::span<const rct::key> &a, const epee::span<const rct::key> &b, const rct::key &y)
+ {
+ CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b");
+ rct::key res = rct::zero();
+ rct::key y_power = ONE;
+ rct::key temp;
+ for (size_t i = 0; i < a.size(); ++i)
+ {
+ sc_mul(temp.bytes, a[i].bytes, b[i].bytes);
+ sc_mul(y_power.bytes, y_power.bytes, y.bytes);
+ sc_muladd(res.bytes, temp.bytes, y_power.bytes, res.bytes);
+ }
+ return res;
+ }
+
+ static rct::key weighted_inner_product(const rct::keyV &a, const epee::span<const rct::key> &b, const rct::key &y)
+ {
+ CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b");
+ return weighted_inner_product(epee::to_span(a), b, y);
+ }
+
+ // Fold inner-product point vectors
+ static void hadamard_fold(std::vector<ge_p3> &v, const rct::key &a, const rct::key &b)
+ {
+ CHECK_AND_ASSERT_THROW_MES((v.size() & 1) == 0, "Vector size should be even");
+ const size_t sz = v.size() / 2;
+ for (size_t n = 0; n < sz; ++n)
+ {
+ ge_dsmp c[2];
+ ge_dsm_precomp(c[0], &v[n]);
+ ge_dsm_precomp(c[1], &v[sz + n]);
+ ge_double_scalarmult_precomp_vartime2_p3(&v[n], a.bytes, c[0], b.bytes, c[1]);
+ }
+ v.resize(sz);
+ }
+
+ // Add vectors componentwise
+ static rct::keyV vector_add(const rct::keyV &a, const rct::keyV &b)
+ {
+ CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b");
+ rct::keyV res(a.size());
+ for (size_t i = 0; i < a.size(); ++i)
+ {
+ sc_add(res[i].bytes, a[i].bytes, b[i].bytes);
+ }
+ return res;
+ }
+
+ // Add a scalar to all elements of a vector
+ static rct::keyV vector_add(const rct::keyV &a, const rct::key &b)
+ {
+ rct::keyV res(a.size());
+ for (size_t i = 0; i < a.size(); ++i)
+ {
+ sc_add(res[i].bytes, a[i].bytes, b.bytes);
+ }
+ return res;
+ }
+
+ // Subtract a scalar from all elements of a vector
+ static rct::keyV vector_subtract(const rct::keyV &a, const rct::key &b)
+ {
+ rct::keyV res(a.size());
+ for (size_t i = 0; i < a.size(); ++i)
+ {
+ sc_sub(res[i].bytes, a[i].bytes, b.bytes);
+ }
+ return res;
+ }
+
+ // Multiply a scalar by all elements of a vector
+ static rct::keyV vector_scalar(const epee::span<const rct::key> &a, const rct::key &x)
+ {
+ rct::keyV res(a.size());
+ for (size_t i = 0; i < a.size(); ++i)
+ {
+ sc_mul(res[i].bytes, a[i].bytes, x.bytes);
+ }
+ return res;
+ }
+
+ // Inversion helper function
+ static rct::key sm(rct::key y, int n, const rct::key &x)
+ {
+ while (n--)
+ sc_mul(y.bytes, y.bytes, y.bytes);
+ sc_mul(y.bytes, y.bytes, x.bytes);
+ return y;
+ }
+
+ // Compute the inverse of a nonzero
+ static rct::key invert(const rct::key &x)
+ {
+ CHECK_AND_ASSERT_THROW_MES(!(x == ZERO), "Cannot invert zero!");
+ rct::key _1, _10, _100, _11, _101, _111, _1001, _1011, _1111;
+
+ _1 = x;
+ sc_mul(_10.bytes, _1.bytes, _1.bytes);
+ sc_mul(_100.bytes, _10.bytes, _10.bytes);
+ sc_mul(_11.bytes, _10.bytes, _1.bytes);
+ sc_mul(_101.bytes, _10.bytes, _11.bytes);
+ sc_mul(_111.bytes, _10.bytes, _101.bytes);
+ sc_mul(_1001.bytes, _10.bytes, _111.bytes);
+ sc_mul(_1011.bytes, _10.bytes, _1001.bytes);
+ sc_mul(_1111.bytes, _100.bytes, _1011.bytes);
+
+ rct::key inv;
+ sc_mul(inv.bytes, _1111.bytes, _1.bytes);
+
+ inv = sm(inv, 123 + 3, _101);
+ inv = sm(inv, 2 + 2, _11);
+ inv = sm(inv, 1 + 4, _1111);
+ inv = sm(inv, 1 + 4, _1111);
+ inv = sm(inv, 4, _1001);
+ inv = sm(inv, 2, _11);
+ inv = sm(inv, 1 + 4, _1111);
+ inv = sm(inv, 1 + 3, _101);
+ inv = sm(inv, 3 + 3, _101);
+ inv = sm(inv, 3, _111);
+ inv = sm(inv, 1 + 4, _1111);
+ inv = sm(inv, 2 + 3, _111);
+ inv = sm(inv, 2 + 2, _11);
+ inv = sm(inv, 1 + 4, _1011);
+ inv = sm(inv, 2 + 4, _1011);
+ inv = sm(inv, 6 + 4, _1001);
+ inv = sm(inv, 2 + 2, _11);
+ inv = sm(inv, 3 + 2, _11);
+ inv = sm(inv, 3 + 2, _11);
+ inv = sm(inv, 1 + 4, _1001);
+ inv = sm(inv, 1 + 3, _111);
+ inv = sm(inv, 2 + 4, _1111);
+ inv = sm(inv, 1 + 4, _1011);
+ inv = sm(inv, 3, _101);
+ inv = sm(inv, 2 + 4, _1111);
+ inv = sm(inv, 3, _101);
+ inv = sm(inv, 1 + 2, _11);
+
+ return inv;
+ }
+
+ // Invert a batch of scalars, all of which _must_ be nonzero
+ static rct::keyV invert(rct::keyV x)
+ {
+ rct::keyV scratch;
+ scratch.reserve(x.size());
+
+ rct::key acc = rct::identity();
+ for (size_t n = 0; n < x.size(); ++n)
+ {
+ CHECK_AND_ASSERT_THROW_MES(!(x[n] == ZERO), "Cannot invert zero!");
+ scratch.push_back(acc);
+ if (n == 0)
+ acc = x[0];
+ else
+ sc_mul(acc.bytes, acc.bytes, x[n].bytes);
+ }
+
+ acc = invert(acc);
+
+ rct::key tmp;
+ for (int i = x.size(); i-- > 0; )
+ {
+ sc_mul(tmp.bytes, acc.bytes, x[i].bytes);
+ sc_mul(x[i].bytes, acc.bytes, scratch[i].bytes);
+ acc = tmp;
+ }
+
+ return x;
+ }
+
+ // Compute the slice of a vector
+ static epee::span<const rct::key> slice(const rct::keyV &a, size_t start, size_t stop)
+ {
+ CHECK_AND_ASSERT_THROW_MES(start < a.size(), "Invalid start index");
+ CHECK_AND_ASSERT_THROW_MES(stop <= a.size(), "Invalid stop index");
+ CHECK_AND_ASSERT_THROW_MES(start < stop, "Invalid start/stop indices");
+ return epee::span<const rct::key>(&a[start], stop - start);
+ }
+
+ // Update the transcript
+ static rct::key transcript_update(rct::key &transcript, const rct::key &update_0)
+ {
+ rct::key data[2];
+ data[0] = transcript;
+ data[1] = update_0;
+ rct::hash_to_scalar(transcript, data, sizeof(data));
+ return transcript;
+ }
+
+ static rct::key transcript_update(rct::key &transcript, const rct::key &update_0, const rct::key &update_1)
+ {
+ rct::key data[3];
+ data[0] = transcript;
+ data[1] = update_0;
+ data[2] = update_1;
+ rct::hash_to_scalar(transcript, data, sizeof(data));
+ return transcript;
+ }
+
+ // Given a value v [0..2**N) and a mask gamma, construct a range proof
+ BulletproofPlus bulletproof_plus_PROVE(const rct::key &sv, const rct::key &gamma)
+ {
+ return bulletproof_plus_PROVE(rct::keyV(1, sv), rct::keyV(1, gamma));
+ }
+
+ BulletproofPlus bulletproof_plus_PROVE(uint64_t v, const rct::key &gamma)
+ {
+ return bulletproof_plus_PROVE(std::vector<uint64_t>(1, v), rct::keyV(1, gamma));
+ }
+
+ // Given a set of values v [0..2**N) and masks gamma, construct a range proof
+ BulletproofPlus bulletproof_plus_PROVE(const rct::keyV &sv, const rct::keyV &gamma)
+ {
+ // Sanity check on inputs
+ CHECK_AND_ASSERT_THROW_MES(sv.size() == gamma.size(), "Incompatible sizes of sv and gamma");
+ CHECK_AND_ASSERT_THROW_MES(!sv.empty(), "sv is empty");
+ for (const rct::key &sve: sv)
+ CHECK_AND_ASSERT_THROW_MES(is_reduced(sve), "Invalid sv input");
+ for (const rct::key &g: gamma)
+ CHECK_AND_ASSERT_THROW_MES(is_reduced(g), "Invalid gamma input");
+
+ init_exponents();
+
+ // Useful proof bounds
+ //
+ // N: number of bits in each range (here, 64)
+ // logN: base-2 logarithm
+ // M: first power of 2 greater than or equal to the number of range proofs to aggregate
+ // logM: base-2 logarithm
+ constexpr size_t logN = 6; // log2(64)
+ constexpr size_t N = 1<<logN;
+ size_t M, logM;
+ for (logM = 0; (M = 1<<logM) <= maxM && M < sv.size(); ++logM);
+ CHECK_AND_ASSERT_THROW_MES(M <= maxM, "sv/gamma are too large");
+ const size_t logMN = logM + logN;
+ const size_t MN = M * N;
+
+ rct::keyV V(sv.size());
+ rct::keyV aL(MN), aR(MN);
+ rct::keyV aL8(MN), aR8(MN);
+ rct::key temp;
+ rct::key temp2;
+
+ // Prepare output commitments and offset by a factor of 8**(-1)
+ //
+ // This offset is applied to other group elements as well;
+ // it allows us to apply a multiply-by-8 operation in the verifier efficiently
+ // to ensure that the resulting group elements are in the prime-order point subgroup
+ // and avoid much more constly multiply-by-group-order operations.
+ for (size_t i = 0; i < sv.size(); ++i)
+ {
+ rct::key gamma8, sv8;
+ sc_mul(gamma8.bytes, gamma[i].bytes, INV_EIGHT.bytes);
+ sc_mul(sv8.bytes, sv[i].bytes, INV_EIGHT.bytes);
+ rct::addKeys2(V[i], gamma8, sv8, rct::H);
+ }
+
+ // Decompose values
+ //
+ // Note that this effectively pads the set to a power of 2, which is required for the inner-product argument later.
+ for (size_t j = 0; j < M; ++j)
+ {
+ for (size_t i = N; i-- > 0; )
+ {
+ if (j < sv.size() && (sv[j][i/8] & (((uint64_t)1)<<(i%8))))
+ {
+ aL[j*N+i] = rct::identity();
+ aL8[j*N+i] = INV_EIGHT;
+ aR[j*N+i] = aR8[j*N+i] = rct::zero();
+ }
+ else
+ {
+ aL[j*N+i] = aL8[j*N+i] = rct::zero();
+ aR[j*N+i] = MINUS_ONE;
+ aR8[j*N+i] = MINUS_INV_EIGHT;
+ }
+ }
+ }
+
+try_again:
+ // This is a Fiat-Shamir transcript
+ rct::key transcript = copy(initial_transcript);
+ transcript = transcript_update(transcript, rct::hash_to_scalar(V));
+
+ // A
+ rct::key alpha = rct::skGen();
+ rct::key pre_A = vector_exponent(aL8, aR8);
+ rct::key A;
+ sc_mul(temp.bytes, alpha.bytes, INV_EIGHT.bytes);
+ rct::addKeys(A, pre_A, rct::scalarmultBase(temp));
+
+ // Challenges
+ rct::key y = transcript_update(transcript, A);
+ if (y == rct::zero())
+ {
+ MINFO("y is 0, trying again");
+ goto try_again;
+ }
+ rct::key z = transcript = rct::hash_to_scalar(y);
+ if (z == rct::zero())
+ {
+ MINFO("z is 0, trying again");
+ goto try_again;
+ }
+ rct::key z_squared;
+ sc_mul(z_squared.bytes, z.bytes, z.bytes);
+
+ // Windowed vector
+ // d[j*N+i] = z**(2*(j+1)) * 2**i
+ //
+ // We compute this iteratively in order to reduce scalar operations.
+ rct::keyV d(MN, rct::zero());
+ d[0] = z_squared;
+ for (size_t i = 1; i < N; i++)
+ {
+ sc_mul(d[i].bytes, d[i-1].bytes, TWO.bytes);
+ }
+
+ for (size_t j = 1; j < M; j++)
+ {
+ for (size_t i = 0; i < N; i++)
+ {
+ sc_mul(d[j*N+i].bytes, d[(j-1)*N+i].bytes, z_squared.bytes);
+ }
+ }
+
+ rct::keyV y_powers = vector_of_scalar_powers(y, MN+2);
+
+ // Prepare inner product terms
+ rct::keyV aL1 = vector_subtract(aL, z);
+
+ rct::keyV aR1 = vector_add(aR, z);
+ rct::keyV d_y(MN);
+ for (size_t i = 0; i < MN; i++)
+ {
+ sc_mul(d_y[i].bytes, d[i].bytes, y_powers[MN-i].bytes);
+ }
+ aR1 = vector_add(aR1, d_y);
+
+ rct::key alpha1 = alpha;
+ temp = ONE;
+ for (size_t j = 0; j < sv.size(); j++)
+ {
+ sc_mul(temp.bytes, temp.bytes, z_squared.bytes);
+ sc_mul(temp2.bytes, y_powers[MN+1].bytes, temp.bytes);
+ sc_mul(temp2.bytes, temp2.bytes, gamma[j].bytes);
+ sc_add(alpha1.bytes, alpha1.bytes, temp2.bytes);
+ }
+
+ // These are used in the inner product rounds
+ size_t nprime = MN;
+ std::vector<ge_p3> Gprime(MN);
+ std::vector<ge_p3> Hprime(MN);
+ rct::keyV aprime(MN);
+ rct::keyV bprime(MN);
+
+ const rct::key yinv = invert(y);
+ rct::keyV yinvpow(MN);
+ yinvpow[0] = ONE;
+ for (size_t i = 0; i < MN; ++i)
+ {
+ Gprime[i] = Gi_p3[i];
+ Hprime[i] = Hi_p3[i];
+ if (i > 0)
+ {
+ sc_mul(yinvpow[i].bytes, yinvpow[i-1].bytes, yinv.bytes);
+ }
+ aprime[i] = aL1[i];
+ bprime[i] = aR1[i];
+ }
+ rct::keyV L(logMN);
+ rct::keyV R(logMN);
+ int round = 0;
+
+ // Inner-product rounds
+ while (nprime > 1)
+ {
+ nprime /= 2;
+
+ rct::key cL = weighted_inner_product(slice(aprime, 0, nprime), slice(bprime, nprime, bprime.size()), y);
+ rct::key cR = weighted_inner_product(vector_scalar(slice(aprime, nprime, aprime.size()), y_powers[nprime]), slice(bprime, 0, nprime), y);
+
+ rct::key dL = rct::skGen();
+ rct::key dR = rct::skGen();
+
+ L[round] = compute_LR(nprime, yinvpow[nprime], Gprime, nprime, Hprime, 0, aprime, 0, bprime, nprime, cL, dL);
+ R[round] = compute_LR(nprime, y_powers[nprime], Gprime, 0, Hprime, nprime, aprime, nprime, bprime, 0, cR, dR);
+
+ const rct::key challenge = transcript_update(transcript, L[round], R[round]);
+ if (challenge == rct::zero())
+ {
+ MINFO("challenge is 0, trying again");
+ goto try_again;
+ }
+
+ const rct::key challenge_inv = invert(challenge);
+
+ sc_mul(temp.bytes, yinvpow[nprime].bytes, challenge.bytes);
+ hadamard_fold(Gprime, challenge_inv, temp);
+ hadamard_fold(Hprime, challenge, challenge_inv);
+
+ sc_mul(temp.bytes, challenge_inv.bytes, y_powers[nprime].bytes);
+ aprime = vector_add(vector_scalar(slice(aprime, 0, nprime), challenge), vector_scalar(slice(aprime, nprime, aprime.size()), temp));
+ bprime = vector_add(vector_scalar(slice(bprime, 0, nprime), challenge_inv), vector_scalar(slice(bprime, nprime, bprime.size()), challenge));
+
+ rct::key challenge_squared;
+ sc_mul(challenge_squared.bytes, challenge.bytes, challenge.bytes);
+ rct::key challenge_squared_inv = invert(challenge_squared);
+ sc_muladd(alpha1.bytes, dL.bytes, challenge_squared.bytes, alpha1.bytes);
+ sc_muladd(alpha1.bytes, dR.bytes, challenge_squared_inv.bytes, alpha1.bytes);
+
+ ++round;
+ }
+
+ // Final round computations
+ rct::key r = rct::skGen();
+ rct::key s = rct::skGen();
+ rct::key d_ = rct::skGen();
+ rct::key eta = rct::skGen();
+
+ std::vector<MultiexpData> A1_data;
+ A1_data.reserve(4);
+ A1_data.resize(4);
+
+ sc_mul(A1_data[0].scalar.bytes, r.bytes, INV_EIGHT.bytes);
+ A1_data[0].point = Gprime[0];
+
+ sc_mul(A1_data[1].scalar.bytes, s.bytes, INV_EIGHT.bytes);
+ A1_data[1].point = Hprime[0];
+
+ sc_mul(A1_data[2].scalar.bytes, d_.bytes, INV_EIGHT.bytes);
+ ge_p3 G_p3;
+ ge_frombytes_vartime(&G_p3, rct::G.bytes);
+ A1_data[2].point = G_p3;
+
+ sc_mul(temp.bytes, r.bytes, y.bytes);
+ sc_mul(temp.bytes, temp.bytes, bprime[0].bytes);
+ sc_mul(temp2.bytes, s.bytes, y.bytes);
+ sc_mul(temp2.bytes, temp2.bytes, aprime[0].bytes);
+ sc_add(temp.bytes, temp.bytes, temp2.bytes);
+ sc_mul(A1_data[3].scalar.bytes, temp.bytes, INV_EIGHT.bytes);
+ ge_p3 H_p3;
+ ge_frombytes_vartime(&H_p3, rct::H.bytes);
+ A1_data[3].point = H_p3;
+
+ rct::key A1 = multiexp(A1_data, 0);
+
+ sc_mul(temp.bytes, r.bytes, y.bytes);
+ sc_mul(temp.bytes, temp.bytes, s.bytes);
+ sc_mul(temp.bytes, temp.bytes, INV_EIGHT.bytes);
+ sc_mul(temp2.bytes, eta.bytes, INV_EIGHT.bytes);
+ rct::key B;
+ rct::addKeys2(B, temp2, temp, rct::H);
+
+ rct::key e = transcript_update(transcript, A1, B);
+ if (e == rct::zero())
+ {
+ MINFO("e is 0, trying again");
+ goto try_again;
+ }
+ rct::key e_squared;
+ sc_mul(e_squared.bytes, e.bytes, e.bytes);
+
+ rct::key r1;
+ sc_muladd(r1.bytes, aprime[0].bytes, e.bytes, r.bytes);
+
+ rct::key s1;
+ sc_muladd(s1.bytes, bprime[0].bytes, e.bytes, s.bytes);
+
+ rct::key d1;
+ sc_muladd(d1.bytes, d_.bytes, e.bytes, eta.bytes);
+ sc_muladd(d1.bytes, alpha1.bytes, e_squared.bytes, d1.bytes);
+
+ return BulletproofPlus(std::move(V), A, A1, B, r1, s1, d1, std::move(L), std::move(R));
+ }
+
+ BulletproofPlus bulletproof_plus_PROVE(const std::vector<uint64_t> &v, const rct::keyV &gamma)
+ {
+ CHECK_AND_ASSERT_THROW_MES(v.size() == gamma.size(), "Incompatible sizes of v and gamma");
+
+ // vG + gammaH
+ rct::keyV sv(v.size());
+ for (size_t i = 0; i < v.size(); ++i)
+ {
+ sv[i] = rct::d2h(v[i]);
+ }
+ return bulletproof_plus_PROVE(sv, gamma);
+ }
+
+ struct bp_plus_proof_data_t
+ {
+ rct::key y, z, e;
+ std::vector<rct::key> challenges;
+ size_t logM, inv_offset;
+ };
+
+ // Given a batch of range proofs, determine if they are all valid
+ bool bulletproof_plus_VERIFY(const std::vector<const BulletproofPlus*> &proofs)
+ {
+ init_exponents();
+
+ const size_t logN = 6;
+ const size_t N = 1 << logN;
+
+ // Set up
+ size_t max_length = 0; // size of each of the longest proof's inner-product vectors
+ size_t nV = 0; // number of output commitments across all proofs
+ size_t inv_offset = 0;
+ size_t max_logM = 0;
+
+ std::vector<bp_plus_proof_data_t> proof_data;
+ proof_data.reserve(proofs.size());
+
+ // We'll perform only a single batch inversion across all proofs in the batch,
+ // since batch inversion requires only one scalar inversion operation.
+ std::vector<rct::key> to_invert;
+ to_invert.reserve(11 * proofs.size()); // maximal size, given the aggregation limit
+
+ for (const BulletproofPlus *p: proofs)
+ {
+ const BulletproofPlus &proof = *p;
+
+ // Sanity checks
+ CHECK_AND_ASSERT_MES(is_reduced(proof.r1), false, "Input scalar not in range");
+ CHECK_AND_ASSERT_MES(is_reduced(proof.s1), false, "Input scalar not in range");
+ CHECK_AND_ASSERT_MES(is_reduced(proof.d1), false, "Input scalar not in range");
+
+ CHECK_AND_ASSERT_MES(proof.V.size() >= 1, false, "V does not have at least one element");
+ CHECK_AND_ASSERT_MES(proof.L.size() == proof.R.size(), false, "Mismatched L and R sizes");
+ CHECK_AND_ASSERT_MES(proof.L.size() > 0, false, "Empty proof");
+
+ max_length = std::max(max_length, proof.L.size());
+ nV += proof.V.size();
+
+ proof_data.push_back({});
+ bp_plus_proof_data_t &pd = proof_data.back();
+
+ // Reconstruct the challenges
+ rct::key transcript = copy(initial_transcript);
+ transcript = transcript_update(transcript, rct::hash_to_scalar(proof.V));
+ pd.y = transcript_update(transcript, proof.A);
+ CHECK_AND_ASSERT_MES(!(pd.y == rct::zero()), false, "y == 0");
+ pd.z = transcript = rct::hash_to_scalar(pd.y);
+ CHECK_AND_ASSERT_MES(!(pd.z == rct::zero()), false, "z == 0");
+
+ // Determine the number of inner-product rounds based on proof size
+ size_t M;
+ for (pd.logM = 0; (M = 1<<pd.logM) <= maxM && M < proof.V.size(); ++pd.logM);
+ CHECK_AND_ASSERT_MES(proof.L.size() == 6+pd.logM, false, "Proof is not the expected size");
+ max_logM = std::max(pd.logM, max_logM);
+
+ const size_t rounds = pd.logM+logN;
+ CHECK_AND_ASSERT_MES(rounds > 0, false, "Zero rounds");
+
+ // The inner-product challenges are computed per round
+ pd.challenges.resize(rounds);
+ for (size_t j = 0; j < rounds; ++j)
+ {
+ pd.challenges[j] = transcript_update(transcript, proof.L[j], proof.R[j]);
+ CHECK_AND_ASSERT_MES(!(pd.challenges[j] == rct::zero()), false, "challenges[j] == 0");
+ }
+
+ // Final challenge
+ pd.e = transcript_update(transcript,proof.A1,proof.B);
+ CHECK_AND_ASSERT_MES(!(pd.e == rct::zero()), false, "e == 0");
+
+ // Batch scalar inversions
+ pd.inv_offset = inv_offset;
+ for (size_t j = 0; j < rounds; ++j)
+ to_invert.push_back(pd.challenges[j]);
+ to_invert.push_back(pd.y);
+ inv_offset += rounds + 1;
+ }
+ CHECK_AND_ASSERT_MES(max_length < 32, false, "At least one proof is too large");
+ size_t maxMN = 1u << max_length;
+
+ rct::key temp;
+ rct::key temp2;
+
+ // Final batch proof data
+ std::vector<MultiexpData> multiexp_data;
+ multiexp_data.reserve(nV + (2 * (max_logM + logN) + 3) * proofs.size() + 2 * maxMN);
+ multiexp_data.resize(2 * maxMN);
+
+ const std::vector<rct::key> inverses = invert(std::move(to_invert));
+ to_invert.clear();
+
+ // Weights and aggregates
+ //
+ // The idea is to take the single multiscalar multiplication used in the verification
+ // of each proof in the batch and weight it using a random weighting factor, resulting
+ // in just one multiscalar multiplication check to zero for the entire batch.
+ // We can further simplify the verifier complexity by including common group elements
+ // only once in this single multiscalar multiplication.
+ // Common group elements' weighted scalar sums are tracked across proofs for this reason.
+ //
+ // To build a multiscalar multiplication for each proof, we use the method described in
+ // Section 6.1 of the preprint. Note that the result given there does not account for
+ // the construction of the inner-product inputs that are produced in the range proof
+ // verifier algorithm; we have done so here.
+ rct::key G_scalar = rct::zero();
+ rct::key H_scalar = rct::zero();
+ rct::keyV Gi_scalars(maxMN, rct::zero());
+ rct::keyV Hi_scalars(maxMN, rct::zero());
+
+ int proof_data_index = 0;
+ rct::keyV challenges_cache;
+ std::vector<ge_p3> proof8_V, proof8_L, proof8_R;
+
+ // Process each proof and add to the weighted batch
+ for (const BulletproofPlus *p: proofs)
+ {
+ const BulletproofPlus &proof = *p;
+ const bp_plus_proof_data_t &pd = proof_data[proof_data_index++];
+
+ CHECK_AND_ASSERT_MES(proof.L.size() == 6+pd.logM, false, "Proof is not the expected size");
+ const size_t M = 1 << pd.logM;
+ const size_t MN = M*N;
+
+ // Random weighting factor must be nonzero, which is exceptionally unlikely!
+ rct::key weight = ZERO;
+ while (weight == ZERO)
+ {
+ weight = rct::skGen();
+ }
+
+ // Rescale previously offset proof elements
+ //
+ // This ensures that all such group elements are in the prime-order subgroup.
+ proof8_V.resize(proof.V.size()); for (size_t i = 0; i < proof.V.size(); ++i) rct::scalarmult8(proof8_V[i], proof.V[i]);
+ proof8_L.resize(proof.L.size()); for (size_t i = 0; i < proof.L.size(); ++i) rct::scalarmult8(proof8_L[i], proof.L[i]);
+ proof8_R.resize(proof.R.size()); for (size_t i = 0; i < proof.R.size(); ++i) rct::scalarmult8(proof8_R[i], proof.R[i]);
+ ge_p3 proof8_A1;
+ ge_p3 proof8_B;
+ ge_p3 proof8_A;
+ rct::scalarmult8(proof8_A1, proof.A1);
+ rct::scalarmult8(proof8_B, proof.B);
+ rct::scalarmult8(proof8_A, proof.A);
+
+ // Compute necessary powers of the y-challenge
+ rct::key y_MN = copy(pd.y);
+ rct::key y_MN_1;
+ size_t temp_MN = MN;
+ while (temp_MN > 1)
+ {
+ sc_mul(y_MN.bytes, y_MN.bytes, y_MN.bytes);
+ temp_MN /= 2;
+ }
+ sc_mul(y_MN_1.bytes, y_MN.bytes, pd.y.bytes);
+
+ // V_j: -e**2 * z**(2*j+1) * y**(MN+1) * weight
+ rct::key e_squared;
+ sc_mul(e_squared.bytes, pd.e.bytes, pd.e.bytes);
+
+ rct::key z_squared;
+ sc_mul(z_squared.bytes, pd.z.bytes, pd.z.bytes);
+
+ sc_sub(temp.bytes, ZERO.bytes, e_squared.bytes);
+ sc_mul(temp.bytes, temp.bytes, y_MN_1.bytes);
+ sc_mul(temp.bytes, temp.bytes, weight.bytes);
+ for (size_t j = 0; j < proof8_V.size(); j++)
+ {
+ sc_mul(temp.bytes, temp.bytes, z_squared.bytes);
+ multiexp_data.emplace_back(temp, proof8_V[j]);
+ }
+
+ // B: -weight
+ sc_mul(temp.bytes, MINUS_ONE.bytes, weight.bytes);
+ multiexp_data.emplace_back(temp, proof8_B);
+
+ // A1: -weight*e
+ sc_mul(temp.bytes, temp.bytes, pd.e.bytes);
+ multiexp_data.emplace_back(temp, proof8_A1);
+
+ // A: -weight*e*e
+ rct::key minus_weight_e_squared;
+ sc_mul(minus_weight_e_squared.bytes, temp.bytes, pd.e.bytes);
+ multiexp_data.emplace_back(minus_weight_e_squared, proof8_A);
+
+ // G: weight*d1
+ sc_muladd(G_scalar.bytes, weight.bytes, proof.d1.bytes, G_scalar.bytes);
+
+ // Windowed vector
+ // d[j*N+i] = z**(2*(j+1)) * 2**i
+ rct::keyV d(MN, rct::zero());
+ d[0] = z_squared;
+ for (size_t i = 1; i < N; i++)
+ {
+ sc_add(d[i].bytes, d[i-1].bytes, d[i-1].bytes);
+ }
+
+ for (size_t j = 1; j < M; j++)
+ {
+ for (size_t i = 0; i < N; i++)
+ {
+ sc_mul(d[j*N+i].bytes, d[(j-1)*N+i].bytes, z_squared.bytes);
+ }
+ }
+
+ // More efficient computation of sum(d)
+ rct::key sum_d;
+ sc_mul(sum_d.bytes, TWO_SIXTY_FOUR_MINUS_ONE.bytes, sum_of_even_powers(pd.z, 2*M).bytes);
+
+ // H: weight*( r1*y*s1 + e**2*( y**(MN+1)*z*sum(d) + (z**2-z)*sum(y) ) )
+ rct::key sum_y = sum_of_scalar_powers(pd.y, MN);
+ sc_sub(temp.bytes, z_squared.bytes, pd.z.bytes);
+ sc_mul(temp.bytes, temp.bytes, sum_y.bytes);
+
+ sc_mul(temp2.bytes, y_MN_1.bytes, pd.z.bytes);
+ sc_mul(temp2.bytes, temp2.bytes, sum_d.bytes);
+ sc_add(temp.bytes, temp.bytes, temp2.bytes);
+ sc_mul(temp.bytes, temp.bytes, e_squared.bytes);
+ sc_mul(temp2.bytes, proof.r1.bytes, pd.y.bytes);
+ sc_mul(temp2.bytes, temp2.bytes, proof.s1.bytes);
+ sc_add(temp.bytes, temp.bytes, temp2.bytes);
+ sc_muladd(H_scalar.bytes, temp.bytes, weight.bytes, H_scalar.bytes);
+
+ // Compute the number of rounds for the inner-product argument
+ const size_t rounds = pd.logM+logN;
+ CHECK_AND_ASSERT_MES(rounds > 0, false, "Zero rounds");
+
+ const rct::key *challenges_inv = &inverses[pd.inv_offset];
+ const rct::key yinv = inverses[pd.inv_offset + rounds];
+
+ // Compute challenge products
+ challenges_cache.resize(1<<rounds);
+ challenges_cache[0] = challenges_inv[0];
+ challenges_cache[1] = pd.challenges[0];
+ for (size_t j = 1; j < rounds; ++j)
+ {
+ const size_t slots = 1<<(j+1);
+ for (size_t s = slots; s-- > 0; --s)
+ {
+ sc_mul(challenges_cache[s].bytes, challenges_cache[s/2].bytes, pd.challenges[j].bytes);
+ sc_mul(challenges_cache[s-1].bytes, challenges_cache[s/2].bytes, challenges_inv[j].bytes);
+ }
+ }
+
+ // Gi and Hi
+ rct::key e_r1_w_y;
+ sc_mul(e_r1_w_y.bytes, pd.e.bytes, proof.r1.bytes);
+ sc_mul(e_r1_w_y.bytes, e_r1_w_y.bytes, weight.bytes);
+ rct::key e_s1_w;
+ sc_mul(e_s1_w.bytes, pd.e.bytes, proof.s1.bytes);
+ sc_mul(e_s1_w.bytes, e_s1_w.bytes, weight.bytes);
+ rct::key e_squared_z_w;
+ sc_mul(e_squared_z_w.bytes, e_squared.bytes, pd.z.bytes);
+ sc_mul(e_squared_z_w.bytes, e_squared_z_w.bytes, weight.bytes);
+ rct::key minus_e_squared_z_w;
+ sc_sub(minus_e_squared_z_w.bytes, ZERO.bytes, e_squared_z_w.bytes);
+ rct::key minus_e_squared_w_y;
+ sc_sub(minus_e_squared_w_y.bytes, ZERO.bytes, e_squared.bytes);
+ sc_mul(minus_e_squared_w_y.bytes, minus_e_squared_w_y.bytes, weight.bytes);
+ sc_mul(minus_e_squared_w_y.bytes, minus_e_squared_w_y.bytes, y_MN.bytes);
+ for (size_t i = 0; i < MN; ++i)
+ {
+ rct::key g_scalar = copy(e_r1_w_y);
+ rct::key h_scalar;
+
+ // Use the binary decomposition of the index
+ sc_muladd(g_scalar.bytes, g_scalar.bytes, challenges_cache[i].bytes, e_squared_z_w.bytes);
+ sc_muladd(h_scalar.bytes, e_s1_w.bytes, challenges_cache[(~i) & (MN-1)].bytes, minus_e_squared_z_w.bytes);
+
+ // Complete the scalar derivation
+ sc_add(Gi_scalars[i].bytes, Gi_scalars[i].bytes, g_scalar.bytes);
+ sc_muladd(h_scalar.bytes, minus_e_squared_w_y.bytes, d[i].bytes, h_scalar.bytes);
+ sc_add(Hi_scalars[i].bytes, Hi_scalars[i].bytes, h_scalar.bytes);
+
+ // Update iterated values
+ sc_mul(e_r1_w_y.bytes, e_r1_w_y.bytes, yinv.bytes);
+ sc_mul(minus_e_squared_w_y.bytes, minus_e_squared_w_y.bytes, yinv.bytes);
+ }
+
+ // L_j: -weight*e*e*challenges[j]**2
+ // R_j: -weight*e*e*challenges[j]**(-2)
+ for (size_t j = 0; j < rounds; ++j)
+ {
+ sc_mul(temp.bytes, pd.challenges[j].bytes, pd.challenges[j].bytes);
+ sc_mul(temp.bytes, temp.bytes, minus_weight_e_squared.bytes);
+ multiexp_data.emplace_back(temp, proof8_L[j]);
+
+ sc_mul(temp.bytes, challenges_inv[j].bytes, challenges_inv[j].bytes);
+ sc_mul(temp.bytes, temp.bytes, minus_weight_e_squared.bytes);
+ multiexp_data.emplace_back(temp, proof8_R[j]);
+ }
+ }
+
+ // Verify all proofs in the weighted batch
+ multiexp_data.emplace_back(G_scalar, rct::G);
+ multiexp_data.emplace_back(H_scalar, rct::H);
+ for (size_t i = 0; i < maxMN; ++i)
+ {
+ multiexp_data[i * 2] = {Gi_scalars[i], Gi_p3[i]};
+ multiexp_data[i * 2 + 1] = {Hi_scalars[i], Hi_p3[i]};
+ }
+ if (!(multiexp(multiexp_data, 2 * maxMN) == rct::identity()))
+ {
+ MERROR("Verification failure");
+ return false;
+ }
+
+ return true;
+ }
+
+ bool bulletproof_plus_VERIFY(const std::vector<BulletproofPlus> &proofs)
+ {
+ std::vector<const BulletproofPlus*> proof_pointers;
+ proof_pointers.reserve(proofs.size());
+ for (const BulletproofPlus &proof: proofs)
+ proof_pointers.push_back(&proof);
+ return bulletproof_plus_VERIFY(proof_pointers);
+ }
+
+ bool bulletproof_plus_VERIFY(const BulletproofPlus &proof)
+ {
+ std::vector<const BulletproofPlus*> proofs;
+ proofs.push_back(&proof);
+ return bulletproof_plus_VERIFY(proofs);
+ }
+}
diff --git a/src/ringct/bulletproofs_plus.h b/src/ringct/bulletproofs_plus.h
new file mode 100644
index 000000000..d9084075a
--- /dev/null
+++ b/src/ringct/bulletproofs_plus.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2017-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.
+
+#pragma once
+
+#ifndef BULLETPROOFS_PLUS_H
+#define BULLETPROOFS_PLUS_H
+
+#include "rctTypes.h"
+
+namespace rct
+{
+
+BulletproofPlus bulletproof_plus_PROVE(const rct::key &v, const rct::key &gamma);
+BulletproofPlus bulletproof_plus_PROVE(uint64_t v, const rct::key &gamma);
+BulletproofPlus bulletproof_plus_PROVE(const rct::keyV &v, const rct::keyV &gamma);
+BulletproofPlus bulletproof_plus_PROVE(const std::vector<uint64_t> &v, const rct::keyV &gamma);
+bool bulletproof_plus_VERIFY(const BulletproofPlus &proof);
+bool bulletproof_plus_VERIFY(const std::vector<const BulletproofPlus*> &proofs);
+bool bulletproof_plus_VERIFY(const std::vector<BulletproofPlus> &proofs);
+
+}
+
+#endif
diff --git a/src/ringct/multiexp.cc b/src/ringct/multiexp.cc
index 784c90a4e..f256325a1 100644
--- a/src/ringct/multiexp.cc
+++ b/src/ringct/multiexp.cc
@@ -235,7 +235,7 @@ rct::key bos_coster_heap_conv_robust(std::vector<MultiexpData> data)
heap.reserve(points);
for (size_t n = 0; n < points; ++n)
{
- if (!(data[n].scalar == rct::zero()) && !ge_p3_is_point_at_infinity(&data[n].point))
+ if (!(data[n].scalar == rct::zero()) && !ge_p3_is_point_at_infinity_vartime(&data[n].point))
heap.push_back(n);
}
points = heap.size();
@@ -457,7 +457,7 @@ rct::key straus(const std::vector<MultiexpData> &data, const std::shared_ptr<str
MULTIEXP_PERF(PERF_TIMER_START_UNIT(skip, 1000000));
std::vector<uint8_t> skip(data.size());
for (size_t i = 0; i < data.size(); ++i)
- skip[i] = data[i].scalar == rct::zero() || ge_p3_is_point_at_infinity(&data[i].point);
+ skip[i] = data[i].scalar == rct::zero() || ge_p3_is_point_at_infinity_vartime(&data[i].point);
MULTIEXP_PERF(PERF_TIMER_STOP(skip));
#endif
diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp
index f5950c53c..d7883baac 100644
--- a/src/ringct/rctSigs.cpp
+++ b/src/ringct/rctSigs.cpp
@@ -35,6 +35,7 @@
#include "common/util.h"
#include "rctSigs.h"
#include "bulletproofs.h"
+#include "bulletproofs_plus.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "cryptonote_config.h"
@@ -78,6 +79,36 @@ namespace
return rct::Bulletproof{rct::keyV(n_outs, I), I, I, I, I, I, I, rct::keyV(nrl, I), rct::keyV(nrl, I), I, I, I};
}
+ rct::BulletproofPlus make_dummy_bulletproof_plus(const std::vector<uint64_t> &outamounts, rct::keyV &C, rct::keyV &masks)
+ {
+ const size_t n_outs = outamounts.size();
+ const rct::key I = rct::identity();
+ size_t nrl = 0;
+ while ((1u << nrl) < n_outs)
+ ++nrl;
+ nrl += 6;
+
+ C.resize(n_outs);
+ masks.resize(n_outs);
+ for (size_t i = 0; i < n_outs; ++i)
+ {
+ masks[i] = I;
+ rct::key sv8, sv;
+ sv = rct::zero();
+ sv.bytes[0] = outamounts[i] & 255;
+ sv.bytes[1] = (outamounts[i] >> 8) & 255;
+ sv.bytes[2] = (outamounts[i] >> 16) & 255;
+ sv.bytes[3] = (outamounts[i] >> 24) & 255;
+ sv.bytes[4] = (outamounts[i] >> 32) & 255;
+ sv.bytes[5] = (outamounts[i] >> 40) & 255;
+ sv.bytes[6] = (outamounts[i] >> 48) & 255;
+ sv.bytes[7] = (outamounts[i] >> 56) & 255;
+ sc_mul(sv8.bytes, sv.bytes, rct::INV_EIGHT.bytes);
+ rct::addKeys2(C[i], rct::INV_EIGHT, sv8, rct::H);
+ }
+
+ return rct::BulletproofPlus{rct::keyV(n_outs, I), I, I, I, I, I, I, rct::keyV(nrl, I), rct::keyV(nrl, I)};
+ }
}
namespace rct {
@@ -107,6 +138,32 @@ namespace rct {
catch (...) { return false; }
}
+ BulletproofPlus proveRangeBulletproofPlus(keyV &C, keyV &masks, const std::vector<uint64_t> &amounts, epee::span<const key> sk, hw::device &hwdev)
+ {
+ CHECK_AND_ASSERT_THROW_MES(amounts.size() == sk.size(), "Invalid amounts/sk sizes");
+ masks.resize(amounts.size());
+ for (size_t i = 0; i < masks.size(); ++i)
+ masks[i] = hwdev.genCommitmentMask(sk[i]);
+ BulletproofPlus proof = bulletproof_plus_PROVE(amounts, masks);
+ CHECK_AND_ASSERT_THROW_MES(proof.V.size() == amounts.size(), "V does not have the expected size");
+ C = proof.V;
+ return proof;
+ }
+
+ bool verBulletproofPlus(const BulletproofPlus &proof)
+ {
+ try { return bulletproof_plus_VERIFY(proof); }
+ // we can get deep throws from ge_frombytes_vartime if input isn't valid
+ catch (...) { return false; }
+ }
+
+ bool verBulletproofPlus(const std::vector<const BulletproofPlus*> &proofs)
+ {
+ try { return bulletproof_plus_VERIFY(proofs); }
+ // we can get deep throws from ge_frombytes_vartime if input isn't valid
+ catch (...) { return false; }
+ }
+
//Borromean (c.f. gmax/andytoshi's paper)
boroSig genBorromean(const key64 x, const key64 P1, const key64 P2, const bits indices) {
key64 L[2], alpha;
@@ -611,6 +668,25 @@ namespace rct {
kv.push_back(p.t);
}
}
+ else if (rv.type == RCTTypeBulletproofPlus)
+ {
+ kv.reserve((6*2+6) * rv.p.bulletproofs_plus.size());
+ for (const auto &p: rv.p.bulletproofs_plus)
+ {
+ // V are not hashed as they're expanded from outPk.mask
+ // (and thus hashed as part of rctSigBase above)
+ kv.push_back(p.A);
+ kv.push_back(p.A1);
+ kv.push_back(p.B);
+ kv.push_back(p.r1);
+ kv.push_back(p.s1);
+ kv.push_back(p.d1);
+ for (size_t n = 0; n < p.L.size(); ++n)
+ kv.push_back(p.L[n]);
+ for (size_t n = 0; n < p.R.size(); ++n)
+ kv.push_back(p.R[n]);
+ }
+ }
else
{
kv.reserve((64*3+1) * rv.p.rangeSigs.size());
@@ -1031,7 +1107,7 @@ namespace rct {
//mask amount and mask
rv.ecdhInfo[i].mask = copy(outSk[i].mask);
rv.ecdhInfo[i].amount = d2h(amounts[i]);
- hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG);
+ hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus);
}
//set txn fee
@@ -1063,7 +1139,7 @@ namespace rct {
//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) {
- const bool bulletproof = rct_config.range_proof_type != RangeProofBorromean;
+ 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");
CHECK_AND_ASSERT_THROW_MES(outamounts.size() == destinations.size(), "Different number of amounts/destinations");
@@ -1079,11 +1155,14 @@ namespace rct {
}
rctSig rv;
- if (bulletproof)
+ if (bulletproof_or_plus)
{
switch (rct_config.bp_version)
{
case 0:
+ case 4:
+ rv.type = RCTTypeBulletproofPlus;
+ break;
case 3:
rv.type = RCTTypeCLSAG;
break;
@@ -1102,7 +1181,7 @@ namespace rct {
rv.message = message;
rv.outPk.resize(destinations.size());
- if (!bulletproof)
+ if (!bulletproof_or_plus)
rv.p.rangeSigs.resize(destinations.size());
rv.ecdhInfo.resize(destinations.size());
@@ -1114,17 +1193,19 @@ namespace rct {
//add destination to sig
rv.outPk[i].dest = copy(destinations[i]);
//compute range proof
- if (!bulletproof)
+ if (!bulletproof_or_plus)
rv.p.rangeSigs[i] = proveRange(rv.outPk[i].mask, outSk[i].mask, outamounts[i]);
#ifdef DBG
- if (!bulletproof)
+ if (!bulletproof_or_plus)
CHECK_AND_ASSERT_THROW_MES(verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]), "verRange failed on newly created proof");
#endif
}
rv.p.bulletproofs.clear();
- if (bulletproof)
+ rv.p.bulletproofs_plus.clear();
+ if (bulletproof_or_plus)
{
+ const bool plus = is_rct_bulletproof_plus(rv.type);
size_t n_amounts = outamounts.size();
size_t amounts_proved = 0;
if (rct_config.range_proof_type == RangeProofPaddedBulletproof)
@@ -1133,19 +1214,31 @@ namespace rct {
if (hwdev.get_mode() == hw::device::TRANSACTION_CREATE_FAKE)
{
// use a fake bulletproof for speed
- rv.p.bulletproofs.push_back(make_dummy_bulletproof(outamounts, C, masks));
+ if (plus)
+ rv.p.bulletproofs_plus.push_back(make_dummy_bulletproof_plus(outamounts, C, masks));
+ else
+ rv.p.bulletproofs.push_back(make_dummy_bulletproof(outamounts, C, masks));
}
else
{
const epee::span<const key> keys{&amount_keys[0], amount_keys.size()};
- rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, outamounts, keys, hwdev));
+ if (plus)
+ rv.p.bulletproofs_plus.push_back(proveRangeBulletproofPlus(C, masks, outamounts, keys, hwdev));
+ else
+ rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, outamounts, keys, hwdev));
#ifdef DBG
- CHECK_AND_ASSERT_THROW_MES(verBulletproof(rv.p.bulletproofs.back()), "verBulletproof failed on newly created proof");
+ if (plus)
+ CHECK_AND_ASSERT_THROW_MES(verBulletproofPlus(rv.p.bulletproofs_plus.back()), "verBulletproofPlus failed on newly created proof");
+ else
+ CHECK_AND_ASSERT_THROW_MES(verBulletproof(rv.p.bulletproofs.back()), "verBulletproof failed on newly created proof");
#endif
}
for (i = 0; i < outamounts.size(); ++i)
{
- rv.outPk[i].mask = rct::scalarmult8(C[i]);
+ if (plus)
+ rv.outPk[i].mask = C[i];
+ else
+ rv.outPk[i].mask = rct::scalarmult8(C[i]);
outSk[i].mask = masks[i];
}
}
@@ -1153,7 +1246,7 @@ namespace rct {
{
size_t batch_size = 1;
if (rct_config.range_proof_type == RangeProofMultiOutputBulletproof)
- while (batch_size * 2 + amounts_proved <= n_amounts && batch_size * 2 <= BULLETPROOF_MAX_OUTPUTS)
+ while (batch_size * 2 + amounts_proved <= n_amounts && batch_size * 2 <= (plus ? BULLETPROOF_PLUS_MAX_OUTPUTS : BULLETPROOF_MAX_OUTPUTS))
batch_size *= 2;
rct::keyV C, masks;
std::vector<uint64_t> batch_amounts(batch_size);
@@ -1162,19 +1255,31 @@ namespace rct {
if (hwdev.get_mode() == hw::device::TRANSACTION_CREATE_FAKE)
{
// use a fake bulletproof for speed
- rv.p.bulletproofs.push_back(make_dummy_bulletproof(batch_amounts, C, masks));
+ if (plus)
+ rv.p.bulletproofs_plus.push_back(make_dummy_bulletproof_plus(batch_amounts, C, masks));
+ else
+ rv.p.bulletproofs.push_back(make_dummy_bulletproof(batch_amounts, C, masks));
}
else
{
const epee::span<const key> keys{&amount_keys[amounts_proved], batch_size};
- rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, batch_amounts, keys, hwdev));
+ if (plus)
+ rv.p.bulletproofs_plus.push_back(proveRangeBulletproofPlus(C, masks, batch_amounts, keys, hwdev));
+ else
+ rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, batch_amounts, keys, hwdev));
#ifdef DBG
- CHECK_AND_ASSERT_THROW_MES(verBulletproof(rv.p.bulletproofs.back()), "verBulletproof failed on newly created proof");
+ if (plus)
+ CHECK_AND_ASSERT_THROW_MES(verBulletproofPlus(rv.p.bulletproofs_plus.back()), "verBulletproofPlus failed on newly created proof");
+ else
+ CHECK_AND_ASSERT_THROW_MES(verBulletproof(rv.p.bulletproofs.back()), "verBulletproof failed on newly created proof");
#endif
}
for (i = 0; i < batch_size; ++i)
{
- rv.outPk[i + amounts_proved].mask = rct::scalarmult8(C[i]);
+ if (plus)
+ rv.outPk[i + amounts_proved].mask = C[i];
+ else
+ rv.outPk[i + amounts_proved].mask = rct::scalarmult8(C[i]);
outSk[i + amounts_proved].mask = masks[i];
}
amounts_proved += batch_size;
@@ -1189,7 +1294,7 @@ namespace rct {
//mask amount and mask
rv.ecdhInfo[i].mask = copy(outSk[i].mask);
rv.ecdhInfo[i].amount = d2h(outamounts[i]);
- hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG);
+ hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus);
}
//set txn fee
@@ -1197,9 +1302,9 @@ namespace rct {
// TODO: unused ??
// key txnFeeKey = scalarmultH(d2h(rv.txnFee));
rv.mixRing = mixRing;
- keyV &pseudoOuts = bulletproof ? rv.p.pseudoOuts : rv.pseudoOuts;
+ keyV &pseudoOuts = bulletproof_or_plus ? rv.p.pseudoOuts : rv.pseudoOuts;
pseudoOuts.resize(inamounts.size());
- if (rv.type == RCTTypeCLSAG)
+ if (is_rct_clsag(rv.type))
rv.p.CLSAGs.resize(inamounts.size());
else
rv.p.MGs.resize(inamounts.size());
@@ -1218,11 +1323,11 @@ namespace rct {
if (msout)
{
msout->c.resize(inamounts.size());
- msout->mu_p.resize(rv.type == RCTTypeCLSAG ? inamounts.size() : 0);
+ msout->mu_p.resize(is_rct_clsag(rv.type) ? inamounts.size() : 0);
}
for (i = 0 ; i < inamounts.size(); i++)
{
- if (rv.type == RCTTypeCLSAG)
+ if (is_rct_clsag(rv.type))
{
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);
}
@@ -1328,20 +1433,25 @@ namespace rct {
tools::threadpool& tpool = tools::threadpool::getInstance();
tools::threadpool::waiter waiter(tpool);
std::deque<bool> results;
- std::vector<const Bulletproof*> proofs;
+ std::vector<const Bulletproof*> bp_proofs;
+ std::vector<const BulletproofPlus*> bpp_proofs;
size_t max_non_bp_proofs = 0, offset = 0;
for (const rctSig *rvp: rvv)
{
CHECK_AND_ASSERT_MES(rvp, false, "rctSig pointer is NULL");
const rctSig &rv = *rvp;
- CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG,
+ CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus,
false, "verRctSemanticsSimple called on non simple rctSig");
const bool bulletproof = is_rct_bulletproof(rv.type);
- if (bulletproof)
+ const bool bulletproof_plus = is_rct_bulletproof_plus(rv.type);
+ if (bulletproof || bulletproof_plus)
{
- CHECK_AND_ASSERT_MES(rv.outPk.size() == n_bulletproof_amounts(rv.p.bulletproofs), false, "Mismatched sizes of outPk and bulletproofs");
- if (rv.type == RCTTypeCLSAG)
+ if (bulletproof_plus)
+ CHECK_AND_ASSERT_MES(rv.outPk.size() == n_bulletproof_plus_amounts(rv.p.bulletproofs_plus), false, "Mismatched sizes of outPk and bulletproofs_plus");
+ else
+ CHECK_AND_ASSERT_MES(rv.outPk.size() == n_bulletproof_amounts(rv.p.bulletproofs), false, "Mismatched sizes of outPk and bulletproofs");
+ if (is_rct_clsag(rv.type))
{
CHECK_AND_ASSERT_MES(rv.p.MGs.empty(), false, "MGs are not empty for CLSAG");
CHECK_AND_ASSERT_MES(rv.p.pseudoOuts.size() == rv.p.CLSAGs.size(), false, "Mismatched sizes of rv.p.pseudoOuts and rv.p.CLSAGs");
@@ -1361,7 +1471,7 @@ namespace rct {
}
CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.ecdhInfo.size(), false, "Mismatched sizes of outPk and rv.ecdhInfo");
- if (!bulletproof)
+ if (!bulletproof && !bulletproof_plus)
max_non_bp_proofs += rv.p.rangeSigs.size();
}
@@ -1371,11 +1481,15 @@ namespace rct {
const rctSig &rv = *rvp;
const bool bulletproof = is_rct_bulletproof(rv.type);
- const keyV &pseudoOuts = bulletproof ? rv.p.pseudoOuts : rv.pseudoOuts;
+ const bool bulletproof_plus = is_rct_bulletproof_plus(rv.type);
+ const keyV &pseudoOuts = bulletproof || bulletproof_plus ? rv.p.pseudoOuts : rv.pseudoOuts;
rct::keyV masks(rv.outPk.size());
for (size_t i = 0; i < rv.outPk.size(); i++) {
- masks[i] = rv.outPk[i].mask;
+ if (bulletproof_plus)
+ masks[i] = rct::scalarmult8(rv.outPk[i].mask);
+ else
+ masks[i] = rv.outPk[i].mask;
}
key sumOutpks = addKeys(masks);
DP(sumOutpks);
@@ -1391,10 +1505,15 @@ namespace rct {
return false;
}
- if (bulletproof)
+ if (bulletproof_plus)
+ {
+ for (size_t i = 0; i < rv.p.bulletproofs_plus.size(); i++)
+ bpp_proofs.push_back(&rv.p.bulletproofs_plus[i]);
+ }
+ else if (bulletproof)
{
for (size_t i = 0; i < rv.p.bulletproofs.size(); i++)
- proofs.push_back(&rv.p.bulletproofs[i]);
+ bp_proofs.push_back(&rv.p.bulletproofs[i]);
}
else
{
@@ -1403,9 +1522,18 @@ namespace rct {
offset += rv.p.rangeSigs.size();
}
}
- if (!proofs.empty() && !verBulletproof(proofs))
+ if (!bpp_proofs.empty() && !verBulletproofPlus(bpp_proofs))
+ {
+ LOG_PRINT_L1("Aggregate range proof verified failed");
+ if (!waiter.wait())
+ return false;
+ return false;
+ }
+ if (!bp_proofs.empty() && !verBulletproof(bp_proofs))
{
LOG_PRINT_L1("Aggregate range proof verified failed");
+ if (!waiter.wait())
+ return false;
return false;
}
@@ -1445,11 +1573,12 @@ namespace rct {
{
PERF_TIMER(verRctNonSemanticsSimple);
- CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG,
+ CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus,
false, "verRctNonSemanticsSimple called on non simple rctSig");
const bool bulletproof = is_rct_bulletproof(rv.type);
+ const bool bulletproof_plus = is_rct_bulletproof_plus(rv.type);
// semantics check is early, and mixRing/MGs aren't resolved yet
- if (bulletproof)
+ if (bulletproof || bulletproof_plus)
CHECK_AND_ASSERT_MES(rv.p.pseudoOuts.size() == rv.mixRing.size(), false, "Mismatched sizes of rv.p.pseudoOuts and mixRing");
else
CHECK_AND_ASSERT_MES(rv.pseudoOuts.size() == rv.mixRing.size(), false, "Mismatched sizes of rv.pseudoOuts and mixRing");
@@ -1460,7 +1589,7 @@ namespace rct {
tools::threadpool& tpool = tools::threadpool::getInstance();
tools::threadpool::waiter waiter(tpool);
- const keyV &pseudoOuts = bulletproof ? rv.p.pseudoOuts : rv.pseudoOuts;
+ const keyV &pseudoOuts = bulletproof || bulletproof_plus ? rv.p.pseudoOuts : rv.pseudoOuts;
const key message = get_pre_mlsag_hash(rv, hw::get_device("default"));
@@ -1468,10 +1597,8 @@ namespace rct {
results.resize(rv.mixRing.size());
for (size_t i = 0 ; i < rv.mixRing.size() ; i++) {
tpool.submit(&waiter, [&, i] {
- if (rv.type == RCTTypeCLSAG)
- {
+ if (is_rct_clsag(rv.type))
results[i] = verRctCLSAGSimple(message, rv.p.CLSAGs[i], rv.mixRing[i], pseudoOuts[i]);
- }
else
results[i] = verRctMGSimple(message, rv.p.MGs[i], rv.mixRing[i], pseudoOuts[i]);
});
@@ -1518,10 +1645,12 @@ namespace rct {
//mask amount and mask
ecdhTuple ecdh_info = rv.ecdhInfo[i];
- hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG);
+ hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus);
mask = ecdh_info.mask;
key amount = ecdh_info.amount;
key C = rv.outPk[i].mask;
+ if (is_rct_bulletproof_plus(rv.type))
+ C = scalarmult8(C);
DP("C");
DP(C);
key Ctmp;
@@ -1542,16 +1671,19 @@ namespace rct {
}
xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i, key &mask, hw::device &hwdev) {
- CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG, false, "decodeRct called on non simple rctSig");
+ CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus,
+ false, "decodeRct called on non simple rctSig");
CHECK_AND_ASSERT_THROW_MES(i < rv.ecdhInfo.size(), "Bad index");
CHECK_AND_ASSERT_THROW_MES(rv.outPk.size() == rv.ecdhInfo.size(), "Mismatched sizes of rv.outPk and rv.ecdhInfo");
//mask amount and mask
ecdhTuple ecdh_info = rv.ecdhInfo[i];
- hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG);
+ hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus);
mask = ecdh_info.mask;
key amount = ecdh_info.amount;
key C = rv.outPk[i].mask;
+ if (is_rct_bulletproof_plus(rv.type))
+ C = scalarmult8(C);
DP("C");
DP(C);
key Ctmp;
@@ -1574,6 +1706,7 @@ namespace rct {
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");
@@ -1598,7 +1731,7 @@ namespace rct {
}
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(rv.type == RCTTypeCLSAG, false, "unsupported rct type");
+ 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");
@@ -1620,7 +1753,7 @@ namespace rct {
}
bool signMultisig(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key) {
- if (rv.type == RCTTypeCLSAG)
+ 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/rctTypes.cpp b/src/ringct/rctTypes.cpp
index 1f674056d..c22b0524f 100644
--- a/src/ringct/rctTypes.cpp
+++ b/src/ringct/rctTypes.cpp
@@ -196,6 +196,7 @@ namespace rct {
case RCTTypeBulletproof:
case RCTTypeBulletproof2:
case RCTTypeCLSAG:
+ case RCTTypeBulletproofPlus:
return true;
default:
return false;
@@ -215,6 +216,17 @@ namespace rct {
}
}
+ bool is_rct_bulletproof_plus(int type)
+ {
+ switch (type)
+ {
+ case RCTTypeBulletproofPlus:
+ return true;
+ default:
+ return false;
+ }
+ }
+
bool is_rct_borromean(int type)
{
switch (type)
@@ -227,19 +239,34 @@ namespace rct {
}
}
- size_t n_bulletproof_amounts(const Bulletproof &proof)
+ bool is_rct_clsag(int type)
{
- CHECK_AND_ASSERT_MES(proof.L.size() >= 6, 0, "Invalid bulletproof L size");
- CHECK_AND_ASSERT_MES(proof.L.size() == proof.R.size(), 0, "Mismatched bulletproof L/R size");
+ switch (type)
+ {
+ case RCTTypeCLSAG:
+ case RCTTypeBulletproofPlus:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ static size_t n_bulletproof_amounts_base(const size_t L_size, const size_t R_size, const size_t V_size, const size_t max_outputs)
+ {
+ CHECK_AND_ASSERT_MES(L_size >= 6, 0, "Invalid bulletproof L size");
+ CHECK_AND_ASSERT_MES(L_size == R_size, 0, "Mismatched bulletproof L/R size");
static const size_t extra_bits = 4;
- static_assert((1 << extra_bits) == BULLETPROOF_MAX_OUTPUTS, "log2(BULLETPROOF_MAX_OUTPUTS) is out of date");
- CHECK_AND_ASSERT_MES(proof.L.size() <= 6 + extra_bits, 0, "Invalid bulletproof L size");
- CHECK_AND_ASSERT_MES(proof.V.size() <= (1u<<(proof.L.size()-6)), 0, "Invalid bulletproof V/L");
- CHECK_AND_ASSERT_MES(proof.V.size() * 2 > (1u<<(proof.L.size()-6)), 0, "Invalid bulletproof V/L");
- CHECK_AND_ASSERT_MES(proof.V.size() > 0, 0, "Empty bulletproof");
- return proof.V.size();
+ CHECK_AND_ASSERT_MES((1 << extra_bits) == max_outputs, 0, "log2(max_outputs) is out of date");
+ CHECK_AND_ASSERT_MES(L_size <= 6 + extra_bits, 0, "Invalid bulletproof L size");
+ CHECK_AND_ASSERT_MES(V_size <= (1u<<(L_size-6)), 0, "Invalid bulletproof V/L");
+ CHECK_AND_ASSERT_MES(V_size * 2 > (1u<<(L_size-6)), 0, "Invalid bulletproof V/L");
+ CHECK_AND_ASSERT_MES(V_size > 0, 0, "Empty bulletproof");
+ return V_size;
}
+ size_t n_bulletproof_amounts(const Bulletproof &proof) { return n_bulletproof_amounts_base(proof.L.size(), proof.R.size(), proof.V.size(), BULLETPROOF_MAX_OUTPUTS); }
+ size_t n_bulletproof_plus_amounts(const BulletproofPlus &proof) { return n_bulletproof_amounts_base(proof.L.size(), proof.R.size(), proof.V.size(), BULLETPROOF_PLUS_MAX_OUTPUTS); }
+
size_t n_bulletproof_amounts(const std::vector<Bulletproof> &proofs)
{
size_t n = 0;
@@ -254,15 +281,31 @@ namespace rct {
return n;
}
- size_t n_bulletproof_max_amounts(const Bulletproof &proof)
+ size_t n_bulletproof_plus_amounts(const std::vector<BulletproofPlus> &proofs)
+ {
+ size_t n = 0;
+ for (const BulletproofPlus &proof: proofs)
+ {
+ size_t n2 = n_bulletproof_plus_amounts(proof);
+ CHECK_AND_ASSERT_MES(n2 < std::numeric_limits<uint32_t>::max() - n, 0, "Invalid number of bulletproofs");
+ if (n2 == 0)
+ return 0;
+ n += n2;
+ }
+ return n;
+ }
+
+ static size_t n_bulletproof_max_amounts_base(size_t L_size, size_t R_size, size_t max_outputs)
{
- CHECK_AND_ASSERT_MES(proof.L.size() >= 6, 0, "Invalid bulletproof L size");
- CHECK_AND_ASSERT_MES(proof.L.size() == proof.R.size(), 0, "Mismatched bulletproof L/R size");
+ CHECK_AND_ASSERT_MES(L_size >= 6, 0, "Invalid bulletproof L size");
+ CHECK_AND_ASSERT_MES(L_size == R_size, 0, "Mismatched bulletproof L/R size");
static const size_t extra_bits = 4;
- static_assert((1 << extra_bits) == BULLETPROOF_MAX_OUTPUTS, "log2(BULLETPROOF_MAX_OUTPUTS) is out of date");
- CHECK_AND_ASSERT_MES(proof.L.size() <= 6 + extra_bits, 0, "Invalid bulletproof L size");
- return 1 << (proof.L.size() - 6);
+ CHECK_AND_ASSERT_MES((1 << extra_bits) == max_outputs, 0, "log2(max_outputs) is out of date");
+ CHECK_AND_ASSERT_MES(L_size <= 6 + extra_bits, 0, "Invalid bulletproof L size");
+ return 1 << (L_size - 6);
}
+ size_t n_bulletproof_max_amounts(const Bulletproof &proof) { return n_bulletproof_max_amounts_base(proof.L.size(), proof.R.size(), BULLETPROOF_MAX_OUTPUTS); }
+ size_t n_bulletproof_plus_max_amounts(const BulletproofPlus &proof) { return n_bulletproof_max_amounts_base(proof.L.size(), proof.R.size(), BULLETPROOF_PLUS_MAX_OUTPUTS); }
size_t n_bulletproof_max_amounts(const std::vector<Bulletproof> &proofs)
{
@@ -278,4 +321,18 @@ namespace rct {
return n;
}
+ size_t n_bulletproof_plus_max_amounts(const std::vector<BulletproofPlus> &proofs)
+ {
+ size_t n = 0;
+ for (const BulletproofPlus &proof: proofs)
+ {
+ size_t n2 = n_bulletproof_plus_max_amounts(proof);
+ CHECK_AND_ASSERT_MES(n2 < std::numeric_limits<uint32_t>::max() - n, 0, "Invalid number of bulletproofs");
+ if (n2 == 0)
+ return 0;
+ n += n2;
+ }
+ return n;
+ }
+
}
diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h
index 278ff4164..59ed4d6a6 100644
--- a/src/ringct/rctTypes.h
+++ b/src/ringct/rctTypes.h
@@ -238,11 +238,48 @@ namespace rct {
END_SERIALIZE()
};
+ struct BulletproofPlus
+ {
+ rct::keyV V;
+ rct::key A, A1, B;
+ rct::key r1, s1, d1;
+ rct::keyV L, R;
+
+ BulletproofPlus() {}
+ BulletproofPlus(const rct::key &V, const rct::key &A, const rct::key &A1, const rct::key &B, const rct::key &r1, const rct::key &s1, const rct::key &d1, const rct::keyV &L, const rct::keyV &R):
+ V({V}), A(A), A1(A1), B(B), r1(r1), s1(s1), d1(d1), L(L), R(R) {}
+ BulletproofPlus(const rct::keyV &V, const rct::key &A, const rct::key &A1, const rct::key &B, const rct::key &r1, const rct::key &s1, const rct::key &d1, const rct::keyV &L, const rct::keyV &R):
+ V(V), A(A), A1(A1), B(B), r1(r1), s1(s1), d1(d1), L(L), R(R) {}
+
+ bool operator==(const BulletproofPlus &other) const { return V == other.V && A == other.A && A1 == other.A1 && B == other.B && r1 == other.r1 && s1 == other.s1 && d1 == other.d1 && L == other.L && R == other.R; }
+
+ BEGIN_SERIALIZE_OBJECT()
+ // Commitments aren't saved, they're restored via outPk
+ // FIELD(V)
+ FIELD(A)
+ FIELD(A1)
+ FIELD(B)
+ FIELD(r1)
+ FIELD(s1)
+ FIELD(d1)
+ FIELD(L)
+ FIELD(R)
+
+ if (L.empty() || L.size() != R.size())
+ return false;
+ END_SERIALIZE()
+ };
+
size_t n_bulletproof_amounts(const Bulletproof &proof);
size_t n_bulletproof_max_amounts(const Bulletproof &proof);
size_t n_bulletproof_amounts(const std::vector<Bulletproof> &proofs);
size_t n_bulletproof_max_amounts(const std::vector<Bulletproof> &proofs);
+ size_t n_bulletproof_plus_amounts(const BulletproofPlus &proof);
+ size_t n_bulletproof_plus_max_amounts(const BulletproofPlus &proof);
+ size_t n_bulletproof_plus_amounts(const std::vector<BulletproofPlus> &proofs);
+ size_t n_bulletproof_plus_max_amounts(const std::vector<BulletproofPlus> &proofs);
+
//A container to hold all signatures necessary for RingCT
// rangeSigs holds all the rangeproof data of a transaction
// MG holds the MLSAG signature of a transaction
@@ -257,6 +294,7 @@ namespace rct {
RCTTypeBulletproof = 3,
RCTTypeBulletproof2 = 4,
RCTTypeCLSAG = 5,
+ RCTTypeBulletproofPlus = 6,
};
enum RangeProofType { RangeProofBorromean, RangeProofBulletproof, RangeProofMultiOutputBulletproof, RangeProofPaddedBulletproof };
struct RCTConfig {
@@ -285,7 +323,7 @@ namespace rct {
FIELD(type)
if (type == RCTTypeNull)
return ar.good();
- if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2 && type != RCTTypeCLSAG)
+ if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2 && type != RCTTypeCLSAG && type != RCTTypeBulletproofPlus)
return false;
VARINT_FIELD(txnFee)
// inputs/outputs not saved, only here for serialization help
@@ -314,7 +352,7 @@ namespace rct {
return false;
for (size_t i = 0; i < outputs; ++i)
{
- if (type == RCTTypeBulletproof2 || type == RCTTypeCLSAG)
+ if (type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeBulletproofPlus)
{
ar.begin_object();
if (!typename Archive<W>::is_saving())
@@ -360,6 +398,7 @@ namespace rct {
struct rctSigPrunable {
std::vector<rangeSig> rangeSigs;
std::vector<Bulletproof> bulletproofs;
+ std::vector<BulletproofPlus> bulletproofs_plus;
std::vector<mgSig> MGs; // simple rct has N, full has 1
std::vector<clsag> CLSAGs;
keyV pseudoOuts; //C - for simple rct
@@ -376,9 +415,28 @@ namespace rct {
return false;
if (type == RCTTypeNull)
return ar.good();
- if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2 && type != RCTTypeCLSAG)
+ if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2 && type != RCTTypeCLSAG && type != RCTTypeBulletproofPlus)
return false;
- if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG)
+ if (type == RCTTypeBulletproofPlus)
+ {
+ uint32_t nbp = bulletproofs_plus.size();
+ VARINT_FIELD(nbp)
+ ar.tag("bpp");
+ ar.begin_array();
+ if (nbp > outputs)
+ return false;
+ PREPARE_CUSTOM_VECTOR_SERIALIZATION(nbp, bulletproofs_plus);
+ for (size_t i = 0; i < nbp; ++i)
+ {
+ FIELDS(bulletproofs_plus[i])
+ if (nbp - i > 1)
+ ar.delimit_array();
+ }
+ if (n_bulletproof_plus_max_amounts(bulletproofs_plus) < outputs)
+ return false;
+ ar.end_array();
+ }
+ else if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG)
{
uint32_t nbp = bulletproofs.size();
if (type == RCTTypeBulletproof2 || type == RCTTypeCLSAG)
@@ -416,7 +474,7 @@ namespace rct {
ar.end_array();
}
- if (type == RCTTypeCLSAG)
+ if (type == RCTTypeCLSAG || type == RCTTypeBulletproofPlus)
{
ar.tag("CLSAGs");
ar.begin_array();
@@ -507,7 +565,7 @@ namespace rct {
}
ar.end_array();
}
- if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG)
+ if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeBulletproofPlus)
{
ar.tag("pseudoOuts");
ar.begin_array();
@@ -528,6 +586,7 @@ namespace rct {
BEGIN_SERIALIZE_OBJECT()
FIELD(rangeSigs)
FIELD(bulletproofs)
+ FIELD(bulletproofs_plus)
FIELD(MGs)
FIELD(CLSAGs)
FIELD(pseudoOuts)
@@ -538,12 +597,12 @@ namespace rct {
keyV& get_pseudo_outs()
{
- return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG ? p.pseudoOuts : pseudoOuts;
+ return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeBulletproofPlus ? p.pseudoOuts : pseudoOuts;
}
keyV const& get_pseudo_outs() const
{
- return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG ? p.pseudoOuts : pseudoOuts;
+ return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeBulletproofPlus ? p.pseudoOuts : pseudoOuts;
}
BEGIN_SERIALIZE_OBJECT()
@@ -655,7 +714,9 @@ namespace rct {
bool is_rct_simple(int type);
bool is_rct_bulletproof(int type);
+ bool is_rct_bulletproof_plus(int type);
bool is_rct_borromean(int type);
+ bool is_rct_clsag(int type);
static inline const rct::key &pk2rct(const crypto::public_key &pk) { return (const rct::key&)pk; }
static inline const rct::key &sk2rct(const crypto::secret_key &sk) { return (const rct::key&)sk; }
@@ -711,6 +772,7 @@ VARIANT_TAG(debug_archive, rct::Bulletproof, "rct::bulletproof");
VARIANT_TAG(debug_archive, rct::multisig_kLRki, "rct::multisig_kLRki");
VARIANT_TAG(debug_archive, rct::multisig_out, "rct::multisig_out");
VARIANT_TAG(debug_archive, rct::clsag, "rct::clsag");
+VARIANT_TAG(debug_archive, rct::BulletproofPlus, "rct::bulletproof_plus");
VARIANT_TAG(binary_archive, rct::key, 0x90);
VARIANT_TAG(binary_archive, rct::key64, 0x91);
@@ -728,6 +790,7 @@ VARIANT_TAG(binary_archive, rct::Bulletproof, 0x9c);
VARIANT_TAG(binary_archive, rct::multisig_kLRki, 0x9d);
VARIANT_TAG(binary_archive, rct::multisig_out, 0x9e);
VARIANT_TAG(binary_archive, rct::clsag, 0x9f);
+VARIANT_TAG(binary_archive, rct::BulletproofPlus, 0xa0);
VARIANT_TAG(json_archive, rct::key, "rct_key");
VARIANT_TAG(json_archive, rct::key64, "rct_key64");
@@ -745,5 +808,6 @@ VARIANT_TAG(json_archive, rct::Bulletproof, "rct_bulletproof");
VARIANT_TAG(json_archive, rct::multisig_kLRki, "rct_multisig_kLR");
VARIANT_TAG(json_archive, rct::multisig_out, "rct_multisig_out");
VARIANT_TAG(json_archive, rct::clsag, "rct_clsag");
+VARIANT_TAG(json_archive, rct::BulletproofPlus, "rct_bulletproof_plus");
#endif /* RCTTYPES_H */
diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp
index e114ea7c6..40b1b1000 100644
--- a/src/rpc/core_rpc_server.cpp
+++ b/src/rpc/core_rpc_server.cpp
@@ -478,6 +478,7 @@ 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;
@@ -534,6 +535,7 @@ namespace cryptonote
res.version = restricted ? "" : MONERO_VERSION_FULL;
res.synchronized = check_core_ready();
res.busy_syncing = m_p2p.get_payload_object().is_busy_syncing();
+ res.restricted = restricted;
res.status = CORE_RPC_STATUS_OK;
return true;
@@ -582,6 +584,7 @@ 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())
@@ -592,7 +595,7 @@ namespace cryptonote
if (last_block_hash == req.block_ids.front())
{
res.start_height = 0;
- res.current_height = m_core.get_current_blockchain_height();
+ res.current_height = last_block_height + 1;
res.status = CORE_RPC_STATUS_OK;
return true;
}
@@ -713,6 +716,7 @@ 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;
@@ -1574,6 +1578,7 @@ 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;
@@ -1598,6 +1603,7 @@ 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;
@@ -1700,11 +1706,14 @@ 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];
- if(m_core.get_current_blockchain_height() <= h)
+ uint64_t blockchain_height = m_core.get_current_blockchain_height();
+ if(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(m_core.get_current_blockchain_height() - 1);
+ 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;
}
res = string_tools::pod_to_hex(m_core.get_block_id_by_height(h));
return true;
@@ -1854,6 +1863,7 @@ 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;
@@ -2327,6 +2337,7 @@ 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);
@@ -2367,6 +2378,8 @@ 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);
@@ -2426,13 +2439,6 @@ 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)
{
@@ -2442,6 +2448,16 @@ 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);
@@ -2486,10 +2502,12 @@ 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;
- if(m_core.get_current_blockchain_height() <= req.height)
+ 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)
{
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(m_core.get_current_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(blockchain_height - 1);
return false;
}
CHECK_PAYMENT_MIN1(req, res, COST_PER_BLOCK_HEADER, false);
@@ -2522,6 +2540,7 @@ 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())
@@ -2536,10 +2555,11 @@ namespace cryptonote
}
else
{
- if(m_core.get_current_blockchain_height() <= req.height)
+ uint64_t blockchain_height = m_core.get_current_blockchain_height();
+ if(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(m_core.get_current_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(blockchain_height - 1);
return false;
}
block_hash = m_core.get_block_id_by_height(req.height);
@@ -2847,6 +2867,7 @@ 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)
{
@@ -2878,6 +2899,7 @@ 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();
@@ -3180,6 +3202,7 @@ 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_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h
index 166fb39ea..d25196841 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 9
+#define CORE_RPC_VERSION_MINOR 10
#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)
@@ -689,6 +689,7 @@ namespace cryptonote
bool busy_syncing;
std::string version;
bool synchronized;
+ bool restricted;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE_PARENT(rpc_access_response_base)
@@ -730,6 +731,7 @@ namespace cryptonote
KV_SERIALIZE(busy_syncing)
KV_SERIALIZE(version)
KV_SERIALIZE(synchronized)
+ KV_SERIALIZE(restricted)
END_KV_SERIALIZE_MAP()
};
typedef epee::misc_utils::struct_init<response_t> response;
diff --git a/src/rpc/rpc_args.cpp b/src/rpc/rpc_args.cpp
index 0966fb6d2..b2d019ae7 100644
--- a/src/rpc/rpc_args.cpp
+++ b/src/rpc/rpc_args.cpp
@@ -247,12 +247,6 @@ namespace cryptonote
auto access_control_origins_input = command_line::get_arg(vm, arg.rpc_access_control_origins);
if (!access_control_origins_input.empty())
{
- if (!config.login)
- {
- LOG_ERROR(arg.rpc_access_control_origins.name << tr(" requires RPC server password --") << arg.rpc_login.name << tr(" cannot be empty"));
- return boost::none;
- }
-
std::vector<std::string> access_control_origins;
boost::split(access_control_origins, access_control_origins_input, boost::is_any_of(","));
std::for_each(access_control_origins.begin(), access_control_origins.end(), std::bind(&boost::trim<std::string>, std::placeholders::_1, std::locale::classic()));
diff --git a/src/serialization/json_object.cpp b/src/serialization/json_object.cpp
index b03da1edc..bd715dcfd 100644
--- a/src/serialization/json_object.cpp
+++ b/src/serialization/json_object.cpp
@@ -300,7 +300,7 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::transaction& tx)
}
const auto& rsig = tx.rct_signatures;
- if (!cryptonote::is_coinbase(tx) && rsig.p.bulletproofs.empty() && rsig.p.rangeSigs.empty() && rsig.p.MGs.empty() && rsig.get_pseudo_outs().empty() && sigs == val.MemberEnd())
+ if (!cryptonote::is_coinbase(tx) && rsig.p.bulletproofs.empty() && rsig.p.bulletproofs_plus.empty() && rsig.p.rangeSigs.empty() && rsig.p.MGs.empty() && rsig.get_pseudo_outs().empty() && sigs == val.MemberEnd())
tx.pruned = true;
}
@@ -1100,13 +1100,14 @@ void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const rct::rctSig&
}
// prunable
- if (!sig.p.bulletproofs.empty() || !sig.p.rangeSigs.empty() || !sig.p.MGs.empty() || !sig.get_pseudo_outs().empty())
+ if (!sig.p.bulletproofs.empty() || !sig.p.bulletproofs_plus.empty() || !sig.p.rangeSigs.empty() || !sig.p.MGs.empty() || !sig.get_pseudo_outs().empty())
{
dest.Key("prunable");
dest.StartObject();
INSERT_INTO_JSON_OBJECT(dest, range_proofs, sig.p.rangeSigs);
INSERT_INTO_JSON_OBJECT(dest, bulletproofs, sig.p.bulletproofs);
+ INSERT_INTO_JSON_OBJECT(dest, bulletproofs_plus, sig.p.bulletproofs_plus);
INSERT_INTO_JSON_OBJECT(dest, mlsags, sig.p.MGs);
INSERT_INTO_JSON_OBJECT(dest, pseudo_outs, sig.get_pseudo_outs());
@@ -1141,6 +1142,7 @@ void fromJsonValue(const rapidjson::Value& val, rct::rctSig& sig)
GET_FROM_JSON_OBJECT(prunable->value, sig.p.rangeSigs, range_proofs);
GET_FROM_JSON_OBJECT(prunable->value, sig.p.bulletproofs, bulletproofs);
+ GET_FROM_JSON_OBJECT(prunable->value, sig.p.bulletproofs_plus, bulletproofs_plus);
GET_FROM_JSON_OBJECT(prunable->value, sig.p.MGs, mlsags);
GET_FROM_JSON_OBJECT(prunable->value, pseudo_outs, pseudo_outs);
@@ -1150,6 +1152,7 @@ void fromJsonValue(const rapidjson::Value& val, rct::rctSig& sig)
{
sig.p.rangeSigs.clear();
sig.p.bulletproofs.clear();
+ sig.p.bulletproofs_plus.clear();
sig.p.MGs.clear();
sig.get_pseudo_outs().clear();
}
@@ -1258,6 +1261,41 @@ void fromJsonValue(const rapidjson::Value& val, rct::Bulletproof& p)
GET_FROM_JSON_OBJECT(val, p.t, t);
}
+void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const rct::BulletproofPlus& p)
+{
+ dest.StartObject();
+
+ INSERT_INTO_JSON_OBJECT(dest, V, p.V);
+ INSERT_INTO_JSON_OBJECT(dest, A, p.A);
+ INSERT_INTO_JSON_OBJECT(dest, A1, p.A1);
+ INSERT_INTO_JSON_OBJECT(dest, B, p.B);
+ INSERT_INTO_JSON_OBJECT(dest, r1, p.r1);
+ INSERT_INTO_JSON_OBJECT(dest, s1, p.s1);
+ INSERT_INTO_JSON_OBJECT(dest, d1, p.d1);
+ INSERT_INTO_JSON_OBJECT(dest, L, p.L);
+ INSERT_INTO_JSON_OBJECT(dest, R, p.R);
+
+ dest.EndObject();
+}
+
+void fromJsonValue(const rapidjson::Value& val, rct::BulletproofPlus& p)
+{
+ if (!val.IsObject())
+ {
+ throw WRONG_TYPE("json object");
+ }
+
+ GET_FROM_JSON_OBJECT(val, p.V, V);
+ GET_FROM_JSON_OBJECT(val, p.A, A);
+ GET_FROM_JSON_OBJECT(val, p.A1, A1);
+ GET_FROM_JSON_OBJECT(val, p.B, B);
+ GET_FROM_JSON_OBJECT(val, p.r1, r1);
+ GET_FROM_JSON_OBJECT(val, p.s1, s1);
+ GET_FROM_JSON_OBJECT(val, p.d1, d1);
+ GET_FROM_JSON_OBJECT(val, p.L, L);
+ GET_FROM_JSON_OBJECT(val, p.R, R);
+}
+
void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const rct::boroSig& sig)
{
dest.StartObject();
diff --git a/src/serialization/json_object.h b/src/serialization/json_object.h
index c858faf5a..4514ad568 100644
--- a/src/serialization/json_object.h
+++ b/src/serialization/json_object.h
@@ -292,6 +292,9 @@ void fromJsonValue(const rapidjson::Value& val, rct::rangeSig& sig);
void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const rct::Bulletproof& p);
void fromJsonValue(const rapidjson::Value& val, rct::Bulletproof& p);
+void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const rct::BulletproofPlus& p);
+void fromJsonValue(const rapidjson::Value& val, rct::BulletproofPlus& p);
+
void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const rct::boroSig& sig);
void fromJsonValue(const rapidjson::Value& val, rct::boroSig& sig);
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp
index fd784c5ae..752adc640 100644
--- a/src/simplewallet/simplewallet.cpp
+++ b/src/simplewallet/simplewallet.cpp
@@ -235,7 +235,6 @@ 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_FINALIZE_MULTISIG("finalize_multisig <string> [<string>...]");
const char* USAGE_EXCHANGE_MULTISIG_KEYS("exchange_multisig_keys <string> [<string>...]");
const char* USAGE_EXPORT_MULTISIG_INFO("export_multisig_info <filename>");
const char* USAGE_IMPORT_MULTISIG_INFO("import_multisig_info <filename> [<filename>...]");
@@ -1021,7 +1020,7 @@ bool simple_wallet::prepare_multisig_main(const std::vector<std::string> &args,
SCOPED_WALLET_UNLOCK_ON_BAD_PASSWORD(return false;);
- std::string multisig_info = m_wallet->get_multisig_info();
+ std::string multisig_info = m_wallet->get_multisig_first_kex_msg();
success_msg_writer() << multisig_info;
success_msg_writer() << tr("Send this multisig info to all other participants, then use make_multisig <threshold> <info1> [<info2>...] with others' multisig info");
success_msg_writer() << tr("This includes the PRIVATE view key, so needs to be disclosed only to that multisig wallet's participants ");
@@ -1122,58 +1121,6 @@ bool simple_wallet::make_multisig_main(const std::vector<std::string> &args, boo
return true;
}
-bool simple_wallet::finalize_multisig(const std::vector<std::string> &args)
-{
- bool ready;
- if (m_wallet->key_on_device())
- {
- fail_msg_writer() << tr("command not supported by HW wallet");
- return true;
- }
-
- const auto pwd_container = get_and_verify_password();
- if(pwd_container == boost::none)
- {
- fail_msg_writer() << tr("Your original password was incorrect.");
- return true;
- }
-
- if (!m_wallet->multisig(&ready))
- {
- fail_msg_writer() << tr("This wallet is not multisig");
- return true;
- }
- if (ready)
- {
- fail_msg_writer() << tr("This wallet is already finalized");
- return true;
- }
-
- LOCK_IDLE_SCOPE();
-
- if (args.size() < 2)
- {
- PRINT_USAGE(USAGE_FINALIZE_MULTISIG);
- return true;
- }
-
- try
- {
- if (!m_wallet->finalize_multisig(pwd_container->password(), args))
- {
- fail_msg_writer() << tr("Failed to finalize multisig");
- return true;
- }
- }
- catch (const std::exception &e)
- {
- fail_msg_writer() << tr("Failed to finalize multisig: ") << e.what();
- return true;
- }
-
- return true;
-}
-
bool simple_wallet::exchange_multisig_keys(const std::vector<std::string> &args)
{
exchange_multisig_keys_main(args, false);
@@ -3041,6 +2988,19 @@ bool simple_wallet::set_track_uses(const std::vector<std::string> &args/* = std:
return true;
}
+bool simple_wallet::set_show_wallet_name_when_locked(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
+{
+ const auto pwd_container = get_and_verify_password();
+ if (pwd_container)
+ {
+ parse_bool_and_use(args[1], [&](bool r) {
+ m_wallet->show_wallet_name_when_locked(r);
+ m_wallet->rewrite(m_wallet_file, pwd_container->password());
+ });
+ }
+ return true;
+}
+
bool simple_wallet::set_inactivity_lock_timeout(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
{
#ifdef _WIN32
@@ -3484,6 +3444,8 @@ simple_wallet::simple_wallet()
" Whether to automatically start mining for RPC payment if the daemon requires it.\n"
"credits-target <unsigned int>\n"
" 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 "
"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",
@@ -3544,7 +3506,7 @@ simple_wallet::simple_wallet()
"** Set of address indices used as inputs in this transfer."));
m_cmd_binder.set_handler("export_transfers",
boost::bind(&simple_wallet::on_command, this, &simple_wallet::export_transfers, _1),
- tr("export_transfers [in|out|all|pending|failed|pool|coinbase] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]] [output=<filepath>]"),
+ tr("export_transfers [in|out|all|pending|failed|pool|coinbase] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]] [output=<filepath>] [option=<with_keys>]"),
tr("Export to CSV the incoming/outgoing transfers within an optional height range."));
m_cmd_binder.set_handler("unspent_outputs",
boost::bind(&simple_wallet::on_command, this, &simple_wallet::unspent_outputs, _1),
@@ -3627,10 +3589,6 @@ simple_wallet::simple_wallet()
m_cmd_binder.set_handler("make_multisig", boost::bind(&simple_wallet::on_command, this, &simple_wallet::make_multisig, _1),
tr(USAGE_MAKE_MULTISIG),
tr("Turn this wallet into a multisig wallet"));
- m_cmd_binder.set_handler("finalize_multisig",
- boost::bind(&simple_wallet::on_command, this, &simple_wallet::finalize_multisig, _1),
- tr(USAGE_FINALIZE_MULTISIG),
- tr("Turn this wallet into a multisig wallet, extra step for N-1/N wallets"));
m_cmd_binder.set_handler("exchange_multisig_keys",
boost::bind(&simple_wallet::on_command, this, &simple_wallet::exchange_multisig_keys, _1),
tr(USAGE_EXCHANGE_MULTISIG_KEYS),
@@ -3893,6 +3851,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
success_msg_writer() << "setup-background-mining = " << setup_background_mining_string;
success_msg_writer() << "device-name = " << m_wallet->device_name();
success_msg_writer() << "export-format = " << (m_wallet->export_format() == tools::wallet2::ExportFormat::Ascii ? "ascii" : "binary");
+ success_msg_writer() << "show-wallet-name-when-locked = " << m_wallet->show_wallet_name_when_locked();
success_msg_writer() << "inactivity-lock-timeout = " << m_wallet->inactivity_lock_timeout()
#ifdef _WIN32
<< " (disabled on Windows)"
@@ -3960,6 +3919,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
CHECK_SIMPLE_VARIABLE("ignore-outputs-above", set_ignore_outputs_above, tr("amount"));
CHECK_SIMPLE_VARIABLE("ignore-outputs-below", set_ignore_outputs_below, tr("amount"));
CHECK_SIMPLE_VARIABLE("track-uses", set_track_uses, tr("0 or 1"));
+ CHECK_SIMPLE_VARIABLE("show-wallet-name-when-locked", set_show_wallet_name_when_locked, tr("1 or 0"));
CHECK_SIMPLE_VARIABLE("inactivity-lock-timeout", set_inactivity_lock_timeout, tr("unsigned integer (seconds, 0 to disable)"));
CHECK_SIMPLE_VARIABLE("setup-background-mining", set_setup_background_mining, tr("1/yes or 0/no"));
CHECK_SIMPLE_VARIABLE("device-name", set_device_name, tr("<device_name[:device_spec]>"));
@@ -6520,6 +6480,16 @@ void simple_wallet::check_for_inactivity_lock(bool user)
{
const char *inactivity_msg = user ? "" : tr("Locked due to inactivity.");
tools::msg_writer() << inactivity_msg << (inactivity_msg[0] ? " " : "") << tr("The wallet password is required to unlock the console.");
+
+ const bool show_wallet_name = m_wallet->show_wallet_name_when_locked();
+ if (show_wallet_name)
+ {
+ tools::msg_writer() << tr("Filename: ") << m_wallet->get_wallet_file();
+ tools::msg_writer() << tr("Network type: ") << (
+ m_wallet->nettype() == cryptonote::TESTNET ? tr("Testnet") :
+ m_wallet->nettype() == cryptonote::STAGENET ? tr("Stagenet") : tr("Mainnet")
+ );
+ }
try
{
if (get_and_verify_password())
@@ -8051,6 +8021,15 @@ bool simple_wallet::submit_transfer(const std::vector<std::string> &args_)
return true;
}
//----------------------------------------------------------------------------------------------------
+std::string get_tx_key_stream(crypto::secret_key tx_key, std::vector<crypto::secret_key> additional_tx_keys)
+{
+ ostringstream oss;
+ oss << epee::string_tools::pod_to_hex(tx_key);
+ for (size_t i = 0; i < additional_tx_keys.size(); ++i)
+ oss << epee::string_tools::pod_to_hex(additional_tx_keys[i]);
+ return oss.str();
+}
+
bool simple_wallet::get_tx_key(const std::vector<std::string> &args_)
{
std::vector<std::string> local_args = args_;
@@ -8080,11 +8059,8 @@ bool simple_wallet::get_tx_key(const std::vector<std::string> &args_)
bool found_tx_key = m_wallet->get_tx_key(txid, tx_key, additional_tx_keys);
if (found_tx_key)
{
- ostringstream oss;
- oss << epee::string_tools::pod_to_hex(tx_key);
- for (size_t i = 0; i < additional_tx_keys.size(); ++i)
- oss << epee::string_tools::pod_to_hex(additional_tx_keys[i]);
- success_msg_writer() << tr("Tx key: ") << oss.str();
+ std::string stream = get_tx_key_stream(tx_key, additional_tx_keys);
+ success_msg_writer() << tr("Tx key: ") << stream;
return true;
}
else
@@ -8895,13 +8871,11 @@ bool simple_wallet::export_transfers(const std::vector<std::string>& args_)
{
std::vector<std::string> local_args = args_;
- if(local_args.size() > 5) {
- fail_msg_writer() << tr("usage: export_transfers [in|out|all|pending|failed|pool|coinbase] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]] [output=<path>]");
+ if(local_args.size() > 6) {
+ fail_msg_writer() << tr("usage: export_transfers [in|out|all|pending|failed|pool|coinbase] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]] [output=<path>] [option=<with_keys>]");
return true;
}
- LOCK_IDLE_SCOPE();
-
std::vector<transfer_view> all_transfers;
// might consumes arguments in local_args
@@ -8915,17 +8889,36 @@ bool simple_wallet::export_transfers(const std::vector<std::string>& args_)
filename = local_args[0].substr(7, -1);
local_args.erase(local_args.begin());
}
+ // check for export with tx keys
+ bool export_keys = false;
+ if (local_args.size() > 0 && local_args[0].substr(0, 7) == "option=")
+ {
+ export_keys = local_args[0].substr(7, -1) == "with_keys";
+ local_args.erase(local_args.begin());
+ }
+ if (export_keys)
+ {
+ if (m_wallet->key_on_device() && m_wallet->get_account().get_device().get_type() != hw::device::TREZOR)
+ {
+ fail_msg_writer() << tr("command not supported by HW wallet");
+ return true;
+ }
+ SCOPED_WALLET_UNLOCK();
+ } else
+ {
+ LOCK_IDLE_SCOPE();
+ }
std::ofstream file(filename);
// header
file <<
- boost::format("%8.8s,%9.9s,%8.8s,%25.25s,%20.20s,%20.20s,%64.64s,%16.16s,%14.14s,%106.106s,%20.20s,%s,%s") %
- tr("block") % tr("direction") % tr("unlocked") % tr("timestamp") % tr("amount") % tr("running balance") % tr("hash") % tr("payment ID") % tr("fee") % tr("destination") % tr("amount") % tr("index") % tr("note")
+ boost::format("%8.8s,%9.9s,%8.8s,%25.25s,%20.20s,%20.20s,%64.64s,%16.16s,%14.14s,%106.106s,%20.20s,%s,%s,%s") %
+ tr("block") % tr("direction") % tr("unlocked") % tr("timestamp") % tr("amount") % tr("running balance") % tr("hash") % tr("payment ID") % tr("fee") % tr("destination") % tr("amount") % tr("index") % tr("note") % tr("tx key")
<< std::endl;
uint64_t running_balance = 0;
- auto formatter = boost::format("%8.8llu,%9.9s,%8.8s,%25.25s,%20.20s,%20.20s,%64.64s,%16.16s,%14.14s,%106.106s,%20.20s,\"%s\",%s");
+ auto formatter = boost::format("%8.8llu,%9.9s,%8.8s,%25.25s,%20.20s,%20.20s,%64.64s,%16.16s,%14.14s,%106.106s,%20.20s,\"%s\",%s,%s");
for (const auto& transfer : all_transfers)
{
@@ -8938,6 +8931,15 @@ bool simple_wallet::export_transfers(const std::vector<std::string>& args_)
running_balance -= transfer.amount + transfer.fee;
}
+ crypto::secret_key tx_key;
+ std::vector<crypto::secret_key> additional_tx_keys;
+ bool found_tx_key = m_wallet->get_tx_key(transfer.hash, tx_key, additional_tx_keys);
+ std::string key_string;
+ if (export_keys && found_tx_key)
+ {
+ key_string = get_tx_key_stream(tx_key, additional_tx_keys);
+ }
+
file << formatter
% transfer.block
% transfer.direction
@@ -8952,6 +8954,7 @@ bool simple_wallet::export_transfers(const std::vector<std::string>& args_)
% (transfer.outputs.size() ? print_money(transfer.outputs[0].second) : "")
% boost::algorithm::join(transfer.index | boost::adaptors::transformed([](uint32_t i) { return std::to_string(i); }), ", ")
% transfer.note
+ % key_string
<< std::endl;
for (size_t i = 1; i < transfer.outputs.size(); ++i)
@@ -8970,6 +8973,7 @@ bool simple_wallet::export_transfers(const std::vector<std::string>& args_)
% print_money(transfer.outputs[i].second)
% ""
% ""
+ % ""
<< std::endl;
}
}
@@ -9591,8 +9595,8 @@ void simple_wallet::print_accounts(const std::string& tag)
total_balance += m_wallet->balance(account_index, false);
total_unlocked_balance += m_wallet->unlocked_balance(account_index, false);
}
- success_msg_writer() << tr("----------------------------------------------------------------------------------");
- success_msg_writer() << boost::format(tr("%15s %21s %21s")) % "Total" % print_money(total_balance) % print_money(total_unlocked_balance);
+ success_msg_writer() << tr("------------------------------------------------------------------------------------");
+ success_msg_writer() << boost::format(tr("%15s %21s %21s")) % "Total" % print_money(total_balance) % print_money(total_unlocked_balance);
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::print_address(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
@@ -10971,8 +10975,8 @@ void simple_wallet::mms_init(const std::vector<std::string> &args)
std::vector<std::string> numbers;
boost::split(numbers, mn, boost::is_any_of("/"));
bool mn_ok = (numbers.size() == 2)
- && get_number_from_arg(numbers[1], num_authorized_signers, 2, 100)
- && get_number_from_arg(numbers[0], num_required_signers, 2, num_authorized_signers);
+ && get_number_from_arg(numbers[1], num_authorized_signers, 2, config::MULTISIG_MAX_SIGNERS)
+ && get_number_from_arg(numbers[0], num_required_signers, 1, num_authorized_signers);
if (!mn_ok)
{
fail_msg_writer() << tr("Error in the number of required signers and/or authorized signers");
diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h
index 8780bee1d..643270735 100644
--- a/src/simplewallet/simplewallet.h
+++ b/src/simplewallet/simplewallet.h
@@ -148,6 +148,7 @@ namespace cryptonote
bool set_ignore_outputs_above(const std::vector<std::string> &args = std::vector<std::string>());
bool set_ignore_outputs_below(const std::vector<std::string> &args = std::vector<std::string>());
bool set_track_uses(const std::vector<std::string> &args = std::vector<std::string>());
+ bool set_show_wallet_name_when_locked(const std::vector<std::string> &args = std::vector<std::string>());
bool set_inactivity_lock_timeout(const std::vector<std::string> &args = std::vector<std::string>());
bool set_setup_background_mining(const std::vector<std::string> &args = std::vector<std::string>());
bool set_device_name(const std::vector<std::string> &args = std::vector<std::string>());
@@ -233,7 +234,6 @@ namespace cryptonote
bool prepare_multisig_main(const std::vector<std::string>& args, bool called_by_mms);
bool make_multisig(const std::vector<std::string>& args);
bool make_multisig_main(const std::vector<std::string>& args, bool called_by_mms);
- bool finalize_multisig(const std::vector<std::string> &args);
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 export_multisig(const std::vector<std::string>& args);
diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp
index 989061250..87242b79c 100644
--- a/src/wallet/api/wallet.cpp
+++ b/src/wallet/api/wallet.cpp
@@ -1332,7 +1332,7 @@ MultisigState WalletImpl::multisig() const {
string WalletImpl::getMultisigInfo() const {
try {
clearStatus();
- return m_wallet->get_multisig_info();
+ return m_wallet->get_multisig_first_kex_msg();
} catch (const exception& e) {
LOG_ERROR("Error on generating multisig info: " << e.what());
setStatusError(string(tr("Failed to get multisig info: ")) + e.what());
@@ -1341,7 +1341,7 @@ string WalletImpl::getMultisigInfo() const {
return string();
}
-string WalletImpl::makeMultisig(const vector<string>& info, uint32_t threshold) {
+string WalletImpl::makeMultisig(const vector<string>& info, const uint32_t threshold) {
try {
clearStatus();
@@ -1366,30 +1366,12 @@ std::string WalletImpl::exchangeMultisigKeys(const std::vector<std::string> &inf
return m_wallet->exchange_multisig_keys(epee::wipeable_string(m_password), info);
} catch (const exception& e) {
LOG_ERROR("Error on exchanging multisig keys: " << e.what());
- setStatusError(string(tr("Failed to make multisig: ")) + e.what());
+ setStatusError(string(tr("Failed to exchange multisig keys: ")) + e.what());
}
return string();
}
-bool WalletImpl::finalizeMultisig(const vector<string>& extraMultisigInfo) {
- try {
- clearStatus();
- checkMultisigWalletNotReady(m_wallet);
-
- if (m_wallet->finalize_multisig(epee::wipeable_string(m_password), extraMultisigInfo)) {
- return true;
- }
-
- setStatusError(tr("Failed to finalize multisig wallet creation"));
- } catch (const exception& e) {
- LOG_ERROR("Error on finalizing multisig wallet creation: " << e.what());
- setStatusError(string(tr("Failed to finalize multisig wallet creation: ")) + e.what());
- }
-
- return false;
-}
-
bool WalletImpl::exportMultisigImages(string& images) {
try {
clearStatus();
@@ -1760,6 +1742,7 @@ uint64_t WalletImpl::estimateTransactionFee(const std::vector<std::pair<std::str
extra_size,
m_wallet->use_fork_rules(8, 0),
m_wallet->use_fork_rules(HF_VERSION_CLSAG, 0),
+ m_wallet->use_fork_rules(HF_VERSION_BULLETPROOF_PLUS, 0),
m_wallet->get_base_fee(),
m_wallet->get_fee_multiplier(m_wallet->adjust_priority(static_cast<uint32_t>(priority))),
m_wallet->get_fee_quantization_mask());
diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h
index 67fc2c08a..7e1e62081 100644
--- a/src/wallet/api/wallet.h
+++ b/src/wallet/api/wallet.h
@@ -147,7 +147,6 @@ public:
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;
- bool finalizeMultisig(const std::vector<std::string>& extraMultisigInfo) override;
bool exportMultisigImages(std::string& images) override;
size_t importMultisigImages(const std::vector<std::string>& images) override;
bool hasMultisigPartialKeyImages() const override;
diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h
index f9c421a93..6da547054 100644
--- a/src/wallet/api/wallet2_api.h
+++ b/src/wallet/api/wallet2_api.h
@@ -790,7 +790,7 @@ struct Wallet
/**
* @brief makeMultisig - switches wallet in multisig state. The one and only creation phase for N / N wallets
* @param info - vector of multisig infos from other participants obtained with getMulitisInfo call
- * @param threshold - number of required signers to make valid transaction. Must be equal to number of participants (N) or N - 1
+ * @param threshold - number of required signers to make valid transaction. Must be <= number of participants
* @return in case of N / N wallets returns empty string since no more key exchanges needed. For N - 1 / N wallets returns base58 encoded extra multisig info
*/
virtual std::string makeMultisig(const std::vector<std::string>& info, uint32_t threshold) = 0;
@@ -801,12 +801,6 @@ struct Wallet
*/
virtual std::string exchangeMultisigKeys(const std::vector<std::string> &info) = 0;
/**
- * @brief finalizeMultisig - finalizes N - 1 / N multisig wallets creation
- * @param extraMultisigInfo - wallet participants' extra multisig info obtained with makeMultisig call
- * @return true if success
- */
- virtual bool finalizeMultisig(const std::vector<std::string>& extraMultisigInfo) = 0;
- /**
* @brief exportMultisigImages - exports transfers' key images
* @param images - output paramter for hex encoded array of images
* @return true if success
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index 49b8a6821..bd040cae9 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -28,6 +28,7 @@
//
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
+#include <algorithm>
#include <numeric>
#include <tuple>
#include <queue>
@@ -59,6 +60,8 @@ using namespace epee;
#include "misc_language.h"
#include "cryptonote_basic/cryptonote_basic_impl.h"
#include "multisig/multisig.h"
+#include "multisig/multisig_account.h"
+#include "multisig/multisig_kex_msg.h"
#include "common/boost_serialization_helper.h"
#include "common/command_line.h"
#include "common/threadpool.h"
@@ -149,7 +152,6 @@ using namespace cryptonote;
#define RECENT_SPEND_WINDOW (15 * DIFFICULTY_TARGET_V2)
static const std::string MULTISIG_SIGNATURE_MAGIC = "SigMultisigPkV1";
-static const std::string MULTISIG_EXTRA_INFO_MAGIC = "MultisigxV1";
static const std::string ASCII_OUTPUT_MAGIC = "MoneroAsciiDataV1";
@@ -167,42 +169,6 @@ namespace
return dir.string();
}
- std::string pack_multisignature_keys(const std::string& prefix, const std::vector<crypto::public_key>& keys, const crypto::secret_key& signer_secret_key)
- {
- std::string data;
- crypto::public_key signer;
- CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(signer_secret_key, signer), "Failed to derive public spend key");
- data += std::string((const char *)&signer, sizeof(crypto::public_key));
-
- for (const auto &key: keys)
- {
- data += std::string((const char *)&key, sizeof(crypto::public_key));
- }
-
- data.resize(data.size() + sizeof(crypto::signature));
-
- crypto::hash hash;
- crypto::cn_fast_hash(data.data(), data.size() - sizeof(crypto::signature), hash);
- crypto::signature &signature = *(crypto::signature*)&data[data.size() - sizeof(crypto::signature)];
- crypto::generate_signature(hash, signer, signer_secret_key, signature);
-
- return MULTISIG_EXTRA_INFO_MAGIC + tools::base58::encode(data);
- }
-
- std::vector<crypto::public_key> secret_keys_to_public_keys(const std::vector<crypto::secret_key>& keys)
- {
- std::vector<crypto::public_key> public_keys;
- public_keys.reserve(keys.size());
-
- std::transform(keys.begin(), keys.end(), std::back_inserter(public_keys), [] (const crypto::secret_key& k) -> crypto::public_key {
- crypto::public_key p;
- CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(k, p), "Failed to derive public spend key");
- return p;
- });
-
- return public_keys;
- }
-
bool keys_intersect(const std::unordered_set<crypto::public_key>& s1, const std::unordered_set<crypto::public_key>& s2)
{
if (s1.empty() || s2.empty())
@@ -815,7 +781,7 @@ void drop_from_short_history(std::list<crypto::hash> &short_chain_history, size_
}
}
-size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag)
+size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, bool bulletproof_plus)
{
size_t size = 0;
@@ -839,12 +805,12 @@ size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra
size += 1;
// rangeSigs
- if (bulletproof)
+ if (bulletproof || bulletproof_plus)
{
size_t log_padded_outputs = 0;
while ((1<<log_padded_outputs) < n_outputs)
++log_padded_outputs;
- size += (2 * (6 + log_padded_outputs) + 4 + 5) * 32 + 3;
+ size += (2 * (6 + log_padded_outputs) + (bulletproof_plus ? 6 : (4 + 5))) * 32 + 3;
}
else
size += (2*64*32+32+64*32) * n_outputs;
@@ -867,29 +833,29 @@ size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra
// txnFee
size += 4;
- LOG_PRINT_L2("estimated " << (bulletproof ? "bulletproof" : "borromean") << " rct tx size for " << n_inputs << " inputs with ring size " << (mixin+1) << " and " << n_outputs << " outputs: " << size << " (" << ((32 * n_inputs/*+1*/) + 2 * 32 * (mixin+1) * n_inputs + 32 * n_outputs) << " saved)");
+ LOG_PRINT_L2("estimated " << (bulletproof_plus ? "bulletproof plus" : bulletproof ? "bulletproof" : "borromean") << " rct tx size for " << n_inputs << " inputs with ring size " << (mixin+1) << " and " << n_outputs << " outputs: " << size << " (" << ((32 * n_inputs/*+1*/) + 2 * 32 * (mixin+1) * n_inputs + 32 * n_outputs) << " saved)");
return size;
}
-size_t estimate_tx_size(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag)
+size_t estimate_tx_size(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, bool bulletproof_plus)
{
if (use_rct)
- return estimate_rct_tx_size(n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag);
+ return estimate_rct_tx_size(n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus);
else
return n_inputs * (mixin+1) * APPROXIMATE_INPUT_BYTES + extra_size;
}
-uint64_t estimate_tx_weight(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag)
+uint64_t estimate_tx_weight(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, bool bulletproof_plus)
{
- size_t size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag);
- if (use_rct && bulletproof && n_outputs > 2)
+ size_t size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus);
+ if (use_rct && (bulletproof || bulletproof_plus) && n_outputs > 2)
{
- const uint64_t bp_base = 368;
+ const uint64_t bp_base = (32 * ((bulletproof_plus ? 6 : 9) + 7 * 2)) / 2; // notional size of a 2 output proof, normalized to 1 proof (ie, divided by 2)
size_t log_padded_outputs = 2;
while ((1<<log_padded_outputs) < n_outputs)
++log_padded_outputs;
uint64_t nlr = 2 * (6 + log_padded_outputs);
- const uint64_t bp_size = 32 * (9 + nlr);
+ const uint64_t bp_size = 32 * ((bulletproof_plus ? 6 : 9) + nlr);
const uint64_t bp_clawback = (bp_base * (1<<log_padded_outputs) - bp_size) * 4 / 5;
MDEBUG("clawback on size " << size << ": " << bp_clawback);
size += bp_clawback;
@@ -902,6 +868,11 @@ uint8_t get_bulletproof_fork()
return 8;
}
+uint8_t get_bulletproof_plus_fork()
+{
+ return HF_VERSION_BULLETPROOF_PLUS;
+}
+
uint8_t get_clsag_fork()
{
return HF_VERSION_CLSAG;
@@ -1202,6 +1173,7 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std
m_ignore_outputs_above(MONEY_SUPPLY),
m_ignore_outputs_below(0),
m_track_uses(false),
+ m_show_wallet_name_when_locked(false),
m_inactivity_lock_timeout(DEFAULT_INACTIVITY_LOCK_TIMEOUT),
m_setup_background_mining(BackgroundMiningMaybe),
m_persistent_rpc_client_id(false),
@@ -1850,6 +1822,7 @@ static uint64_t decodeRct(const rct::rctSig & rv, const crypto::key_derivation &
case rct::RCTTypeBulletproof:
case rct::RCTTypeBulletproof2:
case rct::RCTTypeCLSAG:
+ case rct::RCTTypeBulletproofPlus:
return rct::decodeRctSimple(rv, rct::sk2rct(scalar1), i, mask, hwdev);
case rct::RCTTypeFull:
return rct::decodeRct(rv, rct::sk2rct(scalar1), i, mask, hwdev);
@@ -4000,6 +3973,9 @@ boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee:
value2.SetInt(m_track_uses ? 1 : 0);
json.AddMember("track_uses", value2, json.GetAllocator());
+ value2.SetInt(m_show_wallet_name_when_locked ? 1 : 0);
+ json.AddMember("show_wallet_name_when_locked", value2, json.GetAllocator());
+
value2.SetInt(m_inactivity_lock_timeout);
json.AddMember("inactivity_lock_timeout", value2, json.GetAllocator());
@@ -4184,6 +4160,7 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st
m_ignore_outputs_above = MONEY_SUPPLY;
m_ignore_outputs_below = 0;
m_track_uses = false;
+ m_show_wallet_name_when_locked = false;
m_inactivity_lock_timeout = DEFAULT_INACTIVITY_LOCK_TIMEOUT;
m_setup_background_mining = BackgroundMiningMaybe;
m_subaddress_lookahead_major = SUBADDRESS_LOOKAHEAD_MAJOR;
@@ -4358,6 +4335,8 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st
m_ignore_outputs_below = field_ignore_outputs_below;
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, track_uses, int, Int, false, false);
m_track_uses = field_track_uses;
+ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, show_wallet_name_when_locked, int, Int, false, false);
+ m_show_wallet_name_when_locked = field_show_wallet_name_when_locked;
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, inactivity_lock_timeout, uint32_t, Uint, false, DEFAULT_INACTIVITY_LOCK_TIMEOUT);
m_inactivity_lock_timeout = field_inactivity_lock_timeout;
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, setup_background_mining, BackgroundMiningSetupType, Int, false, BackgroundMiningMaybe);
@@ -4763,7 +4742,6 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string&
memwipe(&skey, sizeof(rct::key));
m_account.make_multisig(view_secret_key, spend_secret_key, spend_public_key, multisig_keys);
- m_account.finalize_multisig(spend_public_key);
// Not possible to restore a multisig wallet that is able to activate the MMS
// (because the original keys are not (yet) part of the restore info), so
@@ -4978,24 +4956,12 @@ void wallet2::restore(const std::string& wallet_, const epee::wipeable_string& p
store();
}
}
-
+//----------------------------------------------------------------------------------------------------
std::string wallet2::make_multisig(const epee::wipeable_string &password,
- const std::vector<crypto::secret_key> &view_keys,
- const std::vector<crypto::public_key> &spend_keys,
- uint32_t threshold)
+ const std::vector<std::string> &initial_kex_msgs,
+ const std::uint32_t threshold)
{
- CHECK_AND_ASSERT_THROW_MES(!view_keys.empty(), "empty view keys");
- CHECK_AND_ASSERT_THROW_MES(view_keys.size() == spend_keys.size(), "Mismatched view/spend key sizes");
- CHECK_AND_ASSERT_THROW_MES(threshold > 1 && threshold <= spend_keys.size() + 1, "Invalid threshold");
-
- std::string extra_multisig_info;
- std::vector<crypto::secret_key> multisig_keys;
- rct::key spend_pkey = rct::identity();
- rct::key spend_skey;
- auto wiper = epee::misc_utils::create_scope_leave_handler([&](){memwipe(&spend_skey, sizeof(spend_skey));});
- std::vector<crypto::public_key> multisig_signers;
-
- // decrypt keys
+ // decrypt account keys
epee::misc_utils::auto_scope_leave_caller keys_reencryptor;
if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only)
{
@@ -5003,104 +4969,89 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password,
crypto::generate_chacha_key(password.data(), password.size(), chacha_key, m_kdf_rounds);
m_account.encrypt_viewkey(chacha_key);
m_account.decrypt_keys(chacha_key);
- keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this, chacha_key]() { m_account.encrypt_keys(chacha_key); m_account.decrypt_viewkey(chacha_key); });
+ keys_reencryptor = epee::misc_utils::create_scope_leave_handler(
+ [&, this, chacha_key]()
+ {
+ m_account.encrypt_keys(chacha_key);
+ m_account.decrypt_viewkey(chacha_key);
+ }
+ );
}
- // In common multisig scheme there are 4 types of key exchange rounds:
- // 1. First round is exchange of view secret keys and public spend keys.
- // 2. Middle round is exchange of derivations: Ki = b * Mj, where b - spend secret key,
- // M - public multisig key (in first round it equals to public spend key), K - new public multisig key.
- // 3. Secret spend establishment round sets your secret multisig keys as follows: kl = H(Ml), where M - is *your* public multisig key,
- // k - secret multisig key used to sign transactions. k and M are sets of keys, of course.
- // And secret spend key as the sum of all participant's secret multisig keys
- // 4. Last round establishes multisig wallet's public spend key. Participants exchange their public multisig keys
- // and calculate common spend public key as sum of all unique participants' public multisig keys.
- // Note that N/N scheme has only first round. N-1/N has 2 rounds: first and last. Common M/N has all 4 rounds.
+ // create multisig account
+ multisig::multisig_account multisig_account{
+ multisig::get_multisig_blinded_secret_key(get_account().get_keys().m_spend_secret_key),
+ multisig::get_multisig_blinded_secret_key(get_account().get_keys().m_view_secret_key)
+ };
- // IMPORTANT: wallet's public spend key is not equal to secret_spend_key * G!
- // Wallet's public spend key is the sum of unique public multisig keys of all participants.
- // secret_spend_key * G = public signer key
+ // open initial kex messages, validate them, extract signers
+ std::vector<multisig::multisig_kex_msg> expanded_msgs;
+ std::vector<crypto::public_key> signers;
+ expanded_msgs.reserve(initial_kex_msgs.size());
+ signers.reserve(initial_kex_msgs.size() + 1);
- if (threshold == spend_keys.size() + 1)
+ for (const auto &msg : initial_kex_msgs)
{
- // In N / N case we only need to do one round and calculate secret multisig keys and new secret spend key
- MINFO("Creating spend key...");
+ expanded_msgs.emplace_back(msg);
- // Calculates all multisig keys and spend key
- cryptonote::generate_multisig_N_N(get_account().get_keys(), spend_keys, multisig_keys, spend_skey, spend_pkey);
+ // validate each message
+ // 1. must be 'round 1'
+ CHECK_AND_ASSERT_THROW_MES(expanded_msgs.back().get_round() == 1,
+ "Trying to make multisig with message that has invalid multisig kex round (should be '1').");
- // Our signer key is b * G, where b is secret spend key.
- multisig_signers = spend_keys;
- multisig_signers.push_back(get_multisig_signer_public_key(get_account().get_keys().m_spend_secret_key));
- }
- else
- {
- // We just got public spend keys of all participants and deriving multisig keys (set of Mi = b * Bi).
- // note that derivations are public keys as DH exchange suppose it to be
- auto derivations = cryptonote::generate_multisig_derivations(get_account().get_keys(), spend_keys);
+ // 2. duplicate signers not allowed
+ CHECK_AND_ASSERT_THROW_MES(std::find(signers.begin(), signers.end(), expanded_msgs.back().get_signing_pubkey()) == signers.end(),
+ "Duplicate signers not allowed when converting a wallet to multisig.");
- spend_pkey = rct::identity();
- multisig_signers = std::vector<crypto::public_key>(spend_keys.size() + 1, crypto::null_pkey);
-
- if (threshold == spend_keys.size())
- {
- // N - 1 / N case
-
- // We need an extra step, so we package all the composite public keys
- // we know about, and make a signed string out of them
- MINFO("Creating spend key...");
+ // add signer (skip self for now)
+ if (expanded_msgs.back().get_signing_pubkey() != multisig_account.get_base_pubkey())
+ signers.push_back(expanded_msgs.back().get_signing_pubkey());
+ }
- // Calculating set of our secret multisig keys as follows: mi = H(Mi),
- // where mi - secret multisig key, Mi - others' participants public multisig key
- multisig_keys = cryptonote::calculate_multisig_keys(derivations);
+ // add self to signers
+ signers.push_back(multisig_account.get_base_pubkey());
- // calculating current participant's spend secret key as sum of all secret multisig keys for current participant.
- // IMPORTANT: participant's secret spend key is not an entire wallet's secret spend!
- // Entire wallet's secret spend is sum of all unique secret multisig keys
- // among all of participants and is not held by anyone!
- spend_skey = rct::sk2rct(cryptonote::calculate_multisig_signer_key(multisig_keys));
+ // intialize key exchange
+ multisig_account.initialize_kex(threshold, signers, expanded_msgs);
+ CHECK_AND_ASSERT_THROW_MES(multisig_account.account_is_active(), "Failed to activate multisig account.");
- // Preparing data for the last round to calculate common public spend key. The data contains public multisig keys.
- extra_multisig_info = pack_multisignature_keys(MULTISIG_EXTRA_INFO_MAGIC, secret_keys_to_public_keys(multisig_keys), rct::rct2sk(spend_skey));
- }
- else
- {
- // M / N case
- MINFO("Preparing keys for next exchange round...");
-
- // Preparing data for middle round - packing new public multisig keys to exchage with others.
- extra_multisig_info = pack_multisignature_keys(MULTISIG_EXTRA_INFO_MAGIC, derivations, m_account.get_keys().m_spend_secret_key);
- spend_skey = rct::sk2rct(m_account.get_keys().m_spend_secret_key);
-
- // Need to store middle keys to be able to proceed in case of wallet shutdown.
- m_multisig_derivations = derivations;
- }
- }
-
+ // update wallet state
if (!m_original_keys_available)
{
// Save the original i.e. non-multisig keys so the MMS can continue to use them to encrypt and decrypt messages
// (making a wallet multisig overwrites those keys, see account_base::make_multisig)
- m_original_address = m_account.get_keys().m_account_address;
- m_original_view_secret_key = m_account.get_keys().m_view_secret_key;
+ m_original_address = get_account().get_keys().m_account_address;
+ m_original_view_secret_key = get_account().get_keys().m_view_secret_key;
m_original_keys_available = true;
}
clear();
- MINFO("Creating view key...");
- crypto::secret_key view_skey = cryptonote::generate_multisig_view_secret_key(get_account().get_keys().m_view_secret_key, view_keys);
+ // account base
MINFO("Creating multisig address...");
- CHECK_AND_ASSERT_THROW_MES(m_account.make_multisig(view_skey, rct::rct2sk(spend_skey), rct::rct2pk(spend_pkey), multisig_keys),
- "Failed to create multisig wallet due to bad keys");
- memwipe(&spend_skey, sizeof(rct::key));
+ CHECK_AND_ASSERT_THROW_MES(m_account.make_multisig(multisig_account.get_common_privkey(),
+ multisig_account.get_base_privkey(),
+ multisig_account.get_multisig_pubkey(),
+ multisig_account.get_multisig_privkeys()),
+ "Failed to create multisig wallet account due to bad keys");
init_type(hw::device::device_type::SOFTWARE);
m_original_keys_available = true;
m_multisig = true;
m_multisig_threshold = threshold;
- m_multisig_signers = multisig_signers;
- ++m_multisig_rounds_passed;
+ m_multisig_signers = signers;
+ 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());
+
+ for (const auto &key_to_origins : multisig_account.get_kex_keys_to_origins_map())
+ m_multisig_derivations.push_back(key_to_origins.first);
+
+ // address
+ m_account_public_address.m_spend_public_key = multisig_account.get_multisig_pubkey();
// re-encrypt keys
keys_reencryptor = epee::misc_utils::auto_scope_leave_caller();
@@ -5113,42 +5064,18 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password,
if (!m_wallet_file.empty())
store();
- return extra_multisig_info;
+ return multisig_account.get_next_kex_round_msg();
}
-
-std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &password,
- const std::vector<std::string> &info)
-{
- THROW_WALLET_EXCEPTION_IF(info.empty(),
- error::wallet_internal_error, "Empty multisig info");
-
- if (info[0].substr(0, MULTISIG_EXTRA_INFO_MAGIC.size()) != MULTISIG_EXTRA_INFO_MAGIC)
- {
- THROW_WALLET_EXCEPTION_IF(false,
- error::wallet_internal_error, "Unsupported info string");
- }
-
- std::vector<crypto::public_key> signers;
- std::unordered_set<crypto::public_key> pkeys;
-
- THROW_WALLET_EXCEPTION_IF(!unpack_extra_multisig_info(info, signers, pkeys),
- error::wallet_internal_error, "Bad extra multisig info");
-
- return exchange_multisig_keys(password, pkeys, signers);
-}
-
+//----------------------------------------------------------------------------------------------------
std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &password,
- std::unordered_set<crypto::public_key> derivations,
- std::vector<crypto::public_key> signers)
+ const std::vector<std::string> &kex_messages)
{
- CHECK_AND_ASSERT_THROW_MES(!derivations.empty(), "empty pkeys");
- CHECK_AND_ASSERT_THROW_MES(!signers.empty(), "empty signers");
-
- bool ready = 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.");
- // keys are decrypted
+ // decrypt account keys
epee::misc_utils::auto_scope_leave_caller keys_reencryptor;
if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only)
{
@@ -5156,37 +5083,72 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor
crypto::generate_chacha_key(password.data(), password.size(), chacha_key, m_kdf_rounds);
m_account.encrypt_viewkey(chacha_key);
m_account.decrypt_keys(chacha_key);
- keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this, chacha_key]() { m_account.encrypt_keys(chacha_key); m_account.decrypt_viewkey(chacha_key); });
- }
-
- if (m_multisig_rounds_passed == multisig_rounds_required(m_multisig_signers.size(), m_multisig_threshold) - 1)
- {
- // the last round is passed and we have to calculate spend public key
- // add ours if not included
- crypto::public_key local_signer = get_multisig_signer_public_key();
-
- if (std::find(signers.begin(), signers.end(), local_signer) == signers.end())
- {
- signers.push_back(local_signer);
- for (const auto &msk: get_account().get_multisig_keys())
+ keys_reencryptor = epee::misc_utils::create_scope_leave_handler(
+ [&, this, chacha_key]()
{
- derivations.insert(rct::rct2pk(rct::scalarmultBase(rct::sk2rct(msk))));
+ m_account.encrypt_keys(chacha_key);
+ m_account.decrypt_viewkey(chacha_key);
}
- }
+ );
+ }
+
+ // 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_account::kex_origins_map_t kex_origins_map;
+
+ for (const auto &derivation : m_multisig_derivations)
+ kex_origins_map[derivation];
+
+ multisig::multisig_account multisig_account{
+ m_multisig_threshold,
+ m_multisig_signers,
+ get_account().get_keys().m_spend_secret_key,
+ crypto::null_skey, //base common privkey: not used
+ get_account().get_keys().m_multisig_keys,
+ get_account().get_keys().m_view_secret_key,
+ m_account_public_address.m_spend_public_key,
+ dummy, //common pubkey: not used
+ m_multisig_rounds_passed,
+ std::move(kex_origins_map),
+ ""
+ };
- CHECK_AND_ASSERT_THROW_MES(signers.size() == m_multisig_signers.size(), "Bad signers size");
+ // update multisig kex
+ multisig_account.kex_update(expanded_msgs);
- // Summing all of unique public multisig keys to calculate common public spend key
- crypto::public_key spend_public_key = cryptonote::generate_multisig_M_N_spend_public_key(std::vector<crypto::public_key>(derivations.begin(), derivations.end()));
- m_account_public_address.m_spend_public_key = spend_public_key;
- m_account.finalize_multisig(spend_public_key);
+ // update wallet state
- m_multisig_signers = signers;
- std::sort(m_multisig_signers.begin(), m_multisig_signers.end(), [](const crypto::public_key &e0, const crypto::public_key &e1){ return memcmp(&e0, &e1, sizeof(e0)) < 0; });
+ // address
+ m_account_public_address.m_spend_public_key = multisig_account.get_multisig_pubkey();
- ++m_multisig_rounds_passed;
- m_multisig_derivations.clear();
+ // account base
+ CHECK_AND_ASSERT_THROW_MES(m_account.make_multisig(multisig_account.get_common_privkey(),
+ multisig_account.get_base_privkey(),
+ multisig_account.get_multisig_pubkey(),
+ multisig_account.get_multisig_privkeys()),
+ "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());
+
+ for (const auto &key_to_origins : multisig_account.get_kex_keys_to_origins_map())
+ m_multisig_derivations.push_back(key_to_origins.first);
+
+ // rounds passed
+ m_multisig_rounds_passed = multisig_account.get_kex_rounds_complete();
+ // why is this necessary? who knows...
+ if (multisig_account.multisig_is_ready())
+ {
// keys are encrypted again
keys_reencryptor = epee::misc_utils::auto_scope_leave_caller();
@@ -5208,270 +5170,28 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor
if (!m_wallet_file.empty())
store();
-
- return {};
}
- // Below are either middle or secret spend key establishment rounds
-
- for (const auto& key: m_multisig_derivations)
- derivations.erase(key);
-
- // Deriving multisig keys (set of Mi = b * Bi) according to DH from other participants' multisig keys.
- auto new_derivations = cryptonote::generate_multisig_derivations(get_account().get_keys(), std::vector<crypto::public_key>(derivations.begin(), derivations.end()));
-
- std::string extra_multisig_info;
- if (m_multisig_rounds_passed == multisig_rounds_required(m_multisig_signers.size(), m_multisig_threshold) - 2) // next round is last
- {
- // Next round is last therefore we are performing secret spend establishment round as described above.
- MINFO("Creating spend key...");
-
- // Calculating our secret multisig keys by hashing our public multisig keys.
- auto multisig_keys = cryptonote::calculate_multisig_keys(std::vector<crypto::public_key>(new_derivations.begin(), new_derivations.end()));
- // And summing it to get personal secret spend key
- crypto::secret_key spend_skey = cryptonote::calculate_multisig_signer_key(multisig_keys);
-
- m_account.make_multisig(m_account.get_keys().m_view_secret_key, spend_skey, rct::rct2pk(rct::identity()), multisig_keys);
-
- // Packing public multisig keys to exchange with others and calculate common public spend key in the last round
- extra_multisig_info = pack_multisignature_keys(MULTISIG_EXTRA_INFO_MAGIC, secret_keys_to_public_keys(multisig_keys), spend_skey);
- }
- else
- {
- // This is just middle round
- MINFO("Preparing keys for next exchange round...");
- extra_multisig_info = pack_multisignature_keys(MULTISIG_EXTRA_INFO_MAGIC, new_derivations, m_account.get_keys().m_spend_secret_key);
- m_multisig_derivations = new_derivations;
- }
-
- ++m_multisig_rounds_passed;
-
+ // wallet/file relationship
if (!m_wallet_file.empty())
create_keys_file(m_wallet_file, false, password, boost::filesystem::exists(m_wallet_file + ".address.txt"));
- return extra_multisig_info;
-}
-
-void wallet2::unpack_multisig_info(const std::vector<std::string>& info,
- std::vector<crypto::public_key> &public_keys,
- std::vector<crypto::secret_key> &secret_keys) const
-{
- // parse all multisig info
- public_keys.resize(info.size());
- secret_keys.resize(info.size());
- for (size_t i = 0; i < info.size(); ++i)
- {
- THROW_WALLET_EXCEPTION_IF(!verify_multisig_info(info[i], secret_keys[i], public_keys[i]),
- error::wallet_internal_error, "Bad multisig info: " + info[i]);
- }
-
- // remove duplicates
- for (size_t i = 0; i < secret_keys.size(); ++i)
- {
- for (size_t j = i + 1; j < secret_keys.size(); ++j)
- {
- if (rct::sk2rct(secret_keys[i]) == rct::sk2rct(secret_keys[j]))
- {
- MDEBUG("Duplicate key found, ignoring");
- secret_keys[j] = secret_keys.back();
- public_keys[j] = public_keys.back();
- secret_keys.pop_back();
- public_keys.pop_back();
- --j;
- }
- }
- }
-
- // people may include their own, weed it out
- const crypto::secret_key local_skey = cryptonote::get_multisig_blinded_secret_key(get_account().get_keys().m_view_secret_key);
- const crypto::public_key local_pkey = get_multisig_signer_public_key(get_account().get_keys().m_spend_secret_key);
- for (size_t i = 0; i < secret_keys.size(); ++i)
- {
- if (secret_keys[i] == local_skey)
- {
- MDEBUG("Local key is present, ignoring");
- secret_keys[i] = secret_keys.back();
- public_keys[i] = public_keys.back();
- secret_keys.pop_back();
- public_keys.pop_back();
- --i;
- }
- else
- {
- THROW_WALLET_EXCEPTION_IF(public_keys[i] == local_pkey, error::wallet_internal_error,
- "Found local spend public key, but not local view secret key - something very weird");
- }
- }
-}
-
-std::string wallet2::make_multisig(const epee::wipeable_string &password,
- const std::vector<std::string> &info,
- uint32_t threshold)
-{
- std::vector<crypto::secret_key> secret_keys(info.size());
- std::vector<crypto::public_key> public_keys(info.size());
- unpack_multisig_info(info, public_keys, secret_keys);
- return make_multisig(password, secret_keys, public_keys, threshold);
-}
-
-bool wallet2::finalize_multisig(const epee::wipeable_string &password, const std::unordered_set<crypto::public_key> &pkeys, std::vector<crypto::public_key> signers)
-{
- bool ready;
- uint32_t threshold, total;
- if (!multisig(&ready, &threshold, &total))
- {
- MERROR("This is not a multisig wallet");
- return false;
- }
- if (ready)
- {
- MERROR("This multisig wallet is already finalized");
- return false;
- }
- if (threshold + 1 != total)
- {
- MERROR("finalize_multisig should only be used for N-1/N wallets, use exchange_multisig_keys instead");
- return false;
- }
- exchange_multisig_keys(password, pkeys, signers);
- return true;
-}
-
-bool wallet2::unpack_extra_multisig_info(const std::vector<std::string>& info,
- std::vector<crypto::public_key> &signers,
- std::unordered_set<crypto::public_key> &pkeys) const
-{
- // parse all multisig info
- signers.resize(info.size(), crypto::null_pkey);
- for (size_t i = 0; i < info.size(); ++i)
- {
- if (!verify_extra_multisig_info(info[i], pkeys, signers[i]))
- {
- return false;
- }
- }
-
- return true;
-}
-
-bool wallet2::finalize_multisig(const epee::wipeable_string &password, const std::vector<std::string> &info)
-{
- std::unordered_set<crypto::public_key> public_keys;
- std::vector<crypto::public_key> signers;
- if (!unpack_extra_multisig_info(info, signers, public_keys))
- {
- MERROR("Bad multisig info");
- return false;
- }
-
- return finalize_multisig(password, public_keys, signers);
-}
-
-std::string wallet2::get_multisig_info() const
-{
- // It's a signed package of private view key and public spend key
- const crypto::secret_key skey = cryptonote::get_multisig_blinded_secret_key(get_account().get_keys().m_view_secret_key);
- const crypto::public_key pkey = get_multisig_signer_public_key(get_account().get_keys().m_spend_secret_key);
- crypto::hash hash;
-
- std::string data;
- data += std::string((const char *)&skey, sizeof(crypto::secret_key));
- data += std::string((const char *)&pkey, sizeof(crypto::public_key));
-
- data.resize(data.size() + sizeof(crypto::signature));
- crypto::cn_fast_hash(data.data(), data.size() - sizeof(signature), hash);
- crypto::signature &signature = *(crypto::signature*)&data[data.size() - sizeof(crypto::signature)];
- crypto::generate_signature(hash, pkey, get_multisig_blinded_secret_key(get_account().get_keys().m_spend_secret_key), signature);
-
- return std::string("MultisigV1") + tools::base58::encode(data);
+ return multisig_account.get_next_kex_round_msg();
}
-
-bool wallet2::verify_multisig_info(const std::string &data, crypto::secret_key &skey, crypto::public_key &pkey)
-{
- const size_t header_len = strlen("MultisigV1");
- if (data.size() < header_len || data.substr(0, header_len) != "MultisigV1")
- {
- MERROR("Multisig info header check error");
- return false;
- }
- std::string decoded;
- if (!tools::base58::decode(data.substr(header_len), decoded))
- {
- MERROR("Multisig info decoding error");
- return false;
- }
- if (decoded.size() != sizeof(crypto::secret_key) + sizeof(crypto::public_key) + sizeof(crypto::signature))
- {
- MERROR("Multisig info is corrupt");
- return false;
- }
-
- size_t offset = 0;
- skey = *(const crypto::secret_key*)(decoded.data() + offset);
- offset += sizeof(skey);
- pkey = *(const crypto::public_key*)(decoded.data() + offset);
- offset += sizeof(pkey);
- const crypto::signature &signature = *(const crypto::signature*)(decoded.data() + offset);
-
- crypto::hash hash;
- crypto::cn_fast_hash(decoded.data(), decoded.size() - sizeof(signature), hash);
- if (!crypto::check_signature(hash, pkey, signature))
- {
- MERROR("Multisig info signature is invalid");
- return false;
- }
-
- return true;
-}
-
-bool wallet2::verify_extra_multisig_info(const std::string &data, std::unordered_set<crypto::public_key> &pkeys, crypto::public_key &signer)
+//----------------------------------------------------------------------------------------------------
+std::string wallet2::get_multisig_first_kex_msg() const
{
- if (data.size() < MULTISIG_EXTRA_INFO_MAGIC.size() || data.substr(0, MULTISIG_EXTRA_INFO_MAGIC.size()) != MULTISIG_EXTRA_INFO_MAGIC)
- {
- MERROR("Multisig info header check error");
- return false;
- }
- std::string decoded;
- if (!tools::base58::decode(data.substr(MULTISIG_EXTRA_INFO_MAGIC.size()), decoded))
- {
- MERROR("Multisig info decoding error");
- return false;
- }
- if (decoded.size() < sizeof(crypto::public_key) + sizeof(crypto::signature))
- {
- MERROR("Multisig info is corrupt");
- return false;
- }
- if ((decoded.size() - (sizeof(crypto::public_key) + sizeof(crypto::signature))) % sizeof(crypto::public_key))
- {
- MERROR("Multisig info is corrupt");
- return false;
- }
-
- const size_t n_keys = (decoded.size() - (sizeof(crypto::public_key) + sizeof(crypto::signature))) / sizeof(crypto::public_key);
- size_t offset = 0;
- signer = *(const crypto::public_key*)(decoded.data() + offset);
- offset += sizeof(signer);
- const crypto::signature &signature = *(const crypto::signature*)(decoded.data() + offset + n_keys * sizeof(crypto::public_key));
-
- crypto::hash hash;
- crypto::cn_fast_hash(decoded.data(), decoded.size() - sizeof(signature), hash);
- if (!crypto::check_signature(hash, signer, signature))
- {
- MERROR("Multisig info signature is invalid");
- return false;
- }
-
- for (size_t n = 0; n < n_keys; ++n)
- {
- crypto::public_key mspk = *(const crypto::public_key*)(decoded.data() + offset);
- pkeys.insert(mspk);
- offset += sizeof(mspk);
- }
+ // create multisig account
+ multisig::multisig_account multisig_account{
+ // k_base = H(normal private spend key)
+ multisig::get_multisig_blinded_secret_key(get_account().get_keys().m_spend_secret_key),
+ // k_view = H(normal private view key)
+ multisig::get_multisig_blinded_secret_key(get_account().get_keys().m_view_secret_key)
+ };
- return true;
+ return multisig_account.get_next_kex_round_msg();
}
-
+//----------------------------------------------------------------------------------------------------
bool wallet2::multisig(bool *ready, uint32_t *threshold, uint32_t *total) const
{
if (!m_multisig)
@@ -5484,7 +5204,7 @@ bool wallet2::multisig(bool *ready, uint32_t *threshold, uint32_t *total) const
*ready = !(get_account().get_keys().m_account_address.m_spend_public_key == rct::rct2pk(rct::identity()));
return true;
}
-
+//----------------------------------------------------------------------------------------------------
bool wallet2::has_multisig_partial_key_images() const
{
if (!m_multisig)
@@ -5494,7 +5214,7 @@ bool wallet2::has_multisig_partial_key_images() const
return true;
return false;
}
-
+//----------------------------------------------------------------------------------------------------
bool wallet2::has_unknown_key_images() const
{
for (const auto &td: m_transfers)
@@ -6126,6 +5846,19 @@ std::map<uint32_t, uint64_t> wallet2::balance_per_subaddress(uint32_t index_majo
amount_per_subaddr[0] = utx.second.m_change;
else
found->second += utx.second.m_change;
+
+ // add transfers to same wallet
+ for (const auto &dest: utx.second.m_dests) {
+ auto index = get_subaddress_index(dest.addr);
+ if (index && (*index).major == index_major)
+ {
+ auto found = amount_per_subaddr.find((*index).minor);
+ if (found == amount_per_subaddr.end())
+ amount_per_subaddr[(*index).minor] = dest.amount;
+ else
+ found->second += dest.amount;
+ }
+ }
}
}
@@ -6722,7 +6455,7 @@ std::string wallet2::dump_tx_to_str(const std::vector<pending_tx> &ptx_vector) c
txs.txes.push_back(get_construction_data_with_decrypted_short_payment_id(tx, m_account.get_device()));
}
- txs.transfers = export_outputs();
+ txs.new_transfers = export_outputs();
// save as binary
std::ostringstream oss;
binary_archive<true> ar(oss);
@@ -6863,7 +6596,10 @@ 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)
{
- import_outputs(exported_txs.transfers);
+ if (!exported_txs.new_transfers.second.empty())
+ import_outputs(exported_txs.new_transfers);
+ else
+ import_outputs(exported_txs.transfers);
// sign the transactions
for (size_t n = 0; n < exported_txs.txes.size(); ++n)
@@ -7496,16 +7232,16 @@ bool wallet2::sign_multisig_tx_from_file(const std::string &filename, std::vecto
return sign_multisig_tx_to_file(exported_txs, filename, txids);
}
//----------------------------------------------------------------------------------------------------
-uint64_t wallet2::estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) const
+uint64_t wallet2::estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, bool bulletproof_plus, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) const
{
if (use_per_byte_fee)
{
- const size_t estimated_tx_weight = estimate_tx_weight(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag);
+ const size_t estimated_tx_weight = estimate_tx_weight(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus);
return calculate_fee_from_weight(base_fee, estimated_tx_weight, fee_multiplier, fee_quantization_mask);
}
else
{
- const size_t estimated_tx_size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag);
+ const size_t estimated_tx_size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus);
return calculate_fee(base_fee, estimated_tx_size, fee_multiplier);
}
}
@@ -9226,8 +8962,8 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
ptx.construction_data.unlock_time = unlock_time;
ptx.construction_data.use_rct = true;
ptx.construction_data.rct_config = {
- tx.rct_signatures.p.bulletproofs.empty() ? rct::RangeProofBorromean : rct::RangeProofPaddedBulletproof,
- use_fork_rules(HF_VERSION_CLSAG, -10) ? 3 : use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1
+ rct::RangeProofPaddedBulletproof,
+ use_fork_rules(HF_VERSION_BULLETPROOF_PLUS, -10) ? 4 : 3
};
ptx.construction_data.dests = dsts;
// record which subaddress indices are being used as inputs
@@ -9922,10 +9658,11 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
const bool use_per_byte_fee = use_fork_rules(HF_VERSION_PER_BYTE_FEE, 0);
const bool use_rct = use_fork_rules(4, 0);
const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0);
+ const bool bulletproof_plus = use_fork_rules(get_bulletproof_plus_fork(), 0);
const bool clsag = use_fork_rules(get_clsag_fork(), 0);
const rct::RCTConfig rct_config {
- bulletproof ? rct::RangeProofPaddedBulletproof : rct::RangeProofBorromean,
- bulletproof ? (use_fork_rules(HF_VERSION_CLSAG, -10) ? 3 : use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1) : 0
+ rct::RangeProofPaddedBulletproof,
+ bulletproof_plus ? 4 : 3
};
const uint64_t base_fee = get_base_fee();
@@ -9961,7 +9698,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
// early out if we know we can't make it anyway
// we could also check for being within FEE_PER_KB, but if the fee calculation
// ever changes, this might be missed, so let this go through
- const uint64_t min_fee = (fee_multiplier * base_fee * estimate_tx_size(use_rct, 1, fake_outs_count, 2, extra.size(), bulletproof, clsag));
+ const uint64_t min_fee = (fee_multiplier * base_fee * estimate_tx_size(use_rct, 1, fake_outs_count, 2, extra.size(), bulletproof, clsag, bulletproof_plus));
uint64_t balance_subtotal = 0;
uint64_t unlocked_balance_subtotal = 0;
for (uint32_t index_minor : subaddr_indices)
@@ -9979,8 +9716,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
LOG_PRINT_L2("Candidate subaddress index for spending: " << i);
// determine threshold for fractional amount
- const size_t tx_weight_one_ring = estimate_tx_weight(use_rct, 1, fake_outs_count, 2, 0, bulletproof, clsag);
- const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof, clsag);
+ const size_t tx_weight_one_ring = estimate_tx_weight(use_rct, 1, fake_outs_count, 2, 0, bulletproof, clsag, bulletproof_plus);
+ const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof, clsag, bulletproof_plus);
THROW_WALLET_EXCEPTION_IF(tx_weight_one_ring > tx_weight_two_rings, error::wallet_internal_error, "Estimated tx weight with 1 input is larger than with 2 inputs!");
const size_t tx_weight_per_ring = tx_weight_two_rings - tx_weight_one_ring;
const uint64_t fractional_threshold = (fee_multiplier * base_fee * tx_weight_per_ring) / (use_per_byte_fee ? 1 : 1024);
@@ -10077,7 +9814,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
{
// this is used to build a tx that's 1 or 2 inputs, and 2 outputs, which
// will get us a known fee.
- uint64_t estimated_fee = estimate_fee(use_per_byte_fee, use_rct, 2, fake_outs_count, 2, extra.size(), bulletproof, clsag, base_fee, fee_multiplier, fee_quantization_mask);
+ uint64_t estimated_fee = estimate_fee(use_per_byte_fee, use_rct, 2, fake_outs_count, 2, extra.size(), bulletproof, clsag, bulletproof_plus, base_fee, fee_multiplier, fee_quantization_mask);
preferred_inputs = pick_preferred_rct_inputs(needed_money + estimated_fee, subaddr_account, subaddr_indices);
if (!preferred_inputs.empty())
{
@@ -10190,7 +9927,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
}
else
{
- while (!dsts.empty() && dsts[0].amount <= available_amount && estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag) < TX_WEIGHT_TARGET(upper_transaction_weight_limit))
+ while (!dsts.empty() && dsts[0].amount <= available_amount && estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag, bulletproof_plus) < TX_WEIGHT_TARGET(upper_transaction_weight_limit))
{
// we can fully pay that destination
LOG_PRINT_L2("We can fully pay " << get_account_address_as_str(m_nettype, dsts[0].is_subaddress, dsts[0].addr) <<
@@ -10207,7 +9944,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
++original_output_index;
}
- if (!out_slots_exhausted && available_amount > 0 && !dsts.empty() && estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag) < TX_WEIGHT_TARGET(upper_transaction_weight_limit)) {
+ if (!out_slots_exhausted && available_amount > 0 && !dsts.empty() &&
+ estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag, bulletproof_plus) < TX_WEIGHT_TARGET(upper_transaction_weight_limit)) {
// we can partially fill that destination
LOG_PRINT_L2("We can partially pay " << get_account_address_as_str(m_nettype, dsts[0].is_subaddress, dsts[0].addr) <<
" for " << print_money(available_amount) << "/" << print_money(dsts[0].amount));
@@ -10245,7 +9983,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
}
else
{
- const size_t estimated_rct_tx_weight = estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag);
+ const size_t estimated_rct_tx_weight = estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag, bulletproof_plus);
try_tx = dsts.empty() || (estimated_rct_tx_weight >= TX_WEIGHT_TARGET(upper_transaction_weight_limit));
THROW_WALLET_EXCEPTION_IF(try_tx && tx.dsts.empty(), error::tx_too_big, estimated_rct_tx_weight, upper_transaction_weight_limit);
}
@@ -10256,7 +9994,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
pending_tx test_ptx;
const size_t num_outputs = get_num_outputs(tx.dsts, m_transfers, tx.selected_transfers);
- needed_fee = estimate_fee(use_per_byte_fee, use_rct ,tx.selected_transfers.size(), fake_outs_count, num_outputs, extra.size(), bulletproof, clsag, base_fee, fee_multiplier, fee_quantization_mask);
+ needed_fee = estimate_fee(use_per_byte_fee, use_rct ,tx.selected_transfers.size(), fake_outs_count, num_outputs, extra.size(), bulletproof, clsag, bulletproof_plus, base_fee, fee_multiplier, fee_quantization_mask);
auto try_carving_from_partial_payment = [&](uint64_t needed_fee, uint64_t available_for_fee)
{
@@ -10516,11 +10254,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below
// determine threshold for fractional amount
const bool use_per_byte_fee = use_fork_rules(HF_VERSION_PER_BYTE_FEE, 0);
const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0);
+ const bool bulletproof_plus = use_fork_rules(get_bulletproof_plus_fork(), 0);
const bool clsag = use_fork_rules(get_clsag_fork(), 0);
const uint64_t base_fee = get_base_fee();
const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm());
- const size_t tx_weight_one_ring = estimate_tx_weight(use_rct, 1, fake_outs_count, 2, 0, bulletproof, clsag);
- const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof, clsag);
+ const size_t tx_weight_one_ring = estimate_tx_weight(use_rct, 1, fake_outs_count, 2, 0, bulletproof, clsag, bulletproof_plus);
+ const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof, clsag, bulletproof_plus);
THROW_WALLET_EXCEPTION_IF(tx_weight_one_ring > tx_weight_two_rings, error::wallet_internal_error, "Estimated tx weight with 1 input is larger than with 2 inputs!");
const size_t tx_weight_per_ring = tx_weight_two_rings - tx_weight_one_ring;
const uint64_t fractional_threshold = (fee_multiplier * base_fee * tx_weight_per_ring) / (use_per_byte_fee ? 1 : 1024);
@@ -10626,10 +10365,11 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
const bool use_per_byte_fee = use_fork_rules(HF_VERSION_PER_BYTE_FEE);
const bool use_rct = fake_outs_count > 0 && use_fork_rules(4, 0);
const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0);
+ const bool bulletproof_plus = use_fork_rules(get_bulletproof_plus_fork(), 0);
const bool clsag = use_fork_rules(get_clsag_fork(), 0);
const rct::RCTConfig rct_config {
- bulletproof ? rct::RangeProofPaddedBulletproof : rct::RangeProofBorromean,
- bulletproof ? (use_fork_rules(HF_VERSION_CLSAG, -10) ? 3 : use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1) : 0,
+ rct::RangeProofPaddedBulletproof,
+ bulletproof_plus ? 4 : 3
};
const uint64_t base_fee = get_base_fee();
const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm());
@@ -10658,7 +10398,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
uint64_t fee_dust_threshold;
if (use_fork_rules(HF_VERSION_PER_BYTE_FEE))
{
- const uint64_t estimated_tx_weight_with_one_extra_output = estimate_tx_weight(use_rct, tx.selected_transfers.size() + 1, fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag);
+ const uint64_t estimated_tx_weight_with_one_extra_output = estimate_tx_weight(use_rct, tx.selected_transfers.size() + 1, fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag, bulletproof_plus);
fee_dust_threshold = calculate_fee_from_weight(base_fee, estimated_tx_weight_with_one_extra_output, fee_multiplier, fee_quantization_mask);
}
else
@@ -10689,7 +10429,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
// here, check if we need to sent tx and start a new one
LOG_PRINT_L2("Considering whether to create a tx now, " << tx.selected_transfers.size() << " inputs, tx limit "
<< upper_transaction_weight_limit);
- const size_t estimated_rct_tx_weight = estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 2, extra.size(), bulletproof, clsag);
+ const size_t estimated_rct_tx_weight = estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 2, extra.size(), bulletproof, clsag, bulletproof_plus);
bool try_tx = (unused_dust_indices.empty() && unused_transfers_indices.empty()) || ( estimated_rct_tx_weight >= TX_WEIGHT_TARGET(upper_transaction_weight_limit));
if (try_tx) {
@@ -10697,7 +10437,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
pending_tx test_ptx;
const size_t num_outputs = get_num_outputs(tx.dsts, m_transfers, tx.selected_transfers);
- needed_fee = estimate_fee(use_per_byte_fee, use_rct, tx.selected_transfers.size(), fake_outs_count, num_outputs, extra.size(), bulletproof, clsag, base_fee, fee_multiplier, fee_quantization_mask);
+ needed_fee = estimate_fee(use_per_byte_fee, use_rct, tx.selected_transfers.size(), fake_outs_count, num_outputs, extra.size(), bulletproof, clsag, bulletproof_plus, base_fee, fee_multiplier, fee_quantization_mask);
// add N - 1 outputs for correct initial fee estimation
for (size_t i = 0; i < ((outputs > 1) ? outputs - 1 : outputs); ++i)
@@ -11559,8 +11299,10 @@ void wallet2::check_tx_key_helper(const cryptonote::transaction &tx, const crypt
crypto::secret_key scalar1;
crypto::derivation_to_scalar(found_derivation, n, scalar1);
rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n];
- rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG);
- const rct::key C = tx.rct_signatures.outPk[n].mask;
+ rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG || tx.rct_signatures.type == rct::RCTTypeBulletproofPlus);
+ rct::key C = tx.rct_signatures.outPk[n].mask;
+ if (rct::is_rct_bulletproof_plus(tx.rct_signatures.type))
+ C = rct::scalarmult8(C);
rct::key Ctmp;
THROW_WALLET_EXCEPTION_IF(sc_check(ecdh_info.mask.bytes) != 0, error::wallet_internal_error, "Bad ECDH input mask");
THROW_WALLET_EXCEPTION_IF(sc_check(ecdh_info.amount.bytes) != 0, error::wallet_internal_error, "Bad ECDH input amount");
@@ -12212,7 +11954,7 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr
crypto::secret_key shared_secret;
crypto::derivation_to_scalar(derivation, proof.index_in_tx, shared_secret);
rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[proof.index_in_tx];
- rct::ecdhDecode(ecdh_info, rct::sk2rct(shared_secret), tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG);
+ rct::ecdhDecode(ecdh_info, rct::sk2rct(shared_secret), tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG || tx.rct_signatures.type == rct::RCTTypeBulletproofPlus);
amount = rct::h2d(ecdh_info.amount);
}
total += amount;
@@ -13133,10 +12875,10 @@ 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::transfer_details>> wallet2::export_outputs(bool all) const
+std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> wallet2::export_outputs(bool all) const
{
PERF_TIMER(export_outputs);
- std::vector<tools::wallet2::transfer_details> outs;
+ std::vector<tools::wallet2::exported_transfer_details> outs;
size_t offset = 0;
if (!all)
@@ -13148,7 +12890,22 @@ std::pair<uint64_t, std::vector<tools::wallet2::transfer_details>> wallet2::expo
{
const transfer_details &td = m_transfers[n];
- outs.push_back(td);
+ exported_transfer_details etd;
+ etd.m_pubkey = td.get_public_key();
+ etd.m_tx_pubkey = get_tx_pub_key_from_extra(td.m_tx, td.m_pk_index);
+ etd.m_internal_output_index = td.m_internal_output_index;
+ etd.m_global_output_index = td.m_global_output_index;
+ etd.m_flags.flags = 0;
+ etd.m_flags.m_spent = td.m_spent;
+ etd.m_flags.m_frozen = td.m_frozen;
+ etd.m_flags.m_rct = td.m_rct;
+ etd.m_flags.m_key_image_known = td.m_key_image_known;
+ etd.m_flags.m_key_image_request = td.m_key_image_request;
+ 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);
+
+ outs.push_back(etd);
}
return std::make_pair(offset, outs);
@@ -13241,6 +12998,93 @@ process:
return m_transfers.size();
}
//----------------------------------------------------------------------------------------------------
+size_t wallet2::import_outputs(const std::pair<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,
+ "Imported outputs omit more outputs that we know of. Try using export_outputs all.");
+
+ const size_t offset = outputs.first;
+ 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)
+ {
+ exported_transfer_details etd = outputs.second[i];
+ transfer_details &td = m_transfers[i + offset];
+
+ // setup td with "cheao" loaded data
+ td.m_block_height = 0;
+ td.m_txid = crypto::null_hash;
+ td.m_global_output_index = etd.m_global_output_index;
+ td.m_spent = etd.m_flags.m_spent;
+ td.m_frozen = etd.m_flags.m_frozen;
+ td.m_spent_height = 0;
+ td.m_mask = rct::identity();
+ td.m_amount = etd.m_amount;
+ td.m_rct = etd.m_flags.m_rct;
+ 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;
+
+ // skip those we've already imported, or which have different data
+ if (i + offset < original_size)
+ {
+ bool needs_processing = false;
+ if (!td.m_key_image_known)
+ needs_processing = true;
+ else if (!(etd.m_internal_output_index == td.m_internal_output_index))
+ needs_processing = true;
+ else if (!(etd.m_pubkey == td.get_public_key()))
+ needs_processing = true;
+
+ if (!needs_processing)
+ continue;
+ }
+
+ // construct a synthetix tx prefix that has the info we'll need: the output with its pubkey, the tx pubkey in extra
+ td.m_tx = {};
+
+ THROW_WALLET_EXCEPTION_IF(etd.m_internal_output_index >= 65536, error::wallet_internal_error, "internal output index seems outrageously high, rejecting");
+ td.m_internal_output_index = etd.m_internal_output_index;
+ cryptonote::txout_to_key tk;
+ tk.key = etd.m_pubkey;
+ cryptonote::tx_out out;
+ out.amount = etd.m_amount;
+ out.target = tk;
+ td.m_tx.vout.resize(etd.m_internal_output_index);
+ td.m_tx.vout.push_back(out);
+
+ td.m_pk_index = 0;
+ add_tx_pub_key_to_extra(td.m_tx, etd.m_tx_pubkey);
+ if (!etd.m_additional_tx_keys.empty())
+ add_additional_tx_pub_keys_to_extra(td.m_tx.extra, etd.m_additional_tx_keys);
+
+ // the hot wallet wouldn't have known about key images (except if we already exported them)
+ cryptonote::keypair in_ephemeral;
+
+ 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;
+ 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))
+ expand_subaddresses(td.m_subaddr_index);
+ td.m_key_image_known = true;
+ td.m_key_image_request = true;
+ td.m_key_image_partial = false;
+ THROW_WALLET_EXCEPTION_IF(in_ephemeral.pub != out_key,
+ error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key at index " + boost::lexical_cast<std::string>(i + offset));
+
+ m_key_images[td.m_key_image] = i + offset;
+ m_pub_keys[td.get_public_key()] = i + offset;
+ }
+
+ return m_transfers.size();
+}
+//----------------------------------------------------------------------------------------------------
size_t wallet2::import_outputs_from_str(const std::string &outputs_st)
{
PERF_TIMER(import_outputs_from_str);
@@ -13279,10 +13123,23 @@ size_t wallet2::import_outputs_from_str(const std::string &outputs_st)
try
{
std::string body(data, headerlen);
- std::pair<uint64_t, std::vector<tools::wallet2::transfer_details>> outputs;
+
+ std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> new_outputs;
try
{
binary_archive<false> ar{epee::strspan<std::uint8_t>(body)};
+ if (::serialization::serialize(ar, new_outputs))
+ if (::serialization::check_stream_state(ar))
+ loaded = true;
+ }
+ catch (...) {}
+ if (!loaded)
+ new_outputs.second.clear();
+
+ std::pair<uint64_t, std::vector<tools::wallet2::transfer_details>> outputs;
+ if (!loaded) try
+ {
+ binary_archive<false> ar{epee::strspan<std::uint8_t>(body)};
if (::serialization::serialize(ar, outputs))
if (::serialization::check_stream_state(ar))
loaded = true;
@@ -13308,7 +13165,7 @@ size_t wallet2::import_outputs_from_str(const std::string &outputs_st)
outputs.second = {};
}
- imported_outputs = import_outputs(outputs);
+ imported_outputs = new_outputs.second.empty() ? import_outputs(outputs) : import_outputs(new_outputs);
}
catch (const std::exception &e)
{
@@ -13318,13 +13175,6 @@ size_t wallet2::import_outputs_from_str(const std::string &outputs_st)
return imported_outputs;
}
//----------------------------------------------------------------------------------------------------
-crypto::public_key wallet2::get_multisig_signer_public_key(const crypto::secret_key &spend_skey) const
-{
- crypto::public_key pkey;
- crypto::secret_key_to_public_key(get_multisig_blinded_secret_key(spend_skey), pkey);
- return pkey;
-}
-//----------------------------------------------------------------------------------------------------
crypto::public_key wallet2::get_multisig_signer_public_key() const
{
CHECK_AND_ASSERT_THROW_MES(m_multisig, "Wallet is not multisig");
@@ -13368,7 +13218,7 @@ rct::multisig_kLRki wallet2::get_multisig_kLRki(size_t n, const rct::key &k) con
CHECK_AND_ASSERT_THROW_MES(n < m_transfers.size(), "Bad m_transfers index");
rct::multisig_kLRki kLRki;
kLRki.k = k;
- cryptonote::generate_multisig_LR(m_transfers[n].get_public_key(), rct::rct2sk(kLRki.k), (crypto::public_key&)kLRki.L, (crypto::public_key&)kLRki.R);
+ multisig::generate_multisig_LR(m_transfers[n].get_public_key(), rct::rct2sk(kLRki.k), (crypto::public_key&)kLRki.L, (crypto::public_key&)kLRki.R);
kLRki.ki = rct::ki2rct(m_transfers[n].m_key_image);
return kLRki;
}
@@ -13415,7 +13265,7 @@ crypto::key_image wallet2::get_multisig_composite_key_image(size_t n) const
for (const auto &info: td.m_multisig_info)
for (const auto &pki: info.m_partial_key_images)
pkis.push_back(pki);
- bool r = cryptonote::generate_multisig_composite_key_image(get_account().get_keys(), m_subaddresses, td.get_public_key(), tx_key, additional_tx_keys, td.m_internal_output_index, pkis, ki);
+ bool r = multisig::generate_multisig_composite_key_image(get_account().get_keys(), m_subaddresses, td.get_public_key(), tx_key, additional_tx_keys, td.m_internal_output_index, pkis, ki);
THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image");
return ki;
}
@@ -13438,7 +13288,7 @@ cryptonote::blobdata wallet2::export_multisig()
for (size_t m = 0; m < get_account().get_multisig_keys().size(); ++m)
{
// we want to export the partial key image, not the full one, so we can't use td.m_key_image
- bool r = generate_multisig_key_image(get_account().get_keys(), m, td.get_public_key(), ki);
+ bool r = multisig::generate_multisig_key_image(get_account().get_keys(), m, td.get_public_key(), ki);
CHECK_AND_ASSERT_THROW_MES(r, "Failed to generate key image");
info[n].m_partial_key_images.push_back(ki);
}
@@ -14353,9 +14203,10 @@ std::pair<size_t, uint64_t> wallet2::estimate_tx_size_and_weight(bool use_rct, i
n_outputs = 2; // extra dummy output
const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0);
+ const bool bulletproof_plus = use_fork_rules(get_bulletproof_plus_fork(), 0);
const bool clsag = use_fork_rules(get_clsag_fork(), 0);
- size_t size = estimate_tx_size(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof, clsag);
- uint64_t weight = estimate_tx_weight(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof, clsag);
+ size_t size = estimate_tx_size(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus);
+ uint64_t weight = estimate_tx_weight(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus);
return std::make_pair(size, weight);
}
//----------------------------------------------------------------------------------------------------
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index 7648becc8..ee0974fdc 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -373,6 +373,40 @@ private:
END_SERIALIZE()
};
+ struct exported_transfer_details
+ {
+ crypto::public_key m_pubkey;
+ uint64_t m_internal_output_index;
+ uint64_t m_global_output_index;
+ crypto::public_key m_tx_pubkey;
+ union
+ {
+ struct
+ {
+ uint8_t m_spent: 1;
+ uint8_t m_frozen: 1;
+ uint8_t m_rct: 1;
+ uint8_t m_key_image_known: 1;
+ uint8_t m_key_image_request: 1; // view wallets: we want to request it; cold wallets: it was requested
+ uint8_t m_key_image_partial: 1;
+ };
+ uint8_t flags;
+ } m_flags;
+ uint64_t m_amount;
+ std::vector<crypto::public_key> m_additional_tx_keys;
+
+ BEGIN_SERIALIZE_OBJECT()
+ VERSION_FIELD(0)
+ FIELD(m_pubkey)
+ VARINT_FIELD(m_internal_output_index)
+ VARINT_FIELD(m_global_output_index)
+ FIELD(m_tx_pubkey)
+ FIELD(m_flags.flags)
+ VARINT_FIELD(m_amount)
+ FIELD(m_additional_tx_keys)
+ END_SERIALIZE()
+ };
+
typedef std::vector<uint64_t> amounts_container;
struct payment_details
{
@@ -575,11 +609,15 @@ private:
{
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;
BEGIN_SERIALIZE_OBJECT()
- VERSION_FIELD(0)
+ VERSION_FIELD(1)
FIELD(txes)
- FIELD(transfers)
+ if (version >= 1)
+ FIELD(new_transfers)
+ else
+ FIELD(transfers)
END_SERIALIZE()
};
@@ -757,45 +795,20 @@ private:
* to other participants
*/
std::string make_multisig(const epee::wipeable_string &password,
- const std::vector<std::string> &info,
- uint32_t threshold);
+ const std::vector<std::string> &kex_messages,
+ const std::uint32_t threshold);
/*!
- * \brief Creates a multisig wallet
+ * \brief Increment the multisig key exchange round
* \return empty if done, non empty if we need to send another string
* to other participants
*/
- std::string make_multisig(const epee::wipeable_string &password,
- const std::vector<crypto::secret_key> &view_keys,
- const std::vector<crypto::public_key> &spend_keys,
- uint32_t threshold);
std::string exchange_multisig_keys(const epee::wipeable_string &password,
- const std::vector<std::string> &info);
- /*!
- * \brief Any but first round of keys exchange
- */
- std::string exchange_multisig_keys(const epee::wipeable_string &password,
- std::unordered_set<crypto::public_key> pkeys,
- std::vector<crypto::public_key> signers);
- /*!
- * \brief Finalizes creation of a multisig wallet
- */
- bool finalize_multisig(const epee::wipeable_string &password, const std::vector<std::string> &info);
- /*!
- * \brief Finalizes creation of a multisig wallet
- */
- bool finalize_multisig(const epee::wipeable_string &password, const std::unordered_set<crypto::public_key> &pkeys, std::vector<crypto::public_key> signers);
- /*!
- * Get a packaged multisig information string
- */
- std::string get_multisig_info() const;
- /*!
- * Verifies and extracts keys from a packaged multisig information string
- */
- static bool verify_multisig_info(const std::string &data, crypto::secret_key &skey, crypto::public_key &pkey);
+ const std::vector<std::string> &kex_messages);
/*!
- * Verifies and extracts keys from a packaged multisig information string
+ * \brief Get initial message to start multisig key exchange (before 'make_multisig()' is called)
+ * \return string to send to other participants
*/
- static bool verify_extra_multisig_info(const std::string &data, std::unordered_set<crypto::public_key> &pkeys, crypto::public_key &signer);
+ std::string get_multisig_first_kex_msg() const;
/*!
* Export multisig info
* This will generate and remember new k values
@@ -1229,6 +1242,8 @@ private:
void ignore_outputs_below(uint64_t value) { m_ignore_outputs_below = value; }
bool track_uses() const { return m_track_uses; }
void track_uses(bool value) { m_track_uses = value; }
+ bool show_wallet_name_when_locked() const { return m_show_wallet_name_when_locked; }
+ void show_wallet_name_when_locked(bool value) { m_show_wallet_name_when_locked = value; }
BackgroundMiningSetupType setup_background_mining() const { return m_setup_background_mining; }
void setup_background_mining(BackgroundMiningSetupType value) { m_setup_background_mining = value; }
uint32_t inactivity_lock_timeout() const { return m_inactivity_lock_timeout; }
@@ -1372,8 +1387,9 @@ 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::transfer_details>> export_outputs(bool all = false) const;
+ 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);
size_t import_outputs_from_str(const std::string &outputs_st);
payment_container export_payments() const;
@@ -1411,7 +1427,7 @@ private:
std::vector<std::pair<uint64_t, uint64_t>> estimate_backlog(const std::vector<std::pair<double, double>> &fee_levels);
std::vector<std::pair<uint64_t, uint64_t>> estimate_backlog(uint64_t min_tx_weight, uint64_t max_tx_weight, const std::vector<uint64_t> &fees);
- uint64_t estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) const;
+ uint64_t estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, bool bulletproof_plus, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) const;
uint64_t get_fee_multiplier(uint32_t priority, int fee_algorithm = -1);
uint64_t get_base_fee();
uint64_t get_fee_quantization_mask();
@@ -1477,7 +1493,6 @@ private:
void set_attribute(const std::string &key, const std::string &value);
bool get_attribute(const std::string &key, std::string &value) const;
- crypto::public_key get_multisig_signer_public_key(const crypto::secret_key &spend_skey) const;
crypto::public_key get_multisig_signer_public_key() const;
crypto::public_key get_multisig_signing_public_key(size_t idx) const;
crypto::public_key get_multisig_signing_public_key(const crypto::secret_key &skey) const;
@@ -1641,12 +1656,6 @@ private:
bool get_rct_distribution(uint64_t &start_height, std::vector<uint64_t> &distribution);
uint64_t get_segregation_fork_height() const;
- void unpack_multisig_info(const std::vector<std::string>& info,
- std::vector<crypto::public_key> &public_keys,
- std::vector<crypto::secret_key> &secret_keys) const;
- bool unpack_extra_multisig_info(const std::vector<std::string>& info,
- std::vector<crypto::public_key> &signers,
- std::unordered_set<crypto::public_key> &pkeys) const;
void cache_tx_data(const cryptonote::transaction& tx, const crypto::hash &txid, tx_cache_data &tx_cache_data) const;
std::shared_ptr<std::map<std::pair<uint64_t, uint64_t>, size_t>> create_output_tracker_cache() const;
@@ -1749,6 +1758,7 @@ private:
uint64_t m_ignore_outputs_above;
uint64_t m_ignore_outputs_below;
bool m_track_uses;
+ bool m_show_wallet_name_when_locked;
uint32_t m_inactivity_lock_timeout;
BackgroundMiningSetupType m_setup_background_mining;
bool m_persistent_rpc_client_id;
diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp
index 4655e24cd..a173b5a50 100644
--- a/src/wallet/wallet_rpc_server.cpp
+++ b/src/wallet/wallet_rpc_server.cpp
@@ -3938,7 +3938,7 @@ namespace tools
return false;
}
- res.multisig_info = m_wallet->get_multisig_info();
+ res.multisig_info = m_wallet->get_multisig_first_kex_msg();
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
@@ -4069,7 +4069,7 @@ namespace tools
catch (const std::exception &e)
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
- er.message = "Error calling import_multisig";
+ er.message = std::string{"Error calling import_multisig: "} + e.what();
return false;
}
@@ -4094,53 +4094,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)
{
- if (!m_wallet) return not_open(er);
- if (m_restricted)
- {
- er.code = WALLET_RPC_ERROR_CODE_DENIED;
- er.message = "Command unavailable in restricted mode.";
- return false;
- }
- bool ready;
- uint32_t threshold, total;
- if (!m_wallet->multisig(&ready, &threshold, &total))
- {
- er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG;
- er.message = "This wallet is not multisig";
- return false;
- }
- if (ready)
- {
- er.code = WALLET_RPC_ERROR_CODE_ALREADY_MULTISIG;
- er.message = "This wallet is multisig, and already finalized";
- return false;
- }
-
- if (req.multisig_info.size() < 1 || req.multisig_info.size() > total)
- {
- er.code = WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED;
- er.message = "Needs multisig info from more participants";
- return false;
- }
-
- try
- {
- if (!m_wallet->finalize_multisig(req.password, req.multisig_info))
- {
- er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
- er.message = "Error calling finalize_multisig";
- return false;
- }
- }
- catch (const std::exception &e)
- {
- er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
- er.message = std::string("Error calling finalize_multisig: ") + e.what();
- return false;
- }
- res.address = m_wallet->get_account().get_public_address_str(m_wallet->nettype());
-
- return true;
+ return false;
}
//------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_exchange_multisig_keys(const wallet_rpc::COMMAND_RPC_EXCHANGE_MULTISIG_KEYS::request& req, wallet_rpc::COMMAND_RPC_EXCHANGE_MULTISIG_KEYS::response& res, epee::json_rpc::error& er, const connection_context *ctx)
@@ -4168,7 +4122,7 @@ namespace tools
return false;
}
- if (req.multisig_info.size() < 1 || req.multisig_info.size() > total)
+ if (req.multisig_info.size() + 1 < total)
{
er.code = WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED;
er.message = "Needs multisig info from more participants";
@@ -4426,7 +4380,11 @@ namespace tools
return false;
}
- if (!m_wallet->set_daemon(req.address, boost::none, req.trusted, std::move(ssl_options)))
+ boost::optional<epee::net_utils::http::login> daemon_login{};
+ if (!req.username.empty() || !req.password.empty())
+ daemon_login.emplace(req.username, req.password);
+
+ if (!m_wallet->set_daemon(req.address, daemon_login, req.trusted, std::move(ssl_options)))
{
er.code = WALLET_RPC_ERROR_CODE_NO_DAEMON_CONNECTION;
er.message = std::string("Unable to set daemon");
diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h
index 248d31aa4..867ea54bd 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 23
+#define WALLET_RPC_VERSION_MINOR 24
#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
@@ -2504,24 +2504,17 @@ namespace wallet_rpc
struct COMMAND_RPC_FINALIZE_MULTISIG
{
+ // NOP
struct request_t
{
- std::string password;
- std::vector<std::string> multisig_info;
-
BEGIN_KV_SERIALIZE_MAP()
- KV_SERIALIZE(password)
- KV_SERIALIZE(multisig_info)
END_KV_SERIALIZE_MAP()
};
typedef epee::misc_utils::struct_init<request_t> request;
struct response_t
{
- std::string address;
-
BEGIN_KV_SERIALIZE_MAP()
- KV_SERIALIZE(address)
END_KV_SERIALIZE_MAP()
};
typedef epee::misc_utils::struct_init<response_t> response;
@@ -2664,6 +2657,8 @@ namespace wallet_rpc
struct request_t
{
std::string address;
+ std::string username;
+ std::string password;
bool trusted;
std::string ssl_support; // disabled, enabled, autodetect
std::string ssl_private_key_path;
@@ -2674,6 +2669,8 @@ namespace wallet_rpc
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(address)
+ KV_SERIALIZE(username)
+ KV_SERIALIZE(password)
KV_SERIALIZE_OPT(trusted, false)
KV_SERIALIZE_OPT(ssl_support, (std::string)"autodetect")
KV_SERIALIZE(ssl_private_key_path)
diff --git a/tests/core_tests/CMakeLists.txt b/tests/core_tests/CMakeLists.txt
index 7455639ca..c8583c476 100644
--- a/tests/core_tests/CMakeLists.txt
+++ b/tests/core_tests/CMakeLists.txt
@@ -44,6 +44,7 @@ set(core_tests_sources
v2_tests.cpp
rct.cpp
bulletproofs.cpp
+ bulletproof_plus.cpp
rct2.cpp
wallet_tools.cpp)
@@ -65,6 +66,7 @@ set(core_tests_headers
v2_tests.h
rct.h
bulletproofs.h
+ bulletproof_plus.h
rct2.h
wallet_tools.h)
diff --git a/tests/core_tests/bulletproof_plus.cpp b/tests/core_tests/bulletproof_plus.cpp
new file mode 100644
index 000000000..c3879e646
--- /dev/null
+++ b/tests/core_tests/bulletproof_plus.cpp
@@ -0,0 +1,373 @@
+// 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
+
+#include "ringct/rctSigs.h"
+#include "ringct/bulletproofs_plus.h"
+#include "chaingen.h"
+#include "bulletproof_plus.h"
+#include "device/device.hpp"
+
+using namespace epee;
+using namespace crypto;
+using namespace cryptonote;
+
+//----------------------------------------------------------------------------------------------------------------------
+// Tests
+
+bool gen_bpp_tx_validation_base::generate_with(std::vector<test_event_entry>& events,
+ size_t mixin, size_t n_txes, const uint64_t *amounts_paid, bool valid, const rct::RCTConfig *rct_config, uint8_t hf_version,
+ const std::function<bool(std::vector<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations, size_t tx_idx)> &pre_tx,
+ const std::function<bool(transaction &tx, size_t tx_idx)> &post_tx) const
+{
+ uint64_t ts_start = 1338224400;
+
+ GENERATE_ACCOUNT(miner_account);
+ MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start);
+
+ // create 12 miner accounts, and have them mine the next 12 blocks
+ cryptonote::account_base miner_accounts[12];
+ const cryptonote::block *prev_block = &blk_0;
+ cryptonote::block blocks[12 + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW];
+ for (size_t n = 0; n < 12; ++n) {
+ miner_accounts[n].generate();
+ CHECK_AND_ASSERT_MES(generator.construct_block_manually(blocks[n], *prev_block, miner_accounts[n],
+ test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_hf_version,
+ 2, 2, prev_block->timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long
+ crypto::hash(), 0, transaction(), std::vector<crypto::hash>(), 0, 0, 2),
+ false, "Failed to generate block");
+ events.push_back(blocks[n]);
+ prev_block = blocks + n;
+ }
+
+ // rewind
+ cryptonote::block blk_r, blk_last;
+ {
+ blk_last = blocks[11];
+ for (size_t i = 0; i < CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; ++i)
+ {
+ CHECK_AND_ASSERT_MES(generator.construct_block_manually(blocks[12+i], blk_last, miner_account,
+ test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_hf_version,
+ 2, 2, blk_last.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long
+ crypto::hash(), 0, transaction(), std::vector<crypto::hash>(), 0, 0, 2),
+ false, "Failed to generate block");
+ events.push_back(blocks[12+i]);
+ blk_last = blocks[12+i];
+ }
+ blk_r = blk_last;
+ }
+
+ // create 4 txes from these miners in another block, to generate some rct outputs
+ std::vector<transaction> rct_txes;
+ cryptonote::block blk_txes;
+ std::vector<crypto::hash> starting_rct_tx_hashes;
+ uint64_t fees = 0;
+ static const uint64_t input_amounts_available[] = {5000000000000, 30000000000000, 100000000000, 80000000000};
+ for (size_t n = 0; n < n_txes; ++n)
+ {
+ std::vector<tx_source_entry> sources;
+
+ sources.resize(1);
+ tx_source_entry& src = sources.back();
+
+ const uint64_t needed_amount = input_amounts_available[n];
+ src.amount = input_amounts_available[n];
+ size_t real_index_in_tx = 0;
+ for (size_t m = 0; m <= mixin; ++m) {
+ size_t index_in_tx = 0;
+ for (size_t i = 0; i < blocks[m].miner_tx.vout.size(); ++i)
+ if (blocks[m].miner_tx.vout[i].amount == needed_amount)
+ index_in_tx = i;
+ CHECK_AND_ASSERT_MES(blocks[m].miner_tx.vout[index_in_tx].amount == needed_amount, false, "Expected amount not found");
+ src.push_output(m, boost::get<txout_to_key>(blocks[m].miner_tx.vout[index_in_tx].target).key, src.amount);
+ if (m == n)
+ real_index_in_tx = index_in_tx;
+ }
+ src.real_out_tx_key = cryptonote::get_tx_pub_key_from_extra(blocks[n].miner_tx);
+ src.real_output = n;
+ src.real_output_in_tx_index = real_index_in_tx;
+ src.mask = rct::identity();
+ src.rct = false;
+
+ //fill outputs entry
+ tx_destination_entry td;
+ td.addr = miner_accounts[n].get_keys().m_account_address;
+ std::vector<tx_destination_entry> destinations;
+ for (int o = 0; amounts_paid[o] != (uint64_t)-1; ++o)
+ {
+ td.amount = amounts_paid[o];
+ destinations.push_back(td);
+ }
+
+ if (pre_tx && !pre_tx(sources, destinations, n))
+ {
+ MDEBUG("pre_tx returned failure");
+ return false;
+ }
+
+ crypto::secret_key tx_key;
+ std::vector<crypto::secret_key> additional_tx_keys;
+ std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses;
+ subaddresses[miner_accounts[n].get_keys().m_account_address.m_spend_public_key] = {0,0};
+ rct_txes.resize(rct_txes.size() + 1);
+ bool r = construct_tx_and_get_tx_key(miner_accounts[n].get_keys(), subaddresses, sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), rct_txes.back(), 0, tx_key, additional_tx_keys, true, rct_config[n]);
+ CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction");
+
+ if (post_tx && !post_tx(rct_txes.back(), n))
+ {
+ MDEBUG("post_tx returned failure");
+ return false;
+ }
+
+ //events.push_back(rct_txes.back());
+ starting_rct_tx_hashes.push_back(get_transaction_hash(rct_txes.back()));
+ LOG_PRINT_L0("Test tx: " << obj_to_json_str(rct_txes.back()));
+
+ for (int o = 0; amounts_paid[o] != (uint64_t)-1; ++o)
+ {
+ crypto::key_derivation derivation;
+ bool r = crypto::generate_key_derivation(destinations[o].addr.m_view_public_key, tx_key, derivation);
+ CHECK_AND_ASSERT_MES(r, false, "Failed to generate key derivation");
+ crypto::secret_key amount_key;
+ crypto::derivation_to_scalar(derivation, o, amount_key);
+ rct::key rct_tx_mask;
+ const uint8_t type = rct_txes.back().rct_signatures.type;
+ if (rct::is_rct_simple(type))
+ rct::decodeRctSimple(rct_txes.back().rct_signatures, rct::sk2rct(amount_key), o, rct_tx_mask, hw::get_device("default"));
+ else
+ rct::decodeRct(rct_txes.back().rct_signatures, rct::sk2rct(amount_key), o, rct_tx_mask, hw::get_device("default"));
+ }
+
+ while (amounts_paid[0] != (size_t)-1)
+ ++amounts_paid;
+ ++amounts_paid;
+
+ uint64_t fee = 0;
+ get_tx_fee(rct_txes.back(), fee);
+ fees += fee;
+ }
+ if (!valid)
+ DO_CALLBACK(events, "mark_invalid_tx");
+ events.push_back(rct_txes);
+
+ CHECK_AND_ASSERT_MES(generator.construct_block_manually(blk_txes, blk_last, miner_account,
+ test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_tx_hashes | test_generator::bf_hf_version | test_generator::bf_max_outs | test_generator::bf_tx_fees,
+ hf_version, hf_version, blk_last.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long
+ crypto::hash(), 0, transaction(), starting_rct_tx_hashes, 0, 6, hf_version, fees),
+ false, "Failed to generate block");
+ if (!valid)
+ DO_CALLBACK(events, "mark_invalid_block");
+ events.push_back(blk_txes);
+ blk_last = blk_txes;
+
+ return true;
+}
+
+bool gen_bpp_tx_validation_base::check_bpp(const cryptonote::transaction &tx, size_t tx_idx, const size_t *sizes, const char *context) const
+{
+ DEFINE_TESTS_ERROR_CONTEXT(context);
+ CHECK_TEST_CONDITION(tx.version >= 2);
+ CHECK_TEST_CONDITION(rct::is_rct_bulletproof_plus(tx.rct_signatures.type));
+ size_t n_sizes = 0, n_amounts = 0;
+ for (size_t n = 0; n < tx_idx; ++n)
+ {
+ while (sizes[0] != (size_t)-1)
+ ++sizes;
+ ++sizes;
+ }
+ while (sizes[n_sizes] != (size_t)-1)
+ n_amounts += sizes[n_sizes++];
+ CHECK_TEST_CONDITION(tx.rct_signatures.p.bulletproofs_plus.size() == n_sizes);
+ CHECK_TEST_CONDITION(rct::n_bulletproof_plus_max_amounts(tx.rct_signatures.p.bulletproofs_plus) == n_amounts);
+ for (size_t n = 0; n < n_sizes; ++n)
+ CHECK_TEST_CONDITION(rct::n_bulletproof_plus_max_amounts(tx.rct_signatures.p.bulletproofs_plus[n]) == sizes[n]);
+ return true;
+}
+
+bool gen_bpp_tx_invalid_before_fork::generate(std::vector<test_event_entry>& events) const
+{
+ const size_t mixin = 10;
+ const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1};
+ const size_t bp_sizes[] = {2, (size_t)-1};
+ const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 4 } };
+ return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_BULLETPROOF_PLUS - 1, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bpp(tx, tx_idx, bp_sizes, "gen_bpp_tx_invalid_before_fork"); });
+}
+
+bool gen_bpp_tx_valid_at_fork::generate(std::vector<test_event_entry>& events) const
+{
+ const size_t mixin = 10;
+ const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1};
+ const size_t bp_sizes[] = {2, (size_t)-1};
+ const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 4 } };
+ return generate_with(events, mixin, 1, amounts_paid, true, rct_config, HF_VERSION_BULLETPROOF_PLUS, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bpp(tx, tx_idx, bp_sizes, "gen_bpp_tx_valid_at_fork"); });
+}
+
+bool gen_bpp_tx_invalid_1_1::generate(std::vector<test_event_entry>& events) const
+{
+ const size_t mixin = 10;
+ const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1};
+ const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof , 4 } };
+ return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_BULLETPROOF_PLUS, NULL, NULL);
+}
+
+bool gen_bpp_tx_valid_2::generate(std::vector<test_event_entry>& events) const
+{
+ const size_t mixin = 10;
+ const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1};
+ const size_t bp_sizes[] = {2, (size_t)-1};
+ const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 4 } };
+ return generate_with(events, mixin, 1, amounts_paid, true, rct_config, HF_VERSION_BULLETPROOF_PLUS, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bpp(tx, tx_idx, bp_sizes, "gen_bpp_tx_valid_2"); });
+}
+
+bool gen_bpp_tx_valid_3::generate(std::vector<test_event_entry>& events) const
+{
+ const size_t mixin = 10;
+ const uint64_t amounts_paid[] = {5000, 5000, 5000, (uint64_t)-1};
+ const size_t bp_sizes[] = {4, (size_t)-1};
+ const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof , 4 } };
+ return generate_with(events, mixin, 1, amounts_paid, true, rct_config, HF_VERSION_BULLETPROOF_PLUS, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bpp(tx, tx_idx, bp_sizes, "gen_bpp_tx_valid_3"); });
+}
+
+bool gen_bpp_tx_valid_16::generate(std::vector<test_event_entry>& events) const
+{
+ const size_t mixin = 10;
+ const uint64_t amounts_paid[] = {500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, (uint64_t)-1};
+ const size_t bp_sizes[] = {16, (size_t)-1};
+ const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof , 4 } };
+ return generate_with(events, mixin, 1, amounts_paid, true, rct_config, HF_VERSION_BULLETPROOF_PLUS, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bpp(tx, tx_idx, bp_sizes, "gen_bpp_tx_valid_16"); });
+}
+
+bool gen_bpp_tx_invalid_4_2_1::generate(std::vector<test_event_entry>& events) const
+{
+ const size_t mixin = 10;
+ const uint64_t amounts_paid[] = {1000, 1000, 1000, 1000, 1000, 1000, 1000, (uint64_t)-1};
+ const rct::RCTConfig rct_config[] = { { rct::RangeProofMultiOutputBulletproof , 4 } };
+ return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_BULLETPROOF_PLUS, NULL, NULL);
+}
+
+bool gen_bpp_tx_invalid_16_16::generate(std::vector<test_event_entry>& events) const
+{
+ const size_t mixin = 10;
+ const uint64_t amounts_paid[] = {1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, (uint64_t)-1};
+ const rct::RCTConfig rct_config[] = { { rct::RangeProofMultiOutputBulletproof , 4 } };
+ return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_BULLETPROOF_PLUS, NULL, NULL);
+}
+
+bool gen_bpp_txs_valid_2_and_2::generate(std::vector<test_event_entry>& events) const
+{
+ const size_t mixin = 10;
+ const uint64_t amounts_paid[] = {1000, 1000, (size_t)-1, 1000, 1000, (uint64_t)-1};
+ const size_t bp_sizes[] = {2, (size_t)-1, 2, (size_t)-1};
+ const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 4 }, {rct::RangeProofPaddedBulletproof, 4 } };
+ return generate_with(events, mixin, 2, amounts_paid, true, rct_config, HF_VERSION_BULLETPROOF_PLUS, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bpp(tx, tx_idx, bp_sizes, "gen_bpp_txs_valid_2_and_2"); });
+}
+
+bool gen_bpp_txs_invalid_2_and_8_2_and_16_16_1::generate(std::vector<test_event_entry>& events) const
+{
+ const size_t mixin = 10;
+ const uint64_t amounts_paid[] = {1000, 1000, (uint64_t)-1, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, (uint64_t)-1, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, (uint64_t)-1};
+ const rct::RCTConfig rct_config[] = {{rct::RangeProofMultiOutputBulletproof, 4}, {rct::RangeProofMultiOutputBulletproof, 4}, {rct::RangeProofMultiOutputBulletproof, 4}};
+ return generate_with(events, mixin, 3, amounts_paid, false, rct_config, HF_VERSION_BULLETPROOF_PLUS, NULL, NULL);
+}
+
+bool gen_bpp_txs_valid_2_and_3_and_2_and_4::generate(std::vector<test_event_entry>& events) const
+{
+ const size_t mixin = 10;
+ const uint64_t amounts_paid[] = {11111115000, 11111115000, (uint64_t)-1, 11111115000, 11111115000, 11111115001, (uint64_t)-1, 11111115000, 11111115002, (uint64_t)-1, 11111115000, 11111115000, 11111115000, 11111115003, (uint64_t)-1};
+ const rct::RCTConfig rct_config[] = {{rct::RangeProofPaddedBulletproof, 4}, {rct::RangeProofPaddedBulletproof, 4}, {rct::RangeProofPaddedBulletproof, 4}, {rct::RangeProofPaddedBulletproof, 4}};
+ const size_t bp_sizes[] = {2, (size_t)-1, 4, (size_t)-1, 2, (size_t)-1, 4, (size_t)-1};
+ return generate_with(events, mixin, 4, amounts_paid, true, rct_config, HF_VERSION_BULLETPROOF_PLUS, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx) { return check_bpp(tx, tx_idx, bp_sizes, "gen_bpp_txs_valid_2_and_3_and_2_and_4"); });
+}
+
+bool gen_bpp_tx_invalid_not_enough_proofs::generate(std::vector<test_event_entry>& events) const
+{
+ DEFINE_TESTS_ERROR_CONTEXT("gen_bpp_tx_invalid_not_enough_proofs");
+ const size_t mixin = 10;
+ const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1};
+ const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 4 } };
+ return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_BULLETPROOF_PLUS, NULL, [&](cryptonote::transaction &tx, size_t idx){
+ CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproofPlus);
+ CHECK_TEST_CONDITION(!tx.rct_signatures.p.bulletproofs_plus.empty());
+ tx.rct_signatures.p.bulletproofs_plus.pop_back();
+ CHECK_TEST_CONDITION(!tx.rct_signatures.p.bulletproofs_plus.empty());
+ return true;
+ });
+}
+
+bool gen_bpp_tx_invalid_empty_proofs::generate(std::vector<test_event_entry>& events) const
+{
+ DEFINE_TESTS_ERROR_CONTEXT("gen_bpp_tx_invalid_empty_proofs");
+ const size_t mixin = 10;
+ const uint64_t amounts_paid[] = {50000, 50000, (uint64_t)-1};
+ const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 4 } };
+ return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_BULLETPROOF_PLUS, NULL, [&](cryptonote::transaction &tx, size_t idx){
+ CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproofPlus);
+ tx.rct_signatures.p.bulletproofs_plus.clear();
+ return true;
+ });
+}
+
+bool gen_bpp_tx_invalid_too_many_proofs::generate(std::vector<test_event_entry>& events) const
+{
+ DEFINE_TESTS_ERROR_CONTEXT("gen_bpp_tx_invalid_too_many_proofs");
+ const size_t mixin = 10;
+ const uint64_t amounts_paid[] = {10000, (uint64_t)-1};
+ const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 4 } };
+ return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_BULLETPROOF_PLUS, NULL, [&](cryptonote::transaction &tx, size_t idx){
+ CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproofPlus);
+ CHECK_TEST_CONDITION(!tx.rct_signatures.p.bulletproofs_plus.empty());
+ tx.rct_signatures.p.bulletproofs_plus.push_back(tx.rct_signatures.p.bulletproofs_plus.back());
+ return true;
+ });
+}
+
+bool gen_bpp_tx_invalid_wrong_amount::generate(std::vector<test_event_entry>& events) const
+{
+ DEFINE_TESTS_ERROR_CONTEXT("gen_bpp_tx_invalid_wrong_amount");
+ const size_t mixin = 10;
+ const uint64_t amounts_paid[] = {10000, (uint64_t)-1};
+ const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 4 } };
+ return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_BULLETPROOF_PLUS, NULL, [&](cryptonote::transaction &tx, size_t idx){
+ CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproofPlus);
+ CHECK_TEST_CONDITION(!tx.rct_signatures.p.bulletproofs_plus.empty());
+ tx.rct_signatures.p.bulletproofs_plus.back() = rct::bulletproof_plus_PROVE(1000, rct::skGen());
+ return true;
+ });
+}
+
+bool gen_bpp_tx_invalid_clsag_type::generate(std::vector<test_event_entry>& events) const
+{
+ DEFINE_TESTS_ERROR_CONTEXT("gen_bpp_tx_invalid_clsag_type");
+ const size_t mixin = 10;
+ const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1};
+ const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 3 } };
+ return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_BULLETPROOF_PLUS + 1, NULL, [&](cryptonote::transaction &tx, size_t tx_idx){
+ return true;
+ });
+}
diff --git a/tests/core_tests/bulletproof_plus.h b/tests/core_tests/bulletproof_plus.h
new file mode 100644
index 000000000..481044fd4
--- /dev/null
+++ b/tests/core_tests/bulletproof_plus.h
@@ -0,0 +1,206 @@
+// 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 "chaingen.h"
+
+struct gen_bpp_tx_validation_base : public test_chain_unit_base
+{
+ gen_bpp_tx_validation_base()
+ : m_invalid_tx_index(0)
+ , m_invalid_block_index(0)
+ {
+ REGISTER_CALLBACK_METHOD(gen_bpp_tx_validation_base, mark_invalid_tx);
+ REGISTER_CALLBACK_METHOD(gen_bpp_tx_validation_base, mark_invalid_block);
+ }
+
+ bool check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_idx, const cryptonote::transaction& /*tx*/)
+ {
+ if (m_invalid_tx_index == event_idx)
+ return tvc.m_verifivation_failed;
+ else
+ return !tvc.m_verifivation_failed && tx_added;
+ }
+
+ bool check_tx_verification_context_array(const std::vector<cryptonote::tx_verification_context>& tvcs, size_t tx_added, size_t event_idx, const std::vector<cryptonote::transaction>& /*txs*/)
+ {
+ size_t failed = 0;
+ for (const cryptonote::tx_verification_context &tvc: tvcs)
+ if (tvc.m_verifivation_failed)
+ ++failed;
+ if (m_invalid_tx_index == event_idx)
+ return failed > 0;
+ else
+ return failed == 0 && tx_added == tvcs.size();
+ }
+
+ bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& /*block*/)
+ {
+ if (m_invalid_block_index == event_idx)
+ return bvc.m_verifivation_failed;
+ else
+ return !bvc.m_verifivation_failed;
+ }
+
+ bool mark_invalid_block(cryptonote::core& /*c*/, size_t ev_index, const std::vector<test_event_entry>& /*events*/)
+ {
+ m_invalid_block_index = ev_index + 1;
+ return true;
+ }
+
+ bool mark_invalid_tx(cryptonote::core& /*c*/, size_t ev_index, const std::vector<test_event_entry>& /*events*/)
+ {
+ m_invalid_tx_index = ev_index + 1;
+ return true;
+ }
+
+ bool generate_with(std::vector<test_event_entry>& events, size_t mixin,
+ size_t n_txes, const uint64_t *amounts_paid, bool valid, const rct::RCTConfig *rct_config, uint8_t hf_version,
+ const std::function<bool(std::vector<cryptonote::tx_source_entry> &sources, std::vector<cryptonote::tx_destination_entry> &destinations, size_t)> &pre_tx,
+ const std::function<bool(cryptonote::transaction &tx, size_t)> &post_tx) const;
+
+ bool check_bpp(const cryptonote::transaction &tx, size_t tx_idx, const size_t *sizes, const char *context) const;
+
+private:
+ size_t m_invalid_tx_index;
+ size_t m_invalid_block_index;
+};
+
+template<>
+struct get_test_options<gen_bpp_tx_validation_base> {
+ const std::pair<uint8_t, uint64_t> hard_forks[4] = {std::make_pair(1, 0), std::make_pair(2, 1), std::make_pair(HF_VERSION_BULLETPROOF_PLUS, 73), std::make_pair(0, 0)};
+ const cryptonote::test_options test_options = {
+ hard_forks, 0
+ };
+};
+
+template<uint8_t test_version = 1>
+struct get_bpp_versioned_test_options: public get_test_options<gen_bpp_tx_validation_base> {
+ const std::pair<uint8_t, uint64_t> hard_forks[4] = {std::make_pair(1, 0), std::make_pair(2, 1), std::make_pair(test_version, 73), std::make_pair(0, 0)};
+ const cryptonote::test_options test_options = {
+ hard_forks, 0
+ };
+};
+
+struct gen_bpp_tx_invalid_before_fork : public gen_bpp_tx_validation_base
+{
+ bool generate(std::vector<test_event_entry>& events) const;
+};
+template<> struct get_test_options<gen_bpp_tx_invalid_before_fork>: public get_bpp_versioned_test_options<HF_VERSION_BULLETPROOF_PLUS - 1> {};
+
+struct gen_bpp_tx_valid_at_fork : public gen_bpp_tx_validation_base
+{
+ bool generate(std::vector<test_event_entry>& events) const;
+};
+template<> struct get_test_options<gen_bpp_tx_valid_at_fork>: public get_bpp_versioned_test_options<HF_VERSION_BULLETPROOF_PLUS> {};
+
+struct gen_bpp_tx_invalid_1_1 : public gen_bpp_tx_validation_base
+{
+ bool generate(std::vector<test_event_entry>& events) const;
+};
+template<> struct get_test_options<gen_bpp_tx_invalid_1_1>: public get_bpp_versioned_test_options<HF_VERSION_BULLETPROOF_PLUS> {};
+
+struct gen_bpp_tx_valid_2 : public gen_bpp_tx_validation_base
+{
+ bool generate(std::vector<test_event_entry>& events) const;
+};
+template<> struct get_test_options<gen_bpp_tx_valid_2>: public get_bpp_versioned_test_options<HF_VERSION_BULLETPROOF_PLUS> {};
+
+struct gen_bpp_tx_valid_3 : public gen_bpp_tx_validation_base
+{
+ bool generate(std::vector<test_event_entry>& events) const;
+};
+template<> struct get_test_options<gen_bpp_tx_valid_3>: public get_bpp_versioned_test_options<HF_VERSION_BULLETPROOF_PLUS> {};
+
+struct gen_bpp_tx_valid_16 : public gen_bpp_tx_validation_base
+{
+ bool generate(std::vector<test_event_entry>& events) const;
+};
+template<> struct get_test_options<gen_bpp_tx_valid_16>: public get_bpp_versioned_test_options<HF_VERSION_BULLETPROOF_PLUS> {};
+
+struct gen_bpp_tx_invalid_4_2_1 : public gen_bpp_tx_validation_base
+{
+ bool generate(std::vector<test_event_entry>& events) const;
+};
+template<> struct get_test_options<gen_bpp_tx_invalid_4_2_1>: public get_bpp_versioned_test_options<HF_VERSION_BULLETPROOF_PLUS> {};
+
+struct gen_bpp_tx_invalid_16_16 : public gen_bpp_tx_validation_base
+{
+ bool generate(std::vector<test_event_entry>& events) const;
+};
+template<> struct get_test_options<gen_bpp_tx_invalid_16_16>: public get_bpp_versioned_test_options<HF_VERSION_BULLETPROOF_PLUS> {};
+
+struct gen_bpp_txs_valid_2_and_2 : public gen_bpp_tx_validation_base
+{
+ bool generate(std::vector<test_event_entry>& events) const;
+};
+template<> struct get_test_options<gen_bpp_txs_valid_2_and_2>: public get_bpp_versioned_test_options<HF_VERSION_BULLETPROOF_PLUS> {};
+
+struct gen_bpp_txs_invalid_2_and_8_2_and_16_16_1 : public gen_bpp_tx_validation_base
+{
+ bool generate(std::vector<test_event_entry>& events) const;
+};
+template<> struct get_test_options<gen_bpp_txs_invalid_2_and_8_2_and_16_16_1>: public get_bpp_versioned_test_options<HF_VERSION_BULLETPROOF_PLUS> {};
+
+struct gen_bpp_txs_valid_2_and_3_and_2_and_4 : public gen_bpp_tx_validation_base
+{
+ bool generate(std::vector<test_event_entry>& events) const;
+};
+template<> struct get_test_options<gen_bpp_txs_valid_2_and_3_and_2_and_4>: public get_bpp_versioned_test_options<HF_VERSION_BULLETPROOF_PLUS> {};
+
+struct gen_bpp_tx_invalid_not_enough_proofs : public gen_bpp_tx_validation_base
+{
+ bool generate(std::vector<test_event_entry>& events) const;
+};
+template<> struct get_test_options<gen_bpp_tx_invalid_not_enough_proofs>: public get_bpp_versioned_test_options<HF_VERSION_BULLETPROOF_PLUS> {};
+
+struct gen_bpp_tx_invalid_empty_proofs : public gen_bpp_tx_validation_base
+{
+ bool generate(std::vector<test_event_entry>& events) const;
+};
+template<> struct get_test_options<gen_bpp_tx_invalid_empty_proofs>: public get_bpp_versioned_test_options<HF_VERSION_BULLETPROOF_PLUS> {};
+
+struct gen_bpp_tx_invalid_too_many_proofs : public gen_bpp_tx_validation_base
+{
+ bool generate(std::vector<test_event_entry>& events) const;
+};
+template<> struct get_test_options<gen_bpp_tx_invalid_too_many_proofs>: public get_bpp_versioned_test_options<HF_VERSION_BULLETPROOF_PLUS> {};
+
+struct gen_bpp_tx_invalid_wrong_amount : public gen_bpp_tx_validation_base
+{
+ bool generate(std::vector<test_event_entry>& events) const;
+};
+template<> struct get_test_options<gen_bpp_tx_invalid_wrong_amount>: public get_bpp_versioned_test_options<HF_VERSION_BULLETPROOF_PLUS> {};
+
+struct gen_bpp_tx_invalid_clsag_type : public gen_bpp_tx_validation_base
+{
+ bool generate(std::vector<test_event_entry>& events) const;
+};
+template<> struct get_test_options<gen_bpp_tx_invalid_clsag_type>: public get_bpp_versioned_test_options<HF_VERSION_BULLETPROOF_PLUS + 1> {};
diff --git a/tests/core_tests/bulletproofs.cpp b/tests/core_tests/bulletproofs.cpp
index c46b5e657..e7b42405b 100644
--- a/tests/core_tests/bulletproofs.cpp
+++ b/tests/core_tests/bulletproofs.cpp
@@ -158,7 +158,7 @@ bool gen_bp_tx_validation_base::generate_with(std::vector<test_event_entry>& eve
crypto::derivation_to_scalar(derivation, o, amount_key);
rct::key rct_tx_mask;
const uint8_t type = rct_txes.back().rct_signatures.type;
- if (type == rct::RCTTypeSimple || type == rct::RCTTypeBulletproof || type == rct::RCTTypeBulletproof2 || type == rct::RCTTypeCLSAG)
+ if (rct::is_rct_simple(type))
rct::decodeRctSimple(rct_txes.back().rct_signatures, rct::sk2rct(amount_key), o, rct_tx_mask, hw::get_device("default"));
else
rct::decodeRct(rct_txes.back().rct_signatures, rct::sk2rct(amount_key), o, rct_tx_mask, hw::get_device("default"));
@@ -232,7 +232,7 @@ bool gen_bp_tx_invalid_1_1::generate(std::vector<test_event_entry>& events) cons
{
const size_t mixin = 10;
const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1};
- const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof , 0 } };
+ const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof , 3 } };
return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_CLSAG, NULL, NULL);
}
@@ -241,7 +241,7 @@ bool gen_bp_tx_valid_2::generate(std::vector<test_event_entry>& events) const
const size_t mixin = 10;
const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1};
const size_t bp_sizes[] = {2, (size_t)-1};
- const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 0 } };
+ const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 3 } };
return generate_with(events, mixin, 1, amounts_paid, true, rct_config, HF_VERSION_CLSAG, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_valid_2"); });
}
@@ -250,7 +250,7 @@ bool gen_bp_tx_valid_3::generate(std::vector<test_event_entry>& events) const
const size_t mixin = 10;
const uint64_t amounts_paid[] = {5000, 5000, 5000, (uint64_t)-1};
const size_t bp_sizes[] = {4, (size_t)-1};
- const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof , 0 } };
+ const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 3 } };
return generate_with(events, mixin, 1, amounts_paid, true, rct_config, HF_VERSION_CLSAG, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_valid_3"); });
}
@@ -259,7 +259,7 @@ bool gen_bp_tx_valid_16::generate(std::vector<test_event_entry>& events) const
const size_t mixin = 10;
const uint64_t amounts_paid[] = {500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, (uint64_t)-1};
const size_t bp_sizes[] = {16, (size_t)-1};
- const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof , 0 } };
+ const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 3 } };
return generate_with(events, mixin, 1, amounts_paid, true, rct_config, HF_VERSION_CLSAG, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_valid_16"); });
}
@@ -267,7 +267,7 @@ bool gen_bp_tx_invalid_4_2_1::generate(std::vector<test_event_entry>& events) co
{
const size_t mixin = 10;
const uint64_t amounts_paid[] = {1000, 1000, 1000, 1000, 1000, 1000, 1000, (uint64_t)-1};
- const rct::RCTConfig rct_config[] = { { rct::RangeProofMultiOutputBulletproof , 0 } };
+ const rct::RCTConfig rct_config[] = { { rct::RangeProofMultiOutputBulletproof, 3 } };
return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_CLSAG, NULL, NULL);
}
@@ -275,7 +275,7 @@ bool gen_bp_tx_invalid_16_16::generate(std::vector<test_event_entry>& events) co
{
const size_t mixin = 10;
const uint64_t amounts_paid[] = {1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, (uint64_t)-1};
- const rct::RCTConfig rct_config[] = { { rct::RangeProofMultiOutputBulletproof , 0 } };
+ const rct::RCTConfig rct_config[] = { { rct::RangeProofMultiOutputBulletproof, 3 } };
return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_CLSAG, NULL, NULL);
}
@@ -284,7 +284,7 @@ bool gen_bp_txs_valid_2_and_2::generate(std::vector<test_event_entry>& events) c
const size_t mixin = 10;
const uint64_t amounts_paid[] = {1000, 1000, (size_t)-1, 1000, 1000, (uint64_t)-1};
const size_t bp_sizes[] = {2, (size_t)-1, 2, (size_t)-1};
- const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 0 }, {rct::RangeProofPaddedBulletproof, 0 } };
+ const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 3 }, {rct::RangeProofPaddedBulletproof, 3 } };
return generate_with(events, mixin, 2, amounts_paid, true, rct_config, HF_VERSION_CLSAG, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_txs_valid_2_and_2"); });
}
@@ -292,7 +292,7 @@ bool gen_bp_txs_invalid_2_and_8_2_and_16_16_1::generate(std::vector<test_event_e
{
const size_t mixin = 10;
const uint64_t amounts_paid[] = {1000, 1000, (uint64_t)-1, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, (uint64_t)-1, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, (uint64_t)-1};
- const rct::RCTConfig rct_config[] = {{rct::RangeProofMultiOutputBulletproof, 0}, {rct::RangeProofMultiOutputBulletproof, 0}, {rct::RangeProofMultiOutputBulletproof, 0}};
+ const rct::RCTConfig rct_config[] = {{rct::RangeProofMultiOutputBulletproof, 3}, {rct::RangeProofMultiOutputBulletproof, 3}, {rct::RangeProofMultiOutputBulletproof, 3}};
return generate_with(events, mixin, 3, amounts_paid, false, rct_config, HF_VERSION_CLSAG, NULL, NULL);
}
@@ -300,7 +300,7 @@ bool gen_bp_txs_valid_2_and_3_and_2_and_4::generate(std::vector<test_event_entry
{
const size_t mixin = 10;
const uint64_t amounts_paid[] = {11111115000, 11111115000, (uint64_t)-1, 11111115000, 11111115000, 11111115001, (uint64_t)-1, 11111115000, 11111115002, (uint64_t)-1, 11111115000, 11111115000, 11111115000, 11111115003, (uint64_t)-1};
- const rct::RCTConfig rct_config[] = {{rct::RangeProofPaddedBulletproof, 0}, {rct::RangeProofPaddedBulletproof, 0}, {rct::RangeProofPaddedBulletproof, 0}, {rct::RangeProofPaddedBulletproof, 0}};
+ const rct::RCTConfig rct_config[] = {{rct::RangeProofPaddedBulletproof, 3}, {rct::RangeProofPaddedBulletproof, 3}, {rct::RangeProofPaddedBulletproof, 3}, {rct::RangeProofPaddedBulletproof, 3}};
const size_t bp_sizes[] = {2, (size_t)-1, 4, (size_t)-1, 2, (size_t)-1, 4, (size_t)-1};
return generate_with(events, mixin, 4, amounts_paid, true, rct_config, HF_VERSION_CLSAG, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx) { return check_bp(tx, tx_idx, bp_sizes, "gen_bp_txs_valid_2_and_3_and_2_and_4"); });
}
@@ -310,7 +310,7 @@ bool gen_bp_tx_invalid_not_enough_proofs::generate(std::vector<test_event_entry>
DEFINE_TESTS_ERROR_CONTEXT("gen_bp_tx_invalid_not_enough_proofs");
const size_t mixin = 10;
const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1};
- const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 0 } };
+ const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 3 } };
return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_CLSAG, NULL, [&](cryptonote::transaction &tx, size_t idx){
CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproof || tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG);
CHECK_TEST_CONDITION(!tx.rct_signatures.p.bulletproofs.empty());
@@ -325,7 +325,7 @@ bool gen_bp_tx_invalid_empty_proofs::generate(std::vector<test_event_entry>& eve
DEFINE_TESTS_ERROR_CONTEXT("gen_bp_tx_invalid_empty_proofs");
const size_t mixin = 10;
const uint64_t amounts_paid[] = {50000, 50000, (uint64_t)-1};
- const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 0 } };
+ const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 3 } };
return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_CLSAG, NULL, [&](cryptonote::transaction &tx, size_t idx){
CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproof || tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG);
tx.rct_signatures.p.bulletproofs.clear();
@@ -338,7 +338,7 @@ bool gen_bp_tx_invalid_too_many_proofs::generate(std::vector<test_event_entry>&
DEFINE_TESTS_ERROR_CONTEXT("gen_bp_tx_invalid_too_many_proofs");
const size_t mixin = 10;
const uint64_t amounts_paid[] = {10000, (uint64_t)-1};
- const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 0 } };
+ const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 3 } };
return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_CLSAG, NULL, [&](cryptonote::transaction &tx, size_t idx){
CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproof || tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG);
CHECK_TEST_CONDITION(!tx.rct_signatures.p.bulletproofs.empty());
@@ -352,7 +352,7 @@ bool gen_bp_tx_invalid_wrong_amount::generate(std::vector<test_event_entry>& eve
DEFINE_TESTS_ERROR_CONTEXT("gen_bp_tx_invalid_wrong_amount");
const size_t mixin = 10;
const uint64_t amounts_paid[] = {10000, (uint64_t)-1};
- const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 0 } };
+ const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 3 } };
return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_CLSAG, NULL, [&](cryptonote::transaction &tx, size_t idx){
CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproof || tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG);
CHECK_TEST_CONDITION(!tx.rct_signatures.p.bulletproofs.empty());
@@ -366,7 +366,7 @@ bool gen_bp_tx_invalid_borromean_type::generate(std::vector<test_event_entry>& e
DEFINE_TESTS_ERROR_CONTEXT("gen_bp_tx_invalid_borromean_type");
const size_t mixin = 10;
const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1};
- const rct::RCTConfig rct_config[] = { { rct::RangeProofBorromean, 0 } };
+ const rct::RCTConfig rct_config[] = { { rct::RangeProofBorromean, 3 } };
return generate_with(events, mixin, 1, amounts_paid, false, rct_config, 11, NULL, [&](cryptonote::transaction &tx, size_t tx_idx){
return true;
});
@@ -382,3 +382,14 @@ bool gen_bp_tx_invalid_bulletproof2_type::generate(std::vector<test_event_entry>
return true;
});
}
+
+bool gen_bp_tx_invalid_clsag_type::generate(std::vector<test_event_entry>& events) const
+{
+ DEFINE_TESTS_ERROR_CONTEXT("gen_bp_tx_invalid_clsag_type");
+ const size_t mixin = 10;
+ const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1};
+ const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 3 } };
+ return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_BULLETPROOF_PLUS + 1, NULL, [&](cryptonote::transaction &tx, size_t tx_idx){
+ return true;
+ });
+}
diff --git a/tests/core_tests/bulletproofs.h b/tests/core_tests/bulletproofs.h
index b30d82e68..115ad49b5 100644
--- a/tests/core_tests/bulletproofs.h
+++ b/tests/core_tests/bulletproofs.h
@@ -211,3 +211,9 @@ struct gen_bp_tx_invalid_bulletproof2_type : public gen_bp_tx_validation_base
bool generate(std::vector<test_event_entry>& events) const;
};
template<> struct get_test_options<gen_bp_tx_invalid_bulletproof2_type>: public get_bp_versioned_test_options<HF_VERSION_CLSAG + 1> {};
+
+struct gen_bp_tx_invalid_clsag_type : public gen_bp_tx_validation_base
+{
+ bool generate(std::vector<test_event_entry>& events) const;
+};
+template<> struct get_test_options<gen_bp_tx_invalid_clsag_type>: public get_bp_versioned_test_options<HF_VERSION_BULLETPROOF_PLUS + 1> {};
diff --git a/tests/core_tests/chaingen.h b/tests/core_tests/chaingen.h
index 3f9f01a11..a1101a3b1 100644
--- a/tests/core_tests/chaingen.h
+++ b/tests/core_tests/chaingen.h
@@ -836,7 +836,7 @@ inline bool do_replay_file(const std::string& filename)
{ \
for (size_t msidx = 0; msidx < total; ++msidx) \
account[msidx].generate(); \
- make_multisig_accounts(account, threshold); \
+ CHECK_AND_ASSERT_MES(make_multisig_accounts(account, threshold), false, "Failed to make multisig accounts."); \
} while(0)
#define MAKE_ACCOUNT(VEC_EVENTS, account) \
diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp
index c55154917..c573eb995 100644
--- a/tests/core_tests/chaingen_main.cpp
+++ b/tests/core_tests/chaingen_main.cpp
@@ -265,6 +265,24 @@ int main(int argc, char* argv[])
GENERATE_AND_PLAY(gen_bp_tx_invalid_wrong_amount);
GENERATE_AND_PLAY(gen_bp_tx_invalid_borromean_type);
GENERATE_AND_PLAY(gen_bp_tx_invalid_bulletproof2_type);
+ GENERATE_AND_PLAY(gen_bp_tx_invalid_clsag_type);
+
+ GENERATE_AND_PLAY(gen_bpp_tx_invalid_before_fork);
+ GENERATE_AND_PLAY(gen_bpp_tx_valid_at_fork);
+ GENERATE_AND_PLAY(gen_bpp_tx_invalid_1_1);
+ GENERATE_AND_PLAY(gen_bpp_tx_valid_2);
+ GENERATE_AND_PLAY(gen_bpp_tx_valid_3);
+ GENERATE_AND_PLAY(gen_bpp_tx_valid_16);
+ GENERATE_AND_PLAY(gen_bpp_tx_invalid_4_2_1);
+ GENERATE_AND_PLAY(gen_bpp_tx_invalid_16_16);
+ GENERATE_AND_PLAY(gen_bpp_txs_valid_2_and_2);
+ GENERATE_AND_PLAY(gen_bpp_txs_invalid_2_and_8_2_and_16_16_1);
+ GENERATE_AND_PLAY(gen_bpp_txs_valid_2_and_3_and_2_and_4);
+ GENERATE_AND_PLAY(gen_bpp_tx_invalid_not_enough_proofs);
+ GENERATE_AND_PLAY(gen_bpp_tx_invalid_empty_proofs);
+ GENERATE_AND_PLAY(gen_bpp_tx_invalid_too_many_proofs);
+ GENERATE_AND_PLAY(gen_bpp_tx_invalid_wrong_amount);
+ GENERATE_AND_PLAY(gen_bpp_tx_invalid_clsag_type);
GENERATE_AND_PLAY(gen_rct2_tx_clsag_malleability);
diff --git a/tests/core_tests/chaingen_tests_list.h b/tests/core_tests/chaingen_tests_list.h
index db78c3e41..1a5489b9e 100644
--- a/tests/core_tests/chaingen_tests_list.h
+++ b/tests/core_tests/chaingen_tests_list.h
@@ -43,6 +43,7 @@
#include "rct.h"
#include "multisig.h"
#include "bulletproofs.h"
+#include "bulletproof_plus.h"
#include "rct2.h"
/************************************************************************/
/* */
diff --git a/tests/core_tests/multisig.cpp b/tests/core_tests/multisig.cpp
index f098e1bce..49ae86eb0 100644
--- a/tests/core_tests/multisig.cpp
+++ b/tests/core_tests/multisig.cpp
@@ -28,98 +28,88 @@
//
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
-#include "ringct/rctSigs.h"
-#include "cryptonote_basic/cryptonote_basic.h"
-#include "multisig/multisig.h"
-#include "common/apply_permutation.h"
#include "chaingen.h"
#include "multisig.h"
+
+#include "common/apply_permutation.h"
+#include "crypto/crypto.h"
+#include "cryptonote_basic/cryptonote_basic.h"
#include "device/device.hpp"
+#include "multisig/multisig.h"
+#include "multisig/multisig_account.h"
+#include "multisig/multisig_kex_msg.h"
+#include "ringct/rctOps.h"
+#include "ringct/rctSigs.h"
+
using namespace epee;
using namespace crypto;
using namespace cryptonote;
+using namespace multisig;
//#define NO_MULTISIG
-void make_multisig_accounts(std::vector<cryptonote::account_base>& account, uint32_t threshold)
+static bool make_multisig_accounts(std::vector<cryptonote::account_base> &accounts, const uint32_t threshold)
{
- std::vector<crypto::secret_key> all_view_keys;
- std::vector<std::vector<crypto::public_key>> derivations(account.size());
- //storage for all set of multisig derivations and spend public key (in first round)
- std::unordered_set<crypto::public_key> exchanging_keys;
+ CHECK_AND_ASSERT_MES(accounts.size() > 0, false, "Invalid multisig scheme");
- for (size_t msidx = 0; msidx < account.size(); ++msidx)
+ std::vector<multisig_account> multisig_accounts;
+ std::vector<crypto::public_key> signers;
+ std::vector<multisig_kex_msg> round_msgs;
+ multisig_accounts.reserve(accounts.size());
+ signers.reserve(accounts.size());
+ round_msgs.reserve(accounts.size());
+
+ // create multisig accounts
+ for (std::size_t account_index{0}; account_index < accounts.size(); ++account_index)
{
- crypto::secret_key vkh = cryptonote::get_multisig_blinded_secret_key(account[msidx].get_keys().m_view_secret_key);
- all_view_keys.push_back(vkh);
+ // create account and collect signer
+ multisig_accounts.emplace_back(
+ multisig_account{
+ get_multisig_blinded_secret_key(accounts[account_index].get_keys().m_spend_secret_key),
+ get_multisig_blinded_secret_key(accounts[account_index].get_keys().m_view_secret_key)
+ }
+ );
- crypto::secret_key skh = cryptonote::get_multisig_blinded_secret_key(account[msidx].get_keys().m_spend_secret_key);
- crypto::public_key pskh;
- crypto::secret_key_to_public_key(skh, pskh);
+ signers.emplace_back(multisig_accounts.back().get_base_pubkey());
- derivations[msidx].push_back(pskh);
- exchanging_keys.insert(pskh);
+ // collect account's first kex msg
+ round_msgs.emplace_back(multisig_accounts.back().get_next_kex_round_msg());
}
- uint32_t roundsTotal = 1;
- if (threshold < account.size())
- roundsTotal = account.size() - threshold;
-
- //secret multisig keys of every account
- std::vector<std::vector<crypto::secret_key>> multisig_keys(account.size());
- std::vector<crypto::secret_key> spend_skey(account.size());
- std::vector<crypto::public_key> spend_pkey(account.size());
- for (uint32_t round = 0; round < roundsTotal; ++round)
+ // initialize accounts and collect kex messages for the next round
+ std::vector<multisig_kex_msg> temp_round_msgs(multisig_accounts.size());
+ for (std::size_t account_index{0}; account_index < accounts.size(); ++account_index)
{
- std::unordered_set<crypto::public_key> roundKeys;
- for (size_t msidx = 0; msidx < account.size(); ++msidx)
- {
- // subtracting one's keys from set of all unique keys is the same as key exchange
- auto myKeys = exchanging_keys;
- for (const auto& d: derivations[msidx])
- myKeys.erase(d);
-
- if (threshold == account.size())
- {
- cryptonote::generate_multisig_N_N(account[msidx].get_keys(), std::vector<crypto::public_key>(myKeys.begin(), myKeys.end()), multisig_keys[msidx], (rct::key&)spend_skey[msidx], (rct::key&)spend_pkey[msidx]);
- }
- else
- {
- derivations[msidx] = cryptonote::generate_multisig_derivations(account[msidx].get_keys(), std::vector<crypto::public_key>(myKeys.begin(), myKeys.end()));
- roundKeys.insert(derivations[msidx].begin(), derivations[msidx].end());
- }
- }
+ multisig_accounts[account_index].initialize_kex(threshold, signers, round_msgs);
- exchanging_keys = roundKeys;
- roundKeys.clear();
+ if (!multisig_accounts[account_index].multisig_is_ready())
+ temp_round_msgs[account_index] = multisig_accounts[account_index].get_next_kex_round_msg();
}
- std::unordered_set<crypto::public_key> all_multisig_keys;
- for (size_t msidx = 0; msidx < account.size(); ++msidx)
+ // perform key exchange rounds
+ while (!multisig_accounts[0].multisig_is_ready())
{
- std::unordered_set<crypto::secret_key> view_keys(all_view_keys.begin(), all_view_keys.end());
- view_keys.erase(all_view_keys[msidx]);
+ round_msgs = temp_round_msgs;
- crypto::secret_key view_skey = cryptonote::generate_multisig_view_secret_key(account[msidx].get_keys().m_view_secret_key, std::vector<secret_key>(view_keys.begin(), view_keys.end()));
- if (threshold < account.size())
+ for (std::size_t account_index{0}; account_index < multisig_accounts.size(); ++account_index)
{
- multisig_keys[msidx] = cryptonote::calculate_multisig_keys(derivations[msidx]);
- spend_skey[msidx] = cryptonote::calculate_multisig_signer_key(multisig_keys[msidx]);
- }
- account[msidx].make_multisig(view_skey, spend_skey[msidx], spend_pkey[msidx], multisig_keys[msidx]);
- for (const auto &k: multisig_keys[msidx]) {
- all_multisig_keys.insert(rct::rct2pk(rct::scalarmultBase(rct::sk2rct(k))));
+ multisig_accounts[account_index].kex_update(round_msgs);
+
+ if (!multisig_accounts[account_index].multisig_is_ready())
+ temp_round_msgs[account_index] = multisig_accounts[account_index].get_next_kex_round_msg();
}
}
- if (threshold < account.size())
+ // update accounts post key exchange
+ for (std::size_t account_index{0}; account_index < accounts.size(); ++account_index)
{
- std::vector<crypto::public_key> public_keys(std::vector<crypto::public_key>(all_multisig_keys.begin(), all_multisig_keys.end()));
- crypto::public_key spend_pkey = cryptonote::generate_multisig_M_N_spend_public_key(public_keys);
-
- for (size_t msidx = 0; msidx < account.size(); ++msidx)
- account[msidx].finalize_multisig(spend_pkey);
+ accounts[account_index].make_multisig(multisig_accounts[account_index].get_common_privkey(),
+ multisig_accounts[account_index].get_base_privkey(),
+ multisig_accounts[account_index].get_multisig_pubkey(),
+ multisig_accounts[account_index].get_multisig_privkeys());
}
+
+ return true;
}
//----------------------------------------------------------------------------------------------------------------------
@@ -175,7 +165,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry
account_base &account = n < inputs ? miner_account[creator] : miner_accounts[n];
CHECK_AND_ASSERT_MES(generator.construct_block_manually(blocks[n], *prev_block, account,
test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_hf_version | test_generator::bf_max_outs,
- 10, 10, prev_block->timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long
+ HF_VERSION_BULLETPROOF_PLUS, HF_VERSION_BULLETPROOF_PLUS, prev_block->timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long
crypto::hash(), 0, transaction(), std::vector<crypto::hash>(), 0, 1, 4),
false, "Failed to generate block");
events.push_back(blocks[n]);
@@ -191,7 +181,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry
cryptonote::block blk;
CHECK_AND_ASSERT_MES(generator.construct_block_manually(blk, blk_last, miner_accounts[0],
test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_hf_version | test_generator::bf_max_outs,
- 10, 10, blk_last.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long
+ HF_VERSION_BULLETPROOF_PLUS, HF_VERSION_BULLETPROOF_PLUS, blk_last.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long
crypto::hash(), 0, transaction(), std::vector<crypto::hash>(), 0, 1, 4),
false, "Failed to generate block");
events.push_back(blk);
@@ -238,13 +228,13 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry
for (size_t n = 0; n < nlr; ++n)
{
account_k[msidx][tdidx].push_back(rct::rct2sk(rct::skGen()));
- cryptonote::generate_multisig_LR(output_pub_key[tdidx], account_k[msidx][tdidx][n], account_L[msidx][tdidx][n], account_R[msidx][tdidx][n]);
+ multisig::generate_multisig_LR(output_pub_key[tdidx], account_k[msidx][tdidx][n], account_L[msidx][tdidx][n], account_R[msidx][tdidx][n]);
}
size_t numki = miner_account[msidx].get_multisig_keys().size();
account_ki[msidx][tdidx].resize(numki);
for (size_t kiidx = 0; kiidx < numki; ++kiidx)
{
- r = cryptonote::generate_multisig_key_image(miner_account[msidx].get_keys(), kiidx, output_pub_key[tdidx], account_ki[msidx][tdidx][kiidx]);
+ r = multisig::generate_multisig_key_image(miner_account[msidx].get_keys(), kiidx, output_pub_key[tdidx], account_ki[msidx][tdidx][kiidx]);
CHECK_AND_ASSERT_MES(r, false, "Failed to generate multisig export key image");
}
MDEBUG("Party " << msidx << ":");
@@ -303,7 +293,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry
for (size_t msidx = 0; msidx < total; ++msidx)
for (size_t n = 0; n < account_ki[msidx][tdidx].size(); ++n)
pkis.push_back(account_ki[msidx][tdidx][n]);
- r = cryptonote::generate_multisig_composite_key_image(miner_account[0].get_keys(), subaddresses, output_pub_key[tdidx], tx_pub_key[tdidx], additional_tx_keys, 0, pkis, (crypto::key_image&)kLRki.ki);
+ r = multisig::generate_multisig_composite_key_image(miner_account[0].get_keys(), subaddresses, output_pub_key[tdidx], tx_pub_key[tdidx], additional_tx_keys, 0, pkis, (crypto::key_image&)kLRki.ki);
CHECK_AND_ASSERT_MES(r, false, "Failed to generate composite key image");
MDEBUG("composite ki: " << kLRki.ki);
MDEBUG("L: " << kLRki.L);
@@ -311,7 +301,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry
for (size_t n = 1; n < total; ++n)
{
rct::key ki;
- r = cryptonote::generate_multisig_composite_key_image(miner_account[n].get_keys(), subaddresses, output_pub_key[tdidx], tx_pub_key[tdidx], additional_tx_keys, 0, pkis, (crypto::key_image&)ki);
+ r = multisig::generate_multisig_composite_key_image(miner_account[n].get_keys(), subaddresses, output_pub_key[tdidx], tx_pub_key[tdidx], additional_tx_keys, 0, pkis, (crypto::key_image&)ki);
CHECK_AND_ASSERT_MES(r, false, "Failed to generate composite key image");
CHECK_AND_ASSERT_MES(kLRki.ki == ki, false, "Composite key images do not match");
}
@@ -349,6 +339,11 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry
td.amount = amount_paid;
std::vector<tx_destination_entry> destinations;
destinations.push_back(td);
+ cryptonote::account_base dummy;
+ dummy.generate();
+ td.addr = dummy.get_keys().m_account_address;
+ td.amount = 0;
+ destinations.push_back(td);
if (pre_tx)
pre_tx(sources, destinations);
@@ -363,7 +358,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry
#endif
std::vector<crypto::secret_key> additional_tx_secret_keys;
auto sources_copy = sources;
- r = construct_tx_and_get_tx_key(miner_account[creator].get_keys(), subaddresses, sources, destinations, boost::none, std::vector<uint8_t>(), tx, 0, tx_key, additional_tx_secret_keys, true, { rct::RangeProofPaddedBulletproof, 2 }, msoutp);
+ r = construct_tx_and_get_tx_key(miner_account[creator].get_keys(), subaddresses, sources, destinations, boost::none, std::vector<uint8_t>(), tx, 0, tx_key, additional_tx_secret_keys, true, { rct::RangeProofPaddedBulletproof, 0 }, msoutp);
CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction");
#ifndef NO_MULTISIG
@@ -453,8 +448,10 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry
crypto::secret_key scalar1;
crypto::derivation_to_scalar(derivation, n, scalar1);
rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n];
- rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG);
+ rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG || tx.rct_signatures.type == rct::RCTTypeBulletproofPlus);
rct::key C = tx.rct_signatures.outPk[n].mask;
+ if (rct::is_rct_bulletproof_plus(tx.rct_signatures.type))
+ C = rct::scalarmult8(C);
rct::addKeys2(Ctmp, ecdh_info.mask, ecdh_info.amount, rct::H);
CHECK_AND_ASSERT_MES(rct::equalKeys(C, Ctmp), false, "Failed to decode amount");
amount += rct::h2d(ecdh_info.amount);
diff --git a/tests/core_tests/multisig.h b/tests/core_tests/multisig.h
index 333c4fe38..c7376bf63 100644
--- a/tests/core_tests/multisig.h
+++ b/tests/core_tests/multisig.h
@@ -82,7 +82,7 @@ private:
template<>
struct get_test_options<gen_multisig_tx_validation_base> {
- const std::pair<uint8_t, uint64_t> hard_forks[3] = {std::make_pair(1, 0), std::make_pair(10, 1), std::make_pair(0, 0)};
+ const std::pair<uint8_t, uint64_t> hard_forks[3] = {std::make_pair(1, 0), std::make_pair(HF_VERSION_BULLETPROOF_PLUS, 1), std::make_pair(0, 0)};
const cryptonote::test_options test_options = {
hard_forks, 0
};
diff --git a/tests/core_tests/rct.cpp b/tests/core_tests/rct.cpp
index 6ce99e76e..035128177 100644
--- a/tests/core_tests/rct.cpp
+++ b/tests/core_tests/rct.cpp
@@ -133,7 +133,7 @@ bool gen_rct_tx_validation_base::generate_with_full(std::vector<test_event_entry
crypto::secret_key amount_key;
crypto::derivation_to_scalar(derivation, o, amount_key);
const uint8_t type = rct_txes[n].rct_signatures.type;
- if (type == rct::RCTTypeSimple || type == rct::RCTTypeBulletproof || type == rct::RCTTypeBulletproof2 || type == rct::RCTTypeCLSAG)
+ if (rct::is_rct_simple(type))
rct::decodeRctSimple(rct_txes[n].rct_signatures, rct::sk2rct(amount_key), o, rct_tx_masks[o+n*4], hw::get_device("default"));
else
rct::decodeRct(rct_txes[n].rct_signatures, rct::sk2rct(amount_key), o, rct_tx_masks[o+n*4], hw::get_device("default"));
diff --git a/tests/core_tests/rct2.cpp b/tests/core_tests/rct2.cpp
index 8d7c4b3eb..4772be38c 100644
--- a/tests/core_tests/rct2.cpp
+++ b/tests/core_tests/rct2.cpp
@@ -158,7 +158,7 @@ bool gen_rct2_tx_validation_base::generate_with(std::vector<test_event_entry>& e
crypto::derivation_to_scalar(derivation, o, amount_key);
rct::key rct_tx_mask;
const uint8_t type = rct_txes.back().rct_signatures.type;
- if (type == rct::RCTTypeSimple || type == rct::RCTTypeBulletproof || type == rct::RCTTypeBulletproof2 || type == rct::RCTTypeCLSAG)
+ if (rct::is_rct_simple(type))
rct::decodeRctSimple(rct_txes.back().rct_signatures, rct::sk2rct(amount_key), o, rct_tx_mask, hw::get_device("default"));
else
rct::decodeRct(rct_txes.back().rct_signatures, rct::sk2rct(amount_key), o, rct_tx_mask, hw::get_device("default"));
diff --git a/tests/crypto/crypto-tests.h b/tests/crypto/crypto-tests.h
index e833c0444..29a50e0fb 100644
--- a/tests/crypto/crypto-tests.h
+++ b/tests/crypto/crypto-tests.h
@@ -46,4 +46,6 @@ void random_scalar(crypto::ec_scalar &res);
void hash_to_scalar(const void *data, std::size_t length, crypto::ec_scalar &res);
void hash_to_point(const crypto::hash &h, crypto::ec_point &res);
void hash_to_ec(const crypto::public_key &key, crypto::ec_point &res);
+bool check_ge_p3_identity_failure(const crypto::public_key &point);
+bool check_ge_p3_identity_success(const crypto::public_key &point);
#endif
diff --git a/tests/crypto/crypto.cpp b/tests/crypto/crypto.cpp
index 145ec1d86..e1be38054 100644
--- a/tests/crypto/crypto.cpp
+++ b/tests/crypto/crypto.cpp
@@ -32,6 +32,36 @@
#include "crypto-tests.h"
+static void get_ge_p3_for_identity_test(const crypto::public_key &point, crypto::ge_p3 &result_out_p3)
+{
+ // compute (K + K) - K - K to get a specific ge_p3 point representation of identity
+ crypto::ge_cached temp_cache;
+ crypto::ge_p1p1 temp_p1p1;
+
+ crypto::ge_frombytes_vartime(&result_out_p3, &point); // K
+ crypto::ge_p3_to_cached(&temp_cache, &result_out_p3);
+ crypto::ge_add(&temp_p1p1, &result_out_p3, &temp_cache); // K + K
+ crypto::ge_p1p1_to_p3(&result_out_p3, &temp_p1p1);
+ crypto::ge_sub(&temp_p1p1, &result_out_p3, &temp_cache); // (K + K) - K
+ crypto::ge_p1p1_to_p3(&result_out_p3, &temp_p1p1);
+ crypto::ge_sub(&temp_p1p1, &result_out_p3, &temp_cache); // ((K + K) - K) - K
+ crypto::ge_p1p1_to_p3(&result_out_p3, &temp_p1p1);
+}
+
+static int ge_p3_is_point_at_infinity_vartime_bad(const crypto::ge_p3 *p) {
+ // X = 0 and Y == Z
+ // bad: components of 'p' are not reduced mod q
+ int n;
+ for (n = 0; n < 10; ++n)
+ {
+ if (p->X[n] | p->T[n])
+ return 0;
+ if (p->Y[n] != p->Z[n])
+ return 0;
+ }
+ return 1;
+}
+
bool check_scalar(const crypto::ec_scalar &scalar) {
return crypto::sc_check(crypto::operator &(scalar)) == 0;
}
@@ -55,3 +85,19 @@ void hash_to_ec(const crypto::public_key &key, crypto::ec_point &res) {
crypto::hash_to_ec(key, tmp);
crypto::ge_p3_tobytes(crypto::operator &(res), &tmp);
}
+
+bool check_ge_p3_identity_failure(const crypto::public_key &point)
+{
+ crypto::ge_p3 ident_p3;
+ get_ge_p3_for_identity_test(point, ident_p3);
+
+ return ge_p3_is_point_at_infinity_vartime_bad(&ident_p3) == 1;
+}
+
+bool check_ge_p3_identity_success(const crypto::public_key &point)
+{
+ crypto::ge_p3 ident_p3;
+ get_ge_p3_for_identity_test(point, ident_p3);
+
+ return crypto::ge_p3_is_point_at_infinity_vartime(&ident_p3) == 1;
+}
diff --git a/tests/crypto/main.cpp b/tests/crypto/main.cpp
index f804c45dc..5486937c2 100644
--- a/tests/crypto/main.cpp
+++ b/tests/crypto/main.cpp
@@ -259,6 +259,16 @@ int main(int argc, char *argv[]) {
if (expected != actual) {
goto error;
}
+ } else if (cmd == "check_ge_p3_identity") {
+ cerr << "Testing: " << cmd << endl;
+ public_key point;
+ bool expected_bad, expected_good, result_badfunc, result_goodfunc;
+ get(input, point, expected_bad, expected_good);
+ result_badfunc = check_ge_p3_identity_failure(point);
+ result_goodfunc = check_ge_p3_identity_success(point);
+ if (expected_bad != result_badfunc || expected_good != result_goodfunc) {
+ goto error;
+ }
} else {
throw ios_base::failure("Unknown function: " + cmd);
}
diff --git a/tests/crypto/tests.txt b/tests/crypto/tests.txt
index 8e6534a87..3a7fe5453 100644
--- a/tests/crypto/tests.txt
+++ b/tests/crypto/tests.txt
@@ -5467,3 +5467,9 @@ check_ring_signature 8427e7179050bc38d5c25e68f70c2c98990042388e326d6bebd5674ca8d
check_ring_signature f3d2b5b25d663325acca133163bbf3219f1b22fea6bd6d6e3194db8bc30dc6fa 448071ee63780f0fcfe35245353e4fd28f5c5362d9a5f3d74e5bd6685986729f 6 fb706555f8358ac3db60d9a52eb4981f91d28cc4d518c1a5c988ce94c7051379 e07dbe16cee565a221af2353c4761cdb7c7fab5880372b0e46d49ab4842d3b4c 05db3f4b53f17fe0525e2b5002664d9b0d5680c10146640cbbf23a118d6d88fb a446a6e907f653e0db5888927971d0dfd5c854d2b04f02367e18b02378271b11 72ce0ff9a80faaf2ea826cfe8244cc2d345a7c887143e481ffe826b8630b6f93 d17c0b9ba4aeb04ddb8288222a0d5d22abe327981786f790cf8b9ee8831090a7 8a11ee60dd8470e1bd447f472b60723d8a30ac8a84fb4bd98580b2f3ddfc32027193f368c87e02deb9219eb50e0a9b2d9d43bab27c14ac4be23640a1fca01a0dd12337332fdfe9f3aef0235281614f3e94cf7902486b8a5b76444e9a56e21a09beab12ff902fdc05b16a2ad417d92711107bcf7a554cf82fa069e9432874680dce00e8a862e417b2efaa66cb9e4693e00b7d6cd1c452fc71630473799fdc7603080d006b562aafe0e75e456e6e09f1a655dcc40e296f0f3083c5f58c75e9dd03c61115f05d75c65e04f2d02c6232c72c99a32e9f8c851b23219e2dbcdbd6190de6c9ae374530c0f268758611fffac7dbb8f16d8c71a0da950ae12603d942b808146f9131049a75364f45cc606ab4d6882a137999c163312c87fb3d8e78ef3406b97dbd90bbdd20a8025d4ba438491ff0923da5055b7c3ee446b7ddac341f350056efeaa3f706fa2ac8a6d02d5f2a4cd5644af7f9a48a699801469d33601667081fa0971b1b6dc3c86d539197a531a76ebe611fc967d26067f2c2f0d879ef0309 false
check_ring_signature 07a23b78f73ca487ec5ab0f4d7725d7ffb547543ae4f96e30df871c2241489ca 2073d5a5ccb03402ded68c31d3de658f7c5be2265bec656a12212c83e2499a75 9 ce3fe390c5309c0aa6c0a1e4dc29ef63fdf55ad2fef737b775bae9857c666966 08088bfe1f076131d82458ef08e0a6d8003d26d824360033895e62409fceadba 4faffacfacc069e09f80a0249daf97a40b53a64ab870c62dfd08998d382dba52 386e435138fba8c063966d8308927f8c8788782a3a263500133325c9c82d38a3 efd955c96135d72fee34c765998cf714f37365af8f77cae145ba5d126f1fe914 60dbea373e81c0276c4fbb83ca1fcbb647e2fa11a8bbc62c8e56d4d147b39bd5 1495004110d8e2fe1774ba6eb9492b1bbc54f674ed2082401cd6eab71ad9dc40 7a2af9f6dad76479c5f3345ffd250f55b234682b4fd51d3ee9ae8da2e5a35cb2 02548abda0688fc63cc485b956fd4303bf0dfc43a581e01c59c62243461ea348 e784e4e19099667803c6f08fd0877e85937bec50ab02f75b6f2e3dd61876b40510bf63faa2346e3b35b0ade96bfef145dd17b92ebdb1cf96899e10e3442aaf0d782eafc8be306390ffcda93025f6832a2d4c4e4d1c2b56add53550b3b512b71af959bc1b5902a7b628eae1d16d242c099fb4ce33a4bee12af49a41aab958940910b0d0ef596b753688f228a7184e38e7df8644cbecfa658d721736ed2882e20f9a2fb61818c0060b3fd4b2ced7984bb17b10381efde2330bbb1de208655e080cd33d9e5d5d853a8875623195ed30e2c2e475ebe4ac97e3c5216520b0a201a608b6c6013266ab0e63a461dae171af4c3a14a5fa8c4c33ca25b84540e32efcf302b18e610545ff35d45dae4116ba6d51ac9125cdb7f681739744237827e768bb0487b6b0a1d9e8d5ce809b9e17a6c32df9ef77a26af987b2348c748cba73e917034e21609b0d92a5b7106d39c8de2f057013bd347be67e553e608cbb70683f2e04ca7ed84ed7f4c671efac5deb3db17498f23170cc8deb4596d1ea958caa7e4e06204833104aed36ee0b7808dba1194cc374c193e4e926832ac171d03f3abe6607803d6b33a1350ec428afc88977eb411505bcb54113da91f63fdd2bb85a08140cbc040fadb23ba1c92fd3bad8c4930f36d37403f8e9d4b87bf3636ccffc53c30cc4eeb9945214f9d8e288edbfa1d7546baae6860f9f56af7d79f9349c104579064d404a3d7a893d64fbcca32c5d4e6158d92cd87c8a78bb3a61eb55ec92a7ee0aaabe59d33af5534ae258f48bca9d5459e0708ebf9289d9bb22acb540b0f39003 false
check_ring_signature 0e6194f7b3ec1594e6b727b990c6bf65a2b1eb1a9b73ea00d18a709ffdee1276 f931b871addf92f407f087ae176804734ba35fd65086f6c3e07d9d7b001be265 51 8daf0c2e434171ef0e31f1fd17307a30690639fc7fef1a85a9a7858868924c1d 93edf23dcd46477698f2e4795ab9e4e75ef04c8ca561670c22d0a379f7cc9d12 5fb23ddfd7bc6db6798d0dfadc6accc8c7fd75adb090ecb6bad2021bc3bac5da d093463f76e271597f6cddc74b5685b0c4d4ab6b6f6bae4b0217c7097ced9bb6 1504b6cab3b2454a066fe9462be135eda844480da85ad3baf67fbf39679cdbf0 e812fa7c7f0b6e11dd0d87fd88767a768f9e89f2c54396a9a53d443d0edafdcf 6570828a5e056a4e035b1042b80007a872c71545ea225feb982d24eea14373f0 a3adddfa513dca877eca62e12255c981b148a605c998458c6786aba6aaf1207a 43f3fb57f9a0c90200a940217893685296537f34041fd9586586937386f8d33d 9ff696ea156a4468e1d0d32590fcc865f491d821254594535c1694d7eb0102c2 3f0245304a5047ce42fb0b36544220d7df36c919aa2321bf39e83fa71c4de21c 01c527e626c1b5c928058d7ea772cda93833ba111792da212a5eae0041ccae51 61a79b26403f88acf0d4a7bf86efff4bd9f32e05e9a900e7d890ce36ab8abbef be302d5d4b83cd447536c08dfe66a32fab021889d9eb7a8621e59a6e3756b14a 693a9d20a1d12828a94d01a2bdc856f3499745e4c5830cda407962d2b6953784 62438ad0ca1dc718def66c97d8a4b2a1c4e61322c7cb8c3b904177cc8d8fbcaf 88dbaf2eab9896bac05f93c953c548de45c032d40b7c7e452c17ac73606b942b 894905861e3c952ab0e350b12900b454b3870ce3e9b590f94203ce2bbc41f944 cbf933f67076f66793bf06f2a5008bd01b36b6aa094483269da52bbba7465580 557046df81dbcfe18c65e1364448c8d94c710cd1fbf8a3c10550254d97afcdf7 7b24c0ee3a7836c1bef02956b81358d8f698da40f0ccd706daf3038cb0b28793 28ddf7553a328fe5ef8c8a6abd52ba6d725240f1511665884cb68c96d46d92d5 ffd267319c8ed9e424371ef74e5301102997c26265a2044bbbe05ee291f5bcc9 d467502be7c3753d9e4d2a147d3a40d1d817a865184030870bf97ef6f1610da7 b1ec5f6565cc19a6d015696594ada55b5fbe82108fcec0d54fb11b05bfd37408 19e7e65cdd957ab0d25dfc9e4e1449b1d4283d2561754fc147adb51e057dc7ba c5d4274eef9f5d92e7a8dda59de38f5728d60350d8e98871968765925d18ed09 a44814c8f2912d13c09242b0b75dad90a6357b844e021dc96668c45e311b64b3 cd88baa50640c7409a98921e0ad1e7f2496b39a3b07544eeef2455fc5018b17e 9ab6f34f081bccc8d8b29513851a402759c96b8d95d836db977e24e595deb2fa 56123960591257a70a334628d9868505176533debe06cdfc24f906ec8a997efd a37b03d5446abb9a3fb7aee94d6740d85f984fb363a9e78a9d363d5882cd2823 bd9607c5e6c167f5eb6a289be620592c7e850590393787d01acd958fd717b0c2 d0d69b420c93d9b1b8eee6796db2c7cc2110dc78b624396e144c135fe69c812d b6a2ee98807a00bcff0fa5403102c9a628948d3478234cdde85e5effbefcf204 e183a5d8daabdea01f7cd7f0960b75566aec78874cc933f5acc3dce0512ab335 ea7f86e49af2a4883e11b3362127dfdb14f0f517170ddb4338a5a38d2c92566a 4ea9d24ebb852962e745d4ec30679bed40f581d1ccf6825e80ddb38759025ac7 d0c887ff472e65d7fcb502988dfb0c919c8d23c2897d49b8c8de90d725604ea8 0177735a5271e254705979ef38a0737897f608aa144f4c4ed97e220dcb32f1af fc49aac2eaf95de87156c0348381d78d9b468a1da3eae6cc25bb398563b0bdad 84a98d5f20b608d31af41925052dfd378f0252e1bf7c13cf9b773e0cd4b95a52 8fd6df156a95abf7ca37fa1d65e820b56f2498c9ada0a4027f0dbc285b9a9b86 f7bcadf3751cdc85db850619a857288d1291c2f403732d79daec6eb774fd26cb c87e4373dadc7a65f53df413d22b3370f7a8c47a929ded626f3f71f3b249eb4d 02398630b92a7f782dd0153d39a56123ec7ea8d5a0fde77aebd302e641de2a54 f655dea86af87ed4d93ea10c2ffd5e52aff83a6fb5fab65a1ab67a02cfae6523 990747d3687fcc37ba0fe4845f9b247ebddd0d0a5a61f2637bb498f0b05e9fb3 38050abbc28c5847708f0142d8e89d44dad6d0ee8bd60b5771bd03d590a7ff85 867b6cdc08dbecaf1b6f9ee2add2d2c9dfe18474a805a028533b619f52abf6b3 0bda8ca264a6747f18195bbf56fafe96c9a0c8e6557f1b17c50eec71ae52fb50 cc925498a1aaccee819efd183df25795472fda1c7abbad6eaf42b00192c1e7005cacf5a965e831702169f97d374b7b30a7cb3343170c89c325090815e663a805f1d64336bfcdc25794467e68c093403faa50071b992863da69b6399f8bd33308abac8a69fe14a4a3de62ccb4bb5b49f760d1567fb1c966881476f7c5cdd20d0738b0452fa686ec759a6f19f50f1cee1ef06d69162d6d9c6d97055ce7b746a7038ad3913cf9c92a4a5faebecc5417ee900e8cb9e58c35d06e7b554a38815efa05a2f578665a2f96fff87bc9c025b0a23775a8e079f98805a350cac06456ed230f3bf10e3a133abd448f811ed82663f4bfd3fd6492d3ff6e49625b1efc8b9a090bbd70ee46aba76648c7e899cc19433a230f59314dc72d10d7c5dff9c9e2b79907161b00e307b10a9f53332a325c305744d2b96229831fefd891fa1f0776278005fc792d7d1882368def90bb0c75c927464eb4a95ad133e9cca7c57708c620520cb4a4a25880d80cbd1101d0a78e5cc7cf9aae6fe52449911be59a405f35356709977a9532074a05627c13787192cdad3b40f9434d469f189e311c0d917d788d011693c8319cc0b0c4c0ef4fdf08f945be0c6e65fd1c548bba7608a128c3a327020818f93aad968cd094149dbe064e3873b4d704bd0a5ee97fe040944de667a3063ecf7be8aedfd54bd73d13a56b944c546c4c6fb7cc5a9c74a6508c8af26410095f34c6787f38abd729bbb728a4213e9e079b98b81d5f6562f29ebf21ac85010fd4d83ffb1851ed601ae7bc93ae5b36da273101f7485a2d38ed29f68636dd5700b2fcdc9709461a027e02850560fc3e3f1c6fd8dbf4cf1084e059bdddcb8d450f8f3bc1834060ec6375cb94cab89cfce4aabcfc3ef1bf13018535607234e63800d9f6d20030fab8384f287271183563e0730708203261ebbcc0531f4637e0540c7354ffb862e9c435afcf4a11313a2c60f8fccc6483cb70b3dd7b1e7abdbc8e0779dae0268351518fe07b03fe764675ba91f7e612c6d07231e6deab9a909bdd0f419adc092fe62fb5378be479f7cde0e80eb642aa89441feb6b0119595bd7770446d253edeaf3c0dee883f046f0f7e1d2a26701eff4d7b3e9c5f76e27bed51002ee4a57c4e51ee3d2bece02c959ddf90e422f739f1f3b628b5be04f8845f06b00fc891ff96e22af1869f22c1b79afa8ce7eaa4afa70158cde42310f312f92d8013e3aeca05cceb2ee5bcf0a50a5c267f4e548bf32e7cb935ee28c644511beaa071923a1d8b15b2e72b864cf5a4e3f4c290467c9a70dcf924345cd82c2d1f68c0e031f05602b53686d50a7a7bc429e1b54287fbedd16c6cc4aedfc9d66bdf31f0959689980fba888d99ffc6d40aa6f7914bbf593fe736e9edad1a4fc7811853e03f927c357cf3d5dbf58327d4b43ce3f8797063492961a04c30995bbddfe281b0b153043fe51178ff43933a0699ab3a2d995457e7cfba7149ca8d3da1e99d2280bf505e8bed2cdb4f037c2d5c1efd00cda5db5cede0d37a3ebdd94b0f12c457b05f546748f69c5212c58f028bc6a29e11ddb4647e58414ee25060e47506fdb7a0232a7ac6df4481bde4ca5f3ee88a97dd96396ad4727b076743e64e8ee1eaeff0b2532804e4cc7a28306c80b9498eeaaf5be9087caa51807bca9377f76ef6a500cadc72f6f95aea184d559be04f14bb0b05f0ce303fb68cb7091ac96007264ec05c442a68d867351823252d8951d7f6b9671b857244d477e75466c3017f8621808fd511a14c5dab254e5d685d08a922317dadc6fa90b6b9df5b2d23dd392ac910bc8fec0ac56c9ee5d491d9685709fb0d3683905fa390472ecaa01090bd4969c0df17f699e6c78942625672228e7474c37db2336b1136b4dea290940f4a35285013264411791a89ae770a63257478339572c85e7b838078a56db7c97a029a04e0ff040a4d5f08fa4e4a0933b7566d8dce9970eb471b270b70970b5ea19124e0d09e643c89b98f199fae32c088512e6839c33ebd646920e360294d869ad6bffa30de976920a838bc8b88025514d0312f6f28066aec9b4d2d9253ce94e85358b54078ce1b56aed1de3f9071419e2d3590df4d3072ea1d93f11d1c9bb011097f2380ab7341e0b1c7a2d4de64e82f4203a87c9805e1298d69f14b3911622f1119a900d49de89000147275fc18aa830228bafb687e8cc9eb8ca4c2ec4c90bb33fe25201417fd7408111e0efdb16d59412852deac263800de4fcb96b19a2ebbbe4d719051ae02b8b3c4292f097380c9587e96bdfd82037c54649154034d95057e883e00cfe4250f079e2a5460a06aeb4103e6c4b7b64a38a851e1650d758e9a5bc12d3093fc4e9f701612625c969d7dbfc1369349c255f883dc436abaef5c16e453d7a0571cf9797a89446077045d3eb3169261025698dbbff5f1354426e7375bf04ed07b930b24bf02049e577c98bd7abe8c5903f48af2eb03686166364ce54aaf9c20a9774b7b04b922ad510182f41a8b84e0b5e8e8cb404e775f9cf146dcae0fa5f0460da3dfb2ecabe850b5dda46d54c8375003ffaa9f7efd508ffc799523c6c1b0ec1406363554dce15ecd47d91936292b448022b5f162ec09937f50f86d1e50908762bc5330f51c639e035dd07b227e6f499884b51829d920ebb517c3732044500150323d68280d23c6711473abe8d5d835c5f071779af65647334d656caded90a6e66585fa0cc6806375853481eb0b409211473afdd46d6686c8ab95c27aee304a048fadac986152ba89037f084dee9368e5768434610f7904a3552d0d1bcdd06ee33c31485d28838266987b1584ee02a1ff0383a134d11114bbd11b5f8e0b10cd19a175be6520ec55c0380478f723bfc1f0ad1f14ee839547657979ee73e9907939245c535483d3fd79ccc65d776903ebd014303ee2c508d87f053666b42200e8731cb3e79f0a543673cbe1acd23740c442a00db5c6c23c06d008029184dea0970641273d935bc76bdef35f483052e57cfd00b1188c54948f7856857dcc58f05c0735df9a9896da2fc8b82b5c3ceaf98db68508a1004230e0f8f78595da2b702508216f0ba4d65d5dfa14f0cf2d54c67ab4bb54eb4e820c5af761978d0531b0a25a136f6bca068eeafb0828bb76bf4b8bae4cbfce93e8cf9a4c5ce2a1d00c20c2a713c981bd9e46e48dc3e90d2a09e40722212dc3d46c7ec98c830af1545dd014cddd1f0492fc19c597f24acdb0cb9836bf16507be54d9eb229d6b778c20370149e7ee1d371105f331f829e9b611340e1120ee6ce4bffe9c3a7f96f1adf2b802a62f4331c3052309a74465d7f3ac8059bb7b1e0093977c1ce177c04a4be8060447c3a9a49e13f4a2f8579668d929b6782c76b41ef127b21cf91c766de44c930005089f2fad932b12ec662c1bfd7d17c8384b98db2a64ff3b952034131cfa370b5ed0b3bc48d3fd66cf1200984c8929298bf7102932b658432ba2e93d0c01400a75775b1dbea79c9acb1b3348ba24a454f104b72c39ffff01a56d60fe2084af02078204e1c51773f8a91aac91564e8b5e069ce2e88319e47e65f01965743ace06f6540d22fc0e0e2038173960189818af53095c82b0726b6ddeeef14edb01950e7c381d852224666623294305816e713561e1146a37a1212b41df75079a26e601ae81025abd07cd3bfe475f3c44db05a2c715ed902bb43cdcf4e505925d3e18006518b18a46dd21adb117bfb342c68b96f974f1be18c47e7bd5f00a90c225500aa0d5c11207714e4bc729f3662664534064a6fdae4c03456e9e7a75821eefbc0b9cfa05ec403734d1ac35e3a03ca5bf085d0473195dd9490375d241c73ef5d50bf79ed1fe14e93d0710ebf915f8f52d0062b7986129ae8903f78a4b449b7ce7013f5b2aebbd5fcc76b71d0c36dbbb24cb6ed6cb50de9b06a01ba11de6025f2d0c4af71c28d9eb53d9ab885981e27554255cd0aed13438469d4713c0e37fb8ad0c751d50fd4db099dac66358fbada177b5f69f3a6518216f0eb7c92878f94da6d6c18a4c5606f55afb9e66610ef4a41953cff6f1ac7181b6f76ee54aa728466f0c73129d70c6affca90b8931deef27bb1185e7097a93b12b3d389388e1ad1adc07dfd96e6dc926eefdcf373c64566f690b970717afd1df3e4326d73c6d1cc5330c8fdee071684fe4e042b8aec497e2e4fc1098149d0707ce95b550b63fa74cd808ae6a0059002b016d814e0a6e716dde400e5b3716ae33fc8c92915488ce7fe50d94c19f927482e0d6b3cc2dcd9e5fb9d741aeb81c07a9dbad27b926257895af087791a6b9c4048f17832148be5c58f89e4877e22c5087ddbf0a7bf273ae45a50b71d9305c8813c886ea454d6c24174f143856abadc9319d6eabd8d48ec3c66a031394f78c9c9cdb2d0de8f44a932d30a6b913378999ebf0560c1c5f7c4098ce06f117b53db72a4436c885eb9c748deb2587ca0904743138de7e98bc3b0494e40a5a56d71f4204e8b8cad941bfd3abd384b976baa2f6b88c987e61f049f80c890df6470aad55283b8b891b0707aa22089351441fac575587b2ea4ac2f9f9c38b071fdbcc7f5efab14d86b22181e741ed824447a2facb46a63e91538af78c5fdf02 false
+check_ge_p3_identity 078c63ceebcfc9d8af51e232b497fb3cd5f491bb9bed5aa3c556a47e62cb186b false true
+check_ge_p3_identity 62218e5f0710af551bee941adb3981650862d2a3c4c18c794f450b5bb538975b false true
+check_ge_p3_identity 046e1450f147f3ade34d149973913cc75d4e7b9669eb1ed61da0f1d4a0bd7f13 false true
+check_ge_p3_identity ca8a2f621cfc7aa3efcd7ddf55dce5352e757b38aca0869b050c0a27824e5c5e true true
+check_ge_p3_identity 64a247eef6087d86e1e9fa048a3c181fdb1728431f29ba738634bdc38f02a859 true true
+check_ge_p3_identity cff0c7170a41395b0658ee42b76545c45360736b973ab2f31f6f227b9415df67 true true
diff --git a/tests/functional_tests/multisig.py b/tests/functional_tests/multisig.py
index 0a58f469a..bb7ccbe56 100755
--- a/tests/functional_tests/multisig.py
+++ b/tests/functional_tests/multisig.py
@@ -39,40 +39,40 @@ from framework.wallet import Wallet
class MultisigTest():
def run_test(self):
self.reset()
- self.mine('493DsrfJPqiN3Suv9RcRDoZEbQtKZX1sNcGPA3GhkKYEEmivk8kjQrTdRdVc4ZbmzWJuE157z9NNUKmF2VDfdYDR3CziGMk', 5)
- self.mine('42jSRGmmKN96V2j3B8X2DbiNThBXW1tSi1rW1uwkqbyURenq3eC3yosNm8HEMdHuWwKMFGzMUB3RCTvcTaW9kHpdRPP7p5y', 5)
- self.mine('47fF32AdrmXG84FcPY697uZdd42pMMGiH5UpiTRTt3YX2pZC7t7wkzEMStEicxbQGRfrYvAAYxH6Fe8rnD56EaNwUgxRd53', 5)
- self.mine('44SKxxLQw929wRF6BA9paQ1EWFshNnKhXM3qz6Mo3JGDE2YG3xyzVutMStEicxbQGRfrYvAAYxH6Fe8rnD56EaNwUiqhcwR', 5)
- self.mine('4ADHswEU3XBUee8pudBkZQd9beJainqNo1BQKkHJujAEPJyQrLj9U4dNm8HEMdHuWwKMFGzMUB3RCTvcTaW9kHpdRUDxgjW', 5)
+ self.mine('45J58b7PmKJFSiNPFFrTdtfMcFGnruP7V4CMuRpX7NsH4j3jGHKAjo3YJP2RePX6HMaSkbvTbrWUFhDNcNcHgtNmQ3gr7sG', 5)
+ self.mine('44G2TQNfsiURKkvxp7gbgaJY8WynZvANnhmyMAwv6WeEbAvyAWMfKXRhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5duN94i', 5)
+ self.mine('41mro238grj56GnrWkakAKTkBy2yDcXYsUZ2iXCM9pe5Ueajd2RRc6Fhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5ief4ZP', 5)
+ self.mine('44vZSprQKJQRFe6t1VHgU4ESvq2dv7TjBLVGE7QscKxMdFSiyyPCEV64NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6dakeff', 5)
+ self.mine('47puypSwsV1gvUDratmX4y58fSwikXVehEiBhVLxJA1gRCxHyrRgTDr4NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6aRPj5U', 5)
self.mine('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 60)
self.test_states()
- self.create_multisig_wallets(2, 2, '493DsrfJPqiN3Suv9RcRDoZEbQtKZX1sNcGPA3GhkKYEEmivk8kjQrTdRdVc4ZbmzWJuE157z9NNUKmF2VDfdYDR3CziGMk')
+ self.create_multisig_wallets(2, 2, '45J58b7PmKJFSiNPFFrTdtfMcFGnruP7V4CMuRpX7NsH4j3jGHKAjo3YJP2RePX6HMaSkbvTbrWUFhDNcNcHgtNmQ3gr7sG')
self.import_multisig_info([1, 0], 5)
txid = self.transfer([1, 0])
self.import_multisig_info([0, 1], 6)
self.check_transaction(txid)
- self.create_multisig_wallets(2, 3, '42jSRGmmKN96V2j3B8X2DbiNThBXW1tSi1rW1uwkqbyURenq3eC3yosNm8HEMdHuWwKMFGzMUB3RCTvcTaW9kHpdRPP7p5y')
+ self.create_multisig_wallets(2, 3, '44G2TQNfsiURKkvxp7gbgaJY8WynZvANnhmyMAwv6WeEbAvyAWMfKXRhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5duN94i')
self.import_multisig_info([0, 2], 5)
txid = self.transfer([0, 2])
self.import_multisig_info([0, 1, 2], 6)
self.check_transaction(txid)
- self.create_multisig_wallets(3, 3, '4ADHswEU3XBUee8pudBkZQd9beJainqNo1BQKkHJujAEPJyQrLj9U4dNm8HEMdHuWwKMFGzMUB3RCTvcTaW9kHpdRUDxgjW')
+ self.create_multisig_wallets(3, 3, '41mro238grj56GnrWkakAKTkBy2yDcXYsUZ2iXCM9pe5Ueajd2RRc6Fhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5ief4ZP')
self.import_multisig_info([2, 0, 1], 5)
txid = self.transfer([2, 1, 0])
self.import_multisig_info([0, 2, 1], 6)
self.check_transaction(txid)
- self.create_multisig_wallets(3, 4, '47fF32AdrmXG84FcPY697uZdd42pMMGiH5UpiTRTt3YX2pZC7t7wkzEMStEicxbQGRfrYvAAYxH6Fe8rnD56EaNwUgxRd53')
+ self.create_multisig_wallets(3, 4, '44vZSprQKJQRFe6t1VHgU4ESvq2dv7TjBLVGE7QscKxMdFSiyyPCEV64NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6dakeff')
self.import_multisig_info([0, 2, 3], 5)
txid = self.transfer([0, 2, 3])
self.import_multisig_info([0, 1, 2, 3], 6)
self.check_transaction(txid)
- self.create_multisig_wallets(2, 4, '44SKxxLQw929wRF6BA9paQ1EWFshNnKhXM3qz6Mo3JGDE2YG3xyzVutMStEicxbQGRfrYvAAYxH6Fe8rnD56EaNwUiqhcwR')
+ self.create_multisig_wallets(2, 4, '47puypSwsV1gvUDratmX4y58fSwikXVehEiBhVLxJA1gRCxHyrRgTDr4NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6aRPj5U')
self.import_multisig_info([1, 2], 5)
txid = self.transfer([1, 2])
self.import_multisig_info([0, 1, 2, 3], 6)
@@ -177,10 +177,6 @@ class MultisigTest():
for i in range(3):
ok = False
- try: res = wallet[i].finalize_multisig(info)
- except: ok = True
- assert ok
- ok = False
try: res = wallet[i].exchange_multisig_keys(info)
except: ok = True
assert ok
@@ -193,11 +189,6 @@ class MultisigTest():
assert res.ready
ok = False
- try: res = wallet[0].finalize_multisig(info)
- except: ok = True
- assert ok
-
- ok = False
try: res = wallet[0].prepare_multisig()
except: ok = True
assert ok
diff --git a/tests/functional_tests/transfer.py b/tests/functional_tests/transfer.py
index fca9ce91c..07a24546e 100755
--- a/tests/functional_tests/transfer.py
+++ b/tests/functional_tests/transfer.py
@@ -171,7 +171,7 @@ class TransferTest():
assert e.double_spend_seen == False
assert not 'confirmations' in e or e.confirmations == 0
- running_balances[0] -= 1000000000000 + fee
+ running_balances[0] -= fee
res = self.wallet[0].get_balance()
assert res.balance == running_balances[0]
@@ -183,8 +183,6 @@ class TransferTest():
running_balances[0] += res.block_header.reward
self.wallet[0].refresh()
- running_balances[0] += 1000000000000
-
res = self.wallet[0].get_transfers()
assert len(res['in']) == height # coinbases
assert len(res.out) == 1 # not mined yet
@@ -337,7 +335,7 @@ class TransferTest():
assert len(res.unsigned_txset) == 0
unsigned_txset = res.unsigned_txset
- running_balances[0] -= 1000000000000 + 1100000000000 + 1200000000000 + fee
+ running_balances[0] -= 1100000000000 + 1200000000000 + fee
res = self.wallet[0].get_balance()
assert res.balance == running_balances[0]
@@ -347,7 +345,6 @@ class TransferTest():
daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1)
res = daemon.getlastblockheader()
running_balances[0] += res.block_header.reward
- running_balances[0] += 1000000000000
running_balances[1] += 1100000000000
running_balances[2] += 1200000000000
self.wallet[0].refresh()
diff --git a/tests/performance_tests/CMakeLists.txt b/tests/performance_tests/CMakeLists.txt
index 542d204e0..e8810ff85 100644
--- a/tests/performance_tests/CMakeLists.txt
+++ b/tests/performance_tests/CMakeLists.txt
@@ -46,6 +46,7 @@ set(performance_tests_headers
subaddress_expand.h
range_proof.h
bulletproof.h
+ bulletproof_plus.h
crypto_ops.h
sc_reduce32.h
sc_check.h
diff --git a/tests/performance_tests/bulletproof_plus.h b/tests/performance_tests/bulletproof_plus.h
new file mode 100644
index 000000000..9aad61065
--- /dev/null
+++ b/tests/performance_tests/bulletproof_plus.h
@@ -0,0 +1,99 @@
+// 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 "ringct/rctSigs.h"
+#include "ringct/bulletproofs_plus.h"
+
+template<bool a_verify, size_t n_amounts>
+class test_bulletproof_plus
+{
+public:
+ static const size_t approx_loop_count = 100 / n_amounts;
+ static const size_t loop_count = (approx_loop_count >= 10 ? approx_loop_count : 10) / (a_verify ? 1 : 5);
+ static const bool verify = a_verify;
+
+ bool init()
+ {
+ proof = rct::bulletproof_plus_PROVE(std::vector<uint64_t>(n_amounts, 749327532984), rct::skvGen(n_amounts));
+ return true;
+ }
+
+ bool test()
+ {
+ bool ret = true;
+ if (verify)
+ ret = rct::bulletproof_plus_VERIFY(proof);
+ else
+ rct::bulletproof_plus_PROVE(std::vector<uint64_t>(n_amounts, 749327532984), rct::skvGen(n_amounts));
+ return ret;
+ }
+
+private:
+ rct::BulletproofPlus proof;
+};
+
+template<bool batch, size_t start, size_t repeat, size_t mul, size_t add, size_t N>
+class test_aggregated_bulletproof_plus
+{
+public:
+ static const size_t loop_count = 500 / (N * repeat);
+
+ bool init()
+ {
+ size_t o = start;
+ for (size_t n = 0; n < N; ++n)
+ {
+ for (size_t i = 0; i < repeat; ++i)
+ proofs.push_back(rct::bulletproof_plus_PROVE(std::vector<uint64_t>(o, 749327532984), rct::skvGen(o)));
+ o = o * mul + add;
+ }
+ return true;
+ }
+
+ bool test()
+ {
+ if (batch)
+ {
+ return rct::bulletproof_plus_VERIFY(proofs);
+ }
+ else
+ {
+ for (const rct::BulletproofPlus &proof: proofs)
+ if (!rct::bulletproof_plus_VERIFY(proof))
+ return false;
+ return true;
+ }
+ }
+
+private:
+ std::vector<rct::BulletproofPlus> proofs;
+};
diff --git a/tests/performance_tests/main.cpp b/tests/performance_tests/main.cpp
index e59bb52fd..a61d84bed 100644
--- a/tests/performance_tests/main.cpp
+++ b/tests/performance_tests/main.cpp
@@ -58,6 +58,7 @@
#include "equality.h"
#include "range_proof.h"
#include "bulletproof.h"
+#include "bulletproof_plus.h"
#include "crypto_ops.h"
#include "multiexp.h"
#include "sig_mlsag.h"
@@ -241,6 +242,26 @@ int main(int argc, char** argv)
TEST_PERFORMANCE1(filter, p, test_range_proof, true);
TEST_PERFORMANCE1(filter, p, test_range_proof, false);
+ TEST_PERFORMANCE2(filter, p, test_bulletproof_plus, true, 1); // 1 bulletproof_plus with 1 amount
+ TEST_PERFORMANCE2(filter, p, test_bulletproof_plus, false, 1);
+
+ TEST_PERFORMANCE2(filter, p, test_bulletproof_plus, true, 2); // 1 bulletproof_plus with 2 amounts
+ TEST_PERFORMANCE2(filter, p, test_bulletproof_plus, false, 2);
+
+ TEST_PERFORMANCE2(filter, p, test_bulletproof_plus, true, 15); // 1 bulletproof_plus with 15 amounts
+ TEST_PERFORMANCE2(filter, p, test_bulletproof_plus, false, 15);
+
+ TEST_PERFORMANCE6(filter, p, test_aggregated_bulletproof_plus, false, 2, 1, 1, 0, 4);
+ TEST_PERFORMANCE6(filter, p, test_aggregated_bulletproof_plus, true, 2, 1, 1, 0, 4); // 4 proofs, each with 2 amounts
+ TEST_PERFORMANCE6(filter, p, test_aggregated_bulletproof_plus, false, 8, 1, 1, 0, 4);
+ TEST_PERFORMANCE6(filter, p, test_aggregated_bulletproof_plus, true, 8, 1, 1, 0, 4); // 4 proofs, each with 8 amounts
+ TEST_PERFORMANCE6(filter, p, test_aggregated_bulletproof_plus, false, 1, 1, 2, 0, 4);
+ TEST_PERFORMANCE6(filter, p, test_aggregated_bulletproof_plus, true, 1, 1, 2, 0, 4); // 4 proofs with 1, 2, 4, 8 amounts
+ TEST_PERFORMANCE6(filter, p, test_aggregated_bulletproof_plus, false, 1, 8, 1, 1, 4);
+ TEST_PERFORMANCE6(filter, p, test_aggregated_bulletproof_plus, true, 1, 8, 1, 1, 4); // 32 proofs, with 1, 2, 3, 4 amounts, 8 of each
+ TEST_PERFORMANCE6(filter, p, test_aggregated_bulletproof_plus, false, 2, 1, 1, 0, 64);
+ TEST_PERFORMANCE6(filter, p, test_aggregated_bulletproof_plus, true, 2, 1, 1, 0, 64); // 64 proof, each with 2 amounts
+
TEST_PERFORMANCE2(filter, p, test_bulletproof, true, 1); // 1 bulletproof with 1 amount
TEST_PERFORMANCE2(filter, p, test_bulletproof, false, 1);
diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt
index 556e0ec40..5f6b1e749 100644
--- a/tests/unit_tests/CMakeLists.txt
+++ b/tests/unit_tests/CMakeLists.txt
@@ -36,6 +36,7 @@ set(unit_tests_sources
block_reward.cpp
bootstrap_node_selector.cpp
bulletproofs.cpp
+ bulletproofs_plus.cpp
canonical_amounts.cpp
chacha.cpp
checkpoints.cpp
diff --git a/tests/unit_tests/bulletproofs.cpp b/tests/unit_tests/bulletproofs.cpp
index ee617938c..43a359a59 100644
--- a/tests/unit_tests/bulletproofs.cpp
+++ b/tests/unit_tests/bulletproofs.cpp
@@ -131,7 +131,7 @@ TEST(bulletproofs, multi_splitting)
}
rct::ctkeyV outSk;
- rct::RCTConfig rct_config { rct::RangeProofPaddedBulletproof, 0 };
+ rct::RCTConfig rct_config { rct::RangeProofPaddedBulletproof, 4 };
rct::rctSig s = rct::genRctSimple(rct::zero(), sc, destinations, inamounts, outamounts, available, mixRing, amount_keys, NULL, NULL, index, outSk, rct_config, hw::get_device("default"));
ASSERT_TRUE(rct::verRctSimple(s));
for (size_t i = 0; i < n_outputs; ++i)
diff --git a/tests/unit_tests/bulletproofs_plus.cpp b/tests/unit_tests/bulletproofs_plus.cpp
new file mode 100644
index 000000000..a64320233
--- /dev/null
+++ b/tests/unit_tests/bulletproofs_plus.cpp
@@ -0,0 +1,169 @@
+// Copyright (c) 2017-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
+
+#include "gtest/gtest.h"
+
+#include "string_tools.h"
+#include "ringct/rctOps.h"
+#include "ringct/rctSigs.h"
+#include "ringct/bulletproofs_plus.h"
+#include "cryptonote_basic/blobdatatype.h"
+#include "cryptonote_basic/cryptonote_format_utils.h"
+#include "device/device.hpp"
+#include "misc_log_ex.h"
+
+TEST(bulletproofs_plus, valid_zero)
+{
+ rct::BulletproofPlus proof = bulletproof_plus_PROVE(0, rct::skGen());
+ ASSERT_TRUE(rct::bulletproof_plus_VERIFY(proof));
+}
+
+TEST(bulletproofs_plus, valid_max)
+{
+ rct::BulletproofPlus proof = bulletproof_plus_PROVE(0xffffffffffffffff, rct::skGen());
+ ASSERT_TRUE(rct::bulletproof_plus_VERIFY(proof));
+}
+
+TEST(bulletproofs_plus, valid_random)
+{
+ for (int n = 0; n < 8; ++n)
+ {
+ rct::BulletproofPlus proof = bulletproof_plus_PROVE(crypto::rand<uint64_t>(), rct::skGen());
+ ASSERT_TRUE(rct::bulletproof_plus_VERIFY(proof));
+ }
+}
+
+TEST(bulletproofs_plus, valid_multi_random)
+{
+ for (int n = 0; n < 8; ++n)
+ {
+ size_t outputs = 2 + n;
+ std::vector<uint64_t> amounts;
+ rct::keyV gamma;
+ for (size_t i = 0; i < outputs; ++i)
+ {
+ amounts.push_back(crypto::rand<uint64_t>());
+ gamma.push_back(rct::skGen());
+ }
+ rct::BulletproofPlus proof = bulletproof_plus_PROVE(amounts, gamma);
+ ASSERT_TRUE(rct::bulletproof_plus_VERIFY(proof));
+ }
+}
+
+TEST(bulletproofs_plus, valid_aggregated)
+{
+ static const size_t N_PROOFS = 8;
+ std::vector<rct::BulletproofPlus> proofs(N_PROOFS);
+ for (size_t n = 0; n < N_PROOFS; ++n)
+ {
+ size_t outputs = 2 + n;
+ std::vector<uint64_t> amounts;
+ rct::keyV gamma;
+ for (size_t i = 0; i < outputs; ++i)
+ {
+ amounts.push_back(crypto::rand<uint64_t>());
+ gamma.push_back(rct::skGen());
+ }
+ proofs[n] = bulletproof_plus_PROVE(amounts, gamma);
+ }
+ ASSERT_TRUE(rct::bulletproof_plus_VERIFY(proofs));
+}
+
+TEST(bulletproofs_plus, invalid_8)
+{
+ rct::key invalid_amount = rct::zero();
+ invalid_amount[8] = 1;
+ rct::BulletproofPlus proof = bulletproof_plus_PROVE(invalid_amount, rct::skGen());
+ ASSERT_FALSE(rct::bulletproof_plus_VERIFY(proof));
+}
+
+TEST(bulletproofs_plus, invalid_31)
+{
+ rct::key invalid_amount = rct::zero();
+ invalid_amount[31] = 1;
+ rct::BulletproofPlus proof = bulletproof_plus_PROVE(invalid_amount, rct::skGen());
+ ASSERT_FALSE(rct::bulletproof_plus_VERIFY(proof));
+}
+
+static const char * const torsion_elements[] =
+{
+ "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa",
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85",
+ "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f",
+ "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05",
+ "0000000000000000000000000000000000000000000000000000000000000080",
+ "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a",
+};
+
+TEST(bulletproofs_plus, invalid_torsion)
+{
+ rct::BulletproofPlus proof = bulletproof_plus_PROVE(7329838943733, rct::skGen());
+ ASSERT_TRUE(rct::bulletproof_plus_VERIFY(proof));
+ for (const auto &xs: torsion_elements)
+ {
+ rct::key x;
+ ASSERT_TRUE(epee::string_tools::hex_to_pod(xs, x));
+ ASSERT_FALSE(rct::isInMainSubgroup(x));
+ for (auto &k: proof.V)
+ {
+ const rct::key org_k = k;
+ rct::addKeys(k, org_k, x);
+ ASSERT_FALSE(rct::bulletproof_plus_VERIFY(proof));
+ k = org_k;
+ }
+ for (auto &k: proof.L)
+ {
+ const rct::key org_k = k;
+ rct::addKeys(k, org_k, x);
+ ASSERT_FALSE(rct::bulletproof_plus_VERIFY(proof));
+ k = org_k;
+ }
+ for (auto &k: proof.R)
+ {
+ const rct::key org_k = k;
+ rct::addKeys(k, org_k, x);
+ ASSERT_FALSE(rct::bulletproof_plus_VERIFY(proof));
+ k = org_k;
+ }
+ const rct::key org_A = proof.A;
+ rct::addKeys(proof.A, org_A, x);
+ ASSERT_FALSE(rct::bulletproof_plus_VERIFY(proof));
+ proof.A = org_A;
+ const rct::key org_A1 = proof.A1;
+ rct::addKeys(proof.A1, org_A1, x);
+ ASSERT_FALSE(rct::bulletproof_plus_VERIFY(proof));
+ proof.A1 = org_A1;
+ const rct::key org_B = proof.B;
+ rct::addKeys(proof.B, org_B, x);
+ ASSERT_FALSE(rct::bulletproof_plus_VERIFY(proof));
+ proof.B = org_B;
+ }
+}
diff --git a/tests/unit_tests/multisig.cpp b/tests/unit_tests/multisig.cpp
index 79775960d..362a658de 100644
--- a/tests/unit_tests/multisig.cpp
+++ b/tests/unit_tests/multisig.cpp
@@ -26,12 +26,16 @@
// 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 "crypto/crypto.h"
+#include "multisig/multisig_account.h"
+#include "multisig/multisig_kex_msg.h"
+#include "ringct/rctOps.h"
+#include "wallet/wallet2.h"
+
#include "gtest/gtest.h"
#include <cstdint>
-#include "wallet/wallet2.h"
-
static const struct
{
const char *address;
@@ -86,59 +90,145 @@ static void make_wallet(unsigned int idx, tools::wallet2 &wallet)
}
}
-static std::vector<std::string> exchange_round(std::vector<tools::wallet2>& wallets, const std::vector<std::string>& mis)
+static std::vector<std::string> exchange_round(std::vector<tools::wallet2>& wallets, const std::vector<std::string>& infos)
{
std::vector<std::string> new_infos;
- for (size_t i = 0; i < wallets.size(); ++i) {
- new_infos.push_back(wallets[i].exchange_multisig_keys("", mis));
+ new_infos.reserve(infos.size());
+
+ for (size_t i = 0; i < wallets.size(); ++i)
+ {
+ new_infos.push_back(wallets[i].exchange_multisig_keys("", infos));
}
return new_infos;
}
+static void check_results(const std::vector<std::string> &intermediate_infos,
+ std::vector<tools::wallet2>& wallets,
+ std::uint32_t M)
+{
+ // check results
+ std::unordered_set<crypto::secret_key> unique_privkeys;
+ rct::key composite_pubkey = rct::identity();
+
+ wallets[0].decrypt_keys("");
+ crypto::public_key spend_pubkey = wallets[0].get_account().get_keys().m_account_address.m_spend_public_key;
+ crypto::secret_key view_privkey = wallets[0].get_account().get_keys().m_view_secret_key;
+ crypto::public_key view_pubkey;
+ EXPECT_TRUE(crypto::secret_key_to_public_key(view_privkey, view_pubkey));
+ wallets[0].encrypt_keys("");
+
+ for (size_t i = 0; i < wallets.size(); ++i)
+ {
+ EXPECT_TRUE(intermediate_infos[i].empty());
+ bool ready;
+ uint32_t threshold, total;
+ EXPECT_TRUE(wallets[i].multisig(&ready, &threshold, &total));
+ EXPECT_TRUE(ready);
+ EXPECT_TRUE(threshold == M);
+ EXPECT_TRUE(total == wallets.size());
+
+ wallets[i].decrypt_keys("");
+
+ if (i != 0)
+ {
+ // "equals" is transitive relation so we need only to compare first wallet's address to each others' addresses.
+ // no need to compare 0's address with itself.
+ EXPECT_TRUE(wallets[0].get_account().get_public_address_str(cryptonote::TESTNET) ==
+ wallets[i].get_account().get_public_address_str(cryptonote::TESTNET));
+
+ EXPECT_EQ(spend_pubkey, wallets[i].get_account().get_keys().m_account_address.m_spend_public_key);
+ EXPECT_EQ(view_privkey, wallets[i].get_account().get_keys().m_view_secret_key);
+ EXPECT_EQ(view_pubkey, wallets[i].get_account().get_keys().m_account_address.m_view_public_key);
+ }
+
+ // sum together unique multisig keys
+ for (const auto &privkey : wallets[i].get_account().get_keys().m_multisig_keys)
+ {
+ EXPECT_NE(privkey, crypto::null_skey);
+
+ if (unique_privkeys.find(privkey) == unique_privkeys.end())
+ {
+ unique_privkeys.insert(privkey);
+ crypto::public_key pubkey;
+ crypto::secret_key_to_public_key(privkey, pubkey);
+ EXPECT_NE(privkey, crypto::null_skey);
+ EXPECT_NE(pubkey, crypto::null_pkey);
+ EXPECT_NE(pubkey, rct::rct2pk(rct::identity()));
+ rct::addKeys(composite_pubkey, composite_pubkey, rct::pk2rct(pubkey));
+ }
+ }
+ wallets[i].encrypt_keys("");
+ }
+
+ // final key via sums should equal the wallets' public spend key
+ wallets[0].decrypt_keys("");
+ EXPECT_EQ(wallets[0].get_account().get_keys().m_account_address.m_spend_public_key, rct::rct2pk(composite_pubkey));
+ wallets[0].encrypt_keys("");
+}
+
static void make_wallets(std::vector<tools::wallet2>& wallets, unsigned int M)
{
ASSERT_TRUE(wallets.size() > 1 && wallets.size() <= KEYS_COUNT);
ASSERT_TRUE(M <= wallets.size());
+ std::uint32_t rounds_required = multisig::multisig_kex_rounds_required(wallets.size(), M);
+ std::uint32_t rounds_complete{0};
- std::vector<std::string> mis(wallets.size());
+ // initialize wallets, get first round multisig kex msgs
+ std::vector<std::string> initial_infos(wallets.size());
- for (size_t i = 0; i < wallets.size(); ++i) {
+ for (size_t i = 0; i < wallets.size(); ++i)
+ {
make_wallet(i, wallets[i]);
wallets[i].decrypt_keys("");
- mis[i] = wallets[i].get_multisig_info();
+ initial_infos[i] = wallets[i].get_multisig_first_kex_msg();
wallets[i].encrypt_keys("");
}
- for (auto& wallet: wallets) {
+ // wallets should not be multisig yet
+ for (const auto &wallet: wallets)
+ {
ASSERT_FALSE(wallet.multisig());
}
- std::vector<std::string> mxis;
- for (size_t i = 0; i < wallets.size(); ++i) {
- // it's ok to put all of multisig keys in this function. it throws in case of error
- mxis.push_back(wallets[i].make_multisig("", mis, M));
- }
+ // make wallets multisig, get second round kex messages (if appropriate)
+ std::vector<std::string> intermediate_infos(wallets.size());
- while (!mxis[0].empty()) {
- mxis = exchange_round(wallets, mxis);
+ for (size_t i = 0; i < wallets.size(); ++i)
+ {
+ intermediate_infos[i] = wallets[i].make_multisig("", initial_infos, M);
}
- for (size_t i = 0; i < wallets.size(); ++i) {
- ASSERT_TRUE(mxis[i].empty());
- bool ready;
- uint32_t threshold, total;
- ASSERT_TRUE(wallets[i].multisig(&ready, &threshold, &total));
- ASSERT_TRUE(ready);
- ASSERT_TRUE(threshold == M);
- ASSERT_TRUE(total == wallets.size());
-
- if (i != 0) {
- // "equals" is transitive relation so we need only to compare first wallet's address to each others' addresses. no need to compare 0's address with itself.
- ASSERT_TRUE(wallets[0].get_account().get_public_address_str(cryptonote::TESTNET) == wallets[i].get_account().get_public_address_str(cryptonote::TESTNET));
- }
+ ++rounds_complete;
+
+ // perform kex rounds until kex is complete
+ while (!intermediate_infos[0].empty())
+ {
+ bool ready{false};
+ wallets[0].multisig(&ready);
+ EXPECT_FALSE(ready);
+
+ intermediate_infos = exchange_round(wallets, intermediate_infos);
+
+ ++rounds_complete;
}
+
+ EXPECT_EQ(rounds_required, rounds_complete);
+
+ check_results(intermediate_infos, wallets, M);
+}
+
+TEST(multisig, make_1_2)
+{
+ std::vector<tools::wallet2> wallets(2);
+ make_wallets(wallets, 1);
+}
+
+TEST(multisig, make_1_3)
+{
+ std::vector<tools::wallet2> wallets(3);
+ make_wallets(wallets, 1);
}
TEST(multisig, make_2_2)
@@ -165,8 +255,88 @@ TEST(multisig, make_2_4)
make_wallets(wallets, 2);
}
-TEST(multisig, make_2_5)
+TEST(multisig, multisig_kex_msg)
{
- std::vector<tools::wallet2> wallets(5);
- make_wallets(wallets, 2);
+ using namespace multisig;
+
+ crypto::public_key pubkey1;
+ crypto::public_key pubkey2;
+ crypto::public_key pubkey3;
+ crypto::secret_key_to_public_key(rct::rct2sk(rct::skGen()), pubkey1);
+ crypto::secret_key_to_public_key(rct::rct2sk(rct::skGen()), pubkey2);
+ crypto::secret_key_to_public_key(rct::rct2sk(rct::skGen()), pubkey3);
+
+ crypto::secret_key signing_skey = rct::rct2sk(rct::skGen());
+ crypto::public_key signing_pubkey;
+ while(!crypto::secret_key_to_public_key(signing_skey, signing_pubkey))
+ {
+ signing_skey = rct::rct2sk(rct::skGen());
+ }
+
+ crypto::secret_key ancillary_skey = rct::rct2sk(rct::skGen());
+ while (ancillary_skey == crypto::null_skey)
+ ancillary_skey = rct::rct2sk(rct::skGen());
+
+ // misc. edge cases
+ EXPECT_NO_THROW((multisig_kex_msg{}));
+ EXPECT_ANY_THROW((multisig_kex_msg{multisig_kex_msg{}.get_msg()}));
+ EXPECT_ANY_THROW((multisig_kex_msg{"abc"}));
+ EXPECT_ANY_THROW((multisig_kex_msg{0, crypto::null_skey, std::vector<crypto::public_key>{}, crypto::null_skey}));
+ EXPECT_ANY_THROW((multisig_kex_msg{1, crypto::null_skey, std::vector<crypto::public_key>{}, crypto::null_skey}));
+ EXPECT_ANY_THROW((multisig_kex_msg{1, signing_skey, std::vector<crypto::public_key>{}, crypto::null_skey}));
+ EXPECT_ANY_THROW((multisig_kex_msg{1, crypto::null_skey, std::vector<crypto::public_key>{}, ancillary_skey}));
+
+ // test that messages are both constructible and reversible
+
+ // round 1
+ EXPECT_NO_THROW((multisig_kex_msg{
+ multisig_kex_msg{1, signing_skey, std::vector<crypto::public_key>{}, ancillary_skey}.get_msg()
+ }));
+ EXPECT_NO_THROW((multisig_kex_msg{
+ multisig_kex_msg{1, signing_skey, std::vector<crypto::public_key>{pubkey1}, ancillary_skey}.get_msg()
+ }));
+
+ // round 2
+ EXPECT_NO_THROW((multisig_kex_msg{
+ multisig_kex_msg{2, signing_skey, std::vector<crypto::public_key>{pubkey1}, ancillary_skey}.get_msg()
+ }));
+ EXPECT_NO_THROW((multisig_kex_msg{
+ multisig_kex_msg{2, signing_skey, std::vector<crypto::public_key>{pubkey1}, crypto::null_skey}.get_msg()
+ }));
+ EXPECT_NO_THROW((multisig_kex_msg{
+ multisig_kex_msg{2, signing_skey, std::vector<crypto::public_key>{pubkey1, pubkey2}, ancillary_skey}.get_msg()
+ }));
+ EXPECT_NO_THROW((multisig_kex_msg{
+ multisig_kex_msg{2, signing_skey, std::vector<crypto::public_key>{pubkey1, pubkey2, pubkey3}, crypto::null_skey}.get_msg()
+ }));
+
+ // test that keys can be recovered if stored in a message and the message's reverse
+
+ // round 1
+ multisig_kex_msg msg_rnd1{1, signing_skey, std::vector<crypto::public_key>{pubkey1}, ancillary_skey};
+ multisig_kex_msg msg_rnd1_reverse{msg_rnd1.get_msg()};
+ EXPECT_EQ(msg_rnd1.get_round(), 1);
+ EXPECT_EQ(msg_rnd1.get_round(), msg_rnd1_reverse.get_round());
+ EXPECT_EQ(msg_rnd1.get_signing_pubkey(), signing_pubkey);
+ EXPECT_EQ(msg_rnd1.get_signing_pubkey(), msg_rnd1_reverse.get_signing_pubkey());
+ EXPECT_EQ(msg_rnd1.get_msg_pubkeys().size(), 0);
+ EXPECT_EQ(msg_rnd1.get_msg_pubkeys().size(), msg_rnd1_reverse.get_msg_pubkeys().size());
+ EXPECT_EQ(msg_rnd1.get_msg_privkey(), ancillary_skey);
+ EXPECT_EQ(msg_rnd1.get_msg_privkey(), msg_rnd1_reverse.get_msg_privkey());
+
+ // round 2
+ multisig_kex_msg msg_rnd2{2, signing_skey, std::vector<crypto::public_key>{pubkey1, pubkey2}, ancillary_skey};
+ multisig_kex_msg msg_rnd2_reverse{msg_rnd2.get_msg()};
+ EXPECT_EQ(msg_rnd2.get_round(), 2);
+ EXPECT_EQ(msg_rnd2.get_round(), msg_rnd2_reverse.get_round());
+ EXPECT_EQ(msg_rnd2.get_signing_pubkey(), signing_pubkey);
+ EXPECT_EQ(msg_rnd2.get_signing_pubkey(), msg_rnd2_reverse.get_signing_pubkey());
+ ASSERT_EQ(msg_rnd2.get_msg_pubkeys().size(), 2);
+ ASSERT_EQ(msg_rnd2.get_msg_pubkeys().size(), msg_rnd2_reverse.get_msg_pubkeys().size());
+ EXPECT_EQ(msg_rnd2.get_msg_pubkeys()[0], pubkey1);
+ EXPECT_EQ(msg_rnd2.get_msg_pubkeys()[1], pubkey2);
+ EXPECT_EQ(msg_rnd2.get_msg_pubkeys()[0], msg_rnd2_reverse.get_msg_pubkeys()[0]);
+ EXPECT_EQ(msg_rnd2.get_msg_pubkeys()[1], msg_rnd2_reverse.get_msg_pubkeys()[1]);
+ EXPECT_EQ(msg_rnd2.get_msg_privkey(), crypto::null_skey);
+ EXPECT_EQ(msg_rnd2.get_msg_privkey(), msg_rnd2_reverse.get_msg_privkey());
}
diff --git a/utils/python-rpc/framework/wallet.py b/utils/python-rpc/framework/wallet.py
index 02084620c..e531bf13d 100644
--- a/utils/python-rpc/framework/wallet.py
+++ b/utils/python-rpc/framework/wallet.py
@@ -512,14 +512,12 @@ class Wallet(object):
}
return self.rpc.send_json_rpc_request(make_multisig)
- def finalize_multisig(self, multisig_info, password = ''):
+ def finalize_multisig(self):
finalize_multisig = {
'method': 'finalize_multisig',
'params' : {
- 'multisig_info': multisig_info,
- 'password': password,
},
- 'jsonrpc': '2.0',
+ 'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(finalize_multisig)
diff --git a/utils/systemd/monerod.service b/utils/systemd/monerod.service
index c89e31f8c..63daefa82 100644
--- a/utils/systemd/monerod.service
+++ b/utils/systemd/monerod.service
@@ -9,28 +9,11 @@ WorkingDirectory=~
StateDirectory=monero
LogsDirectory=monero
-# Clearnet config
-#
Type=simple
ExecStart=/usr/bin/monerod --config-file /etc/monerod.conf --non-interactive
StandardOutput=null
StandardError=null
-# Tor config
-#
-## We have to use simple, not forking, because we cannot pass --detach
-## because stderr/stdout is not available when detached, but torsocks
-## attempts to write to it, and fails with 'invalid argument', causing
-## monerod to fail.
-#Type=simple
-#Environment=DNS_PUBLIC=tcp
-## The following is needed only when accessing wallet from a different
-## host in the LAN, VPN, etc, the RPC must bind to 0.0.0.0, but
-## by default torsocks only allows binding to localhost.
-#Environment=TORSOCKS_ALLOW_INBOUND=1
-#ExecStart=/usr/bin/torsocks /usr/bin/monerod --config-file /etc/monerod.conf \
-# --non-interactive
-
Restart=always
[Install]