aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/cryptonote_core/blockchain.cpp16
-rw-r--r--src/cryptonote_core/cryptonote_tx_utils.cpp12
-rw-r--r--src/cryptonote_core/cryptonote_tx_utils.h4
-rw-r--r--src/ringct/rctSigs.cpp143
-rw-r--r--src/ringct/rctSigs.h4
-rw-r--r--src/ringct/rctTypes.cpp32
-rw-r--r--src/ringct/rctTypes.h18
-rw-r--r--src/wallet/wallet2.cpp42
-rw-r--r--src/wallet/wallet2.h4
-rw-r--r--tests/core_tests/multisig.cpp2
-rw-r--r--tests/performance_tests/check_tx_signature.h2
-rw-r--r--tests/unit_tests/bulletproofs.cpp63
-rw-r--r--tests/unit_tests/json_serialization.cpp2
13 files changed, 247 insertions, 97 deletions
diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp
index e96dc6bb6..c815b507e 100644
--- a/src/cryptonote_core/blockchain.cpp
+++ b/src/cryptonote_core/blockchain.cpp
@@ -3059,6 +3059,22 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
MERROR_VER("Unsupported rct type: " << rv.type);
return false;
}
+
+ // for bulletproofs, check they're only multi-output after v8
+ if (rv.type == rct::RCTTypeFullBulletproof || rv.type == rct::RCTTypeSimpleBulletproof)
+ {
+ if (hf_version < 8)
+ {
+ for (const rct::Bulletproof &proof: rv.p.bulletproofs)
+ {
+ if (proof.V.size() > 1)
+ {
+ MERROR_VER("Multi output bulletproofs are invalid before v8");
+ return false;
+ }
+ }
+ }
+ }
}
return true;
}
diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp
index 1581f3088..32031e950 100644
--- a/src/cryptonote_core/cryptonote_tx_utils.cpp
+++ b/src/cryptonote_core/cryptonote_tx_utils.cpp
@@ -195,7 +195,7 @@ namespace cryptonote
return addr.m_view_public_key;
}
//---------------------------------------------------------------
- bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct, bool bulletproof, rct::multisig_out *msout, bool shuffle_outs)
+ bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct, rct::RangeProofType range_proof_type, rct::multisig_out *msout, bool shuffle_outs)
{
hw::device &hwdev = sender_account_keys.get_device();
@@ -589,9 +589,9 @@ namespace cryptonote
get_transaction_prefix_hash(tx, tx_prefix_hash);
rct::ctkeyV outSk;
if (use_simple_rct)
- tx.rct_signatures = rct::genRctSimple(rct::hash2rct(tx_prefix_hash), inSk, destinations, inamounts, outamounts, amount_in - amount_out, mixRing, amount_keys, msout ? &kLRki : NULL, msout, index, outSk, bulletproof, hwdev);
+ tx.rct_signatures = rct::genRctSimple(rct::hash2rct(tx_prefix_hash), inSk, destinations, inamounts, outamounts, amount_in - amount_out, mixRing, amount_keys, msout ? &kLRki : NULL, msout, index, outSk, range_proof_type, hwdev);
else
- tx.rct_signatures = rct::genRct(rct::hash2rct(tx_prefix_hash), inSk, destinations, outamounts, mixRing, amount_keys, msout ? &kLRki[0] : NULL, msout, sources[0].real_output, outSk, bulletproof, hwdev); // same index assumption
+ tx.rct_signatures = rct::genRct(rct::hash2rct(tx_prefix_hash), inSk, destinations, outamounts, mixRing, amount_keys, msout ? &kLRki[0] : NULL, msout, sources[0].real_output, outSk, range_proof_type, hwdev); // same index assumption
memwipe(inSk.data(), inSk.size() * sizeof(rct::ctkey));
CHECK_AND_ASSERT_MES(tx.vout.size() == outSk.size(), false, "outSk size does not match vout");
@@ -604,7 +604,7 @@ namespace cryptonote
return true;
}
//---------------------------------------------------------------
- bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct, bool bulletproof, rct::multisig_out *msout)
+ bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct, rct::RangeProofType range_proof_type, rct::multisig_out *msout)
{
hw::device &hwdev = sender_account_keys.get_device();
hwdev.open_tx(tx_key);
@@ -622,7 +622,7 @@ namespace cryptonote
additional_tx_keys.push_back(keypair::generate(sender_account_keys.get_device()).sec);
}
- bool r = construct_tx_with_tx_key(sender_account_keys, subaddresses, sources, destinations, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, rct, bulletproof, msout);
+ bool r = construct_tx_with_tx_key(sender_account_keys, subaddresses, sources, destinations, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, rct, range_proof_type, msout);
hwdev.close_tx();
return r;
}
@@ -634,7 +634,7 @@ namespace cryptonote
crypto::secret_key tx_key;
std::vector<crypto::secret_key> additional_tx_keys;
std::vector<tx_destination_entry> destinations_copy = destinations;
- return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations_copy, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, false, NULL);
+ return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations_copy, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, rct::RangeProofBorromean, NULL);
}
//---------------------------------------------------------------
bool generate_genesis_block(
diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h
index a5d149fca..08e8725f7 100644
--- a/src/cryptonote_core/cryptonote_tx_utils.h
+++ b/src/cryptonote_core/cryptonote_tx_utils.h
@@ -90,8 +90,8 @@ namespace cryptonote
//---------------------------------------------------------------
crypto::public_key get_destination_view_key_pub(const std::vector<tx_destination_entry> &destinations, const boost::optional<cryptonote::account_public_address>& change_addr);
bool construct_tx(const account_keys& sender_account_keys, std::vector<tx_source_entry> &sources, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time);
- bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, bool bulletproof = false, rct::multisig_out *msout = NULL, bool shuffle_outs = true);
- bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, bool bulletproof = false, rct::multisig_out *msout = NULL);
+ bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, rct::RangeProofType range_proof_type = rct::RangeProofBorromean, rct::multisig_out *msout = NULL, bool shuffle_outs = true);
+ bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, rct::RangeProofType range_proof_type = rct::RangeProofBorromean, rct::multisig_out *msout = NULL);
bool generate_genesis_block(
block& bl
diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp
index cc966c44b..6dcb203bf 100644
--- a/src/ringct/rctSigs.cpp
+++ b/src/ringct/rctSigs.cpp
@@ -45,30 +45,6 @@ using namespace std;
#define CHECK_AND_ASSERT_MES_L1(expr, ret, message) {if(!(expr)) {MCERROR("verify", message); return ret;}}
namespace rct {
- bool is_simple(int type)
- {
- switch (type)
- {
- case RCTTypeSimple:
- case RCTTypeSimpleBulletproof:
- return true;
- default:
- return false;
- }
- }
-
- bool is_bulletproof(int type)
- {
- switch (type)
- {
- case RCTTypeSimpleBulletproof:
- case RCTTypeFullBulletproof:
- return true;
- default:
- return false;
- }
- }
-
Bulletproof proveRangeBulletproof(key &C, key &mask, uint64_t amount)
{
mask = rct::skGen();
@@ -78,6 +54,15 @@ namespace rct {
return proof;
}
+ Bulletproof proveRangeBulletproof(keyV &C, keyV &masks, const std::vector<uint64_t> &amounts)
+ {
+ masks = rct::skvGen(amounts.size());
+ Bulletproof proof = bulletproof_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 verBulletproof(const Bulletproof &proof)
{
try { return bulletproof_VERIFY(proof); }
@@ -389,7 +374,7 @@ namespace rct {
std::stringstream ss;
binary_archive<true> ba(ss);
CHECK_AND_ASSERT_THROW_MES(!rv.mixRing.empty(), "Empty mixRing");
- const size_t inputs = is_simple(rv.type) ? rv.mixRing.size() : rv.mixRing[0].size();
+ const size_t inputs = is_rct_simple(rv.type) ? rv.mixRing.size() : rv.mixRing[0].size();
const size_t outputs = rv.ecdhInfo.size();
key prehash;
CHECK_AND_ASSERT_THROW_MES(const_cast<rctSig&>(rv).serialize_rctsig_base(ba, inputs, outputs),
@@ -659,7 +644,8 @@ namespace rct {
// must know the destination private key to find the correct amount, else will return a random number
// Note: For txn fees, the last index in the amounts vector should contain that
// Thus the amounts vector will be "one" longer than the destinations vectort
- rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> & amounts, const ctkeyM &mixRing, const keyV &amount_keys, const multisig_kLRki *kLRki, multisig_out *msout, unsigned int index, ctkeyV &outSk, bool bulletproof, hw::device &hwdev) {
+ rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> & amounts, const ctkeyM &mixRing, const keyV &amount_keys, const multisig_kLRki *kLRki, multisig_out *msout, unsigned int index, ctkeyV &outSk, RangeProofType range_proof_type, hw::device &hwdev) {
+ const bool bulletproof = range_proof_type != RangeProofBorromean;
CHECK_AND_ASSERT_THROW_MES(amounts.size() == destinations.size() || amounts.size() == destinations.size() + 1, "Different number of amounts/destinations");
CHECK_AND_ASSERT_THROW_MES(amount_keys.size() == destinations.size(), "Different number of amount_keys/destinations");
CHECK_AND_ASSERT_THROW_MES(index < mixRing.size(), "Bad index into mixRing");
@@ -672,9 +658,7 @@ namespace rct {
rv.type = bulletproof ? RCTTypeFullBulletproof : RCTTypeFull;
rv.message = message;
rv.outPk.resize(destinations.size());
- if (bulletproof)
- rv.p.bulletproofs.resize(destinations.size());
- else
+ if (!bulletproof)
rv.p.rangeSigs.resize(destinations.size());
rv.ecdhInfo.resize(destinations.size());
@@ -684,18 +668,46 @@ namespace rct {
for (i = 0; i < destinations.size(); i++) {
//add destination to sig
rv.outPk[i].dest = copy(destinations[i]);
- //compute range proof
- if (bulletproof)
- rv.p.bulletproofs[i] = proveRangeBulletproof(rv.outPk[i].mask, outSk[i].mask, amounts[i]);
- else
+ //compute range proof (bulletproofs are done later)
+ if (!bulletproof)
+ {
rv.p.rangeSigs[i] = proveRange(rv.outPk[i].mask, outSk[i].mask, amounts[i]);
#ifdef DBG
- if (bulletproof)
- CHECK_AND_ASSERT_THROW_MES(verBulletproof(rv.p.bulletproofs[i]), "verBulletproof failed on newly created proof");
- else
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)
+ {
+ std::vector<uint64_t> proof_amounts;
+ size_t amounts_proved = 0;
+ while (amounts_proved < amounts.size())
+ {
+ size_t batch_size = 1;
+ if (range_proof_type == RangeProofMultiOutputBulletproof)
+ while (batch_size * 2 + amounts_proved <= amounts.size())
+ batch_size *= 2;
+ rct::keyV C, masks;
+ std::vector<uint64_t> batch_amounts(batch_size);
+ for (i = 0; i < batch_size; ++i)
+ batch_amounts[i] = amounts[i + amounts_proved];
+ rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, batch_amounts));
+ #ifdef DBG
+ 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 = C[i];
+ outSk[i + amounts_proved].mask = masks[i];
+ }
+ amounts_proved += batch_size;
+ }
+ }
+ for (i = 0; i < outSk.size(); ++i)
+ {
//mask amount and mask
rv.ecdhInfo[i].mask = copy(outSk[i].mask);
rv.ecdhInfo[i].amount = d2h(amounts[i]);
@@ -725,12 +737,13 @@ namespace rct {
ctkeyM mixRing;
ctkeyV outSk;
tie(mixRing, index) = populateFromBlockchain(inPk, mixin);
- return genRct(message, inSk, destinations, amounts, mixRing, amount_keys, kLRki, msout, index, outSk, false, hwdev);
+ return genRct(message, inSk, destinations, amounts, mixRing, amount_keys, kLRki, msout, index, outSk, RangeProofBorromean, hwdev);
}
//RCT simple
//for post-rct only
- rctSig genRctSimple(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> &inamounts, const vector<xmr_amount> &outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<multisig_kLRki> *kLRki, multisig_out *msout, const std::vector<unsigned int> & index, ctkeyV &outSk, bool bulletproof, hw::device &hwdev) {
+ rctSig genRctSimple(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> &inamounts, const vector<xmr_amount> &outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<multisig_kLRki> *kLRki, multisig_out *msout, const std::vector<unsigned int> & index, ctkeyV &outSk, RangeProofType range_proof_type, hw::device &hwdev) {
+ const bool bulletproof = 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");
@@ -822,7 +835,7 @@ namespace rct {
mixRing[i].resize(mixin+1);
index[i] = populateFromBlockchainSimple(mixRing[i], inPk[i], mixin);
}
- return genRctSimple(message, inSk, destinations, inamounts, outamounts, txnFee, mixRing, amount_keys, kLRki, msout, index, outSk, false, hwdev);
+ return genRctSimple(message, inSk, destinations, inamounts, outamounts, txnFee, mixRing, amount_keys, kLRki, msout, index, outSk, RangeProofBorromean, hwdev);
}
//RingCT protocol
@@ -838,10 +851,11 @@ namespace rct {
bool verRct(const rctSig & rv, bool semantics) {
PERF_TIMER(verRct);
CHECK_AND_ASSERT_MES(rv.type == RCTTypeFull || rv.type == RCTTypeFullBulletproof, false, "verRct called on non-full rctSig");
+ const bool bulletproof = is_rct_bulletproof(rv.type);
if (semantics)
{
- if (rv.type == RCTTypeFullBulletproof)
- CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.p.bulletproofs.size(), false, "Mismatched sizes of outPk and rv.p.bulletproofs");
+ if (bulletproof)
+ CHECK_AND_ASSERT_MES(rv.outPk.size() == n_bulletproof_amounts(rv.p.bulletproofs), false, "Mismatched sizes of outPk and bulletproofs");
else
CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.p.rangeSigs.size(), false, "Mismatched sizes of outPk and rv.p.rangeSigs");
CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.ecdhInfo.size(), false, "Mismatched sizes of outPk and rv.ecdhInfo");
@@ -858,21 +872,19 @@ namespace rct {
if (semantics) {
tools::threadpool& tpool = tools::threadpool::getInstance();
tools::threadpool::waiter waiter;
- std::deque<bool> results(rv.outPk.size(), false);
+ std::deque<bool> results(bulletproof ? rv.p.bulletproofs.size() : rv.outPk.size(), false);
DP("range proofs verified?");
- for (size_t i = 0; i < rv.outPk.size(); i++) {
- tpool.submit(&waiter, [&, i] {
- if (rv.p.rangeSigs.empty())
- results[i] = verBulletproof(rv.p.bulletproofs[i]);
- else
- results[i] = verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]);
- }, true);
- }
+ if (bulletproof)
+ for (size_t i = 0; i < rv.p.bulletproofs.size(); i++)
+ tpool.submit(&waiter, [&, i] { results[i] = verBulletproof(rv.p.bulletproofs[i]); });
+ else
+ for (size_t i = 0; i < rv.outPk.size(); i++)
+ tpool.submit(&waiter, [&, i] { results[i] = verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]); });
waiter.wait(&tpool);
- for (size_t i = 0; i < rv.outPk.size(); ++i) {
+ for (size_t i = 0; i < results.size(); ++i) {
if (!results[i]) {
- LOG_PRINT_L1("Range proof verified failed for output " << i);
+ LOG_PRINT_L1("Range proof verified failed for proof " << i);
return false;
}
}
@@ -912,11 +924,12 @@ namespace rct {
PERF_TIMER(verRctSimple);
CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeSimpleBulletproof, false, "verRctSimple called on non simple rctSig");
+ const bool bulletproof = is_rct_bulletproof(rv.type);
if (semantics)
{
- if (rv.type == RCTTypeSimpleBulletproof)
+ if (bulletproof)
{
- CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.p.bulletproofs.size(), false, "Mismatched sizes of outPk and rv.p.bulletproofs");
+ CHECK_AND_ASSERT_MES(rv.outPk.size() == n_bulletproof_amounts(rv.p.bulletproofs), false, "Mismatched sizes of outPk and bulletproofs");
CHECK_AND_ASSERT_MES(rv.p.pseudoOuts.size() == rv.p.MGs.size(), false, "Mismatched sizes of rv.p.pseudoOuts and rv.p.MGs");
CHECK_AND_ASSERT_MES(rv.pseudoOuts.empty(), false, "rv.pseudoOuts is not empty");
}
@@ -931,7 +944,7 @@ namespace rct {
else
{
// semantics check is early, and mixRing/MGs aren't resolved yet
- if (rv.type == RCTTypeSimpleBulletproof)
+ if (bulletproof)
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");
@@ -943,7 +956,7 @@ namespace rct {
tools::threadpool& tpool = tools::threadpool::getInstance();
tools::threadpool::waiter waiter;
- const keyV &pseudoOuts = is_bulletproof(rv.type) ? rv.p.pseudoOuts : rv.pseudoOuts;
+ const keyV &pseudoOuts = bulletproof ? rv.p.pseudoOuts : rv.pseudoOuts;
if (semantics) {
key sumOutpks = identity();
@@ -967,20 +980,18 @@ namespace rct {
}
results.clear();
- results.resize(rv.outPk.size());
- for (size_t i = 0; i < rv.outPk.size(); i++) {
- tpool.submit(&waiter, [&, i] {
- if (rv.p.rangeSigs.empty())
- results[i] = verBulletproof(rv.p.bulletproofs[i]);
- else
- results[i] = verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]);
- }, true);
- }
+ results.resize(bulletproof ? rv.p.bulletproofs.size() : rv.outPk.size());
+ if (bulletproof)
+ for (size_t i = 0; i < rv.p.bulletproofs.size(); i++)
+ tpool.submit(&waiter, [&, i] { results[i] = verBulletproof(rv.p.bulletproofs[i]); });
+ else
+ for (size_t i = 0; i < rv.p.rangeSigs.size(); i++)
+ tpool.submit(&waiter, [&, i] { results[i] = verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]); });
waiter.wait(&tpool);
for (size_t i = 0; i < results.size(); ++i) {
if (!results[i]) {
- LOG_PRINT_L1("Range proof verified failed for output " << i);
+ LOG_PRINT_L1("Range proof verified failed for proof " << i);
return false;
}
}
diff --git a/src/ringct/rctSigs.h b/src/ringct/rctSigs.h
index 5a9b2dd44..9a66310c7 100644
--- a/src/ringct/rctSigs.h
+++ b/src/ringct/rctSigs.h
@@ -119,10 +119,10 @@ namespace rct {
//decodeRct: (c.f. https://eprint.iacr.org/2015/1098 section 5.1.1)
// uses the attached ecdh info to find the amounts represented by each output commitment
// must know the destination private key to find the correct amount, else will return a random number
- rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const std::vector<xmr_amount> & amounts, const ctkeyM &mixRing, const keyV &amount_keys, const multisig_kLRki *kLRki, multisig_out *msout, unsigned int index, ctkeyV &outSk, bool bulletproof, hw::device &hwdev);
+ rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const std::vector<xmr_amount> & amounts, const ctkeyM &mixRing, const keyV &amount_keys, const multisig_kLRki *kLRki, multisig_out *msout, unsigned int index, ctkeyV &outSk, RangeProofType range_proof_type, hw::device &hwdev);
rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const std::vector<xmr_amount> & amounts, const keyV &amount_keys, const multisig_kLRki *kLRki, multisig_out *msout, const int mixin, hw::device &hwdev);
rctSig genRctSimple(const key & message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const std::vector<xmr_amount> & inamounts, const std::vector<xmr_amount> & outamounts, const keyV &amount_keys, const std::vector<multisig_kLRki> *kLRki, multisig_out *msout, xmr_amount txnFee, unsigned int mixin, hw::device &hwdev);
- rctSig genRctSimple(const key & message, const ctkeyV & inSk, const keyV & destinations, const std::vector<xmr_amount> & inamounts, const std::vector<xmr_amount> & outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<multisig_kLRki> *kLRki, multisig_out *msout, const std::vector<unsigned int> & index, ctkeyV &outSk, bool bulletproof, hw::device &hwdev);
+ rctSig genRctSimple(const key & message, const ctkeyV & inSk, const keyV & destinations, const std::vector<xmr_amount> & inamounts, const std::vector<xmr_amount> & outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<multisig_kLRki> *kLRki, multisig_out *msout, const std::vector<unsigned int> & index, ctkeyV &outSk, RangeProofType range_proof_type, hw::device &hwdev);
bool verRct(const rctSig & rv, bool semantics);
static inline bool verRct(const rctSig & rv) { return verRct(rv, true) && verRct(rv, false); }
bool verRctSimple(const rctSig & rv, bool semantics);
diff --git a/src/ringct/rctTypes.cpp b/src/ringct/rctTypes.cpp
index 5650b3ba1..72ef75780 100644
--- a/src/ringct/rctTypes.cpp
+++ b/src/ringct/rctTypes.cpp
@@ -209,4 +209,36 @@ namespace rct {
return vali;
}
+ bool is_rct_simple(int type)
+ {
+ switch (type)
+ {
+ case RCTTypeSimple:
+ case RCTTypeSimpleBulletproof:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ bool is_rct_bulletproof(int type)
+ {
+ switch (type)
+ {
+ case RCTTypeSimpleBulletproof:
+ case RCTTypeFullBulletproof:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ size_t n_bulletproof_amounts(const std::vector<Bulletproof> &proofs)
+ {
+ size_t n = 0;
+ for (const Bulletproof &proof: proofs)
+ n += proof.V.size();
+ return n;
+ }
+
}
diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h
index 68f04b0f1..5574c7784 100644
--- a/src/ringct/rctTypes.h
+++ b/src/ringct/rctTypes.h
@@ -213,6 +213,8 @@ namespace rct {
END_SERIALIZE()
};
+ size_t n_bulletproof_amounts(const std::vector<Bulletproof> &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
@@ -227,6 +229,7 @@ namespace rct {
RCTTypeFullBulletproof = 3,
RCTTypeSimpleBulletproof = 4,
};
+ enum RangeProofType { RangeProofBorromean, RangeProofBulletproof, RangeProofMultiOutputBulletproof };
struct rctSigBase {
uint8_t type;
key message;
@@ -310,15 +313,19 @@ namespace rct {
{
ar.tag("bp");
ar.begin_array();
- PREPARE_CUSTOM_VECTOR_SERIALIZATION(outputs, bulletproofs);
- if (bulletproofs.size() != outputs)
+ uint32_t nbp = bulletproofs.size();
+ FIELD(nbp)
+ PREPARE_CUSTOM_VECTOR_SERIALIZATION(nbp, bulletproofs);
+ if (bulletproofs.size() > outputs)
return false;
- for (size_t i = 0; i < outputs; ++i)
+ for (size_t i = 0; i < nbp; ++i)
{
FIELDS(bulletproofs[i])
- if (outputs - i > 1)
+ if (nbp - i > 1)
ar.delimit_array();
}
+ if (n_bulletproof_amounts(bulletproofs) != outputs)
+ return false;
ar.end_array();
}
else
@@ -519,6 +526,9 @@ namespace rct {
//int[64] to uint long long
xmr_amount b2d(bits amountb);
+ bool is_rct_simple(int type);
+ bool is_rct_bulletproof(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; }
static inline const rct::key &ki2rct(const crypto::key_image &ki) { return (const rct::key&)ki; }
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index a2e36706e..fdba6d84a 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -5268,10 +5268,18 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pendin
LOG_PRINT_L1(" " << (n+1) << ": " << sd.sources.size() << " inputs, ring size " << sd.sources[0].outputs.size());
signed_txes.ptx.push_back(pending_tx());
tools::wallet2::pending_tx &ptx = signed_txes.ptx.back();
+ rct::RangeProofType range_proof_type = rct::RangeProofBorromean;
+ if (sd.use_bulletproofs)
+ {
+ range_proof_type = rct::RangeProofBulletproof;
+ for (const rct::Bulletproof &proof: ptx.tx.rct_signatures.p.bulletproofs)
+ if (proof.V.size() > 1)
+ range_proof_type = rct::RangeProofMultiOutputBulletproof;
+ }
crypto::secret_key tx_key;
std::vector<crypto::secret_key> additional_tx_keys;
rct::multisig_out msout;
- bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sd.sources, sd.splitted_dsts, sd.change_dts.addr, sd.extra, ptx.tx, sd.unlock_time, tx_key, additional_tx_keys, sd.use_rct, sd.use_bulletproofs, m_multisig ? &msout : NULL);
+ bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sd.sources, sd.splitted_dsts, sd.change_dts.addr, sd.extra, ptx.tx, sd.unlock_time, tx_key, additional_tx_keys, sd.use_rct, range_proof_type, m_multisig ? &msout : NULL);
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, sd.unlock_time, m_nettype);
// we don't test tx size, because we don't know the current limit, due to not having a blockchain,
// and it's a bit pointless to fail there anyway, since it'd be a (good) guess only. We sign anyway,
@@ -5684,7 +5692,15 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto
cryptonote::transaction tx;
rct::multisig_out msout = ptx.multisig_sigs.front().msout;
auto sources = sd.sources;
- bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources, sd.splitted_dsts, ptx.change_dts.addr, sd.extra, tx, sd.unlock_time, ptx.tx_key, ptx.additional_tx_keys, sd.use_rct, sd.use_bulletproofs, &msout, false);
+ rct::RangeProofType range_proof_type = rct::RangeProofBorromean;
+ if (sd.use_bulletproofs)
+ {
+ range_proof_type = rct::RangeProofBulletproof;
+ for (const rct::Bulletproof &proof: ptx.tx.rct_signatures.p.bulletproofs)
+ if (proof.V.size() > 1)
+ range_proof_type = rct::RangeProofMultiOutputBulletproof;
+ }
+ bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources, sd.splitted_dsts, ptx.change_dts.addr, sd.extra, tx, sd.unlock_time, ptx.tx_key, ptx.additional_tx_keys, sd.use_rct, range_proof_type, &msout, false);
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, sd.unlock_time, m_nettype);
THROW_WALLET_EXCEPTION_IF(get_transaction_prefix_hash (tx) != get_transaction_prefix_hash(ptx.tx),
@@ -7054,7 +7070,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
std::vector<crypto::secret_key> additional_tx_keys;
rct::multisig_out msout;
LOG_PRINT_L2("constructing tx");
- bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, false, m_multisig ? &msout : NULL);
+ bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, rct::RangeProofBulletproof, m_multisig ? &msout : NULL);
LOG_PRINT_L2("constructed tx, r="<<r);
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_nettype);
THROW_WALLET_EXCEPTION_IF(upper_transaction_size_limit <= get_object_blobsize(tx), error::tx_too_big, tx, upper_transaction_size_limit);
@@ -7103,7 +7119,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::vector<size_t>& selected_transfers, size_t fake_outputs_count,
std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs,
- uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx, bool bulletproof)
+ uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx, rct::RangeProofType range_proof_type)
{
using namespace cryptonote;
// throw if attempting a transaction with no destinations
@@ -7259,7 +7275,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
rct::multisig_out msout;
LOG_PRINT_L2("constructing tx");
auto sources_copy = sources;
- bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, true, bulletproof, m_multisig ? &msout : NULL);
+ bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, true, range_proof_type, m_multisig ? &msout : NULL);
LOG_PRINT_L2("constructed tx, r="<<r);
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, dsts, unlock_time, m_nettype);
THROW_WALLET_EXCEPTION_IF(upper_transaction_size_limit <= get_object_blobsize(tx), error::tx_too_big, tx, upper_transaction_size_limit);
@@ -7304,7 +7320,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
LOG_PRINT_L2("Creating supplementary multisig transaction");
cryptonote::transaction ms_tx;
auto sources_copy_copy = sources_copy;
- bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources_copy_copy, splitted_dsts, change_dts.addr, extra, ms_tx, unlock_time,tx_key, additional_tx_keys, true, bulletproof, &msout, false);
+ bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources_copy_copy, splitted_dsts, change_dts.addr, extra, ms_tx, unlock_time,tx_key, additional_tx_keys, true, range_proof_type, &msout, false);
LOG_PRINT_L2("constructed tx, r="<<r);
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_nettype);
THROW_WALLET_EXCEPTION_IF(upper_transaction_size_limit <= get_object_blobsize(tx), error::tx_too_big, tx, upper_transaction_size_limit);
@@ -8010,6 +8026,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
uint64_t upper_transaction_size_limit = get_upper_transaction_size_limit();
const bool use_rct = use_fork_rules(4, 0);
const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0);
+ const rct::RangeProofType range_proof_type = bulletproof ? rct::RangeProofMultiOutputBulletproof : rct::RangeProofBorromean;
const uint64_t fee_per_kb = get_per_kb_fee();
const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm());
@@ -8326,7 +8343,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
tx.selected_transfers.size() << " inputs");
if (use_rct)
transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
- test_tx, test_ptx, bulletproof);
+ test_tx, test_ptx, range_proof_type);
else
transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx);
@@ -8369,7 +8386,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
while (needed_fee > test_ptx.fee) {
if (use_rct)
transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
- test_tx, test_ptx, bulletproof);
+ test_tx, test_ptx, range_proof_type);
else
transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx);
@@ -8442,7 +8459,7 @@ skip_tx:
extra, /* const std::vector<uint8_t>& extra, */
test_tx, /* OUT cryptonote::transaction& tx, */
test_ptx, /* OUT cryptonote::transaction& tx, */
- bulletproof);
+ range_proof_type);
} else {
transfer_selected(tx.dsts,
tx.selected_transfers,
@@ -8581,6 +8598,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
const bool use_rct = fake_outs_count > 0 && use_fork_rules(4, 0);
const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0);
+ const rct::RangeProofType range_proof_type = bulletproof ? rct::RangeProofBulletproof : rct::RangeProofBorromean;
const uint64_t fee_per_kb = get_per_kb_fee();
const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm());
@@ -8636,7 +8654,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
tx.selected_transfers.size() << " outputs");
if (use_rct)
transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
- test_tx, test_ptx, bulletproof);
+ test_tx, test_ptx, range_proof_type);
else
transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx);
@@ -8653,7 +8671,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
tx.dsts[0].amount = available_for_fee - needed_fee;
if (use_rct)
transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
- test_tx, test_ptx, bulletproof);
+ test_tx, test_ptx, range_proof_type);
else
transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx);
@@ -8692,7 +8710,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
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,
- test_tx, test_ptx, bulletproof);
+ test_tx, test_ptx, range_proof_type);
} else {
transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, tx.outs, unlock_time, tx.needed_fee, extra,
detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx);
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index 556679f51..3ba51a27c 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -729,7 +729,7 @@ namespace tools
uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx);
void transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::vector<size_t>& selected_transfers, size_t fake_outputs_count,
std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs,
- uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx, bool bulletproof);
+ uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx, rct::RangeProofType range_proof_type);
void commit_tx(pending_tx& ptx_vector);
void commit_tx(std::vector<pending_tx>& ptx_vector);
@@ -1968,7 +1968,7 @@ namespace tools
crypto::secret_key tx_key;
std::vector<crypto::secret_key> additional_tx_keys;
rct::multisig_out msout;
- bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, false, m_multisig ? &msout : NULL);
+ bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, rct::RangeProofBorromean, m_multisig ? &msout : NULL);
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_nettype);
THROW_WALLET_EXCEPTION_IF(upper_transaction_size_limit <= get_object_blobsize(tx), error::tx_too_big, tx, upper_transaction_size_limit);
diff --git a/tests/core_tests/multisig.cpp b/tests/core_tests/multisig.cpp
index 936a4b742..46cc0ff35 100644
--- a/tests/core_tests/multisig.cpp
+++ b/tests/core_tests/multisig.cpp
@@ -285,7 +285,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, false, 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::RangeProofBorromean, msoutp);
CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction");
#ifndef NO_MULTISIG
diff --git a/tests/performance_tests/check_tx_signature.h b/tests/performance_tests/check_tx_signature.h
index 93a8304be..693ea1866 100644
--- a/tests/performance_tests/check_tx_signature.h
+++ b/tests/performance_tests/check_tx_signature.h
@@ -72,7 +72,7 @@ public:
std::vector<crypto::secret_key> additional_tx_keys;
std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses;
subaddresses[this->m_miners[this->real_source_idx].get_keys().m_account_address.m_spend_public_key] = {0,0};
- if (!construct_tx_and_get_tx_key(this->m_miners[this->real_source_idx].get_keys(), subaddresses, this->m_sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), m_tx, 0, tx_key, additional_tx_keys, rct, bulletproof))
+ if (!construct_tx_and_get_tx_key(this->m_miners[this->real_source_idx].get_keys(), subaddresses, this->m_sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), m_tx, 0, tx_key, additional_tx_keys, rct, bulletproof ? rct::RangeProofMultiOutputBulletproof : rct::RangeProofBorromean))
return false;
get_transaction_prefix_hash(m_tx, m_tx_prefix_hash);
diff --git a/tests/unit_tests/bulletproofs.cpp b/tests/unit_tests/bulletproofs.cpp
index d3b804530..183bb5167 100644
--- a/tests/unit_tests/bulletproofs.cpp
+++ b/tests/unit_tests/bulletproofs.cpp
@@ -31,7 +31,9 @@
#include "gtest/gtest.h"
#include "ringct/rctOps.h"
+#include "ringct/rctSigs.h"
#include "ringct/bulletproofs.h"
+#include "device/device.hpp"
#include "misc_log_ex.h"
TEST(bulletproofs, valid_zero)
@@ -72,6 +74,67 @@ TEST(bulletproofs, valid_multi_random)
}
}
+TEST(bulletproofs, multi_splitting)
+{
+ rct::ctkeyV sc, pc;
+ rct::ctkey sctmp, pctmp;
+
+ std::tie(sctmp, pctmp) = rct::ctskpkGen(6000);
+ sc.push_back(sctmp);
+ pc.push_back(pctmp);
+
+ std::tie(sctmp, pctmp) = rct::ctskpkGen(7000);
+ sc.push_back(sctmp);
+ pc.push_back(pctmp);
+
+ const int mixin = 3, max_outputs = 16;
+
+ for (int n_outputs = 1; n_outputs <= max_outputs; ++n_outputs)
+ {
+ std::vector<uint64_t> amounts;
+ rct::keyV amount_keys;
+ rct::keyV destinations;
+ rct::key Sk, Pk;
+ uint64_t available = 6000 + 7000;
+ uint64_t amount;
+ rct::ctkeyM mixRing(mixin+1);
+
+ //add output
+ for (size_t i = 0; i < n_outputs; ++i)
+ {
+ amount = rct::randXmrAmount(available);
+ amounts.push_back(amount);
+ amount_keys.push_back(rct::hash_to_scalar(rct::zero()));
+ rct::skpkGen(Sk, Pk);
+ destinations.push_back(Pk);
+ available -= amount;
+ }
+ if (!amounts.empty())
+ amounts.back() += available;
+
+ for (size_t j = 0; j <= mixin; ++j)
+ {
+ for (size_t i = 0; i < sc.size(); ++i)
+ {
+ if (j == 1)
+ mixRing[j].push_back(pc[i]);
+ else
+ mixRing[j].push_back({rct::scalarmultBase(rct::skGen()), rct::scalarmultBase(rct::skGen())});
+ }
+ }
+
+ rct::ctkeyV outSk;
+ rct::rctSig s = rct::genRct(rct::zero(), sc, destinations, amounts, mixRing, amount_keys, NULL, NULL, 1, outSk, rct::RangeProofMultiOutputBulletproof, hw::get_device("default"));
+ ASSERT_TRUE(rct::verRct(s));
+ for (size_t i = 0; i < n_outputs; ++i)
+ {
+ rct::key mask;
+ rct::decodeRct(s, amount_keys[i], i, mask, hw::get_device("default"));
+ ASSERT_TRUE(mask == outSk[i].mask);
+ }
+ }
+}
+
TEST(bulletproofs, invalid_8)
{
diff --git a/tests/unit_tests/json_serialization.cpp b/tests/unit_tests/json_serialization.cpp
index ac6b60846..234cb2c33 100644
--- a/tests/unit_tests/json_serialization.cpp
+++ b/tests/unit_tests/json_serialization.cpp
@@ -75,7 +75,7 @@ namespace
std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses;
subaddresses[from.m_account_address.m_spend_public_key] = {0,0};
- if (!cryptonote::construct_tx_and_get_tx_key(from, subaddresses, actual_sources, to, boost::none, {}, tx, 0, tx_key, extra_keys, rct, bulletproof))
+ if (!cryptonote::construct_tx_and_get_tx_key(from, subaddresses, actual_sources, to, boost::none, {}, tx, 0, tx_key, extra_keys, rct, bulletproof ? rct::RangeProofBulletproof : rct::RangeProofBorromean))
throw std::runtime_error{"transaction construction error"};
return tx;