Skip to content

Commit

Permalink
Fix DoubleProposalProofs
Browse files Browse the repository at this point in the history
They required data unrelated to the signed proposals. It now uses the
same signing infrastructure by factoring it out to `TendermintProposal`
in a common crate.

Fixes #2981.
  • Loading branch information
hrxi committed Oct 23, 2024
1 parent 1ca95c2 commit 3da995c
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 81 deletions.
35 changes: 29 additions & 6 deletions blockchain/tests/history_store.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use nimiq_block::{
Block, DoubleProposalProof, DoubleVoteProof, EquivocationProof, ForkProof, MacroHeader,
MicroHeader,
Block, DoubleProposalProof, DoubleVoteProof, EquivocationProof, ForkProof, MicroHeader,
};
use nimiq_blockchain::interface::HistoryInterface;
use nimiq_blockchain_interface::{AbstractBlockchain, PushResult};
Expand All @@ -9,7 +8,9 @@ use nimiq_database::traits::WriteTransaction;
use nimiq_genesis::NetworkId;
use nimiq_hash::{Blake2sHash, HashOutput};
use nimiq_keys::{KeyPair, PrivateKey};
use nimiq_primitives::{policy::Policy, TendermintIdentifier, TendermintStep, TendermintVote};
use nimiq_primitives::{
policy::Policy, TendermintIdentifier, TendermintProposal, TendermintStep, TendermintVote,
};
use nimiq_serde::Deserialize;
use nimiq_test_log::test;
use nimiq_test_utils::{
Expand Down Expand Up @@ -135,15 +136,37 @@ fn do_double_proposal(
.clone()
.unwrap_macro()
.header;
let justification1 = signing_key.sign(MacroHeader::hash(&header1).as_bytes());
let justification2 = signing_key.sign(MacroHeader::hash(&header2).as_bytes());
let round = 0;
let valid_round = None;
let justification1 = signing_key.sign(
TendermintProposal {
proposal: &header1,
round,
valid_round,
}
.hash()
.as_bytes(),
);
let justification2 = signing_key.sign(
TendermintProposal {
proposal: &header2,
round,
valid_round,
}
.hash()
.as_bytes(),
);

// Produce the double proposal proof.
EquivocationProof::DoubleProposal(DoubleProposalProof::new(
validator_address(),
header1.clone(),
header1,
round,
valid_round,
justification1,
header2,
round,
valid_round,
justification2,
))
}
Expand Down
30 changes: 25 additions & 5 deletions blockchain/tests/inherents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use nimiq_primitives::{
networks::NetworkId,
policy::Policy,
slots_allocation::{JailedValidator, PenalizedSlot},
TendermintIdentifier, TendermintStep, TendermintVote,
TendermintIdentifier, TendermintProposal, TendermintStep, TendermintVote,
};
use nimiq_test_log::test;
use nimiq_test_utils::{
Expand Down Expand Up @@ -513,17 +513,37 @@ fn it_correctly_creates_inherents_from_double_proposal_proof() {
.next_block(vec![], false)
.unwrap_macro()
.header;
let header1_hash: Blake2bHash = header1.hash();
let header2_hash: Blake2bHash = header2.hash();
let justification1 = signing_key.sign(header1_hash.as_bytes());
let justification2 = signing_key.sign(header2_hash.as_bytes());
let round = 0;
let valid_round = None;
let justification1 = signing_key.sign(
TendermintProposal {
proposal: &header1,
round,
valid_round,
}
.hash()
.as_bytes(),
);
let justification2 = signing_key.sign(
TendermintProposal {
proposal: &header2,
round,
valid_round,
}
.hash()
.as_bytes(),
);

// Produce the double proposal proof.
let double_proposal_proof = DoubleProposalProof::new(
validator_address(),
header1.clone(),
round,
valid_round,
justification1,
header2,
round,
valid_round,
justification2,
);

Expand Down
30 changes: 25 additions & 5 deletions blockchain/tests/push.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use nimiq_hash::{Blake2bHash, Blake2sHash, HashOutput};
use nimiq_keys::KeyPair;
use nimiq_primitives::{
key_nibbles::KeyNibbles, networks::NetworkId, policy::Policy, TendermintIdentifier,
TendermintStep,
TendermintProposal, TendermintStep,
};
use nimiq_test_log::test;
use nimiq_test_utils::{
Expand Down Expand Up @@ -484,18 +484,38 @@ fn it_validates_double_proposal_proofs() {
.header;
let mut header2 = header1.clone();
header2.timestamp += 1;
let header1_hash: Blake2bHash = header1.hash();
let header2_hash: Blake2bHash = header2.hash();
let justification1 = signing_key.sign(header1_hash.as_bytes());
let justification2 = signing_key.sign(header2_hash.as_bytes());
let round = 0;
let valid_round = None;
let justification1 = signing_key.sign(
TendermintProposal {
proposal: &header1,
round,
valid_round,
}
.hash()
.as_bytes(),
);
let justification2 = signing_key.sign(
TendermintProposal {
proposal: &header2,
round,
valid_round,
}
.hash()
.as_bytes(),
);

expect_push_micro_block(
BlockConfig {
equivocation_proofs: vec![DoubleProposalProof::new(
validator_address(),
header1,
round,
valid_round,
justification1,
header2,
round,
valid_round,
justification2,
)
.into()],
Expand Down
85 changes: 74 additions & 11 deletions primitives/block/src/equivocation_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use nimiq_primitives::{
networks::NetworkId,
policy::Policy,
slots_allocation::{Validator, Validators},
TendermintIdentifier, TendermintStep, TendermintVote,
TendermintIdentifier, TendermintProposal, TendermintStep, TendermintVote,
};
use nimiq_serde::{Deserialize, Serialize, SerializedMaxSize};
use nimiq_transaction::{
Expand Down Expand Up @@ -317,6 +317,10 @@ pub struct DoubleProposalProof {
header1: MacroHeader,
/// Header number 2.
header2: MacroHeader,
round1: u32,
round2: u32,
valid_round1: Option<u32>,
valid_round2: Option<u32>,
/// Justification for header number 1.
justification1: SchnorrSignature,
/// Justification for header number 2.
Expand All @@ -327,20 +331,30 @@ impl DoubleProposalProof {
pub fn new(
validator_address: Address,
mut header1: MacroHeader,
mut round1: u32,
mut valid_round1: Option<u32>,
mut justification1: SchnorrSignature,
mut header2: MacroHeader,
mut round2: u32,
mut valid_round2: Option<u32>,
mut justification2: SchnorrSignature,
) -> DoubleProposalProof {
let hash1: Blake2bHash = header1.hash();
let hash2: Blake2bHash = header2.hash();
if hash1 > hash2 {
mem::swap(&mut header1, &mut header2);
mem::swap(&mut justification1, &mut justification2);
mem::swap(&mut round1, &mut round2);
mem::swap(&mut valid_round1, &mut valid_round2);
}
DoubleProposalProof {
validator_address,
header1,
header2,
round1,
round2,
valid_round1,
valid_round2,
justification1,
justification2,
}
Expand Down Expand Up @@ -405,13 +419,27 @@ impl DoubleProposalProof {

if self.header1.block_number != self.header2.block_number
|| self.header1.round != self.header2.round
|| self.round1 != self.round2
{
return Err(EquivocationProofError::SlotMismatch);
}

let signed_hash1: Blake2sHash = TendermintProposal {
proposal: &self.header1,
round: self.round1,
valid_round: self.valid_round1,
}
.hash();
let signed_hash2: Blake2sHash = TendermintProposal {
proposal: &self.header2,
round: self.round2,
valid_round: self.valid_round2,
}
.hash();

// Check that the justifications are valid.
if !signing_key.verify(&self.justification1, hash1.as_slice())
|| !signing_key.verify(&self.justification2, hash2.as_slice())
if !signing_key.verify(&self.justification1, signed_hash1.as_slice())
|| !signing_key.verify(&self.justification2, signed_hash2.as_slice())
{
return Err(EquivocationProofError::InvalidJustification);
}
Expand Down Expand Up @@ -596,7 +624,8 @@ mod test {
use nimiq_hash::{Blake2bHash, Blake2sHash, Hash, HashOutput};
use nimiq_keys::{Address, KeyPair, PrivateKey};
use nimiq_primitives::{
networks::NetworkId, policy::Policy, TendermintIdentifier, TendermintStep, TendermintVote,
networks::NetworkId, policy::Policy, TendermintIdentifier, TendermintProposal,
TendermintStep, TendermintVote,
};
use nimiq_serde::Deserialize;
use nimiq_vrf::VrfSeed;
Expand Down Expand Up @@ -794,53 +823,87 @@ mod test {
let mut header7 = header2.clone();
header7.round += 1;

let justification1 = key.sign(header1.hash().as_bytes());
let justification2 = key.sign(header2.hash().as_bytes());
let justification3 = key.sign(header3.hash().as_bytes());
let justification4 = key.sign(header4.hash().as_bytes());
let justification5 = key.sign(header5.hash().as_bytes());
let justification6 = key.sign(header6.hash().as_bytes());
let justification7 = key.sign(header7.hash().as_bytes());
let round = 0;
let valid_round = None;
let sign = |proposal: &MacroHeader| {
key.sign(
TendermintProposal {
proposal,
round,
valid_round,
}
.hash()
.as_bytes(),
)
};

let justification1 = sign(&header1);
let justification2 = sign(&header2);
let justification3 = sign(&header3);
let justification4 = sign(&header4);
let justification5 = sign(&header5);
let justification6 = sign(&header6);
let justification7 = sign(&header7);

let proof1: EquivocationProof = DoubleProposalProof::new(
Address::burn_address(),
header1.clone(),
round,
valid_round,
justification1.clone(),
header2.clone(),
round,
valid_round,
justification2.clone(),
)
.into();
let proof2: EquivocationProof = DoubleProposalProof::new(
Address::burn_address(),
header2.clone(),
round,
valid_round,
justification2.clone(),
header3.clone(),
round,
valid_round,
justification3.clone(),
)
.into();
let proof3: EquivocationProof = DoubleProposalProof::new(
Address::burn_address(),
header3.clone(),
round,
valid_round,
justification3.clone(),
header1.clone(),
round,
valid_round,
justification1.clone(),
)
.into();
// Different block height.
let proof4: EquivocationProof = DoubleProposalProof::new(
Address::burn_address(),
header4.clone(),
round,
valid_round,
justification4.clone(),
header5.clone(),
round,
valid_round,
justification5.clone(),
)
.into();
// Different round.
let proof5: EquivocationProof = DoubleProposalProof::new(
Address::burn_address(),
header6.clone(),
round,
valid_round,
justification6.clone(),
header7.clone(),
round,
valid_round,
justification7.clone(),
)
.into();
Expand Down
27 changes: 26 additions & 1 deletion primitives/src/tendermint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::{
io,
};

use nimiq_hash::{Blake2sHash, SerializeContent};
use nimiq_hash::{Blake2sHash, Hash, HashOutput, SerializeContent};
use nimiq_serde::{Deserialize, Serialize, SerializedSize};
use serde_repr::{Deserialize_repr, Serialize_repr};

Expand Down Expand Up @@ -55,6 +55,31 @@ impl Display for TendermintIdentifier {
}
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct TendermintProposal<T> {
pub proposal: T,
pub round: u32,
pub valid_round: Option<u32>,
}

impl<T: SerializeContent> SerializeContent for TendermintProposal<T> {
fn serialize_content<W: io::Write, H: HashOutput>(&self, writer: &mut W) -> io::Result<()> {
// First of all serialize that this is a proposal, this serves as the
// unique prefix for this message type.
TendermintStep::Propose.serialize_to_writer(writer)?;
self.proposal.serialize_content::<_, H>(writer)?;
self.round.serialize_to_writer(writer)?;
self.valid_round.serialize_to_writer(writer)?;
Ok(())
}
}

impl<T: SerializeContent> TendermintProposal<T> {
pub fn hash(&self) -> Blake2sHash {
Hash::hash(self)
}
}

// Multiple things this needs to take care of when it comes to what needs signing here:
// First of all to be able to create a block proof the signatures must be over a hash which includes:
// * block-height
Expand Down
Loading

0 comments on commit 3da995c

Please sign in to comment.