From f474c61e43cd35bacec40918820df33c0871ee32 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Fri, 21 Feb 2025 10:02:06 +0100 Subject: [PATCH 1/3] chore(code/starknet): Change signature scheme from ECDSA to Ed25519 --- code/Cargo.lock | 1 + code/crates/network/test/src/lib.rs | 7 +- code/crates/signing-ed25519/src/lib.rs | 6 +- code/crates/starknet/host/src/codec.rs | 21 +- .../crates/starknet/host/src/host/starknet.rs | 8 +- code/crates/starknet/host/src/node.rs | 14 +- code/crates/starknet/host/src/spawn.rs | 23 +- code/crates/starknet/p2p-types/Cargo.toml | 1 + code/crates/starknet/p2p-types/src/address.rs | 7 +- code/crates/starknet/p2p-types/src/context.rs | 6 +- code/crates/starknet/p2p-types/src/lib.rs | 4 +- code/crates/starknet/p2p-types/src/signing.rs | 300 ++++++------------ .../starknet/p2p-types/src/signing/ecdsa.rs | 218 +++++++++++++ code/crates/starknet/test/src/lib.rs | 2 +- 14 files changed, 378 insertions(+), 240 deletions(-) create mode 100644 code/crates/starknet/p2p-types/src/signing/ecdsa.rs diff --git a/code/Cargo.lock b/code/Cargo.lock index 19fe2503e..eeb4ec1e7 100644 --- a/code/Cargo.lock +++ b/code/Cargo.lock @@ -2425,6 +2425,7 @@ dependencies = [ "bytes", "informalsystems-malachitebft-core-types", "informalsystems-malachitebft-proto", + "informalsystems-malachitebft-signing-ed25519", "informalsystems-malachitebft-starknet-p2p-proto", "rand 0.8.5", "serde", diff --git a/code/crates/network/test/src/lib.rs b/code/crates/network/test/src/lib.rs index 8618aefb3..7b624a1b9 100644 --- a/code/crates/network/test/src/lib.rs +++ b/code/crates/network/test/src/lib.rs @@ -1,7 +1,7 @@ use core::fmt; use std::time::{Duration, SystemTime, UNIX_EPOCH}; -use libp2p_identity::{ecdsa, PeerId}; +use libp2p_identity::PeerId; use malachitebft_config::TransportProtocol; use malachitebft_metrics::SharedRegistry; use malachitebft_network::{ @@ -139,10 +139,7 @@ impl Test { ); std::array::from_fn(|_| { let privkey = PrivateKey::generate(&mut rng); - let pk_bytes = privkey.inner().to_bytes_be(); - let secret_key = ecdsa::SecretKey::try_from_bytes(pk_bytes).unwrap(); - let ecdsa_keypair = ecdsa::Keypair::from(secret_key); - Keypair::from(ecdsa_keypair) + Keypair::ed25519_from_bytes(privkey.inner().to_bytes()).unwrap() }) } diff --git a/code/crates/signing-ed25519/src/lib.rs b/code/crates/signing-ed25519/src/lib.rs index 24c3278a1..8be2fcbbe 100644 --- a/code/crates/signing-ed25519/src/lib.rs +++ b/code/crates/signing-ed25519/src/lib.rs @@ -157,7 +157,7 @@ impl Keypair for PrivateKey { } } -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", serde(transparent))] pub struct PublicKey( @@ -174,6 +174,10 @@ impl PublicKey { self.0.as_bytes() } + pub fn from_bytes(bytes: [u8; 32]) -> Self { + Self(ed25519_consensus::VerificationKey::try_from(bytes).unwrap()) + } + pub fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), signature::Error> { self.0 .verify(signature.inner(), msg) diff --git a/code/crates/starknet/host/src/codec.rs b/code/crates/starknet/host/src/codec.rs index a813f88e8..1e1f53e83 100644 --- a/code/crates/starknet/host/src/codec.rs +++ b/code/crates/starknet/host/src/codec.rs @@ -1,6 +1,6 @@ use bytes::Bytes; use malachitebft_app::streaming::StreamId; -use malachitebft_starknet_p2p_types::Signature; +use malachitebft_starknet_p2p_types::{Felt, FeltExt, Signature}; use prost::Message; use malachitebft_codec::Codec; @@ -350,7 +350,7 @@ pub fn decode_consensus_message( proto: proto::Vote, ) -> Result, ProtoError> { let vote = Vote::from_proto(proto)?; - let signature = p2p::Signature::dummy(); + let signature = p2p::Signature::test(); Ok(SignedConsensusMsg::Vote(SignedVote::new(vote, signature))) } @@ -418,6 +418,17 @@ where } } +pub fn encode_signature(_signature: &Signature) -> Result { + Ok(proto::ConsensusSignature { + r: Some(Felt::ONE.to_proto()?), + s: Some(Felt::ONE.to_proto()?), + }) +} + +pub fn decode_signature(_signature: proto::ConsensusSignature) -> Result { + Ok(Signature::test()) +} + pub fn decode_aggregated_signature( signature: proto::sync::AggregatedSignature, ) -> Result, ProtoError> { @@ -430,7 +441,7 @@ pub fn decode_aggregated_signature( .ok_or_else(|| { ProtoError::missing_field::("signature") }) - .and_then(p2p::Signature::from_proto)?; + .and_then(decode_signature)?; let address = s .validator_address @@ -454,7 +465,7 @@ pub fn encode_aggregate_signature( .iter() .map(|s| { let validator_address = s.address.to_proto()?; - let signature = s.signature.to_proto()?; + let signature = encode_signature(&s.signature)?; Ok(proto::sync::CommitSignature { validator_address: Some(validator_address), @@ -601,7 +612,7 @@ pub(crate) fn encode_vote(vote: &SignedVote) -> Result Option> { - let signature = Signature::dummy(); + let signature = Signature::test(); let vote = Vote::from_proto(msg).ok()?; Some(SignedVote::new(vote, signature)) } diff --git a/code/crates/starknet/host/src/host/starknet.rs b/code/crates/starknet/host/src/host/starknet.rs index 812a04d43..1dc6fd938 100644 --- a/code/crates/starknet/host/src/host/starknet.rs +++ b/code/crates/starknet/host/src/host/starknet.rs @@ -83,7 +83,7 @@ impl Host for StarknetHost { height, round, self.address, - self.private_key, + self.private_key.clone(), self.params, deadline, self.mempool.clone(), @@ -151,7 +151,7 @@ impl Host for StarknetHost { /// Sign a message hash async fn sign(&self, message: Self::MessageHash) -> Self::Signature { - self.private_key.sign(&message.as_felt()) + self.private_key.sign(message.as_bytes().as_slice()) } /// Validates the signature field of a message. If None returns false. @@ -161,7 +161,9 @@ impl Host for StarknetHost { signature: &Self::Signature, public_key: &Self::PublicKey, ) -> bool { - public_key.verify(&hash.as_felt(), signature) + public_key + .verify(hash.as_bytes().as_slice(), signature) + .is_ok() } /// Update the Context about which decision has been made. It is responsible for pinging any diff --git a/code/crates/starknet/host/src/node.rs b/code/crates/starknet/host/src/node.rs index 47c0131d8..f96d06f4b 100644 --- a/code/crates/starknet/host/src/node.rs +++ b/code/crates/starknet/host/src/node.rs @@ -1,6 +1,5 @@ use std::path::PathBuf; -use libp2p_identity::ecdsa; use ractor::async_trait; use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; @@ -12,7 +11,7 @@ use malachitebft_app::{Node, NodeHandle}; use malachitebft_config::Config; use malachitebft_core_types::VotingPower; use malachitebft_engine::node::NodeRef; -use malachitebft_starknet_p2p_types::EcdsaProvider; +use malachitebft_starknet_p2p_types::Ed25519Provider; use crate::spawn::spawn_node_actor; use crate::types::{Address, Height, MockContext, PrivateKey, PublicKey, Validator, ValidatorSet}; @@ -83,7 +82,7 @@ impl Node for StarknetNode { type Context = MockContext; type Genesis = Genesis; type PrivateKeyFile = PrivateKeyFile; - type SigningProvider = EcdsaProvider; + type SigningProvider = Ed25519Provider; type NodeHandle = Handle; fn get_home_dir(&self) -> PathBuf { @@ -106,10 +105,7 @@ impl Node for StarknetNode { } fn get_keypair(&self, pk: PrivateKey) -> Keypair { - let pk_bytes = pk.inner().to_bytes_be(); - let secret_key = ecdsa::SecretKey::try_from_bytes(pk_bytes).unwrap(); - let ecdsa_keypair = ecdsa::Keypair::from(secret_key); - Keypair::from(ecdsa_keypair) + Keypair::ed25519_from_bytes(pk.inner().to_bytes()).unwrap() } fn load_private_key(&self, file: Self::PrivateKeyFile) -> PrivateKey { @@ -126,7 +122,7 @@ impl Node for StarknetNode { } fn get_signing_provider(&self, private_key: PrivateKey) -> Self::SigningProvider { - EcdsaProvider::new(private_key) + Self::SigningProvider::new(private_key) } fn load_genesis(&self) -> std::io::Result { @@ -213,7 +209,7 @@ fn test_starknet_node() { file::save_priv_validator_key( &node, &node.private_key_file(), - &PrivateKeyFile::from(priv_keys[0]), + &PrivateKeyFile::from(priv_keys[0].clone()), ) .unwrap(); diff --git a/code/crates/starknet/host/src/spawn.rs b/code/crates/starknet/host/src/spawn.rs index 1b52b31ae..b57905b76 100644 --- a/code/crates/starknet/host/src/spawn.rs +++ b/code/crates/starknet/host/src/spawn.rs @@ -1,7 +1,6 @@ use std::path::{Path, PathBuf}; use std::time::Duration; -use libp2p_identity::ecdsa; use tokio::task::JoinHandle; use tracing::warn; @@ -18,7 +17,7 @@ use malachitebft_engine::util::events::TxEvent; use malachitebft_engine::wal::{Wal, WalRef}; use malachitebft_metrics::{Metrics, SharedRegistry}; use malachitebft_network::Keypair; -use malachitebft_starknet_p2p_types::EcdsaProvider; +use malachitebft_starknet_p2p_types::Ed25519Provider; use malachitebft_sync as sync; use malachitebft_test_mempool::Config as MempoolNetworkConfig; @@ -46,7 +45,7 @@ pub async fn spawn_node_actor( let registry = SharedRegistry::global().with_moniker(cfg.moniker.as_str()); let metrics = Metrics::register(®istry); let address = Address::from_public_key(private_key.public_key()); - let signing_provider = EcdsaProvider::new(private_key); + let signing_provider = Ed25519Provider::new(private_key.clone()); // Spawn mempool and its gossip layer let mempool_network = spawn_mempool_network_actor(&cfg, &private_key, ®istry, &span).await; @@ -156,7 +155,7 @@ async fn spawn_consensus_actor( address: Address, ctx: MockContext, cfg: NodeConfig, - signing_provider: EcdsaProvider, + signing_provider: Ed25519Provider, network: NetworkRef, host: HostRef, wal: WalRef, @@ -256,11 +255,15 @@ async fn spawn_network_actor( .unwrap() } -fn make_keypair(private_key: &PrivateKey) -> Keypair { - let pk_bytes = private_key.inner().to_bytes_be(); - let secret_key = ecdsa::SecretKey::try_from_bytes(pk_bytes).unwrap(); - let ecdsa_keypair = ecdsa::Keypair::from(secret_key); - Keypair::from(ecdsa_keypair) +// fn make_keypair(private_key: &PrivateKey) -> Keypair { +// let pk_bytes = private_key.inner().to_bytes_be(); +// let secret_key = ecdsa::SecretKey::try_from_bytes(pk_bytes).unwrap(); +// let ecdsa_keypair = ecdsa::Keypair::from(secret_key); +// Keypair::from(ecdsa_keypair) +// } + +fn make_keypair(pk: &PrivateKey) -> Keypair { + Keypair::ed25519_from_bytes(pk.inner().to_bytes()).unwrap() } async fn spawn_mempool_actor( @@ -335,7 +338,7 @@ async fn spawn_host_actor( mock_params, mempool.clone(), *address, - *private_key, + private_key.clone(), initial_validator_set.clone(), ); diff --git a/code/crates/starknet/p2p-types/Cargo.toml b/code/crates/starknet/p2p-types/Cargo.toml index ddbf737e5..e87192c7d 100644 --- a/code/crates/starknet/p2p-types/Cargo.toml +++ b/code/crates/starknet/p2p-types/Cargo.toml @@ -11,6 +11,7 @@ publish = false malachitebft-core-types.workspace = true malachitebft-proto.workspace = true malachitebft-starknet-p2p-proto.workspace = true +malachitebft-signing-ed25519 = { workspace = true, features = ["serde", "rand"] } starknet-core.workspace = true starknet-crypto.workspace = true diff --git a/code/crates/starknet/p2p-types/src/address.rs b/code/crates/starknet/p2p-types/src/address.rs index 846638b84..37cf08beb 100644 --- a/code/crates/starknet/p2p-types/src/address.rs +++ b/code/crates/starknet/p2p-types/src/address.rs @@ -26,7 +26,10 @@ impl Address { impl fmt::Display for Address { #[cfg_attr(coverage_nightly, coverage(off))] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) + for byte in self.0.as_bytes().iter() { + write!(f, "{:02X}", byte)?; + } + Ok(()) } } @@ -57,7 +60,7 @@ impl Protobuf for Address { fn to_proto(&self) -> Result { Ok(p2p_proto::Address { - elements: Bytes::copy_from_slice(&self.0.as_bytes()), + elements: Bytes::copy_from_slice(self.0.as_bytes().as_slice()), }) } } diff --git a/code/crates/starknet/p2p-types/src/context.rs b/code/crates/starknet/p2p-types/src/context.rs index d655eb114..3ee31ea0c 100644 --- a/code/crates/starknet/p2p-types/src/context.rs +++ b/code/crates/starknet/p2p-types/src/context.rs @@ -2,7 +2,9 @@ use bytes::Bytes; use malachitebft_core_types::{Context, NilOrVal, Round, ValidatorSet as _}; -use crate::{Address, Ecdsa, Hash, Height, Proposal, ProposalPart, Validator, ValidatorSet, Vote}; +use crate::{ + Address, Ed25519, Hash, Height, Proposal, ProposalPart, Validator, ValidatorSet, Vote, +}; mod impls; @@ -25,7 +27,7 @@ impl Context for MockContext { type Value = Hash; type Vote = Vote; type Extension = Bytes; - type SigningScheme = Ecdsa; + type SigningScheme = Ed25519; fn select_proposer<'a>( &self, diff --git a/code/crates/starknet/p2p-types/src/lib.rs b/code/crates/starknet/p2p-types/src/lib.rs index 067cc53e0..438b8199c 100644 --- a/code/crates/starknet/p2p-types/src/lib.rs +++ b/code/crates/starknet/p2p-types/src/lib.rs @@ -4,7 +4,7 @@ mod context; pub use context::MockContext; mod felt; -pub use felt::Felt; +pub use felt::{Felt, FeltExt}; mod address; pub use address::Address; @@ -49,4 +49,4 @@ mod streaming; pub use streaming::{StreamContent, StreamMessage}; mod signing; -pub use signing::{Ecdsa, EcdsaProvider, PrivateKey, PublicKey, Signature}; +pub use signing::{Ed25519, Ed25519Provider, PrivateKey, PublicKey, Signature}; diff --git a/code/crates/starknet/p2p-types/src/signing.rs b/code/crates/starknet/p2p-types/src/signing.rs index ce65bcac4..ee7e8736e 100644 --- a/code/crates/starknet/p2p-types/src/signing.rs +++ b/code/crates/starknet/p2p-types/src/signing.rs @@ -1,218 +1,118 @@ -use core::fmt; +use bytes::Bytes; +use malachitebft_core_types::{ + CertificateError, CommitCertificate, CommitSignature, NilOrVal, SignedExtension, + SignedProposal, SignedProposalPart, SignedVote, SigningProvider, VotingPower, +}; -use malachitebft_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}; +pub use malachitebft_signing_ed25519::{Ed25519, PrivateKey, PublicKey, Signature}; -use malachitebft_proto::{Error as ProtoError, Protobuf}; -use malachitebft_starknet_p2p_proto as proto; - -mod provider; -pub use provider::EcdsaProvider; - -use crate::felt::FeltExt; - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct Ecdsa; - -impl Ecdsa { - #[cfg_attr(coverage_nightly, coverage(off))] - pub fn generate_keypair(rng: R) -> PrivateKey - where - R: RngCore + CryptoRng, - { - PrivateKey::generate(rng) - } -} - -#[derive(Copy, Clone, Debug)] -pub struct InvalidSignatureLength(usize); - -impl fmt::Display for InvalidSignatureLength { - #[cfg_attr(coverage_nightly, coverage(off))] - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "Invalid signature length: got {}, expected {}", - self.0, - 32 * 3 - ) - } -} - -impl SigningScheme for Ecdsa { - type DecodingError = InvalidSignatureLength; - - type Signature = Signature; - type PublicKey = PublicKey; - type PrivateKey = PrivateKey; - - fn encode_signature(signature: &Self::Signature) -> Vec { - let mut result = Vec::with_capacity(64); - result.extend_from_slice(&signature.0.r.to_bytes_be()); - result.extend_from_slice(&signature.0.s.to_bytes_be()); - result - } - - fn decode_signature(bytes: &[u8]) -> Result { - if bytes.len() != 32 * 2 { - return Err(InvalidSignatureLength(bytes.len())); - } - - let r = Felt::from_bytes_be_slice(&bytes[0..32]); - let s = Felt::from_bytes_be_slice(&bytes[32..64]); - - Ok(Signature(starknet_crypto::Signature { r, s })) - } -} +use crate::{MockContext, Proposal, ProposalPart, Validator, Vote}; #[derive(Debug)] -pub struct Signature(starknet_crypto::Signature); - -impl Signature { - #[cfg_attr(coverage_nightly, coverage(off))] - pub fn inner(&self) -> &starknet_crypto::Signature { - &self.0 - } - - pub fn dummy() -> Self { - Self(starknet_crypto::Signature { - r: Felt::ZERO, - s: Felt::ZERO, - }) - } -} - -impl Clone for Signature { - #[cfg_attr(coverage_nightly, coverage(off))] - fn clone(&self) -> Self { - Self(starknet_crypto::Signature { - r: self.0.r, - s: self.0.s, - }) - } -} - -impl PartialEq for Signature { - #[cfg_attr(coverage_nightly, coverage(off))] - fn eq(&self, other: &Self) -> bool { - self.0.r == other.0.r && self.0.s == other.0.s - } -} - -impl Eq for Signature {} - -impl PartialOrd for Signature { - #[cfg_attr(coverage_nightly, coverage(off))] - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for Signature { - #[cfg_attr(coverage_nightly, coverage(off))] - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.0.r.cmp(&other.0.r).then(self.0.s.cmp(&other.0.s)) - } +pub struct Ed25519Provider { + private_key: PrivateKey, } -impl Protobuf for Signature { - type Proto = proto::ConsensusSignature; - - fn from_proto(proto: Self::Proto) -> Result { - let r = proto - .r - .ok_or_else(|| ProtoError::missing_field::("r"))?; - let s = proto - .s - .ok_or_else(|| ProtoError::missing_field::("s"))?; - - Ok(Self(starknet_crypto::Signature { - r: Felt::from_proto(r)?, - s: Felt::from_proto(s)?, - })) - } - - fn to_proto(&self) -> Result { - Ok(proto::ConsensusSignature { - r: Some(self.0.r.to_proto()?), - s: Some(self.0.s.to_proto()?), - }) - } -} - -#[derive(Copy, Clone, Debug, Serialize, Deserialize)] -#[serde(transparent)] -pub struct PrivateKey(Felt); - -impl PrivateKey { - #[cfg_attr(coverage_nightly, coverage(off))] - pub fn generate(mut rng: R) -> Self - where - R: RngCore + CryptoRng, - { - let mut bytes = [0u8; 32]; - rng.fill_bytes(&mut bytes); - Self::from(bytes) - } - - #[cfg_attr(coverage_nightly, coverage(off))] - pub fn public_key(&self) -> PublicKey { - PublicKey::new(get_public_key(&self.0)) +impl Ed25519Provider { + pub fn new(private_key: PrivateKey) -> Self { + Self { private_key } } - #[cfg_attr(coverage_nightly, coverage(off))] - pub fn inner(&self) -> Felt { - self.0 + pub fn private_key(&self) -> &PrivateKey { + &self.private_key } - pub fn sign(&self, message: &Felt) -> Signature { - let signature = ecdsa_sign(&self.0, message).unwrap(); - Signature(signature.into()) + pub fn sign(&self, data: &[u8]) -> Signature { + self.private_key.sign(data) } -} -impl From<[u8; 32]> for PrivateKey { - fn from(bytes: [u8; 32]) -> Self { - Self(Felt::from_bytes_be(&bytes)) + pub fn verify(&self, data: &[u8], signature: &Signature, public_key: &PublicKey) -> bool { + public_key.verify(data, signature).is_ok() } } -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -#[serde(transparent)] -pub struct PublicKey(Felt); - -impl PublicKey { - #[cfg_attr(coverage_nightly, coverage(off))] - pub fn new(key: Felt) -> Self { - Self(key) - } - - #[cfg_attr(coverage_nightly, coverage(off))] - pub fn from_bytes(bytes: [u8; 32]) -> Self { - Self::new(Felt::from_bytes_be(&bytes)) - } - - #[cfg_attr(coverage_nightly, coverage(off))] - pub fn as_bytes(&self) -> [u8; 32] { - self.0.to_bytes_be() - } - - #[cfg_attr(coverage_nightly, coverage(off))] - pub fn inner(&self) -> Felt { - self.0 - } - - pub fn verify(&self, message: &Felt, signature: &Signature) -> bool { - ecdsa_verify(&self.0, message, &signature.0).unwrap() - } -} +impl SigningProvider for Ed25519Provider { + fn sign_vote(&self, vote: Vote) -> SignedVote { + // Votes are not signed for now + SignedVote::new(vote, Signature::test()) + } + + fn verify_signed_vote( + &self, + _vote: &Vote, + _signature: &Signature, + _public_key: &PublicKey, + ) -> bool { + // Votes are not signed for now + true + } + + fn sign_proposal(&self, proposal: Proposal) -> SignedProposal { + // Proposals are never sent over the network + SignedProposal::new(proposal, Signature::test()) + } + + fn verify_signed_proposal( + &self, + _proposal: &Proposal, + _signature: &Signature, + _public_key: &PublicKey, + ) -> bool { + // Proposals are never sent over the network + true + } + + fn sign_proposal_part(&self, proposal_part: ProposalPart) -> SignedProposalPart { + // Proposal parts are not signed for now + SignedProposalPart::new(proposal_part, Signature::test()) + } + + fn verify_signed_proposal_part( + &self, + _proposal_part: &ProposalPart, + _signature: &Signature, + _public_key: &PublicKey, + ) -> bool { + // Proposal parts are not signed for now + true + } + + fn sign_vote_extension(&self, extension: Bytes) -> SignedExtension { + // Vote extensions are not enabled + SignedExtension::new(extension, Signature::test()) + } + + fn verify_signed_vote_extension( + &self, + _extension: &Bytes, + _signature: &Signature, + _public_key: &PublicKey, + ) -> bool { + // Vote extensions are not enabled + true + } + + fn verify_commit_signature( + &self, + certificate: &CommitCertificate, + commit_sig: &CommitSignature, + validator: &Validator, + ) -> Result> { + use malachitebft_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())); + } -impl fmt::Display for PublicKey { - #[cfg_attr(coverage_nightly, coverage(off))] - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.to_fixed_hex_string().fmt(f) + Ok(validator.voting_power()) } } diff --git a/code/crates/starknet/p2p-types/src/signing/ecdsa.rs b/code/crates/starknet/p2p-types/src/signing/ecdsa.rs new file mode 100644 index 000000000..ce65bcac4 --- /dev/null +++ b/code/crates/starknet/p2p-types/src/signing/ecdsa.rs @@ -0,0 +1,218 @@ +use core::fmt; + +use malachitebft_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 malachitebft_proto::{Error as ProtoError, Protobuf}; +use malachitebft_starknet_p2p_proto as proto; + +mod provider; +pub use provider::EcdsaProvider; + +use crate::felt::FeltExt; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct Ecdsa; + +impl Ecdsa { + #[cfg_attr(coverage_nightly, coverage(off))] + pub fn generate_keypair(rng: R) -> PrivateKey + where + R: RngCore + CryptoRng, + { + PrivateKey::generate(rng) + } +} + +#[derive(Copy, Clone, Debug)] +pub struct InvalidSignatureLength(usize); + +impl fmt::Display for InvalidSignatureLength { + #[cfg_attr(coverage_nightly, coverage(off))] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "Invalid signature length: got {}, expected {}", + self.0, + 32 * 3 + ) + } +} + +impl SigningScheme for Ecdsa { + type DecodingError = InvalidSignatureLength; + + type Signature = Signature; + type PublicKey = PublicKey; + type PrivateKey = PrivateKey; + + fn encode_signature(signature: &Self::Signature) -> Vec { + let mut result = Vec::with_capacity(64); + result.extend_from_slice(&signature.0.r.to_bytes_be()); + result.extend_from_slice(&signature.0.s.to_bytes_be()); + result + } + + fn decode_signature(bytes: &[u8]) -> Result { + if bytes.len() != 32 * 2 { + return Err(InvalidSignatureLength(bytes.len())); + } + + let r = Felt::from_bytes_be_slice(&bytes[0..32]); + let s = Felt::from_bytes_be_slice(&bytes[32..64]); + + Ok(Signature(starknet_crypto::Signature { r, s })) + } +} + +#[derive(Debug)] +pub struct Signature(starknet_crypto::Signature); + +impl Signature { + #[cfg_attr(coverage_nightly, coverage(off))] + pub fn inner(&self) -> &starknet_crypto::Signature { + &self.0 + } + + pub fn dummy() -> Self { + Self(starknet_crypto::Signature { + r: Felt::ZERO, + s: Felt::ZERO, + }) + } +} + +impl Clone for Signature { + #[cfg_attr(coverage_nightly, coverage(off))] + fn clone(&self) -> Self { + Self(starknet_crypto::Signature { + r: self.0.r, + s: self.0.s, + }) + } +} + +impl PartialEq for Signature { + #[cfg_attr(coverage_nightly, coverage(off))] + fn eq(&self, other: &Self) -> bool { + self.0.r == other.0.r && self.0.s == other.0.s + } +} + +impl Eq for Signature {} + +impl PartialOrd for Signature { + #[cfg_attr(coverage_nightly, coverage(off))] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Signature { + #[cfg_attr(coverage_nightly, coverage(off))] + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.0.r.cmp(&other.0.r).then(self.0.s.cmp(&other.0.s)) + } +} + +impl Protobuf for Signature { + type Proto = proto::ConsensusSignature; + + fn from_proto(proto: Self::Proto) -> Result { + let r = proto + .r + .ok_or_else(|| ProtoError::missing_field::("r"))?; + let s = proto + .s + .ok_or_else(|| ProtoError::missing_field::("s"))?; + + Ok(Self(starknet_crypto::Signature { + r: Felt::from_proto(r)?, + s: Felt::from_proto(s)?, + })) + } + + fn to_proto(&self) -> Result { + Ok(proto::ConsensusSignature { + r: Some(self.0.r.to_proto()?), + s: Some(self.0.s.to_proto()?), + }) + } +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +#[serde(transparent)] +pub struct PrivateKey(Felt); + +impl PrivateKey { + #[cfg_attr(coverage_nightly, coverage(off))] + pub fn generate(mut rng: R) -> Self + where + R: RngCore + CryptoRng, + { + let mut bytes = [0u8; 32]; + rng.fill_bytes(&mut bytes); + Self::from(bytes) + } + + #[cfg_attr(coverage_nightly, coverage(off))] + pub fn public_key(&self) -> PublicKey { + PublicKey::new(get_public_key(&self.0)) + } + + #[cfg_attr(coverage_nightly, coverage(off))] + pub fn inner(&self) -> Felt { + self.0 + } + + pub fn sign(&self, message: &Felt) -> Signature { + let signature = ecdsa_sign(&self.0, message).unwrap(); + Signature(signature.into()) + } +} + +impl From<[u8; 32]> for PrivateKey { + fn from(bytes: [u8; 32]) -> Self { + Self(Felt::from_bytes_be(&bytes)) + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[serde(transparent)] +pub struct PublicKey(Felt); + +impl PublicKey { + #[cfg_attr(coverage_nightly, coverage(off))] + pub fn new(key: Felt) -> Self { + Self(key) + } + + #[cfg_attr(coverage_nightly, coverage(off))] + pub fn from_bytes(bytes: [u8; 32]) -> Self { + Self::new(Felt::from_bytes_be(&bytes)) + } + + #[cfg_attr(coverage_nightly, coverage(off))] + pub fn as_bytes(&self) -> [u8; 32] { + self.0.to_bytes_be() + } + + #[cfg_attr(coverage_nightly, coverage(off))] + pub fn inner(&self) -> Felt { + self.0 + } + + pub fn verify(&self, message: &Felt, signature: &Signature) -> bool { + ecdsa_verify(&self.0, message, &signature.0).unwrap() + } +} + +impl fmt::Display for PublicKey { + #[cfg_attr(coverage_nightly, coverage(off))] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.to_fixed_hex_string().fmt(f) + } +} diff --git a/code/crates/starknet/test/src/lib.rs b/code/crates/starknet/test/src/lib.rs index 25a8421d2..b63d6a51d 100644 --- a/code/crates/starknet/test/src/lib.rs +++ b/code/crates/starknet/test/src/lib.rs @@ -103,7 +103,7 @@ impl NodeRunner for TestRunner { fs::create_dir_all(app.genesis_file().parent().unwrap())?; fs::write(app.genesis_file(), serde_json::to_string(&genesis)?)?; - let priv_key_file = app.make_private_key_file(self.private_keys[&id]); + let priv_key_file = app.make_private_key_file(self.private_keys[&id].clone()); fs::create_dir_all(app.private_key_file().parent().unwrap())?; fs::write( app.private_key_file(), From 34b0d8ef53ac56ce37c6ae3735707373061d39c1 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Fri, 21 Feb 2025 14:49:14 +0100 Subject: [PATCH 2/3] Fix MBT test --- .../starknet/test/mbt/src/tests/streaming/utils.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/code/crates/starknet/test/mbt/src/tests/streaming/utils.rs b/code/crates/starknet/test/mbt/src/tests/streaming/utils.rs index d8cb8dfbe..6f60ac163 100644 --- a/code/crates/starknet/test/mbt/src/tests/streaming/utils.rs +++ b/code/crates/starknet/test/mbt/src/tests/streaming/utils.rs @@ -4,7 +4,7 @@ use crate::streaming::{Buffer, Message}; use malachitebft_core_types::Round; use malachitebft_engine::util::streaming::{Sequence, StreamId}; use malachitebft_engine::util::streaming::{StreamContent, StreamMessage}; -use malachitebft_starknet_host::types::TransactionBatch; +use malachitebft_starknet_host::types::{PrivateKey, TransactionBatch}; use malachitebft_starknet_host::{ streaming::MinHeap, types::{Address, Height, ProposalInit, ProposalPart, Transaction}, @@ -74,11 +74,13 @@ pub fn init_message_to_proposal_init(message: &Option) -> Option ProposalInit { let bytes: [u8; 32] = [ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, - 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, - 0x1F, 0x20, + 0x08, 0xd7, 0xa2, 0x0f, 0x32, 0x0c, 0x4d, 0x23, 0xd9, 0xad, 0xf3, 0x29, 0xf5, 0x7c, 0x7b, + 0x62, 0x35, 0x9d, 0x97, 0xce, 0x0b, 0xb3, 0xb7, 0x66, 0x19, 0xd8, 0x50, 0x4d, 0x59, 0xa1, + 0x88, 0x4b, ]; - let proposer_addr = Address::new(bytes); + let private_key = PrivateKey::from(bytes); + + let proposer_addr = Address::new(*private_key.public_key().as_bytes()); let height = Height { block_number: 1, From fb665b460aef6d2acf312f3966514d3359d33c3f Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Fri, 21 Feb 2025 16:36:14 +0100 Subject: [PATCH 3/3] Remove dead code --- code/crates/starknet/host/src/spawn.rs | 7 - .../starknet/p2p-types/src/signing/ecdsa.rs | 218 ------------------ 2 files changed, 225 deletions(-) delete mode 100644 code/crates/starknet/p2p-types/src/signing/ecdsa.rs diff --git a/code/crates/starknet/host/src/spawn.rs b/code/crates/starknet/host/src/spawn.rs index b57905b76..c6752652b 100644 --- a/code/crates/starknet/host/src/spawn.rs +++ b/code/crates/starknet/host/src/spawn.rs @@ -255,13 +255,6 @@ async fn spawn_network_actor( .unwrap() } -// fn make_keypair(private_key: &PrivateKey) -> Keypair { -// let pk_bytes = private_key.inner().to_bytes_be(); -// let secret_key = ecdsa::SecretKey::try_from_bytes(pk_bytes).unwrap(); -// let ecdsa_keypair = ecdsa::Keypair::from(secret_key); -// Keypair::from(ecdsa_keypair) -// } - fn make_keypair(pk: &PrivateKey) -> Keypair { Keypair::ed25519_from_bytes(pk.inner().to_bytes()).unwrap() } diff --git a/code/crates/starknet/p2p-types/src/signing/ecdsa.rs b/code/crates/starknet/p2p-types/src/signing/ecdsa.rs deleted file mode 100644 index ce65bcac4..000000000 --- a/code/crates/starknet/p2p-types/src/signing/ecdsa.rs +++ /dev/null @@ -1,218 +0,0 @@ -use core::fmt; - -use malachitebft_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 malachitebft_proto::{Error as ProtoError, Protobuf}; -use malachitebft_starknet_p2p_proto as proto; - -mod provider; -pub use provider::EcdsaProvider; - -use crate::felt::FeltExt; - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct Ecdsa; - -impl Ecdsa { - #[cfg_attr(coverage_nightly, coverage(off))] - pub fn generate_keypair(rng: R) -> PrivateKey - where - R: RngCore + CryptoRng, - { - PrivateKey::generate(rng) - } -} - -#[derive(Copy, Clone, Debug)] -pub struct InvalidSignatureLength(usize); - -impl fmt::Display for InvalidSignatureLength { - #[cfg_attr(coverage_nightly, coverage(off))] - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "Invalid signature length: got {}, expected {}", - self.0, - 32 * 3 - ) - } -} - -impl SigningScheme for Ecdsa { - type DecodingError = InvalidSignatureLength; - - type Signature = Signature; - type PublicKey = PublicKey; - type PrivateKey = PrivateKey; - - fn encode_signature(signature: &Self::Signature) -> Vec { - let mut result = Vec::with_capacity(64); - result.extend_from_slice(&signature.0.r.to_bytes_be()); - result.extend_from_slice(&signature.0.s.to_bytes_be()); - result - } - - fn decode_signature(bytes: &[u8]) -> Result { - if bytes.len() != 32 * 2 { - return Err(InvalidSignatureLength(bytes.len())); - } - - let r = Felt::from_bytes_be_slice(&bytes[0..32]); - let s = Felt::from_bytes_be_slice(&bytes[32..64]); - - Ok(Signature(starknet_crypto::Signature { r, s })) - } -} - -#[derive(Debug)] -pub struct Signature(starknet_crypto::Signature); - -impl Signature { - #[cfg_attr(coverage_nightly, coverage(off))] - pub fn inner(&self) -> &starknet_crypto::Signature { - &self.0 - } - - pub fn dummy() -> Self { - Self(starknet_crypto::Signature { - r: Felt::ZERO, - s: Felt::ZERO, - }) - } -} - -impl Clone for Signature { - #[cfg_attr(coverage_nightly, coverage(off))] - fn clone(&self) -> Self { - Self(starknet_crypto::Signature { - r: self.0.r, - s: self.0.s, - }) - } -} - -impl PartialEq for Signature { - #[cfg_attr(coverage_nightly, coverage(off))] - fn eq(&self, other: &Self) -> bool { - self.0.r == other.0.r && self.0.s == other.0.s - } -} - -impl Eq for Signature {} - -impl PartialOrd for Signature { - #[cfg_attr(coverage_nightly, coverage(off))] - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for Signature { - #[cfg_attr(coverage_nightly, coverage(off))] - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.0.r.cmp(&other.0.r).then(self.0.s.cmp(&other.0.s)) - } -} - -impl Protobuf for Signature { - type Proto = proto::ConsensusSignature; - - fn from_proto(proto: Self::Proto) -> Result { - let r = proto - .r - .ok_or_else(|| ProtoError::missing_field::("r"))?; - let s = proto - .s - .ok_or_else(|| ProtoError::missing_field::("s"))?; - - Ok(Self(starknet_crypto::Signature { - r: Felt::from_proto(r)?, - s: Felt::from_proto(s)?, - })) - } - - fn to_proto(&self) -> Result { - Ok(proto::ConsensusSignature { - r: Some(self.0.r.to_proto()?), - s: Some(self.0.s.to_proto()?), - }) - } -} - -#[derive(Copy, Clone, Debug, Serialize, Deserialize)] -#[serde(transparent)] -pub struct PrivateKey(Felt); - -impl PrivateKey { - #[cfg_attr(coverage_nightly, coverage(off))] - pub fn generate(mut rng: R) -> Self - where - R: RngCore + CryptoRng, - { - let mut bytes = [0u8; 32]; - rng.fill_bytes(&mut bytes); - Self::from(bytes) - } - - #[cfg_attr(coverage_nightly, coverage(off))] - pub fn public_key(&self) -> PublicKey { - PublicKey::new(get_public_key(&self.0)) - } - - #[cfg_attr(coverage_nightly, coverage(off))] - pub fn inner(&self) -> Felt { - self.0 - } - - pub fn sign(&self, message: &Felt) -> Signature { - let signature = ecdsa_sign(&self.0, message).unwrap(); - Signature(signature.into()) - } -} - -impl From<[u8; 32]> for PrivateKey { - fn from(bytes: [u8; 32]) -> Self { - Self(Felt::from_bytes_be(&bytes)) - } -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -#[serde(transparent)] -pub struct PublicKey(Felt); - -impl PublicKey { - #[cfg_attr(coverage_nightly, coverage(off))] - pub fn new(key: Felt) -> Self { - Self(key) - } - - #[cfg_attr(coverage_nightly, coverage(off))] - pub fn from_bytes(bytes: [u8; 32]) -> Self { - Self::new(Felt::from_bytes_be(&bytes)) - } - - #[cfg_attr(coverage_nightly, coverage(off))] - pub fn as_bytes(&self) -> [u8; 32] { - self.0.to_bytes_be() - } - - #[cfg_attr(coverage_nightly, coverage(off))] - pub fn inner(&self) -> Felt { - self.0 - } - - pub fn verify(&self, message: &Felt, signature: &Signature) -> bool { - ecdsa_verify(&self.0, message, &signature.0).unwrap() - } -} - -impl fmt::Display for PublicKey { - #[cfg_attr(coverage_nightly, coverage(off))] - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.to_fixed_hex_string().fmt(f) - } -}