use super::proof::{verify_consistency, ConsistencyProof};
use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
use sigstore_merkle::hash_children;
use sigstore_types::Sha256Hash;
fn litewitness_leaf_hash(index: u8) -> Sha256Hash {
let mut hash = [0u8; 32];
hash[0] = 0x2a; hash[1] = index;
Sha256Hash::from_bytes(hash)
}
fn compute_litewitness_root(size: usize) -> Sha256Hash {
if size == 0 {
return empty_root_hash();
}
let leaves: Vec<Sha256Hash> = (0..size).map(|i| litewitness_leaf_hash(i as u8)).collect();
compute_root(&leaves)
}
fn compute_root(leaves: &[Sha256Hash]) -> Sha256Hash {
if leaves.is_empty() {
return empty_root_hash();
}
if leaves.len() == 1 {
return leaves[0];
}
let mut range: Vec<Option<Sha256Hash>> = Vec::new();
for (i, &leaf) in leaves.iter().enumerate() {
let mut hash = leaf;
let mut idx = i as u64;
let mut level = 0usize;
while idx & 1 == 1 {
if let Some(Some(left)) = range.get(level) {
hash = hash_children(left, &hash);
range[level] = None;
level += 1;
idx >>= 1;
} else {
break;
}
}
while range.len() <= level {
range.push(None);
}
range[level] = Some(hash);
}
let mut root: Option<Sha256Hash> = None;
for hash in range.iter().flatten() {
root = Some(match root {
None => *hash,
Some(r) => hash_children(hash, &r),
});
}
root.unwrap_or_else(empty_root_hash)
}
fn empty_root_hash() -> Sha256Hash {
Sha256Hash::from_bytes([
0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9,
0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52,
0xb8, 0x55,
])
}
fn decode_hash(base64_str: &str) -> Sha256Hash {
let bytes = BASE64.decode(base64_str).expect("invalid base64");
let mut arr = [0u8; 32];
arr.copy_from_slice(&bytes);
Sha256Hash::from_bytes(arr)
}
#[test]
fn test_litewitness_tree_roots() {
let root1 = compute_litewitness_root(1);
let expected1 = decode_hash("KgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=");
assert_eq!(root1, expected1, "Root at size 1 mismatch");
let root3 = compute_litewitness_root(3);
let expected3 = decode_hash("RcCI1Nk56ZcSmIEfIn0SleqtV7uvrlXNccFx595Iwl0=");
assert_eq!(root3, expected3, "Root at size 3 mismatch");
let root5 = compute_litewitness_root(5);
let expected5 = decode_hash("QrtXrQZCCvpIgsSmOsah7HdICzMLLyDfxToMql9WTjY=");
assert_eq!(root5, expected5, "Root at size 5 mismatch");
}
#[test]
fn test_litewitness_consistency_1_to_3() {
let old_size = 1u64;
let new_size = 3u64;
let old_root = compute_litewitness_root(1);
let new_root = compute_litewitness_root(3);
let proof_hashes = vec![
decode_hash("KgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="), decode_hash("KgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="), ];
let proof = ConsistencyProof::new(proof_hashes);
let result = verify_consistency(old_size, new_size, &old_root, &new_root, &proof);
assert!(
result.is_ok(),
"Consistency proof 1->3 failed: {:?}",
result
);
}
#[test]
fn test_litewitness_consistency_1_to_5() {
let old_size = 1u64;
let new_size = 5u64;
let old_root = compute_litewitness_root(1);
let new_root = compute_litewitness_root(5);
let proof_hashes = vec![
decode_hash("KgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="),
decode_hash("+fUDV+k970B4I3uKrqJM4aP1lloPZP8mvr2Z4wRw2LI="),
decode_hash("KgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="),
];
let proof = ConsistencyProof::new(proof_hashes);
let result = verify_consistency(old_size, new_size, &old_root, &new_root, &proof);
assert!(
result.is_ok(),
"Consistency proof 1->5 failed: {:?}",
result
);
}
#[test]
fn test_litewitness_consistency_3_to_5() {
let old_size = 3u64;
let new_size = 5u64;
let old_root = compute_litewitness_root(3);
let new_root = compute_litewitness_root(5);
let proof_hashes = vec![
decode_hash("KgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="),
decode_hash("KgMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="),
decode_hash("wgiIFdZfYNv6WU1OllBKsWnLYIS/DBMqt8Uh/S4OukE="),
decode_hash("KgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="),
];
let proof = ConsistencyProof::new(proof_hashes);
let result = verify_consistency(old_size, new_size, &old_root, &new_root, &proof);
assert!(
result.is_ok(),
"Consistency proof 3->5 failed: {:?}",
result
);
}
#[test]
fn test_litewitness_consistency_0_to_1() {
let old_size = 0u64;
let new_size = 1u64;
let old_root = empty_root_hash();
let new_root = compute_litewitness_root(1);
let proof = ConsistencyProof::new(vec![]);
let result = verify_consistency(old_size, new_size, &old_root, &new_root, &proof);
assert!(
result.is_ok(),
"Consistency proof 0->1 failed: {:?}",
result
);
}
#[test]
fn test_litewitness_bad_proof_hash() {
let old_size = 1u64;
let new_size = 3u64;
let old_root = compute_litewitness_root(1);
let new_root = compute_litewitness_root(3);
let proof_hashes = vec![
decode_hash("KgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="),
decode_hash("KgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABA="), ];
let proof = ConsistencyProof::new(proof_hashes);
let result = verify_consistency(old_size, new_size, &old_root, &new_root, &proof);
assert!(result.is_err(), "Corrupted proof should fail verification");
}
#[test]
fn test_litewitness_missing_proof() {
let old_size = 1u64;
let new_size = 3u64;
let old_root = compute_litewitness_root(1);
let new_root = compute_litewitness_root(3);
let proof = ConsistencyProof::new(vec![]);
let result = verify_consistency(old_size, new_size, &old_root, &new_root, &proof);
assert!(result.is_err(), "Missing proof should fail verification");
}
#[test]
fn test_litewitness_same_size() {
let size = 3u64;
let root = compute_litewitness_root(3);
let proof = ConsistencyProof::new(vec![]);
let result = verify_consistency(size, size, &root, &root, &proof);
assert!(result.is_ok(), "Same size proof failed: {:?}", result);
}
#[test]
fn test_litewitness_same_size_different_roots() {
let size = 3u64;
let root1 = compute_litewitness_root(3);
let root2 = compute_litewitness_root(5);
let proof = ConsistencyProof::new(vec![]);
let result = verify_consistency(size, size, &root1, &root2, &proof);
assert!(
result.is_err(),
"Same size with different roots should fail"
);
}