Skip to content

Commit

Permalink
executor: Sign votes and verify votes signatures (#16)
Browse files Browse the repository at this point in the history
* Introduce `SignedVote` type and remove address from round `Vote`

* Add facility for signing votes

* Verify votes signatures

* Better name for the module

* Doc comments

* Refactor signing facility into a `SigningScheme`
  • Loading branch information
romac committed Nov 8, 2023
1 parent 10d3490 commit a4e9899
Show file tree
Hide file tree
Showing 27 changed files with 449 additions and 289 deletions.
1 change: 0 additions & 1 deletion Code/QUESTIONS.md
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
- How do we deal with errors?
- How do we parametrize over values, id(v), etc.
4 changes: 1 addition & 3 deletions Code/TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,4 @@ if complete proposal from a past round => to current one
if we have some threshold (f+1) of votes for a future round => skip to that round

context (get proposer, get value)
signing contextt

abstract over values and validator sets
signing context
2 changes: 2 additions & 0 deletions Code/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ edition = "2021"
publish = false

[dependencies]
secrecy = "0.8.0"
signature = "2.1.0"
28 changes: 13 additions & 15 deletions Code/common/src/consensus.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::{
Address, Height, Proposal, PublicKey, Round, Validator, ValidatorSet, Value, ValueId, Vote,
Address, Height, PrivateKey, Proposal, PublicKey, Round, Signature, SignedVote, SigningScheme,
Validator, ValidatorSet, Value, ValueId, Vote,
};

/// This trait allows to abstract over the various datatypes
Expand All @@ -11,18 +12,23 @@ where
type Address: Address;
type Height: Height;
type Proposal: Proposal<Self>;
type PublicKey: PublicKey;
type Validator: Validator<Self>;
type ValidatorSet: ValidatorSet<Self>;
type Value: Value;
type Vote: Vote<Self>;

// FIXME: Remove this and thread it through where necessary
const DUMMY_ADDRESS: Self::Address;
type SigningScheme: SigningScheme; // TODO: Do we need to support multiple signing schemes?

// FIXME: Remove altogether
const DUMMY_VALUE: Self::Value;

/// Sign the given vote using the given private key.
/// TODO: Maybe move this as concrete methods in `SignedVote`?
fn sign_vote(vote: &Self::Vote, private_key: &PrivateKey<Self>) -> Signature<Self>;

/// Verify the given vote's signature using the given public key.
/// TODO: Maybe move this as concrete methods in `SignedVote`?
fn verify_signed_vote(signed_vote: &SignedVote<Self>, public_key: &PublicKey<Self>) -> bool;

/// Build a new proposal for the given value at the given height, round and POL round.
fn new_proposal(
height: Self::Height,
Expand All @@ -33,17 +39,9 @@ where

/// Build a new prevote vote by the validator with the given address,
/// for the value identified by the given value id, at the given round.
fn new_prevote(
round: Round,
value_id: Option<ValueId<Self>>,
address: Self::Address,
) -> Self::Vote;
fn new_prevote(round: Round, value_id: Option<ValueId<Self>>) -> Self::Vote;

/// Build a new precommit vote by the validator with the given address,
/// for the value identified by the given value id, at the given round.
fn new_precommit(
round: Round,
value_id: Option<ValueId<Self>>,
address: Self::Address,
) -> Self::Vote;
fn new_precommit(round: Round, value_id: Option<ValueId<Self>>) -> Self::Vote;
}
13 changes: 12 additions & 1 deletion Code/common/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Common data types and abstractions for the consensus engine.
#![no_std]
#![forbid(unsafe_code)]
#![deny(unused_crate_dependencies, trivial_casts, trivial_numeric_casts)]
#![warn(
Expand All @@ -14,19 +15,29 @@ mod consensus;
mod height;
mod proposal;
mod round;
mod signed_vote;
mod signing;
mod timeout;
mod validator_set;
mod value;
mod vote;

// Re-export `signature` crate for convenience
pub use ::signature;

/// Type alias to make it easier to refer the `ValueId` type of a given `Consensus` engine.
pub type ValueId<C> = <<C as Consensus>::Value as Value>::Id;
pub type PublicKey<C> = <<C as Consensus>::SigningScheme as SigningScheme>::PublicKey;
pub type PrivateKey<C> = <<C as Consensus>::SigningScheme as SigningScheme>::PrivateKey;
pub type Signature<C> = <<C as Consensus>::SigningScheme as SigningScheme>::Signature;

pub use consensus::Consensus;
pub use height::Height;
pub use proposal::Proposal;
pub use round::Round;
pub use signed_vote::SignedVote;
pub use signing::SigningScheme;
pub use timeout::{Timeout, TimeoutStep};
pub use validator_set::{Address, PublicKey, Validator, ValidatorSet, VotingPower};
pub use validator_set::{Address, Validator, ValidatorSet, VotingPower};
pub use value::Value;
pub use vote::{Vote, VoteType};
6 changes: 4 additions & 2 deletions Code/common/src/round.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use core::cmp;

/// A round number.
///
/// Can be either:
Expand Down Expand Up @@ -69,13 +71,13 @@ impl Round {
}

impl PartialOrd for Round {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}

impl Ord for Round {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.as_i64().cmp(&other.as_i64())
}
}
Expand Down
26 changes: 26 additions & 0 deletions Code/common/src/signed_vote.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use crate::{Consensus, Signature};

// TODO: Do we need to abstract over `SignedVote` as well?

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SignedVote<C>
where
C: Consensus,
{
pub vote: C::Vote,
pub address: C::Address,
pub signature: Signature<C>,
}

impl<C> SignedVote<C>
where
C: Consensus,
{
pub fn new(vote: C::Vote, address: C::Address, signature: Signature<C>) -> Self {
Self {
vote,
address,
signature,
}
}
}
20 changes: 20 additions & 0 deletions Code/common/src/signing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use core::fmt::Debug;

use secrecy::{CloneableSecret, DebugSecret, Zeroize};
use signature::{Keypair, Signer, Verifier};

pub trait SigningScheme
where
Self: Clone + Debug + Eq,
{
type Signature: Clone + Debug + Eq;

type PublicKey: Clone + Debug + Eq + Verifier<Self::Signature>;

type PrivateKey: Clone
+ Signer<Self::Signature>
+ Keypair<VerifyingKey = Self::PublicKey>
+ Zeroize
+ DebugSecret
+ CloneableSecret;
}
13 changes: 3 additions & 10 deletions Code/common/src/validator_set.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
use core::fmt::Debug;

use crate::Consensus;
use crate::{Consensus, PublicKey};

/// Voting power held by a validator.
///
/// TODO: Do we need to abstract over this as well?
pub type VotingPower = u64;

/// Defines the requirements for a public key type.
pub trait PublicKey
where
Self: Clone + Debug + PartialEq + Eq,
{
}

/// Defines the requirements for an address.
///
/// TODO: Keep this trait or just add the bounds to Consensus::Address?
Expand All @@ -33,7 +26,7 @@ where
fn address(&self) -> &C::Address;

/// The public key of the validator, used to verify signatures.
fn public_key(&self) -> &C::PublicKey;
fn public_key(&self) -> &PublicKey<C>;

/// The voting power held by the validaror.
fn voting_power(&self) -> VotingPower;
Expand All @@ -53,7 +46,7 @@ where
fn get_proposer(&self) -> C::Validator;

/// Get the validator with the given public key.
fn get_by_public_key(&self, public_key: &C::PublicKey) -> Option<&C::Validator>;
fn get_by_public_key(&self, public_key: &PublicKey<C>) -> Option<&C::Validator>;

/// Get the validator with the given address.
fn get_by_address(&self, address: &C::Address) -> Option<&C::Validator>;
Expand Down
4 changes: 0 additions & 4 deletions Code/common/src/vote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,4 @@ where

/// The type of vote.
fn vote_type(&self) -> VoteType;

// FIXME: round message votes should not include address
fn address(&self) -> &C::Address;
fn set_address(&mut self, address: C::Address);
}
2 changes: 2 additions & 0 deletions Code/consensus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ publish = false
malachite-common = { version = "0.1.0", path = "../common" }
malachite-round = { version = "0.1.0", path = "../round" }
malachite-vote = { version = "0.1.0", path = "../vote" }

secrecy = "0.8.0"
64 changes: 29 additions & 35 deletions Code/consensus/src/executor.rs
Original file line number Diff line number Diff line change
@@ -1,46 +1,40 @@
use std::collections::BTreeMap;

use secrecy::{ExposeSecret, Secret};

use malachite_common::signature::Keypair;
use malachite_common::{
Consensus, Proposal, Round, Timeout, TimeoutStep, Validator, ValidatorSet, Value, Vote,
VoteType,
Consensus, PrivateKey, Proposal, Round, SignedVote, Timeout, TimeoutStep, Validator,
ValidatorSet, Value, Vote, VoteType,
};
use malachite_round::events::Event as RoundEvent;
use malachite_round::message::Message as RoundMessage;
use malachite_round::state::State as RoundState;
use malachite_vote::count::Threshold;
use malachite_vote::keeper::VoteKeeper;

use crate::message::Message;

#[derive(Clone, Debug)]
pub struct Executor<C>
where
C: Consensus,
{
height: C::Height,
key: C::PublicKey,
key: Secret<PrivateKey<C>>,
validator_set: C::ValidatorSet,
round: Round,
votes: VoteKeeper<C>,
round_states: BTreeMap<Round, RoundState<C>>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Message<C>
where
C: Consensus,
{
NewRound(Round),
Proposal(C::Proposal),
Vote(C::Vote),
Timeout(Timeout),
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Output<C>
where
C: Consensus,
{
Propose(C::Proposal),
Vote(C::Vote),
Vote(SignedVote<C>),
Decide(Round, C::Value),
SetTimeout(Timeout),
}
Expand All @@ -49,7 +43,7 @@ impl<C> Executor<C>
where
C: Consensus,
{
pub fn new(height: C::Height, validator_set: C::ValidatorSet, key: C::PublicKey) -> Self {
pub fn new(height: C::Height, validator_set: C::ValidatorSet, key: PrivateKey<C>) -> Self {
let votes = VoteKeeper::new(
height.clone(),
Round::INITIAL,
Expand All @@ -58,7 +52,7 @@ where

Self {
height,
key,
key: Secret::new(key),
validator_set,
round: Round::INITIAL,
votes,
Expand Down Expand Up @@ -92,19 +86,17 @@ where
Some(Output::Propose(proposal))
}

RoundMessage::Vote(mut v) => {
// sign the vote

// FIXME: round message votes should not include address
RoundMessage::Vote(vote) => {
let address = self
.validator_set
.get_by_public_key(&self.key)?
.get_by_public_key(&self.key.expose_secret().verifying_key())?
.address()
.clone();

v.set_address(address);
let signature = C::sign_vote(&vote, self.key.expose_secret());
let signed_vote = SignedVote::new(vote, address, signature);

Some(Output::Vote(v))
Some(Output::Vote(signed_vote))
}

RoundMessage::Timeout(timeout) => Some(Output::SetTimeout(timeout)),
Expand All @@ -120,15 +112,15 @@ where
match msg {
Message::NewRound(round) => self.apply_new_round(round),
Message::Proposal(proposal) => self.apply_proposal(proposal),
Message::Vote(vote) => self.apply_vote(vote),
Message::Vote(signed_vote) => self.apply_vote(signed_vote),
Message::Timeout(timeout) => self.apply_timeout(timeout),
}
}

fn apply_new_round(&mut self, round: Round) -> Option<RoundMessage<C>> {
let proposer = self.validator_set.get_proposer();

let event = if proposer.public_key() == &self.key {
let event = if proposer.public_key() == &self.key.expose_secret().verifying_key() {
let value = self.get_value();
RoundEvent::NewRoundProposer(value)
} else {
Expand Down Expand Up @@ -185,18 +177,20 @@ where
}
}

fn apply_vote(&mut self, vote: C::Vote) -> Option<RoundMessage<C>> {
let Some(validator) = self.validator_set.get_by_address(vote.address()) else {
// TODO: Is this the correct behavior? How to log such "errors"?
fn apply_vote(&mut self, signed_vote: SignedVote<C>) -> Option<RoundMessage<C>> {
// TODO: How to handle missing validator?
let validator = self.validator_set.get_by_address(&signed_vote.address)?;

if !C::verify_signed_vote(&signed_vote, validator.public_key()) {
// TODO: How to handle invalid votes?
return None;
};
}

let round = vote.round();
let round = signed_vote.vote.round();

let event = match self.votes.apply_vote(vote, validator.voting_power()) {
Some(event) => event,
None => return None,
};
let event = self
.votes
.apply_vote(signed_vote.vote, validator.voting_power())?;

self.apply_event(round, event)
}
Expand Down
1 change: 1 addition & 0 deletions Code/consensus/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@
#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::panic))]

pub mod executor;
pub mod message;
Loading

0 comments on commit a4e9899

Please sign in to comment.