Skip to content

Commit d88dbd8

Browse files
committed
test(node): check process_candidate malleability
1 parent c4b3f41 commit d88dbd8

File tree

3 files changed

+212
-7
lines changed

3 files changed

+212
-7
lines changed

Cargo.lock

Lines changed: 18 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

node/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ witnet_util = { path = "../util" }
4242
witnet_validations = { path = "../validations" }
4343

4444
[dev-dependencies]
45+
env_logger = "0.9.0"
4546
glob = "0.3.0"
4647

4748
[features]

node/src/actors/chain_manager/mod.rs

Lines changed: 193 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2948,12 +2948,20 @@ pub fn run_dr_locally(dr: &DataRequestOutput) -> Result<RadonTypes, failure::Err
29482948
mod tests {
29492949
use crate::utils::test_actix_system;
29502950
use witnet_config::{config::consensus_constants_from_partial, defaults::Testnet};
2951+
use witnet_crypto::signature::sign;
29512952
use witnet_data_structures::{
29522953
chain::{
2953-
ChainInfo, Environment, KeyedSignature, PartialConsensusConstants, PublicKey,
2954-
ValueTransferOutput,
2954+
BlockMerkleRoots, BlockTransactions, ChainInfo, Environment, KeyedSignature,
2955+
PartialConsensusConstants, PublicKey, SecretKey, Signature, ValueTransferOutput,
29552956
},
2956-
transaction::{CommitTransaction, DRTransaction, RevealTransaction},
2957+
transaction::{CommitTransaction, DRTransaction, MintTransaction, RevealTransaction},
2958+
vrf::BlockEligibilityClaim,
2959+
};
2960+
use witnet_protected::Protected;
2961+
use witnet_validations::validations::{block_reward, merkle_tree_root};
2962+
2963+
use secp256k1::{
2964+
PublicKey as Secp256k1_PublicKey, Secp256k1, SecretKey as Secp256k1_SecretKey,
29572965
};
29582966

29592967
use super::*;
@@ -3368,4 +3376,186 @@ mod tests {
33683376
]
33693377
);
33703378
}
3379+
3380+
static PRIV_KEY_1: [u8; 32] = [0xcd; 32];
3381+
static PRIV_KEY_2: [u8; 32] = [0x43; 32];
3382+
3383+
fn build_merkle_tree(block_header: &mut BlockHeader, txns: &BlockTransactions) {
3384+
let merkle_roots = BlockMerkleRoots {
3385+
mint_hash: txns.mint.hash(),
3386+
vt_hash_merkle_root: merkle_tree_root(&txns.value_transfer_txns),
3387+
dr_hash_merkle_root: merkle_tree_root(&txns.data_request_txns),
3388+
commit_hash_merkle_root: merkle_tree_root(&txns.commit_txns),
3389+
reveal_hash_merkle_root: merkle_tree_root(&txns.reveal_txns),
3390+
tally_hash_merkle_root: merkle_tree_root(&txns.tally_txns),
3391+
};
3392+
block_header.merkle_roots = merkle_roots;
3393+
}
3394+
3395+
fn sign_tx<H: Hashable>(mk: [u8; 32], tx: &H) -> KeyedSignature {
3396+
let Hash::SHA256(data) = tx.hash();
3397+
3398+
let secp = &Secp256k1::new();
3399+
let secret_key =
3400+
Secp256k1_SecretKey::from_slice(&mk).expect("32 bytes, within curve order");
3401+
let public_key = Secp256k1_PublicKey::from_secret_key(secp, &secret_key);
3402+
let public_key = PublicKey::from(public_key);
3403+
3404+
let signature = sign(secp, secret_key, &data).unwrap();
3405+
3406+
KeyedSignature {
3407+
signature: Signature::from(signature),
3408+
public_key,
3409+
}
3410+
}
3411+
3412+
fn create_valid_block(chain_manager: &mut ChainManager, priv_key: &[u8; 32]) -> Block {
3413+
let vrf = &mut VrfCtx::secp256k1().unwrap();
3414+
let current_epoch = chain_manager.current_epoch.unwrap();
3415+
3416+
let consensus_constants = chain_manager.consensus_constants();
3417+
let secret_key = SecretKey {
3418+
bytes: Protected::from(priv_key.to_vec()),
3419+
};
3420+
let last_block_hash = chain_manager
3421+
.chain_state
3422+
.chain_info
3423+
.as_ref()
3424+
.unwrap()
3425+
.highest_block_checkpoint
3426+
.hash_prev_block;
3427+
let last_vrf_input = chain_manager
3428+
.chain_state
3429+
.chain_info
3430+
.as_ref()
3431+
.unwrap()
3432+
.highest_vrf_output
3433+
.hash_prev_vrf;
3434+
let block_beacon = CheckpointBeacon {
3435+
checkpoint: current_epoch,
3436+
hash_prev_block: last_block_hash,
3437+
};
3438+
3439+
let vrf_input = CheckpointVRF {
3440+
checkpoint: current_epoch,
3441+
hash_prev_vrf: last_vrf_input,
3442+
};
3443+
3444+
let my_pkh = PublicKeyHash::default();
3445+
3446+
let txns = BlockTransactions {
3447+
mint: MintTransaction::new(
3448+
current_epoch,
3449+
vec![ValueTransferOutput {
3450+
time_lock: 0,
3451+
pkh: my_pkh,
3452+
value: block_reward(
3453+
current_epoch,
3454+
consensus_constants.initial_block_reward,
3455+
consensus_constants.halving_period,
3456+
),
3457+
}],
3458+
),
3459+
..BlockTransactions::default()
3460+
};
3461+
3462+
let mut block_header = BlockHeader::default();
3463+
build_merkle_tree(&mut block_header, &txns);
3464+
block_header.beacon = block_beacon;
3465+
block_header.proof = BlockEligibilityClaim::create(vrf, &secret_key, vrf_input).unwrap();
3466+
3467+
let block_sig = sign_tx(*priv_key, &block_header);
3468+
3469+
Block::new(block_header, block_sig, txns)
3470+
}
3471+
3472+
#[test]
3473+
fn test_process_candidate_malleability() {
3474+
let _ = env_logger::builder().is_test(true).try_init();
3475+
test_actix_system(|| async {
3476+
let mut chain_manager = ChainManager::default();
3477+
3478+
chain_manager.current_epoch = Some(2000000);
3479+
// 1 epoch = 1000 seconds, for easy testing
3480+
chain_manager.epoch_constants = Some(EpochConstants {
3481+
checkpoint_zero_timestamp: 0,
3482+
checkpoints_period: 1_000,
3483+
});
3484+
chain_manager.chain_state.chain_info = Some(ChainInfo {
3485+
environment: Environment::default(),
3486+
consensus_constants: consensus_constants_from_partial(
3487+
&PartialConsensusConstants::default(),
3488+
&Testnet,
3489+
),
3490+
highest_block_checkpoint: CheckpointBeacon::default(),
3491+
highest_superblock_checkpoint: CheckpointBeacon {
3492+
checkpoint: 0,
3493+
hash_prev_block: Hash::SHA256([1; 32]),
3494+
},
3495+
highest_vrf_output: CheckpointVRF::default(),
3496+
});
3497+
chain_manager.chain_state.reputation_engine = Some(ReputationEngine::new(1000));
3498+
chain_manager.vrf_ctx = Some(VrfCtx::secp256k1().unwrap());
3499+
chain_manager.secp = Some(Secp256k1::new());
3500+
chain_manager.sm_state = StateMachine::Synced;
3501+
3502+
let block_1 = create_valid_block(&mut chain_manager, &PRIV_KEY_2);
3503+
let block_2 = create_valid_block(&mut chain_manager, &PRIV_KEY_1);
3504+
3505+
// block_1 should be better candidate than block_2
3506+
let vrf_ctx = &mut VrfCtx::secp256k1().unwrap();
3507+
let vrf_hash_1 = block_1
3508+
.block_header
3509+
.proof
3510+
.proof
3511+
.proof_to_hash(vrf_ctx)
3512+
.unwrap();
3513+
let vrf_hash_2 = block_2
3514+
.block_header
3515+
.proof
3516+
.proof
3517+
.proof_to_hash(vrf_ctx)
3518+
.unwrap();
3519+
assert_eq!(
3520+
compare_block_candidates(
3521+
block_1.hash(),
3522+
Reputation(0),
3523+
vrf_hash_1,
3524+
false,
3525+
block_2.hash(),
3526+
Reputation(0),
3527+
vrf_hash_2,
3528+
false,
3529+
&VrfSlots::new(vec![Hash::default()]),
3530+
),
3531+
Ordering::Greater
3532+
);
3533+
3534+
let mut block_mal_1 = block_1.clone();
3535+
// Malleability!
3536+
block_mal_1.txns.mint.outputs.clear();
3537+
// Changing block txns field does not change block hash
3538+
assert_eq!(block_1.hash(), block_mal_1.hash());
3539+
// But the blocks are different
3540+
assert_ne!(block_1, block_mal_1);
3541+
3542+
// Process the modified candidate first
3543+
chain_manager.process_candidate(block_mal_1);
3544+
// The best candidate should be None because this block is invalid
3545+
let best_cand = chain_manager.best_candidate.as_ref().map(|bc| &bc.block);
3546+
assert_eq!(best_cand, None);
3547+
3548+
// Process candidate with the same hash, but this one is valid
3549+
chain_manager.process_candidate(block_1.clone());
3550+
// The best candidate should be block_1
3551+
let best_cand = chain_manager.best_candidate.as_ref().map(|bc| &bc.block);
3552+
assert_eq!(best_cand, Some(&block_1));
3553+
3554+
// Process another valid candidate, but worse than the other one
3555+
chain_manager.process_candidate(block_2);
3556+
// The best candidate should still be block_1
3557+
let best_cand = chain_manager.best_candidate.as_ref().map(|bc| &bc.block);
3558+
assert_eq!(best_cand, Some(&block_1));
3559+
});
3560+
}
33713561
}

0 commit comments

Comments
 (0)