use sha3::{Digest, Keccak256};
use crate::swarm::errors::Error;
use crate::swarm::typed_bytes::{Reference, SPAN_LENGTH, Span};
pub const CHUNK_SIZE: usize = 4096;
pub const SEGMENT_SIZE: usize = 32;
pub const SEGMENTS_COUNT: usize = CHUNK_SIZE / SEGMENT_SIZE;
pub const MIN_PAYLOAD_SIZE: usize = 1;
pub const MAX_PAYLOAD_SIZE: usize = CHUNK_SIZE;
pub fn keccak256(input: &[u8]) -> [u8; 32] {
let mut h = Keccak256::new();
h.update(input);
let out = h.finalize();
let mut a = [0u8; 32];
a.copy_from_slice(&out);
a
}
pub fn calculate_chunk_address(data: &[u8]) -> Result<[u8; 32], Error> {
if data.len() < SPAN_LENGTH {
return Err(Error::argument("chunk data shorter than span"));
}
let span = &data[..SPAN_LENGTH];
let payload = &data[SPAN_LENGTH..];
if payload.len() > CHUNK_SIZE {
return Err(Error::argument("chunk payload exceeds CHUNK_SIZE"));
}
let mut padded = [0u8; CHUNK_SIZE];
padded[..payload.len()].copy_from_slice(payload);
let root = bmt_root(&padded);
let mut h = Keccak256::new();
h.update(span);
h.update(root);
let out = h.finalize();
let mut a = [0u8; 32];
a.copy_from_slice(&out);
Ok(a)
}
fn bmt_root(payload: &[u8; CHUNK_SIZE]) -> [u8; 32] {
let mut level: Vec<[u8; 32]> = (0..SEGMENTS_COUNT)
.map(|i| {
let mut seg = [0u8; SEGMENT_SIZE];
seg.copy_from_slice(&payload[i * SEGMENT_SIZE..(i + 1) * SEGMENT_SIZE]);
seg
})
.collect();
while level.len() > 1 {
let next: Vec<[u8; 32]> = level
.chunks(2)
.map(|pair| {
let mut h = Keccak256::new();
h.update(pair[0]);
h.update(pair[1]);
let out = h.finalize();
let mut a = [0u8; 32];
a.copy_from_slice(&out);
a
})
.collect();
level = next;
}
level[0]
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Chunk {
pub address: Reference,
pub span: Span,
pub payload: Vec<u8>,
}
impl Chunk {
pub fn data(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(SPAN_LENGTH + self.payload.len());
out.extend_from_slice(self.span.as_bytes());
out.extend_from_slice(&self.payload);
out
}
}
pub fn make_content_addressed_chunk(payload: &[u8]) -> Result<Chunk, Error> {
if payload.is_empty() || payload.len() > MAX_PAYLOAD_SIZE {
return Err(Error::argument(format!(
"payload size out of bounds: {}",
payload.len()
)));
}
let span = Span::from_u64(payload.len() as u64);
let mut full = Vec::with_capacity(SPAN_LENGTH + payload.len());
full.extend_from_slice(span.as_bytes());
full.extend_from_slice(payload);
let addr = calculate_chunk_address(&full)?;
let address = Reference::new(&addr)?;
Ok(Chunk {
address,
span,
payload: payload.to_vec(),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cac_address_for_known_payload() {
let chunk = make_content_addressed_chunk(b"hello world").unwrap();
assert_eq!(
chunk.address.to_hex(),
"92672a471f4419b255d7cb0cf313474a6f5856fb347c5ece85fb706d644b630f"
);
assert_eq!(chunk.span.to_u64(), 11);
assert_eq!(chunk.payload, b"hello world");
}
#[test]
fn rejects_empty_and_oversized_payload() {
assert!(make_content_addressed_chunk(&[]).is_err());
let oversize = vec![0u8; MAX_PAYLOAD_SIZE + 1];
assert!(make_content_addressed_chunk(&oversize).is_err());
}
#[test]
fn data_returns_span_then_payload() {
let payload = b"test";
let chunk = make_content_addressed_chunk(payload).unwrap();
let data = chunk.data();
assert_eq!(data.len(), SPAN_LENGTH + payload.len());
assert_eq!(&data[..SPAN_LENGTH], chunk.span.as_bytes());
assert_eq!(&data[SPAN_LENGTH..], payload);
}
#[test]
fn calculate_chunk_address_rejects_short_data() {
assert!(calculate_chunk_address(&[0u8; 4]).is_err());
assert!(calculate_chunk_address(&[0u8; SPAN_LENGTH]).is_ok()); }
#[test]
fn keccak256_known_value() {
assert_eq!(
hex::encode(keccak256(b"")),
"c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
);
}
}