diff --git a/code/crates/consensus/src/effect.rs b/code/crates/consensus/src/effect.rs index 48d1fe504..859ab0e02 100644 --- a/code/crates/consensus/src/effect.rs +++ b/code/crates/consensus/src/effect.rs @@ -55,18 +55,6 @@ where /// Resume with: [`Resume::ValidatorSet`] GetValidatorSet(Ctx::Height), - /// Sign a vote with this node's private key - /// Resume with: [`Resume::SignedVote`] - SignVote(Ctx::Vote), - - /// Sign a proposal with this node's private key - /// Resume with: [`Resume::SignedProposal`] - SignProposal(Ctx::Proposal), - - /// Verify a signature - /// Resume with: [`Resume::SignatureValidity`] - VerifySignature(SignedMessage>, PublicKey), - /// Consensus has decided on a value /// Resume with: [`Resume::Continue`] Decide { certificate: CommitCertificate }, @@ -83,6 +71,21 @@ where /// Persist a timeout in the Write-Ahead Log for crash recovery PersistTimeout(Timeout), + + /// Sign a vote with this node's private key + /// Resume with: [`Resume::SignedVote`] + SignVote(Ctx::Vote), + + /// Sign a proposal with this node's private key + /// Resume with: [`Resume::SignedProposal`] + SignProposal(Ctx::Proposal), + + /// Verify a signature + /// Resume with: [`Resume::SignatureValidity`] + VerifySignature(SignedMessage>, PublicKey), + + /// Verify a commit certificate + VerifyCertificate(CommitCertificate, Ctx::ValidatorSet, ThresholdParams), } /// A value with which the consensus process can be resumed after yielding an [`Effect`]. @@ -103,7 +106,7 @@ where /// Resume execution with an optional validator set at the given height ValidatorSet(Ctx::Height, Option), - /// Resume execution with the validity of the signature just verified + /// Resume execution with the validity of the signature SignatureValidity(bool), /// Resume execution with the signed vote @@ -111,4 +114,7 @@ where /// Resume execution with the signed proposal SignedProposal(SignedMessage), + + /// Resume execution with the result of the verification of the [`CommitCertificate`] + CertificateValidity(Result<(), CertificateError>), } diff --git a/code/crates/consensus/src/handle/proposed_value.rs b/code/crates/consensus/src/handle/proposed_value.rs index bd66d3c2b..64197537f 100644 --- a/code/crates/consensus/src/handle/proposed_value.rs +++ b/code/crates/consensus/src/handle/proposed_value.rs @@ -3,6 +3,8 @@ use crate::prelude::*; use crate::handle::driver::apply_driver_input; use crate::types::ProposedValue; +use super::signature::sign_proposal; + #[tracing::instrument( skip_all, fields( @@ -52,9 +54,10 @@ where proposed_value.validator_address.clone(), ); - // TODO - keep unsigned proposals in keeper. For now we keep all happy - // by signing all "implicit" proposals with this node's key - let signed_proposal = Ctx::sign_proposal(&state.ctx, proposal); + // TODO: Keep unsigned proposals in keeper. + // For now we keep all happy by signing all "implicit" proposals with this node's key + let signed_proposal = sign_proposal(co, proposal).await?; + state.store_proposal(signed_proposal); } diff --git a/code/crates/consensus/src/handle/signature.rs b/code/crates/consensus/src/handle/signature.rs index 6bf589b3c..2e03f6833 100644 --- a/code/crates/consensus/src/handle/signature.rs +++ b/code/crates/consensus/src/handle/signature.rs @@ -44,3 +44,20 @@ where Ok(signed_proposal) } + +pub async fn verify_certificate( + co: &Co, + certificate: CommitCertificate, + validator_set: Ctx::ValidatorSet, + threshold_params: ThresholdParams, +) -> Result>, Error> +where + Ctx: Context, +{ + let result = perform!(co, + Effect::VerifyCertificate(certificate, validator_set, threshold_params), + Resume::CertificateValidity(result) => result + ); + + Ok(result) +} diff --git a/code/crates/consensus/src/handle/sync.rs b/code/crates/consensus/src/handle/sync.rs index 91c56961d..9c6b83535 100644 --- a/code/crates/consensus/src/handle/sync.rs +++ b/code/crates/consensus/src/handle/sync.rs @@ -1,6 +1,5 @@ -use std::borrow::Borrow; - use crate::handle::driver::apply_driver_input; +use crate::handle::signature::verify_certificate; use crate::handle::validator_set::get_validator_set; use crate::prelude::*; @@ -23,11 +22,14 @@ where return Err(Error::ValidatorSetNotFound(certificate.height)); }; - if let Err(e) = certificate.verify( - &state.ctx, - validator_set.borrow(), + if let Err(e) = verify_certificate( + co, + certificate.clone(), + validator_set.as_ref().clone(), state.params.threshold_params, - ) { + ) + .await? + { return Err(Error::InvalidCertificate(certificate, e)); } diff --git a/code/crates/consensus/tests/full_proposal.rs b/code/crates/consensus/tests/full_proposal.rs index eed4f4262..84330fad8 100644 --- a/code/crates/consensus/tests/full_proposal.rs +++ b/code/crates/consensus/tests/full_proposal.rs @@ -1,5 +1,7 @@ use malachite_consensus::{FullProposal, FullProposalKeeper, Input, ProposedValue}; -use malachite_core_types::{Context, Round, SignedProposal, Validity, ValueOrigin}; +use malachite_core_types::{ + Context, Round, SignedProposal, SigningProvider, Validity, ValueOrigin, +}; use malachite_test::utils::validators::make_validators; use malachite_test::{Address, Proposal, Value}; use malachite_test::{Height, TestContext}; @@ -13,7 +15,7 @@ fn signed_proposal_pol( address: Address, ) -> SignedProposal { let proposal1 = Proposal::new(height, round, value, pol_round, address); - ctx.sign_proposal(proposal1) + ctx.signing_provider().sign_proposal(proposal1) } fn prop( diff --git a/code/crates/core-types/src/certificate.rs b/code/crates/core-types/src/certificate.rs index 299dbb293..3577e350e 100644 --- a/code/crates/core-types/src/certificate.rs +++ b/code/crates/core-types/src/certificate.rs @@ -3,8 +3,8 @@ use derive_where::derive_where; use thiserror::Error; use crate::{ - Context, NilOrVal, Round, Signature, SignedExtension, SignedVote, ThresholdParams, Validator, - ValidatorSet, ValueId, Vote, VoteType, VotingPower, + Context, NilOrVal, Round, Signature, SignedExtension, SignedVote, ValueId, Vote, VoteType, + VotingPower, }; /// Represents a signature for a certificate, including the address and the signature itself. @@ -94,76 +94,6 @@ impl CommitCertificate { aggregated_signature, } } - - /// Verify the certificate against the given validator set. - /// - /// - For each commit signature in the certificate: - /// - Reconstruct the signed precommit and verify its signature - /// - Check that we have 2/3+ of voting power has signed the certificate - /// - /// If any of those steps fail, return a [`CertificateError`]. - /// - /// TODO: Move to Context - pub fn verify( - &self, - ctx: &Ctx, - validator_set: &Ctx::ValidatorSet, - thresholds: ThresholdParams, - ) -> Result<(), CertificateError> { - let total_voting_power = validator_set.total_voting_power(); - let mut signed_voting_power = 0; - - // For each commit signature, reconstruct the signed precommit and verify the signature - for commit_sig in &self.aggregated_signature.signatures { - // Abort if validator not in validator set - let Some(validator) = validator_set.get_by_address(&commit_sig.address) else { - return Err(CertificateError::UnknownValidator(commit_sig.clone())); - }; - - let voting_power = self.verify_commit_signature(ctx, commit_sig, validator)?; - signed_voting_power += voting_power; - } - - // Check if we have 2/3+ voting power - if thresholds - .quorum - .is_met(signed_voting_power, total_voting_power) - { - Ok(()) - } else { - Err(CertificateError::NotEnoughVotingPower { - signed: signed_voting_power, - total: total_voting_power, - expected: thresholds.quorum.min_expected(total_voting_power), - }) - } - } - - /// Verify a commit signature against the public key of its validator. - /// - /// ## Return - /// Return the voting power of that validator if the signature is valid. - fn verify_commit_signature( - &self, - ctx: &Ctx, - commit_sig: &CommitSignature, - validator: &Ctx::Validator, - ) -> Result> { - // Reconstruct the vote that was signed - let vote = Ctx::new_precommit( - self.height, - self.round, - NilOrVal::Val(self.value_id.clone()), - validator.address().clone(), - ); - - // Verify signature - if !ctx.verify_signed_vote(&vote, &commit_sig.signature, validator.public_key()) { - return Err(CertificateError::InvalidSignature(commit_sig.clone())); - } - - Ok(validator.voting_power()) - } } /// Represents an error that can occur when verifying a certificate. diff --git a/code/crates/core-types/src/context.rs b/code/crates/core-types/src/context.rs index 16068a55c..765f486dd 100644 --- a/code/crates/core-types/src/context.rs +++ b/code/crates/core-types/src/context.rs @@ -1,6 +1,7 @@ +use crate::signing::SigningProvider; use crate::{ - Address, Height, NilOrVal, Proposal, ProposalPart, PublicKey, Round, Signature, SignedMessage, - SigningScheme, Validator, ValidatorSet, Value, ValueId, Vote, + Address, Height, NilOrVal, Proposal, ProposalPart, Round, SigningScheme, Validator, + ValidatorSet, Value, ValueId, Vote, }; /// This trait allows to abstract over the various datatypes @@ -33,9 +34,12 @@ where /// The type of votes that can be cast. type Vote: Vote; - /// The signing scheme used to sign votes. + /// The signing scheme used to sign consensus messages. type SigningScheme: SigningScheme; + /// The signing provider used to sign and verify consensus messages. + type SigningProvider: SigningProvider; + /// Select a proposer in the validator set for the given height and round. fn select_proposer<'a>( &self, @@ -44,41 +48,8 @@ where round: Round, ) -> &'a Self::Validator; - /// Sign the given vote with our private key. - fn sign_vote(&self, vote: Self::Vote) -> SignedMessage; - - /// Verify the given vote's signature using the given public key. - fn verify_signed_vote( - &self, - vote: &Self::Vote, - signature: &Signature, - public_key: &PublicKey, - ) -> bool; - - /// Sign the given proposal with our private key. - fn sign_proposal(&self, proposal: Self::Proposal) -> SignedMessage; - - /// Verify the given proposal's signature using the given public key. - fn verify_signed_proposal( - &self, - proposal: &Self::Proposal, - signature: &Signature, - public_key: &PublicKey, - ) -> bool; - - /// Sign the proposal part with our private key. - fn sign_proposal_part( - &self, - proposal_part: Self::ProposalPart, - ) -> SignedMessage; - - /// Verify the given proposal part signature using the given public key. - fn verify_signed_proposal_part( - &self, - proposal_part: &Self::ProposalPart, - signature: &Signature, - public_key: &PublicKey, - ) -> bool; + /// Get the singing provider. + fn signing_provider(&self) -> &Self::SigningProvider; /// Build a new proposal for the given value at the given height, round and POL round. fn new_proposal( diff --git a/code/crates/core-types/src/lib.rs b/code/crates/core-types/src/lib.rs index a3c3a6c9d..d54bfb9c7 100644 --- a/code/crates/core-types/src/lib.rs +++ b/code/crates/core-types/src/lib.rs @@ -62,7 +62,7 @@ pub use proposal::{Proposal, Validity}; pub use proposal_part::ProposalPart; pub use round::Round; pub use signed_message::SignedMessage; -pub use signing::SigningScheme; +pub use signing::{SigningProvider, SigningProviderExt, SigningScheme}; pub use threshold::{Threshold, ThresholdParam, ThresholdParams}; pub use timeout::{Timeout, TimeoutKind}; pub use validator_set::{Address, Validator, ValidatorSet, VotingPower}; diff --git a/code/crates/core-types/src/signing.rs b/code/crates/core-types/src/signing.rs index f099001e4..843aef689 100644 --- a/code/crates/core-types/src/signing.rs +++ b/code/crates/core-types/src/signing.rs @@ -1,6 +1,11 @@ use alloc::vec::Vec; use core::fmt::{Debug, Display}; +use crate::{ + CertificateError, CommitCertificate, CommitSignature, Context, PublicKey, Signature, + SignedMessage, ThresholdParams, VotingPower, +}; + /// A signing scheme that can be used to sign votes and verify such signatures. /// /// This trait is used to abstract over the signature scheme used by the consensus engine. @@ -31,3 +36,138 @@ where /// Encode a signature to a byte array. fn encode_signature(signature: &Self::Signature) -> Vec; } + +/// A provider of signing functionality for the consensus engine. +/// +/// This trait defines the core signing operations needed by the engine, +/// including signing and verifying votes, proposals, proposal parts, and commit signatures. +/// It is parameterized by a context type `Ctx` that defines the specific types used +/// for votes, proposals, and other consensus-related data structures. +/// +/// Implementors of this trait are responsible for managing the private keys used for signing +/// and providing verification logic using the corresponding public keys. +pub trait SigningProvider +where + Ctx: Context, +{ + /// Sign the given vote with our private key. + fn sign_vote(&self, vote: Ctx::Vote) -> SignedMessage; + + /// Verify the given vote's signature using the given public key. + fn verify_signed_vote( + &self, + vote: &Ctx::Vote, + signature: &Signature, + public_key: &PublicKey, + ) -> bool; + + /// Sign the given proposal with our private key. + fn sign_proposal(&self, proposal: Ctx::Proposal) -> SignedMessage; + + /// Verify the given proposal's signature using the given public key. + fn verify_signed_proposal( + &self, + proposal: &Ctx::Proposal, + signature: &Signature, + public_key: &PublicKey, + ) -> bool; + + /// Sign the proposal part with our private key. + fn sign_proposal_part( + &self, + proposal_part: Ctx::ProposalPart, + ) -> SignedMessage; + + /// Verify the given proposal part signature using the given public key. + fn verify_signed_proposal_part( + &self, + proposal_part: &Ctx::ProposalPart, + signature: &Signature, + public_key: &PublicKey, + ) -> bool; + + /// Verify a commit signature in a certificate against the public key of its validator. + /// + /// ## Return + /// Return the voting power of that validator if the signature is valid. + fn verify_commit_signature( + &self, + certificate: &CommitCertificate, + commit_sig: &CommitSignature, + validator: &Ctx::Validator, + ) -> Result>; +} + +/// Extension trait providing additional certificate verification functionality for signing providers. +/// +/// This trait extends the base [`SigningProvider`] functionality with methods for verifying +/// commit certificates against validator sets. It is automatically implemented for any type +/// that implements [`SigningProvider`]. +pub trait SigningProviderExt +where + Ctx: Context, +{ + /// Verify the given certificate against the given validator set. + /// + /// - For each commit signature in the certificate: + /// - Reconstruct the signed precommit and verify its signature + /// - Check that we have 2/3+ of voting power has signed the certificate + /// + /// If any of those steps fail, return a [`CertificateError`]. + fn verify_certificate( + &self, + certificate: &CommitCertificate, + validator_set: &Ctx::ValidatorSet, + thresholds: ThresholdParams, + ) -> Result<(), CertificateError>; +} + +impl SigningProviderExt for P +where + Ctx: Context, + P: SigningProvider, +{ + /// Verify the certificate against the given validator set. + /// + /// - For each commit signature in the certificate: + /// - Reconstruct the signed precommit and verify its signature + /// - Check that we have 2/3+ of voting power has signed the certificate + /// + /// If any of those steps fail, return a [`CertificateError`]. + fn verify_certificate( + &self, + certificate: &CommitCertificate, + validator_set: &Ctx::ValidatorSet, + thresholds: ThresholdParams, + ) -> Result<(), CertificateError> { + use crate::ValidatorSet; + + let total_voting_power = validator_set.total_voting_power(); + let mut signed_voting_power = 0; + + // For each commit signature, reconstruct the signed precommit and verify the signature + for commit_sig in &certificate.aggregated_signature.signatures { + // Abort if validator not in validator set + let Some(validator) = validator_set.get_by_address(&commit_sig.address) else { + return Err(CertificateError::UnknownValidator(commit_sig.clone())); + }; + + let voting_power = self.verify_commit_signature(certificate, commit_sig, validator)?; + signed_voting_power += voting_power; + } + + // Check if we have 2/3+ voting power + if thresholds + .quorum + .is_met(signed_voting_power, total_voting_power) + { + Ok(()) + } else { + Err(CertificateError::NotEnoughVotingPower { + signed: signed_voting_power, + total: total_voting_power, + expected: thresholds.quorum.min_expected(total_voting_power), + }) + } + } +} diff --git a/code/crates/engine/src/consensus.rs b/code/crates/engine/src/consensus.rs index f7d45a065..1212bdc1d 100644 --- a/code/crates/engine/src/consensus.rs +++ b/code/crates/engine/src/consensus.rs @@ -3,8 +3,6 @@ use std::time::Duration; use async_trait::async_trait; use eyre::eyre; -use malachite_sync::InboundRequestId; - use ractor::{Actor, ActorProcessingErr, ActorRef, RpcReplyPort}; use tokio::time::Instant; use tracing::{debug, error, info, warn}; @@ -13,10 +11,13 @@ use malachite_codec as codec; use malachite_config::TimeoutConfig; use malachite_consensus::{Effect, PeerId, Resume, SignedConsensusMsg, ValueToPropose}; use malachite_core_types::{ - Context, Round, SignedExtension, Timeout, TimeoutKind, ValidatorSet, ValueOrigin, + Context, Round, SignedExtension, SigningProvider, SigningProviderExt, Timeout, TimeoutKind, + ValidatorSet, ValueOrigin, }; use malachite_metrics::Metrics; -use malachite_sync::{self as sync, Response, ValueResponse, VoteSetRequest, VoteSetResponse}; +use malachite_sync::{ + self as sync, InboundRequestId, Response, ValueResponse, VoteSetRequest, VoteSetResponse, +}; use crate::host::{HostMsg, HostRef, LocallyProposedValue, ProposedValue}; use crate::network::{NetworkEvent, NetworkMsg, NetworkRef, Status}; @@ -812,7 +813,7 @@ where Effect::SignProposal(proposal) => { let start = Instant::now(); - let signed_proposal = self.ctx.sign_proposal(proposal); + let signed_proposal = self.ctx.signing_provider().sign_proposal(proposal); self.metrics .signature_signing_time @@ -824,7 +825,7 @@ where Effect::SignVote(vote) => { let start = Instant::now(); - let signed_vote = self.ctx.sign_vote(vote); + let signed_vote = self.ctx.signing_provider().sign_vote(vote); self.metrics .signature_signing_time @@ -839,8 +840,16 @@ where let start = Instant::now(); let valid = match msg.message { - Msg::Vote(v) => self.ctx.verify_signed_vote(&v, &msg.signature, &pk), - Msg::Proposal(p) => self.ctx.verify_signed_proposal(&p, &msg.signature, &pk), + Msg::Vote(v) => { + self.ctx + .signing_provider() + .verify_signed_vote(&v, &msg.signature, &pk) + } + Msg::Proposal(p) => { + self.ctx + .signing_provider() + .verify_signed_proposal(&p, &msg.signature, &pk) + } }; self.metrics @@ -850,6 +859,16 @@ where Ok(Resume::SignatureValidity(valid)) } + Effect::VerifyCertificate(certificate, validator_set, threshold_params) => { + let valid = self.ctx.signing_provider().verify_certificate( + &certificate, + &validator_set, + threshold_params, + ); + + Ok(Resume::CertificateValidity(valid)) + } + Effect::Broadcast(msg) => { // Sync the WAL to disk before we broadcast the message // NOTE: The message has already been append to the WAL by the `PersistMessage` effect. diff --git a/code/crates/starknet/host/src/actor.rs b/code/crates/starknet/host/src/actor.rs index 50135ac8a..bc9d28616 100644 --- a/code/crates/starknet/host/src/actor.rs +++ b/code/crates/starknet/host/src/actor.rs @@ -385,7 +385,7 @@ async fn on_restream_value( height, proposal_round: round, valid_round, - proposer: address.clone(), + proposer: address, }; let signature = compute_proposal_signature(&init, &value_id, &state.host.private_key); diff --git a/code/crates/starknet/host/src/host/proposal.rs b/code/crates/starknet/host/src/host/proposal.rs index bd5b6b9c7..e66ffad93 100644 --- a/code/crates/starknet/host/src/host/proposal.rs +++ b/code/crates/starknet/host/src/host/proposal.rs @@ -71,7 +71,7 @@ async fn run_build_proposal_task( let init = ProposalInit { height, proposal_round: round, - proposer: proposer.clone(), + proposer, valid_round: Round::Nil, }; diff --git a/code/crates/starknet/host/src/host/starknet.rs b/code/crates/starknet/host/src/host/starknet.rs index 3ec867a19..50677ce93 100644 --- a/code/crates/starknet/host/src/host/starknet.rs +++ b/code/crates/starknet/host/src/host/starknet.rs @@ -111,7 +111,7 @@ impl Host for StarknetHost { build_proposal_task( height, round, - self.address.clone(), + self.address, self.private_key, self.params, deadline, diff --git a/code/crates/starknet/host/src/host/state.rs b/code/crates/starknet/host/src/host/state.rs index 498598e65..69e13944b 100644 --- a/code/crates/starknet/host/src/host/state.rs +++ b/code/crates/starknet/host/src/host/state.rs @@ -135,13 +135,7 @@ impl HostState { .verify_proposal_validity(init, &proposal_hash, &fin.signature) .await?; - Some(( - valid_round, - block_hash, - init.proposer.clone(), - validity, - extension, - )) + Some((valid_round, block_hash, init.proposer, validity, extension)) } async fn verify_proposal_validity( diff --git a/code/crates/starknet/host/src/spawn.rs b/code/crates/starknet/host/src/spawn.rs index 3462f8dba..27895e3b3 100644 --- a/code/crates/starknet/host/src/spawn.rs +++ b/code/crates/starknet/host/src/spawn.rs @@ -345,7 +345,7 @@ async fn spawn_host_actor( let mock_host = StarknetHost::new( mock_params, mempool.clone(), - address.clone(), + *address, *private_key, initial_validator_set.clone(), ); diff --git a/code/crates/starknet/host/src/streaming.rs b/code/crates/starknet/host/src/streaming.rs index 1306479d6..7eacd1201 100644 --- a/code/crates/starknet/host/src/streaming.rs +++ b/code/crates/starknet/host/src/streaming.rs @@ -149,7 +149,7 @@ impl PartStreamsMap { Some(ProposalParts { height: init_info.height, round: init_info.proposal_round, - proposer: init_info.proposer.clone(), + proposer: init_info.proposer, parts: to_emit, }) } @@ -177,7 +177,7 @@ impl PartStreamsMap { Some(ProposalParts { height: init_info.height, round: init_info.proposal_round, - proposer: init_info.proposer.clone(), + proposer: init_info.proposer, parts: to_emit, }) } diff --git a/code/crates/starknet/p2p-types/src/address.rs b/code/crates/starknet/p2p-types/src/address.rs index 1d6360832..c3694001a 100644 --- a/code/crates/starknet/p2p-types/src/address.rs +++ b/code/crates/starknet/p2p-types/src/address.rs @@ -7,7 +7,7 @@ use malachite_starknet_p2p_proto as p2p_proto; use crate::PublicKey; -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[serde(transparent)] pub struct Address(PublicKey); diff --git a/code/crates/starknet/p2p-types/src/context.rs b/code/crates/starknet/p2p-types/src/context.rs index b3c74076c..a4bbf8ed4 100644 --- a/code/crates/starknet/p2p-types/src/context.rs +++ b/code/crates/starknet/p2p-types/src/context.rs @@ -1,26 +1,24 @@ use std::sync::Arc; -use malachite_core_types::{ - Context, NilOrVal, Round, SignedProposal, SignedProposalPart, SignedVote, ValidatorSet as _, -}; -use starknet_core::utils::starknet_keccak; +use malachite_core_types::{Context, NilOrVal, Round, ValidatorSet as _}; +use crate::signing::EcdsaProvider; use crate::{ - Address, BlockHash, Height, PrivateKey, Proposal, ProposalPart, PublicKey, Signature, - SigningScheme, Validator, ValidatorSet, Vote, + Address, BlockHash, Ecdsa, Height, PrivateKey, Proposal, ProposalPart, Validator, ValidatorSet, + Vote, }; mod impls; #[derive(Clone, Debug)] pub struct MockContext { - private_key: Arc, + ecdsa_provider: Arc, } impl MockContext { pub fn new(private_key: PrivateKey) -> Self { Self { - private_key: Arc::new(private_key), + ecdsa_provider: Arc::new(EcdsaProvider::new(private_key)), } } } @@ -34,7 +32,12 @@ impl Context for MockContext { type Validator = Validator; type Value = BlockHash; type Vote = Vote; - type SigningScheme = SigningScheme; + type SigningScheme = Ecdsa; + type SigningProvider = EcdsaProvider; + + fn signing_provider(&self) -> &Self::SigningProvider { + &self.ecdsa_provider + } fn select_proposer<'a>( &self, @@ -57,54 +60,6 @@ impl Context for MockContext { .expect("proposer_index is valid") } - fn sign_vote(&self, vote: Self::Vote) -> SignedVote { - let hash = starknet_keccak(&vote.to_sign_bytes()); - let signature = self.private_key.sign(&hash); - SignedVote::new(vote, signature) - } - - fn verify_signed_vote( - &self, - vote: &Vote, - signature: &Signature, - public_key: &PublicKey, - ) -> bool { - let hash = starknet_keccak(&vote.to_sign_bytes()); - public_key.verify(&hash, signature) - } - - fn sign_proposal(&self, proposal: Self::Proposal) -> SignedProposal { - let hash = starknet_keccak(&proposal.to_sign_bytes()); - let signature = self.private_key.sign(&hash); - SignedProposal::new(proposal, signature) - } - - fn verify_signed_proposal( - &self, - proposal: &Proposal, - signature: &Signature, - public_key: &PublicKey, - ) -> bool { - let hash = starknet_keccak(&proposal.to_sign_bytes()); - public_key.verify(&hash, signature) - } - - fn sign_proposal_part(&self, proposal_part: Self::ProposalPart) -> SignedProposalPart { - let hash = starknet_keccak(&proposal_part.to_sign_bytes()); - let signature = self.private_key.sign(&hash); - SignedProposalPart::new(proposal_part, signature) - } - - fn verify_signed_proposal_part( - &self, - proposal_part: &ProposalPart, - signature: &Signature, - public_key: &PublicKey, - ) -> bool { - let hash = starknet_keccak(&proposal_part.to_sign_bytes()); - public_key.verify(&hash, signature) - } - fn new_proposal( height: Height, round: Round, diff --git a/code/crates/starknet/p2p-types/src/lib.rs b/code/crates/starknet/p2p-types/src/lib.rs index 4a7058a9a..819066d28 100644 --- a/code/crates/starknet/p2p-types/src/lib.rs +++ b/code/crates/starknet/p2p-types/src/lib.rs @@ -45,8 +45,4 @@ mod streaming; pub use streaming::{StreamContent, StreamMessage}; mod signing; - -pub type SigningScheme = signing::Ecdsa; -pub type Signature = signing::Signature; -pub type PublicKey = signing::PublicKey; -pub type PrivateKey = signing::PrivateKey; +pub use signing::{Ecdsa, EcdsaProvider, PrivateKey, PublicKey, Signature}; diff --git a/code/crates/starknet/p2p-types/src/signing.rs b/code/crates/starknet/p2p-types/src/signing.rs index 8911a5f58..f8a4193c0 100644 --- a/code/crates/starknet/p2p-types/src/signing.rs +++ b/code/crates/starknet/p2p-types/src/signing.rs @@ -1,14 +1,17 @@ use core::fmt; +use malachite_core_types::SigningScheme; use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; use starknet_core::crypto::{ecdsa_sign, ecdsa_verify}; use starknet_crypto::{get_public_key, Felt}; -use malachite_core_types::SigningScheme; use malachite_proto::{Error as ProtoError, Protobuf}; use malachite_starknet_p2p_proto as proto; +mod provider; +pub use provider::EcdsaProvider; + use crate::felt::FeltExt; #[derive(Copy, Clone, Debug, PartialEq, Eq)] diff --git a/code/crates/starknet/p2p-types/src/signing/provider.rs b/code/crates/starknet/p2p-types/src/signing/provider.rs new file mode 100644 index 000000000..56eda8a4b --- /dev/null +++ b/code/crates/starknet/p2p-types/src/signing/provider.rs @@ -0,0 +1,95 @@ +use starknet_core::utils::starknet_keccak; + +use malachite_core_types::{ + CertificateError, CommitCertificate, CommitSignature, NilOrVal, SignedProposal, + SignedProposalPart, SignedVote, SigningProvider, VotingPower, +}; + +use crate::{ + MockContext, PrivateKey, Proposal, ProposalPart, PublicKey, Signature, Validator, Vote, +}; + +#[derive(Debug)] +pub struct EcdsaProvider { + private_key: PrivateKey, +} + +impl EcdsaProvider { + pub fn new(private_key: PrivateKey) -> Self { + Self { private_key } + } +} + +impl SigningProvider for EcdsaProvider { + fn sign_vote(&self, vote: Vote) -> SignedVote { + let hash = starknet_keccak(&vote.to_sign_bytes()); + let signature = self.private_key.sign(&hash); + SignedVote::new(vote, signature) + } + + fn verify_signed_vote( + &self, + vote: &Vote, + signature: &Signature, + public_key: &PublicKey, + ) -> bool { + let hash = starknet_keccak(&vote.to_sign_bytes()); + public_key.verify(&hash, signature) + } + + fn sign_proposal(&self, proposal: Proposal) -> SignedProposal { + let hash = starknet_keccak(&proposal.to_sign_bytes()); + let signature = self.private_key.sign(&hash); + SignedProposal::new(proposal, signature) + } + + fn verify_signed_proposal( + &self, + proposal: &Proposal, + signature: &Signature, + public_key: &PublicKey, + ) -> bool { + let hash = starknet_keccak(&proposal.to_sign_bytes()); + public_key.verify(&hash, signature) + } + + fn sign_proposal_part(&self, proposal_part: ProposalPart) -> SignedProposalPart { + let hash = starknet_keccak(&proposal_part.to_sign_bytes()); + let signature = self.private_key.sign(&hash); + SignedProposalPart::new(proposal_part, signature) + } + + fn verify_signed_proposal_part( + &self, + proposal_part: &ProposalPart, + signature: &Signature, + public_key: &PublicKey, + ) -> bool { + let hash = starknet_keccak(&proposal_part.to_sign_bytes()); + public_key.verify(&hash, signature) + } + + fn verify_commit_signature( + &self, + certificate: &CommitCertificate, + commit_sig: &CommitSignature, + validator: &Validator, + ) -> Result> { + use malachite_core_types::Validator; + + // Reconstruct the vote that was signed + let vote = Vote::new_precommit( + certificate.height, + certificate.round, + NilOrVal::Val(certificate.value_id), + *validator.address(), + ); + + // Verify signature + if !self.verify_signed_vote(&vote, &commit_sig.signature, validator.public_key()) { + return Err(CertificateError::InvalidSignature(commit_sig.clone())); + } + + Ok(validator.voting_power()) + } +} diff --git a/code/crates/test/src/context.rs b/code/crates/test/src/context.rs index e3eda9afe..2d1319776 100644 --- a/code/crates/test/src/context.rs +++ b/code/crates/test/src/context.rs @@ -1,8 +1,6 @@ use std::sync::Arc; -use malachite_core_types::{ - Context, NilOrVal, Round, SignedProposal, SignedProposalPart, SignedVote, ValidatorSet as _, -}; +use malachite_core_types::{Context, NilOrVal, Round, ValidatorSet as _}; use crate::address::*; use crate::height::*; @@ -15,13 +13,13 @@ use crate::vote::*; #[derive(Clone, Debug)] pub struct TestContext { - private_key: Arc, + ed25519_provider: Arc, } impl TestContext { pub fn new(private_key: PrivateKey) -> Self { Self { - private_key: Arc::new(private_key), + ed25519_provider: Arc::new(Ed25519Provider::new(private_key)), } } } @@ -36,6 +34,11 @@ impl Context for TestContext { type Value = Value; type Vote = Vote; type SigningScheme = Ed25519; + type SigningProvider = Ed25519Provider; + + fn signing_provider(&self) -> &Self::SigningProvider { + &self.ed25519_provider + } fn select_proposer<'a>( &self, @@ -58,62 +61,6 @@ impl Context for TestContext { .expect("proposer_index is valid") } - #[cfg_attr(coverage_nightly, coverage(off))] - fn sign_vote(&self, vote: Self::Vote) -> SignedVote { - use signature::Signer; - let signature = self.private_key.sign(&vote.to_bytes()); - SignedVote::new(vote, signature) - } - - #[cfg_attr(coverage_nightly, coverage(off))] - fn verify_signed_vote( - &self, - vote: &Vote, - signature: &Signature, - public_key: &PublicKey, - ) -> bool { - use signature::Verifier; - public_key.verify(&vote.to_bytes(), signature).is_ok() - } - - #[cfg_attr(coverage_nightly, coverage(off))] - fn sign_proposal(&self, proposal: Self::Proposal) -> SignedProposal { - use signature::Signer; - let signature = self.private_key.sign(&proposal.to_bytes()); - SignedProposal::new(proposal, signature) - } - - #[cfg_attr(coverage_nightly, coverage(off))] - fn verify_signed_proposal( - &self, - proposal: &Proposal, - signature: &Signature, - public_key: &PublicKey, - ) -> bool { - use signature::Verifier; - public_key.verify(&proposal.to_bytes(), signature).is_ok() - } - - #[cfg_attr(coverage_nightly, coverage(off))] - fn sign_proposal_part(&self, proposal_part: Self::ProposalPart) -> SignedProposalPart { - use signature::Signer; - let signature = self.private_key.sign(&proposal_part.to_bytes()); - SignedProposalPart::new(proposal_part, signature) - } - - #[cfg_attr(coverage_nightly, coverage(off))] - fn verify_signed_proposal_part( - &self, - proposal_part: &ProposalPart, - signature: &Signature, - public_key: &PublicKey, - ) -> bool { - use signature::Verifier; - public_key - .verify(&proposal_part.to_bytes(), signature) - .is_ok() - } - fn new_proposal( height: Height, round: Round, diff --git a/code/crates/test/src/signing.rs b/code/crates/test/src/signing.rs index 2c0492d5c..f75657c64 100644 --- a/code/crates/test/src/signing.rs +++ b/code/crates/test/src/signing.rs @@ -1,5 +1,11 @@ +use malachite_core_types::{ + CertificateError, CommitCertificate, CommitSignature, NilOrVal, SignedProposal, + SignedProposalPart, SignedVote, SigningProvider, VotingPower, +}; pub use malachite_signing_ed25519::*; +use crate::{Proposal, ProposalPart, TestContext, Validator, Vote}; + pub trait Hashable { type Output; fn hash(&self) -> Self::Output; @@ -15,3 +21,97 @@ impl Hashable for PublicKey { hasher.finalize().into() } } + +#[derive(Debug)] +pub struct Ed25519Provider { + private_key: PrivateKey, +} + +impl Ed25519Provider { + pub fn new(private_key: PrivateKey) -> Self { + Self { private_key } + } +} + +impl SigningProvider for Ed25519Provider { + #[cfg_attr(coverage_nightly, coverage(off))] + fn sign_vote(&self, vote: Vote) -> SignedVote { + use signature::Signer; + let signature = self.private_key.sign(&vote.to_bytes()); + SignedVote::new(vote, signature) + } + + #[cfg_attr(coverage_nightly, coverage(off))] + fn verify_signed_vote( + &self, + vote: &Vote, + signature: &Signature, + public_key: &PublicKey, + ) -> bool { + use signature::Verifier; + public_key.verify(&vote.to_bytes(), signature).is_ok() + } + + #[cfg_attr(coverage_nightly, coverage(off))] + fn sign_proposal(&self, proposal: Proposal) -> SignedProposal { + use signature::Signer; + let signature = self.private_key.sign(&proposal.to_bytes()); + SignedProposal::new(proposal, signature) + } + + #[cfg_attr(coverage_nightly, coverage(off))] + fn verify_signed_proposal( + &self, + proposal: &Proposal, + signature: &Signature, + public_key: &PublicKey, + ) -> bool { + use signature::Verifier; + public_key.verify(&proposal.to_bytes(), signature).is_ok() + } + + #[cfg_attr(coverage_nightly, coverage(off))] + fn sign_proposal_part(&self, proposal_part: ProposalPart) -> SignedProposalPart { + use signature::Signer; + let signature = self.private_key.sign(&proposal_part.to_bytes()); + SignedProposalPart::new(proposal_part, signature) + } + + #[cfg_attr(coverage_nightly, coverage(off))] + fn verify_signed_proposal_part( + &self, + proposal_part: &ProposalPart, + signature: &Signature, + public_key: &PublicKey, + ) -> bool { + use signature::Verifier; + public_key + .verify(&proposal_part.to_bytes(), signature) + .is_ok() + } + + #[cfg_attr(coverage_nightly, coverage(off))] + fn verify_commit_signature( + &self, + certificate: &CommitCertificate, + commit_sig: &CommitSignature, + validator: &Validator, + ) -> Result> { + use malachite_core_types::Validator; + + // Reconstruct the vote that was signed + let vote = Vote::new_precommit( + certificate.height, + certificate.round, + NilOrVal::Val(certificate.value_id), + *validator.address(), + ); + + // Verify signature + if !self.verify_signed_vote(&vote, &commit_sig.signature, validator.public_key()) { + return Err(CertificateError::InvalidSignature(commit_sig.clone())); + } + + Ok(validator.voting_power()) + } +}