From 86cbf6e02eccdafb57f5d5ac1d31373d8ae99b9e Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 12 Apr 2023 16:06:14 -0400 Subject: [PATCH] Bind the signature scheme for tendermint-machine --- coordinator/tributary/Cargo.toml | 3 + coordinator/tributary/src/lib.rs | 5 +- coordinator/tributary/src/tendermint.rs | 128 ++++++++++++++++++++++++ 3 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 coordinator/tributary/src/tendermint.rs diff --git a/coordinator/tributary/Cargo.toml b/coordinator/tributary/Cargo.toml index 88d551e0..e38a06fe 100644 --- a/coordinator/tributary/Cargo.toml +++ b/coordinator/tributary/Cargo.toml @@ -8,12 +8,15 @@ authors = ["Luke Parker "] edition = "2021" [dependencies] +async-trait = "0.1" thiserror = "1" +subtle = "^2" zeroize = { version = "^1.5", optional = true } rand_core = { version = "0.6", optional = true } blake2 = "0.10" +transcript = { package = "flexible-transcript", path = "../../crypto/transcript", features = ["recommended"] } ciphersuite = { package = "ciphersuite", path = "../../crypto/ciphersuite", features = ["ristretto"] } schnorr = { package = "schnorr-signatures", path = "../../crypto/schnorr" } diff --git a/coordinator/tributary/src/lib.rs b/coordinator/tributary/src/lib.rs index 1040b379..7fef0032 100644 --- a/coordinator/tributary/src/lib.rs +++ b/coordinator/tributary/src/lib.rs @@ -7,7 +7,7 @@ mod transaction; pub use transaction::*; mod provided; -pub use provided::*; +pub(crate) use provided::*; mod block; pub use block::*; @@ -18,6 +18,9 @@ pub use blockchain::*; mod mempool; pub use mempool::*; +mod tendermint; +pub use crate::tendermint::*; + #[cfg(any(test, feature = "tests"))] pub mod tests; diff --git a/coordinator/tributary/src/tendermint.rs b/coordinator/tributary/src/tendermint.rs new file mode 100644 index 00000000..452ae4f6 --- /dev/null +++ b/coordinator/tributary/src/tendermint.rs @@ -0,0 +1,128 @@ +use core::ops::Deref; + +use subtle::ConstantTimeEq; +use zeroize::{Zeroize, Zeroizing}; + +use transcript::{Transcript, RecommendedTranscript}; + +use ciphersuite::{ + group::{ + GroupEncoding, + ff::{Field, PrimeField}, + }, + Ciphersuite, Ristretto, +}; +use schnorr::SchnorrSignature; + +use tendermint::ext::{Signer as SignerTrait, SignatureScheme as SignatureSchemeTrait}; + +fn challenge( + genesis: [u8; 32], + key: [u8; 32], + nonce: &[u8], + msg: &[u8], +) -> ::F { + let mut transcript = RecommendedTranscript::new(b"Tributary Chain Tendermint Message"); + transcript.append_message(b"genesis", genesis); + transcript.append_message(b"key", key); + transcript.append_message(b"nonce", nonce); + transcript.append_message(b"message", msg); + + ::F::from_bytes_mod_order_wide(&transcript.challenge(b"schnorr").into()) +} + +#[derive(Clone, PartialEq, Eq, Debug)] +struct Signer { + genesis: [u8; 32], + key: Zeroizing<::F>, +} + +#[async_trait::async_trait] +impl SignerTrait for Signer { + type ValidatorId = [u8; 32]; + type Signature = [u8; 64]; + + /// Returns the validator's current ID. Returns None if they aren't a current validator. + async fn validator_id(&self) -> Option { + Some((Ristretto::generator() * self.key.deref()).to_bytes()) + } + + /// Sign a signature with the current validator's private key. + async fn sign(&self, msg: &[u8]) -> Self::Signature { + let mut nonce = Zeroizing::new(RecommendedTranscript::new(b"Tributary Chain Tendermint Nonce")); + nonce.append_message(b"genesis", self.genesis); + nonce.append_message(b"key", Zeroizing::new(self.key.deref().to_repr()).as_ref()); + nonce.append_message(b"message", msg); + let mut nonce = nonce.challenge(b"nonce"); + + let mut nonce_arr = [0; 64]; + nonce_arr.copy_from_slice(nonce.as_ref()); + + let nonce_ref: &mut [u8] = nonce.as_mut(); + nonce_ref.zeroize(); + let nonce_ref: &[u8] = nonce.as_ref(); + assert_eq!(nonce_ref, [0; 64].as_ref()); + + let nonce = + Zeroizing::new(::F::from_bytes_mod_order_wide(&nonce_arr)); + nonce_arr.zeroize(); + + assert!(!bool::from(nonce.ct_eq(&::F::ZERO))); + + let challenge = challenge( + self.genesis, + (Ristretto::generator() * self.key.deref()).to_bytes(), + (Ristretto::generator() * nonce.deref()).to_bytes().as_ref(), + msg, + ); + + let sig = SchnorrSignature::::sign(&self.key, nonce, challenge).serialize(); + + let mut res = [0; 64]; + res.copy_from_slice(&sig); + res + } +} + +#[derive(Clone, PartialEq, Eq, Debug)] +struct SignatureScheme { + genesis: [u8; 32], +} + +impl SignatureSchemeTrait for SignatureScheme { + type ValidatorId = [u8; 32]; + type Signature = [u8; 64]; + // TODO: Use half-aggregation. + type AggregateSignature = Vec<[u8; 64]>; + type Signer = Signer; + + #[must_use] + fn verify(&self, validator: Self::ValidatorId, msg: &[u8], sig: &Self::Signature) -> bool { + let Ok(validator_point) = Ristretto::read_G::<&[u8]>(&mut validator.as_ref()) else { + return false; + }; + let Ok(actual_sig) = SchnorrSignature::::read::<&[u8]>(&mut sig.as_ref()) else { + return false; + }; + actual_sig.verify(validator_point, challenge(self.genesis, validator, &sig[.. 32], msg)) + } + + fn aggregate(sigs: &[Self::Signature]) -> Self::AggregateSignature { + sigs.to_vec() + } + + #[must_use] + fn verify_aggregate( + &self, + signers: &[Self::ValidatorId], + msg: &[u8], + sig: &Self::AggregateSignature, + ) -> bool { + for (signer, sig) in signers.iter().zip(sig.iter()) { + if !self.verify(*signer, msg, sig) { + return false; + } + } + true + } +}