From 5f10d144d34b7f649efe2bc30c542f482a17ce6a Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Wed, 12 Aug 2020 20:16:15 -0700 Subject: [PATCH] Replace `signatory` with `ed25519-dalek` and `k256` crates Native support for the `signature` traits used by the `signatory*` was recently added to `ed25519-dalek` in v1.0.0-pre.4, and the `k256` crate recently added a native pure Rust implementation of ECDSA/secp256k1. Together these changes eliminate the need for the `signatory*` wrapper crates. Additionally, `k256` eliminates the need for the rust-secp256k1 C-based library, which has been problematic with e.g. WASM (#391). This commit eliminates all `signatory` dependencies and uses `ed25519-dalek` and `k256` directly instead. Both of these were already dependencies, so it's not adding any additional dependencies, but instead removes several of them. --- light-client/src/operations/voting_power.rs | 13 +- tendermint/Cargo.toml | 13 +- tendermint/src/account.rs | 33 ++-- tendermint/src/amino_types/ed25519.rs | 43 +++-- tendermint/src/amino_types/proposal.rs | 1 - tendermint/src/amino_types/signature.rs | 1 - tendermint/src/amino_types/vote.rs | 1 - tendermint/src/config/node_key.rs | 7 +- tendermint/src/lib.rs | 5 +- tendermint/src/node/id.rs | 11 +- tendermint/src/private_key.rs | 100 ++++------- tendermint/src/public_key.rs | 188 +++++++++++++------- tendermint/src/serializers/tests.rs | 8 +- tendermint/src/signature.rs | 42 ++++- tendermint/src/validator.rs | 33 ++-- tendermint/src/vote.rs | 4 +- testgen/Cargo.toml | 3 +- testgen/src/commit.rs | 4 +- testgen/src/helpers.rs | 13 +- testgen/src/validator.rs | 36 ++-- testgen/src/vote.rs | 21 +-- 21 files changed, 320 insertions(+), 260 deletions(-) diff --git a/light-client/src/operations/voting_power.rs b/light-client/src/operations/voting_power.rs index ecf28d349..f1a25dcf6 100644 --- a/light-client/src/operations/voting_power.rs +++ b/light-client/src/operations/voting_power.rs @@ -146,9 +146,12 @@ impl VotingPowerCalculator for ProdVotingPowerCalculator { // Check vote is valid let sign_bytes = signed_vote.sign_bytes(); - if !validator.verify_signature(&sign_bytes, signed_vote.signature()) { + if validator + .verify_signature(&sign_bytes, signed_vote.signature()) + .is_err() + { bail!(VerificationError::InvalidSignature { - signature: signed_vote.signature().to_vec(), + signature: signed_vote.signature().to_bytes(), validator, sign_bytes, }); @@ -186,14 +189,14 @@ fn non_absent_vote(commit_sig: &CommitSig, validator_index: u64, commit: &Commit } => ( *validator_address, *timestamp, - signature.clone(), + signature, Some(commit.block_id.clone()), ), CommitSig::BlockIDFlagNil { validator_address, timestamp, signature, - } => (*validator_address, *timestamp, signature.clone(), None), + } => (*validator_address, *timestamp, signature, None), }; Some(Vote { @@ -204,7 +207,7 @@ fn non_absent_vote(commit_sig: &CommitSig, validator_index: u64, commit: &Commit timestamp, validator_address, validator_index, - signature, + signature: *signature, }) } diff --git a/tendermint/Cargo.toml b/tendermint/Cargo.toml index 4bda89e09..46ab19be7 100644 --- a/tendermint/Cargo.toml +++ b/tendermint/Cargo.toml @@ -27,16 +27,17 @@ authors = [ [package.metadata.docs.rs] all-features = true - -[badges] -codecov = { repository = "..."} +rustdoc-args = ["--cfg", "docsrs"] [dependencies] anomaly = "0.2" async-trait = "0.1" bytes = "0.5" chrono = { version = "0.4", features = ["serde"] } +ed25519 = "1" +ed25519-dalek = { version = "= 1.0.0-pre.4", features = ["serde"] } futures = "0.3" +k256 = { version = "0.4.2", optional = true, features = ["ecdsa"] } once_cell = "1.3" prost-amino = "0.6" prost-amino-derive = "0.6" @@ -45,9 +46,7 @@ serde_json = "1" serde_bytes = "0.11" serde_repr = "0.1" sha2 = { version = "0.9", default-features = false } -signatory = { version = "0.20", features = ["ed25519", "ecdsa"] } -signatory-dalek = "0.20" -signatory-secp256k1 = { version = "0.20", optional = true } +signature = "1.2" subtle = "2" subtle-encoding = { version = "0.5", features = ["bech32-preview"] } tai64 = { version = "3", features = ["chrono"] } @@ -61,4 +60,4 @@ tendermint-rpc = { path = "../rpc", features = [ "client" ] } tokio = { version = "0.2", features = [ "macros" ] } [features] -secp256k1 = ["signatory-secp256k1", "ripemd160"] +secp256k1 = ["k256", "ripemd160"] diff --git a/tendermint/src/account.rs b/tendermint/src/account.rs index 623d9472a..b31236dc6 100644 --- a/tendermint/src/account.rs +++ b/tendermint/src/account.rs @@ -1,20 +1,25 @@ //! Tendermint accounts -use crate::error::{Error, Kind}; -#[cfg(feature = "secp256k1")] -use ripemd160::Ripemd160; +use crate::{ + error::{Error, Kind}, + public_key::Ed25519, +}; + use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use sha2::{Digest, Sha256}; -#[cfg(feature = "secp256k1")] -use signatory::ecdsa::curve::secp256k1; -use signatory::ed25519; use std::{ + convert::TryInto, fmt::{self, Debug, Display}, str::FromStr, }; use subtle::{self, ConstantTimeEq}; use subtle_encoding::hex; +#[cfg(feature = "secp256k1")] +use crate::public_key::Secp256k1; +#[cfg(feature = "secp256k1")] +use ripemd160::Ripemd160; + /// Size of an account ID in bytes pub const LENGTH: usize = 20; @@ -64,8 +69,8 @@ impl Debug for Id { // RIPEMD160(SHA256(pk)) #[cfg(feature = "secp256k1")] -impl From for Id { - fn from(pk: secp256k1::PublicKey) -> Id { +impl From for Id { + fn from(pk: Secp256k1) -> Id { let sha_digest = Sha256::digest(pk.as_bytes()); let ripemd_digest = Ripemd160::digest(&sha_digest[..]); let mut bytes = [0u8; LENGTH]; @@ -75,12 +80,10 @@ impl From for Id { } // SHA256(pk)[:20] -impl From for Id { - fn from(pk: ed25519::PublicKey) -> Id { +impl From for Id { + fn from(pk: Ed25519) -> Id { let digest = Sha256::digest(pk.as_bytes()); - let mut bytes = [0u8; LENGTH]; - bytes.copy_from_slice(&digest[..LENGTH]); - Id(bytes) + Id(digest[..LENGTH].try_into().unwrap()) } } @@ -141,7 +144,7 @@ mod tests { let id_bytes = Id::from_str(id_hex).expect("expected id_hex to decode properly"); // get id for pubkey - let pubkey = ed25519::PublicKey::from_bytes(pubkey_bytes).unwrap(); + let pubkey = Ed25519::from_bytes(pubkey_bytes).unwrap(); let id = Id::from(pubkey); assert_eq!(id_bytes.ct_eq(&id).unwrap_u8(), 1); @@ -160,7 +163,7 @@ mod tests { let id_bytes = Id::from_str(id_hex).expect("expected id_hex to decode properly"); // get id for pubkey - let pubkey = secp256k1::PublicKey::from_bytes(pubkey_bytes).unwrap(); + let pubkey = Secp256k1::from_bytes(pubkey_bytes).unwrap(); let id = Id::from(pubkey); assert_eq!(id_bytes.ct_eq(&id).unwrap_u8(), 1); diff --git a/tendermint/src/amino_types/ed25519.rs b/tendermint/src/amino_types/ed25519.rs index d63cb8767..345b3059a 100644 --- a/tendermint/src/amino_types/ed25519.rs +++ b/tendermint/src/amino_types/ed25519.rs @@ -1,8 +1,13 @@ use super::compute_prefix; -use crate::public_key::PublicKey; +use crate::{ + error, + public_key::{Ed25519, PublicKey}, + Error, +}; +use anomaly::format_err; use once_cell::sync::Lazy; use prost_amino_derive::Message; -use signatory::ed25519::PUBLIC_KEY_SIZE; +use std::convert::TryFrom; // Note:On the golang side this is generic in the sense that it could everything that implements // github.com/tendermint/tendermint/crypto.PubKey @@ -24,13 +29,15 @@ pub struct PubKeyResponse { #[amino_name = "tendermint/remotesigner/PubKeyRequest"] pub struct PubKeyRequest {} -impl From for PublicKey { +impl TryFrom for PublicKey { + type Error = Error; + // This does not check if the underlying pub_key_ed25519 has the right size. // The caller needs to make sure that this is actually the case. - fn from(response: PubKeyResponse) -> PublicKey { - let mut public_key = [0u8; PUBLIC_KEY_SIZE]; - public_key.copy_from_slice(response.pub_key_ed25519.as_ref()); - PublicKey::Ed25519(signatory::ed25519::PublicKey::new(public_key)) + fn try_from(response: PubKeyResponse) -> Result { + Ed25519::from_bytes(&response.pub_key_ed25519) + .map(Into::into) + .map_err(|_| format_err!(error::Kind::InvalidKey, "malformed Ed25519 key").into()) } } @@ -49,7 +56,9 @@ impl From for PubKeyResponse { #[cfg(test)] mod tests { use super::*; + use ed25519_dalek::PUBLIC_KEY_LENGTH; use prost_amino::Message; + use std::convert::TryInto; #[test] fn test_empty_pubkey_msg() { @@ -140,21 +149,21 @@ mod tests { #[test] fn test_into() { - let raw_pk: [u8; PUBLIC_KEY_SIZE] = [ - 0x79, 0xce, 0xd, 0xe0, 0x43, 0x33, 0x4a, 0xec, 0xe0, 0x8b, 0x7b, 0xb5, 0x61, 0xbc, - 0xe7, 0xc1, 0xd4, 0x69, 0xc3, 0x44, 0x26, 0xec, 0xef, 0xc0, 0x72, 0xa, 0x52, 0x4d, - 0x37, 0x32, 0xef, 0xed, + let raw_pk: [u8; PUBLIC_KEY_LENGTH] = [ + 0xaf, 0xf3, 0x94, 0xc5, 0xb7, 0x5c, 0xfb, 0xd, 0xd9, 0x28, 0xe5, 0x8a, 0x92, 0xdd, + 0x76, 0x55, 0x2b, 0x2e, 0x8d, 0x19, 0x6f, 0xe9, 0x12, 0x14, 0x50, 0x80, 0x6b, 0xd0, + 0xd9, 0x3f, 0xd0, 0xcb, ]; - let want = PublicKey::Ed25519(signatory::ed25519::PublicKey::new(raw_pk)); + let want = PublicKey::Ed25519(Ed25519::from_bytes(&raw_pk).unwrap()); let pk = PubKeyResponse { pub_key_ed25519: vec![ - 0x79, 0xce, 0xd, 0xe0, 0x43, 0x33, 0x4a, 0xec, 0xe0, 0x8b, 0x7b, 0xb5, 0x61, 0xbc, - 0xe7, 0xc1, 0xd4, 0x69, 0xc3, 0x44, 0x26, 0xec, 0xef, 0xc0, 0x72, 0xa, 0x52, 0x4d, - 0x37, 0x32, 0xef, 0xed, + 0xaf, 0xf3, 0x94, 0xc5, 0xb7, 0x5c, 0xfb, 0xd, 0xd9, 0x28, 0xe5, 0x8a, 0x92, 0xdd, + 0x76, 0x55, 0x2b, 0x2e, 0x8d, 0x19, 0x6f, 0xe9, 0x12, 0x14, 0x50, 0x80, 0x6b, 0xd0, + 0xd9, 0x3f, 0xd0, 0xcb, ], }; let orig = pk.clone(); - let got: PublicKey = pk.into(); + let got: PublicKey = pk.try_into().unwrap(); assert_eq!(got, want); @@ -170,6 +179,6 @@ mod tests { pub_key_ed25519: vec![], }; // we expect this to panic: - let _got: PublicKey = empty_msg.into(); + let _got: PublicKey = empty_msg.try_into().unwrap(); } } diff --git a/tendermint/src/amino_types/proposal.rs b/tendermint/src/amino_types/proposal.rs index 2e904aa8d..03529d621 100644 --- a/tendermint/src/amino_types/proposal.rs +++ b/tendermint/src/amino_types/proposal.rs @@ -17,7 +17,6 @@ use bytes::BufMut; use once_cell::sync::Lazy; use prost_amino::{EncodeError, Message}; use prost_amino_derive::Message; -use signatory::ed25519; use std::convert::TryFrom; #[derive(Clone, PartialEq, Message)] diff --git a/tendermint/src/amino_types/signature.rs b/tendermint/src/amino_types/signature.rs index 925e53b21..3844381e1 100644 --- a/tendermint/src/amino_types/signature.rs +++ b/tendermint/src/amino_types/signature.rs @@ -2,7 +2,6 @@ use super::validate; use crate::{chain, consensus}; use bytes::BufMut; use prost_amino::{DecodeError, EncodeError}; -use signatory::ed25519; /// Amino messages which are signable within a Tendermint network pub trait SignableMsg { diff --git a/tendermint/src/amino_types/vote.rs b/tendermint/src/amino_types/vote.rs index 5b2dc29de..ea8bb2221 100644 --- a/tendermint/src/amino_types/vote.rs +++ b/tendermint/src/amino_types/vote.rs @@ -19,7 +19,6 @@ use bytes::BufMut; use once_cell::sync::Lazy; use prost_amino::{error::EncodeError, Message}; use prost_amino_derive::Message; -use signatory::ed25519; use std::convert::TryFrom; const VALIDATOR_ADDR_SIZE: usize = 20; diff --git a/tendermint/src/config/node_key.rs b/tendermint/src/config/node_key.rs index 44a7178a1..ffd0e1d42 100644 --- a/tendermint/src/config/node_key.rs +++ b/tendermint/src/config/node_key.rs @@ -2,9 +2,10 @@ use crate::{ error::{Error, Kind}, + node, private_key::PrivateKey, + public_key::PublicKey, }; -use crate::{node, public_key::PublicKey}; use anomaly::format_err; use serde::{Deserialize, Serialize}; use std::{fs, path::Path}; @@ -42,7 +43,7 @@ impl NodeKey { /// Get the public key for this keypair pub fn public_key(&self) -> PublicKey { match &self.priv_key { - PrivateKey::Ed25519(key) => key.public_key(), + PrivateKey::Ed25519(keypair) => keypair.public.into(), } } @@ -50,7 +51,7 @@ impl NodeKey { pub fn node_id(&self) -> node::Id { #[allow(unreachable_patterns)] match &self.public_key() { - PublicKey::Ed25519(key) => node::Id::from(*key), + PublicKey::Ed25519(pubkey) => node::Id::from(*pubkey), _ => unreachable!(), } } diff --git a/tendermint/src/lib.rs b/tendermint/src/lib.rs index 567acf0c6..859f762a0 100644 --- a/tendermint/src/lib.rs +++ b/tendermint/src/lib.rs @@ -4,6 +4,7 @@ //! blockchain networks, including chain information types, secret connections, //! and remote procedure calls (JSONRPC). +#![cfg_attr(docsrs, feature(doc_cfg))] #![deny( warnings, missing_docs, @@ -50,12 +51,13 @@ pub mod vote; #[cfg(test)] mod test; -pub use crate::genesis::Genesis; pub use crate::{ block::Block, error::{Error, Kind}, + genesis::Genesis, hash::Hash, moniker::Moniker, + private_key::PrivateKey, public_key::{PublicKey, TendermintKey}, signature::Signature, time::Time, @@ -63,4 +65,3 @@ pub use crate::{ version::Version, vote::Vote, }; -pub use private_key::PrivateKey; diff --git a/tendermint/src/node/id.rs b/tendermint/src/node/id.rs index 981ef6663..675181aa7 100644 --- a/tendermint/src/node/id.rs +++ b/tendermint/src/node/id.rs @@ -1,9 +1,12 @@ //! Tendermint node IDs -use crate::error::{Error, Kind}; +use crate::{ + error::{Error, Kind}, + public_key::Ed25519, +}; + use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use sha2::{Digest, Sha256}; -use signatory::ed25519; use std::{ fmt::{self, Debug, Display}, str::FromStr, @@ -58,8 +61,8 @@ impl Debug for Id { } } -impl From for Id { - fn from(pk: ed25519::PublicKey) -> Id { +impl From for Id { + fn from(pk: Ed25519) -> Id { let digest = Sha256::digest(pk.as_bytes()); let mut bytes = [0u8; LENGTH]; bytes.copy_from_slice(&digest[..LENGTH]); diff --git a/tendermint/src/private_key.rs b/tendermint/src/private_key.rs index 6832fe046..08baa722f 100644 --- a/tendermint/src/private_key.rs +++ b/tendermint/src/private_key.rs @@ -1,95 +1,67 @@ //! Cryptographic private keys +pub use ed25519_dalek::{Keypair as Ed25519, EXPANDED_SECRET_KEY_LENGTH as ED25519_KEYPAIR_SIZE}; + use crate::public_key::PublicKey; -use serde::{de, de::Error as _, ser, Deserialize, Serialize}; -use signatory::ed25519; -use signatory::public_key::PublicKeyed; -use signatory_dalek::Ed25519Signer; +use serde::{de, ser, Deserialize, Serialize}; use subtle_encoding::{Base64, Encoding}; -use zeroize::{Zeroize, Zeroizing}; - -/// Size of an Ed25519 keypair (private + public key) in bytes -pub const ED25519_KEYPAIR_SIZE: usize = 64; +use zeroize::Zeroizing; /// Private keys as parsed from configuration files #[derive(Serialize, Deserialize)] +#[non_exhaustive] #[serde(tag = "type", content = "value")] pub enum PrivateKey { /// Ed25519 keys - #[serde(rename = "tendermint/PrivKeyEd25519")] - Ed25519(Ed25519Keypair), + #[serde( + rename = "tendermint/PrivKeyEd25519", + serialize_with = "serialize_ed25519_keypair", + deserialize_with = "deserialize_ed25519_keypair" + )] + Ed25519(Ed25519), } impl PrivateKey { /// Get the public key associated with this private key pub fn public_key(&self) -> PublicKey { match self { - PrivateKey::Ed25519(private_key) => private_key.public_key(), + PrivateKey::Ed25519(private_key) => private_key.public.into(), } } /// If applicable, borrow the Ed25519 keypair - pub fn ed25519_keypair(&self) -> Option<&Ed25519Keypair> { + pub fn ed25519_keypair(&self) -> Option<&Ed25519> { match self { PrivateKey::Ed25519(keypair) => Some(keypair), } } } -/// Ed25519 keypairs -#[derive(Zeroize)] -#[zeroize(drop)] -pub struct Ed25519Keypair([u8; ED25519_KEYPAIR_SIZE]); - -impl Ed25519Keypair { - /// Get the public key associated with this keypair - pub fn public_key(&self) -> PublicKey { - let seed = ed25519::Seed::from_keypair(&self.0[..]).unwrap(); - let pk = signatory_dalek::Ed25519Signer::from(&seed) - .public_key() - .unwrap(); - - PublicKey::from(pk) - } - - /// Get the Signatory Ed25519 "seed" for this signer - pub fn to_seed(&self) -> ed25519::Seed { - ed25519::Seed::from(self) - } - - /// Get a Signatory Ed25519 signer (ed25519-dalek based) - pub fn to_signer(&self) -> Ed25519Signer { - Ed25519Signer::from(&self.to_seed()) - } +/// Serialize an Ed25519 keypair as Base64 +fn serialize_ed25519_keypair(keypair: &Ed25519, serializer: S) -> Result +where + S: ser::Serializer, +{ + let keypair_bytes = Zeroizing::new(keypair.to_bytes()); + Zeroizing::new(String::from_utf8(Base64::default().encode(&keypair_bytes[..])).unwrap()) + .serialize(serializer) } -impl<'a> From<&'a Ed25519Keypair> for ed25519::Seed { - fn from(keypair: &'a Ed25519Keypair) -> ed25519::Seed { - ed25519::Seed::from_keypair(&keypair.0[..]).unwrap() +/// Deserialize an Ed25519 keypair from Base64 +fn deserialize_ed25519_keypair<'de, D>(deserializer: D) -> Result +where + D: de::Deserializer<'de>, +{ + use de::Error; + let string = Zeroizing::new(String::deserialize(deserializer)?); + let mut keypair_bytes = Zeroizing::new([0u8; ED25519_KEYPAIR_SIZE]); + let decoded_len = Base64::default() + .decode_to_slice(string.as_bytes(), &mut *keypair_bytes) + .map_err(D::Error::custom)?; + + if decoded_len != ED25519_KEYPAIR_SIZE { + return Err(D::Error::custom("invalid Ed25519 keypair size")); } -} - -impl<'de> Deserialize<'de> for Ed25519Keypair { - fn deserialize>(deserializer: D) -> Result { - let string = Zeroizing::new(String::deserialize(deserializer)?); - - let mut keypair_bytes = [0u8; ED25519_KEYPAIR_SIZE]; - let decoded_len = Base64::default() - .decode_to_slice(string.as_bytes(), &mut keypair_bytes) - .map_err(|_| D::Error::custom("invalid Ed25519 keypair"))?; - - if decoded_len != ED25519_KEYPAIR_SIZE { - return Err(D::Error::custom("invalid Ed25519 keypair size")); - } - Ok(Ed25519Keypair(keypair_bytes)) - } -} - -impl Serialize for Ed25519Keypair { - fn serialize(&self, serializer: S) -> Result { - String::from_utf8(Base64::default().encode(&self.0[..])) - .unwrap() - .serialize(serializer) - } + Ed25519::from_bytes(&*keypair_bytes).map_err(D::Error::custom) } diff --git a/tendermint/src/public_key.rs b/tendermint/src/public_key.rs index 3590db354..631a9f462 100644 --- a/tendermint/src/public_key.rs +++ b/tendermint/src/public_key.rs @@ -1,23 +1,24 @@ //! Public keys used in Tendermint networks -use crate::error::{Error, Kind}; -use anomaly::fail; -use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; +pub use ed25519_dalek::PublicKey as Ed25519; + #[cfg(feature = "secp256k1")] -use signatory::ecdsa::curve::secp256k1; -use signatory::ed25519; -use std::{ - fmt::{self, Display}, - ops::Deref, - str::FromStr, +pub use k256::PublicKey as Secp256k1; + +use crate::{ + error::{self, Error}, + signature::Signature, }; -use subtle_encoding::base64; -use subtle_encoding::{bech32, hex}; +use anomaly::{fail, format_err}; +use serde::{de, ser, Deserialize, Serialize}; +use signature::Verifier as _; +use std::{cmp::Ordering, fmt, ops::Deref, str::FromStr}; +use subtle_encoding::{base64, bech32, hex}; /// Public keys allowed in Tendermint protocols -#[derive(Serialize, Deserialize)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] #[serde(tag = "type", content = "value")] -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] pub enum PublicKey { /// Ed25519 keys #[serde( @@ -25,34 +26,34 @@ pub enum PublicKey { serialize_with = "serialize_ed25519_base64", deserialize_with = "deserialize_ed25519_base64" )] - Ed25519(ed25519::PublicKey), + Ed25519(Ed25519), /// Secp256k1 keys #[cfg(feature = "secp256k1")] + #[cfg_attr(docsrs, doc(cfg(feature = "secp256k1")))] #[serde( rename = "tendermint/PubKeySecp256k1", serialize_with = "serialize_secp256k1_base64", deserialize_with = "deserialize_secp256k1_base64" )] - Secp256k1(secp256k1::PublicKey), + Secp256k1(Secp256k1), } impl PublicKey { /// From raw secp256k1 public key bytes #[cfg(feature = "secp256k1")] + #[cfg_attr(docsrs, doc(cfg(feature = "secp256k1")))] pub fn from_raw_secp256k1(bytes: &[u8]) -> Option { - Some(PublicKey::Secp256k1(secp256k1::PublicKey::from_bytes( - bytes, - )?)) + Some(PublicKey::Secp256k1(Secp256k1::from_bytes(bytes)?)) } /// From raw Ed25519 public key bytes pub fn from_raw_ed25519(bytes: &[u8]) -> Option { - Some(PublicKey::Ed25519(ed25519::PublicKey::from_bytes(bytes)?)) + Ed25519::from_bytes(bytes).map(Into::into).ok() } /// Get Ed25519 public key - pub fn ed25519(self) -> Option { + pub fn ed25519(self) -> Option { #[allow(unreachable_patterns)] match self { PublicKey::Ed25519(pk) => Some(pk), @@ -60,14 +61,48 @@ impl PublicKey { } } - /// Serialize this key as raw bytes - pub fn as_bytes(self) -> Vec { + /// Get Secp256k1 public key + #[cfg(feature = "secp256k1")] + #[cfg_attr(docsrs, doc(cfg(feature = "secp256k1")))] + pub fn secp256k1(self) -> Option { + match self { + PublicKey::Secp256k1(pk) => Some(pk), + _ => None, + } + } + + /// Verify the given [`Signature`] using this public key + pub fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), Error> { match self { - PublicKey::Ed25519(ref pk) => pk.as_bytes(), + PublicKey::Ed25519(pk) => match signature { + Signature::Ed25519(sig) => pk.verify(msg, sig).map_err(|_| { + format_err!( + error::Kind::SignatureInvalid, + "Ed25519 signature verification failed" + ) + .into() + }), + }, #[cfg(feature = "secp256k1")] - PublicKey::Secp256k1(ref pk) => pk.as_bytes(), + PublicKey::Secp256k1(_) => fail!( + error::Kind::InvalidKey, + "unsupported signature algorithm (ECDSA/secp256k1)" + ), } - .to_vec() + } + + /// View this key as a byte slice + pub fn as_bytes(&self) -> &[u8] { + match self { + PublicKey::Ed25519(pk) => pk.as_bytes(), + #[cfg(feature = "secp256k1")] + PublicKey::Secp256k1(pk) => pk.as_bytes(), + } + } + + /// Get a vector containing the byte serialization of this key + pub fn to_bytes(self) -> Vec { + self.as_bytes().to_vec() } /// Serialize this key as amino bytes @@ -99,19 +134,43 @@ impl PublicKey { } } -impl From for PublicKey { - fn from(pk: ed25519::PublicKey) -> PublicKey { +impl From for PublicKey { + fn from(pk: Ed25519) -> PublicKey { PublicKey::Ed25519(pk) } } #[cfg(feature = "secp256k1")] -impl From for PublicKey { - fn from(pk: secp256k1::PublicKey) -> PublicKey { +impl From for PublicKey { + fn from(pk: Secp256k1) -> PublicKey { PublicKey::Secp256k1(pk) } } +impl PartialOrd for PublicKey { + fn partial_cmp(&self, other: &PublicKey) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for PublicKey { + fn cmp(&self, other: &Self) -> Ordering { + match self { + PublicKey::Ed25519(a) => match other { + PublicKey::Ed25519(b) => a.as_bytes().cmp(b.as_bytes()), + #[cfg(feature = "secp256k1")] + PublicKey::Secp256k1(_) => Ordering::Less, + }, + #[cfg(feature = "secp256k1")] + PublicKey::Secp256k1(a) => match other { + PublicKey::Ed25519(_) => Ordering::Greater, + #[cfg(feature = "secp256k1")] + PublicKey::Secp256k1(b) => a.as_bytes().cmp(b.as_bytes()), + }, + } + } +} + /// Public key roles used in Tendermint networks #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] pub enum TendermintKey { @@ -123,7 +182,7 @@ pub enum TendermintKey { } impl TendermintKey { - /// Create a new account key from a `PublicKey` + /// Create a new account key from a [`PublicKey`] pub fn new_account_key(public_key: PublicKey) -> Result { match public_key { PublicKey::Ed25519(_) => Ok(TendermintKey::AccountKey(public_key)), @@ -132,27 +191,33 @@ impl TendermintKey { } } - /// Create a new consensus key from a `PublicKey` + /// Create a new consensus key from a [`PublicKey`] pub fn new_consensus_key(public_key: PublicKey) -> Result { #[allow(unreachable_patterns)] match public_key { PublicKey::Ed25519(_) => Ok(TendermintKey::AccountKey(public_key)), _ => fail!( - Kind::InvalidKey, + error::Kind::InvalidKey, "only ed25519 consensus keys are supported" ), } } + + /// Get the [`PublicKey`] value for this [`TendermintKey`] + pub fn public_key(&self) -> &PublicKey { + match self { + TendermintKey::AccountKey(key) => key, + TendermintKey::ConsensusKey(key) => key, + } + } } +// TODO(tarcieri): deprecate/remove this in favor of `TendermintKey::public_key` impl Deref for TendermintKey { type Target = PublicKey; fn deref(&self) -> &PublicKey { - match self { - TendermintKey::AccountKey(key) => key, - TendermintKey::ConsensusKey(key) => key, - } + self.public_key() } } @@ -176,7 +241,7 @@ impl Algorithm { } } -impl Display for Algorithm { +impl fmt::Display for Algorithm { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.as_str()) } @@ -189,28 +254,29 @@ impl FromStr for Algorithm { match s { "ed25519" => Ok(Algorithm::Ed25519), "secp256k1" => Ok(Algorithm::Secp256k1), - _ => Err(Kind::Parse.into()), + _ => Err(error::Kind::Parse.into()), } } } impl Serialize for Algorithm { - fn serialize(&self, serializer: S) -> Result { + fn serialize(&self, serializer: S) -> Result { self.as_str().serialize(serializer) } } impl<'de> Deserialize<'de> for Algorithm { - fn deserialize>(deserializer: D) -> Result { - Self::from_str(&String::deserialize(deserializer)?) - .map_err(|e| D::Error::custom(format!("{}", e))) + fn deserialize>(deserializer: D) -> Result { + use de::Error; + let s = String::deserialize(deserializer)?; + s.parse().map_err(D::Error::custom) } } /// Serialize the bytes of an Ed25519 public key as Base64. Used for serializing JSON -fn serialize_ed25519_base64(pk: &ed25519::PublicKey, serializer: S) -> Result +fn serialize_ed25519_base64(pk: &Ed25519, serializer: S) -> Result where - S: Serializer, + S: ser::Serializer, { String::from_utf8(base64::encode(pk.as_bytes())) .unwrap() @@ -219,40 +285,34 @@ where /// Serialize the bytes of a secp256k1 ECDSA public key as Base64. Used for serializing JSON #[cfg(feature = "secp256k1")] -fn serialize_secp256k1_base64( - pk: &secp256k1::PublicKey, - serializer: S, -) -> Result +fn serialize_secp256k1_base64(pk: &Secp256k1, serializer: S) -> Result where - S: Serializer, + S: ser::Serializer, { String::from_utf8(base64::encode(pk.as_bytes())) .unwrap() .serialize(serializer) } -fn deserialize_ed25519_base64<'de, D>(deserializer: D) -> Result +fn deserialize_ed25519_base64<'de, D>(deserializer: D) -> Result where - D: Deserializer<'de>, + D: de::Deserializer<'de>, { - let bytes = base64::decode(String::deserialize(deserializer)?.as_bytes()) - .map_err(|e| D::Error::custom(format!("{}", e)))?; - - ed25519::PublicKey::from_bytes(&bytes).ok_or_else(|| D::Error::custom("invalid ed25519 key")) + use de::Error; + let encoded = String::deserialize(deserializer)?; + let bytes = base64::decode(&encoded).map_err(D::Error::custom)?; + Ed25519::from_bytes(&bytes).map_err(D::Error::custom) } #[cfg(feature = "secp256k1")] -fn deserialize_secp256k1_base64<'de, D>( - deserializer: D, -) -> Result +fn deserialize_secp256k1_base64<'de, D>(deserializer: D) -> Result where - D: Deserializer<'de>, + D: de::Deserializer<'de>, { - let bytes = base64::decode(String::deserialize(deserializer)?.as_bytes()) - .map_err(|e| D::Error::custom(format!("{}", e)))?; - - secp256k1::PublicKey::from_bytes(&bytes) - .ok_or_else(|| D::Error::custom("invalid secp256k1 key")) + use de::Error; + let encoded = String::deserialize(deserializer)?; + let bytes = base64::decode(&encoded).map_err(D::Error::custom)?; + Secp256k1::from_bytes(&bytes).ok_or_else(|| D::Error::custom("invalid secp256k1 key")) } #[cfg(test)] diff --git a/tendermint/src/serializers/tests.rs b/tendermint/src/serializers/tests.rs index b1cce5470..ff3521fc0 100644 --- a/tendermint/src/serializers/tests.rs +++ b/tendermint/src/serializers/tests.rs @@ -132,9 +132,7 @@ fn deserialize_commit_sig_commit_vote() { assert_eq!(timestamp, Time::unix_epoch()); assert_eq!( signature, - crate::signature::Signature::Ed25519(signatory::ed25519::Signature::new( - EXAMPLE_SIGNATURE - )) + crate::signature::Signature::Ed25519(ed25519::Signature::new(EXAMPLE_SIGNATURE)) ); } else { panic!(format!("expected BlockIDFlagCommit, received {:?}", result)); @@ -169,9 +167,7 @@ fn deserialize_commit_sig_nil_vote() { assert_eq!(timestamp, Time::unix_epoch()); assert_eq!( signature, - crate::signature::Signature::Ed25519(signatory::ed25519::Signature::new( - EXAMPLE_SIGNATURE - )) + crate::signature::Signature::Ed25519(ed25519::Signature::new(EXAMPLE_SIGNATURE)) ); } else { panic!(format!("expected BlockIDFlagNil, received {:?}", result)); diff --git a/tendermint/src/signature.rs b/tendermint/src/signature.rs index db3959146..bb4864bd6 100644 --- a/tendermint/src/signature.rs +++ b/tendermint/src/signature.rs @@ -1,28 +1,47 @@ //! Cryptographic (a.k.a. digital) signatures +pub use ed25519::{Signature as Ed25519, SIGNATURE_LENGTH as ED25519_SIGNATURE_SIZE}; +pub use signature::{Signer, Verifier}; + +#[cfg(feature = "secp256k1")] +pub use k256::ecdsa::Signature as Secp256k1; + use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; -use signatory::signature::Signature as _; +use signature::Signature as _; use subtle_encoding::base64; /// Signatures -#[derive(Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq)] +#[non_exhaustive] pub enum Signature { /// Ed25519 block signature - Ed25519(signatory::ed25519::Signature), + Ed25519(Ed25519), } impl Signature { /// Return the algorithm used to create this particular signature - pub fn algorithm(self) -> Algorithm { + pub fn algorithm(&self) -> Algorithm { match self { Signature::Ed25519(_) => Algorithm::Ed25519, } } + /// Get Ed25519 signature + pub fn ed25519(self) -> Option { + match self { + Signature::Ed25519(sig) => Some(sig), + } + } + /// Return the raw bytes of this signature pub fn as_bytes(&self) -> &[u8] { self.as_ref() } + + /// Get a vector containing the byte serialization of this key + pub fn to_bytes(&self) -> Vec { + self.as_bytes().to_vec() + } } impl AsRef<[u8]> for Signature { @@ -33,15 +52,20 @@ impl AsRef<[u8]> for Signature { } } +impl From for Signature { + fn from(pk: Ed25519) -> Signature { + Signature::Ed25519(pk) + } +} + impl<'de> Deserialize<'de> for Signature { fn deserialize>(deserializer: D) -> Result { let bytes = base64::decode(String::deserialize(deserializer)?.as_bytes()) - .map_err(|e| D::Error::custom(format!("{}", e)))?; + .map_err(D::Error::custom)?; - Ok(Signature::Ed25519( - signatory::ed25519::Signature::from_bytes(&bytes) - .map_err(|e| D::Error::custom(format!("{}", e)))?, - )) + Ed25519::from_bytes(&bytes) + .map(Into::into) + .map_err(D::Error::custom) } } diff --git a/tendermint/src/validator.rs b/tendermint/src/validator.rs index 30aec3bd0..45fb2ac6f 100644 --- a/tendermint/src/validator.rs +++ b/tendermint/src/validator.rs @@ -2,16 +2,10 @@ use prost_amino_derive::Message; use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; -use signatory::{ - ed25519, - signature::{Signature, Verifier}, -}; -use signatory_dalek::Ed25519Verifier; use subtle_encoding::base64; use crate::amino_types::message::AminoMessage; -use crate::hash::Hash; -use crate::{account, merkle, vote, PublicKey}; +use crate::{account, hash::Hash, merkle, vote, Error, PublicKey, Signature}; /// Validator set contains a vector of validators #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] @@ -103,14 +97,8 @@ 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: &[u8]) -> bool { - if let Some(pk) = &self.pub_key.ed25519() { - let verifier = Ed25519Verifier::from(pk); - if let Ok(sig) = ed25519::Signature::from_bytes(signature) { - return verifier.verify(sign_bytes, &sig).is_ok(); - } - } - false + pub fn verify_signature(&self, sign_bytes: &[u8], signature: &Signature) -> Result<(), Error> { + self.pub_key.verify(sign_bytes, signature) } } @@ -153,7 +141,7 @@ struct InfoHashable { impl From<&Info> for InfoHashable { fn from(info: &Info) -> InfoHashable { InfoHashable { - pub_key: info.pub_key.as_bytes(), + pub_key: info.pub_key.to_bytes(), voting_power: info.voting_power.value(), } } @@ -254,7 +242,8 @@ mod tests { // make a validator from a hex ed25519 pubkey and a voting power fn make_validator(pk_string: &str, vp: u64) -> Info { - let pk = PublicKey::from_raw_ed25519(&hex::decode_upper(pk_string).unwrap()).unwrap(); + let bytes = hex::decode_upper(pk_string).unwrap(); + let pk = PublicKey::from_raw_ed25519(&bytes).unwrap(); Info::new(pk, vote::Power::new(vp)) } @@ -270,18 +259,18 @@ mod tests { 158_095_448_483_785_107, ); let v3 = make_validator( - "EB6B732C4BD86B5FA3F3BC3DB688DA0ED182A7411F81C2D405506B298FC19E52", + "76A2B3F5CBB567F0D689D9DF7155FC89A4C878F040D7A5BB85FF68B74D253FC7", 770_561_664_770_006_272, ); - let hash_string = "B92B4474567A1B57969375C13CF8129AA70230642BD7FB9FB2CC316E87CE01D7"; - let hash_expect = &hex::decode_upper(hash_string).unwrap(); + let hash_string = "7B7B00C03EBA5ED17923243D5F7CF974BE2499522EF5B92A3EC60E868A0CCA19"; + let hash_expect = hex::decode_upper(hash_string).unwrap(); let val_set = Set::new(vec![v1, v2, v3]); let hash = val_set.hash(); - assert_eq!(hash_expect, &hash.as_bytes().to_vec()); + assert_eq!(hash_expect, hash.as_bytes().to_vec()); let not_in_set = make_validator( - "EB6B732C5BD86B5FA3F3BC3DB688DA0ED182A7411F81C2D405506B298FC19E52", + "76A2B3F5CBB567F0D689D9DF7155FC89A4C878F040D7A5BB85FF68B74D253FC9", 1, ); assert_eq!(val_set.validator(v1.address).unwrap(), v1); diff --git a/tendermint/src/vote.rs b/tendermint/src/vote.rs index 9adf60190..36193de3d 100644 --- a/tendermint/src/vote.rs +++ b/tendermint/src/vote.rs @@ -108,8 +108,8 @@ impl SignedVote { } /// Return the actual signature on the canonicalized vote. - pub fn signature(&self) -> &[u8] { - self.signature.as_ref() + pub fn signature(&self) -> &Signature { + &self.signature } } diff --git a/testgen/Cargo.toml b/testgen/Cargo.toml index e3130f43b..b38cf5e0f 100644 --- a/testgen/Cargo.toml +++ b/testgen/Cargo.toml @@ -8,9 +8,8 @@ edition = "2018" tendermint = { version = "0.15.0", path = "../tendermint" } serde = { version = "1", features = ["derive"] } serde_json = "1" +ed25519-dalek = "= 1.0.0-pre.4" gumdrop = "0.8.0" -signatory = { version = "0.20", features = ["ed25519", "ecdsa"] } -signatory-dalek = "0.20" simple-error = "0.2.1" [[bin]] diff --git a/testgen/src/commit.rs b/testgen/src/commit.rs index b2c8c1107..411a146cf 100644 --- a/testgen/src/commit.rs +++ b/testgen/src/commit.rs @@ -183,12 +183,12 @@ mod tests { let sign_bytes = get_vote_sign_bytes(block_header.chain_id.as_str(), &block_vote); assert!(!verify_signature( - &valset2[i].get_verifier().unwrap(), + &valset2[i].get_public_key().unwrap(), &sign_bytes, signature )); assert!(verify_signature( - &valset1[i].get_verifier().unwrap(), + &valset1[i].get_public_key().unwrap(), &sign_bytes, signature )); diff --git a/testgen/src/helpers.rs b/testgen/src/helpers.rs index 689f335a0..209ab755f 100644 --- a/testgen/src/helpers.rs +++ b/testgen/src/helpers.rs @@ -1,11 +1,13 @@ //! Helper functions use serde::de::DeserializeOwned; -use signatory::signature::Verifier; -use signatory_dalek::Ed25519Verifier; use simple_error::*; use std::io::{self, Read}; -use tendermint::{amino_types, signature::Signature, vote}; +use tendermint::{ + amino_types, public_key, + signature::{Signature, Verifier}, + vote, +}; /// A macro that generates a complete setter method from a one-liner with necessary information #[macro_export] @@ -45,13 +47,14 @@ pub fn get_vote_sign_bytes(chain_id: &str, vote: &vote::Vote) -> Vec { amino_types::vote::Vote::from(vote), chain_id, vote.validator_address, - vote.signature.clone(), + vote.signature, ); signed_vote.sign_bytes() } -pub fn verify_signature(verifier: &Ed25519Verifier, msg: &[u8], signature: &Signature) -> bool { +pub fn verify_signature(verifier: &public_key::Ed25519, msg: &[u8], signature: &Signature) -> bool { match signature { tendermint::signature::Signature::Ed25519(sig) => verifier.verify(msg, sig).is_ok(), + _ => false, } } diff --git a/testgen/src/validator.rs b/testgen/src/validator.rs index 33aee42a8..4ca8655f5 100644 --- a/testgen/src/validator.rs +++ b/testgen/src/validator.rs @@ -1,10 +1,13 @@ use crate::{helpers::*, Generator}; +use ed25519_dalek::SecretKey as Ed25519SecretKey; use gumdrop::Options; use serde::Deserialize; -use signatory::{ed25519, public_key::PublicKeyed}; -use signatory_dalek::{Ed25519Signer, Ed25519Verifier}; use simple_error::*; -use tendermint::{account, public_key::PublicKey, validator, vote}; +use tendermint::{ + account, private_key, + public_key::{self, PublicKey}, + validator, vote, +}; #[derive(Debug, Options, Deserialize, Clone)] pub struct Validator { @@ -31,8 +34,8 @@ impl Validator { set_option!(voting_power, u64); set_option!(proposer_priority, i64); - /// Get a signer from this validator companion. - pub fn get_signer(&self) -> Result { + /// Get private key for this validator companion. + pub fn get_private_key(&self) -> Result { let id = match &self.id { None => bail!("validator identifier is missing"), Some(id) => id, @@ -45,19 +48,17 @@ impl Validator { bail!("validator identifier is too long") } bytes.extend(vec![0u8; 32 - bytes.len()].iter()); - let seed = require_with!( - ed25519::Seed::from_bytes(bytes), + let secret = require_with!( + Ed25519SecretKey::from_bytes(&bytes).ok(), "failed to construct a seed from validator identifier" ); - Ok(Ed25519Signer::from(&seed)) + let public = public_key::Ed25519::from(&secret); + Ok(private_key::Ed25519 { secret, public }) } - /// Get a verifier from this validator companion. - pub fn get_verifier(&self) -> Result { - let signer = self.get_signer()?; - let public_key = try_with!(signer.public_key(), "failed to get public key"); - let verifier = Ed25519Verifier::from(&public_key); - Ok(verifier) + /// Get public key for this validator companion. + pub fn get_public_key(&self) -> Result { + self.get_private_key().map(|keypair| keypair.public) } } @@ -89,11 +90,10 @@ impl Generator for Validator { } fn generate(&self) -> Result { - let signer = self.get_signer()?; - let pk = try_with!(signer.public_key(), "failed to get a public key"); + let keypair = self.get_private_key()?; let info = validator::Info { - address: account::Id::from(pk), - pub_key: PublicKey::from(pk), + address: account::Id::from(keypair.public), + pub_key: PublicKey::from(keypair.public), voting_power: vote::Power::new(self.voting_power.unwrap_or(0)), proposer_priority: match self.proposer_priority { None => None, diff --git a/testgen/src/vote.rs b/testgen/src/vote.rs index 96546b8b5..acfbc400a 100644 --- a/testgen/src/vote.rs +++ b/testgen/src/vote.rs @@ -1,11 +1,12 @@ use gumdrop::Options; use serde::Deserialize; -use signatory::{ - ed25519, - signature::{Signature as _, Signer}, -}; use simple_error::*; -use tendermint::{block, signature::Signature, vote, Time}; +use std::convert::TryFrom; +use tendermint::{ + block, + signature::{self, Signature, Signer, ED25519_SIGNATURE_SIZE}, + vote, Time, +}; use crate::{helpers::*, Generator, Header, Validator}; @@ -79,7 +80,7 @@ impl Generator for Vote { None => bail!("failed to generate vote: header is missing"), Some(h) => h, }; - let signer = validator.get_signer()?; + let signer = validator.get_private_key()?; let block_validator = validator.generate()?; let block_header = header.generate()?; let block_id = block::Id::new(block_header.hash(), None); @@ -103,12 +104,12 @@ impl Generator for Vote { validator_address: block_validator.address, validator_index, signature: Signature::Ed25519(try_with!( - ed25519::Signature::from_bytes(&[0_u8; ed25519::SIGNATURE_SIZE]), + signature::Ed25519::try_from(&[0_u8; ED25519_SIGNATURE_SIZE][..]), "failed to construct empty ed25519 signature" )), }; let sign_bytes = get_vote_sign_bytes(block_header.chain_id.as_str(), &vote); - vote.signature = Signature::Ed25519(signer.sign(sign_bytes.as_slice())); + vote.signature = signer.sign(sign_bytes.as_slice()).into(); Ok(vote) } } @@ -152,12 +153,12 @@ mod tests { let sign_bytes = get_vote_sign_bytes(block_header.chain_id.as_str(), &block_vote); assert!(!verify_signature( - &valset1[0].get_verifier().unwrap(), + &valset1[0].get_public_key().unwrap(), &sign_bytes, &block_vote.signature )); assert!(verify_signature( - &valset1[1].get_verifier().unwrap(), + &valset1[1].get_public_key().unwrap(), &sign_bytes, &block_vote.signature ));