aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/blockchain_db/blockchain_db.cpp9
-rw-r--r--src/crypto/keccak.c95
-rw-r--r--src/cryptonote_basic/cryptonote_format_utils.cpp2
-rw-r--r--src/hardforks/hardforks.cpp2
-rw-r--r--src/multisig/multisig_account.cpp49
-rw-r--r--src/multisig/multisig_account.h26
-rw-r--r--src/multisig/multisig_account_kex_impl.cpp333
-rw-r--r--src/multisig/multisig_kex_msg.cpp2
-rw-r--r--src/multisig/multisig_kex_msg.h2
-rw-r--r--src/multisig/multisig_kex_msg_serialization.h2
-rw-r--r--src/net/zmq.cpp2
-rw-r--r--src/ringct/bulletproofs_plus.cc8
-rw-r--r--src/ringct/bulletproofs_plus.h2
-rw-r--r--src/ringct/rctSigs.cpp32
-rw-r--r--src/serialization/list.h58
-rw-r--r--src/serialization/serialization.h7
-rw-r--r--src/simplewallet/simplewallet.cpp10
-rw-r--r--src/wallet/ringdb.cpp39
-rw-r--r--src/wallet/ringdb.h2
-rw-r--r--src/wallet/wallet2.cpp130
-rw-r--r--src/wallet/wallet2.h12
-rw-r--r--src/wallet/wallet_rpc_server.cpp3
22 files changed, 501 insertions, 326 deletions
diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp
index 44be08003..ab73e255c 100644
--- a/src/blockchain_db/blockchain_db.cpp
+++ b/src/blockchain_db/blockchain_db.cpp
@@ -241,15 +241,8 @@ 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 ? &commitment : NULL);
+ tx.version > 1 ? &tx.rct_signatures.outPk[i].mask : NULL);
}
}
add_tx_amount_output_indices(tx_id, amount_output_indices);
diff --git a/src/crypto/keccak.c b/src/crypto/keccak.c
index 72d472d8a..f098cbdf0 100644
--- a/src/crypto/keccak.c
+++ b/src/crypto/keccak.c
@@ -31,54 +31,83 @@ const uint64_t keccakf_rndc[24] =
0x8000000000008080, 0x0000000080000001, 0x8000000080008008
};
-const int keccakf_rotc[24] =
-{
- 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14,
- 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44
-};
-
-const int keccakf_piln[24] =
-{
- 10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4,
- 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1
-};
-
// update the state with given number of rounds
void keccakf(uint64_t st[25], int rounds)
{
- int i, j, round;
+ int round;
uint64_t t, bc[5];
- for (round = 0; round < rounds; round++) {
-
+ for (round = 0; round < rounds; ++round) {
// Theta
- for (i = 0; i < 5; i++)
- bc[i] = st[i] ^ st[i + 5] ^ st[i + 10] ^ st[i + 15] ^ st[i + 20];
-
- for (i = 0; i < 5; i++) {
- t = bc[(i + 4) % 5] ^ ROTL64(bc[(i + 1) % 5], 1);
- for (j = 0; j < 25; j += 5)
- st[j + i] ^= t;
+ bc[0] = st[0] ^ st[5] ^ st[10] ^ st[15] ^ st[20];
+ bc[1] = st[1] ^ st[6] ^ st[11] ^ st[16] ^ st[21];
+ bc[2] = st[2] ^ st[7] ^ st[12] ^ st[17] ^ st[22];
+ bc[3] = st[3] ^ st[8] ^ st[13] ^ st[18] ^ st[23];
+ bc[4] = st[4] ^ st[9] ^ st[14] ^ st[19] ^ st[24];
+
+#define THETA(i) { \
+ t = bc[(i + 4) % 5] ^ ROTL64(bc[(i + 1) % 5], 1); \
+ st[i ] ^= t; \
+ st[i + 5] ^= t; \
+ st[i + 10] ^= t; \
+ st[i + 15] ^= t; \
+ st[i + 20] ^= t; \
}
+ THETA(0);
+ THETA(1);
+ THETA(2);
+ THETA(3);
+ THETA(4);
+
// Rho Pi
t = st[1];
- for (i = 0; i < 24; i++) {
- j = keccakf_piln[i];
- bc[0] = st[j];
- st[j] = ROTL64(t, keccakf_rotc[i]);
- t = bc[0];
- }
+ st[ 1] = ROTL64(st[ 6], 44);
+ st[ 6] = ROTL64(st[ 9], 20);
+ st[ 9] = ROTL64(st[22], 61);
+ st[22] = ROTL64(st[14], 39);
+ st[14] = ROTL64(st[20], 18);
+ st[20] = ROTL64(st[ 2], 62);
+ st[ 2] = ROTL64(st[12], 43);
+ st[12] = ROTL64(st[13], 25);
+ st[13] = ROTL64(st[19], 8);
+ st[19] = ROTL64(st[23], 56);
+ st[23] = ROTL64(st[15], 41);
+ st[15] = ROTL64(st[ 4], 27);
+ st[ 4] = ROTL64(st[24], 14);
+ st[24] = ROTL64(st[21], 2);
+ st[21] = ROTL64(st[ 8], 55);
+ st[ 8] = ROTL64(st[16], 45);
+ st[16] = ROTL64(st[ 5], 36);
+ st[ 5] = ROTL64(st[ 3], 28);
+ st[ 3] = ROTL64(st[18], 21);
+ st[18] = ROTL64(st[17], 15);
+ st[17] = ROTL64(st[11], 10);
+ st[11] = ROTL64(st[ 7], 6);
+ st[ 7] = ROTL64(st[10], 3);
+ st[10] = ROTL64(t, 1);
// Chi
- for (j = 0; j < 25; j += 5) {
- for (i = 0; i < 5; i++)
- bc[i] = st[j + i];
- for (i = 0; i < 5; i++)
- st[j + i] ^= (~bc[(i + 1) % 5]) & bc[(i + 2) % 5];
+#define CHI(j) { \
+ const uint64_t st0 = st[j ]; \
+ const uint64_t st1 = st[j + 1]; \
+ const uint64_t st2 = st[j + 2]; \
+ const uint64_t st3 = st[j + 3]; \
+ const uint64_t st4 = st[j + 4]; \
+ st[j ] ^= ~st1 & st2; \
+ st[j + 1] ^= ~st2 & st3; \
+ st[j + 2] ^= ~st3 & st4; \
+ st[j + 3] ^= ~st4 & st0; \
+ st[j + 4] ^= ~st0 & st1; \
}
+ CHI( 0);
+ CHI( 5);
+ CHI(10);
+ CHI(15);
+ CHI(20);
+
// Iota
st[0] ^= keccakf_rndc[round];
}
diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp
index 432617a4f..f101f10c5 100644
--- a/src/cryptonote_basic/cryptonote_format_utils.cpp
+++ b/src/cryptonote_basic/cryptonote_format_utils.cpp
@@ -190,7 +190,7 @@ namespace cryptonote
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;
+ rv.p.bulletproofs_plus[0].V[i] = rct::scalarmultKey(rv.outPk[i].mask, rct::INV_EIGHT);
}
else if (bulletproof)
{
diff --git a/src/hardforks/hardforks.cpp b/src/hardforks/hardforks.cpp
index c055f49d2..0dd0cf5f0 100644
--- a/src/hardforks/hardforks.cpp
+++ b/src/hardforks/hardforks.cpp
@@ -98,6 +98,8 @@ const hardfork_t testnet_hard_forks[] = {
{ 12, 1308737, 0, 1569582000 },
{ 13, 1543939, 0, 1599069376 },
{ 14, 1544659, 0, 1599069377 },
+ { 15, 1982800, 0, 1652727000 },
+ { 16, 1983520, 0, 1652813400 },
};
const size_t num_testnet_hard_forks = sizeof(testnet_hard_forks) / sizeof(testnet_hard_forks[0]);
const uint64_t testnet_hard_fork_version_1_till = 624633;
diff --git a/src/multisig/multisig_account.cpp b/src/multisig/multisig_account.cpp
index b7298c4b6..9bdcf2dbc 100644
--- a/src/multisig/multisig_account.cpp
+++ b/src/multisig/multisig_account.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2021, The Monero Project
+// Copyright (c) 2021-2022, The Monero Project
//
// All rights reserved.
//
@@ -73,7 +73,7 @@ namespace multisig
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,
+ multisig_keyset_map_memsafe_t kex_origins_map,
std::string next_round_kex_message) :
m_base_privkey{base_privkey},
m_base_common_privkey{base_common_privkey},
@@ -89,6 +89,20 @@ namespace multisig
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));
+
+ // kex rounds should not exceed post-kex verification round
+ const std::uint32_t kex_rounds_required{multisig_kex_rounds_required(m_signers.size(), m_threshold)};
+ CHECK_AND_ASSERT_THROW_MES(m_kex_rounds_complete <= kex_rounds_required + 1,
+ "multisig account: tried to reconstruct account, but kex rounds complete counter is invalid.");
+
+ // once an account is done with kex, the 'next kex msg' is always the post-kex verification message
+ // i.e. the multisig account pubkey signed by the signer's privkey AND the common pubkey
+ if (main_kex_rounds_done())
+ {
+ m_next_round_kex_message = multisig_kex_msg{kex_rounds_required + 1,
+ m_base_privkey,
+ std::vector<crypto::public_key>{m_multisig_pubkey, m_common_pubkey}}.get_msg();
+ }
}
//----------------------------------------------------------------------------------------------------------------------
// multisig_account: EXTERNAL
@@ -100,14 +114,24 @@ namespace multisig
//----------------------------------------------------------------------------------------------------------------------
// multisig_account: EXTERNAL
//----------------------------------------------------------------------------------------------------------------------
- bool multisig_account::multisig_is_ready() const
+ bool multisig_account::main_kex_rounds_done() const
{
if (account_is_active())
- return multisig_kex_rounds_required(m_signers.size(), m_threshold) == m_kex_rounds_complete;
+ return m_kex_rounds_complete >= multisig_kex_rounds_required(m_signers.size(), m_threshold);
+ else
+ return false;
+ }
+ //----------------------------------------------------------------------------------------------------------------------
+ // multisig_account: EXTERNAL
+ //----------------------------------------------------------------------------------------------------------------------
+ bool multisig_account::multisig_is_ready() const
+ {
+ if (main_kex_rounds_done())
+ return m_kex_rounds_complete >= multisig_kex_rounds_required(m_signers.size(), m_threshold) + 1;
else
return false;
}
- //----------------------------------------------------------------------------------------------------------------------
+ //----------------------------------------------------------------------------------------------------------------------
// multisig_account: INTERNAL
//----------------------------------------------------------------------------------------------------------------------
void multisig_account::set_multisig_config(const std::size_t threshold, std::vector<crypto::public_key> signers)
@@ -119,10 +143,6 @@ namespace multisig
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.");
@@ -133,12 +153,11 @@ namespace multisig
"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;
- }
- );
+ std::sort(signers.begin(), signers.end());
+
+ // signers should all be unique
+ CHECK_AND_ASSERT_THROW_MES(std::adjacent_find(signers.begin(), signers.end()) == signers.end(),
+ "multisig account: tried to set signers, but there are duplicate signers unexpectedly.");
// set
m_threshold = threshold;
diff --git a/src/multisig/multisig_account.h b/src/multisig/multisig_account.h
index b01ae6c88..7b372bbff 100644
--- a/src/multisig/multisig_account.h
+++ b/src/multisig/multisig_account.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2021, The Monero Project
+// Copyright (c) 2021-2022, The Monero Project
//
// All rights reserved.
//
@@ -75,12 +75,12 @@ namespace multisig
* - 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
*/
+ using multisig_keyset_map_memsafe_t =
+ std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>>;
+
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;
@@ -105,7 +105,7 @@ namespace multisig
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,
+ multisig_keyset_map_memsafe_t kex_origins_map,
std::string next_round_kex_message);
// copy constructor: default
@@ -137,13 +137,15 @@ namespace multisig
// 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; }
+ const multisig_keyset_map_memsafe_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 has gone through main kex rounds, only remaining step is to verify all other participants are ready
+ bool main_kex_rounds_done() const;
// account is ready to make multisig signatures
bool multisig_is_ready() const;
@@ -178,21 +180,21 @@ namespace multisig
* - 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
+ * param: kex_rounds_required - number of rounds required for kex (not including post-kex verification round)
* 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,
+ const std::uint32_t kex_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: kex_rounds_required - number of rounds required for kex (not including post-kex verification round)
* 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);
+ void finalize_kex_update(const std::uint32_t kex_rounds_required,
+ multisig_keyset_map_memsafe_t result_keys_to_origins_map);
//member variables
private:
@@ -226,7 +228,7 @@ namespace multisig
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;
+ multisig_keyset_map_memsafe_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;
};
diff --git a/src/multisig/multisig_account_kex_impl.cpp b/src/multisig/multisig_account_kex_impl.cpp
index 0a0ca7bdc..be9ed9cb2 100644
--- a/src/multisig/multisig_account_kex_impl.cpp
+++ b/src/multisig/multisig_account_kex_impl.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2021, The Monero Project
+// Copyright (c) 2021-2022, The Monero Project
//
// All rights reserved.
//
@@ -57,6 +57,30 @@ namespace multisig
/**
* INTERNAL
*
+ * brief: check_multisig_config - validate multisig configuration details
+ * 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)
+ */
+ //----------------------------------------------------------------------------------------------------------------------
+ static void check_multisig_config(const std::uint32_t round,
+ const std::uint32_t threshold,
+ const std::uint32_t num_signers)
+ {
+ 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 > 0, "Multisig kex round must be > 0.");
+ CHECK_AND_ASSERT_THROW_MES(round <= multisig_kex_rounds_required(num_signers, threshold) + 1,
+ "Trying to process multisig kex for an invalid round.");
+ }
+ //----------------------------------------------------------------------------------------------------------------------
+ /**
+ * 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().
@@ -224,50 +248,23 @@ namespace multisig
/**
* INTERNAL
*
- * brief: multisig_kex_make_next_msg - Construct a kex msg for any round > 1 of multisig key construction.
+ * brief: multisig_kex_make_round_keys - Makes a kex round's keys.
* - 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)
+ static void multisig_kex_make_round_keys(const crypto::secret_key &base_privkey,
+ multisig_keyset_map_memsafe_t pubkey_origins_map,
+ multisig_keyset_map_memsafe_t &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)
+ for (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
@@ -281,27 +278,29 @@ namespace multisig
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);
+ // note: if working on last kex round, then caller must know how to handle these derivations properly
+ derivation_origins_map_out[rct::rct2pk(derivation_rct)] = std::move(pubkey_and_origins.second);
}
+ }
+ //----------------------------------------------------------------------------------------------------------------------
+ /**
+ * INTERNAL
+ *
+ * brief: check_messages_round - Check that a set of messages have an expected round number.
+ * param: expanded_msgs - set of multisig kex messages to process
+ * param: expected_round - round number the kex messages should have
+ */
+ //----------------------------------------------------------------------------------------------------------------------
+ static void check_messages_round(const std::vector<multisig_kex_msg> &expanded_msgs,
+ const std::uint32_t expected_round)
+ {
+ CHECK_AND_ASSERT_THROW_MES(expanded_msgs.size() > 0, "At least one input message expected.");
+ const std::uint32_t round{expanded_msgs[0].get_round()};
+ CHECK_AND_ASSERT_THROW_MES(round == expected_round, "Messages don't have the expected kex round number.");
- return multisig_kex_msg{round, base_privkey, std::move(msg_pubkeys)};
+ 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.");
}
//----------------------------------------------------------------------------------------------------------------------
/**
@@ -327,19 +326,19 @@ namespace multisig
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)
+ multisig_keyset_map_memsafe_t &sanitized_pubkeys_out)
{
+ // all messages should have the same round (redundant sanity check)
CHECK_AND_ASSERT_THROW_MES(expanded_msgs.size() > 0, "At least one input message expected.");
+ const std::uint32_t round{expanded_msgs[0].get_round()};
+ check_messages_round(expanded_msgs, round);
- 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;
@@ -378,7 +377,7 @@ namespace multisig
*
* 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'.
+ * - Require uniqueness in: '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.
@@ -388,39 +387,21 @@ namespace multisig
* 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: base_pubkey - multisig account's base public 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(
+ static multisig_keyset_map_memsafe_t 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)
{
@@ -429,7 +410,8 @@ namespace multisig
}
// sanitize input messages
- std::uint32_t round = multisig_kex_msgs_sanitize_pubkeys(base_pubkey, expanded_msgs, exclude_pubkeys, pubkey_origins_map);
+ multisig_keyset_map_memsafe_t pubkey_origins_map;
+ const 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 << "]");
@@ -486,10 +468,10 @@ namespace multisig
// - 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);
+ const 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);
+ const std::uint32_t expected_recommendations_self = n_choose_k_f(signers.size() - 1, round - 1);
// note: expected_recommendations_others would be 0 in the last round of 1-of-N, but we return early for that case
CHECK_AND_ASSERT_THROW_MES(expected_recommendations_self > 0 && expected_recommendations_others > 0,
@@ -517,7 +499,60 @@ namespace multisig
/**
* INTERNAL
*
- * brief: multisig_kex_process_round - Process kex messages for the active kex round.
+ * brief: evaluate_multisig_post_kex_round_msgs - Evaluate messages for the post-kex verification round.
+ * - Sanitizes input msgs.
+ * - Requires that only one pubkey is recommended.
+ * - Requires that all signers (other than self) recommend that one pubkey.
+ * param: base_pubkey - multisig account's base public key
+ * param: expected_round - expected kex round of input messages
+ * param: signers - expected participants in multisig kex
+ * param: expanded_msgs - set of multisig kex messages to process
+ * return: sanitized and validated pubkey:origins map
+ */
+ //----------------------------------------------------------------------------------------------------------------------
+ static multisig_keyset_map_memsafe_t evaluate_multisig_post_kex_round_msgs(
+ const crypto::public_key &base_pubkey,
+ const std::uint32_t expected_round,
+ const std::vector<crypto::public_key> &signers,
+ const std::vector<multisig_kex_msg> &expanded_msgs)
+ {
+ // sanitize input messages
+ const std::vector<crypto::public_key> dummy;
+ multisig_keyset_map_memsafe_t pubkey_origins_map;
+ const std::uint32_t round = multisig_kex_msgs_sanitize_pubkeys(base_pubkey, expanded_msgs, dummy, pubkey_origins_map);
+ CHECK_AND_ASSERT_THROW_MES(round == expected_round,
+ "Kex messages were for round [" << round << "], but expected round is [" << expected_round << "]");
+
+ // evaluate pubkeys collected
+
+ // 1) there should only be two pubkeys
+ CHECK_AND_ASSERT_THROW_MES(pubkey_origins_map.size() == 2,
+ "Multisig post-kex round messages from other signers did not all contain two pubkeys.");
+
+ // 2) both keys should be recommended by the same set of signers
+ CHECK_AND_ASSERT_THROW_MES(pubkey_origins_map.begin()->second == (++(pubkey_origins_map.begin()))->second,
+ "Multisig post-kex round messages from other signers did not all recommend the same pubkey pair.");
+
+ // 3) all signers should be present in the recommendation list
+ auto origins = pubkey_origins_map.begin()->second;
+ origins.insert(base_pubkey); //add self
+
+ CHECK_AND_ASSERT_THROW_MES(origins.size() == signers.size(),
+ "Multisig post-kex round message origins don't line up with multisig signer set.");
+
+ for (const crypto::public_key &signer : signers)
+ {
+ CHECK_AND_ASSERT_THROW_MES(origins.find(signer) != origins.end(),
+ "Could not find an expected signer in multisig post-kex round messages (all signers expected).");
+ }
+
+ return pubkey_origins_map;
+ }
+ //----------------------------------------------------------------------------------------------------------------------
+ /**
+ * INTERNAL
+ *
+ * brief: multisig_kex_process_round_msgs - 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
@@ -536,43 +571,62 @@ namespace multisig
* 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,
+ static void multisig_kex_process_round_msgs(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)
+ multisig_keyset_map_memsafe_t &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);
+ check_multisig_config(current_round, threshold, signers.size());
+ const std::uint32_t kex_rounds_required{multisig_kex_rounds_required(signers.size(), threshold)};
- // produce message for next round (if there is one)
- if (current_round < multisig_kex_rounds_required(signers.size(), threshold))
+ // process messages into a [pubkey : {origins}] map
+ multisig_keyset_map_memsafe_t evaluated_pubkeys;
+
+ if (threshold == 1 && current_round == kex_rounds_required)
{
- return multisig_kex_make_next_msg(base_privkey,
- current_round + 1,
- threshold,
- signers.size(),
- evaluated_pubkeys,
- keys_to_origins_map_out);
+ // in the last main kex round of 1-of-N, all signers share a key so the local signer doesn't care about evaluating
+ // recommendations from other signers
}
- else
+ else if (current_round <= kex_rounds_required)
{
- // 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);
+ // for normal kex rounds, fully evaluate kex round messages
+ evaluated_pubkeys = evaluate_multisig_kex_round_msgs(base_pubkey,
+ current_round,
+ signers,
+ expanded_msgs,
+ exclude_pubkeys);
+ }
+ else //(current_round == kex_rounds_required + 1)
+ {
+ // for the post-kex verification round, validate the last kex round's messages
+ evaluated_pubkeys = evaluate_multisig_post_kex_round_msgs(base_pubkey,
+ current_round,
+ signers,
+ expanded_msgs);
+ }
- return multisig_kex_msg{};
+ // prepare keys-to-origins map for updating the multisig account
+ if (current_round < kex_rounds_required)
+ {
+ // normal kex round: make new keys
+ multisig_kex_make_round_keys(base_privkey, std::move(evaluated_pubkeys), keys_to_origins_map_out);
+ }
+ else if (current_round >= kex_rounds_required)
+ {
+ // last kex round: collect the key shares recommended by other signers for the final aggregate key
+ // post-kex verification round: save the keys found in input messages
+ keys_to_origins_map_out = std::move(evaluated_pubkeys);
}
}
//----------------------------------------------------------------------------------------------------------------------
// multisig_account: INTERNAL
//----------------------------------------------------------------------------------------------------------------------
void multisig_account::initialize_kex_update(const std::vector<multisig_kex_msg> &expanded_msgs,
- const std::uint32_t rounds_required,
+ const std::uint32_t kex_rounds_required,
std::vector<crypto::public_key> &exclude_pubkeys_out)
{
if (m_kex_rounds_complete == 0)
@@ -605,7 +659,7 @@ namespace multisig
"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)
+ if (kex_rounds_required == 1)
{
m_multisig_privkeys.clear();
m_multisig_privkeys.emplace_back(m_base_privkey);
@@ -629,13 +683,29 @@ namespace multisig
//----------------------------------------------------------------------------------------------------------------------
// 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)
+ void multisig_account::finalize_kex_update(const std::uint32_t kex_rounds_required,
+ multisig_keyset_map_memsafe_t result_keys_to_origins_map)
{
+ std::vector<crypto::public_key> next_msg_keys;
+
// prepare for next round (or complete the multisig account fully)
- if (rounds_required == m_kex_rounds_complete + 1)
+ if (m_kex_rounds_complete == kex_rounds_required)
+ {
+ // post-kex verification round: check that the multisig pubkey and common pubkey were recommended by other signers
+ CHECK_AND_ASSERT_THROW_MES(result_keys_to_origins_map.count(m_multisig_pubkey) > 0,
+ "Multisig post-kex round: expected multisig pubkey wasn't found in other signers' messages.");
+ CHECK_AND_ASSERT_THROW_MES(result_keys_to_origins_map.count(m_common_pubkey) > 0,
+ "Multisig post-kex round: expected common pubkey wasn't found in other signers' messages.");
+
+ // save keys that should be recommended to other signers
+ // - for convenience, re-recommend the post-kex verification message once an account is complete
+ next_msg_keys.reserve(2);
+ next_msg_keys.push_back(m_multisig_pubkey);
+ next_msg_keys.push_back(m_common_pubkey);
+ }
+ else if (m_kex_rounds_complete + 1 == kex_rounds_required)
{
- // finished (have set of msgs to complete address)
+ // finished with main kex rounds (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;
@@ -652,8 +722,14 @@ namespace multisig
// 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();
+
+ // save keys that should be recommended to other signers
+ // - for post-kex verification, recommend the multisig pubkeys to notify other signers that the local signer is done
+ next_msg_keys.reserve(2);
+ next_msg_keys.push_back(m_multisig_pubkey);
+ next_msg_keys.push_back(m_common_pubkey);
}
- else if (rounds_required == m_kex_rounds_complete + 2)
+ else if (m_kex_rounds_complete + 2 == kex_rounds_required)
{
// 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
@@ -668,6 +744,7 @@ namespace multisig
m_multisig_privkeys.reserve(result_keys_to_origins_map.size());
m_kex_keys_to_origins_map.clear();
+ next_msg_keys.reserve(result_keys_to_origins_map.size());
for (const auto &derivation_and_origins : result_keys_to_origins_map)
{
@@ -679,37 +756,59 @@ namespace multisig
// 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);
+
+ // save keys that should be recommended to other signers
+ // - The keys multisig_key*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().
+ next_msg_keys.push_back(derived_pubkey);
}
}
- else
+ else //(m_kex_rounds_complete + 3 <= kex_rounds_required)
{
// 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]
+ // save keys that should be recommended to other signers
+ // - Send this round's DH derivations to other participants, who will make more DH derivations for the following round.
+ next_msg_keys.reserve(result_keys_to_origins_map.size());
+
+ for (const auto &derivation_and_origins : result_keys_to_origins_map)
+ next_msg_keys.push_back(derivation_and_origins.first);
+
+ // save the account's kex keys for this round [DH derivation : other signers who should 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;
+
+ // make next round's message (or reproduce the post-kex verification round if kex is complete)
+ m_next_round_kex_message = multisig_kex_msg{
+ (m_kex_rounds_complete > kex_rounds_required ? kex_rounds_required : m_kex_rounds_complete) + 1,
+ m_base_privkey,
+ std::move(next_msg_keys)}.get_msg();
}
//----------------------------------------------------------------------------------------------------------------------
// 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.");
+ // check messages are for the expected kex round
+ check_messages_round(expanded_msgs, m_kex_rounds_complete + 1);
+
+ // check kex round count
+ const std::uint32_t kex_rounds_required{multisig_kex_rounds_required(m_signers.size(), m_threshold)};
- 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.");
+ CHECK_AND_ASSERT_THROW_MES(kex_rounds_required > 0, "Multisig kex rounds required unexpectedly 0.");
+ CHECK_AND_ASSERT_THROW_MES(m_kex_rounds_complete < kex_rounds_required + 1,
+ "Multisig kex has already completed all required rounds (including post-kex verification).");
// 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;
+ initialize_kex_update(expanded_msgs, kex_rounds_required, exclude_pubkeys);
- m_next_round_kex_message = multisig_kex_process_round(
+ // process messages into a [pubkey : {origins}] map
+ multisig_keyset_map_memsafe_t result_keys_to_origins_map;
+ multisig_kex_process_round_msgs(
m_base_privkey,
m_base_pubkey,
m_kex_rounds_complete + 1,
@@ -717,10 +816,10 @@ namespace multisig
m_signers,
expanded_msgs,
exclude_pubkeys,
- result_keys_to_origins_map).get_msg();
+ result_keys_to_origins_map);
// finish account update
- finalize_kex_update(rounds_required, std::move(result_keys_to_origins_map));
+ finalize_kex_update(kex_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
index 2bbceb19d..c717e23ad 100644
--- a/src/multisig/multisig_kex_msg.cpp
+++ b/src/multisig/multisig_kex_msg.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2021, The Monero Project
+// Copyright (c) 2021-2022, The Monero Project
//
// All rights reserved.
//
diff --git a/src/multisig/multisig_kex_msg.h b/src/multisig/multisig_kex_msg.h
index 23e3042f2..833c7c8f6 100644
--- a/src/multisig/multisig_kex_msg.h
+++ b/src/multisig/multisig_kex_msg.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2021, The Monero Project
+// Copyright (c) 2021-2022, The Monero Project
//
// All rights reserved.
//
diff --git a/src/multisig/multisig_kex_msg_serialization.h b/src/multisig/multisig_kex_msg_serialization.h
index 9c7b993a7..e5558cdff 100644
--- a/src/multisig/multisig_kex_msg_serialization.h
+++ b/src/multisig/multisig_kex_msg_serialization.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2021, The Monero Project
+// Copyright (c) 2021-2022, The Monero Project
//
// All rights reserved.
//
diff --git a/src/net/zmq.cpp b/src/net/zmq.cpp
index bd7855f21..2b3ca8376 100644
--- a/src/net/zmq.cpp
+++ b/src/net/zmq.cpp
@@ -134,7 +134,7 @@ namespace zmq
{
/* ZMQ documentation states that message parts are atomic - either
all are received or none are. Looking through ZMQ code and
- Github discussions indicates that after part 1 is returned,
+ GitHub discussions indicates that after part 1 is returned,
`EAGAIN` cannot be returned to meet these guarantees. Unit tests
verify (for the `inproc://` case) that this is the behavior.
Therefore, read errors after the first part are treated as a
diff --git a/src/ringct/bulletproofs_plus.cc b/src/ringct/bulletproofs_plus.cc
index 3d27849c1..77b800064 100644
--- a/src/ringct/bulletproofs_plus.cc
+++ b/src/ringct/bulletproofs_plus.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2020, The Monero Project
+// Copyright (c) 2017-2022, The Monero Project
//
// All rights reserved.
//
@@ -644,8 +644,7 @@ try_again:
{
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);
+ sc_muladd(alpha1.bytes, temp2.bytes, gamma[j].bytes, alpha1.bytes);
}
// These are used in the inner product rounds
@@ -706,7 +705,8 @@ try_again:
rct::key challenge_squared;
sc_mul(challenge_squared.bytes, challenge.bytes, challenge.bytes);
- rct::key challenge_squared_inv = invert(challenge_squared);
+ rct::key challenge_squared_inv;
+ sc_mul(challenge_squared_inv.bytes, challenge_inv.bytes, challenge_inv.bytes);
sc_muladd(alpha1.bytes, dL.bytes, challenge_squared.bytes, alpha1.bytes);
sc_muladd(alpha1.bytes, dR.bytes, challenge_squared_inv.bytes, alpha1.bytes);
diff --git a/src/ringct/bulletproofs_plus.h b/src/ringct/bulletproofs_plus.h
index d9084075a..861c54f4f 100644
--- a/src/ringct/bulletproofs_plus.h
+++ b/src/ringct/bulletproofs_plus.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2020, The Monero Project
+// Copyright (c) 2017-2022, The Monero Project
//
// All rights reserved.
//
diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp
index d7883baac..bd67778ec 100644
--- a/src/ringct/rctSigs.cpp
+++ b/src/ringct/rctSigs.cpp
@@ -79,6 +79,7 @@ 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();
@@ -109,6 +110,13 @@ namespace
return rct::BulletproofPlus{rct::keyV(n_outs, I), I, I, I, I, I, I, rct::keyV(nrl, I), rct::keyV(nrl, I)};
}
+
+ rct::clsag make_dummy_clsag(size_t ring_size)
+ {
+ const rct::key I = rct::identity();
+ const size_t n_scalars = ring_size;
+ return rct::clsag{rct::keyV(n_scalars, I), I, I, I};
+ }
}
namespace rct {
@@ -1235,10 +1243,7 @@ namespace rct {
}
for (i = 0; i < outamounts.size(); ++i)
{
- if (plus)
- rv.outPk[i].mask = C[i];
- else
- rv.outPk[i].mask = rct::scalarmult8(C[i]);
+ rv.outPk[i].mask = rct::scalarmult8(C[i]);
outSk[i].mask = masks[i];
}
}
@@ -1276,10 +1281,7 @@ namespace rct {
}
for (i = 0; i < batch_size; ++i)
{
- if (plus)
- rv.outPk[i + amounts_proved].mask = C[i];
- else
- rv.outPk[i + amounts_proved].mask = rct::scalarmult8(C[i]);
+ rv.outPk[i + amounts_proved].mask = rct::scalarmult8(C[i]);
outSk[i + amounts_proved].mask = masks[i];
}
amounts_proved += batch_size;
@@ -1329,7 +1331,10 @@ namespace rct {
{
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);
+ if (hwdev.get_mode() == hw::device::TRANSACTION_CREATE_FAKE)
+ rv.p.CLSAGs[i] = make_dummy_clsag(rv.mixRing[i].size());
+ else
+ rv.p.CLSAGs[i] = proveRctCLSAGSimple(full_message, rv.mixRing[i], inSk[i], a[i], pseudoOuts[i], kLRki ? &(*kLRki)[i]: NULL, msout ? &msout->c[i] : NULL, msout ? &msout->mu_p[i] : NULL, index[i], hwdev);
}
else
{
@@ -1486,10 +1491,7 @@ namespace rct {
rct::keyV masks(rv.outPk.size());
for (size_t i = 0; i < rv.outPk.size(); i++) {
- if (bulletproof_plus)
- masks[i] = rct::scalarmult8(rv.outPk[i].mask);
- else
- masks[i] = rv.outPk[i].mask;
+ masks[i] = rv.outPk[i].mask;
}
key sumOutpks = addKeys(masks);
DP(sumOutpks);
@@ -1649,8 +1651,6 @@ namespace rct {
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;
@@ -1682,8 +1682,6 @@ namespace rct {
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;
diff --git a/src/serialization/list.h b/src/serialization/list.h
deleted file mode 100644
index 16ee1b034..000000000
--- a/src/serialization/list.h
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright (c) 2014-2022, 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 <list>
-
-template <template <bool> class Archive, class T>
-bool do_serialize(Archive<false> &ar, std::list<T> &v);
-template <template <bool> class Archive, class T>
-bool do_serialize(Archive<true> &ar, std::list<T> &v);
-
-namespace serialization
-{
- namespace detail
- {
- template <typename T>
- void do_add(std::list<T> &c, T &&e)
- {
- c.emplace_back(std::forward<T>(e));
- }
- }
-}
-
-#include "serialization.h"
-
-template <template <bool> class Archive, class T>
-bool do_serialize(Archive<false> &ar, std::list<T> &v) { return do_serialize_container(ar, v); }
-template <template <bool> class Archive, class T>
-bool do_serialize(Archive<true> &ar, std::list<T> &v) { return do_serialize_container(ar, v); }
-
diff --git a/src/serialization/serialization.h b/src/serialization/serialization.h
index 590b84428..381d29cfc 100644
--- a/src/serialization/serialization.h
+++ b/src/serialization/serialization.h
@@ -132,13 +132,6 @@ inline bool do_serialize(Archive &ar, bool &v)
return true;
}
-// Never used in the code base
-// #ifndef __GNUC__
-// #ifndef constexpr
-// #define constexpr
-// #endif
-// #endif
-
/* the following add a trait to a set and define the serialization DSL*/
/*! \macro BLOB_SERIALIZER
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp
index f30225e1d..d3e40ab74 100644
--- a/src/simplewallet/simplewallet.cpp
+++ b/src/simplewallet/simplewallet.cpp
@@ -1087,7 +1087,9 @@ bool simple_wallet::make_multisig_main(const std::vector<std::string> &args, boo
auto local_args = args;
local_args.erase(local_args.begin());
std::string multisig_extra_info = m_wallet->make_multisig(orig_pwd_container->password(), local_args, threshold);
- if (!multisig_extra_info.empty())
+ bool ready;
+ m_wallet->multisig(&ready);
+ if (!ready)
{
success_msg_writer() << tr("Another step is needed");
success_msg_writer() << multisig_extra_info;
@@ -1148,7 +1150,7 @@ bool simple_wallet::exchange_multisig_keys_main(const std::vector<std::string> &
return false;
}
- if (args.size() < 2)
+ if (args.size() < 1)
{
PRINT_USAGE(USAGE_EXCHANGE_MULTISIG_KEYS);
return false;
@@ -1157,7 +1159,9 @@ bool simple_wallet::exchange_multisig_keys_main(const std::vector<std::string> &
try
{
std::string multisig_extra_info = m_wallet->exchange_multisig_keys(orig_pwd_container->password(), args);
- if (!multisig_extra_info.empty())
+ bool ready;
+ m_wallet->multisig(&ready);
+ if (!ready)
{
message_writer() << tr("Another step is needed");
message_writer() << multisig_extra_info;
diff --git a/src/wallet/ringdb.cpp b/src/wallet/ringdb.cpp
index bfbbbaeb7..7e4f12f5b 100644
--- a/src/wallet/ringdb.cpp
+++ b/src/wallet/ringdb.cpp
@@ -344,12 +344,15 @@ bool ringdb::remove_rings(const crypto::chacha_key &chacha_key, const cryptonote
return remove_rings(chacha_key, key_images);
}
-bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, std::vector<uint64_t> &outs)
+bool ringdb::get_rings(const crypto::chacha_key &chacha_key, const std::vector<crypto::key_image> &key_images, std::vector<std::vector<uint64_t>> &all_outs)
{
MDB_txn *txn;
int dbr;
bool tx_active = false;
+ all_outs.clear();
+ all_outs.reserve(key_images.size());
+
dbr = resize_env(env, filename.c_str(), 0);
THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set env map size: " + std::string(mdb_strerror(dbr)));
dbr = mdb_txn_begin(env, NULL, 0, &txn);
@@ -357,6 +360,10 @@ bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_im
epee::misc_utils::auto_scope_leave_caller txn_dtor = epee::misc_utils::create_scope_leave_handler([&](){if (tx_active) mdb_txn_abort(txn);});
tx_active = true;
+ for (size_t i = 0; i < key_images.size(); ++i)
+ {
+ const crypto::key_image &key_image = key_images[i];
+
MDB_val key, data;
std::string key_ciphertext = encrypt(key_image, chacha_key, 0);
key.mv_data = (void*)key_ciphertext.data();
@@ -367,6 +374,7 @@ bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_im
return false;
THROW_WALLET_EXCEPTION_IF(data.mv_size <= 0, tools::error::wallet_internal_error, "Invalid ring data size");
+ std::vector<uint64_t> outs;
bool try_v0 = false;
std::string data_plaintext = decrypt(std::string((const char*)data.mv_data, data.mv_size), key_image, chacha_key, 1);
try { outs = decompress_ring(data_plaintext, V1TAG); if (outs.empty()) try_v0 = true; }
@@ -380,6 +388,9 @@ bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_im
MDEBUG("Relative: " << boost::join(outs | boost::adaptors::transformed([](uint64_t out){return std::to_string(out);}), " "));
outs = cryptonote::relative_output_offsets_to_absolute(outs);
MDEBUG("Absolute: " << boost::join(outs | boost::adaptors::transformed([](uint64_t out){return std::to_string(out);}), " "));
+ all_outs.push_back(std::move(outs));
+
+ }
dbr = mdb_txn_commit(txn);
THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to commit txn getting ring from database: " + std::string(mdb_strerror(dbr)));
@@ -387,20 +398,33 @@ bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_im
return true;
}
-bool ringdb::set_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, const std::vector<uint64_t> &outs, bool relative)
+bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, std::vector<uint64_t> &outs)
+{
+ std::vector<std::vector<uint64_t>> all_outs;
+ if (!get_rings(chacha_key, std::vector<crypto::key_image>(1, key_image), all_outs))
+ return false;
+ outs = std::move(all_outs.front());
+ return true;
+}
+
+bool ringdb::set_rings(const crypto::chacha_key &chacha_key, const std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> &rings, bool relative)
{
MDB_txn *txn;
int dbr;
bool tx_active = false;
- dbr = resize_env(env, filename.c_str(), outs.size() * 64);
+ size_t n_outs = 0;
+ for (const auto &e: rings)
+ n_outs += e.second.size();
+ dbr = resize_env(env, filename.c_str(), n_outs * 64);
THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set env map size: " + std::string(mdb_strerror(dbr)));
dbr = mdb_txn_begin(env, NULL, 0, &txn);
THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr)));
epee::misc_utils::auto_scope_leave_caller txn_dtor = epee::misc_utils::create_scope_leave_handler([&](){if (tx_active) mdb_txn_abort(txn);});
tx_active = true;
- store_relative_ring(txn, dbi_rings, key_image, relative ? outs : cryptonote::absolute_output_offsets_to_relative(outs), chacha_key);
+ for (const auto &e: rings)
+ store_relative_ring(txn, dbi_rings, e.first, relative ? e.second : cryptonote::absolute_output_offsets_to_relative(e.second), chacha_key);
dbr = mdb_txn_commit(txn);
THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to commit txn setting ring to database: " + std::string(mdb_strerror(dbr)));
@@ -408,6 +432,13 @@ bool ringdb::set_ring(const crypto::chacha_key &chacha_key, const crypto::key_im
return true;
}
+bool ringdb::set_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, const std::vector<uint64_t> &outs, bool relative)
+{
+ std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> rings;
+ rings.push_back(std::make_pair(key_image, outs));
+ return set_rings(chacha_key, rings, relative);
+}
+
bool ringdb::blackball_worker(const std::vector<std::pair<uint64_t, uint64_t>> &outputs, int op)
{
MDB_txn *txn;
diff --git a/src/wallet/ringdb.h b/src/wallet/ringdb.h
index e9941bf94..bdecdba37 100644
--- a/src/wallet/ringdb.h
+++ b/src/wallet/ringdb.h
@@ -49,7 +49,9 @@ namespace tools
bool remove_rings(const crypto::chacha_key &chacha_key, const std::vector<crypto::key_image> &key_images);
bool remove_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx);
bool get_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, std::vector<uint64_t> &outs);
+ bool get_rings(const crypto::chacha_key &chacha_key, const std::vector<crypto::key_image> &key_images, std::vector<std::vector<uint64_t>> &all_outs);
bool set_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, const std::vector<uint64_t> &outs, bool relative);
+ bool set_rings(const crypto::chacha_key &chacha_key, const std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> &rings, bool relative);
bool blackball(const std::pair<uint64_t, uint64_t> &output);
bool blackball(const std::vector<std::pair<uint64_t, uint64_t>> &outputs);
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index 91521941b..1eeb893c0 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -5125,7 +5125,7 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor
// reconstruct multisig account
crypto::public_key dummy;
- multisig::multisig_account::kex_origins_map_t kex_origins_map;
+ multisig::multisig_keyset_map_memsafe_t kex_origins_map;
for (const auto &derivation : m_multisig_derivations)
kex_origins_map[derivation];
@@ -5138,7 +5138,7 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor
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_account_public_address.m_view_public_key,
m_multisig_rounds_passed,
std::move(kex_origins_map),
""
@@ -5225,7 +5225,10 @@ bool wallet2::multisig(bool *ready, uint32_t *threshold, uint32_t *total) const
if (total)
*total = m_multisig_signers.size();
if (ready)
- *ready = !(get_account().get_keys().m_account_address.m_spend_public_key == rct::rct2pk(rct::identity()));
+ {
+ *ready = !(get_account().get_keys().m_account_address.m_spend_public_key == rct::rct2pk(rct::identity())) &&
+ (m_multisig_rounds_passed == multisig::multisig_kex_rounds_required(m_multisig_signers.size(), m_multisig_threshold) + 1);
+ }
return true;
}
//----------------------------------------------------------------------------------------------------
@@ -7603,6 +7606,14 @@ bool wallet2::get_ring(const crypto::chacha_key &key, const crypto::key_image &k
catch (const std::exception &e) { return false; }
}
+bool wallet2::get_rings(const crypto::chacha_key &key, const std::vector<crypto::key_image> &key_images, std::vector<std::vector<uint64_t>> &outs)
+{
+ if (!m_ringdb)
+ return false;
+ try { return m_ringdb->get_rings(key, key_images, outs); }
+ catch (const std::exception &e) { return false; }
+}
+
bool wallet2::get_rings(const crypto::hash &txid, std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> &outs)
{
for (auto i: m_confirmed_txs)
@@ -7641,6 +7652,15 @@ bool wallet2::set_ring(const crypto::key_image &key_image, const std::vector<uin
catch (const std::exception &e) { return false; }
}
+bool wallet2::set_rings(const std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> &rings, bool relative)
+{
+ if (!m_ringdb)
+ return false;
+
+ try { return m_ringdb->set_rings(get_ringdb_key(), rings, relative); }
+ catch (const std::exception &e) { return false; }
+}
+
bool wallet2::unset_ring(const std::vector<crypto::key_image> &key_images)
{
if (!m_ringdb)
@@ -7815,7 +7835,7 @@ bool wallet2::is_keys_file_locked() const
return m_keys_file_locker->locked();
}
-bool wallet2::tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t global_index, const crypto::public_key& output_public_key, const rct::key& mask, uint64_t real_index, bool unlocked) const
+bool wallet2::tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t global_index, const crypto::public_key& output_public_key, const rct::key& mask, uint64_t real_index, bool unlocked, std::unordered_set<crypto::public_key> &valid_public_keys_cache) const
{
if (!unlocked) // don't add locked outs
return false;
@@ -7826,16 +7846,18 @@ bool wallet2::tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_out
if (std::find(outs.back().begin(), outs.back().end(), item) != outs.back().end()) // don't add duplicates
return false;
// check the keys are valid
- if (!rct::isInMainSubgroup(rct::pk2rct(output_public_key)))
+ if (valid_public_keys_cache.find(output_public_key) == valid_public_keys_cache.end() && !rct::isInMainSubgroup(rct::pk2rct(output_public_key)))
{
MWARNING("Key " << output_public_key << " at index " << global_index << " is not in the main subgroup");
return false;
}
- if (!rct::isInMainSubgroup(mask))
+ valid_public_keys_cache.insert(output_public_key);
+ if (valid_public_keys_cache.find(rct::rct2pk(mask)) == valid_public_keys_cache.end() && !rct::isInMainSubgroup(mask))
{
MWARNING("Commitment " << mask << " at index " << global_index << " is not in the main subgroup");
return false;
}
+ valid_public_keys_cache.insert(rct::rct2pk(mask));
// if (is_output_blackballed(output_public_key)) // don't add blackballed outputs
// return false;
outs.back().push_back(item);
@@ -7878,6 +7900,7 @@ void wallet2::light_wallet_get_outs(std::vector<std::vector<tools::wallet2::get_
MDEBUG("selected transfers size: " << selected_transfers.size());
+ std::unordered_set<crypto::public_key> valid_public_keys_cache;
for(size_t idx: selected_transfers)
{
// Create new index
@@ -7929,7 +7952,7 @@ void wallet2::light_wallet_get_outs(std::vector<std::vector<tools::wallet2::get_
if(!light_wallet_parse_rct_str(ores.amount_outs[amount_key].outputs[i].rct, tx_public_key, 0, mask, rct_commit, false))
rct_commit = rct::zeroCommit(td.amount());
- if (tx_add_fake_output(outs, global_index, tx_public_key, rct_commit, td.m_global_output_index, true)) {
+ if (tx_add_fake_output(outs, global_index, tx_public_key, rct_commit, td.m_global_output_index, true, valid_public_keys_cache)) {
MDEBUG("added fake output " << ores.amount_outs[amount_key].outputs[i].public_key);
MDEBUG("index " << global_index);
}
@@ -7966,12 +7989,12 @@ std::pair<std::set<uint64_t>, size_t> outs_unique(const std::vector<std::vector<
return std::make_pair(std::move(unique), total);
}
-void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count, bool rct)
+void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count, bool rct, std::unordered_set<crypto::public_key> &valid_public_keys_cache)
{
std::vector<uint64_t> rct_offsets;
for (size_t attempts = 3; attempts > 0; --attempts)
{
- get_outs(outs, selected_transfers, fake_outputs_count, rct_offsets);
+ get_outs(outs, selected_transfers, fake_outputs_count, rct_offsets, valid_public_keys_cache);
if (!rct)
return;
@@ -7993,7 +8016,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
THROW_WALLET_EXCEPTION(error::wallet_internal_error, tr("Transaction sanity check failed"));
}
-void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count, std::vector<uint64_t> &rct_offsets)
+void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count, std::vector<uint64_t> &rct_offsets, std::unordered_set<crypto::public_key> &valid_public_keys_cache)
{
LOG_PRINT_L2("fake_outputs_count: " << fake_outputs_count);
outs.clear();
@@ -8037,6 +8060,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request req_t = AUTO_VAL_INIT(req_t);
cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response resp_t = AUTO_VAL_INIT(resp_t);
// request histogram for all outputs, except 0 if we have the rct distribution
+ req_t.amounts.reserve(selected_transfers.size());
for(size_t idx: selected_transfers)
if (!m_transfers[idx].is_rct() || !has_rct_distribution)
req_t.amounts.push_back(m_transfers[idx].is_rct() ? 0 : m_transfers[idx].amount());
@@ -8064,6 +8088,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
{
cryptonote::COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::request req_t = AUTO_VAL_INIT(req_t);
cryptonote::COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::response resp_t = AUTO_VAL_INIT(resp_t);
+ req_t.amounts.reserve(req_t.amounts.size() + selected_transfers.size());
for(size_t idx: selected_transfers)
req_t.amounts.push_back(m_transfers[idx].is_rct() ? 0 : m_transfers[idx].amount());
std::sort(req_t.amounts.begin(), req_t.amounts.end());
@@ -8110,6 +8135,25 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
}
}
+ std::vector<crypto::key_image> ring_key_images;
+ ring_key_images.reserve(selected_transfers.size());
+ std::unordered_map<crypto::key_image, std::vector<uint64_t>> existing_rings;
+ for(size_t idx: selected_transfers)
+ {
+ const transfer_details &td = m_transfers[idx];
+ if (td.m_key_image_known && !td.m_key_image_partial)
+ ring_key_images.push_back(td.m_key_image);
+ }
+ if (!ring_key_images.empty())
+ {
+ std::vector<std::vector<uint64_t>> all_outs;
+ if (get_rings(get_ringdb_key(), ring_key_images, all_outs))
+ {
+ for (size_t i = 0; i < ring_key_images.size(); ++i)
+ existing_rings[ring_key_images[i]] = std::move(all_outs[i]);
+ }
+ }
+
// we ask for more, to have spares if some outputs are still locked
size_t base_requested_outputs_count = (size_t)((fake_outputs_count + 1) * 1.5 + 1);
LOG_PRINT_L2("base_requested_outputs_count: " << base_requested_outputs_count);
@@ -8123,6 +8167,8 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
gamma.reset(new gamma_picker(rct_offsets));
size_t num_selected_transfers = 0;
+ req.outputs.reserve(selected_transfers.size() * (base_requested_outputs_count + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW));
+ daemon_resp.outs.reserve(selected_transfers.size() * (base_requested_outputs_count + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW));
for(size_t idx: selected_transfers)
{
++num_selected_transfers;
@@ -8232,9 +8278,12 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
// if we have a known ring, use it
if (td.m_key_image_known && !td.m_key_image_partial)
{
- std::vector<uint64_t> ring;
- if (get_ring(get_ringdb_key(), td.m_key_image, ring))
+
+ const auto it = existing_rings.find(td.m_key_image);
+ const bool has_ring = it != existing_rings.end();
+ if (has_ring)
{
+ const std::vector<uint64_t> &ring = it->second;
MINFO("This output has a known ring, reusing (size " << ring.size() << ")");
THROW_WALLET_EXCEPTION_IF(ring.size() > fake_outputs_count + 1, error::wallet_internal_error,
"An output in this transaction was previously spent on another chain with ring size " +
@@ -8434,7 +8483,9 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
COMMAND_RPC_GET_OUTPUTS_BIN::request chunk_req = AUTO_VAL_INIT(chunk_req);
COMMAND_RPC_GET_OUTPUTS_BIN::response chunk_daemon_resp = AUTO_VAL_INIT(chunk_daemon_resp);
chunk_req.get_txid = false;
- for (size_t i = 0; i < std::min<size_t>(req.outputs.size() - offset, chunk_size); ++i)
+ const size_t this_chunk_size = std::min<size_t>(req.outputs.size() - offset, chunk_size);
+ chunk_req.outputs.reserve(this_chunk_size);
+ for (size_t i = 0; i < this_chunk_size; ++i)
chunk_req.outputs.push_back(req.outputs[offset + i]);
const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
@@ -8504,9 +8555,10 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
// then pick outs from an existing ring, if any
if (td.m_key_image_known && !td.m_key_image_partial)
{
- std::vector<uint64_t> ring;
- if (get_ring(get_ringdb_key(), td.m_key_image, ring))
+ const auto it = existing_rings.find(td.m_key_image);
+ if (it != existing_rings.end())
{
+ const std::vector<uint64_t> &ring = it->second;
for (uint64_t out: ring)
{
if (out < num_outs)
@@ -8520,7 +8572,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
if (req.outputs[i].index == out)
{
LOG_PRINT_L2("Index " << i << "/" << requested_outputs_count << ": idx " << req.outputs[i].index << " (real " << td.m_global_output_index << "), unlocked " << daemon_resp.outs[i].unlocked << ", key " << daemon_resp.outs[i].key << " (from existing ring)");
- tx_add_fake_output(outs, req.outputs[i].index, daemon_resp.outs[i].key, daemon_resp.outs[i].mask, td.m_global_output_index, daemon_resp.outs[i].unlocked);
+ tx_add_fake_output(outs, req.outputs[i].index, daemon_resp.outs[i].key, daemon_resp.outs[i].mask, td.m_global_output_index, daemon_resp.outs[i].unlocked, valid_public_keys_cache);
found = true;
break;
}
@@ -8545,7 +8597,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
{
size_t i = base + order[o];
LOG_PRINT_L2("Index " << i << "/" << requested_outputs_count << ": idx " << req.outputs[i].index << " (real " << td.m_global_output_index << "), unlocked " << daemon_resp.outs[i].unlocked << ", key " << daemon_resp.outs[i].key);
- tx_add_fake_output(outs, req.outputs[i].index, daemon_resp.outs[i].key, daemon_resp.outs[i].mask, td.m_global_output_index, daemon_resp.outs[i].unlocked);
+ tx_add_fake_output(outs, req.outputs[i].index, daemon_resp.outs[i].key, daemon_resp.outs[i].mask, td.m_global_output_index, daemon_resp.outs[i].unlocked, valid_public_keys_cache);
}
if (outs.back().size() < fake_outputs_count + 1)
{
@@ -8573,6 +8625,8 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
}
// save those outs in the ringdb for reuse
+ std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> rings;
+ rings.reserve(selected_transfers.size());
for (size_t i = 0; i < selected_transfers.size(); ++i)
{
const size_t idx = selected_transfers[i];
@@ -8582,14 +8636,15 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
ring.reserve(outs[i].size());
for (const auto &e: outs[i])
ring.push_back(std::get<0>(e));
- if (!set_ring(td.m_key_image, ring, false))
- MERROR("Failed to set ring for " << td.m_key_image);
+ rings.push_back(std::make_pair(td.m_key_image, std::move(ring)));
}
+ if (!set_rings(rings, false))
+ MERROR("Failed to set rings");
}
template<typename T>
void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::vector<size_t>& selected_transfers, size_t fake_outputs_count,
- std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs,
+ std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, std::unordered_set<crypto::public_key> &valid_public_keys_cache,
uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx,
bool use_view_tags)
{
@@ -8627,7 +8682,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
THROW_WALLET_EXCEPTION_IF(subaddr_account != m_transfers[*i].m_subaddr_index.major, error::wallet_internal_error, "the tx uses funds from multiple accounts");
if (outs.empty())
- get_outs(outs, selected_transfers, fake_outputs_count, false); // may throw
+ get_outs(outs, selected_transfers, fake_outputs_count, false, valid_public_keys_cache); // may throw
//prepare inputs
LOG_PRINT_L2("preparing outputs");
@@ -8751,7 +8806,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
}
void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::vector<size_t>& selected_transfers, size_t fake_outputs_count,
- std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs,
+ std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, std::unordered_set<crypto::public_key> &valid_public_keys_cache,
uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx, const rct::RCTConfig &rct_config, bool use_view_tags)
{
using namespace cryptonote;
@@ -8845,7 +8900,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
THROW_WALLET_EXCEPTION_IF(subaddr_account != m_transfers[*i].m_subaddr_index.major, error::wallet_internal_error, "the tx uses funds from multiple accounts");
if (outs.empty())
- get_outs(outs, selected_transfers, fake_outputs_count, all_rct); // may throw
+ get_outs(outs, selected_transfers, fake_outputs_count, all_rct, valid_public_keys_cache); // may throw
//prepare inputs
LOG_PRINT_L2("preparing outputs");
@@ -9731,6 +9786,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
bulletproof_plus ? 4 : 3
};
const bool use_view_tags = use_fork_rules(get_view_tag_fork(), 0);
+ std::unordered_set<crypto::public_key> valid_public_keys_cache;
const uint64_t base_fee = get_base_fee(priority);
const uint64_t fee_quantization_mask = get_fee_quantization_mask();
@@ -10104,10 +10160,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " outputs and " <<
tx.selected_transfers.size() << " inputs");
if (use_rct)
- transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
+ transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra,
test_tx, test_ptx, rct_config, use_view_tags);
else
- transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
+ transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra,
detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx, use_view_tags);
auto txBlob = t_serializable_object_to_blob(test_ptx.tx);
needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_quantization_mask);
@@ -10129,10 +10185,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
LOG_PRINT_L2("We made a tx, adjusting fee and saving it, we need " << print_money(needed_fee) << " and we have " << print_money(test_ptx.fee));
while (needed_fee > test_ptx.fee) {
if (use_rct)
- transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
+ transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra,
test_tx, test_ptx, rct_config, use_view_tags);
else
- transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
+ transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra,
detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx, use_view_tags);
txBlob = t_serializable_object_to_blob(test_ptx.tx);
needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_quantization_mask);
@@ -10198,6 +10254,7 @@ skip_tx:
tx.selected_transfers, /* const std::list<size_t> selected_transfers */
fake_outs_count, /* CONST size_t fake_outputs_count, */
tx.outs, /* MOD std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, */
+ valid_public_keys_cache,
unlock_time, /* CONST uint64_t unlock_time, */
tx.needed_fee, /* CONST uint64_t fee, */
extra, /* const std::vector<uint8_t>& extra, */
@@ -10210,6 +10267,7 @@ skip_tx:
tx.selected_transfers,
fake_outs_count,
tx.outs,
+ valid_public_keys_cache,
unlock_time,
tx.needed_fee,
extra,
@@ -10327,6 +10385,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below
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 = (base_fee * tx_weight_per_ring) / (use_per_byte_fee ? 1 : 1024);
+ std::unordered_set<crypto::public_key> valid_public_keys_cache;
THROW_WALLET_EXCEPTION_IF(unlocked_balance(subaddr_account, false) == 0, error::wallet_internal_error, "No unlocked balance in the specified account");
@@ -10408,6 +10467,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
hw::device &hwdev = m_account.get_device();
boost::unique_lock<hw::device> hwdev_lock (hwdev);
hw::reset_mode rst(hwdev);
+ std::unordered_set<crypto::public_key> valid_public_keys_cache;
uint64_t accumulated_fee, accumulated_outputs, accumulated_change;
struct TX {
@@ -10510,10 +10570,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " destinations and " <<
tx.selected_transfers.size() << " outputs");
if (use_rct)
- transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
+ transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra,
test_tx, test_ptx, rct_config, use_view_tags);
else
- transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
+ transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra,
detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx, use_view_tags);
auto txBlob = t_serializable_object_to_blob(test_ptx.tx);
needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_quantization_mask);
@@ -10547,10 +10607,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
dt.amount = dt_amount + dt_residue;
}
if (use_rct)
- transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
+ transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra,
test_tx, test_ptx, rct_config, use_view_tags);
else
- transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
+ transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra,
detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx, use_view_tags);
txBlob = t_serializable_object_to_blob(test_ptx.tx);
needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_quantization_mask);
@@ -10586,10 +10646,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
cryptonote::transaction test_tx;
pending_tx test_ptx;
if (use_rct) {
- transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, tx.outs, unlock_time, tx.needed_fee, extra,
+ transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, tx.outs, valid_public_keys_cache, unlock_time, tx.needed_fee, extra,
test_tx, test_ptx, rct_config, use_view_tags);
} else {
- transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, tx.outs, unlock_time, tx.needed_fee, extra,
+ transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, tx.outs, valid_public_keys_cache, unlock_time, tx.needed_fee, extra,
detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx, use_view_tags);
}
auto txBlob = t_serializable_object_to_blob(test_ptx.tx);
@@ -11351,9 +11411,7 @@ void wallet2::check_tx_key_helper(const cryptonote::transaction &tx, const crypt
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 || 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);
+ const rct::key C = tx.rct_signatures.outPk[n].mask;
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");
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index 660e6a14b..e9e5bc95e 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -1001,10 +1001,10 @@ private:
uint64_t unlocked_balance_all(bool strict, uint64_t *blocks_to_unlock = NULL, uint64_t *time_to_unlock = NULL);
template<typename T>
void transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::vector<size_t>& selected_transfers, size_t fake_outputs_count,
- std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs,
+ std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, std::unordered_set<crypto::public_key> &valid_public_keys_cache,
uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx, const bool use_view_tags);
void transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::vector<size_t>& selected_transfers, size_t fake_outputs_count,
- std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs,
+ std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, std::unordered_set<crypto::public_key> &valid_public_keys_cache,
uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx, const rct::RCTConfig &rct_config, const bool use_view_tags);
void commit_tx(pending_tx& ptx_vector);
@@ -1558,7 +1558,9 @@ private:
const std::string get_ring_database() const { return m_ring_database; }
bool get_ring(const crypto::key_image &key_image, std::vector<uint64_t> &outs);
bool get_rings(const crypto::hash &txid, std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> &outs);
+ bool get_rings(const crypto::chacha_key &key, const std::vector<crypto::key_image> &key_images, std::vector<std::vector<uint64_t>> &outs);
bool set_ring(const crypto::key_image &key_image, const std::vector<uint64_t> &outs, bool relative);
+ bool set_rings(const std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> &rings, bool relative);
bool unset_ring(const std::vector<crypto::key_image> &key_images);
bool unset_ring(const crypto::hash &txid);
bool find_and_save_rings(bool force = true);
@@ -1665,9 +1667,9 @@ private:
void set_unspent(size_t idx);
bool is_spent(const transfer_details &td, bool strict = true) const;
bool is_spent(size_t idx, bool strict = true) const;
- void get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count, bool rct);
- void get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count, std::vector<uint64_t> &rct_offsets);
- bool tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t global_index, const crypto::public_key& tx_public_key, const rct::key& mask, uint64_t real_index, bool unlocked) const;
+ void get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count, bool rct, std::unordered_set<crypto::public_key> &valid_public_keys_cache);
+ void get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count, std::vector<uint64_t> &rct_offsets, std::unordered_set<crypto::public_key> &valid_public_keys_cache);
+ bool tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t global_index, const crypto::public_key& tx_public_key, const rct::key& mask, uint64_t real_index, bool unlocked, std::unordered_set<crypto::public_key> &valid_public_keys_cache) const;
bool should_pick_a_second_output(bool use_rct, size_t n_transfers, const std::vector<size_t> &unused_transfers_indices, const std::vector<size_t> &unused_dust_indices) const;
std::vector<size_t> get_only_rct(const std::vector<size_t> &unused_dust_indices, const std::vector<size_t> &unused_transfers_indices) const;
void scan_output(const cryptonote::transaction &tx, bool miner_tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, std::unordered_map<cryptonote::subaddress_index, uint64_t> &tx_money_got_in_outs, std::vector<size_t> &outs, bool pool);
diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp
index 543caac1b..57baf428f 100644
--- a/src/wallet/wallet_rpc_server.cpp
+++ b/src/wallet/wallet_rpc_server.cpp
@@ -4134,7 +4134,8 @@ namespace tools
try
{
res.multisig_info = m_wallet->exchange_multisig_keys(req.password, req.multisig_info);
- if (res.multisig_info.empty())
+ m_wallet->multisig(&ready);
+ if (ready)
{
res.address = m_wallet->get_account().get_public_address_str(m_wallet->nettype());
}