diff options
-rw-r--r-- | src/cryptonote_core/blockchain.cpp | 212 | ||||
-rw-r--r-- | src/cryptonote_core/cryptonote_basic.h | 12 | ||||
-rw-r--r-- | src/cryptonote_core/cryptonote_boost_serialization.h | 21 | ||||
-rw-r--r-- | src/cryptonote_core/cryptonote_format_utils.cpp | 86 | ||||
-rw-r--r-- | src/ringct/rctSigs.cpp | 84 | ||||
-rw-r--r-- | src/ringct/rctSigs.h | 20 | ||||
-rw-r--r-- | src/ringct/rctTypes.h | 39 | ||||
-rw-r--r-- | src/wallet/wallet2.cpp | 30 | ||||
-rw-r--r-- | tests/core_tests/rct.cpp | 5 | ||||
-rw-r--r-- | tests/unit_tests/ringct.cpp | 64 | ||||
-rw-r--r-- | tests/unit_tests/serialization.cpp | 3 |
11 files changed, 394 insertions, 182 deletions
diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index bef880627..456a78eaf 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -2462,95 +2462,193 @@ bool Blockchain::check_tx_inputs(const transaction& tx, tx_verification_context else { // from version 2, check ringct signatures - rct::ctkeyM reconstructed_mixRing; - rct::keyV reconstructed_II; - - // if the tx already has a non empty mixRing and/or II, use them, - // else reconstruct them - const rct::ctkeyM &mixRing = tx.rct_signatures.mixRing.empty() ? reconstructed_mixRing : tx.rct_signatures.mixRing; - const rct::keyV &II = tx.rct_signatures.MG.II.size() == 1 ? reconstructed_II : tx.rct_signatures.MG.II; - - // RCT needs the same mixin for all inputs - for (size_t n = 1; n < pubkeys.size(); ++n) + // obviously, the original and simple rct APIs use a mixRing that's indexes + // in opposite orders, because it'd be too simple otherwise... + if (tx.rct_signatures.simple) { - if (pubkeys[n].size() != pubkeys[0].size()) - { - LOG_PRINT_L1("Failed to check ringct signatures: mismatched ring sizes"); - return false; - } - } + rct::ctkeyM reconstructed_mixRing; + std::vector<rct::keyV> reconstructed_II; - if (tx.rct_signatures.mixRing.empty()) - { - reconstructed_mixRing.resize(pubkeys[0].size()); - for (size_t n = 0; n < pubkeys.size(); ++n) + // if the tx already has a non empty mixRing, use them, + // else reconstruct them + const rct::ctkeyM &mixRing = tx.rct_signatures.mixRing.empty() ? reconstructed_mixRing : tx.rct_signatures.mixRing; + // always do II, because it's split in the simple version + + // all MGs should have the same II size (1) + for (size_t n = 0; n < tx.rct_signatures.MGs.size(); ++n) { - for (size_t m = 0; m < pubkeys[n].size(); ++m) + if (tx.rct_signatures.MGs[n].II.size() != 1) { - reconstructed_mixRing[m].push_back(pubkeys[n][m]); + LOG_PRINT_L1("Failed to check ringct signatures: mismatched MGs II sizes"); + return false; } } - } - if (tx.rct_signatures.MG.II.size() == 1) - { reconstructed_II.resize(tx.vin.size()); for (size_t n = 0; n < tx.vin.size(); ++n) { - reconstructed_II[n] = rct::ki2rct(boost::get<txin_to_key>(tx.vin[n]).k_image); + reconstructed_II[n].push_back(rct::ki2rct(boost::get<txin_to_key>(tx.vin[n]).k_image)); + reconstructed_II[n].push_back(tx.rct_signatures.MGs[n].II[0]); } - reconstructed_II.push_back(tx.rct_signatures.MG.II.back()); - } - // check all this, either recontructed (so should really pass), or not - { - bool size_matches = true; - for (size_t i = 0; i < pubkeys.size(); ++i) - size_matches &= pubkeys[i].size() == mixRing.size(); - for (size_t i = 0; i < tx.rct_signatures.mixRing.size(); ++i) - size_matches &= pubkeys.size() == mixRing[i].size(); - if (!size_matches) + if (tx.rct_signatures.mixRing.empty()) { - LOG_PRINT_L1("Failed to check ringct signatures: mismatched pubkeys/mixRing size"); - return false; + reconstructed_mixRing.resize(pubkeys.size()); + for (size_t n = 0; n < pubkeys.size(); ++n) + { + for (size_t m = 0; m < pubkeys[n].size(); ++m) + { + reconstructed_mixRing[n].push_back(pubkeys[n][m]); + } + } } - for (size_t n = 0; n < pubkeys.size(); ++n) + // check all this, either recontructed (so should really pass), or not { - for (size_t m = 0; m < pubkeys[n].size(); ++m) + if (pubkeys.size() != mixRing.size()) { - if (pubkeys[n][m].dest != rct::rct2pk(mixRing[m][n].dest)) + LOG_PRINT_L1("Failed to check ringct signatures: mismatched pubkeys/mixRing size"); + return false; + } + for (size_t i = 0; i < pubkeys.size(); ++i) + { + if (pubkeys[i].size() != mixRing[i].size()) { - LOG_PRINT_L1("Failed to check ringct signatures: mismatched pubkey at vin " << n << ", index " << m); + LOG_PRINT_L1("Failed to check ringct signatures: mismatched pubkeys/mixRing size"); return false; } - if (pubkeys[n][m].mask != rct::rct2pk(mixRing[m][n].mask)) + } + + for (size_t n = 0; n < pubkeys.size(); ++n) + { + for (size_t m = 0; m < pubkeys[n].size(); ++m) { - LOG_PRINT_L1("Failed to check ringct signatures: mismatched commitment at vin " << n << ", index " << m); - return false; + if (pubkeys[n][m].dest != rct::rct2pk(mixRing[n][m].dest)) + { + LOG_PRINT_L1("Failed to check ringct signatures: mismatched pubkey at vin " << n << ", index " << m); + return false; + } + if (pubkeys[n][m].mask != rct::rct2pk(mixRing[n][m].mask)) + { + LOG_PRINT_L1("Failed to check ringct signatures: mismatched commitment at vin " << n << ", index " << m); + return false; + } } } } - } - if (II.size() != 1 + tx.vin.size()) - { - LOG_PRINT_L1("Failed to check ringct signatures: mismatched II/vin sizes"); - return false; + if (tx.rct_signatures.MGs.size() != tx.vin.size()) + { + LOG_PRINT_L1("Failed to check ringct signatures: mismatched MGs/vin sizes"); + return false; + } + for (size_t n = 0; n < tx.vin.size(); ++n) + { + if (memcmp(&boost::get<txin_to_key>(tx.vin[n]).k_image, &reconstructed_II[n][0], 32)) + { + LOG_PRINT_L1("Failed to check ringct signatures: mismatched key image"); + return false; + } + } + + if (!rct::verRctSimple(tx.rct_signatures, mixRing, &reconstructed_II, rct::hash2rct(tx_prefix_hash))) + { + LOG_PRINT_L1("Failed to check ringct signatures!"); + return false; + } } - for (size_t n = 0; n < tx.vin.size(); ++n) + else { - if (memcmp(&boost::get<txin_to_key>(tx.vin[n]).k_image, &II[n], 32)) + rct::ctkeyM reconstructed_mixRing; + rct::keyV reconstructed_II; + + // if the tx already has a non empty mixRing and/or II, use them, + // else reconstruct them + const rct::ctkeyM &mixRing = tx.rct_signatures.mixRing.empty() ? reconstructed_mixRing : tx.rct_signatures.mixRing; + const rct::keyV &II = tx.rct_signatures.MG.II.size() == 1 ? reconstructed_II : tx.rct_signatures.MG.II; + + // RCT needs the same mixin for all inputs + for (size_t n = 1; n < pubkeys.size(); ++n) + { + if (pubkeys[n].size() != pubkeys[0].size()) + { + LOG_PRINT_L1("Failed to check ringct signatures: mismatched ring sizes"); + return false; + } + } + + if (tx.rct_signatures.mixRing.empty()) + { + reconstructed_mixRing.resize(pubkeys[0].size()); + for (size_t n = 0; n < pubkeys.size(); ++n) + { + for (size_t m = 0; m < pubkeys[n].size(); ++m) + { + reconstructed_mixRing[m].push_back(pubkeys[n][m]); + } + } + } + + if (tx.rct_signatures.MG.II.size() == 1) + { + reconstructed_II.resize(tx.vin.size()); + for (size_t n = 0; n < tx.vin.size(); ++n) + { + reconstructed_II[n] = rct::ki2rct(boost::get<txin_to_key>(tx.vin[n]).k_image); + } + reconstructed_II.push_back(tx.rct_signatures.MG.II.back()); + } + + // check all this, either recontructed (so should really pass), or not + { + bool size_matches = true; + for (size_t i = 0; i < pubkeys.size(); ++i) + size_matches &= pubkeys[i].size() == mixRing.size(); + for (size_t i = 0; i < tx.rct_signatures.mixRing.size(); ++i) + size_matches &= pubkeys.size() == mixRing[i].size(); + if (!size_matches) + { + LOG_PRINT_L1("Failed to check ringct signatures: mismatched pubkeys/mixRing size"); + return false; + } + + for (size_t n = 0; n < pubkeys.size(); ++n) + { + for (size_t m = 0; m < pubkeys[n].size(); ++m) + { + if (pubkeys[n][m].dest != rct::rct2pk(mixRing[m][n].dest)) + { + LOG_PRINT_L1("Failed to check ringct signatures: mismatched pubkey at vin " << n << ", index " << m); + return false; + } + if (pubkeys[n][m].mask != rct::rct2pk(mixRing[m][n].mask)) + { + LOG_PRINT_L1("Failed to check ringct signatures: mismatched commitment at vin " << n << ", index " << m); + return false; + } + } + } + } + + if (II.size() != 1 + tx.vin.size()) { LOG_PRINT_L1("Failed to check ringct signatures: mismatched II/vin sizes"); return false; } - } + for (size_t n = 0; n < tx.vin.size(); ++n) + { + if (memcmp(&boost::get<txin_to_key>(tx.vin[n]).k_image, &II[n], 32)) + { + LOG_PRINT_L1("Failed to check ringct signatures: mismatched II/vin sizes"); + return false; + } + } - if (!rct::verRct(tx.rct_signatures, mixRing, II, rct::hash2rct(tx_prefix_hash))) - { - LOG_PRINT_L1("Failed to check ringct signatures!"); - return false; + if (!rct::verRct(tx.rct_signatures, mixRing, II, rct::hash2rct(tx_prefix_hash))) + { + LOG_PRINT_L1("Failed to check ringct signatures!"); + return false; + } } } return true; diff --git a/src/cryptonote_core/cryptonote_basic.h b/src/cryptonote_core/cryptonote_basic.h index 91bcef8c5..afe785eb9 100644 --- a/src/cryptonote_core/cryptonote_basic.h +++ b/src/cryptonote_core/cryptonote_basic.h @@ -231,11 +231,19 @@ namespace cryptonote else { FIELD(rct_signatures) - for (size_t i = 0; i < rct_signatures.mixRing.size(); ++i) + if (rct_signatures.simple) { - if (rct_signatures.mixRing[i].size() != vin.size()) + if (rct_signatures.mixRing.size() && rct_signatures.mixRing.size() != vin.size()) return false; } + else + { + for (size_t i = 0; i < rct_signatures.mixRing.size(); ++i) + { + if (rct_signatures.mixRing[i].size() != vin.size()) + return false; + } + } } END_SERIALIZE() diff --git a/src/cryptonote_core/cryptonote_boost_serialization.h b/src/cryptonote_core/cryptonote_boost_serialization.h index 01239c5ae..35fabe7fb 100644 --- a/src/cryptonote_core/cryptonote_boost_serialization.h +++ b/src/cryptonote_core/cryptonote_boost_serialization.h @@ -224,23 +224,16 @@ namespace boost template <class Archive> inline void serialize(Archive &a, rct::rctSig &x, const boost::serialization::version_type ver) { - a & x.rangeSigs; - a & x.MG; - // a & x.mixRing; mixRing is not serialized, as it can be reconstructed from the offsets - a & x.ecdhInfo; - a & x.outPk; - a & x.txnFee; - // a & x.bash_hash; bash_hash is not serialized, as it can be reconstructed from the tx data - } - - template <class Archive> - inline void serialize(Archive &a, rct::sRctSig &x, const boost::serialization::version_type ver) - { + a & x.simple; // a & x.message; message is not serialized, as it can be reconstructed from the tx data a & x.rangeSigs; - a & x.MG; + if (x.simple) + a & x.MGs; + else + a & x.MG; // a & x.mixRing; mixRing is not serialized, as it can be reconstructed from the offsets - a & x.pseudoOuts; + if (x.simple) + a & x.pseudoOuts; a & x.ecdhInfo; a & x.outPk; a & x.txnFee; diff --git a/src/cryptonote_core/cryptonote_format_utils.cpp b/src/cryptonote_core/cryptonote_format_utils.cpp index 4cc62c165..6578776b4 100644 --- a/src/cryptonote_core/cryptonote_format_utils.cpp +++ b/src/cryptonote_core/cryptonote_format_utils.cpp @@ -516,19 +516,6 @@ namespace cryptonote }; std::vector<input_generation_context_data> in_contexts; - if (tx.version > 1) - { - // ringct requires all real inputs to be at the same index for all inputs // TODO - BOOST_FOREACH(const tx_source_entry& src_entr, sources) - { - if(src_entr.real_output != sources.begin()->real_output) - { - LOG_ERROR("All inputs must have the same index for ringct"); - return false; - } - } - } - uint64_t summary_inputs_money = 0; //fill inputs BOOST_FOREACH(const tx_source_entry& src_entr, sources) @@ -641,24 +628,46 @@ namespace cryptonote } else { - // enforce same mixin for all outputs - size_t n_total_outs = sources[0].outputs.size(); - for (size_t i = 1; i < sources.size(); ++i) { - if (n_total_outs != sources[i].outputs.size()) { - LOG_ERROR("Ringct transaction has varying mixin"); - return false; + bool all_rct_inputs = true; + size_t n_total_outs = sources[0].outputs.size(); // only for non-simple rct + BOOST_FOREACH(const tx_source_entry& src_entr, sources) + all_rct_inputs &= !(src_entr.mask == rct::identity()); + bool use_simple_rct = all_rct_inputs; + + if (!use_simple_rct) + { + // non simple ringct requires all real inputs to be at the same index for all inputs + BOOST_FOREACH(const tx_source_entry& src_entr, sources) + { + if(src_entr.real_output != sources.begin()->real_output) + { + LOG_ERROR("All inputs must have the same index for non-simple ringct"); + return false; + } + } + + // enforce same mixin for all outputs + for (size_t i = 1; i < sources.size(); ++i) { + if (n_total_outs != sources[i].outputs.size()) { + LOG_ERROR("Non-simple ringct transaction has varying mixin"); + return false; + } } } uint64_t amount_in = 0, amount_out = 0; rct::ctkeyV inSk; - rct::ctkeyM mixRing(n_total_outs); + // mixRing indexing is done the other way round for simple + rct::ctkeyM mixRing(use_simple_rct ? sources.size() : n_total_outs); rct::keyV destinations; - std::vector<uint64_t> amounts; + std::vector<uint64_t> inamounts, outamounts; + std::vector<unsigned int> index; for (size_t i = 0; i < sources.size(); ++i) { rct::ctkey ctkey; amount_in += sources[i].amount; + inamounts.push_back(sources[i].amount); + index.push_back(sources[i].real_output); // inSk: (secret key, mask) ctkey.dest = rct::sk2rct(in_contexts[i].in_ephemeral.sec); ctkey.mask = sources[i].mask; @@ -669,21 +678,37 @@ namespace cryptonote for (size_t i = 0; i < tx.vout.size(); ++i) { destinations.push_back(rct::pk2rct(boost::get<txout_to_key>(tx.vout[i].target).key)); - amounts.push_back(tx.vout[i].amount); + outamounts.push_back(tx.vout[i].amount); amount_out += tx.vout[i].amount; } - for (size_t i = 0; i < n_total_outs; ++i) // same index assumption + + if (use_simple_rct) + { + // mixRing indexing is done the other way round for simple + for (size_t i = 0; i < sources.size(); ++i) + { + mixRing[i].resize(sources[i].outputs.size()); + for (size_t n = 0; n < sources[i].outputs.size(); ++n) + { + mixRing[i][n] = sources[i].outputs[n].second; + } + } + } + else { - mixRing[i].resize(sources.size()); - for (size_t n = 0; n < sources.size(); ++n) + for (size_t i = 0; i < n_total_outs; ++i) // same index assumption { - mixRing[i][n] = sources[n].outputs[i].second; + mixRing[i].resize(sources.size()); + for (size_t n = 0; n < sources.size(); ++n) + { + mixRing[i][n] = sources[n].outputs[i].second; + } } } // fee - if (amount_in > amount_out) - amounts.push_back(amount_in - amount_out); + if (!use_simple_rct && amount_in > amount_out) + outamounts.push_back(amount_in - amount_out); // zero out all amounts to mask rct outputs, real amounts are now encrypted for (size_t i = 0; i < tx.vin.size(); ++i) @@ -696,7 +721,10 @@ namespace cryptonote crypto::hash tx_prefix_hash; get_transaction_prefix_hash(tx, tx_prefix_hash); - tx.rct_signatures = rct::genRct(inSk, destinations, amounts, mixRing, rct::hash2rct(tx_prefix_hash), sources[0].real_output); // same index assumption + if (use_simple_rct) + tx.rct_signatures = rct::genRctSimple(rct::hash2rct(tx_prefix_hash), inSk, destinations, inamounts, outamounts, amount_in - amount_out, mixRing, index); + else + tx.rct_signatures = rct::genRct(rct::hash2rct(tx_prefix_hash), inSk, destinations, outamounts, mixRing, sources[0].real_output); // same index assumption LOG_PRINT2("construct_tx.log", "transaction_created: " << get_transaction_hash(tx) << ENDL << obj_to_json_str(tx) << ENDL, LOG_LEVEL_3); } diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index 7fcb8e158..687373fe5 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -329,7 +329,7 @@ namespace rct { // this shows that sum inputs = sum outputs //Ver: // verifies the above sig is created corretly - mgSig proveRctMG(const ctkeyM & pubs, const ctkeyV & inSk, const ctkeyV &outSk, const ctkeyV & outPk, unsigned int index, key txnFeeKey, const key &base_hash) { + mgSig proveRctMG(const key &message, const ctkeyM & pubs, const ctkeyV & inSk, const ctkeyV &outSk, const ctkeyV & outPk, unsigned int index, key txnFeeKey) { mgSig mg; //setup vars size_t cols = pubs.size(); @@ -374,9 +374,9 @@ namespace rct { sc_sub(sk[rows].bytes, sk[rows].bytes, outSk[j].mask.bytes); //subtract output masks in last row.. } ctkeyV signed_data = outPk; - signed_data.push_back(ctkey({base_hash, identity()})); - key message = cn_fast_hash(signed_data); - return MLSAG_Gen(message, M, sk, index); + signed_data.push_back(ctkey({message, identity()})); + key msg = cn_fast_hash(signed_data); + return MLSAG_Gen(msg, M, sk, index); } @@ -415,7 +415,7 @@ namespace rct { // this shows that sum inputs = sum outputs //Ver: // verifies the above sig is created corretly - bool verRctMG(mgSig mg, const keyV &II, const ctkeyM & pubs, const ctkeyV & outPk, key txnFeeKey, const key &base_hash) { + bool verRctMG(mgSig mg, const keyV &II, const ctkeyM & pubs, const ctkeyV & outPk, key txnFeeKey, const key &message) { //setup vars size_t cols = pubs.size(); CHECK_AND_ASSERT_MES(cols >= 1, false, "Empty pubs"); @@ -447,11 +447,11 @@ namespace rct { subKeys(M[i][rows], M[i][rows], txnFeeKey); } ctkeyV signed_data = outPk; - signed_data.push_back(ctkey({base_hash, identity()})); - key message = cn_fast_hash(signed_data); + signed_data.push_back(ctkey({message, identity()})); + key msg = cn_fast_hash(signed_data); DP("message:"); - DP(message); - return MLSAG_Ver(message, M, mg, II); + DP(msg); + return MLSAG_Ver(msg, M, mg, II); } //Ring-ct Simple MG sigs @@ -535,7 +535,7 @@ 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 ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> amounts, const ctkeyM &mixRing, const key &base_hash, unsigned int index) { + rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> & amounts, const ctkeyM &mixRing, unsigned int index) { CHECK_AND_ASSERT_THROW_MES(amounts.size() == destinations.size() || amounts.size() == destinations.size() + 1, "Different number of amounts/destinations"); CHECK_AND_ASSERT_THROW_MES(index < mixRing.size(), "Bad index into mixRing"); for (size_t n = 0; n < mixRing.size(); ++n) { @@ -543,6 +543,7 @@ namespace rct { } rctSig rv; + rv.simple = false; rv.outPk.resize(destinations.size()); rv.rangeSigs.resize(destinations.size()); rv.ecdhInfo.resize(destinations.size()); @@ -578,23 +579,22 @@ namespace rct { key txnFeeKey = scalarmultH(d2h(rv.txnFee)); rv.mixRing = mixRing; - rv.base_hash = base_hash; - rv.MG = proveRctMG(rv.mixRing, inSk, outSk, rv.outPk, index, txnFeeKey, base_hash); + rv.message = message; + rv.MG = proveRctMG(message, rv.mixRing, inSk, outSk, rv.outPk, index, txnFeeKey); return rv; } - rctSig genRct(const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> amounts, const key &base_hash, const int mixin) { + rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> & amounts, const int mixin) { unsigned int index; ctkeyM mixRing; tie(mixRing, index) = populateFromBlockchain(inPk, mixin); - return genRct(inSk, destinations, amounts, mixRing, base_hash, index); + return genRct(message, inSk, destinations, amounts, mixRing, index); } //RCT simple //for post-rct only - sRctSig genRctSimple(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> &inamounts, const vector<xmr_amount> &outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const std::vector<unsigned int> & index) { + 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 std::vector<unsigned int> & index) { CHECK_AND_ASSERT_THROW_MES(inamounts.size() > 0, "Empty inamounts"); - CHECK_AND_ASSERT_THROW_MES(inPk.size() == inSk.size(), "Different number of inPk/inSk"); 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"); CHECK_AND_ASSERT_THROW_MES(index.size() == inSk.size(), "Different number of index/inSk"); @@ -603,7 +603,8 @@ namespace rct { CHECK_AND_ASSERT_THROW_MES(index[n] < mixRing[n].size(), "Bad index into mixRing"); } - sRctSig rv; + rctSig rv; + rv.simple = true; rv.message = message; rv.outPk.resize(destinations.size()); rv.rangeSigs.resize(destinations.size()); @@ -637,24 +638,24 @@ namespace rct { // key txnFeeKey = scalarmultH(d2h(rv.txnFee)); rv.mixRing = mixRing; rv.pseudoOuts.resize(inamounts.size()); - rv.MG.resize(inamounts.size()); + rv.MGs.resize(inamounts.size()); key sumpouts = zero(); //sum pseudoOut masks key a; for (i = 0 ; i < inamounts.size() - 1; i++) { skGen(a); sc_add(sumpouts.bytes, a.bytes, sumpouts.bytes); genC(rv.pseudoOuts[i], a, inamounts[i]); - rv.MG[i] = proveRctMGSimple(message, rv.mixRing[i], inSk[i], a, rv.pseudoOuts[i], index[i]); + rv.MGs[i] = proveRctMGSimple(message, rv.mixRing[i], inSk[i], a, rv.pseudoOuts[i], index[i]); } rv.mixRing = mixRing; sc_sub(a.bytes, sumout.bytes, sumpouts.bytes); genC(rv.pseudoOuts[i], a, inamounts[i]); DP(rv.pseudoOuts[i]); - rv.MG[i] = proveRctMGSimple(message, rv.mixRing[i], inSk[i], a, rv.pseudoOuts[i], index[i]); + rv.MGs[i] = proveRctMGSimple(message, rv.mixRing[i], inSk[i], a, rv.pseudoOuts[i], index[i]); return rv; } - sRctSig genRctSimple(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> &inamounts, const vector<xmr_amount> &outamounts, xmr_amount txnFee, unsigned int mixin) { + rctSig genRctSimple(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> &inamounts, const vector<xmr_amount> &outamounts, xmr_amount txnFee, unsigned int mixin) { std::vector<unsigned int> index; index.resize(inPk.size()); ctkeyM mixRing; @@ -663,7 +664,7 @@ namespace rct { mixRing[i].resize(mixin+1); index[i] = populateFromBlockchainSimple(mixRing[i], inPk[i], mixin); } - return genRctSimple(message, inSk, inPk, destinations, inamounts, outamounts, txnFee, mixRing, index); + return genRctSimple(message, inSk, destinations, inamounts, outamounts, txnFee, mixRing, index); } //RingCT protocol @@ -676,7 +677,8 @@ namespace rct { //decodeRct: (c.f. http://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 - bool verRct(const rctSig & rv, const ctkeyM &mixRing, const keyV &II, const key &base_hash) { + bool verRct(const rctSig & rv, const ctkeyM &mixRing, const keyV &II, const key &message) { + CHECK_AND_ASSERT_MES(!rv.simple, false, "verRct called on simple rctSig"); CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.rangeSigs.size(), false, "Mismatched sizes of rv.outPk and rv.rangeSigs"); CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.ecdhInfo.size(), false, "Mismatched sizes of rv.outPk and rv.ecdhInfo"); @@ -694,7 +696,7 @@ namespace rct { } //compute txn fee key txnFeeKey = scalarmultH(d2h(rv.txnFee)); - bool mgVerd = verRctMG(rv.MG, II, mixRing, rv.outPk, txnFeeKey, base_hash); + bool mgVerd = verRctMG(rv.MG, II, mixRing, rv.outPk, txnFeeKey, message); DP("mg sig verified?"); DP(mgVerd); @@ -706,19 +708,28 @@ namespace rct { } } bool verRct(const rctSig & rv) { - return verRct(rv, rv.mixRing, rv.MG.II, rv.base_hash); + return verRct(rv, rv.mixRing, rv.MG.II, rv.message); } //ver RingCT simple //assumes only post-rct style inputs (at least for max anonymity) - bool verRctSimple(const sRctSig & rv) { + bool verRctSimple(const rctSig & rv, const ctkeyM &mixRing, const std::vector<keyV> *II, const key &message) { size_t i = 0; bool rvb = true; + CHECK_AND_ASSERT_MES(rv.simple, false, "verRctSimple called on non simple rctSig"); CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.rangeSigs.size(), false, "Mismatched sizes of rv.outPk and rv.rangeSigs"); CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.ecdhInfo.size(), false, "Mismatched sizes of rv.outPk and rv.ecdhInfo"); CHECK_AND_ASSERT_MES(rv.pseudoOuts.size() == rv.MGs.size(), false, "Mismatched sizes of rv.pseudoOuts and rv.MGs"); - CHECK_AND_ASSERT_MES(rv.pseudoOuts.size() == rv.mixRing.size(), false, "Mismatched sizes of rv.pseudoOuts and rv.MGs"); + CHECK_AND_ASSERT_MES(rv.pseudoOuts.size() == mixRing.size(), false, "Mismatched sizes of rv.pseudoOuts and mixRing"); + CHECK_AND_ASSERT_MES(!II || II->size() == mixRing.size(), false, "Mismatched II/mixRing size"); + if (II) + { + for (size_t n = 0; n < II->size(); ++n) + { + CHECK_AND_ASSERT_MES((*II)[n].size() == 2, false, "Bad II size"); + } + } key sumOutpks = identity(); for (i = 0; i < rv.outPk.size(); i++) { @@ -733,8 +744,8 @@ namespace rct { bool tmpb = false; key sumPseudoOuts = identity(); - for (i = 0 ; i < rv.mixRing.size() ; i++) { - tmpb = verRctMGSimple(rv.message, rv.MG[i], rv.MG[i].II, rv.mixRing[i], rv.pseudoOuts[i]); + for (i = 0 ; i < mixRing.size() ; i++) { + tmpb = verRctMGSimple(message, rv.MGs[i], II ? (*II)[i] : rv.MGs[i].II, mixRing[i], rv.pseudoOuts[i]); addKeys(sumPseudoOuts, sumPseudoOuts, rv.pseudoOuts[i]); DP(tmpb); if (!tmpb) { @@ -755,6 +766,10 @@ namespace rct { return (rvb && mgVerd); } + bool verRctSimple(const rctSig & rv) { + return verRctSimple(rv, rv.mixRing, NULL, rv.message); + } + //RingCT protocol //genRct: // creates an rctSig with all data necessary to verify the rangeProofs and that the signer owns one of the @@ -766,6 +781,7 @@ namespace rct { // 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 xmr_amount decodeRct(const rctSig & rv, const key & sk, unsigned int i, key & mask) { + CHECK_AND_ASSERT_MES(!rv.simple, false, "decodeRct called on simple rctSig"); CHECK_AND_ASSERT_THROW_MES(rv.rangeSigs.size() > 0, "Empty rv.rangeSigs"); CHECK_AND_ASSERT_THROW_MES(rv.outPk.size() == rv.rangeSigs.size(), "Mismatched sizes of rv.outPk and rv.rangeSigs"); CHECK_AND_ASSERT_THROW_MES(i < rv.ecdhInfo.size(), "Bad index"); @@ -793,7 +809,8 @@ namespace rct { return decodeRct(rv, sk, i, mask); } - xmr_amount decodeRct(const sRctSig & rv, const key & sk, unsigned int i) { + xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i, key &mask) { + CHECK_AND_ASSERT_MES(rv.simple, false, "decodeRct called on non simple rctSig"); CHECK_AND_ASSERT_THROW_MES(rv.rangeSigs.size() > 0, "Empty rv.rangeSigs"); CHECK_AND_ASSERT_THROW_MES(rv.outPk.size() == rv.rangeSigs.size(), "Mismatched sizes of rv.outPk and rv.rangeSigs"); CHECK_AND_ASSERT_THROW_MES(i < rv.ecdhInfo.size(), "Bad index"); @@ -801,7 +818,7 @@ namespace rct { //mask amount and mask ecdhTuple ecdh_info = rv.ecdhInfo[i]; ecdhDecode(ecdh_info, sk); - key mask = ecdh_info.mask; + mask = ecdh_info.mask; key amount = ecdh_info.amount; key C = rv.outPk[i].mask; DP("C"); @@ -815,4 +832,9 @@ namespace rct { } return h2d(amount); } + + xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i) { + key mask; + return decodeRctSimple(rv, sk, i, mask); + } } diff --git a/src/ringct/rctSigs.h b/src/ringct/rctSigs.h index 7682510cd..94b67f2d9 100644 --- a/src/ringct/rctSigs.h +++ b/src/ringct/rctSigs.h @@ -112,9 +112,9 @@ namespace rct { // this shows that sum inputs = sum outputs //Ver: // verifies the above sig is created corretly - mgSig proveRctMG(const ctkeyM & pubs, const ctkeyV & inSk, const keyV &outMasks, const ctkeyV & outPk, unsigned int index, key txnFee, const key &base_hash); + mgSig proveRctMG(const ctkeyM & pubs, const ctkeyV & inSk, const keyV &outMasks, const ctkeyV & outPk, unsigned int index, key txnFee, const key &message); mgSig proveRctMGSimple(const key & message, const ctkeyV & pubs, const ctkey & inSk, const key &a , const key &Cout, unsigned int index); - bool verRctMG(mgSig mg, const ctkeyM & pubs, const ctkeyV & outPk, key txnFee, const key &base_hash); + bool verRctMG(mgSig mg, const ctkeyM & pubs, const ctkeyV & outPk, key txnFee, const key &message); bool verRctMGSimple(const key &message, const mgSig &mg, const keyV &II, const ctkeyV & pubs, const key & C); //These functions get keys from blockchain @@ -135,16 +135,18 @@ namespace rct { //decodeRct: (c.f. http://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 ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> amounts, const ctkeyM &mixRing, const key &bash_hash, unsigned int index); - rctSig genRct(const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> amounts, const key &bash_hash, const int mixin); - sRctSig genRctSimple(const key & message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> & inamounts, const vector<xmr_amount> & outamounts, xmr_amount txnFee, unsigned int mixin); - sRctSig genRctSimple(const key & message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> & inamounts, const vector<xmr_amount> & outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const std::vector<unsigned int> & index); + rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> & amounts, const ctkeyM &mixRing, unsigned int index); + rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> & amounts, const int mixin); + rctSig genRctSimple(const key & message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> & inamounts, const vector<xmr_amount> & outamounts, xmr_amount txnFee, unsigned int mixin); + 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 std::vector<unsigned int> & index); bool verRct(const rctSig & rv); - bool verRct(const rctSig & rv, const ctkeyM &mixRing, const keyV &II, const key &base_hash); - bool verRctSimple(const sRctSig & rv); + bool verRct(const rctSig & rv, const ctkeyM &mixRing, const keyV &II, const key &message); + bool verRctSimple(const rctSig & rv); + bool verRctSimple(const rctSig & rv, const ctkeyM &mixRing, const std::vector<keyV> *II, const key &message); xmr_amount decodeRct(const rctSig & rv, const key & sk, unsigned int i, key & mask); xmr_amount decodeRct(const rctSig & rv, const key & sk, unsigned int i); - xmr_amount decodeRct(const sRctSig & rv, const key & sk, unsigned int i); + xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i); + xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i, key & mask); } #endif /* RCTSIGS_H */ diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index f270da70a..a376980fd 100644 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -183,44 +183,29 @@ namespace rct { // outPk contains public keypairs which are destinations (P, C), // P = address, C = commitment to amount struct rctSig { - vector<rangeSig> rangeSigs; - mgSig MG; - ctkeyM mixRing; //the set of all pubkeys / copy - //pairs that you mix with - vector<ecdhTuple> ecdhInfo; - ctkeyV outPk; - xmr_amount txnFee; - key base_hash; - - BEGIN_SERIALIZE_OBJECT() - FIELD(rangeSigs) - FIELD(MG) - // FIELD(mixRing) - not serialized, it can be reconstructed - FIELD(ecdhInfo) - FIELD(outPk) - FIELD(txnFee) - // FIELD(base_hash) - not serialized, it can be reconstructed - END_SERIALIZE() - }; - - //rct simple variant - struct sRctSig { + bool simple; key message; vector<rangeSig> rangeSigs; - vector<mgSig> MG; - vector<ctkeyV> mixRing; //the set of all pubkeys / copy + mgSig MG; // for non simple rct + vector<mgSig> MGs; // for simple rct + ctkeyM mixRing; //the set of all pubkeys / copy //pairs that you mix with - keyV pseudoOuts; //C + keyV pseudoOuts; //C - for simple rct vector<ecdhTuple> ecdhInfo; ctkeyV outPk; xmr_amount txnFee; // contains b BEGIN_SERIALIZE_OBJECT() + FIELD(simple) // FIELD(message) - not serialized, it can be reconstructed FIELD(rangeSigs) - FIELD(MG) + if (simple) + FIELD(MGs) + else + FIELD(MG) // FIELD(mixRing) - not serialized, it can be reconstructed - FIELD(pseudoOuts) + if (simple) + FIELD(pseudoOuts) FIELD(ecdhInfo) FIELD(outPk) FIELD(txnFee) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 75c912461..a6c9faa80 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -193,6 +193,14 @@ void wallet2::check_acc_out(const account_keys &acc, const tx_out &o, const cryp error = false; } //---------------------------------------------------------------------------------------------------- +static uint64_t decodeRct(const rct::rctSig & rv, const rct::key & sk, unsigned int i, rct::key & mask) +{ + if (rv.simple) + return rct::decodeRctSimple(rv, sk, i, mask); + else + return rct::decodeRct(rv, sk, i, mask); +} +//---------------------------------------------------------------------------------------------------- void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_t height, uint64_t ts, bool miner_tx, bool pool) { if (!miner_tx) @@ -251,7 +259,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ outs.push_back(0); if (money_transfered == 0) - money_transfered = rct::decodeRct(tx.rct_signatures, rct::sk2rct(in_ephemeral[0].sec), 0, mask[0]); + money_transfered = tools::decodeRct(tx.rct_signatures, rct::sk2rct(in_ephemeral[0].sec), 0, mask[0]); amount[0] = money_transfered; tx_money_got_in_outs = money_transfered; @@ -290,7 +298,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ outs.push_back(i); if (money_transfered[i] == 0) - money_transfered[i] = rct::decodeRct(tx.rct_signatures, rct::sk2rct(in_ephemeral[i].sec), i, mask[i]); + money_transfered[i] = tools::decodeRct(tx.rct_signatures, rct::sk2rct(in_ephemeral[i].sec), i, mask[i]); tx_money_got_in_outs += money_transfered[i]; amount[i] = money_transfered[i]; } @@ -334,7 +342,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ outs.push_back(i); if (money_transfered[i] == 0) - money_transfered[i] = rct::decodeRct(tx.rct_signatures, rct::sk2rct(in_ephemeral[i].sec), i, mask[i]); + money_transfered[i] = tools::decodeRct(tx.rct_signatures, rct::sk2rct(in_ephemeral[i].sec), i, mask[i]); tx_money_got_in_outs += money_transfered[i]; amount[i] = money_transfered[i]; } @@ -362,7 +370,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ outs.push_back(i); if (money_transfered == 0) - money_transfered = rct::decodeRct(tx.rct_signatures, rct::sk2rct(in_ephemeral[i].sec), i, mask[i]); + money_transfered = tools::decodeRct(tx.rct_signatures, rct::sk2rct(in_ephemeral[i].sec), i, mask[i]); amount[i] = money_transfered; tx_money_got_in_outs += money_transfered; } @@ -3008,19 +3016,29 @@ static size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs) // rct signatures + // simple + size += 1; + + // message + size += 32; + // rangeSigs size += (2*64*32+32+64*32) * n_outputs; - // MG - only the last slot of II is saved, the rest can be reconstructed - size += 32 * (mixin+1) * n_inputs + 32 + 32 * (/*n_inputs+*/1) ; + // MGs - only the last slot of II is saved, the rest can be reconstructed + size += n_inputs * (32 * (mixin+1) * n_inputs + 32 + 32 * (/*n_inputs+*/1)); // mixRing - not serialized, can be reconstructed /* size += 2 * 32 * (mixin+1) * n_inputs; */ + // pseudoOuts + size += 32 * n_outputs; // ecdhInfo size += 3 * 32 * n_outputs; // outPk size += 2 * 32 * n_outputs; + // txnFee + size += 4; LOG_PRINT_L2("estimated rct tx size for " << n_inputs << " at mixin " << mixin << " and " << n_outputs << ": " << size << " (" << (32 * n_inputs + 2 * 32 * (mixin+1) * n_inputs) << " saved)"); return size; diff --git a/tests/core_tests/rct.cpp b/tests/core_tests/rct.cpp index f16045ef6..ef5f72fb0 100644 --- a/tests/core_tests/rct.cpp +++ b/tests/core_tests/rct.cpp @@ -127,7 +127,10 @@ bool gen_rct_tx_validation_base::generate_with(std::vector<test_event_entry>& ev cryptonote::keypair in_ephemeral; crypto::key_image ki; cryptonote::generate_key_image_helper(miner_accounts[n].get_keys(), tx_pub_key, o, in_ephemeral, ki); - rct::decodeRct(rct_txes[n].rct_signatures, rct::sk2rct(in_ephemeral.sec), o, rct_tx_masks[o+n*4]); + if (rct_txes[n].rct_signatures.simple) + rct::decodeRctSimple(rct_txes[n].rct_signatures, rct::sk2rct(in_ephemeral.sec), o, rct_tx_masks[o+n*4]); + else + rct::decodeRct(rct_txes[n].rct_signatures, rct::sk2rct(in_ephemeral.sec), o, rct_tx_masks[o+n*4]); } CHECK_AND_ASSERT_MES(generator.construct_block_manually(blk_txes[n], blk_last, miner_account, diff --git a/tests/unit_tests/ringct.cpp b/tests/unit_tests/ringct.cpp index 4b494edba..a7011bb2b 100644 --- a/tests/unit_tests/ringct.cpp +++ b/tests/unit_tests/ringct.cpp @@ -187,7 +187,7 @@ TEST(ringct, range_proofs) destinations.push_back(Pk); //compute rct data with mixin 500 - rctSig s = genRct(sc, pc, destinations, amounts, rct::zero(), 3); + rctSig s = genRct(rct::zero(), sc, pc, destinations, amounts, 3); //verify rct data ASSERT_TRUE(verRct(s)); @@ -204,7 +204,7 @@ TEST(ringct, range_proofs) //compute rct data with mixin 500 - s = genRct(sc, pc, destinations, amounts, rct::zero(), 3); + s = genRct(rct::zero(), sc, pc, destinations, amounts, 3); //verify rct data ASSERT_FALSE(verRct(s)); @@ -248,7 +248,7 @@ TEST(ringct, range_proofs_with_fee) destinations.push_back(Pk); //compute rct data with mixin 500 - rctSig s = genRct(sc, pc, destinations, amounts, rct::zero(), 3); + rctSig s = genRct(rct::zero(), sc, pc, destinations, amounts, 3); //verify rct data ASSERT_TRUE(verRct(s)); @@ -265,7 +265,7 @@ TEST(ringct, range_proofs_with_fee) //compute rct data with mixin 500 - s = genRct(sc, pc, destinations, amounts, rct::zero(), 3); + s = genRct(rct::zero(), sc, pc, destinations, amounts, 3); //verify rct data ASSERT_FALSE(verRct(s)); @@ -274,6 +274,60 @@ TEST(ringct, range_proofs_with_fee) ASSERT_TRUE(decodeRct(s, Sk, 1)); } +TEST(ringct, simple) +{ + ctkeyV sc, pc; + ctkey sctmp, pctmp; + //this vector corresponds to output amounts + vector<xmr_amount>outamounts; + //this vector corresponds to input amounts + vector<xmr_amount>inamounts; + //this keyV corresponds to destination pubkeys + keyV destinations; + + //add fake input 3000 + //the sc is secret data + //pc is public data + tie(sctmp, pctmp) = ctskpkGen(3000); + sc.push_back(sctmp); + pc.push_back(pctmp); + inamounts.push_back(3000); + + //add fake input 3000 + //the sc is secret data + //pc is public data + tie(sctmp, pctmp) = ctskpkGen(3000); + sc.push_back(sctmp); + pc.push_back(pctmp); + inamounts.push_back(3000); + + //add output 5000 + outamounts.push_back(5000); + //add the corresponding destination pubkey + key Sk, Pk; + skpkGen(Sk, Pk); + destinations.push_back(Pk); + + //add output 999 + outamounts.push_back(999); + //add the corresponding destination pubkey + skpkGen(Sk, Pk); + destinations.push_back(Pk); + + key message = skGen(); //real message later (hash of txn..) + + //compute sig with mixin 2 + xmr_amount txnfee = 1; + + rctSig s = genRctSimple(message, sc, pc, destinations,inamounts, outamounts, txnfee, 2); + + //verify ring ct signature + ASSERT_TRUE(verRctSimple(s)); + + //decode received amount corresponding to output pubkey index 1 + ASSERT_TRUE(decodeRctSimple(s, Sk, 1)); +} + static rct::rctSig make_sample_rct_sig(int n_inputs, const uint64_t input_amounts[], int n_outputs, const uint64_t output_amounts[], bool last_is_fee) { ctkeyV sc, pc; @@ -295,7 +349,7 @@ static rct::rctSig make_sample_rct_sig(int n_inputs, const uint64_t input_amount destinations.push_back(Pk); } - return genRct(sc, pc, destinations, amounts, rct::zero(), 3);; + return genRct(rct::zero(), sc, pc, destinations, amounts, 3);; } static bool range_proof_test(bool expected_valid, diff --git a/tests/unit_tests/serialization.cpp b/tests/unit_tests/serialization.cpp index 01dff3e99..88e67bd73 100644 --- a/tests/unit_tests/serialization.cpp +++ b/tests/unit_tests/serialization.cpp @@ -565,7 +565,7 @@ TEST(Serialization, serializes_ringct_types) rct::skpkGen(Sk, Pk); destinations.push_back(Pk); //compute rct data with mixin 500 - s0 = rct::genRct(sc, pc, destinations, amounts, rct::zero(), 3); + s0 = rct::genRct(rct::zero(), sc, pc, destinations, amounts, 3); mg0 = s0.MG; ASSERT_TRUE(serialization::dump_binary(mg0, blob)); @@ -588,6 +588,7 @@ TEST(Serialization, serializes_ringct_types) ASSERT_TRUE(serialization::dump_binary(s0, blob)); ASSERT_TRUE(serialization::parse_binary(blob, s1)); + ASSERT_TRUE(s0.simple == s1.simple); ASSERT_TRUE(s0.rangeSigs.size() == s1.rangeSigs.size()); for (size_t n = 0; n < s0.rangeSigs.size(); ++n) { |