Skip to content

Commit

Permalink
tendermint: crypto::signature::Verifier trait
Browse files Browse the repository at this point in the history
Define Verifier trait to abstract signature verification given a
PublicKey.

The ed25519-consensus dependency is made optional and gated by the
"rust-crypto" feature.
The Verifier implementation is provided for a dummy type
crate::crypto::default::signature::Verifier, using ed25519-consensus.
The Ed25519 key types in public_key and private_key module are redefined
to in-crate newtypes.
  • Loading branch information
mzabaluev committed Jan 24, 2023
1 parent 2a7f441 commit d525562
Show file tree
Hide file tree
Showing 18 changed files with 219 additions and 76 deletions.
2 changes: 1 addition & 1 deletion config/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ fn priv_validator_json_parser() {
let raw_priv_validator_key = read_fixture("priv_validator_key.json");
let priv_validator_key = PrivValidatorKey::parse_json(raw_priv_validator_key).unwrap();
assert_eq!(
priv_validator_key.consensus_pubkey().to_hex(),
priv_validator_key.consensus_pubkey().public_key().to_hex(),
"F26BF4B2A2E84CEB7A53C3F1AE77408779B20064782FBADBDF0E365959EE4534"
);
}
Expand Down
33 changes: 27 additions & 6 deletions light-client-verifier/src/operations/voting_power.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
//! Provides an interface and default implementation for the `VotingPower` operation
use alloc::collections::BTreeSet as HashSet;
use core::{convert::TryFrom, fmt};
use core::{convert::TryFrom, fmt, marker::PhantomData};

use serde::{Deserialize, Serialize};
use tendermint::{
block::CommitSig,
crypto::signature,
trust_threshold::TrustThreshold as _,
vote::{SignedVote, ValidatorIndex, Vote},
};
Expand Down Expand Up @@ -98,11 +99,31 @@ pub trait VotingPowerCalculator: Send + Sync {
) -> Result<VotingPowerTally, VerificationError>;
}

/// Default implementation of a `VotingPowerCalculator`
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct ProdVotingPowerCalculator;
/// Default implementation of a `VotingPowerCalculator`, parameterized with
/// the signature verification trait.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct ProvidedVotingPowerCalculator<V> {
_verifier: PhantomData<V>,
}

// Safety: the only member is phantom data
unsafe impl<V> Send for ProvidedVotingPowerCalculator<V> {}
unsafe impl<V> Sync for ProvidedVotingPowerCalculator<V> {}

impl<V> Default for ProvidedVotingPowerCalculator<V> {
fn default() -> Self {
Self {
_verifier: PhantomData,
}
}
}

/// Default implementation of a `VotingPowerCalculator`.
#[cfg(feature = "rust-crypto")]
pub type ProdVotingPowerCalculator =
ProvidedVotingPowerCalculator<tendermint::crypto::default::signature::Verifier>;

impl VotingPowerCalculator for ProdVotingPowerCalculator {
impl<V: signature::Verifier> VotingPowerCalculator for ProvidedVotingPowerCalculator<V> {
fn voting_power_in(
&self,
signed_header: &SignedHeader,
Expand Down Expand Up @@ -146,7 +167,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::<V>(&sign_bytes, signed_vote.signature())
.is_err()
{
return Err(VerificationError::invalid_signature(
Expand Down
6 changes: 3 additions & 3 deletions tendermint/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -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 }
Expand All @@ -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"] }
Expand Down
3 changes: 3 additions & 0 deletions tendermint/src/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
//! The abstract framework enabling this extensibility is provided by the
//! `digest` and `signature` crates.
pub mod ed25519;
pub mod sha256;
pub mod signature;

pub use sha256::Sha256;

#[cfg(feature = "rust-crypto")]
Expand Down
2 changes: 2 additions & 0 deletions tendermint/src/crypto/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ impl super::Sha256 for Sha256 {
}
}

pub mod signature;

/// Types implementing the ECDSA algorithm using the Secp256k1 elliptic curve.
#[cfg(feature = "secp256k1")]
pub mod ecdsa_secp256k1 {
Expand Down
31 changes: 31 additions & 0 deletions tendermint/src/crypto/default/signature.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//! The pure Rust implementation of signature verification functions.
use crate::crypto::signature::Error;
use crate::{PublicKey, Signature};

#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct Verifier;

impl crate::crypto::signature::Verifier for Verifier {
fn verify(pubkey: PublicKey, msg: &[u8], signature: &Signature) -> Result<(), Error> {
#[allow(unreachable_patterns)]
match pubkey {
PublicKey::Ed25519(pk) => {
let pubkey = ed25519_consensus::VerificationKey::try_from(pk)
.map_err(|_| Error::MalformedPublicKey)?;
let sig = ed25519_consensus::Signature::try_from(signature.as_bytes())
.map_err(|_| Error::MalformedSignature)?;
pubkey
.verify(&sig, msg)
.map_err(|_| Error::VerificationFailed)
},
#[cfg(feature = "secp256k1")]
PublicKey::Secp256k1(pk) => {
let signature = k256::ecdsa::Signature::try_from(signature.as_bytes())
.map_err(|_| Error::MalformedSignature)?;
pk.verify(msg, &sig).map_err(|_| Error::VerificationFailed)
},
_ => Err(Error::UnsupportedKeyType),
}
}
}
5 changes: 5 additions & 0 deletions tendermint/src/crypto/ed25519.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod signing_key;
mod verification_key;

pub use signing_key::SigningKey;
pub use verification_key::VerificationKey;
42 changes: 42 additions & 0 deletions tendermint/src/crypto/ed25519/signing_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
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<Self, Self::Error> {
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<SigningKey> for ed25519_consensus::SigningKey {
type Error = Error;

fn try_from(src: SigningKey) -> Result<Self, Error> {
ed25519_consensus::SigningKey::try_from(src.0)
.map_err(|_| Error::invalid_key("malformed Ed25519 private key".into()))
}
}
37 changes: 37 additions & 0 deletions tendermint/src/crypto/ed25519/verification_key.rs
Original file line number Diff line number Diff line change
@@ -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<Self, Self::Error> {
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<VerificationKey> for ed25519_consensus::VerificationKey {
type Error = Error;

fn try_from(src: VerificationKey) -> Result<Self, Error> {
ed25519_consensus::VerificationKey::try_from(src.0)
.map_err(|_| Error::invalid_key("malformed Ed25519 public key".into()))
}
}
35 changes: 35 additions & 0 deletions tendermint/src/crypto/signature.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use core::fmt::{self, Display};

use crate::{PublicKey, Signature};

/// Signature error.
///
#[derive(Debug)]
pub enum Error {
/// This variant is deliberately opaque as to avoid side-channel leakage.
VerificationFailed,
/// The key used to verify a signature is not of a type supported by the implementation.
UnsupportedKeyType,
/// The encoding of the public key was malformed.
MalformedPublicKey,
/// The signature data was malformed.
MalformedSignature,
}

impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::VerificationFailed => f.write_str("invalid signature"),
Error::UnsupportedKeyType => f.write_str("key type not supported"),
Error::MalformedPublicKey => f.write_str("malformed public key encoding"),
Error::MalformedSignature => f.write_str("malformed signature"),
}
}
}

#[cfg(feature = "std")]
impl std::error::Error for Error {}

pub trait Verifier {
fn verify(pubkey: PublicKey, msg: &[u8], signature: &Signature) -> Result<(), Error>;
}
2 changes: 1 addition & 1 deletion tendermint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

extern crate alloc;

#[cfg(test)]
#[cfg(any(feature = "std", test))]
extern crate std;

#[macro_use]
Expand Down
7 changes: 3 additions & 4 deletions tendermint/src/private_key.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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"));
}
Expand Down
Loading

0 comments on commit d525562

Please sign in to comment.