blockchain: enforce 10 block age for spending outputs

Some custom wallet code apparently ignores this, which causes users
of that code to be fingerprinted
This commit is contained in:
moneromooo-monero 2019-09-04 13:59:36 +00:00
parent 2c171a9b02
commit a444f06e53
No known key found for this signature in database
GPG Key ID: 686F07454D6CEFC3
5 changed files with 51 additions and 6 deletions

View File

@ -163,6 +163,7 @@
#define HF_VERSION_MIN_V2_COINBASE_TX 12 #define HF_VERSION_MIN_V2_COINBASE_TX 12
#define HF_VERSION_SAME_MIXIN 12 #define HF_VERSION_SAME_MIXIN 12
#define HF_VERSION_REJECT_SIGS_IN_COINBASE 12 #define HF_VERSION_REJECT_SIGS_IN_COINBASE 12
#define HF_VERSION_ENFORCE_MIN_AGE 12
#define PER_KB_FEE_QUANTIZATION_DECIMALS 8 #define PER_KB_FEE_QUANTIZATION_DECIMALS 8

View File

@ -2982,6 +2982,9 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
const auto waiter_guard = epee::misc_utils::create_scope_leave_handler([&]() { waiter.wait(&tpool); }); const auto waiter_guard = epee::misc_utils::create_scope_leave_handler([&]() { waiter.wait(&tpool); });
int threads = tpool.get_max_concurrency(); int threads = tpool.get_max_concurrency();
uint64_t max_used_block_height = 0;
if (!pmax_used_block_height)
pmax_used_block_height = &max_used_block_height;
for (const auto& txin : tx.vin) for (const auto& txin : tx.vin)
{ {
// make sure output being spent is of type txin_to_key, rather than // make sure output being spent is of type txin_to_key, rather than
@ -3048,6 +3051,13 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
if (tx.version == 1 && threads > 1) if (tx.version == 1 && threads > 1)
waiter.wait(&tpool); waiter.wait(&tpool);
// enforce min output age
if (hf_version >= HF_VERSION_ENFORCE_MIN_AGE)
{
CHECK_AND_ASSERT_MES(*pmax_used_block_height + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE <= m_db->height(),
false, "Transaction spends at least one output which is too young");
}
if (tx.version == 1) if (tx.version == 1)
{ {
if (threads > 1) if (threads > 1)

View File

@ -208,6 +208,7 @@ int main(int argc, char* argv[])
GENERATE_AND_PLAY(gen_rct_tx_pre_rct_increase_vin_and_fee); GENERATE_AND_PLAY(gen_rct_tx_pre_rct_increase_vin_and_fee);
GENERATE_AND_PLAY(gen_rct_tx_pre_rct_altered_extra); GENERATE_AND_PLAY(gen_rct_tx_pre_rct_altered_extra);
GENERATE_AND_PLAY(gen_rct_tx_rct_altered_extra); GENERATE_AND_PLAY(gen_rct_tx_rct_altered_extra);
GENERATE_AND_PLAY(gen_rct_tx_uses_output_too_early);
GENERATE_AND_PLAY(gen_multisig_tx_valid_22_1_2); GENERATE_AND_PLAY(gen_multisig_tx_valid_22_1_2);
GENERATE_AND_PLAY(gen_multisig_tx_valid_22_1_2_many_inputs); GENERATE_AND_PLAY(gen_multisig_tx_valid_22_1_2_many_inputs);

View File

@ -40,8 +40,8 @@ using namespace cryptonote;
//---------------------------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------------------------
// Tests // Tests
bool gen_rct_tx_validation_base::generate_with(std::vector<test_event_entry>& events, bool gen_rct_tx_validation_base::generate_with_full(std::vector<test_event_entry>& events,
const int *out_idx, int mixin, uint64_t amount_paid, bool valid, const int *out_idx, int mixin, uint64_t amount_paid, size_t second_rewind, uint8_t last_version, const rct::RCTConfig &rct_config, bool valid,
const std::function<void(std::vector<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations)> &pre_tx, const std::function<void(std::vector<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations)> &pre_tx,
const std::function<void(transaction &tx)> &post_tx) const const std::function<void(transaction &tx)> &post_tx) const
{ {
@ -151,13 +151,13 @@ bool gen_rct_tx_validation_base::generate_with(std::vector<test_event_entry>& ev
// rewind // rewind
{ {
for (size_t i = 0; i < CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; ++i) for (size_t i = 0; i < second_rewind; ++i)
{ {
cryptonote::block blk; cryptonote::block blk;
CHECK_AND_ASSERT_MES(generator.construct_block_manually(blk, blk_last, miner_account, CHECK_AND_ASSERT_MES(generator.construct_block_manually(blk, blk_last, miner_account,
test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_hf_version | test_generator::bf_max_outs, test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_hf_version | test_generator::bf_max_outs,
4, 4, blk_last.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long last_version, last_version, blk_last.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long
crypto::hash(), 0, transaction(), std::vector<crypto::hash>(), 0, 6, 4), crypto::hash(), 0, transaction(), std::vector<crypto::hash>(), 0, 6, last_version),
false, "Failed to generate block"); false, "Failed to generate block");
events.push_back(blk); events.push_back(blk);
blk_last = blk; blk_last = blk;
@ -213,6 +213,8 @@ bool gen_rct_tx_validation_base::generate_with(std::vector<test_event_entry>& ev
td.addr = miner_account.get_keys().m_account_address; td.addr = miner_account.get_keys().m_account_address;
td.amount = amount_paid; td.amount = amount_paid;
std::vector<tx_destination_entry> destinations; std::vector<tx_destination_entry> destinations;
// from v12, we need two outputs at least
destinations.push_back(td);
destinations.push_back(td); destinations.push_back(td);
if (pre_tx) if (pre_tx)
@ -223,7 +225,7 @@ bool gen_rct_tx_validation_base::generate_with(std::vector<test_event_entry>& ev
std::vector<crypto::secret_key> additional_tx_keys; std::vector<crypto::secret_key> additional_tx_keys;
std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses; std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses;
subaddresses[miner_accounts[0].get_keys().m_account_address.m_spend_public_key] = {0,0}; subaddresses[miner_accounts[0].get_keys().m_account_address.m_spend_public_key] = {0,0};
bool r = construct_tx_and_get_tx_key(miner_accounts[0].get_keys(), subaddresses, sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), tx, 0, tx_key, additional_tx_keys, true); bool r = construct_tx_and_get_tx_key(miner_accounts[0].get_keys(), subaddresses, sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), tx, 0, tx_key, additional_tx_keys, true, rct_config);
CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction"); CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction");
if (post_tx) if (post_tx)
@ -237,6 +239,15 @@ bool gen_rct_tx_validation_base::generate_with(std::vector<test_event_entry>& ev
return true; return true;
} }
bool gen_rct_tx_validation_base::generate_with(std::vector<test_event_entry>& events,
const int *out_idx, int mixin, uint64_t amount_paid, bool valid,
const std::function<void(std::vector<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations)> &pre_tx,
const std::function<void(transaction &tx)> &post_tx) const
{
const rct::RCTConfig rct_config { rct::RangeProofBorromean, 0 };
return generate_with_full(events, out_idx, mixin, amount_paid, CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE, 4, rct_config, valid, pre_tx, post_tx);
}
bool gen_rct_tx_valid_from_pre_rct::generate(std::vector<test_event_entry>& events) const bool gen_rct_tx_valid_from_pre_rct::generate(std::vector<test_event_entry>& events) const
{ {
const int mixin = 2; const int mixin = 2;
@ -507,3 +518,11 @@ bool gen_rct_tx_rct_altered_extra::generate(std::vector<test_event_entry>& event
NULL, [&failed](transaction &tx) {std::string extra_nonce; crypto::hash pid = crypto::null_hash; set_payment_id_to_tx_extra_nonce(extra_nonce, pid); if (!add_extra_nonce_to_tx_extra(tx.extra, extra_nonce)) failed = true; }) && !failed; NULL, [&failed](transaction &tx) {std::string extra_nonce; crypto::hash pid = crypto::null_hash; set_payment_id_to_tx_extra_nonce(extra_nonce, pid); if (!add_extra_nonce_to_tx_extra(tx.extra, extra_nonce)) failed = true; }) && !failed;
} }
bool gen_rct_tx_uses_output_too_early::generate(std::vector<test_event_entry>& events) const
{
const int mixin = 10;
const int out_idx[] = {1, -1};
const uint64_t amount_paid = 10000;
const rct::RCTConfig rct_config { rct::RangeProofPaddedBulletproof, 2 };
return generate_with_full(events, out_idx, mixin, amount_paid, CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE-3, HF_VERSION_ENFORCE_MIN_AGE, rct_config, false, NULL, NULL);
}

View File

@ -69,6 +69,10 @@ struct gen_rct_tx_validation_base : public test_chain_unit_base
return true; return true;
} }
bool generate_with_full(std::vector<test_event_entry>& events, const int *out_idx, int mixin,
uint64_t amount_paid, size_t second_rewind, uint8_t last_version, const rct::RCTConfig &rct_config, bool valid,
const std::function<void(std::vector<cryptonote::tx_source_entry> &sources, std::vector<cryptonote::tx_destination_entry> &destinations)> &pre_tx,
const std::function<void(cryptonote::transaction &tx)> &post_tx) const;
bool generate_with(std::vector<test_event_entry>& events, const int *out_idx, int mixin, bool generate_with(std::vector<test_event_entry>& events, const int *out_idx, int mixin,
uint64_t amount_paid, bool valid, uint64_t amount_paid, bool valid,
const std::function<void(std::vector<cryptonote::tx_source_entry> &sources, std::vector<cryptonote::tx_destination_entry> &destinations)> &pre_tx, const std::function<void(std::vector<cryptonote::tx_source_entry> &sources, std::vector<cryptonote::tx_destination_entry> &destinations)> &pre_tx,
@ -262,3 +266,13 @@ struct gen_rct_tx_rct_altered_extra : public gen_rct_tx_validation_base
}; };
template<> struct get_test_options<gen_rct_tx_rct_altered_extra>: public get_test_options<gen_rct_tx_validation_base> {}; template<> struct get_test_options<gen_rct_tx_rct_altered_extra>: public get_test_options<gen_rct_tx_validation_base> {};
struct gen_rct_tx_uses_output_too_early : public gen_rct_tx_validation_base
{
bool generate(std::vector<test_event_entry>& events) const;
};
template<> struct get_test_options<gen_rct_tx_uses_output_too_early> {
const std::pair<uint8_t, uint64_t> hard_forks[5] = {std::make_pair(1, 0), std::make_pair(2, 1), std::make_pair(4, 65), std::make_pair(12, 69), std::make_pair(0, 0)};
const cryptonote::test_options test_options = {
hard_forks, 0
};
};