diff --git a/tendermint/src/amino_types.rs b/tendermint/src/amino_types.rs index 677482f4f..716e21c79 100644 --- a/tendermint/src/amino_types.rs +++ b/tendermint/src/amino_types.rs @@ -11,6 +11,7 @@ pub mod remote_error; pub mod signature; pub mod time; pub mod validate; +pub mod version; pub mod vote; pub use self::{ @@ -22,5 +23,6 @@ pub use self::{ signature::{SignableMsg, SignedMsgType}, time::TimeMsg, validate::ConsensusMessage, + version::ConsensusVersion, vote::{SignVoteRequest, SignedVoteResponse, AMINO_NAME as VOTE_AMINO_NAME}, }; diff --git a/tendermint/src/amino_types/block_id.rs b/tendermint/src/amino_types/block_id.rs index 430649632..88eebbf3c 100644 --- a/tendermint/src/amino_types/block_id.rs +++ b/tendermint/src/amino_types/block_id.rs @@ -1,4 +1,5 @@ use super::validate::{ConsensusMessage, ValidationError, ValidationErrorKind::*}; +use crate::block::parts; use crate::{ block, error::Error, @@ -14,6 +15,12 @@ pub struct BlockId { pub parts_header: Option, } +impl BlockId { + pub fn new(hash: Vec, parts_header: Option) -> Self { + BlockId { hash, parts_header } + } +} + impl block::ParseId for BlockId { fn parse_block_id(&self) -> Result { let hash = Hash::new(hash::Algorithm::Sha256, &self.hash)?; @@ -25,6 +32,16 @@ impl block::ParseId for BlockId { } } +impl From<&block::Id> for BlockId { + fn from(bid: &block::Id) -> Self { + let bid_hash = bid.hash.as_bytes().unwrap().to_vec(); + match &bid.parts { + Some(parts) => BlockId::new(bid_hash, Some(PartsSetHeader::from(parts))), + None => BlockId::new(bid_hash, None), + } + } +} + impl ConsensusMessage for BlockId { fn validate_basic(&self) -> Result<(), ValidationError> { // Hash can be empty in case of POLBlockID in Proposal. @@ -64,6 +81,18 @@ pub struct PartsSetHeader { pub hash: Vec, } +impl PartsSetHeader { + pub fn new(total: i64, hash: Vec) -> Self { + PartsSetHeader { total, hash } + } +} + +impl From<&parts::Header> for PartsSetHeader { + fn from(parts: &parts::Header) -> Self { + PartsSetHeader::new(parts.total as i64, parts.hash.as_bytes().unwrap().to_vec()) + } +} + impl PartsSetHeader { fn parse_parts_header(&self) -> Option { Hash::new(hash::Algorithm::Sha256, &self.hash) diff --git a/tendermint/src/amino_types/version.rs b/tendermint/src/amino_types/version.rs new file mode 100644 index 000000000..b9561654f --- /dev/null +++ b/tendermint/src/amino_types/version.rs @@ -0,0 +1,21 @@ +use crate::block::*; + +#[derive(Clone, Message)] +pub struct ConsensusVersion { + /// Block version + #[prost(uint64, tag = "1")] + pub block: u64, + + /// App version + #[prost(uint64, tag = "2")] + pub app: u64, +} + +impl From<&header::Version> for ConsensusVersion { + fn from(version: &header::Version) -> Self { + ConsensusVersion { + block: version.block, + app: version.app, + } + } +} diff --git a/tendermint/src/amino_types/vote.rs b/tendermint/src/amino_types/vote.rs index e18d7594b..151484519 100644 --- a/tendermint/src/amino_types/vote.rs +++ b/tendermint/src/amino_types/vote.rs @@ -7,10 +7,12 @@ use super::{ validate::{ConsensusMessage, ValidationError, ValidationErrorKind::*}, SignedMsgType, }; +use crate::amino_types::PartsSetHeader; use crate::{ block::{self, ParseId}, chain, consensus, error::Error, + vote, Hash, }; use bytes::BufMut; use prost::{error::EncodeError, Message}; @@ -50,6 +52,30 @@ impl Vote { } } +impl From<&vote::Vote> for Vote { + fn from(vote: &vote::Vote) -> Self { + Vote { + vote_type: vote.vote_type.to_u32(), + height: vote.height.value() as i64, // TODO potential overflow :-/ + round: vote.round as i64, + block_id: Some(BlockId { + hash: match vote.block_id.hash { + Hash::Sha256(h) => h.to_vec(), + _ => vec![], + }, + parts_header: match &vote.block_id.parts { + Some(parts) => Some(PartsSetHeader::from(parts)), + None => None, + }, + }), + timestamp: Some(TimeMsg::from(vote.timestamp)), + validator_address: vote.validator_address.as_bytes().to_vec(), + validator_index: vote.validator_index as i64, // TODO potential overflow :-/ + signature: vote.signature.as_bytes().to_vec(), + } + } +} + impl block::ParseHeight for Vote { fn parse_block_height(&self) -> Result { block::Height::try_from(self.height) @@ -103,7 +129,7 @@ impl block::ParseHeight for CanonicalVote { } impl CanonicalVote { - fn new(vote: Vote, chain_id: &str) -> CanonicalVote { + pub fn new(vote: Vote, chain_id: &str) -> CanonicalVote { CanonicalVote { vote_type: vote.vote_type, chain_id: chain_id.to_string(), diff --git a/tendermint/src/block/header.rs b/tendermint/src/block/header.rs index fa7372b01..42739e3bb 100644 --- a/tendermint/src/block/header.rs +++ b/tendermint/src/block/header.rs @@ -1,5 +1,7 @@ //! Block headers -use crate::{account, block, chain, lite, Hash, Time}; +use crate::merkle::simple_hash_from_byte_slices; +use crate::{account, amino_types, block, chain, lite, Hash, Time}; +use prost::Message; use { crate::serializers, serde::{Deserialize, Serialize}, @@ -83,11 +85,62 @@ impl lite::Header for Header { } fn next_validators_hash(&self) -> Hash { - unimplemented!() + self.next_validators_hash } fn hash(&self) -> Hash { - unimplemented!() + let mut version_enc = vec![]; + // TODO: if there is an encoding problem this will + // panic (as the golang code would): + // https://github.com/tendermint/tendermint/blob/134fe2896275bb926b49743c1e25493f6b24cc31/types/block.go#L393 + // https://github.com/tendermint/tendermint/blob/134fe2896275bb926b49743c1e25493f6b24cc31/types/encoding_helper.go#L9:6 + // Instead, handle errors gracefully here. + amino_types::ConsensusVersion::from(&self.version) + .encode(&mut version_enc) + .unwrap(); + let height_enc = encode_varint(self.height.value()); + let mut time_enc = vec![]; + amino_types::TimeMsg::from(self.time) + .encode(&mut time_enc) + .unwrap(); + let chain_id_bytes = self.chain_id.as_bytes(); + let chain_id_enc = bytes_enc(&chain_id_bytes); + let num_tx_enc = encode_varint(self.num_txs); + let total_tx_enc = encode_varint(self.total_txs); + let mut last_block_id_enc = vec![]; + amino_types::BlockId::from(&self.last_block_id) + .encode(&mut last_block_id_enc) + .unwrap(); + let last_commit_hash_enc = encode_hash(self.last_commit_hash); + let data_hash_enc = encode_hash(self.data_hash); + let validator_hash_enc = encode_hash(self.validators_hash); + let next_validator_hash_enc = encode_hash(self.next_validators_hash); + let consensus_hash_enc = encode_hash(self.consensus_hash); + let app_hash_enc = encode_hash(self.app_hash); + let last_result_hash_enc = encode_hash(self.last_results_hash); + let evidence_hash_enc = encode_hash(self.evidence_hash); + let proposer_address_bytes = self.proposer_address.as_bytes(); + let proposer_address_enc = bytes_enc(&proposer_address_bytes); + + let mut byteslices: Vec<&[u8]> = vec![]; + byteslices.push(version_enc.as_slice()); + byteslices.push(chain_id_enc.as_slice()); + byteslices.push(height_enc.as_slice()); + byteslices.push(time_enc.as_slice()); + byteslices.push(num_tx_enc.as_slice()); + byteslices.push(total_tx_enc.as_slice()); + byteslices.push(last_block_id_enc.as_slice()); + byteslices.push(last_commit_hash_enc.as_slice()); + byteslices.push(data_hash_enc.as_slice()); + byteslices.push(validator_hash_enc.as_slice()); + byteslices.push(next_validator_hash_enc.as_slice()); + byteslices.push(consensus_hash_enc.as_slice()); + byteslices.push(app_hash_enc.as_slice()); + byteslices.push(last_result_hash_enc.as_slice()); + byteslices.push(evidence_hash_enc.as_slice()); + byteslices.push(proposer_address_enc.as_slice()); + + Hash::Sha256(simple_hash_from_byte_slices(byteslices.as_slice())) } } @@ -111,3 +164,24 @@ pub struct Version { )] pub app: u64, } + +fn bytes_enc(bytes: &[u8]) -> Vec { + let mut chain_id_enc = vec![]; + prost_amino::encode_length_delimiter(bytes.len(), &mut chain_id_enc).unwrap(); + chain_id_enc.append(&mut bytes.to_vec()); + chain_id_enc +} + +fn encode_hash(hash: Hash) -> Vec { + let mut hash_enc = vec![]; + if let Some(last_commit_hash_bytes) = hash.as_bytes() { + hash_enc = bytes_enc(last_commit_hash_bytes); + } + hash_enc +} + +fn encode_varint(val: u64) -> Vec { + let mut val_enc = vec![]; + prost_amino::encoding::encode_varint(val, &mut val_enc); + val_enc +} diff --git a/tendermint/src/chain/id.rs b/tendermint/src/chain/id.rs index 624f973f3..85279ad26 100644 --- a/tendermint/src/chain/id.rs +++ b/tendermint/src/chain/id.rs @@ -30,6 +30,11 @@ impl Id { // so in theory this should never panic str::from_utf8(byte_slice).unwrap() } + + /// Get the chain ID as a raw bytes. + pub fn as_bytes(&self) -> &[u8] { + &self.as_str().as_bytes() + } } impl AsRef for Id { diff --git a/tendermint/src/lite.rs b/tendermint/src/lite.rs index 283bd2e99..a9df710fe 100644 --- a/tendermint/src/lite.rs +++ b/tendermint/src/lite.rs @@ -1,4 +1,5 @@ -pub use self::types::*; pub mod types; pub mod verifier; + +pub use self::types::*; pub use self::verifier::*; diff --git a/tendermint/src/lite/types.rs b/tendermint/src/lite/types.rs index 98e4a7c4e..ddf5534d3 100644 --- a/tendermint/src/lite/types.rs +++ b/tendermint/src/lite/types.rs @@ -81,6 +81,8 @@ pub trait Commit { /// we ignore absent votes and votes for nil here. /// NOTE: we may want to check signatures for nil votes, /// and thus use an ternary enum here instead of the binary Option. + // TODO figure out if we want/can do an iter() method here that returns a + // VoteIterator instead of returning a vec fn into_vec(&self) -> Vec>; } @@ -96,7 +98,7 @@ pub trait Commit { /// is only necessary to avoid slashing in the multi chain context. pub trait Vote { fn validator_id(&self) -> Id; - fn sign_bytes(&self) -> &[u8]; + fn sign_bytes(&self) -> Vec; fn signature(&self) -> &[u8]; } diff --git a/tendermint/src/lite/verifier.rs b/tendermint/src/lite/verifier.rs index fc4141148..4579a68cd 100644 --- a/tendermint/src/lite/verifier.rs +++ b/tendermint/src/lite/verifier.rs @@ -127,7 +127,8 @@ where }; // check vote is valid from validator - if !val.verify_signature(vote.sign_bytes(), vote.signature()) { + let sign_bytes = vote.sign_bytes(); + if !val.verify_signature(&sign_bytes, vote.signature()) { return Err(Error::InvalidSignature); } signed_power += val.power(); @@ -176,7 +177,9 @@ where }; // check vote is valid from validator - if !val.verify_signature(vote.sign_bytes(), vote.signature()) { + let sign_bytes = vote.sign_bytes(); + + if !val.verify_signature(&sign_bytes, vote.signature()) { return Err(Error::InvalidSignature); } signed_power += val.power(); diff --git a/tendermint/src/signature.rs b/tendermint/src/signature.rs index 07d7a0101..b73357b24 100644 --- a/tendermint/src/signature.rs +++ b/tendermint/src/signature.rs @@ -18,6 +18,13 @@ impl Signature { Signature::Ed25519(_) => Algorithm::Ed25519, } } + + /// Return the raw bytes of this signature + pub fn as_bytes(&self) -> &[u8] { + match self { + Signature::Ed25519(sig) => sig.as_bytes(), + } + } } impl<'de> Deserialize<'de> for Signature { diff --git a/tendermint/src/validator.rs b/tendermint/src/validator.rs index a80b2f43a..59441fa0c 100644 --- a/tendermint/src/validator.rs +++ b/tendermint/src/validator.rs @@ -1,8 +1,13 @@ //! Tendermint validators -use crate::{account, merkle, vote, PublicKey}; +use crate::validator::signatory::{Signature, Verifier}; +use crate::{account, lite, merkle, vote, Hash, PublicKey}; use prost::Message; use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; +use signatory; +use signatory::ed25519; +use signatory_dalek; +use signatory_dalek::Ed25519Verifier; use subtle_encoding::base64; /// Validator set contains a vector of validators @@ -30,6 +35,31 @@ impl Set { } } +impl lite::ValidatorSet for Set { + type Validator = Info; + + fn hash(&self) -> Hash { + // TODO almost the same as above's pub fn hash(self) -> merkle::Hash + let validator_bytes: &Vec> = + &self.validators.iter().map(|x| x.hash_bytes()).collect(); + let validator_byteslices: Vec<&[u8]> = + (&validator_bytes).iter().map(|x| x.as_slice()).collect(); + Hash::Sha256(merkle::simple_hash_from_byte_slices( + validator_byteslices.as_slice(), + )) + } + + fn total_power(&self) -> u64 { + self.validators.iter().fold(0u64, |total, val_info| { + total + val_info.voting_power.value() + }) + } + + fn into_vec(&self) -> Vec { + self.validators.to_vec() + } +} + /// Validator information #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Info { @@ -47,6 +77,22 @@ pub struct Info { pub proposer_priority: Option, } +impl lite::Validator for Info { + fn power(&self) -> u64 { + self.voting_power.value() + } + + 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 + } +} + impl From for account::Id { fn from(pub_key: PublicKey) -> account::Id { match pub_key { diff --git a/tendermint/src/vote.rs b/tendermint/src/vote.rs index 32a39ae9e..7efc0814b 100644 --- a/tendermint/src/vote.rs +++ b/tendermint/src/vote.rs @@ -3,7 +3,9 @@ mod power; pub use self::power::Power; -use crate::{account, block, Signature, Time}; +use crate::amino_types::vote::CanonicalVote; +use crate::prost::Message; +use crate::{account, block, lite, Signature, Time}; use { crate::serializers, serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}, @@ -67,6 +69,47 @@ impl Vote { } } +/// SignedVote is the union of a canoncialized vote, the signature on +/// the sign bytes of that vote and the id of the validator who signed it. +pub struct SignedVote { + vote: CanonicalVote, + validator_address: account::Id, + signature: Signature, +} + +impl SignedVote { + /// Create new SignedVote from provided canonicalized vote, validator id, and + /// the signature of that validator. + pub fn new( + vote: CanonicalVote, + validator_address: account::Id, + signature: Signature, + ) -> SignedVote { + SignedVote { + vote, + signature, + validator_address, + } + } +} + +impl lite::Vote for SignedVote { + fn validator_id(&self) -> account::Id { + self.validator_address + } + + fn sign_bytes(&self) -> Vec { + let mut sign_bytes = vec![]; + self.vote.encode(&mut sign_bytes).unwrap(); + sign_bytes + } + fn signature(&self) -> &[u8] { + match &self.signature { + Signature::Ed25519(sig) => sig.as_bytes(), + } + } +} + /// Types of votes #[repr(u8)] #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] @@ -92,6 +135,11 @@ impl Type { pub fn to_u8(self) -> u8 { self as u8 } + + /// Serialize this type as a 32-bit unsigned integer + pub fn to_u32(self) -> u32 { + self as u32 + } } impl Serialize for Type { diff --git a/tendermint/tests/rpc.rs b/tendermint/tests/rpc.rs index 5659b4956..6b533d5b2 100644 --- a/tendermint/tests/rpc.rs +++ b/tendermint/tests/rpc.rs @@ -3,6 +3,7 @@ mod endpoints { use std::{fs, path::PathBuf}; use tendermint::abci::Code; + use tendermint::lite::Header; use tendermint::rpc::{self, endpoint, Response}; const EXAMPLE_APP: &str = "GaiaApp"; @@ -167,8 +168,9 @@ mod endpoints { // in SignedHeader; later we should verify some properties: e.g. block_id.hash matches the // header etc: let commit = response.signed_header.commit; - let _block_id = commit.block_id; + let block_id = commit.block_id; let _precommits = commit.precommits; + assert_eq!(header.hash(), block_id.hash); } #[test]