From 305651b594e188ab4c0f39d2f83703e396ce9997 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 3 Aug 2019 15:23:22 -0400 Subject: [PATCH 1/5] merkle::Hash type --- src/merkle.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/merkle.rs b/src/merkle.rs index ba6d9edc0..f0b28efff 100644 --- a/src/merkle.rs +++ b/src/merkle.rs @@ -5,8 +5,11 @@ use sha2::{Digest, Sha256}; /// Size of Merkle root hash pub const HASH_SIZE: usize = 32; +/// Hash is the output of the cryptographic digest function +pub type Hash = [u8; HASH_SIZE]; + /// Compute a simple Merkle root from the arbitrary sized byte slices -pub fn simple_hash_from_byte_slices(byte_slices: &[&[u8]]) -> [u8; HASH_SIZE] { +pub fn simple_hash_from_byte_slices(byte_slices: &[&[u8]]) -> Hash { let length = byte_slices.len(); match length { 0 => [0; HASH_SIZE], @@ -31,7 +34,7 @@ fn get_split_point(length: usize) -> usize { } // tmhash(0x00 || leaf) -fn leaf_hash(bytes: &[u8]) -> [u8; HASH_SIZE] { +fn leaf_hash(bytes: &[u8]) -> Hash { // make a new array starting with 0 and copy in the bytes let mut leaf_bytes = Vec::with_capacity(bytes.len() + 1); leaf_bytes.push(0x00); @@ -47,7 +50,7 @@ fn leaf_hash(bytes: &[u8]) -> [u8; HASH_SIZE] { } // tmhash(0x01 || left || right) -fn inner_hash(left: &[u8], right: &[u8]) -> [u8; HASH_SIZE] { +fn inner_hash(left: &[u8], right: &[u8]) -> Hash { // make a new array starting with 0x1 and copy in the bytes let mut inner_bytes = Vec::with_capacity(left.len() + right.len() + 1); inner_bytes.push(0x01); From 93ffd2c9b5542c5c9976cbf1230b023c661e76ce Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 3 Aug 2019 15:39:20 -0400 Subject: [PATCH 2/5] validator::Set with JSON hash Can run tests with: `cargo test --features "amino-types,keys,serde_json" -- --nocapture` --- src/lib.rs | 1 + src/validator.rs | 104 ++++++++++++++++++++++++++++++++++++++++++++-- src/vote/power.rs | 5 +++ 3 files changed, 107 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 13578ae10..a6f128b6a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -58,6 +58,7 @@ mod serializers; pub mod signature; pub mod time; mod timeout; +#[cfg(feature = "amino-types")] pub mod validator; mod version; pub mod vote; diff --git a/src/validator.rs b/src/validator.rs index 3859f87c4..d26c618d7 100644 --- a/src/validator.rs +++ b/src/validator.rs @@ -1,14 +1,47 @@ //! Tendermint validators -use crate::{account, vote, PublicKey}; +use crate::{account, merkle, vote, PublicKey}; #[cfg(feature = "serde")] use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; #[cfg(feature = "rpc")] use subtle_encoding::base64; +/// Validator set contains a vector of validators +#[derive(Debug)] +pub struct Set { + validators: Vec, +} + +impl Set { + /// Create a new validator set. + /// vals is mutable so it can be sorted by address. + pub fn new(mut vals: Vec) -> Set { + vals.sort_by(|v1, v2| v1.address.partial_cmp(&v2.address).unwrap()); + Set { validators: vals } + } + + /// Compute the Merkle root of the validator set + pub fn hash(self) -> merkle::Hash { + // We need to get from Vec to &[&[u8]] so we can call simple_hash_from_byte_slices. + // This looks like: Vec -> Vec> -> Vec<&[u8]> -> &[&[u8]] + // Can we simplify this? + // Perhaps simple_hash_from_byteslices should take Vec> directly ? + let validator_bytes: Vec> = self + .validators + .into_iter() + .map(|x| x.hash_bytes()) + .collect(); + let validator_byteslices: Vec<&[u8]> = (&validator_bytes) + .into_iter() + .map(|x| x.as_slice()) + .collect(); + merkle::simple_hash_from_byte_slices(validator_byteslices.as_slice()) + } +} + /// Validator information #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug)] pub struct Info { /// Validator account address pub address: account::Id, @@ -23,6 +56,35 @@ pub struct Info { pub proposer_priority: Option, } +/// InfoHashable is the form of the validator used for computing the Merkle tree. +/// It does not include the address, as that is redundant with the pubkey, +/// nor the proposer priority, as that changes with every block even if the validator set didn't. +#[cfg_attr(feature = "serde", derive(Serialize))] +struct InfoHashable { + pub_key: PublicKey, + voting_power: vote::Power, +} + +/// Info -> InfoHashable +impl From<&Info> for InfoHashable { + fn from(info: &Info) -> InfoHashable { + InfoHashable { + pub_key: info.pub_key, + voting_power: info.voting_power, + } + } +} + +// returns the bytes to be hashed into the Merkle tree - +// the leaves of the tree. +impl Info { + #[cfg(feature = "serde_json")] + fn hash_bytes(&self) -> Vec { + // TODO: use proto3 not serde_json + serde_json::to_vec(&InfoHashable::from(self)).unwrap() + } +} + /// Proposer priority #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] pub struct ProposerPriority(i64); @@ -60,7 +122,7 @@ impl Serialize for ProposerPriority { /// Updates to the validator set #[cfg(feature = "rpc")] -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct Update { /// Validator public key #[serde(deserialize_with = "deserialize_public_key")] @@ -99,3 +161,39 @@ where } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::account::Id; + use signatory::{ed25519, PublicKeyed}; + use signatory_dalek; + + fn generate_validator(vp: u64) -> Info { + let seed = ed25519::Seed::generate(); + let signer = signatory_dalek::Ed25519Signer::from(&seed); + let pk = signer.public_key().unwrap(); + Info { + address: Id::from(pk), + pub_key: PublicKey::Ed25519(pk), + voting_power: vote::Power::new(vp), + proposer_priority: None, + } + } + + #[test] + fn test_validator_set() { + // TODO: get a test vector from the Go code instead of generating validators + let v1 = generate_validator(1); + let v2 = generate_validator(2); + let v3 = generate_validator(3); + println!("{:?}", v1.address); + println!("{:?}", v2.address); + println!("{:?}", v3.address); + + let val_set = Set::new(vec![v1, v2, v3]); + println!("{:?}", val_set); + let hash = val_set.hash(); + println!("{:?}", hash); + } +} diff --git a/src/vote/power.rs b/src/vote/power.rs index e037712fa..0a653260e 100644 --- a/src/vote/power.rs +++ b/src/vote/power.rs @@ -8,6 +8,11 @@ use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; pub struct Power(u64); impl Power { + /// Create a new Power + pub fn new(p: u64) -> Power { + Power(p) + } + /// Get the current voting power pub fn value(self) -> u64 { self.0 From 3bd7615fe3c2810ebc809a052f6532b34f29f9c8 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 3 Aug 2019 15:57:13 -0400 Subject: [PATCH 3/5] use amino for hashing --- src/public_key.rs | 8 ++++++++ src/validator.rs | 20 +++++++++++--------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/public_key.rs b/src/public_key.rs index c6fe4b4b8..a0cd7bad9 100644 --- a/src/public_key.rs +++ b/src/public_key.rs @@ -62,6 +62,14 @@ impl PublicKey { } } + /// Serialize this key as raw bytes + pub fn as_bytes(self) -> Vec { + match self { + PublicKey::Ed25519(ref pk) => pk.as_bytes(), + PublicKey::Secp256k1(ref pk) => pk.as_bytes(), + }.to_vec() + } + /// Serialize this key as amino bytes pub fn to_amino_bytes(self) -> Vec { match self { diff --git a/src/validator.rs b/src/validator.rs index d26c618d7..c6b65a066 100644 --- a/src/validator.rs +++ b/src/validator.rs @@ -1,6 +1,7 @@ //! Tendermint validators use crate::{account, merkle, vote, PublicKey}; +use prost::Message; #[cfg(feature = "serde")] use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; #[cfg(feature = "rpc")] @@ -40,7 +41,6 @@ impl Set { } /// Validator information -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Clone, Debug)] pub struct Info { /// Validator account address @@ -59,18 +59,20 @@ pub struct Info { /// InfoHashable is the form of the validator used for computing the Merkle tree. /// It does not include the address, as that is redundant with the pubkey, /// nor the proposer priority, as that changes with every block even if the validator set didn't. -#[cfg_attr(feature = "serde", derive(Serialize))] +#[derive(Clone, PartialEq, Message)] struct InfoHashable { - pub_key: PublicKey, - voting_power: vote::Power, + #[prost(bytes, tag = "1", amino_name = "tendermint/PubKeyEd25519")] + pub pub_key: Vec, + #[prost(uint64, tag = "2")] + voting_power: u64, } /// Info -> InfoHashable impl From<&Info> for InfoHashable { fn from(info: &Info) -> InfoHashable { InfoHashable { - pub_key: info.pub_key, - voting_power: info.voting_power, + pub_key: info.pub_key.as_bytes(), + voting_power: info.voting_power.value(), } } } @@ -78,10 +80,10 @@ impl From<&Info> for InfoHashable { // returns the bytes to be hashed into the Merkle tree - // the leaves of the tree. impl Info { - #[cfg(feature = "serde_json")] fn hash_bytes(&self) -> Vec { - // TODO: use proto3 not serde_json - serde_json::to_vec(&InfoHashable::from(self)).unwrap() + let mut bytes: Vec = Vec::new(); + InfoHashable::from(self).encode(&mut bytes).unwrap(); + bytes } } From a95c692be49f084fab49d8e2d8b1c3fe75908dc3 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 3 Aug 2019 16:44:55 -0400 Subject: [PATCH 4/5] add a test --- src/validator.rs | 58 +++++++++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/src/validator.rs b/src/validator.rs index c6b65a066..d16d0d2b9 100644 --- a/src/validator.rs +++ b/src/validator.rs @@ -56,6 +56,28 @@ pub struct Info { pub proposer_priority: Option, } +impl From for account::Id { + fn from(pub_key: PublicKey) -> account::Id { + match pub_key { + PublicKey::Ed25519(pk) => account::Id::from(pk), + PublicKey::Secp256k1(pk) => account::Id::from(pk), + } + } +} + + +impl Info { + /// Create a new validator. + pub fn new(pk: PublicKey, vp: vote::Power) -> Info { + Info { + address: account::Id::from(pk), + pub_key: pk, + voting_power: vp, + proposer_priority: None, + } + } +} + /// InfoHashable is the form of the validator used for computing the Merkle tree. /// It does not include the address, as that is redundant with the pubkey, /// nor the proposer priority, as that changes with every block even if the validator set didn't. @@ -167,35 +189,25 @@ where #[cfg(test)] mod tests { use super::*; - use crate::account::Id; - use signatory::{ed25519, PublicKeyed}; - use signatory_dalek; - - fn generate_validator(vp: u64) -> Info { - let seed = ed25519::Seed::generate(); - let signer = signatory_dalek::Ed25519Signer::from(&seed); - let pk = signer.public_key().unwrap(); - Info { - address: Id::from(pk), - pub_key: PublicKey::Ed25519(pk), - voting_power: vote::Power::new(vp), - proposer_priority: None, - } + use subtle_encoding::hex; + + // 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(); + Info::new(pk, vote::Power::new(vp)) } #[test] fn test_validator_set() { - // TODO: get a test vector from the Go code instead of generating validators - let v1 = generate_validator(1); - let v2 = generate_validator(2); - let v3 = generate_validator(3); - println!("{:?}", v1.address); - println!("{:?}", v2.address); - println!("{:?}", v3.address); + // test vector generated by Go code + let v1 = make_validator("F349539C7E5EF7C49549B09C4BFC2335318AB0FE51FBFAA2433B4F13E816F4A7", 148151478422287875); + let v2 = make_validator("5646AA4C706B7AF73768903E77D117487D2584B76D83EB8FF287934EE7758AFC", 158095448483785107); + let v3 = make_validator("EB6B732C4BD86B5FA3F3BC3DB688DA0ED182A7411F81C2D405506B298FC19E52", 770561664770006272); + let hash_string = "B92B4474567A1B57969375C13CF8129AA70230642BD7FB9FB2CC316E87CE01D7"; + let hash_expect = &hex::decode_upper(hash_string).unwrap(); let val_set = Set::new(vec![v1, v2, v3]); - println!("{:?}", val_set); let hash = val_set.hash(); - println!("{:?}", hash); + assert_eq!(hash_expect, &hash); } } From ed79706249eac906c85951c2ed6ce988fc85a901 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 3 Aug 2019 16:51:16 -0400 Subject: [PATCH 5/5] minor comments --- src/validator.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/validator.rs b/src/validator.rs index d16d0d2b9..8b6d8a9e7 100644 --- a/src/validator.rs +++ b/src/validator.rs @@ -65,7 +65,6 @@ impl From for account::Id { } } - impl Info { /// Create a new validator. pub fn new(pk: PublicKey, vp: vote::Power) -> Info { @@ -81,6 +80,8 @@ impl Info { /// InfoHashable is the form of the validator used for computing the Merkle tree. /// It does not include the address, as that is redundant with the pubkey, /// nor the proposer priority, as that changes with every block even if the validator set didn't. +/// It contains only the pubkey and the voting power, and is amino encoded. +/// TODO: currently only works for Ed25519 pubkeys #[derive(Clone, PartialEq, Message)] struct InfoHashable { #[prost(bytes, tag = "1", amino_name = "tendermint/PubKeyEd25519")] @@ -100,7 +101,8 @@ impl From<&Info> for InfoHashable { } // returns the bytes to be hashed into the Merkle tree - -// the leaves of the tree. +// the leaves of the tree. this is an amino encoding of the +// pubkey and voting power, so it includes the pubkey's amino prefix. impl Info { fn hash_bytes(&self) -> Vec { let mut bytes: Vec = Vec::new();