use alloc::vec::Vec;
use sha2::{Digest, Sha256};
use crate::error::{FoldError, Result};
use crate::frontend::NodeId;
use crate::verify::Verifier;
pub type Sha256Digest = [u8; 32];
pub const DEFAULT_BLOCK_LOG: u8 = 12;
const HASH_LEN: usize = 32;
const SHA256_INPUT_BLOCK: usize = 64;
pub fn fsverity_digest_sha256(data: &[u8], block_log: u8, salt: &[u8]) -> Sha256Digest {
let bs = 1usize << block_log;
let root = merkle_root(data, bs, &padded_salt(salt));
let mut desc = [0u8; 256];
desc[0] = 1; desc[1] = 1; desc[2] = block_log;
desc[3] = salt.len() as u8;
desc[8..16].copy_from_slice(&(data.len() as u64).to_le_bytes());
desc[16..16 + HASH_LEN].copy_from_slice(&root); let sl = core::cmp::min(salt.len(), 32);
desc[80..80 + sl].copy_from_slice(&salt[..sl]);
Sha256::digest(desc).into()
}
fn padded_salt(salt: &[u8]) -> Vec<u8> {
if salt.is_empty() {
return Vec::new();
}
let mut v = salt.to_vec();
let rem = v.len() % SHA256_INPUT_BLOCK;
if rem != 0 {
v.resize(v.len() + (SHA256_INPUT_BLOCK - rem), 0);
}
v
}
fn hash_block(salt_prefix: &[u8], block: &[u8], bs: usize) -> Sha256Digest {
let mut h = Sha256::new();
h.update(salt_prefix);
h.update(block);
let mut pad = bs - block.len();
let zeros = [0u8; SHA256_INPUT_BLOCK];
while pad > 0 {
let n = core::cmp::min(pad, zeros.len());
h.update(&zeros[..n]);
pad -= n;
}
h.finalize().into()
}
fn merkle_root(data: &[u8], bs: usize, salt_prefix: &[u8]) -> Sha256Digest {
if data.is_empty() {
return [0u8; HASH_LEN];
}
let mut level: Vec<u8> = data.to_vec();
loop {
let nblocks = level.len().div_ceil(bs);
if nblocks <= 1 {
return hash_block(salt_prefix, &level, bs);
}
let mut next = Vec::with_capacity(nblocks * HASH_LEN);
for i in 0..nblocks {
let start = i * bs;
let end = core::cmp::min(start + bs, level.len());
next.extend_from_slice(&hash_block(salt_prefix, &level[start..end], bs));
}
level = next;
}
}
pub struct MerkleVerifier {
block_size: usize,
salt_prefix: Vec<u8>,
leaves: Vec<Sha256Digest>,
measurement: Sha256Digest,
}
impl MerkleVerifier {
pub fn over(data: &[u8], block_log: u8, salt: &[u8]) -> Self {
let block_size = 1usize << block_log;
let salt_prefix = padded_salt(salt);
let mut leaves = Vec::with_capacity(data.len().div_ceil(block_size));
let mut i = 0;
while i < data.len() {
let end = core::cmp::min(i + block_size, data.len());
leaves.push(hash_block(&salt_prefix, &data[i..end], block_size));
i += block_size;
}
Self {
block_size,
salt_prefix,
leaves,
measurement: fsverity_digest_sha256(data, block_log, salt),
}
}
pub fn measurement(&self) -> Sha256Digest {
self.measurement
}
pub fn block_size(&self) -> usize {
self.block_size
}
}
impl Verifier for MerkleVerifier {
fn verify_block(&self, _node: NodeId, offset: u64, data: &[u8]) -> Result<()> {
if !offset.is_multiple_of(self.block_size as u64) {
return Err(FoldError::VerifyFailed("unaligned verify offset"));
}
let idx = (offset / self.block_size as u64) as usize;
let want = self
.leaves
.get(idx)
.ok_or(FoldError::VerifyFailed("block past Merkle tree"))?;
if hash_block(&self.salt_prefix, data, self.block_size) == *want {
Ok(())
} else {
Err(FoldError::VerifyFailed("block hash mismatch"))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn hexs(d: &Sha256Digest) -> alloc::string::String {
use core::fmt::Write;
let mut s = alloc::string::String::new();
for b in d {
write!(s, "{b:02x}").unwrap();
}
s
}
fn ramp(n: usize) -> Vec<u8> {
(0..n).map(|i| (i % 256) as u8).collect()
}
#[test]
fn matches_fsverity_oracle() {
let cases: [(usize, &str); 5] = [
(
0,
"3d248ca542a24fc62d1c43b916eae5016878e2533c88238480b26128a1f1af95",
),
(
100,
"9f37bd4e8c0d50d81d76ec71e6cccc6696f4a196e730c8b2019deaa103a6350a",
),
(
4096,
"15a0095100272ab90a2209e97f8a2c54dff6f84d2b29524f95d92fe23b6ef25b",
),
(
4097,
"eaf219cbd8f40c7424e41b1034906a8d70b7a9ae42f0eca54393b965866f5932",
),
(
528384,
"c9263d6ca4271250c808937ec888e17b08546dc97634f6e98f30b3436ac2e3da",
),
];
for (n, want) in cases {
let got = fsverity_digest_sha256(&ramp(n), DEFAULT_BLOCK_LOG, b"");
assert_eq!(hexs(&got), want, "size {n}");
}
}
#[test]
fn verifier_accepts_good_blocks_and_rejects_tampered() {
let data = ramp(4097); let v = MerkleVerifier::over(&data, DEFAULT_BLOCK_LOG, b"");
assert_eq!(
v.measurement(),
fsverity_digest_sha256(&data, DEFAULT_BLOCK_LOG, b"")
);
assert!(v.verify_block(7, 0, &data[..4096]).is_ok());
assert!(v.verify_block(7, 4096, &data[4096..]).is_ok());
let mut bad = data[..4096].to_vec();
bad[10] ^= 0x01;
assert!(matches!(
v.verify_block(7, 0, &bad),
Err(FoldError::VerifyFailed(_))
));
assert!(v.verify_block(7, 1, &data[..10]).is_err());
assert!(v.verify_block(7, 8192, &[0u8; 1]).is_err());
}
#[test]
fn salt_changes_the_measurement() {
let data = ramp(5000);
let plain = fsverity_digest_sha256(&data, DEFAULT_BLOCK_LOG, b"");
let salted = fsverity_digest_sha256(&data, DEFAULT_BLOCK_LOG, b"pepper");
assert_ne!(plain, salted);
}
}