Skip to content

Commit

Permalink
feat(abci): impl Hashable for ValidatorSet
Browse files Browse the repository at this point in the history
  • Loading branch information
lklimek committed Jan 28, 2025
1 parent eee1597 commit 4714157
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 5 deletions.
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,);
}
}

0 comments on commit 4714157

Please sign in to comment.