317 lines
9.7 KiB
Rust
317 lines
9.7 KiB
Rust
use ghost_traits::hashing::GhostHasher;
|
|
|
|
fn combine_values<H, V>(left: V, right: V) -> H::Hash
|
|
where
|
|
H: GhostHasher,
|
|
V: AsRef<[u8]>,
|
|
{
|
|
let hash_len = H::hash_len();
|
|
let mut combined = sp_std::vec![0u8; hash_len * 2];
|
|
|
|
combined[..hash_len].copy_from_slice(left.as_ref());
|
|
combined[hash_len..].copy_from_slice(right.as_ref());
|
|
|
|
H::hash(&combined)
|
|
}
|
|
|
|
pub fn cumulative_hash<H, S>(raw_values: S) -> H::Hash
|
|
where
|
|
H: GhostHasher,
|
|
S: IntoIterator,
|
|
S::Item: AsRef<[u8]>,
|
|
{
|
|
raw_values
|
|
.into_iter()
|
|
.fold(H::empty(), |acc_hash, raw_value| {
|
|
let hashed_value = H::hash(raw_value.as_ref());
|
|
combine_values::<H, _>(acc_hash, hashed_value)
|
|
})
|
|
}
|
|
|
|
pub fn sequential_hash<H, S>(raw_values: S) -> H::Hash
|
|
where
|
|
H: GhostHasher,
|
|
S: IntoIterator,
|
|
S::Item: AsRef<[u8]>,
|
|
{
|
|
let mut iter = raw_values.into_iter().peekable();
|
|
|
|
let Some(first_value) = iter.next() else {
|
|
return H::empty();
|
|
};
|
|
if iter.peek().is_none() {
|
|
return H::hash(first_value.as_ref());
|
|
}
|
|
|
|
let current_hash = H::from_slice(first_value.as_ref());
|
|
|
|
iter.fold(current_hash, |acc_hash, raw_value| {
|
|
let next_hash = H::from_slice(raw_value.as_ref());
|
|
combine_values::<H, _>(acc_hash, next_hash)
|
|
})
|
|
}
|
|
|
|
pub fn verify_hash_ancestry<H, S>(
|
|
tx_block_header_bytes: &[u8],
|
|
ancestry_headers: S,
|
|
trusted_top_block_hash: H::Hash,
|
|
) -> bool
|
|
where
|
|
H: GhostHasher,
|
|
H::Hash: PartialEq + Copy + AsRef<[u8]> + AsMut<[u8]>,
|
|
S: IntoIterator,
|
|
S::Item: AsRef<[u8]>,
|
|
{
|
|
ancestry_headers
|
|
.into_iter()
|
|
.try_fold(
|
|
sequential_hash::<H, _>(core::iter::once(tx_block_header_bytes)),
|
|
|acc_parent_hash, current_header| {
|
|
let header_ref = current_header.as_ref();
|
|
if header_ref.len() < 36 {
|
|
return None;
|
|
}
|
|
|
|
let mut current_prev_hash = [0u8; 32];
|
|
current_prev_hash.copy_from_slice(&header_ref[4..36]);
|
|
|
|
if current_prev_hash != acc_parent_hash.as_ref() {
|
|
return None;
|
|
}
|
|
|
|
let current_header_iter = core::iter::once(header_ref);
|
|
Some(sequential_hash::<H, _>(current_header_iter))
|
|
},
|
|
)
|
|
.map(|mut final_le_hash| {
|
|
final_le_hash.as_mut().reverse();
|
|
final_le_hash == trusted_top_block_hash
|
|
})
|
|
.unwrap_or(false)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use sp_core::H256;
|
|
use sp_io::hashing::{blake2_256, sha2_256};
|
|
use sp_std::vec::Vec;
|
|
|
|
pub struct TestBlake2Hasher;
|
|
impl GhostHasher for TestBlake2Hasher {
|
|
type Hash = H256;
|
|
type HashBytes = [u8; 32];
|
|
|
|
fn hash(data: &[u8]) -> Self::Hash {
|
|
H256::from(blake2_256(data))
|
|
}
|
|
fn empty() -> Self::Hash {
|
|
H256::zero()
|
|
}
|
|
fn hash_len() -> usize {
|
|
H256::len_bytes()
|
|
}
|
|
}
|
|
|
|
pub struct TestSha2Hasher;
|
|
impl GhostHasher for TestSha2Hasher {
|
|
type Hash = H256;
|
|
type HashBytes = [u8; 32];
|
|
fn hash(data: &[u8]) -> Self::Hash {
|
|
let first_pass: [u8; 32] = sha2_256(data);
|
|
let second_pass: [u8; 32] = sha2_256(&first_pass);
|
|
|
|
H256::from(second_pass)
|
|
}
|
|
fn hash_len() -> usize {
|
|
H256::len_bytes()
|
|
}
|
|
fn empty() -> Self::Hash {
|
|
H256::zero()
|
|
}
|
|
}
|
|
|
|
fn mock_sequential_hash(header: &[u8; 80]) -> H256 {
|
|
sequential_hash::<TestSha2Hasher, _>(core::iter::once(header))
|
|
}
|
|
|
|
fn create_header(prev_hash: [u8; 32]) -> [u8; 80] {
|
|
let mut header = [0u8; 80];
|
|
header[4..36].copy_from_slice(&prev_hash);
|
|
header
|
|
}
|
|
|
|
#[test]
|
|
fn test_generate_empty_values() {
|
|
let empty_list: Vec<Vec<u8>> = sp_std::vec![];
|
|
let result = cumulative_hash::<TestBlake2Hasher, _>(empty_list);
|
|
assert_eq!(H256::zero(), result);
|
|
}
|
|
|
|
#[test]
|
|
fn test_cumulative_hash_determinism_and_avalanche() {
|
|
let dataset_1 = sp_std::vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]];
|
|
let dataset_2 = sp_std::vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]];
|
|
let dataset_3 = sp_std::vec![vec![9, 2, 3], vec![4, 5, 6], vec![7, 8, 9]];
|
|
|
|
let hash_1 = cumulative_hash::<TestBlake2Hasher, _>(dataset_1);
|
|
let hash_2 = cumulative_hash::<TestBlake2Hasher, _>(dataset_2);
|
|
let hash_3 = cumulative_hash::<TestBlake2Hasher, _>(dataset_3);
|
|
|
|
assert_eq!(hash_1, hash_2);
|
|
assert_ne!(hash_1, hash_3);
|
|
assert_ne!(hash_2, hash_3);
|
|
}
|
|
|
|
#[test]
|
|
fn test_cumulative_hash_order_sensitive() {
|
|
let dataset_forward = sp_std::vec![sp_std::vec![1, 1, 1], sp_std::vec![2, 2, 2]];
|
|
let dataset_reversed = sp_std::vec![sp_std::vec![2, 2, 2], sp_std::vec![1, 1, 1]];
|
|
|
|
let hash_forward = cumulative_hash::<TestBlake2Hasher, _>(dataset_forward);
|
|
let hash_reversed = cumulative_hash::<TestBlake2Hasher, _>(dataset_reversed);
|
|
|
|
assert_ne!(hash_forward, hash_reversed);
|
|
}
|
|
|
|
#[test]
|
|
fn test_cumulative_hash_with_slices() {
|
|
let slice_data: &[&[u8]] = &[&[1, 2, 3], &[4, 5, 6]];
|
|
let hash_from_slice = cumulative_hash::<TestBlake2Hasher, _>(slice_data.iter());
|
|
assert_ne!(hash_from_slice, H256::zero());
|
|
}
|
|
|
|
#[test]
|
|
fn test_sequential_hash_empty() {
|
|
let empty_list: Vec<Vec<u8>> = sp_std::vec![];
|
|
let result = sequential_hash::<TestBlake2Hasher, _>(empty_list);
|
|
assert_eq!(H256::zero(), result);
|
|
}
|
|
|
|
#[test]
|
|
fn test_sequential_hash_single_element() {
|
|
let single_item = sp_std::vec![sp_std::vec![1, 2, 3]];
|
|
let result = sequential_hash::<TestBlake2Hasher, _>(single_item);
|
|
let expected = TestBlake2Hasher::hash(&[1, 2, 3]);
|
|
assert_eq!(result, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn test_sequential_hash_determinism_and_order() {
|
|
let data_1 = sp_std::vec![sp_std::vec![1], sp_std::vec![2], sp_std::vec![3]];
|
|
let data_2 = sp_std::vec![sp_std::vec![1], sp_std::vec![2], sp_std::vec![3]];
|
|
let data_reversed = sp_std::vec![sp_std::vec![3], sp_std::vec![2], sp_std::vec![1]];
|
|
|
|
let hash_1 = sequential_hash::<TestBlake2Hasher, _>(data_1);
|
|
let hash_2 = sequential_hash::<TestBlake2Hasher, _>(data_2);
|
|
let hash_reversed = sequential_hash::<TestBlake2Hasher, _>(data_reversed);
|
|
|
|
assert_eq!(hash_1, hash_2);
|
|
assert_ne!(hash_1, hash_reversed);
|
|
}
|
|
|
|
#[test]
|
|
fn test_sequential_vs_cumulative_hash() {
|
|
let dataset = sp_std::vec![sp_std::vec![1, 2, 3], sp_std::vec![4, 5, 6]];
|
|
let seq_hash = sequential_hash::<TestBlake2Hasher, _>(dataset.clone());
|
|
let cum_hash = cumulative_hash::<TestBlake2Hasher, _>(dataset);
|
|
assert_ne!(seq_hash, cum_hash);
|
|
}
|
|
|
|
#[test]
|
|
fn test_sequential_hash_with_slices() {
|
|
let slice_data: &[&[u8]] = &[&[10, 20], &[30, 40]];
|
|
let result = sequential_hash::<TestBlake2Hasher, _>(slice_data.iter());
|
|
assert_ne!(result, H256::zero());
|
|
}
|
|
|
|
#[test]
|
|
fn test_verify_in_current_block_with_empty_ancestry() {
|
|
let tx_header = [0x11u8; 80];
|
|
let tx_block_le_hash = mock_sequential_hash(&tx_header);
|
|
|
|
let mut trusted_bytes = tx_block_le_hash.0;
|
|
trusted_bytes.reverse();
|
|
let trusted_top_block_hash = H256::from(trusted_bytes);
|
|
|
|
let result = verify_hash_ancestry::<TestSha2Hasher, &[[u8; 80]]>(
|
|
&tx_header,
|
|
&[],
|
|
trusted_top_block_hash,
|
|
);
|
|
|
|
assert!(result);
|
|
}
|
|
|
|
#[test]
|
|
fn test_verify_with_single_ancestor_header() {
|
|
let tx_header_100 = [0x22u8; 80];
|
|
let hash_100_le = mock_sequential_hash(&tx_header_100);
|
|
|
|
let header_101 = create_header(hash_100_le.0);
|
|
let hash_101_le = mock_sequential_hash(&header_101);
|
|
|
|
let mut trusted_bytes = hash_101_le.0;
|
|
trusted_bytes.reverse();
|
|
let trusted_top_block_hash = H256::from(trusted_bytes);
|
|
|
|
let ancestry = [header_101];
|
|
let result = verify_hash_ancestry::<TestSha2Hasher, &[[u8; 80]]>(
|
|
&tx_header_100,
|
|
&ancestry,
|
|
trusted_top_block_hash,
|
|
);
|
|
|
|
assert!(result);
|
|
}
|
|
|
|
#[test]
|
|
fn test_verify_with_multiple_ancestor_headers_chain() {
|
|
let header_100 = [0x55u8; 80];
|
|
let hash_100_le = mock_sequential_hash(&header_100);
|
|
|
|
let header_101 = create_header(hash_100_le.0);
|
|
let hash_101_le = mock_sequential_hash(&header_101);
|
|
|
|
let header_102 = create_header(hash_101_le.0);
|
|
let hash_102_le = mock_sequential_hash(&header_102);
|
|
|
|
let header_103 = create_header(hash_102_le.0);
|
|
let hash_103_le = mock_sequential_hash(&header_103);
|
|
|
|
let mut trusted_bytes = hash_103_le.0;
|
|
trusted_bytes.reverse();
|
|
let trusted_top_block_hash = H256::from(trusted_bytes);
|
|
|
|
let ancestry = [header_101, header_102, header_103];
|
|
let result = verify_hash_ancestry::<TestSha2Hasher, &[[u8; 80]]>(
|
|
&header_100,
|
|
&ancestry,
|
|
trusted_top_block_hash,
|
|
);
|
|
|
|
assert!(result);
|
|
}
|
|
|
|
#[test]
|
|
fn test_verify_fails_if_chain_is_broken() {
|
|
let header_100 = [0xAAu8; 80];
|
|
|
|
let broken_header_101 = create_header([0xFF; 32]);
|
|
let hash_101_le = mock_sequential_hash(&broken_header_101);
|
|
|
|
let mut trusted_bytes = hash_101_le.0;
|
|
trusted_bytes.reverse();
|
|
let trusted_top_block_hash = H256::from(trusted_bytes);
|
|
|
|
let ancestry = [broken_header_101];
|
|
let result = verify_hash_ancestry::<TestSha2Hasher, &[[u8; 80]]>(
|
|
&header_100,
|
|
&ancestry,
|
|
trusted_top_block_hash,
|
|
);
|
|
|
|
assert!(!result);
|
|
}
|
|
}
|