diff --git a/coordinator/tributary/src/tendermint/mod.rs b/coordinator/tributary/src/tendermint/mod.rs index 7f995465..a2147c9e 100644 --- a/coordinator/tributary/src/tendermint/mod.rs +++ b/coordinator/tributary/src/tendermint/mod.rs @@ -18,7 +18,10 @@ use ciphersuite::{ }, Ciphersuite, Ristretto, }; -use schnorr::SchnorrSignature; +use schnorr::{ + SchnorrSignature, + aggregate::{SchnorrAggregator, SchnorrAggregate}, +}; use serai_db::Db; @@ -46,6 +49,8 @@ use crate::{ pub mod tx; use tx::{TendermintTx, VoteSignature}; +const DST: &[u8] = b"Tributary Tendermint Commit Aggregator"; + fn challenge( genesis: [u8; 32], key: [u8; 32], @@ -159,8 +164,7 @@ impl Validators { impl SignatureScheme for Validators { type ValidatorId = [u8; 32]; type Signature = [u8; 64]; - // TODO: Use half-aggregation. - type AggregateSignature = Vec<[u8; 64]>; + type AggregateSignature = Vec; type Signer = Arc; #[must_use] @@ -177,8 +181,23 @@ impl SignatureScheme for Validators { actual_sig.verify(validator_point, challenge(self.genesis, validator, &sig[.. 32], msg)) } - fn aggregate(sigs: &[Self::Signature]) -> Self::AggregateSignature { - sigs.to_vec() + fn aggregate( + &self, + validators: &[Self::ValidatorId], + msg: &[u8], + sigs: &[Self::Signature], + ) -> Self::AggregateSignature { + assert_eq!(validators.len(), sigs.len()); + + let mut aggregator = SchnorrAggregator::::new(DST); + for (key, sig) in validators.iter().zip(sigs) { + let actual_sig = SchnorrSignature::::read::<&[u8]>(&mut sig.as_ref()).unwrap(); + let challenge = challenge(self.genesis, *key, actual_sig.R.to_bytes().as_ref(), msg); + aggregator.aggregate(challenge, actual_sig); + } + + let aggregate = aggregator.complete().unwrap(); + aggregate.serialize() } #[must_use] @@ -188,12 +207,28 @@ impl SignatureScheme for Validators { msg: &[u8], sig: &Self::AggregateSignature, ) -> bool { - for (signer, sig) in signers.iter().zip(sig.iter()) { - if !self.verify(*signer, msg, sig) { - return false; - } + let Ok(aggregate) = SchnorrAggregate::::read::<&[u8]>(&mut sig.as_slice()) else { + return false; + }; + + if signers.len() != aggregate.Rs().len() { + return false; } - true + + let mut challenges = vec![]; + for (key, nonce) in signers.iter().zip(aggregate.Rs()) { + challenges.push(challenge(self.genesis, *key, nonce.to_bytes().as_ref(), msg)); + } + + aggregate.verify( + DST, + signers + .iter() + .zip(challenges) + .map(|(s, c)| (::read_G(&mut s.as_slice()).unwrap(), c)) + .collect::>() + .as_slice(), + ) } } diff --git a/coordinator/tributary/src/tendermint/tx.rs b/coordinator/tributary/src/tendermint/tx.rs index 5ae89d80..ea7cac40 100644 --- a/coordinator/tributary/src/tendermint/tx.rs +++ b/coordinator/tributary/src/tendermint/tx.rs @@ -172,7 +172,13 @@ impl Transaction for TendermintTx { let signature = &vote.sig.signature; ::F::from_bytes_mod_order_wide( &Blake2b512::digest( - [genesis.as_ref(), &self.hash(), signature.R.to_bytes().as_ref()].concat(), + [ + b"Tributary Slash Vote", + genesis.as_ref(), + &self.hash(), + signature.R.to_bytes().as_ref(), + ] + .concat(), ) .into(), ) diff --git a/coordinator/tributary/src/transaction.rs b/coordinator/tributary/src/transaction.rs index 9d25bc9b..da03591a 100644 --- a/coordinator/tributary/src/transaction.rs +++ b/coordinator/tributary/src/transaction.rs @@ -126,7 +126,13 @@ pub trait Transaction: 'static + Send + Sync + Clone + Eq + Debug + ReadWrite { TransactionKind::Signed(Signed { signature, .. }) => { ::F::from_bytes_mod_order_wide( &Blake2b512::digest( - [genesis.as_ref(), &self.hash(), signature.R.to_bytes().as_ref()].concat(), + [ + b"Tributary Signed Transaction", + genesis.as_ref(), + &self.hash(), + signature.R.to_bytes().as_ref(), + ] + .concat(), ) .into(), ) diff --git a/coordinator/tributary/tendermint/src/ext.rs b/coordinator/tributary/tendermint/src/ext.rs index c04b711f..787ab1d8 100644 --- a/coordinator/tributary/tendermint/src/ext.rs +++ b/coordinator/tributary/tendermint/src/ext.rs @@ -81,7 +81,13 @@ pub trait SignatureScheme: Send + Sync + Clone { fn verify(&self, validator: Self::ValidatorId, msg: &[u8], sig: &Self::Signature) -> bool; /// Aggregate signatures. - fn aggregate(sigs: &[Self::Signature]) -> Self::AggregateSignature; + /// It may panic if corrupted data passed in. + fn aggregate( + &self, + validators: &[Self::ValidatorId], + msg: &[u8], + sigs: &[Self::Signature], + ) -> Self::AggregateSignature; /// Verify an aggregate signature for the list of signers. #[must_use] fn verify_aggregate( @@ -102,8 +108,13 @@ impl SignatureScheme for Arc { self.as_ref().verify(validator, msg, sig) } - fn aggregate(sigs: &[Self::Signature]) -> Self::AggregateSignature { - S::aggregate(sigs) + fn aggregate( + &self, + validators: &[Self::ValidatorId], + msg: &[u8], + sigs: &[Self::Signature], + ) -> Self::AggregateSignature { + self.as_ref().aggregate(validators, msg, sigs) } #[must_use] diff --git a/coordinator/tributary/tendermint/src/lib.rs b/coordinator/tributary/tendermint/src/lib.rs index fcf83fe0..3ca3d224 100644 --- a/coordinator/tributary/tendermint/src/lib.rs +++ b/coordinator/tributary/tendermint/src/lib.rs @@ -469,10 +469,14 @@ impl TendermintMachine { } } + let commit_msg = commit_msg( + self.block.end_time[&self.block.round().number].canonical(), + block.id().as_ref(), + ); let commit = Commit { end_time: self.block.end_time[&msg.round].canonical(), - validators, - signature: N::SignatureScheme::aggregate(&sigs), + validators: validators.clone(), + signature: self.network.signature_scheme().aggregate(&validators, &commit_msg, &sigs), }; debug_assert!(self.network.verify_commit(block.id(), &commit)); diff --git a/coordinator/tributary/tendermint/tests/ext.rs b/coordinator/tributary/tendermint/tests/ext.rs index adc7637c..59da2940 100644 --- a/coordinator/tributary/tendermint/tests/ext.rs +++ b/coordinator/tributary/tendermint/tests/ext.rs @@ -49,7 +49,12 @@ impl SignatureScheme for TestSignatureScheme { (sig[.. 2] == validator.to_le_bytes()) && (sig[2 ..] == [msg, &[0; 30]].concat()[.. 30]) } - fn aggregate(sigs: &[[u8; 32]]) -> Vec<[u8; 32]> { + fn aggregate( + &self, + _: &[Self::ValidatorId], + _: &[u8], + sigs: &[Self::Signature], + ) -> Self::AggregateSignature { sigs.to_vec() } diff --git a/crypto/schnorr/src/aggregate.rs b/crypto/schnorr/src/aggregate.rs index d5b1d5dc..d393c98e 100644 --- a/crypto/schnorr/src/aggregate.rs +++ b/crypto/schnorr/src/aggregate.rs @@ -111,6 +111,11 @@ impl SchnorrAggregate { buf } + #[allow(non_snake_case)] + pub fn Rs(&self) -> &[C::G] { + self.Rs.as_slice() + } + /// Perform signature verification. /// /// Challenges must be properly crafted, which means being binding to the public key, nonce, and