From dd575531343b332c8875e7e6d82249ff834288c4 Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Mon, 23 Jan 2023 14:54:07 +0200 Subject: [PATCH] WIP: Verifier trait Draft of a trait to abstract signature verification. --- .../src/operations/voting_power.rs | 13 +++- tendermint/Cargo.toml | 6 +- tendermint/src/crypto.rs | 1 + tendermint/src/crypto/default.rs | 3 + tendermint/src/crypto/default/ed25519.rs | 16 ++++ tendermint/src/crypto/ed25519.rs | 11 +++ tendermint/src/crypto/ed25519/signing_key.rs | 32 ++++++++ .../src/crypto/ed25519/verification_key.rs | 37 ++++++++++ tendermint/src/private_key.rs | 7 +- tendermint/src/public_key.rs | 73 +++++-------------- tendermint/src/signature.rs | 1 + tendermint/src/validator.rs | 10 ++- 12 files changed, 140 insertions(+), 70 deletions(-) create mode 100644 tendermint/src/crypto/default/ed25519.rs create mode 100644 tendermint/src/crypto/ed25519.rs create mode 100644 tendermint/src/crypto/ed25519/signing_key.rs create mode 100644 tendermint/src/crypto/ed25519/verification_key.rs diff --git a/light-client-verifier/src/operations/voting_power.rs b/light-client-verifier/src/operations/voting_power.rs index 442db0510..d086947b0 100644 --- a/light-client-verifier/src/operations/voting_power.rs +++ b/light-client-verifier/src/operations/voting_power.rs @@ -98,11 +98,16 @@ pub trait VotingPowerCalculator: Send + Sync { ) -> Result; } -/// Default implementation of a `VotingPowerCalculator` +/// Default implementation of a `VotingPowerCalculator`, parameterized with +/// the signature verification trait. #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] -pub struct ProdVotingPowerCalculator; +pub struct ProvidedVotingPowerCalculator; -impl VotingPowerCalculator for ProdVotingPowerCalculator { +/// Default implementation of a `VotingPowerCalculator`. +pub type ProdVotingPowerCalculator = + ProvidedVotingPowerCalculator; + +impl VotingPowerCalculator for ProvidedVotingPowerCalculator { fn voting_power_in( &self, signed_header: &SignedHeader, @@ -146,7 +151,7 @@ impl VotingPowerCalculator for ProdVotingPowerCalculator { // Check vote is valid let sign_bytes = signed_vote.sign_bytes(); if validator - .verify_signature(&sign_bytes, signed_vote.signature()) + .verify_signature::(&sign_bytes, signed_vote.signature()) .is_err() { return Err(VerificationError::invalid_signature( diff --git a/tendermint/Cargo.toml b/tendermint/Cargo.toml index 9905e1c08..53bf83e61 100644 --- a/tendermint/Cargo.toml +++ b/tendermint/Cargo.toml @@ -32,8 +32,7 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] bytes = { version = "1.2", default-features = false, features = ["serde"] } digest = { version = "0.10", default-features = false } -ed25519 = { version = "1.3", default-features = false } -ed25519-consensus = { version = "2", default-features = false } +ed25519 = { version = "1.5", default-features = false } futures = { version = "0.3", default-features = false } num-traits = { version = "0.2", default-features = false } once_cell = { version = "1.3", default-features = false } @@ -50,6 +49,7 @@ tendermint-proto = { version = "0.28.0", default-features = false, path = "../pr time = { version = "0.3", default-features = false, features = ["macros", "parsing"] } zeroize = { version = "1.1", default-features = false, features = ["zeroize_derive", "alloc"] } flex-error = { version = "0.4.4", default-features = false } +ed25519-consensus = { version = "2", optional = true, default-features = false } sha2 = { version = "0.10", optional = true, default-features = false } k256 = { version = "0.11", optional = true, default-features = false, features = ["ecdsa"] } ripemd = { version = "0.1.3", optional = true, default-features = false } @@ -59,7 +59,7 @@ default = ["std", "rust-crypto"] std = ["flex-error/std", "flex-error/eyre_tracer", "clock"] clock = ["time/std"] secp256k1 = ["k256", "ripemd"] -rust-crypto = ["sha2"] +rust-crypto = ["sha2", "ed25519-consensus"] [dev-dependencies] k256 = { version = "0.11", default-features = false, features = ["ecdsa"] } diff --git a/tendermint/src/crypto.rs b/tendermint/src/crypto.rs index ef578e1b4..080761b85 100644 --- a/tendermint/src/crypto.rs +++ b/tendermint/src/crypto.rs @@ -7,6 +7,7 @@ //! The abstract framework enabling this extensibility is provided by the //! `digest` and `signature` crates. +pub mod ed25519; pub mod sha256; pub use sha256::Sha256; diff --git a/tendermint/src/crypto/default.rs b/tendermint/src/crypto/default.rs index 81ba318f1..d289fb537 100644 --- a/tendermint/src/crypto/default.rs +++ b/tendermint/src/crypto/default.rs @@ -23,6 +23,9 @@ impl super::Sha256 for Sha256 { } } +/// Types implementing the Ed25519 signature algorithm. +pub mod ed25519; + /// Types implementing the ECDSA algorithm using the Secp256k1 elliptic curve. #[cfg(feature = "secp256k1")] pub mod ecdsa_secp256k1 { diff --git a/tendermint/src/crypto/default/ed25519.rs b/tendermint/src/crypto/default/ed25519.rs new file mode 100644 index 000000000..99ecbfa6f --- /dev/null +++ b/tendermint/src/crypto/default/ed25519.rs @@ -0,0 +1,16 @@ +use crate::crypto::ed25519::VerificationKey; +use crate::{Error, Signature}; + +pub struct Verifier; + +impl crate::crypto::ed25519::Verifier for Verifier { + fn verify(pubkey: VerificationKey, msg: &[u8], signature: &Signature) -> Result<(), Error> { + let pubkey = ed25519_consensus::VerificationKey::try_from(pubkey)?; + // Fixme: need more clearly distinguished error variants for the two failure cases. + let signature = ed25519_consensus::Signature::try_from(signature.as_bytes()) + .map_err(|_| Error::invalid_signature("invalid Ed25519 signature".into()))?; + pubkey + .verify(&signature, msg) + .map_err(|_| Error::signature_invalid("Ed25519 signature verification failed".into())) + } +} diff --git a/tendermint/src/crypto/ed25519.rs b/tendermint/src/crypto/ed25519.rs new file mode 100644 index 000000000..9efd2b57c --- /dev/null +++ b/tendermint/src/crypto/ed25519.rs @@ -0,0 +1,11 @@ +mod signing_key; +mod verification_key; + +pub use signing_key::SigningKey; +pub use verification_key::VerificationKey; + +use crate::{Error, Signature}; + +pub trait Verifier { + fn verify(pubkey: VerificationKey, msg: &[u8], signature: &Signature) -> Result<(), Error>; +} diff --git a/tendermint/src/crypto/ed25519/signing_key.rs b/tendermint/src/crypto/ed25519/signing_key.rs new file mode 100644 index 000000000..307b3c08d --- /dev/null +++ b/tendermint/src/crypto/ed25519/signing_key.rs @@ -0,0 +1,32 @@ +use super::VerificationKey; +use crate::Error; + +#[derive(Clone, Debug)] +pub struct SigningKey([u8; 32]); + +impl SigningKey { + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } + + #[cfg(feature = "rust-crypto")] + pub fn verification_key(&self) -> VerificationKey { + let privkey = ed25519_consensus::SigningKey::from(self.0); + let pubkey = privkey.verification_key(); + let pubkey_bytes = pubkey.to_bytes(); + VerificationKey::new(pubkey_bytes) + } +} + +impl TryFrom<&'_ [u8]> for SigningKey { + type Error = Error; + + fn try_from(slice: &'_ [u8]) -> Result { + if slice.len() != 32 { + return Err(Error::invalid_key("invalid ed25519 key length".into())); + } + let mut bytes = [0u8; 32]; + bytes[..].copy_from_slice(slice); + Ok(Self(bytes)) + } +} diff --git a/tendermint/src/crypto/ed25519/verification_key.rs b/tendermint/src/crypto/ed25519/verification_key.rs new file mode 100644 index 000000000..562998df9 --- /dev/null +++ b/tendermint/src/crypto/ed25519/verification_key.rs @@ -0,0 +1,37 @@ +use crate::Error; + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct VerificationKey([u8; 32]); + +impl VerificationKey { + pub(super) fn new(bytes: [u8; 32]) -> Self { + Self(bytes) + } + + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } +} + +impl TryFrom<&'_ [u8]> for VerificationKey { + type Error = Error; + + fn try_from(slice: &'_ [u8]) -> Result { + if slice.len() != 32 { + return Err(Error::invalid_key("invalid ed25519 key length".into())); + } + let mut bytes = [0u8; 32]; + bytes[..].copy_from_slice(slice); + Ok(Self(bytes)) + } +} + +#[cfg(feature = "rust-crypto")] +impl TryFrom for ed25519_consensus::VerificationKey { + type Error = Error; + + fn try_from(src: VerificationKey) -> Result { + ed25519_consensus::VerificationKey::try_from(src.0) + .map_err(|_| Error::invalid_key("malformed Ed25519 public key".into())) + } +} diff --git a/tendermint/src/private_key.rs b/tendermint/src/private_key.rs index 0c0989f54..a1c89d6fe 100644 --- a/tendermint/src/private_key.rs +++ b/tendermint/src/private_key.rs @@ -1,10 +1,9 @@ //! Cryptographic private keys -pub use ed25519_consensus::SigningKey as Ed25519; - +pub use crate::crypto::ed25519::SigningKey as Ed25519; use crate::prelude::*; use crate::public_key::PublicKey; -use ed25519_consensus::VerificationKey; + use serde::{de, ser, Deserialize, Serialize}; use subtle_encoding::{Base64, Encoding}; use zeroize::Zeroizing; @@ -78,7 +77,7 @@ where // with the re-derived data. let signing_key = Ed25519::try_from(&keypair_bytes[0..32]) .map_err(|_| D::Error::custom("invalid signing key"))?; - let verification_key = VerificationKey::from(&signing_key); + let verification_key = signing_key.verification_key(); if &keypair_bytes[32..64] != verification_key.as_bytes() { return Err(D::Error::custom("keypair mismatch")); } diff --git a/tendermint/src/public_key.rs b/tendermint/src/public_key.rs index 286f005ee..bb35f781c 100644 --- a/tendermint/src/public_key.rs +++ b/tendermint/src/public_key.rs @@ -1,6 +1,5 @@ //! Public keys used in Tendermint networks -pub use ed25519_consensus::VerificationKey as Ed25519; #[cfg(feature = "secp256k1")] pub use k256::ecdsa::VerifyingKey as Secp256k1; @@ -11,7 +10,7 @@ pub use pub_key_request::PubKeyRequest; pub use pub_key_response::PubKeyResponse; use core::convert::TryFrom; -use core::{cmp::Ordering, fmt, ops::Deref, str::FromStr}; +use core::{cmp::Ordering, fmt, str::FromStr}; use serde::{de, ser, Deserialize, Deserializer, Serialize}; use serde_json::Value; use subtle_encoding::{base64, bech32, hex}; @@ -20,7 +19,8 @@ use tendermint_proto::{ Protobuf, }; -use crate::{error::Error, prelude::*, signature::Signature}; +pub use crate::crypto::ed25519::VerificationKey as Ed25519; +use crate::{error::Error, prelude::*}; #[cfg(feature = "secp256k1")] use signature::Verifier as _; @@ -132,8 +132,8 @@ impl TryFrom for PublicKey { .sum .ok_or_else(|| Error::invalid_key("empty sum".to_string()))?; if let Sum::Ed25519(b) = sum { - return Self::from_raw_ed25519(b) - .ok_or_else(|| Error::invalid_key("malformed ed25519 key".to_string())); + let key = Ed25519::try_from(&b[..])?; + return Ok(PublicKey::Ed25519(key)); } #[cfg(feature = "secp256k1")] if let Sum::Secp256k1(b) = sum { @@ -196,38 +196,6 @@ impl PublicKey { } } - /// Verify the given [`Signature`] using this public key - pub fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), Error> { - match self { - PublicKey::Ed25519(pk) => { - match ed25519_consensus::Signature::try_from(signature.as_bytes()) { - Ok(sig) => pk.verify(&sig, msg).map_err(|_| { - Error::signature_invalid( - "Ed25519 signature verification failed".to_string(), - ) - }), - Err(_) => Err(Error::signature_invalid( - "Could not parse Ed25519 signature".to_string(), - )), - } - }, - #[cfg(feature = "secp256k1")] - PublicKey::Secp256k1(pk) => { - match k256::ecdsa::Signature::try_from(signature.as_bytes()) { - Ok(sig) => pk.verify(msg, &sig).map_err(|_| { - Error::signature_invalid( - "Secp256k1 signature verification failed".to_string(), - ) - }), - Err(e) => Err(Error::signature_invalid(format!( - "invalid Secp256k1 signature: {}", - e - ))), - } - }, - } - } - /// Serialize this key as a byte vector. pub fn to_bytes(self) -> Vec { match self { @@ -338,15 +306,6 @@ impl TendermintKey { } } -// TODO(tarcieri): deprecate/remove this in favor of `TendermintKey::public_key` -impl Deref for TendermintKey { - type Target = PublicKey; - - fn deref(&self) -> &PublicKey { - self.public_key() - } -} - /// Public key algorithms #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Algorithm { @@ -443,12 +402,10 @@ where #[cfg(test)] mod tests { - use core::convert::TryFrom; - use subtle_encoding::hex; use tendermint_proto::Protobuf; - use super::{PublicKey, Signature, TendermintKey}; + use super::{PublicKey, TendermintKey}; use crate::{prelude::*, public_key::PubKeyResponse}; const EXAMPLE_CONSENSUS_KEY: &str = @@ -476,7 +433,7 @@ mod tests { // fmt.Println(mustBech32ConsPub) // } assert_eq!( - example_key.to_bech32("cosmosvalconspub"), + example_key.public_key().to_bech32("cosmosvalconspub"), "cosmosvalconspub1zcjduepqfgjuveq2raetnjt4xwpffm63kmguxv2chdhvhf5lhslmtgeunh8qmf7exk" ); } @@ -502,7 +459,7 @@ mod tests { let pubkey: PublicKey = serde_json::from_str(json_string).unwrap(); assert_eq!( - pubkey.ed25519().unwrap().as_ref(), + pubkey.ed25519().unwrap().as_bytes(), [ 69, 185, 115, 48, 238, 34, 179, 146, 245, 133, 156, 250, 194, 142, 36, 61, 186, 109, 204, 236, 174, 123, 162, 211, 147, 143, 165, 62, 16, 245, 21, 25 @@ -703,22 +660,26 @@ mod tests { ], ]; + #[cfg(feature = "rust-crypto")] #[test] fn ed25519_test_vectors() { + use crate::crypto::default::ed25519::Verifier; + use crate::crypto::ed25519::Verifier as _; + use crate::Signature; + for (i, v) in ED25519_TEST_VECTORS.iter().enumerate() { let public_key = v[0]; let msg = v[1]; let sig = v[2]; let public_key = PublicKey::from_raw_ed25519(public_key).unwrap(); - match public_key { - PublicKey::Ed25519(_) => {}, + let public_key = match public_key { + PublicKey::Ed25519(key) => key, #[cfg(feature = "secp256k1")] _ => panic!("expected public key to be Ed25519: {:?}", public_key), - } + }; let sig = Signature::try_from(sig).unwrap(); - public_key - .verify(msg, &sig) + Verifier::verify(public_key, msg, &sig) .unwrap_or_else(|_| panic!("signature should be valid for test vector {}", i)); } } diff --git a/tendermint/src/signature.rs b/tendermint/src/signature.rs index 50d8a8e0a..4f6475c2f 100644 --- a/tendermint/src/signature.rs +++ b/tendermint/src/signature.rs @@ -88,6 +88,7 @@ impl From for Signature { } } +#[cfg(feature = "rust-crypto")] impl From for Signature { fn from(sig: ed25519_consensus::Signature) -> Signature { Self(sig.to_bytes().to_vec()) diff --git a/tendermint/src/validator.rs b/tendermint/src/validator.rs index f5c3c82f3..074469c5c 100644 --- a/tendermint/src/validator.rs +++ b/tendermint/src/validator.rs @@ -14,6 +14,7 @@ use tendermint_proto::{ use crate::{ account, + crypto::ed25519::Verifier, crypto::Sha256, hash::Hash, merkle::{self, MerkleHash}, @@ -219,8 +220,11 @@ impl Info { /// Verify the given signature against the given sign_bytes using the validators /// public key. - pub fn verify_signature(&self, sign_bytes: &[u8], signature: &Signature) -> Result<(), Error> { - self.pub_key.verify(sign_bytes, signature) + pub fn verify_signature(&self, sign_bytes: &[u8], signature: &Signature) -> Result<(), Error> + where + V: Verifier, + { + V::verify(self.pub_key, sign_bytes, signature) } #[cfg(feature = "rust-crypto")] @@ -276,7 +280,7 @@ impl From<&Info> for SimpleValidator { fn from(info: &Info) -> SimpleValidator { let sum = match &info.pub_key { PublicKey::Ed25519(pk) => Some(tendermint_proto::crypto::public_key::Sum::Ed25519( - pk.as_ref().to_vec(), + pk.as_bytes().to_vec(), )), #[cfg(feature = "secp256k1")] PublicKey::Secp256k1(pk) => Some(tendermint_proto::crypto::public_key::Sum::Secp256k1(