Skip to content

Commit

Permalink
rct: rework the verification preparation process
Browse files Browse the repository at this point in the history
The whole rct data apart from the MLSAGs is now included in
the signed message, to avoid malleability issues.

Instead of passing the data that's not serialized as extra
parameters to the verification API, the transaction is modified
to fill all that information. This means the transaction can
not be const anymore, but it cleaner in other ways.
  • Loading branch information
moneromooo-monero committed Aug 28, 2016
1 parent 3ab2ab3 commit d93746b
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 200 deletions.
222 changes: 96 additions & 126 deletions src/cryptonote_core/blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2156,7 +2156,7 @@ bool Blockchain::get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vector<u
// This function overloads its sister function with
// an extra value (hash of highest block that holds an output used as input)
// as a return-by-reference.
bool Blockchain::check_tx_inputs(const transaction& tx, uint64_t& max_used_block_height, crypto::hash& max_used_block_id, tx_verification_context &tvc, bool kept_by_block)
bool Blockchain::check_tx_inputs(transaction& tx, uint64_t& max_used_block_height, crypto::hash& max_used_block_id, tx_verification_context &tvc, bool kept_by_block)
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
Expand Down Expand Up @@ -2235,13 +2235,82 @@ bool Blockchain::have_tx_keyimges_as_spent(const transaction &tx) const
}
return false;
}
bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_prefix_hash, const std::vector<std::vector<rct::ctkey>> &pubkeys)
{
CHECK_AND_ASSERT_MES(tx.version == 2, false, "Transaction version is not 2");

rct::rctSig &rv = tx.rct_signatures;

// message - hash of the transaction prefix
rv.message = rct::hash2rct(tx_prefix_hash);

// mixRing - full and simple store it in opposite ways
if (rv.type == rct::RCTTypeFull)
{
rv.mixRing.resize(pubkeys[0].size());
for (size_t m = 0; m < pubkeys[0].size(); ++m)
rv.mixRing[m].clear();
for (size_t n = 0; n < pubkeys.size(); ++n)
{
CHECK_AND_ASSERT_MES(pubkeys[n].size() <= pubkeys[0].size(), false, "More inputs that first ring");
for (size_t m = 0; m < pubkeys[n].size(); ++m)
{
rv.mixRing[m].push_back(pubkeys[n][m]);
}
}
}
else if (rv.type == rct::RCTTypeSimple)
{
rv.mixRing.resize(pubkeys.size());
for (size_t n = 0; n < pubkeys.size(); ++n)
{
rv.mixRing[n].clear();
for (size_t m = 0; m < pubkeys[n].size(); ++m)
{
rv.mixRing[n].push_back(pubkeys[n][m]);
}
}
}
else
{
CHECK_AND_ASSERT_MES(false, false, "Unsupported rct tx type: " + boost::lexical_cast<std::string>(rv.type));
}

// II
if (rv.type == rct::RCTTypeFull)
{
rv.MG.II.resize(tx.vin.size());
for (size_t n = 0; n < tx.vin.size(); ++n)
rv.MG.II[n] = rct::ki2rct(boost::get<txin_to_key>(tx.vin[n]).k_image);
}
else if (rv.type == rct::RCTTypeSimple)
{
CHECK_AND_ASSERT_MES(rv.MGs.size() == tx.vin.size(), false, "Bad MGs size");
for (size_t n = 0; n < tx.vin.size(); ++n)
{
rv.MGs[n].II.resize(1);
rv.MGs[n].II[0] = rct::ki2rct(boost::get<txin_to_key>(tx.vin[n]).k_image);
}
}
else
{
CHECK_AND_ASSERT_MES(false, false, "Unsupported rct tx type: " + boost::lexical_cast<std::string>(rv.type));
}

// outPk
CHECK_AND_ASSERT_MES(rv.outPk.size() == tx.vout.size(), false, "Bad outPk size");
for (size_t n = 0; n < tx.rct_signatures.outPk.size(); ++n)
rv.outPk[n].dest = rct::pk2rct(boost::get<txout_to_key>(tx.vout[n].target).key);

return true;
}
//------------------------------------------------------------------
// This function validates transaction inputs and their keys.
// FIXME: consider moving functionality specific to one input into
// check_tx_input() rather than here, and use this function simply
// to iterate the inputs as necessary (splitting the task
// using threads, etc.)
bool Blockchain::check_tx_inputs(const transaction& tx, tx_verification_context &tvc, uint64_t* pmax_used_block_height)
bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, uint64_t* pmax_used_block_height)
{
LOG_PRINT_L3("Blockchain::" << __func__);
size_t sig_index = 0;
Expand Down Expand Up @@ -2461,71 +2530,29 @@ bool Blockchain::check_tx_inputs(const transaction& tx, tx_verification_context
}
else
{
if (!expand_transaction_2(tx, tx_prefix_hash, pubkeys))
{
LOG_PRINT_L1("Failed to expand rct signatures!");
return false;
}

// from version 2, check ringct signatures
// obviously, the original and simple rct APIs use a mixRing that's indexes
// in opposite orders, because it'd be too simple otherwise...
switch (tx.rct_signatures.type)
const rct::rctSig &rv = tx.rct_signatures;
switch (rv.type)
{
case rct::RCTTypeSimple: {
rct::ctkeyM reconstructed_mixRing;
std::vector<rct::keyV> reconstructed_II;
rct::ctkeyV reconstructed_outPk;

// 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, and always do outPk

// all MGs should have empty II
for (size_t n = 0; n < tx.rct_signatures.MGs.size(); ++n)
{
if (tx.rct_signatures.MGs[n].II.size() != 0)
{
LOG_PRINT_L1("Failed to check ringct signatures: non empty MGs II");
return false;
}
}

reconstructed_II.resize(tx.vin.size());
for (size_t n = 0; n < tx.vin.size(); ++n)
{
reconstructed_II[n].push_back(rct::ki2rct(boost::get<txin_to_key>(tx.vin[n]).k_image));
}

if (tx.rct_signatures.outPk.size() != tx.vout.size())
{
LOG_PRINT_L1("Failed to check ringct signatures: outPk and vout have different sizes");
return false;
}
reconstructed_outPk.resize(tx.vout.size());
for (size_t n = 0; n < tx.vout.size(); ++n)
{
reconstructed_outPk[n].dest = rct::pk2rct(boost::get<txout_to_key>(tx.vout[n].target).key);
reconstructed_outPk[n].mask = tx.rct_signatures.outPk[n].mask;
}

if (tx.rct_signatures.mixRing.empty())
{
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]);
}
}
}

// check all this, either recontructed (so should really pass), or not
{
if (pubkeys.size() != mixRing.size())
if (pubkeys.size() != rv.mixRing.size())
{
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())
if (pubkeys[i].size() != rv.mixRing[i].size())
{
LOG_PRINT_L1("Failed to check ringct signatures: mismatched pubkeys/mixRing size");
return false;
Expand All @@ -2536,12 +2563,12 @@ bool Blockchain::check_tx_inputs(const transaction& tx, tx_verification_context
{
for (size_t m = 0; m < pubkeys[n].size(); ++m)
{
if (pubkeys[n][m].dest != rct::rct2pk(mixRing[n][m].dest))
if (pubkeys[n][m].dest != rct::rct2pk(rv.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))
if (pubkeys[n][m].mask != rct::rct2pk(rv.mixRing[n][m].mask))
{
LOG_PRINT_L1("Failed to check ringct signatures: mismatched commitment at vin " << n << ", index " << m);
return false;
Expand All @@ -2550,88 +2577,35 @@ bool Blockchain::check_tx_inputs(const transaction& tx, tx_verification_context
}
}

if (tx.rct_signatures.MGs.size() != tx.vin.size())
if (rv.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))
if (memcmp(&boost::get<txin_to_key>(tx.vin[n]).k_image, &rv.MGs[n].II[0], 32))
{
LOG_PRINT_L1("Failed to check ringct signatures: mismatched key image");
return false;
}
}

if (!rct::verRctSimple(tx.rct_signatures, mixRing, &reconstructed_II, reconstructed_outPk, rct::hash2rct(tx_prefix_hash)))
if (!rct::verRctSimple(rv))
{
LOG_PRINT_L1("Failed to check ringct signatures!");
return false;
}
break;
}
case rct::RCTTypeFull: {
rct::ctkeyM reconstructed_mixRing;
rct::keyV reconstructed_II;
rct::ctkeyV reconstructed_outPk;

// if the tx already has a non empty mixRing and/or II, use them,
// else reconstruct them. Always do outPk.
const rct::ctkeyM &mixRing = tx.rct_signatures.mixRing.empty() ? reconstructed_mixRing : tx.rct_signatures.mixRing;
const rct::keyV &II = tx.rct_signatures.MG.II.empty() ? reconstructed_II : tx.rct_signatures.MG.II;
const rct::ctkeyV outPk = reconstructed_outPk;

// 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.empty())
{
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);
}
}

if (tx.rct_signatures.outPk.size() != tx.vout.size())
{
LOG_PRINT_L1("Failed to check ringct signatures: outPk and vout have different sizes");
return false;
}
reconstructed_outPk.resize(tx.vout.size());
for (size_t n = 0; n < tx.vout.size(); ++n)
{
reconstructed_outPk[n].dest = rct::pk2rct(boost::get<txout_to_key>(tx.vout[n].target).key);
reconstructed_outPk[n].mask = tx.rct_signatures.outPk[n].mask;
}

// 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();
size_matches &= pubkeys[i].size() == rv.mixRing.size();
for (size_t i = 0; i < rv.mixRing.size(); ++i)
size_matches &= pubkeys.size() == rv.mixRing[i].size();
if (!size_matches)
{
LOG_PRINT_L1("Failed to check ringct signatures: mismatched pubkeys/mixRing size");
Expand All @@ -2642,12 +2616,12 @@ bool Blockchain::check_tx_inputs(const transaction& tx, tx_verification_context
{
for (size_t m = 0; m < pubkeys[n].size(); ++m)
{
if (pubkeys[n][m].dest != rct::rct2pk(mixRing[m][n].dest))
if (pubkeys[n][m].dest != rct::rct2pk(rv.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))
if (pubkeys[n][m].mask != rct::rct2pk(rv.mixRing[m][n].mask))
{
LOG_PRINT_L1("Failed to check ringct signatures: mismatched commitment at vin " << n << ", index " << m);
return false;
Expand All @@ -2656,29 +2630,29 @@ bool Blockchain::check_tx_inputs(const transaction& tx, tx_verification_context
}
}

if (II.size() != tx.vin.size())
if (rv.MG.II.size() != 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))
if (memcmp(&boost::get<txin_to_key>(tx.vin[n]).k_image, &rv.MG.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, outPk, rct::hash2rct(tx_prefix_hash)))
if (!rct::verRct(rv))
{
LOG_PRINT_L1("Failed to check ringct signatures!");
return false;
}
break;
}
default:
LOG_PRINT_L1("Unsupported rct type: " << tx.rct_signatures.type);
LOG_PRINT_L1("Unsupported rct type: " << rv.type);
return false;
}
}
Expand Down Expand Up @@ -2787,11 +2761,7 @@ bool Blockchain::check_tx_input(size_t tx_version, const txin_to_key& txin, cons
if (tx_version == 1) {
CHECK_AND_ASSERT_MES(sig.size() == output_keys.size(), false, "internal error: tx signatures count=" << sig.size() << " mismatch with outputs keys count for inputs=" << output_keys.size());
}
else
{
// rct signatures may be empty (and will be reconstructed later in the caller if so)
CHECK_AND_ASSERT_MES(rct_signatures.mixRing.empty() || rct_signatures.mixRing.size() == output_keys.size(), false, "internal error: tx rct signatures count=" << sig.size() << " mismatch with outputs keys count for inputs=" << output_keys.size());
}
// rct_signatures will be expanded after this
return true;
}
//------------------------------------------------------------------
Expand Down
15 changes: 13 additions & 2 deletions src/cryptonote_core/blockchain.h
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,7 @@ namespace cryptonote
* validates a transaction's inputs as correctly used and not previously
* spent. also returns the hash and height of the most recent block
* which contains an output that was used as an input to the transaction.
* The transaction's rct signatures, if any, are expanded.
*
* @param tx the transaction to validate
* @param pmax_used_block_height return-by-reference block height of most recent input
Expand All @@ -509,7 +510,7 @@ namespace cryptonote
*
* @return false if any input is invalid, otherwise true
*/
bool check_tx_inputs(const transaction& tx, uint64_t& pmax_used_block_height, crypto::hash& max_used_block_id, tx_verification_context &tvc, bool kept_by_block = false);
bool check_tx_inputs(transaction& tx, uint64_t& pmax_used_block_height, crypto::hash& max_used_block_id, tx_verification_context &tvc, bool kept_by_block = false);

/**
* @brief check that a transaction's outputs conform to current standards
Expand Down Expand Up @@ -919,6 +920,7 @@ namespace cryptonote
* This function validates transaction inputs and their keys. Previously
* it also performed double spend checking, but that has been moved to its
* own function.
* The transaction's rct signatures, if any, are expanded.
*
* If pmax_related_block_height is not NULL, its value is set to the height
* of the most recent block which contains an output used in any input set
Expand All @@ -932,7 +934,7 @@ namespace cryptonote
*
* @return false if any validation step fails, otherwise true
*/
bool check_tx_inputs(const transaction& tx, tx_verification_context &tvc, uint64_t* pmax_used_block_height = NULL);
bool check_tx_inputs(transaction& tx, tx_verification_context &tvc, uint64_t* pmax_used_block_height = NULL);

/**
* @brief performs a blockchain reorganization according to the longest chain rule
Expand Down Expand Up @@ -1211,5 +1213,14 @@ namespace cryptonote
* a useful state.
*/
void load_compiled_in_block_hashes();

/**
* @brief expands v2 transaction data from blockchain
*
* RingCT transactions do not transmit some of their data if it
* can be reconstituted by the receiver. This function expands
* that implicit data.
*/
bool expand_transaction_2(transaction &tx, const crypto::hash &tx_prefix_hash, const std::vector<std::vector<rct::ctkey>> &pubkeys);
};
} // namespace cryptonote
Loading

0 comments on commit d93746b

Please sign in to comment.