Skip to content

Commit

Permalink
feat: split BlockHash into more descriptive types
Browse files Browse the repository at this point in the history
  • Loading branch information
DieracDelta committed Jan 11, 2022
1 parent 7487789 commit 7b6d8de
Show file tree
Hide file tree
Showing 12 changed files with 201 additions and 123 deletions.
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ hex_fmt = "0.3.0"
rand = "0.7.3"
rand_chacha = "0.2.2"
serde = { version = "1.0.130", features = ["derive", "rc"] }
serde_bytes = "0.11"
snafu = { version = "0.6.10", features = ["backtraces"] }
threshold_crypto = "0.4.0"
tracing = "0.1.29"
Expand Down
18 changes: 9 additions & 9 deletions src/committee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use rand::Rng;
use rand_chacha::{rand_core::SeedableRng, ChaChaRng};
use std::collections::{HashMap, HashSet};

use crate::{data::BlockHash, PrivKey, PubKey, H_256};
use crate::{data::StateHash, PrivKey, PubKey, H_256};

pub use threshold_crypto as tc;

Expand Down Expand Up @@ -114,7 +114,7 @@ impl<S, const N: usize> DynamicCommittee<S, N> {

/// Hashes the view number and the next hash as the committee seed for vote token generation
/// and verification.
fn hash_commitee_seed(view_number: u64, next_state: BlockHash<N>) -> [u8; H_256] {
fn hash_commitee_seed(view_number: u64, next_state: StateHash<N>) -> [u8; H_256] {
let mut hasher = Hasher::new();
hasher.update("Vote token".as_bytes());
hasher.update(&view_number.to_be_bytes());
Expand Down Expand Up @@ -207,7 +207,7 @@ impl<S, const N: usize> DynamicCommittee<S, N> {
view_number: u64,
pub_key: PubKey,
token: VoteToken,
next_state: BlockHash<N>,
next_state: StateHash<N>,
) -> Option<ValidatedVoteToken> {
let hash = Self::hash_commitee_seed(view_number, next_state);
if !<Self as Vrf<Hasher, Param381>>::verify(token.clone(), pub_key.node, hash) {
Expand Down Expand Up @@ -237,7 +237,7 @@ impl<S, const N: usize> DynamicCommittee<S, N> {
selection_threshold: SelectionThreshold,
view_number: u64,
private_key: &PrivKey,
next_state: BlockHash<N>,
next_state: StateHash<N>,
) -> Option<VoteToken> {
let hash = Self::hash_commitee_seed(view_number, next_state);
let token = <Self as Vrf<Hasher, Param381>>::prove(&private_key.node, &hash);
Expand Down Expand Up @@ -306,7 +306,7 @@ mod tests {

// VRF verification should pass with the correct secret key share, total stake, committee
// seed, and selection threshold
let next_state = BlockHash::<H_256>::from_array(NEXT_STATE);
let next_state = StateHash::<H_256>::from_array(NEXT_STATE);
let input = DynamicCommittee::<S, N>::hash_commitee_seed(VIEW_NUMBER, next_state);
let proof = DynamicCommittee::<S, N>::prove(&secret_key_share_honest, &input);
let valid = DynamicCommittee::<S, N>::verify(proof.clone(), public_key_share_honest, input);
Expand All @@ -330,7 +330,7 @@ mod tests {
assert!(!valid);

// VRF verification should fail if the next state used for proof generation is incorrect
let incorrect_next_state = BlockHash::<H_256>::from_array(INCORRECT_NEXT_STATE);
let incorrect_next_state = StateHash::<H_256>::from_array(INCORRECT_NEXT_STATE);
let incorrect_input =
DynamicCommittee::<S, N>::hash_commitee_seed(VIEW_NUMBER, incorrect_next_state);
let valid =
Expand Down Expand Up @@ -386,7 +386,7 @@ mod tests {
let pub_keys = vec![pub_key.clone()];

// Get the VRF proof
let next_state = BlockHash::<H_256>::from_array(NEXT_STATE);
let next_state = StateHash::<H_256>::from_array(NEXT_STATE);
let input = DynamicCommittee::<S, N>::hash_commitee_seed(VIEW_NUMBER, next_state);
let proof = DynamicCommittee::<S, N>::prove(&secret_key_share, &input);

Expand Down Expand Up @@ -451,7 +451,7 @@ mod tests {
stake_table.insert(pub_key_stakeless, 0);

// Vote token should be null if the public key is not selected as a member.
let next_state = BlockHash::<H_256>::from_array(NEXT_STATE);
let next_state = StateHash::<H_256>::from_array(NEXT_STATE);
let vote_token = DynamicCommittee::<S, N>::make_vote_token(
&stake_table,
SELECTION_THRESHOLD,
Expand Down Expand Up @@ -522,7 +522,7 @@ mod tests {
assert!(votes.is_none());

// No vote should be granted if the next state used for token generation is incorrect
let incorrect_next_state = BlockHash::<H_256>::from_array(INCORRECT_NEXT_STATE);
let incorrect_next_state = StateHash::<H_256>::from_array(INCORRECT_NEXT_STATE);
let incorrect_vote_token = DynamicCommittee::<S, N>::make_vote_token(
&stake_table,
SELECTION_THRESHOLD,
Expand Down
177 changes: 116 additions & 61 deletions src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::traits::BlockContents;
/// as well as the hash of its parent `Leaf`.
pub struct Leaf<T, const N: usize> {
/// The hash of the parent `Leaf`
pub parent: BlockHash<N>,
pub parent: LeafHash<N>,
/// The block contained in this `Leaf`
pub item: T,
}
Expand All @@ -31,19 +31,19 @@ impl<T: BlockContents<N>, const N: usize> Leaf<T, N> {
/// # Arguments
/// * `item` - The block to include
/// * `parent` - The hash of the `Leaf` that is to be the parent of this `Leaf`
pub fn new(item: T, parent: BlockHash<N>) -> Self {
pub fn new(item: T, parent: LeafHash<N>) -> Self {
Leaf { parent, item }
}

/// Hashes the leaf with the hashing algorithm provided by the [`BlockContents`] implementation
///
/// This will concatenate the `parent` hash with the [`BlockContents`] provided hash of the
/// contained block, and then return the hash of the resulting concatenated byte string.
pub fn hash(&self) -> BlockHash<N> {
pub fn hash(&self) -> LeafHash<N> {
let mut bytes = Vec::<u8>::new();
bytes.extend_from_slice(self.parent.as_ref());
bytes.extend_from_slice(BlockContents::hash(&self.item).as_ref());
T::hash_bytes(&bytes)
T::hash_leaf(&bytes)
}
}

Expand Down Expand Up @@ -71,7 +71,7 @@ pub struct QuorumCertificate<const N: usize> {
/// Hash of the [`Leaf`] referred to by this Quorum Certificate
///
/// This value is covered by the threshold signature.
pub(crate) leaf_hash: BlockHash<N>,
pub(crate) leaf_hash: LeafHash<N>,
/// The view number this quorum certificate was generated during
///
/// This value is covered by the threshold signature.
Expand Down Expand Up @@ -105,7 +105,7 @@ impl<const N: usize> QuorumCertificate<N> {
#[must_use]
pub fn verify(&self, key: &tc::PublicKeySet, view: u64, stage: Stage) -> bool {
if let Some(signature) = &self.signature {
let concatenated_hash = create_hash(&self.leaf_hash, view, stage);
let concatenated_hash = create_verify_hash(&self.leaf_hash, view, stage);
key.public_key().verify(signature, &concatenated_hash)
} else {
self.genesis
Expand Down Expand Up @@ -188,49 +188,118 @@ pub enum Stage {
Decide,
}

/// This concatenates the encoding of `blockhash`, `view`, and `stage`, in
/// This concatenates the encoding of `leaf_hash`, `view`, and `stage`, in
/// that order, and hashes the result.
pub fn create_hash<const N: usize>(
blockhash: &BlockHash<N>,
pub fn create_verify_hash<const N: usize>(
leaf_hash: &LeafHash<N>,
view: u64,
stage: Stage,
) -> BlockHash<32> {
) -> VerifyHash<32> {
let mut hasher = Hasher::new();
hasher.update(blockhash.as_ref());
hasher.update(leaf_hash.as_ref());
hasher.update(&view.to_be_bytes());
hasher.update(&(stage as u64).to_be_bytes());
let hash = hasher.finalize();
BlockHash::from_array(*hash.as_bytes())
VerifyHash::from_array(*hash.as_bytes())
}

/// Type used for representing hashes
/// generates boilerplate code for any wrapper types
/// around `InternalHash`
macro_rules! gen_hash_wrapper_type {
($t:ident) => {
#[derive(PartialEq, Eq, Clone, Copy, Hash, Serialize, Deserialize)]
/// External wrapper type
pub struct $t<const N: usize> {
inner: InternalHash<N>,
}

impl<const N: usize> $t<N> {
/// Converts an array of the correct size directly into an `Self`
pub fn from_array(input: [u8; N]) -> Self {
$t {
inner: InternalHash::from_array(input),
}
}
/// Clones the contents of this Hash into a `Vec<u8>`
pub fn to_vec(self) -> Vec<u8> {
self.inner.to_vec()
}
#[cfg(test)]
pub fn random() -> Self {
$t {
inner: InternalHash::random(),
}
}
}
impl<const N: usize> AsRef<[u8]> for $t<N> {
fn as_ref(&self) -> &[u8] {
self.inner.as_ref()
}
}

impl<const N: usize> From<[u8; N]> for $t<N> {
fn from(input: [u8; N]) -> Self {
Self::from_array(input)
}
}

impl<const N: usize> Default for $t<N> {
fn default() -> Self {
$t {
inner: InternalHash::default(),
}
}
}
impl<const N: usize> Debug for $t<N> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct(&format!("{}", std::any::type_name::<$t<N>>()))
.field("inner", &format!("{}", HexFmt(&self.inner)))
.finish()
}
}
};
}

gen_hash_wrapper_type!(BlockHash);
gen_hash_wrapper_type!(LeafHash);
gen_hash_wrapper_type!(TransactionHash);
gen_hash_wrapper_type!(VerifyHash);
gen_hash_wrapper_type!(StateHash);

/// Internal type used for representing hashes
///
/// This is a thin wrapper around a `[u8; N]` used to work around various issues with libraries that
/// have not updated to be const-generic aware. In particular, this provides a `serde` [`Serialize`]
/// and [`Deserialize`] implementation over the const-generic array, which `serde` normally does not
/// have for the general case.
///
/// TODO([#36](https://gitlab.com/translucence/systems/phaselock/-/issues/36)) Break this up into a
/// core type and type-stating wrappers, and utilize `serde_bytes` instead of the visitor
/// implementation
#[derive(PartialEq, Eq, Debug, Clone, Copy, Hash)]
pub struct BlockHash<const N: usize> {
#[derive(PartialEq, Eq, Clone, Copy, Hash, Serialize, Deserialize)]
struct InternalHash<const N: usize> {
/// The underlying array
/// No support for const generics
#[serde(with = "serde_bytes_array")]
inner: [u8; N],
}

impl<const N: usize> BlockHash<N> {
/// Converts an array of the correct size directly into a `BlockHash`
impl<const N: usize> Debug for InternalHash<N> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("InternalHash")
.field("inner", &format!("{}", HexFmt(&self.inner)))
.finish()
}
}

impl<const N: usize> InternalHash<N> {
/// Converts an array of the correct size directly into an `InternalHash`
pub const fn from_array(input: [u8; N]) -> Self {
Self { inner: input }
}

/// Clones the contents of this `BlockHash` into a `Vec<u8>`
pub fn to_vec(&self) -> Vec<u8> {
/// Clones the contents of this `InternalHash` into a `Vec<u8>`
pub fn to_vec(self) -> Vec<u8> {
self.inner.to_vec()
}

/// Testing only random generation of a `BlockHash`
/// Testing only random generation of a `InternalHash`
#[cfg(test)]
pub fn random() -> Self {
use rand::Rng;
Expand All @@ -241,65 +310,51 @@ impl<const N: usize> BlockHash<N> {
}
}

impl<const N: usize> AsRef<[u8]> for BlockHash<N> {
impl<const N: usize> AsRef<[u8]> for InternalHash<N> {
fn as_ref(&self) -> &[u8] {
&self.inner
}
}

impl<const N: usize> From<[u8; N]> for BlockHash<N> {
impl<const N: usize> From<[u8; N]> for InternalHash<N> {
fn from(input: [u8; N]) -> Self {
Self::from_array(input)
}
}

impl<const N: usize> Default for BlockHash<N> {
impl<const N: usize> Default for InternalHash<N> {
fn default() -> Self {
BlockHash {
InternalHash {
inner: [0_u8; { N }],
}
}
}

impl<const N: usize> Serialize for BlockHash<N> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_bytes(&self.inner)
}
}
/// [Needed](https://github.com/serde-rs/bytes/issues/26#issuecomment-902550669) to (de)serialize const generic arrays
mod serde_bytes_array {
use core::convert::TryInto;

use serde::de::Error;
use serde::{Deserializer, Serializer};

impl<'de, const N: usize> Deserialize<'de> for BlockHash<N> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
/// This just specializes [`serde_bytes::serialize`] to `<T = [u8]>`.
pub(crate) fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
where
D: serde::Deserializer<'de>,
S: Serializer,
{
deserializer.deserialize_bytes(BlockHashVisitor::<N>)
serde_bytes::serialize(bytes, serializer)
}
}

/// `Visitor` implementation for deserializing `BlockHash`
struct BlockHashVisitor<const N: usize>;

impl<'de, const N: usize> serde::de::Visitor<'de> for BlockHashVisitor<N> {
type Value = BlockHash<N>;

fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(formatter, "a byte array of length {}", { N })
}

fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
/// This takes the result of [`serde_bytes::deserialize`] from `[u8]` to `[u8; N]`.
pub(crate) fn deserialize<'de, D, const N: usize>(deserializer: D) -> Result<[u8; N], D::Error>
where
E: serde::de::Error,
D: Deserializer<'de>,
{
if v.len() == { N } {
let mut result = [0_u8; { N }];
result.copy_from_slice(v);
Ok(BlockHash { inner: result })
} else {
let x = format!("{}", { N });
Err(E::invalid_length(v.len(), &x.as_str()))
}
let slice: &[u8] = serde_bytes::deserialize(deserializer)?;
let array: [u8; N] = slice.try_into().map_err(|_| {
let expected = format!("[u8; {}]", N);
D::Error::invalid_length(slice.len(), &expected.as_str())
})?;
Ok(array)
}
}
Loading

0 comments on commit 7b6d8de

Please sign in to comment.