use crate::api::paths::{TILE_HEIGHT, TILE_WIDTH};
use crate::error::{Error, Result};
use crate::merkle::HashTile;
use crate::storage::TileStorage;
use crate::types::{PartialSize, TileId, TileIndex, TileLevel, TreeSize};
use sigstore_merkle::hash_children;
use sigstore_types::Sha256Hash;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct TileKey {
level: u64,
index: u64,
}
impl From<TileId> for TileKey {
fn from(id: TileId) -> Self {
Self {
level: id.level.value(),
index: id.index.value(),
}
}
}
#[derive(Debug)]
pub struct IntegrationResult {
pub new_size: TreeSize,
pub root_hash: Sha256Hash,
pub tiles: HashMap<TileId, HashTile>,
}
pub async fn integrate(
storage: &TileStorage,
from_size: TreeSize,
leaf_hashes: &[Sha256Hash],
) -> Result<IntegrationResult> {
if leaf_hashes.is_empty() {
let root_hash = if from_size.value() == 0 {
empty_root_hash()
} else {
return Err(Error::Internal(
"cannot get root of non-empty tree without new entries".into(),
));
};
return Ok(IntegrationResult {
new_size: from_size,
root_hash,
tiles: HashMap::new(),
});
}
let mut builder = TreeBuilder::new(storage, from_size).await?;
for leaf_hash in leaf_hashes {
builder.append(*leaf_hash)?;
}
builder.finalize().await
}
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,
])
}
struct TreeBuilder<'a> {
storage: &'a TileStorage,
from_size: u64,
size: u64,
range: Vec<Option<Sha256Hash>>,
tile_cache: HashMap<TileKey, TileMutator>,
}
impl<'a> TreeBuilder<'a> {
async fn new(storage: &'a TileStorage, from_size: TreeSize) -> Result<Self> {
let size = from_size.value();
let range = if size == 0 {
Vec::new()
} else {
load_compact_range(storage, size).await?
};
Ok(Self {
storage,
from_size: size,
size,
range,
tile_cache: HashMap::new(),
})
}
fn append(&mut self, leaf_hash: Sha256Hash) -> Result<()> {
let leaf_index = self.size;
self.size += 1;
self.set_node(0, leaf_index, leaf_hash)?;
let mut hash = leaf_hash;
let mut index = leaf_index;
let mut level = 0u64;
while index & 1 == 1 {
if let Some(left) = self.range.get(level as usize).and_then(|h| *h) {
hash = hash_children(&left, &hash);
self.range[level as usize] = None;
level += 1;
index >>= 1;
if should_store_node(level, index, self.size) {
self.set_node(level, index, hash)?;
}
} else {
break;
}
}
while self.range.len() <= level as usize {
self.range.push(None);
}
self.range[level as usize] = Some(hash);
Ok(())
}
fn set_node(&mut self, level: u64, index: u64, hash: Sha256Hash) -> Result<()> {
let (tile_level, tile_index, node_level, node_index) = node_to_tile_coords(level, index);
let key = TileKey {
level: tile_level,
index: tile_index,
};
let mutator = self.tile_cache.entry(key).or_insert_with(TileMutator::new);
mutator.set(node_level as usize, node_index as usize, hash);
Ok(())
}
async fn finalize(self) -> Result<IntegrationResult> {
let root_hash = self.compute_root()?;
let mut tiles = HashMap::new();
for (key, mutator) in self.tile_cache {
let level = TileLevel::new(key.level);
let index = TileIndex::new(key.index);
let prev_partial = partial_tile_size(key.level, key.index, self.from_size);
let mut tile = if prev_partial.value() > 0 {
self.storage
.read_tile(level, index, prev_partial)
.await?
.unwrap_or_else(HashTile::new)
} else {
self.storage
.read_tile(level, index, PartialSize::full())
.await?
.unwrap_or_else(HashTile::new)
};
mutator.apply_to(&mut tile);
let tile_id = TileId::new(level, index);
tiles.insert(tile_id, tile);
}
Ok(IntegrationResult {
new_size: TreeSize::new(self.size),
root_hash,
tiles,
})
}
fn compute_root(&self) -> Result<Sha256Hash> {
if self.size == 0 {
return Ok(empty_root_hash());
}
let mut hash: Option<Sha256Hash> = None;
for h in self.range.iter().flatten() {
hash = Some(match hash {
None => *h,
Some(right) => hash_children(h, &right),
});
}
hash.ok_or_else(|| Error::Internal("empty compact range".into()))
}
}
fn should_store_node(level: u64, index: u64, tree_size: u64) -> bool {
let subtree_size = 1u64 << level;
let subtree_end = (index + 1) * subtree_size;
subtree_end <= tree_size
}
fn node_to_tile_coords(level: u64, index: u64) -> (u64, u64, u64, u64) {
let tile_row_width = 1u64 << (TILE_HEIGHT - level % TILE_HEIGHT);
let tile_level = level / TILE_HEIGHT;
let tile_index = index / tile_row_width;
let node_level = level % TILE_HEIGHT;
let node_index = index % tile_row_width;
(tile_level, tile_index, node_level, node_index)
}
fn partial_tile_size(level: u64, index: u64, log_size: u64) -> PartialSize {
let size_at_level = log_size >> (level * TILE_HEIGHT);
let full_tiles = size_at_level / TILE_WIDTH;
if index < full_tiles {
PartialSize::full()
} else {
PartialSize::new((size_at_level % TILE_WIDTH) as u8)
}
}
async fn load_compact_range(storage: &TileStorage, size: u64) -> Result<Vec<Option<Sha256Hash>>> {
let range_nodes = compute_range_nodes(size);
let mut range = Vec::new();
for (level, index) in range_nodes {
let hash = compute_node_hash(storage, level, index, size).await?;
while range.len() <= level as usize {
range.push(None);
}
range[level as usize] = Some(hash);
}
Ok(range)
}
#[async_recursion::async_recursion]
async fn compute_node_hash(
storage: &TileStorage,
level: u64,
index: u64,
tree_size: u64,
) -> Result<Sha256Hash> {
if level == 0 {
let (tile_level, tile_index, _node_level, node_index) = node_to_tile_coords(level, index);
let partial = partial_tile_size(tile_level, tile_index, tree_size);
let tile = storage
.read_tile(
TileLevel::new(tile_level),
TileIndex::new(tile_index),
partial,
)
.await?
.ok_or_else(|| {
Error::Internal(format!(
"missing tile at level={}, index={}",
tile_level, tile_index
))
})?;
tile.nodes
.get(node_index as usize)
.copied()
.ok_or_else(|| Error::Internal(format!("missing leaf at index {}", node_index)))
} else {
let left_index = index * 2;
let right_index = index * 2 + 1;
let left = compute_node_hash(storage, level - 1, left_index, tree_size).await?;
let right = compute_node_hash(storage, level - 1, right_index, tree_size).await?;
Ok(hash_children(&left, &right))
}
}
fn compute_range_nodes(size: u64) -> Vec<(u64, u64)> {
if size == 0 {
return Vec::new();
}
let max_level = 63 - size.leading_zeros() as u64;
let mut nodes = Vec::new();
let mut cumulative_leaves = 0u64;
for level in (0..=max_level).rev() {
let subtree_size = 1u64 << level;
if size & subtree_size != 0 {
let index = cumulative_leaves >> level;
nodes.push((level, index));
cumulative_leaves += subtree_size;
}
}
nodes.reverse();
nodes
}
#[derive(Debug)]
struct TileMutator {
nodes: HashMap<(usize, usize), Sha256Hash>,
}
impl TileMutator {
fn new() -> Self {
Self {
nodes: HashMap::new(),
}
}
fn set(&mut self, level: usize, index: usize, hash: Sha256Hash) {
self.nodes.insert((level, index), hash);
}
fn apply_to(self, tile: &mut HashTile) {
fn flat_offset(level: usize, index: usize) -> usize {
let mut offset = 0;
let mut width = TILE_WIDTH as usize;
for _ in 0..level {
offset += width;
width /= 2;
}
offset + index
}
let max_offset = self
.nodes
.keys()
.map(|&(level, index)| flat_offset(level, index))
.max()
.unwrap_or(0);
while tile.nodes.len() <= max_offset {
tile.nodes.push(Sha256Hash::from_bytes([0u8; 32]));
}
for ((level, index), hash) in self.nodes {
let offset = flat_offset(level, index);
tile.nodes[offset] = hash;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_node_to_tile_coords() {
assert_eq!(node_to_tile_coords(0, 0), (0, 0, 0, 0));
assert_eq!(node_to_tile_coords(0, 255), (0, 0, 0, 255));
assert_eq!(node_to_tile_coords(0, 256), (0, 1, 0, 0));
assert_eq!(node_to_tile_coords(8, 0), (1, 0, 0, 0));
}
#[test]
fn test_node_to_tile_coords_tessera_vectors() {
let test_cases = [
(0, 0, 0, 0, 0, 0),
(0, 255, 0, 0, 0, 255),
(0, 256, 0, 1, 0, 0),
(1, 0, 0, 0, 1, 0),
(8, 0, 1, 0, 0, 0),
];
for (
tree_level,
tree_index,
want_tile_level,
want_tile_index,
want_node_level,
want_node_index,
) in test_cases
{
let (tl, ti, nl, ni) = node_to_tile_coords(tree_level, tree_index);
assert_eq!(
tl, want_tile_level,
"tile_level mismatch for ({}, {})",
tree_level, tree_index
);
assert_eq!(
ti, want_tile_index,
"tile_index mismatch for ({}, {})",
tree_level, tree_index
);
assert_eq!(
nl, want_node_level,
"node_level mismatch for ({}, {})",
tree_level, tree_index
);
assert_eq!(
ni, want_node_index,
"node_index mismatch for ({}, {})",
tree_level, tree_index
);
}
}
#[test]
fn test_compute_range_nodes() {
assert_eq!(compute_range_nodes(1), vec![(0, 0)]);
assert_eq!(compute_range_nodes(2), vec![(1, 0)]);
assert_eq!(compute_range_nodes(3), vec![(0, 2), (1, 0)]);
assert_eq!(compute_range_nodes(4), vec![(2, 0)]);
assert_eq!(compute_range_nodes(5), vec![(0, 4), (2, 0)]);
assert_eq!(
compute_range_nodes(1560),
vec![(3, 194), (4, 96), (9, 2), (10, 0)]
);
}
#[test]
fn test_empty_root() {
let root = empty_root_hash();
assert_eq!(
root.to_hex(),
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
);
}
fn compute_root_from_leaves(leaf_hashes: &[Sha256Hash]) -> Sha256Hash {
if leaf_hashes.is_empty() {
return empty_root_hash();
}
let mut range: Vec<Option<Sha256Hash>> = Vec::new();
for (leaf_index, &leaf_hash) in leaf_hashes.iter().enumerate() {
let mut hash = leaf_hash;
let mut index = leaf_index as u64;
let mut level = 0usize;
while index & 1 == 1 {
if let Some(Some(left)) = range.get(level) {
hash = hash_children(left, &hash);
range[level] = None;
level += 1;
index >>= 1;
} else {
break;
}
}
while range.len() <= level {
range.push(None);
}
range[level] = Some(hash);
}
let mut root: Option<Sha256Hash> = None;
for h in range.iter().flatten() {
root = Some(match root {
None => *h,
Some(right) => hash_children(h, &right),
});
}
root.unwrap_or_else(empty_root_hash)
}
fn rfc6962_hash_leaf(data: &[u8]) -> Sha256Hash {
sigstore_merkle::hash_leaf(data)
}
#[test]
fn test_root_hash_computation() {
let leaf0 = rfc6962_hash_leaf(&[0u8]);
let root1 = compute_root_from_leaves(&[leaf0]);
assert_eq!(root1, leaf0, "single leaf root mismatch");
let leaf1 = rfc6962_hash_leaf(&[1u8]);
let root2 = compute_root_from_leaves(&[leaf0, leaf1]);
let expected_root2 = hash_children(&leaf0, &leaf1);
assert_eq!(root2, expected_root2, "two leaf root mismatch");
let leaf2 = rfc6962_hash_leaf(&[2u8]);
let root3 = compute_root_from_leaves(&[leaf0, leaf1, leaf2]);
let expected_root3 = hash_children(&expected_root2, &leaf2);
assert_eq!(root3, expected_root3, "three leaf root mismatch");
let leaf3 = rfc6962_hash_leaf(&[3u8]);
let root4 = compute_root_from_leaves(&[leaf0, leaf1, leaf2, leaf3]);
let expected_root4 = hash_children(&expected_root2, &hash_children(&leaf2, &leaf3));
assert_eq!(root4, expected_root4, "four leaf root mismatch");
}
#[test]
fn test_incremental_root_consistency() {
let mut leaves = Vec::new();
for i in 0..=255u8 {
let leaf_hash = rfc6962_hash_leaf(&[i]);
leaves.push(leaf_hash);
let incremental_root = compute_root_from_leaves(&leaves);
let batch_root = compute_root_from_leaves(&leaves);
assert_eq!(
incremental_root,
batch_root,
"root mismatch at size {}: incremental {:?} vs batch {:?}",
leaves.len(),
incremental_root.to_hex(),
batch_root.to_hex()
);
}
}
#[test]
fn test_large_tree_root_computation() {
let chunk_size = 200;
let num_chunks = 10;
let mut leaves = Vec::new();
for chunk in 0..num_chunks {
for i in 0..chunk_size {
let seq = chunk * chunk_size + i;
let leaf_hash = rfc6962_hash_leaf(&[seq as u8]);
leaves.push(leaf_hash);
}
let root = compute_root_from_leaves(&leaves);
assert_eq!(
root.as_bytes().len(),
32,
"root should be 32 bytes at chunk {}",
chunk
);
assert_ne!(
root.as_bytes(),
&[0u8; 32],
"root should not be all zeros at chunk {}",
chunk
);
}
}
#[test]
fn test_known_root_hashes() {
assert_eq!(
empty_root_hash().to_hex(),
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
);
let leaf = rfc6962_hash_leaf(&[0x00]);
let root1 = compute_root_from_leaves(&[leaf]);
assert_eq!(root1, leaf);
let sha256_of_zero = "5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9";
assert_ne!(
leaf.to_hex(),
sha256_of_zero,
"RFC 6962 leaf hash should include 0x00 prefix, not be raw SHA256"
);
}
#[test]
fn test_go_tessera_leaf_hash_vectors() {
let test_vectors = [
(
0x00u8,
"96a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7",
),
(
0x01u8,
"b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2",
),
(
0x02u8,
"fcf0a6c700dd13e274b6fba8deea8dd9b26e4eedde3495717cac8408c9c5177f",
),
(
0x03u8,
"583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d",
),
(
0x04u8,
"4f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4",
),
];
for (input, expected_hex) in test_vectors {
let hash = rfc6962_hash_leaf(&[input]);
assert_eq!(
hash.to_hex(),
expected_hex,
"leaf hash mismatch for input 0x{:02x}",
input
);
}
}
#[test]
fn test_go_tessera_root_hash_vectors() {
let test_vectors = [
(
1,
"96a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7",
),
(
2,
"a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a",
),
(
3,
"3b6cccd7e3e023ff393006f030315ee7ad9eb111b022b41fba7e5b7a3973f688",
),
(
4,
"9bcd51240af4005168f033121ba85be5a6ed4f0e6a5fac262066729b8fbfdecb",
),
(
5,
"b855b42d6c30f5b087e05266783fbd6e394f7b926013ccaa67700a8b0c5a596f",
),
(
6,
"bb36e7d3d4cee5720cbd323d02fab15962e2ba1dadf5f8fc6eeef4fd6ad056a8",
),
(
7,
"3560191803028444b232018ac047fdb561c09c23a7a6876c85e08b5e4d48e9f3",
),
(
8,
"ef7f49b620f6c7ea9b963a214da34b5021c6ded8ed57734380a311ab726aa907",
),
(
9,
"162a21c2230e0284ea38cb8739ee4bb75947a1acd5d529c638ec068969fb3c4a",
),
(
10,
"487540cba07f8eee7688295955ee3b4c04003e8ca2de94426237cd44491108a7",
),
];
for (size, expected_hex) in test_vectors {
let leaves: Vec<Sha256Hash> =
(0..size).map(|i| rfc6962_hash_leaf(&[i as u8])).collect();
let root = compute_root_from_leaves(&leaves);
assert_eq!(
root.to_hex(),
expected_hex,
"root hash mismatch for size {}",
size
);
}
}
#[test]
fn test_go_tessera_large_tree_vectors() {
let test_vectors = [
(
16,
"93f2bd0cd60b2e597cf53fb12ae63ed157e0c10efbfe097d23caf8a5f59c6e27",
),
(
100,
"9eccc8f11ab3ecd41cbf41f5045df576dcedc60f44d4a3de5f40b7d4f7e9d7e3",
),
(
200,
"e2f07a9cf65026ba282cb286d673b564787a64785575ad1aaa74468da02d6161",
),
(
256,
"0387b1bfaad194ad5b534d6a57c9710a83484cb544258df5f4df94bc41820b5f",
),
];
for (size, expected_hex) in test_vectors {
let leaves: Vec<Sha256Hash> = (0..size)
.map(|i| rfc6962_hash_leaf(&[(i % 256) as u8]))
.collect();
let root = compute_root_from_leaves(&leaves);
assert_eq!(
root.to_hex(),
expected_hex,
"root hash mismatch for size {}",
size
);
}
}
}