diff --git a/substrate/tendermint/src/ext.rs b/substrate/tendermint/src/ext.rs index 969eaf00..56d4a20d 100644 --- a/substrate/tendermint/src/ext.rs +++ b/substrate/tendermint/src/ext.rs @@ -14,6 +14,9 @@ impl Signature for S {} + // Type aliases which are distinct according to the type system #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Encode, Decode)] pub struct BlockNumber(pub u32); @@ -22,14 +25,12 @@ pub struct Round(pub u16); pub trait SignatureScheme: Send + Sync { type ValidatorId: ValidatorId; - type Signature: Send + Sync + Clone + Copy + PartialEq + Debug + Encode + Decode; - type AggregateSignature: Send + Sync + Clone + PartialEq + Debug + Encode + Decode; + type Signature: Signature; + type AggregateSignature: Signature; fn sign(&self, msg: &[u8]) -> Self::Signature; #[must_use] fn verify(&self, validator: Self::ValidatorId, msg: &[u8], sig: Self::Signature) -> bool; - // Intended to be a BLS signature, a Schnorr signature half-aggregation, or a Vec. - fn aggregate(signatures: &[Self::Signature]) -> Self::AggregateSignature; } pub trait Weights: Send + Sync { @@ -89,5 +90,9 @@ pub trait Network: Send + Sync { fn validate(&mut self, block: &Self::Block) -> Result<(), BlockError>; // Add a block and return the proposal for the next one - fn add_block(&mut self, block: Self::Block) -> Self::Block; + fn add_block( + &mut self, + block: Self::Block, + sigs: Vec<(Self::ValidatorId, ::Signature)>, + ) -> Self::Block; } diff --git a/substrate/tendermint/src/lib.rs b/substrate/tendermint/src/lib.rs index f413a6f6..a1b4d387 100644 --- a/substrate/tendermint/src/lib.rs +++ b/substrate/tendermint/src/lib.rs @@ -29,14 +29,26 @@ enum Step { Precommit, } -#[derive(Clone, PartialEq, Debug, Encode, Decode)] -enum Data { +#[derive(Clone, Debug, Encode, Decode)] +enum Data { Proposal(Option, B), Prevote(Option), - Precommit(Option), + Precommit(Option<(B::Id, S)>), } -impl Data { +impl PartialEq for Data { + fn eq(&self, other: &Data) -> bool { + match (self, other) { + (Data::Proposal(r, b), Data::Proposal(r2, b2)) => (r == r2) && (b == b2), + (Data::Prevote(i), Data::Prevote(i2)) => i == i2, + (Data::Precommit(None), Data::Precommit(None)) => true, + (Data::Precommit(Some((i, _))), Data::Precommit(Some((i2, _)))) => i == i2, + _ => false, + } + } +} + +impl Data { fn step(&self) -> Step { match self { Data::Proposal(..) => Step::Propose, @@ -47,18 +59,18 @@ impl Data { } #[derive(Clone, PartialEq, Debug, Encode, Decode)] -pub struct Message { +pub struct Message { sender: V, number: BlockNumber, round: Round, - data: Data, + data: Data, } #[derive(Clone, PartialEq, Debug, Encode, Decode)] -pub struct SignedMessage { - msg: Message, +pub struct SignedMessage { + msg: Message, sig: S, } @@ -112,7 +124,10 @@ impl TendermintMachine { } #[async_recursion::async_recursion] - async fn broadcast(&mut self, data: Data) -> Option { + async fn broadcast( + &mut self, + data: Data::Signature>, + ) -> Option { let step = data.step(); let msg = Message { sender: self.proposer, number: self.number, round: self.round, data }; let res = self.message(msg.clone()).await.unwrap(); @@ -238,7 +253,15 @@ impl TendermintMachine { match machine.message(msg.msg).await { Ok(None) => (), Ok(Some(block)) => { - let proposal = machine.network.write().await.add_block(block); + let sigs = machine + .log + .precommitted + .iter() + .filter_map(|(k, (id, sig))| { + Some((*k, sig.clone())).filter(|_| id == &block.id()) + }) + .collect(); + let proposal = machine.network.write().await.add_block(block, sigs); machine.reset(proposal).await } Err(TendermintError::Malicious(validator)) => { @@ -265,8 +288,9 @@ impl TendermintMachine { debug_assert!(matches!(proposal, Data::Proposal(..))); if let Data::Proposal(_, block) = proposal { // Check if it has gotten a sufficient amount of precommits - let (participants, weight) = - self.log.message_instances(round, Data::Precommit(Some(block.id()))); + let (participants, weight) = self + .log + .message_instances(round, Data::Precommit(Some((block.id(), self.signer.sign(&[]))))); let threshold = self.weights.threshold(); if weight >= threshold { @@ -286,8 +310,14 @@ impl TendermintMachine { async fn message( &mut self, - msg: Message, + msg: Message::Signature>, ) -> Result, TendermintError> { + if let Data::Precommit(Some((id, sig))) = &msg.data { + if !self.signer.verify(msg.sender, &id.encode(), sig.clone()) { + Err(TendermintError::Malicious(msg.sender))?; + } + } + if msg.number != self.number { Err(TendermintError::Temporal)?; } @@ -390,7 +420,14 @@ impl TendermintMachine { self.valid = Some((self.round, block.clone())); if self.step == Step::Prevote { self.locked = self.valid.clone(); - return Ok(self.broadcast(Data::Precommit(Some(block.id()))).await); + return Ok( + self + .broadcast(Data::Precommit(Some(( + block.id(), + self.signer.sign(&block.id().encode()), + )))) + .await, + ); } } } diff --git a/substrate/tendermint/src/message_log.rs b/substrate/tendermint/src/message_log.rs index f367cb91..5b817467 100644 --- a/substrate/tendermint/src/message_log.rs +++ b/substrate/tendermint/src/message_log.rs @@ -4,8 +4,17 @@ use crate::{ext::*, Round, Step, Data, Message, TendermintError}; pub(crate) struct MessageLog { weights: Arc, - precommitted: HashMap::Id>, - log: HashMap>>>, + pub(crate) precommitted: HashMap< + N::ValidatorId, + (::Id, ::Signature), + >, + log: HashMap< + Round, + HashMap< + N::ValidatorId, + HashMap::Signature>>, + >, + >, } impl MessageLog { @@ -16,7 +25,7 @@ impl MessageLog { // Returns true if it's a new message pub(crate) fn log( &mut self, - msg: Message, + msg: Message::Signature>, ) -> Result> { let round = self.log.entry(msg.round).or_insert_with(HashMap::new); let msgs = round.entry(msg.sender).or_insert_with(HashMap::new); @@ -31,13 +40,13 @@ impl MessageLog { } // If they already precommitted to a distinct hash, error - if let Data::Precommit(Some(hash)) = msg.data { - if let Some(prev) = self.precommitted.get(&msg.sender) { - if hash != *prev { + if let Data::Precommit(Some((hash, sig))) = &msg.data { + if let Some((prev, _)) = self.precommitted.get(&msg.sender) { + if hash != prev { Err(TendermintError::Malicious(msg.sender))?; } } - self.precommitted.insert(msg.sender, hash); + self.precommitted.insert(msg.sender, (*hash, sig.clone())); } msgs.insert(step, msg.data); @@ -46,7 +55,11 @@ impl MessageLog { // For a given round, return the participating weight for this step, and the weight agreeing with // the data. - pub(crate) fn message_instances(&self, round: Round, data: Data) -> (u64, u64) { + pub(crate) fn message_instances( + &self, + round: Round, + data: Data::Signature>, + ) -> (u64, u64) { let mut participating = 0; let mut weight = 0; for (participant, msgs) in &self.log[&round] { @@ -73,7 +86,11 @@ impl MessageLog { } // Check if consensus has been reached on a specific piece of data - pub(crate) fn has_consensus(&self, round: Round, data: Data) -> bool { + pub(crate) fn has_consensus( + &self, + round: Round, + data: Data::Signature>, + ) -> bool { let (_, weight) = self.message_instances(round, data); weight >= self.weights.threshold() } @@ -83,7 +100,7 @@ impl MessageLog { round: Round, sender: N::ValidatorId, step: Step, - ) -> Option<&Data> { + ) -> Option<&Data::Signature>> { self.log.get(&round).and_then(|round| round.get(&sender).and_then(|msgs| msgs.get(&step))) } } diff --git a/substrate/tendermint/tests/ext.rs b/substrate/tendermint/tests/ext.rs index 3fe55e1e..31dfca0c 100644 --- a/substrate/tendermint/tests/ext.rs +++ b/substrate/tendermint/tests/ext.rs @@ -18,17 +18,13 @@ impl SignatureScheme for TestSignatureScheme { fn sign(&self, msg: &[u8]) -> [u8; 32] { let mut sig = [0; 32]; sig[.. 2].copy_from_slice(&self.0.to_le_bytes()); - sig[2 .. (2 + 30.min(msg.len()))].copy_from_slice(msg); + sig[2 .. (2 + 30.min(msg.len()))].copy_from_slice(&msg[.. 30.min(msg.len())]); sig } fn verify(&self, validator: u16, msg: &[u8], sig: [u8; 32]) -> bool { (sig[.. 2] == validator.to_le_bytes()) && (&sig[2 ..] == &[msg, &[0; 30]].concat()[.. 30]) } - - fn aggregate(sigs: &[[u8; 32]]) -> Vec<[u8; 32]> { - sigs.to_vec() - } } struct TestWeights; @@ -95,9 +91,12 @@ impl Network for TestNetwork { block.valid } - fn add_block(&mut self, block: TestBlock) -> TestBlock { + fn add_block(&mut self, block: TestBlock, sigs: Vec<(u16, [u8; 32])>) -> TestBlock { dbg!("Adding ", &block); assert!(block.valid.is_ok()); + for sig in sigs { + assert!(TestSignatureScheme(u16::MAX).verify(sig.0, &block.id().encode(), sig.1)); + } TestBlock { id: block.id + 1, valid: Ok(()) } } }