Skip to content

Commit

Permalink
fix: monero fork attack (#5603)
Browse files Browse the repository at this point in the history
Description
---
Forces the nonce of all Monero blocks to be 0. 

Motivation and Context
---
The Nonce field in the header is used when calculating the hash of the
block. When merge mining monero blocks, the nonce used by the monero
mining sits in pow->monero_header->nonce. It does not use the tari block
header nonce.

But this allows any node to change the nonce in the header and not break
the mined pow, but change the hash of monero header. This will cause the
network to fork of valid mined monero blocks.

How Has This Been Tested?
---
unit tests

RFC update: tari-project/rfcs#104
  • Loading branch information
SWvheerden committed Aug 8, 2023
1 parent 15c7e8f commit 9c81b4d
Show file tree
Hide file tree
Showing 5 changed files with 30 additions and 3 deletions.
2 changes: 2 additions & 0 deletions base_layer/core/src/blocks/block_header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ pub enum BlockHeaderValidationError {
ProofOfWorkError(#[from] PowError),
#[error("Monero seed hash too old")]
OldSeedHash,
#[error("Monero blocks must have a nonce of 0")]
InvalidNonce,
#[error("Incorrect height: Expected {expected} but got {actual}")]
InvalidHeight { expected: u64, actual: u64 },
#[error("Incorrect previous hash: Expected {expected} but got {actual}")]
Expand Down
2 changes: 1 addition & 1 deletion base_layer/core/src/proof_of_work/monero_rx/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ fn get_random_x_difficulty(input: &[u8], vm: &RandomXVMInstance) -> Result<(Diff
/// 1. The merkle proof and coinbase hash produce a matching merkle root
///
/// If these assertions pass, a valid `MoneroPowData` instance is returned
fn verify_header(header: &BlockHeader) -> Result<MoneroPowData, MergeMineError> {
pub fn verify_header(header: &BlockHeader) -> Result<MoneroPowData, MergeMineError> {
let monero_data = MoneroPowData::from_header(header)?;
let expected_merge_mining_hash = header.mining_hash();
let extra_field = ExtraField::try_parse(&monero_data.coinbase_tx.prefix.extra)
Expand Down
1 change: 1 addition & 0 deletions base_layer/core/src/proof_of_work/monero_rx/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub use helpers::{
extract_tari_hash,
randomx_difficulty,
serialize_monero_block_to_hex,
verify_header,
};

mod fixed_array;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,11 @@ fn check_pow_data<B: BlockchainBackend>(
use PowAlgorithm::{RandomX, Sha3x};
match block_header.pow.pow_algo {
RandomX => {
if block_header.nonce != 0 {
return Err(ValidationError::BlockHeaderError(
BlockHeaderValidationError::InvalidNonce,
));
}
let monero_data =
MoneroPowData::from_header(block_header).map_err(|e| ValidationError::CustomError(e.to_string()))?;
let seed_height = db.fetch_monero_seed_first_seen_height(&monero_data.randomx_key)?;
Expand Down
23 changes: 21 additions & 2 deletions base_layer/core/tests/tests/block_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ use tari_core::{
consensus::{consensus_constants::PowAlgorithmConstants, ConsensusConstantsBuilder, ConsensusManager},
proof_of_work::{
monero_rx,
monero_rx::{FixedByteArray, MoneroPowData},
monero_rx::{verify_header, FixedByteArray, MoneroPowData},
randomx_factory::RandomXFactory,
Difficulty,
PowAlgorithm,
Expand Down Expand Up @@ -152,7 +152,26 @@ async fn test_monero_blocks() {

// now lets fix the seed, and try again
add_monero_data(&mut block_3, seed2);
assert_block_add_result_added(&db.add_block(Arc::new(block_3)).unwrap());
// lets break the nonce count
let hash1 = block_3.hash();
block_3.header.nonce = 1;
let hash2 = block_3.hash();
assert_ne!(hash1, hash2);
assert!(verify_header(&block_3.header).is_ok());
match db.add_block(Arc::new(block_3.clone())) {
Err(ChainStorageError::ValidationError {
source: ValidationError::BlockHeaderError(BlockHeaderValidationError::InvalidNonce),
}) => (),
Err(e) => {
panic!("Failed due to other error:{:?}", e);
},
Ok(res) => {
panic!("Block add unexpectedly succeeded with result: {:?}", res);
},
};
// lets fix block3
block_3.header.nonce = 0;
assert_block_add_result_added(&db.add_block(Arc::new(block_3.clone())).unwrap());
}

fn add_monero_data(tblock: &mut Block, seed_key: &str) {
Expand Down

0 comments on commit 9c81b4d

Please sign in to comment.