Skip to content

Commit

Permalink
WIP: Verifier trait
Browse files Browse the repository at this point in the history
Draft of a trait to abstract signature verification.
  • Loading branch information
mzabaluev committed Jan 23, 2023
1 parent 2a7f441 commit dd57553
Show file tree
Hide file tree
Showing 12 changed files with 140 additions and 70 deletions.
13 changes: 9 additions & 4 deletions light-client-verifier/src/operations/voting_power.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,16 @@ pub trait VotingPowerCalculator: Send + Sync {
) -> Result<VotingPowerTally, VerificationError>;
}

/// 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<V>;

impl VotingPowerCalculator for ProdVotingPowerCalculator {
/// Default implementation of a `VotingPowerCalculator`.
pub type ProdVotingPowerCalculator =
ProvidedVotingPowerCalculator<tendermint::crypto::default::ed25519::Verifier>;

impl<V: Verifier> VotingPowerCalculator for ProvidedVotingPowerCalculator<V> {
fn voting_power_in(
&self,
signed_header: &SignedHeader,
Expand Down Expand Up @@ -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::<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
1 change: 1 addition & 0 deletions tendermint/src/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
3 changes: 3 additions & 0 deletions tendermint/src/crypto/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
16 changes: 16 additions & 0 deletions tendermint/src/crypto/default/ed25519.rs
Original file line number Diff line number Diff line change
@@ -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()))
}
}
11 changes: 11 additions & 0 deletions tendermint/src/crypto/ed25519.rs
Original file line number Diff line number Diff line change
@@ -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>;
}
32 changes: 32 additions & 0 deletions tendermint/src/crypto/ed25519/signing_key.rs
Original file line number Diff line number Diff line change
@@ -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<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))
}
}
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()))
}
}
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
73 changes: 17 additions & 56 deletions tendermint/src/public_key.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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};
Expand All @@ -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 _;
Expand Down Expand Up @@ -132,8 +132,8 @@ impl TryFrom<RawPublicKey> 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 {
Expand Down Expand Up @@ -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<u8> {
match self {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -476,7 +433,7 @@ mod tests {
// fmt.Println(mustBech32ConsPub)
// }
assert_eq!(
example_key.to_bech32("cosmosvalconspub"),
example_key.public_key().to_bech32("cosmosvalconspub"),
"cosmosvalconspub1zcjduepqfgjuveq2raetnjt4xwpffm63kmguxv2chdhvhf5lhslmtgeunh8qmf7exk"
);
}
Expand All @@ -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
Expand Down Expand Up @@ -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));
}
}
Expand Down
1 change: 1 addition & 0 deletions tendermint/src/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ impl From<Ed25519Signature> for Signature {
}
}

#[cfg(feature = "rust-crypto")]
impl From<ed25519_consensus::Signature> for Signature {
fn from(sig: ed25519_consensus::Signature) -> Signature {
Self(sig.to_bytes().to_vec())
Expand Down
10 changes: 7 additions & 3 deletions tendermint/src/validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use tendermint_proto::{

use crate::{
account,
crypto::ed25519::Verifier,
crypto::Sha256,
hash::Hash,
merkle::{self, MerkleHash},
Expand Down Expand Up @@ -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<V>(&self, sign_bytes: &[u8], signature: &Signature) -> Result<(), Error>
where
V: Verifier,
{
V::verify(self.pub_key, sign_bytes, signature)
}

#[cfg(feature = "rust-crypto")]
Expand Down Expand Up @@ -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(
Expand Down

0 comments on commit dd57553

Please sign in to comment.