Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(abci): impl Hashable for ValidatorSet #121

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions abci/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@ pub use server::{start_server, CancellationToken, Server, ServerBuilder, ServerR
pub use tenderdash_proto as proto;
use tenderdash_proto::prost::{DecodeError, EncodeError};

#[cfg(feature = "crypto")]
mod merkle;
#[cfg(feature = "crypto")]
pub mod signatures;

#[cfg(feature = "tracing-span")]
/// Create tracing::Span for better logging
pub mod tracing_span;
Expand Down
71 changes: 71 additions & 0 deletions abci/src/merkle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//! Port of Merkle tree implementation in Tenderdash.
//! See https://github.com/dashpay/tenderdash/blob/feat/statesync-integration/crypto/merkle/tree.go
//! for original implementation.

const LEAF_PREFIX: u8 = 0;
const INNER_PREFIX: u8 = 1;

/// Calculate the Merkle root hash of a list of items.
pub(crate) fn merkle_hash<T: AsRef<[u8]>>(items: &[T]) -> [u8; 32] {
match items.len() {
0 => empty_hash(),
1 => leaf(items[0].as_ref()),
_ => {
let k = get_split_point(items.len() as i64) as usize;
let left = merkle_hash(&items[..k]);
let right = merkle_hash(&items[k..]);
inner(left, right)
},
}
}

fn leaf(data: &[u8]) -> [u8; 32] {
let mut buf = vec![LEAF_PREFIX; 1];
buf.extend(data);
lhash::sha256(&buf)
}

fn inner(left: [u8; 32], right: [u8; 32]) -> [u8; 32] {
let mut buf = vec![INNER_PREFIX; 1];
buf.extend(left);
buf.extend(right);
lhash::sha256(&buf)
}

fn empty_hash() -> [u8; 32] {
return lhash::sha256(&[]);
}

fn get_split_point(length: i64) -> i64 {
if length < 1 {
panic!("Trying to split a tree with size < 1");
}
let u_length = length as u64;
let bitlen = 64 - u_length.leading_zeros();
let mut k = 1 << (bitlen - 1);
if k == length as u64 {
k >>= 1;
}
k as i64
}

#[cfg(test)]
mod test {
use crate::merkle::merkle_hash;

#[test]
/// Given a set of test vectors that are the same as on Tenderdash tests,
/// When calculating the Merkle root hash,
/// Then the result should be the same as the expected value generated in
/// Tenderdash.
///
/// ## See also
///
/// Tenderdash test [TestHashFromByteSlices](https://github.com/dashpay/tenderdash/blob/feat/statesync-integration/crypto/merkle/tree_test.go#L21)
fn test_merkle_root() {
let items = vec![vec![1, 2], vec![3, 4], vec![5, 6], vec![7, 8], vec![9, 10]];
let root = merkle_hash(&items);
let expected = "f326493eceab4f2d9ffbc78c59432a0a005d6ea98392045c74df5d14a113be18";
assert_eq!(hex::encode(root), expected)
}
}
93 changes: 88 additions & 5 deletions abci/src/signatures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,15 @@ use std::{
};

use bytes::BufMut;
use tenderdash_proto::{prost::Message, types::CanonicalVote};

use crate::{
proto::types::{
BlockId, CanonicalBlockId, CanonicalVoteExtension, Commit, SignedMsgType, StateId, Vote,
VoteExtension, VoteExtensionType,
merkle::merkle_hash,
proto::{
prost::Message,
types::{
BlockId, CanonicalBlockId, CanonicalVote, CanonicalVoteExtension, Commit,
SignedMsgType, StateId, ValidatorSet, Vote, VoteExtension, VoteExtensionType,
},
},
Error,
};
Expand Down Expand Up @@ -420,6 +423,55 @@ impl SignBytes for VoteExtension {
}
}

impl Hashable for ValidatorSet {
/// Calculate hash of validator set.
///
/// Calculates hash of validator set that is used in block header
/// [Header.validators_hash](crate::proto::types::Header::validators_hash)
/// and
/// [Header.next_validators_hash](crate::proto::types::Header::next_validators_hash) fields.
///
/// ## Tenderdash implementation
///
/// Original Tenderdash implementation:
/// ```go
/// bzs := make([][]byte, 2)
/// bzs[0] = vals.ThresholdPublicKey.Bytes()
/// bzs[1] = vals.QuorumHash
/// return merkle.HashFromByteSlices(bzs)
/// ```
///
/// ## Arguments
///
/// All arguments are ignored.
///
/// ## Returns
///
/// Returns hash of validator set.
fn calculate_msg_hash(
&self,
_chain_id: &str,
_height: i64,
_round: i32,
) -> Result<Vec<u8>, Error> {
use tenderdash_proto::crypto::public_key::Sum::*;
let threshold_public_key_enum = self
.threshold_public_key
.as_ref()
.ok_or(Error::Canonical("missing threshold public key".to_string()))?;

let threshold_public_key = match &threshold_public_key_enum.sum {
Some(Bls12381(pk)) => pk,
Some(Ed25519(pk)) => pk,
Some(Secp256k1(pk)) => pk,
None => return Err(Error::Canonical("missing threshold public key".to_string())),
};

let result = merkle_hash(&[threshold_public_key, &self.quorum_hash]);
Ok(result.to_vec())
}
}

#[cfg(test)]
pub mod tests {
use std::{string::ToString, vec::Vec};
Expand All @@ -429,7 +481,7 @@ pub mod tests {
proto::types::{
Commit, PartSetHeader, SignedMsgType, Vote, VoteExtension, VoteExtensionType,
},
signatures::Signable,
signatures::{Hashable, Signable},
};

#[test]
Expand Down Expand Up @@ -599,4 +651,35 @@ pub mod tests {

assert_eq!(sign_hash, expected_sign_hash);
}

#[test]
fn test_validator_set_hash() {
use crate::proto::crypto::{public_key::Sum::Bls12381, PublicKey};

const QUORUM_HASH_HEX: &str =
"703ee5bfc78765cc9e151d8dd84e30e196ababa83ac6cbdee31a88a46bba81b9";
const THRESHOLD_PUB_KEY_HEX: &str =
"830e45e45e6414d9d615473cc2814e6b171c508f9c77e8b16924b74594f61c9956a6fa16335e98467eac8d8bdb76d187";
const VALIDATORS_HASH_HEX: &str =
"81742F95E99EAE96ABC727FE792CECB4996205DE6BFC88AFEE1F60B96BC648B2";

let pubkey_vec = hex::decode(THRESHOLD_PUB_KEY_HEX).unwrap();
let quorum_hash = hex::decode(QUORUM_HASH_HEX).unwrap();
let expected_validators_hash = hex::decode(VALIDATORS_HASH_HEX).unwrap();

let threshold_public_key = PublicKey {
sum: Some(Bls12381(pubkey_vec)),
};

let vs = crate::proto::types::ValidatorSet {
threshold_public_key: Some(threshold_public_key),
quorum_hash,
..Default::default()
};

// chain_id, height and round are unused
let actual = vs.calculate_msg_hash("", 0, 0).unwrap();

assert_eq!(expected_validators_hash, actual,);
}
}
Loading