diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h index a50ae9c32..946247609 100644 --- a/src/cryptonote_basic/cryptonote_basic.h +++ b/src/cryptonote_basic/cryptonote_basic.h @@ -306,7 +306,8 @@ namespace cryptonote ar.tag("rctsig_prunable"); ar.begin_object(); r = rct_signatures.p.serialize_rctsig_prunable(ar, rct_signatures.type, vin.size(), vout.size(), - vin.size() > 0 && vin[0].type() == typeid(txin_to_key) ? boost::get(vin[0]).key_offsets.size() - 1 : 0); + (vin.empty() || vin[0].type() != typeid(txin_to_key) || rct_signatures.type == rct::RCTTypeFcmpPlusPlus) + ? 0 : boost::get(vin[0]).key_offsets.size() - 1); if (!r || !ar.good()) return false; ar.end_object(); } diff --git a/src/cryptonote_basic/cryptonote_boost_serialization.h b/src/cryptonote_basic/cryptonote_boost_serialization.h index 8948c650c..81da98a78 100644 --- a/src/cryptonote_basic/cryptonote_boost_serialization.h +++ b/src/cryptonote_basic/cryptonote_boost_serialization.h @@ -330,7 +330,7 @@ namespace boost a & x.type; if (x.type == rct::RCTTypeNull) return; - if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple && x.type != rct::RCTTypeBulletproof && x.type != rct::RCTTypeBulletproof2 && x.type != rct::RCTTypeCLSAG && x.type != rct::RCTTypeBulletproofPlus) + if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple && x.type != rct::RCTTypeBulletproof && x.type != rct::RCTTypeBulletproof2 && x.type != rct::RCTTypeCLSAG && x.type != rct::RCTTypeBulletproofPlus && x.type != rct::RCTTypeFcmpPlusPlus) throw boost::archive::archive_exception(boost::archive::archive_exception::other_exception, "Unsupported rct type"); // a & x.message; message is not serialized, as it can be reconstructed from the tx data // a & x.mixRing; mixRing is not serialized, as it can be reconstructed from the offsets @@ -339,6 +339,8 @@ namespace boost a & x.ecdhInfo; serializeOutPk(a, x.outPk, ver); a & x.txnFee; + if (x.type == rct::RCTTypeFcmpPlusPlus) + a & x.referenceBlock; } template @@ -354,6 +356,8 @@ namespace boost a & x.MGs; if (ver >= 1u) a & x.CLSAGs; + if (ver >= 3u) + a & x.fcmp_pp; if (x.rangeSigs.empty()) a & x.pseudoOuts; } @@ -364,7 +368,7 @@ namespace boost a & x.type; if (x.type == rct::RCTTypeNull) return; - if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple && x.type != rct::RCTTypeBulletproof && x.type != rct::RCTTypeBulletproof2 && x.type != rct::RCTTypeCLSAG && x.type != rct::RCTTypeBulletproofPlus) + if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple && x.type != rct::RCTTypeBulletproof && x.type != rct::RCTTypeBulletproof2 && x.type != rct::RCTTypeCLSAG && x.type != rct::RCTTypeBulletproofPlus && x.type != rct::RCTTypeFcmpPlusPlus) throw boost::archive::archive_exception(boost::archive::archive_exception::other_exception, "Unsupported rct type"); // a & x.message; message is not serialized, as it can be reconstructed from the tx data // a & x.mixRing; mixRing is not serialized, as it can be reconstructed from the offsets @@ -373,6 +377,8 @@ namespace boost a & x.ecdhInfo; serializeOutPk(a, x.outPk, ver); a & x.txnFee; + if (x.type == rct::RCTTypeFcmpPlusPlus) + a & x.referenceBlock; //-------------- a & x.p.rangeSigs; if (x.p.rangeSigs.empty()) @@ -384,7 +390,9 @@ namespace boost a & x.p.MGs; if (ver >= 1u) a & x.p.CLSAGs; - if (x.type == rct::RCTTypeBulletproof || x.type == rct::RCTTypeBulletproof2 || x.type == rct::RCTTypeCLSAG || x.type == rct::RCTTypeBulletproofPlus) + if (ver >= 3u) + a & x.p.fcmp_pp; + if (x.type == rct::RCTTypeBulletproof || x.type == rct::RCTTypeBulletproof2 || x.type == rct::RCTTypeCLSAG || x.type == rct::RCTTypeBulletproofPlus || x.type == rct::RCTTypeFcmpPlusPlus) a & x.p.pseudoOuts; } @@ -425,6 +433,6 @@ namespace boost } } -BOOST_CLASS_VERSION(rct::rctSigPrunable, 2) -BOOST_CLASS_VERSION(rct::rctSig, 2) +BOOST_CLASS_VERSION(rct::rctSigPrunable, 3) +BOOST_CLASS_VERSION(rct::rctSig, 3) BOOST_CLASS_VERSION(rct::multisig_out, 1) diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index 05dcac4e2..094cd28a3 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -106,7 +106,7 @@ namespace cryptonote uint64_t get_transaction_weight_clawback(const transaction &tx, size_t n_padded_outputs) { const rct::rctSig &rv = tx.rct_signatures; - const bool plus = rv.type == rct::RCTTypeBulletproofPlus; + const bool plus = rv.type == rct::RCTTypeBulletproofPlus || rv.type == rct::RCTTypeFcmpPlusPlus; const uint64_t bp_base = (32 * ((plus ? 6 : 9) + 7 * 2)) / 2; // notional size of a 2 output proof, normalized to 1 proof (ie, divided by 2) const size_t n_outputs = tx.vout.size(); if (n_padded_outputs <= 2) @@ -484,6 +484,7 @@ namespace cryptonote weight += extra; // calculate deterministic CLSAG/MLSAG data size + // TODO: update for fcmp_pp const size_t ring_size = boost::get(tx.vin[0]).key_offsets.size(); if (rct::is_rct_clsag(tx.rct_signatures.type)) extra = tx.vin.size() * (ring_size + 2) * 32; @@ -1292,7 +1293,8 @@ namespace cryptonote binary_archive ba(ss); const size_t inputs = t.vin.size(); const size_t outputs = t.vout.size(); - const size_t mixin = t.vin.empty() ? 0 : t.vin[0].type() == typeid(txin_to_key) ? boost::get(t.vin[0]).key_offsets.size() - 1 : 0; + const size_t mixin = (t.vin.empty() || t.rct_signatures.type == rct::RCTTypeFcmpPlusPlus || t.vin[0].type() != typeid(txin_to_key)) + ? 0 : boost::get(t.vin[0]).key_offsets.size() - 1; bool r = tt.rct_signatures.p.serialize_rctsig_prunable(ba, t.rct_signatures.type, inputs, outputs, mixin); CHECK_AND_ASSERT_MES(r, false, "Failed to serialize rct signatures prunable"); cryptonote::get_blob_hash(ss.str(), res); diff --git a/src/fcmp/proof.h b/src/fcmp/proof.h new file mode 100644 index 000000000..89eb90d19 --- /dev/null +++ b/src/fcmp/proof.h @@ -0,0 +1,45 @@ +// Copyright (c) 2024, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include + +namespace fcmp +{ + +// Byte buffer containing the fcmp++ proof +using FcmpPpProof = std::vector; + +static inline std::size_t get_fcmp_pp_len_from_n_inputs(const std::size_t n_inputs) +{ + // TODO: implement + return n_inputs * 4; +}; + +}//namespace fcmp diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index 2d92ba05d..c96bc7bba 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -47,8 +47,7 @@ using namespace std; #define CHECK_AND_ASSERT_MES_L1(expr, ret, message) {if(!(expr)) {MCERROR("verify", message); return ret;}} -namespace -{ +namespace rct { rct::Bulletproof make_dummy_bulletproof(const std::vector &outamounts, rct::keyV &C, rct::keyV &masks) { const size_t n_outs = outamounts.size(); @@ -117,9 +116,7 @@ namespace const size_t n_scalars = ring_size; return rct::clsag{rct::keyV(n_scalars, I), I, I, I}; } -} -namespace rct { Bulletproof proveRangeBulletproof(keyV &C, keyV &masks, const std::vector &amounts, epee::span sk, hw::device &hwdev) { CHECK_AND_ASSERT_THROW_MES(amounts.size() == sk.size(), "Invalid amounts/sk sizes"); diff --git a/src/ringct/rctSigs.h b/src/ringct/rctSigs.h index 035d866d6..af533e495 100644 --- a/src/ringct/rctSigs.h +++ b/src/ringct/rctSigs.h @@ -64,6 +64,10 @@ namespace hw { namespace rct { + // helpers for mock txs + Bulletproof make_dummy_bulletproof(const std::vector &outamounts, keyV &C, keyV &masks); + BulletproofPlus make_dummy_bulletproof_plus(const std::vector &outamounts, keyV &C, keyV &masks); + clsag make_dummy_clsag(size_t ring_size); boroSig genBorromean(const key64 x, const key64 P1, const key64 P2, const bits indices); bool verifyBorromean(const boroSig &bb, const key64 P1, const key64 P2); diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index 20b952c5e..8598a4f04 100644 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -45,7 +45,7 @@ extern "C" { } #include "crypto/generic-ops.h" #include "crypto/crypto.h" - +#include "fcmp/proof.h" #include "hex.h" #include "span.h" #include "memwipe.h" @@ -304,6 +304,7 @@ namespace rct { RCTTypeBulletproof2 = 4, RCTTypeCLSAG = 5, RCTTypeBulletproofPlus = 6, + RCTTypeFcmpPlusPlus = 7, }; enum RangeProofType { RangeProofBorromean, RangeProofBulletproof, RangeProofMultiOutputBulletproof, RangeProofPaddedBulletproof }; struct RCTConfig { @@ -325,9 +326,10 @@ namespace rct { std::vector ecdhInfo; ctkeyV outPk; xmr_amount txnFee; // contains b + crypto::hash referenceBlock; // block containing the merkle tree root used for fcmp's rctSigBase() : - type(RCTTypeNull), message{}, mixRing{}, pseudoOuts{}, ecdhInfo{}, outPk{}, txnFee(0) + type(RCTTypeNull), message{}, mixRing{}, pseudoOuts{}, ecdhInfo{}, outPk{}, txnFee(0), referenceBlock{} {} template class Archive> @@ -336,7 +338,7 @@ namespace rct { FIELD(type) if (type == RCTTypeNull) return ar.good(); - if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2 && type != RCTTypeCLSAG && type != RCTTypeBulletproofPlus) + if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2 && type != RCTTypeCLSAG && type != RCTTypeBulletproofPlus && type != RCTTypeFcmpPlusPlus) return false; VARINT_FIELD(txnFee) // inputs/outputs not saved, only here for serialization help @@ -365,7 +367,7 @@ namespace rct { return false; for (size_t i = 0; i < outputs; ++i) { - if (type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeBulletproofPlus) + if (type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeBulletproofPlus || type == RCTTypeFcmpPlusPlus) { // Since RCTTypeBulletproof2 enote types, we don't serialize the blinding factor, and only serialize the // first 8 bytes of ecdhInfo[i].amount @@ -401,6 +403,8 @@ namespace rct { ar.delimit_array(); } ar.end_array(); + if (type == RCTTypeFcmpPlusPlus) + FIELD(referenceBlock) return ar.good(); } @@ -412,6 +416,7 @@ namespace rct { FIELD(ecdhInfo) FIELD(outPk) VARINT_FIELD(txnFee) + FIELD(referenceBlock) END_SERIALIZE() }; struct rctSigPrunable { @@ -421,6 +426,7 @@ namespace rct { std::vector MGs; // simple rct has N, full has 1 std::vector CLSAGs; keyV pseudoOuts; //C - for simple rct + fcmp::FcmpPpProof fcmp_pp; // when changing this function, update cryptonote::get_pruned_transaction_weight template class Archive> @@ -434,9 +440,9 @@ namespace rct { return false; if (type == RCTTypeNull) return ar.good(); - if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2 && type != RCTTypeCLSAG && type != RCTTypeBulletproofPlus) + if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2 && type != RCTTypeCLSAG && type != RCTTypeBulletproofPlus && type != RCTTypeFcmpPlusPlus) return false; - if (type == RCTTypeBulletproofPlus) + if (type == RCTTypeBulletproofPlus || type == RCTTypeFcmpPlusPlus) { uint32_t nbp = bulletproofs_plus.size(); VARINT_FIELD(nbp) @@ -493,7 +499,20 @@ namespace rct { ar.end_array(); } - if (type == RCTTypeCLSAG || type == RCTTypeBulletproofPlus) + if (type == RCTTypeFcmpPlusPlus) + { + ar.begin_object(); + ar.tag("fcmp_pp"); + const std::size_t proof_len = fcmp::get_fcmp_pp_len_from_n_inputs(inputs); + PREPARE_CUSTOM_VECTOR_SERIALIZATION(proof_len, fcmp_pp); + if (fcmp_pp.size() != proof_len) + return false; + ar.serialize_blob(fcmp_pp.data(), proof_len); + if (!ar.good()) + return false; + ar.end_object(); + } + else if (type == RCTTypeCLSAG || type == RCTTypeBulletproofPlus) { ar.tag("CLSAGs"); ar.begin_array(); @@ -584,7 +603,7 @@ namespace rct { } ar.end_array(); } - if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeBulletproofPlus) + if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeBulletproofPlus || type == RCTTypeFcmpPlusPlus) { ar.tag("pseudoOuts"); ar.begin_array(); @@ -608,6 +627,7 @@ namespace rct { FIELD(bulletproofs_plus) FIELD(MGs) FIELD(CLSAGs) + FIELD(fcmp_pp) FIELD(pseudoOuts) END_SERIALIZE() }; @@ -616,12 +636,12 @@ namespace rct { keyV& get_pseudo_outs() { - return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeBulletproofPlus ? p.pseudoOuts : pseudoOuts; + return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeBulletproofPlus || type == RCTTypeFcmpPlusPlus ? p.pseudoOuts : pseudoOuts; } keyV const& get_pseudo_outs() const { - return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeBulletproofPlus ? p.pseudoOuts : pseudoOuts; + return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeBulletproofPlus || type == RCTTypeFcmpPlusPlus ? p.pseudoOuts : pseudoOuts; } BEGIN_SERIALIZE_OBJECT() diff --git a/tests/unit_tests/serialization.cpp b/tests/unit_tests/serialization.cpp index 9daa44351..1e37ee9fb 100644 --- a/tests/unit_tests/serialization.cpp +++ b/tests/unit_tests/serialization.cpp @@ -1304,3 +1304,180 @@ TEST(Serialization, tuple_many_tuples) EXPECT_EQ(tupler, tupler_recovered); } + +TEST(Serialization, tx_fcmp_pp) +{ + using namespace cryptonote; + + const std::size_t n_inputs = 2; + const std::size_t n_outputs = 3; + + const auto make_dummy_fcmp_pp_tx = [n_inputs, n_outputs]() -> transaction + { + transaction tx; + + tx.invalidate_hashes(); + tx.set_null(); + + tx.version = 2; + tx.rct_signatures.type = rct::RCTTypeFcmpPlusPlus; + + // Set inputs + txin_to_key txin_to_key1; + txin_to_key1.amount = 1; + memset(&txin_to_key1.k_image, 0x42, sizeof(crypto::key_image)); + txin_to_key1.key_offsets.clear(); + tx.vin.clear(); + for (size_t i = 0; i < n_inputs; ++i) + tx.vin.push_back(txin_to_key1); + + // Set outputs + const uint64_t amount = 1; + std::vector out_amounts; + tx_out vout; + set_tx_out(amount, crypto::public_key{}, true, crypto::view_tag{}, vout); + for (size_t i = 0; i < n_outputs; ++i) + { + tx.vout.push_back(vout); + out_amounts.push_back(amount); + } + + // 1 ecdhTuple for each output + rct::ecdhTuple ecdhInfo; + memset(&ecdhInfo.mask, 0x01, sizeof(rct::key)); + memset(&ecdhInfo.amount, 0x02, sizeof(rct::key)); + for (size_t i = 0; i < n_outputs; ++i) + tx.rct_signatures.ecdhInfo.push_back(ecdhInfo); + + // 1 outPk for each output + rct::ctkey ctkey; + memset(&ctkey.dest, 0x01, sizeof(rct::key)); + memset(&ctkey.mask, 0x02, sizeof(rct::key)); + for (size_t i = 0; i < n_outputs; ++i) + tx.rct_signatures.outPk.push_back(ctkey); + + // 1 bp+ + rct::keyV C, masks; + tx.rct_signatures.p.bulletproofs_plus.push_back(rct::make_dummy_bulletproof_plus(out_amounts, C, masks)); + + // 1 pseudoOut for each input + const rct::key pseudoOut{0x01}; + for (size_t i = 0; i < n_inputs; ++i) + tx.rct_signatures.p.pseudoOuts.push_back(pseudoOut); + + // Set the reference block for fcmp++ + const crypto::hash referenceBlock{0x01}; + tx.rct_signatures.referenceBlock = referenceBlock; + + // 1 fcmp++ proof + fcmp::FcmpPpProof fcmp_pp; + const std::size_t proof_len = fcmp::get_fcmp_pp_len_from_n_inputs(n_inputs); + fcmp_pp.reserve(proof_len); + for (std::size_t i = 0; i < proof_len; ++i) + fcmp_pp.push_back(i); + tx.rct_signatures.p.fcmp_pp = std::move(fcmp_pp); + + return tx; + }; + + // 1. Set up a normal tx that includes an fcmp++ proof + { + transaction tx = make_dummy_fcmp_pp_tx(); + transaction tx1; + string blob; + + ASSERT_TRUE(serialization::dump_binary(tx, blob)); + ASSERT_TRUE(serialization::parse_binary(blob, tx1)); + ASSERT_EQ(tx, tx1); + ASSERT_EQ(tx.rct_signatures.referenceBlock, crypto::hash{0x01}); + ASSERT_EQ(tx.rct_signatures.referenceBlock, tx1.rct_signatures.referenceBlock); + ASSERT_EQ(tx.rct_signatures.p.fcmp_pp, tx1.rct_signatures.p.fcmp_pp); + } + + // 2. fcmp++ proof is longer than expected when serializing + { + transaction tx = make_dummy_fcmp_pp_tx(); + string blob; + + // Extend fcmp++ proof + ASSERT_TRUE(tx.rct_signatures.p.fcmp_pp.size() == fcmp::get_fcmp_pp_len_from_n_inputs(n_inputs)); + tx.rct_signatures.p.fcmp_pp.push_back(0x01); + + ASSERT_FALSE(serialization::dump_binary(tx, blob)); + } + + // 3. fcmp++ proof is shorter than expected when serializing + { + transaction tx = make_dummy_fcmp_pp_tx(); + + // Shorten the fcmp++ proof + ASSERT_TRUE(tx.rct_signatures.p.fcmp_pp.size() == fcmp::get_fcmp_pp_len_from_n_inputs(n_inputs)); + ASSERT_TRUE(tx.rct_signatures.p.fcmp_pp.size() > 1); + tx.rct_signatures.p.fcmp_pp.pop_back(); + + string blob; + ASSERT_FALSE(serialization::dump_binary(tx, blob)); + } + + const auto fcmp_pp_to_hex_str = [](const transaction &tx) + { + std::string fcmp_pp_str; + for (std::size_t i = 0; i < tx.rct_signatures.p.fcmp_pp.size(); ++i) + { + std::stringstream ss; + ss << std::hex << std::setfill('0') << std::setw(2) << (int)tx.rct_signatures.p.fcmp_pp[i]; + fcmp_pp_str += ss.str(); + } + return fcmp_pp_str; + }; + + // 4. fcmp++ proof is longer than expected when de-serializing + { + transaction tx = make_dummy_fcmp_pp_tx(); + transaction tx1; + string blob; + + ASSERT_TRUE(serialization::dump_binary(tx, blob)); + + std::string blob_str = epee::string_tools::buff_to_hex_nodelimer(blob); + + // Find the proof within the serialized tx blob + const std::string fcmp_pp_str = fcmp_pp_to_hex_str(tx); + ASSERT_TRUE(!fcmp_pp_str.empty()); + const std::size_t pos = blob_str.find(fcmp_pp_str); + ASSERT_TRUE(pos != std::string::npos); + ASSERT_TRUE(blob_str.find(fcmp_pp_str, pos + 1) == std::string::npos); + + // Insert an extra proof elem + blob_str.insert(pos, "2a"); + std::string larger_blob; + epee::string_tools::parse_hexstr_to_binbuff(blob_str, larger_blob); + + ASSERT_FALSE(serialization::parse_binary(larger_blob, tx1)); + } + + // 5. fcmp++ proof is shorter than expected when de-serializing + { + transaction tx = make_dummy_fcmp_pp_tx(); + transaction tx1; + string blob; + + ASSERT_TRUE(serialization::dump_binary(tx, blob)); + + std::string blob_str = epee::string_tools::buff_to_hex_nodelimer(blob); + + // Find the proof within the serialized tx blob + const std::string fcmp_pp_str = fcmp_pp_to_hex_str(tx); + ASSERT_TRUE(!fcmp_pp_str.empty()); + const std::size_t pos = blob_str.find(fcmp_pp_str); + ASSERT_TRUE(pos != std::string::npos); + ASSERT_TRUE(blob_str.find(fcmp_pp_str, pos + 1) == std::string::npos); + + // Delete a proof elem + blob_str.erase(pos, 2); + std::string smaller_blob; + epee::string_tools::parse_hexstr_to_binbuff(blob_str, smaller_blob); + + ASSERT_FALSE(serialization::parse_binary(smaller_blob, tx1)); + } +}