use crate::bmt::constants::{DEFAULT_BODY_SIZE, PROOF_LENGTH};
use super::*;
use alloy_primitives::{
B256, FixedBytes,
hex::{self, ToHexExt},
};
use digest::{Digest, FixedOutputReset};
use proof::Prover;
use rand::RngExt;
type DefaultHasher = Hasher<DEFAULT_BODY_SIZE>;
#[test]
fn test_concurrent_simple() {
let data: [u8; 3] = [1, 2, 3];
let mut hasher = DefaultHasher::new();
hasher.set_span(data.len() as u64);
hasher.update(&data);
let result = hasher.sum();
let expected = B256::from_slice(
&hex::decode("ca6357a08e317d15ec560fef34e4c45f8f19f01c372aa70f1da72bfa7f1a4338").unwrap(),
);
assert_eq!(result, expected);
}
#[test]
fn test_concurrent_fullsize() {
let data: Vec<u8> = (0..DEFAULT_BODY_SIZE)
.map(|_| rand::random::<u8>())
.collect();
let mut hasher = DefaultHasher::new();
hasher.set_span(data.len() as u64);
hasher.update(&data);
let result1 = hasher.sum();
let mut hasher = DefaultHasher::new();
hasher.set_span(data.len() as u64);
hasher.update(&data);
let result2 = hasher.sum();
assert_eq!(result1, result2, "Same data should produce same hash");
}
#[test]
fn test_hasher_empty_data() {
let mut hasher = DefaultHasher::new();
hasher.set_span(0);
let result = hasher.sum();
let mut hasher2 = DefaultHasher::new();
hasher2.set_span(0);
let result2 = hasher2.sum();
assert_eq!(result, result2, "Empty data should have consistent hash");
}
#[test]
fn test_hasher_correctness() {
let mut rng = rand::rng();
let data: Vec<u8> = (0..DEFAULT_BODY_SIZE)
.map(|_| rand::random::<u8>())
.collect();
let mut start = 0;
while start < data.len() {
let slice_len = std::cmp::min(1 + rng.random_range(0..=5), data.len() - start);
let mut hasher = DefaultHasher::new();
hasher.set_span(slice_len as u64);
hasher.update(&data[..slice_len]);
let result = hasher.sum();
let mut hasher2 = DefaultHasher::new();
hasher2.set_span(slice_len as u64);
hasher2.update(&data[..slice_len]);
let result2 = hasher2.sum();
assert_eq!(result, result2, "Same slice should produce same hash");
start += slice_len;
}
}
#[test]
fn test_bmt_hasher_with_prefix() {
let mut hasher1 = DefaultHasher::new();
hasher1.set_span(11);
hasher1.prefix_with(b"prefix-");
let data = b"hello world";
hasher1.update(data);
let result_with_prefix = hasher1.sum();
let mut hasher2 = DefaultHasher::new();
hasher2.set_span(11);
hasher2.update(data);
let result_without_prefix = hasher2.sum();
assert_ne!(result_with_prefix, result_without_prefix);
let mut hasher3 = DefaultHasher::with_prefix(b"prefix-");
hasher3.set_span(11);
hasher3.update(data);
assert_eq!(result_with_prefix, hasher3.sum());
}
fn reference_prefix_root(prefix: Option<&[u8]>, span: u64, payload: &[u8]) -> B256 {
use alloy_primitives::Keccak256;
let node = |left: &[u8], right: &[u8]| {
let mut h = Keccak256::new();
if let Some(p) = prefix {
h.update(p);
}
h.update(left);
h.update(right);
B256::from_slice(h.finalize().as_slice())
};
let mut buf = [0u8; DEFAULT_BODY_SIZE];
let n = payload.len().min(DEFAULT_BODY_SIZE);
buf[..n].copy_from_slice(&payload[..n]);
let mut level: Vec<B256> = (0..DEFAULT_BODY_SIZE / 32)
.map(|i| B256::from_slice(&buf[i * 32..i * 32 + 32]))
.collect();
while level.len() > 1 {
level = level
.chunks(2)
.map(|pair| node(pair[0].as_slice(), pair[1].as_slice()))
.collect();
}
let mut h = Keccak256::new();
if let Some(p) = prefix {
h.update(p);
}
h.update(span.to_le_bytes());
h.update(level[0].as_slice());
B256::from_slice(h.finalize().as_slice())
}
#[test]
fn test_bee_sample_vector_cac_parity() {
const ANCHOR: &[u8] = b"swarm-test-anchor-deterministic!";
const WANT_CHUNK_ADDR: &str =
"902406053a7a2f3a17f16097e1d0b4b6a4abeae6b84968f5503ae621f9522e16";
const WANT_TRANSFORMED_ADDR: &str =
"9dee91d1ed794460474ffc942996bd713176731db4581a3c6470fe9862905a60";
assert_eq!(ANCHOR.len(), 32, "anchor must be exactly 32 bytes");
let payload: Vec<u8> = (0..DEFAULT_BODY_SIZE).map(|i| (i % 256) as u8).collect();
let span = DEFAULT_BODY_SIZE as u64;
let mut plain = DefaultHasher::new();
plain.set_span(span);
plain.update(&payload);
let chunk_addr = plain.sum();
assert_eq!(
chunk_addr.encode_hex(),
WANT_CHUNK_ADDR,
"plain BMT chunk address must match bee"
);
let mut prefixed = DefaultHasher::with_prefix(ANCHOR);
prefixed.set_span(span);
prefixed.update(&payload);
let transformed = prefixed.sum();
assert_eq!(
transformed.encode_hex(),
WANT_TRANSFORMED_ADDR,
"anchor-prefixed BMT transformed address must match bee"
);
assert_eq!(
transformed,
reference_prefix_root(Some(ANCHOR), span, &payload)
);
}
#[test]
fn test_prefix_sparse_trailing_zeros_parity() {
const ANCHOR: &[u8] = b"swarm-test-anchor-deterministic!";
let mut payload = vec![0u8; DEFAULT_BODY_SIZE];
payload[..5].copy_from_slice(b"hello");
let span = DEFAULT_BODY_SIZE as u64;
let mut prefixed = DefaultHasher::with_prefix(ANCHOR);
prefixed.set_span(span);
prefixed.update(&payload);
let optimized = prefixed.sum();
assert_eq!(
optimized,
reference_prefix_root(Some(ANCHOR), span, &payload),
"prefixed hash of a sparse chunk must match the naive reference (zero \
subtrees must be hashed under the prefix, not via the plain zero table)"
);
let zero_payload = vec![0u8; DEFAULT_BODY_SIZE];
let mut zero_hasher = DefaultHasher::with_prefix(ANCHOR);
zero_hasher.set_span(span);
zero_hasher.update(&zero_payload);
assert_eq!(
zero_hasher.sum(),
reference_prefix_root(Some(ANCHOR), span, &zero_payload)
);
}
#[test]
fn test_prefix_proof_roundtrip() {
const ANCHOR: &[u8] = b"swarm-test-anchor-deterministic!";
let payload: Vec<u8> = (0..DEFAULT_BODY_SIZE).map(|i| (i % 256) as u8).collect();
let span = DEFAULT_BODY_SIZE as u64;
let mut hasher = DefaultHasher::with_prefix(ANCHOR);
hasher.set_span(span);
hasher.update(&payload);
let root = hasher.sum();
for seg in [0usize, 1, 63, 127] {
let proof = hasher.generate_proof(&payload, seg).unwrap();
assert_eq!(proof.prefix.as_deref(), Some(ANCHOR));
assert!(
DefaultHasher::verify_proof(&proof, root.as_slice()).unwrap(),
"prefixed proof for segment {seg} must verify against the prefixed root"
);
let mut plain = DefaultHasher::new();
plain.set_span(span);
plain.update(&payload);
let plain_root = plain.sum();
assert!(
!DefaultHasher::verify_proof(&proof, plain_root.as_slice()).unwrap(),
"prefixed proof must not verify against the plain root"
);
}
}
#[test]
fn test_bmt_hasher_large_data() {
let mut hasher = DefaultHasher::new();
hasher.set_span(DEFAULT_BODY_SIZE as u64);
let data = vec![0x42; DEFAULT_BODY_SIZE];
hasher.update(&data);
let result = hasher.sum();
assert_eq!(
result.as_slice().len(),
std::mem::size_of::<FixedBytes<32>>()
);
}
#[test]
fn test_proof_generation_and_verification() {
let data = b"hello world, this is a test for proof generation and verification";
let mut hasher = DefaultHasher::new();
hasher.set_span(data.len() as u64);
hasher.update(data);
let root_hash = hasher.sum();
let proof = hasher
.generate_proof(data, 0)
.expect("Failed to generate proof");
let is_valid =
DefaultHasher::verify_proof(&proof, root_hash.as_slice()).expect("Failed to verify proof");
assert!(is_valid, "Proof verification should succeed");
}
#[test]
fn test_proof_correctness() {
let mut buf = vec![0u8; DEFAULT_BODY_SIZE];
let data = b"hello world";
buf[..data.len()].copy_from_slice(data);
let mut hasher = DefaultHasher::new();
let span = buf.len() as u64;
hasher.set_span(span);
hasher.update(&buf);
let root_hash = hasher.sum();
let proof = hasher
.generate_proof(&buf, 0)
.expect("Failed to generate proof");
assert_eq!(
proof.proof_segments.len(),
PROOF_LENGTH,
"Incorrect proof length"
);
let expected_segments = [
"0000000000000000000000000000000000000000000000000000000000000000",
"ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5",
"b4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d30",
"21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85",
"e58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344",
"0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d",
"887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968",
];
let verify_segments = |expected: &[&str], proof_segments: &[B256]| {
assert_eq!(
expected.len(),
proof_segments.len(),
"Incorrect number of proof segments"
);
for (i, (exp, actual)) in expected.iter().zip(proof_segments.iter()).enumerate() {
let decoded = B256::from_slice(&hex::decode(exp).expect("Invalid hex encoding"));
assert_eq!(
&decoded,
actual,
"Segment {} mismatch: expected {}, got {}",
i,
exp,
actual.encode_hex()
);
}
};
verify_segments(&expected_segments, &proof.proof_segments);
let is_valid =
DefaultHasher::verify_proof(&proof, root_hash.as_slice()).expect("Failed to verify proof");
assert!(is_valid, "Proof verification should succeed");
let rightmost_proof = hasher
.generate_proof(&buf, 127)
.expect("Failed to generate proof for rightmost segment");
let expected_rightmost_segments = [
"0000000000000000000000000000000000000000000000000000000000000000",
"ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5",
"b4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d30",
"21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85",
"e58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344",
"0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d",
"745bae095b6ff5416b4a351a167f731db6d6f5924f30cd88d48e74261795d27b",
];
verify_segments(
&expected_rightmost_segments,
&rightmost_proof.proof_segments,
);
let is_valid = DefaultHasher::verify_proof(&rightmost_proof, root_hash.as_slice())
.expect("Failed to verify rightmost proof");
assert!(is_valid, "Rightmost proof verification should succeed");
let middle_proof = hasher
.generate_proof(&buf, 64)
.expect("Failed to generate proof for middle segment");
let expected_middle_segments = [
"0000000000000000000000000000000000000000000000000000000000000000",
"ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5",
"b4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d30",
"21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85",
"e58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344",
"0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d",
"745bae095b6ff5416b4a351a167f731db6d6f5924f30cd88d48e74261795d27b",
];
verify_segments(&expected_middle_segments, &middle_proof.proof_segments);
let is_valid = DefaultHasher::verify_proof(&middle_proof, root_hash.as_slice())
.expect("Failed to verify middle proof");
assert!(is_valid, "Middle proof verification should succeed");
}
#[test]
fn test_digest_trait_methods() {
let data = b"test data";
let hash1 = DefaultHasher::digest(data);
let mut hasher = DefaultHasher::new();
hasher.update(data);
let hash2 = hasher.finalize_fixed_reset();
assert_eq!(hash1.as_slice(), hash2.as_slice());
assert_eq!(
hasher.span(),
0,
"Span should be reset after finalize_fixed_reset()"
);
}
#[test]
fn test_root_hash_calculation() {
let mut buf = vec![0u8; DEFAULT_BODY_SIZE];
let data = b"hello world";
buf[..data.len()].copy_from_slice(data);
let mut hasher = DefaultHasher::new();
hasher.set_span(buf.len() as u64);
hasher.update(&buf);
let expected_root_hash = hasher.sum();
let proof = hasher
.generate_proof(&buf, 64)
.expect("Failed to generate proof");
let is_valid = DefaultHasher::verify_proof(&proof, expected_root_hash.as_slice())
.expect("Failed to verify proof");
assert!(is_valid, "Proof verification should succeed");
}
#[test]
fn test_proof() {
let mut buf = vec![0u8; DEFAULT_BODY_SIZE];
rand::rng().fill(&mut buf[..]);
let mut hasher = DefaultHasher::new();
hasher.set_span(buf.len() as u64);
hasher.update(&buf);
let root_hash = hasher.sum();
for i in [0, 1, 32, 64, 127] {
let segment_index = i;
let proof = hasher
.generate_proof(&buf, segment_index)
.expect("Failed to generate proof");
let is_valid = DefaultHasher::verify_proof(&proof, root_hash.as_slice())
.expect("Failed to verify proof");
assert!(
is_valid,
"Proof verification failed for segment {}",
segment_index
);
}
}
#[test]
fn test_excess_data_ignored() {
let exact_data: Vec<u8> = (0..DEFAULT_BODY_SIZE).map(|i| (i % 256) as u8).collect();
let mut excess_data = exact_data.clone();
excess_data.extend(vec![0xFF; 100]);
let excess_len = excess_data.len() as u64;
let mut hasher1 = DefaultHasher::new();
hasher1.set_span(excess_len);
hasher1.update(&exact_data);
let result1 = hasher1.sum();
let mut hasher2 = DefaultHasher::new();
hasher2.set_span(excess_len);
hasher2.update(&excess_data);
let result2 = hasher2.sum();
assert_eq!(
result1, result2,
"Excess data should be ignored in hash calculation"
);
let mut hasher3 = DefaultHasher::new();
hasher3.set_span(exact_data.len() as u64);
hasher3.update(&exact_data);
let result_before_excess = hasher3.sum();
hasher3.update(&[0xFF; 100]);
let result_after_excess = hasher3.sum();
assert_eq!(
result_before_excess, result_after_excess,
"Adding excess data should not change the hash"
);
let mut hasher4 = DefaultHasher::new();
hasher4.set_span(exact_data.len() as u64);
std::io::Write::write(&mut hasher4, &exact_data).unwrap();
let write_result_before = hasher4.sum();
std::io::Write::write(&mut hasher4, &[0xFF; 100]).unwrap();
let write_result_after = hasher4.sum();
assert_eq!(
write_result_before, write_result_after,
"Adding excess data via Write trait should not change the hash"
);
}
#[test]
fn test_write_returns_actual_bytes_written() {
use std::io::Write;
let mut hasher = DefaultHasher::new();
let data = vec![0x42; DEFAULT_BODY_SIZE];
let written = hasher.write(&data).unwrap();
assert_eq!(
written, DEFAULT_BODY_SIZE,
"Should report all bytes written when buffer has space"
);
let more = hasher.write(&[0xFF; 100]).unwrap();
assert_eq!(more, 0, "Should return 0 when buffer is full");
hasher.set_span(DEFAULT_BODY_SIZE as u64);
let _hash = hasher.sum();
let mut hasher2 = DefaultHasher::new();
let partial_data = vec![0x42; DEFAULT_BODY_SIZE - 50];
let written = hasher2.write(&partial_data).unwrap();
assert_eq!(
written,
DEFAULT_BODY_SIZE - 50,
"Should report all bytes written for partial fill"
);
let excess = hasher2.write(&[0xFF; 100]).unwrap();
assert_eq!(
excess, 50,
"Should only write bytes that fit in remaining space"
);
let final_write = hasher2.write(&[0xAA; 10]).unwrap();
assert_eq!(
final_write, 0,
"Should return 0 when buffer is already full"
);
}