use sha2::{Digest, Sha256};
pub const ROOT: [u8; 32] = [0u8; 32];
#[derive(Clone)]
struct Entry {
kind: String,
data: Vec<u8>,
hash: [u8; 32],
}
pub struct Corpus {
entries: Vec<Entry>,
}
impl Corpus {
pub fn new() -> Self {
Self {
entries: Vec::new(),
}
}
fn link(prev: &[u8; 32], kind: &str, data: &[u8]) -> [u8; 32] {
let mut h = Sha256::new();
h.update(prev);
h.update(kind.as_bytes());
h.update([0u8]); h.update(data);
h.finalize().into()
}
pub fn append(&mut self, kind: &str, data: &[u8]) {
let prev = self.head();
let hash = Self::link(&prev, kind, data);
self.entries.push(Entry {
kind: kind.to_string(),
data: data.to_vec(),
hash,
});
}
pub fn head(&self) -> [u8; 32] {
self.entries.last().map(|e| e.hash).unwrap_or(ROOT)
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn verify(&self) -> bool {
let mut prev = ROOT;
for e in &self.entries {
let expected = Self::link(&prev, &e.kind, &e.data);
if expected != e.hash {
return false;
}
prev = e.hash;
}
true
}
#[cfg(test)]
fn corrupt_last(&mut self) {
if let Some(e) = self.entries.last_mut() {
e.data.push(0xFF);
}
}
}
impl Default for Corpus {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fresh_corpus_verifies() {
let c = Corpus::new();
assert!(c.is_empty());
assert_eq!(c.head(), ROOT);
assert!(c.verify());
}
#[test]
fn appended_chain_verifies_and_advances_head() {
let mut c = Corpus::new();
c.append("seed", b"hola");
let h1 = c.head();
c.append("breach", b"forgery-x");
assert_eq!(c.len(), 2);
assert_ne!(c.head(), ROOT);
assert_ne!(c.head(), h1, "cada entrada mueve la cabeza");
assert!(c.verify());
}
#[test]
fn poisoning_breaks_the_chain() {
let mut c = Corpus::new();
c.append("seed", b"hola");
c.append("breach", b"forgery-x");
c.corrupt_last();
assert!(!c.verify(), "una entrada alterada debe romper la verificación");
}
}