1use std::cell::{Cell, RefCell};
13use std::collections::HashMap;
14use std::rc::Rc;
15
16use crate::error::{Error, Result};
17use crate::layout::{Layout, Tile, HASH_SIZE, TILE_HEIGHT, TILE_WIDTH};
18use crate::store::TileStore;
19
20pub type Hash = [u8; HASH_SIZE];
22
23type TileCache = RefCell<HashMap<(u8, u64), Rc<Vec<Hash>>>>;
25
26#[must_use]
28pub fn encode_tile(hashes: &[Hash]) -> Vec<u8> {
29 let mut out = Vec::with_capacity(hashes.len() * HASH_SIZE);
30 for hash in hashes {
31 out.extend_from_slice(hash);
32 }
33 out
34}
35
36pub fn decode_tile(path: &str, bytes: &[u8]) -> Result<Vec<Hash>> {
38 if bytes.is_empty() || bytes.len() % HASH_SIZE != 0 || bytes.len() > 256 * HASH_SIZE {
39 return Err(Error::Corrupt {
40 path: path.to_string(),
41 reason: format!("{} bytes is not 1 to 256 hashes", bytes.len()),
42 });
43 }
44 Ok(bytes
45 .chunks_exact(HASH_SIZE)
46 .map(|c| {
47 let mut hash = [0u8; HASH_SIZE];
48 hash.copy_from_slice(c);
49 hash
50 })
51 .collect())
52}
53
54#[must_use]
58pub fn tile_root(hashes: &[Hash]) -> Hash {
59 rfc9162::root(hashes)
60}
61
62fn fold_right(subtrees: &[Hash]) -> Hash {
65 let mut acc = match subtrees.last() {
66 None => return rfc9162::root(&[]),
67 Some(last) => *last,
68 };
69 for subtree in subtrees[..subtrees.len() - 1].iter().rev() {
70 acc = rfc9162::node_hash(subtree, &acc);
71 }
72 acc
73}
74
75fn subtrees_of(hashes: &[Hash]) -> Vec<Hash> {
79 let mut out = Vec::new();
80 let mut at = 0usize;
81 for bit in (0..TILE_HEIGHT).rev() {
82 let chunk = 1usize << bit;
83 if hashes.len() & chunk != 0 {
84 out.push(rfc9162::root(&hashes[at..at + chunk]));
85 at += chunk;
86 }
87 }
88 out
89}
90
91#[derive(Debug, Clone, PartialEq, Eq)]
95pub struct Frontier {
96 size: u64,
97 levels: Vec<Vec<Hash>>,
98}
99
100impl Default for Frontier {
101 fn default() -> Self {
102 Self::empty()
103 }
104}
105
106impl Frontier {
107 #[must_use]
109 pub fn empty() -> Self {
110 Self {
111 size: 0,
112 levels: Vec::new(),
113 }
114 }
115
116 pub fn load(store: &impl TileStore, size: u64) -> Result<Self> {
120 let layout = Layout::new(size);
121 let mut levels = Vec::new();
122 for level in 0..layout.levels() {
123 match layout.partial_at(level) {
124 Some(tile) => levels.push(read_tile(store, &tile)?),
125 None => levels.push(Vec::new()),
126 }
127 }
128 Ok(Self { size, levels })
129 }
130
131 #[must_use]
133 pub fn size(&self) -> u64 {
134 self.size
135 }
136
137 #[must_use]
139 pub fn root(&self) -> Hash {
140 let mut subtrees = Vec::new();
141 for level in self.levels.iter().rev() {
142 subtrees.extend(subtrees_of(level));
143 }
144 fold_right(&subtrees)
145 }
146
147 pub fn push(&mut self, leaf: Hash, completed: &mut Vec<(Tile, Vec<Hash>)>) {
150 self.size += 1;
151 if self.levels.is_empty() {
152 self.levels.push(Vec::with_capacity(TILE_WIDTH as usize));
153 }
154 self.levels[0].push(leaf);
155 let mut level = 0usize;
156 while self.levels[level].len() as u64 == TILE_WIDTH {
157 let hashes = std::mem::take(&mut self.levels[level]);
158 let top = tile_root(&hashes);
159 let shift = (level as u32 + 1) * TILE_HEIGHT;
160 let index = (self.size >> shift) - 1;
161 completed.push((
162 Tile {
163 level: level as u8,
164 index,
165 width: TILE_WIDTH as u16,
166 },
167 hashes,
168 ));
169 if self.levels.len() == level + 1 {
170 self.levels.push(Vec::with_capacity(TILE_WIDTH as usize));
171 }
172 self.levels[level + 1].push(top);
173 level += 1;
174 }
175 }
176
177 #[must_use]
179 pub fn partial_tiles(&self) -> Vec<(Tile, &[Hash])> {
180 let layout = Layout::new(self.size);
181 self.levels
182 .iter()
183 .enumerate()
184 .filter(|(_, hashes)| !hashes.is_empty())
185 .map(|(level, hashes)| {
186 let tile = layout
187 .partial_at(level as u8)
188 .expect("a non-empty level has a partial tile");
189 (tile, hashes.as_slice())
190 })
191 .collect()
192 }
193}
194
195fn read_tile(store: &impl TileStore, tile: &Tile) -> Result<Vec<Hash>> {
199 let path = tile.path();
200 let hashes = match store.get(&path)? {
201 Some(bytes) => decode_tile(&path, &bytes)?,
202 None if !tile.is_full() => {
203 let full = Tile {
204 level: tile.level,
205 index: tile.index,
206 width: TILE_WIDTH as u16,
207 }
208 .path();
209 let mut covering = vec![full.clone()];
210 covering.extend(store.list(&format!("{full}.p"))?);
211 let mut found = None;
212 for candidate in covering {
213 if let Some(bytes) = store.get(&candidate)? {
214 let mut hashes = decode_tile(&candidate, &bytes)?;
215 if hashes.len() >= usize::from(tile.width) {
216 hashes.truncate(usize::from(tile.width));
217 found = Some(hashes);
218 break;
219 }
220 }
221 }
222 found.ok_or_else(|| Error::Corrupt {
223 path: path.clone(),
224 reason: "missing, and no wider tile covers it".to_string(),
225 })?
226 }
227 None => {
228 return Err(Error::Corrupt {
229 path,
230 reason: "missing".to_string(),
231 })
232 }
233 };
234 if hashes.len() != usize::from(tile.width) {
235 return Err(Error::Corrupt {
236 path,
237 reason: format!("{} hashes, width {}", hashes.len(), tile.width),
238 });
239 }
240 Ok(hashes)
241}
242
243pub struct TileHashReader<S: TileStore> {
247 store: S,
248 size: u64,
249 root: Hash,
250 cache: TileCache,
251 frontier_checked: Cell<bool>,
252}
253
254impl<S: TileStore> TileHashReader<S> {
255 pub fn new(store: S, size: u64, root: Hash) -> Self {
257 Self {
258 store,
259 size,
260 root,
261 cache: RefCell::new(HashMap::new()),
262 frontier_checked: Cell::new(false),
263 }
264 }
265
266 pub fn store(&self) -> &S {
268 &self.store
269 }
270
271 #[must_use]
273 pub fn size(&self) -> u64 {
274 self.size
275 }
276
277 #[must_use]
279 pub fn trusted_root(&self) -> Hash {
280 self.root
281 }
282
283 pub fn root(&self) -> Result<Hash> {
286 self.check_frontier()?;
287 Ok(self.root)
288 }
289
290 fn check_frontier(&self) -> Result<()> {
291 if self.frontier_checked.get() {
292 return Ok(());
293 }
294 let frontier = Frontier::load(&self.store, self.size)?;
295 let root = frontier.root();
296 if root != self.root {
297 return Err(Error::Corrupt {
298 path: "tile".to_string(),
299 reason: format!(
300 "the partial tiles fold to {} at size {}, the trusted root is {}",
301 rfc9162::hex(&root),
302 self.size,
303 rfc9162::hex(&self.root)
304 ),
305 });
306 }
307 let mut cache = self.cache.borrow_mut();
308 for (tile, hashes) in frontier.partial_tiles() {
309 cache.insert((tile.level, tile.index), Rc::new(hashes.to_vec()));
310 }
311 self.frontier_checked.set(true);
312 Ok(())
313 }
314
315 pub fn tile(&self, level: u8, index: u64) -> Result<Rc<Vec<Hash>>> {
318 if let Some(hashes) = self.cache.borrow().get(&(level, index)) {
319 return Ok(Rc::clone(hashes));
320 }
321 let layout = Layout::new(self.size);
322 let tile = layout.tile(level, index).ok_or_else(|| {
323 Error::OutOfRange(format!("tile {level}/{index} at size {}", self.size))
324 })?;
325 if !tile.is_full() {
326 self.check_frontier()?;
327 return Ok(Rc::clone(
328 self.cache
329 .borrow()
330 .get(&(level, index))
331 .expect("a checked frontier caches every partial tile"),
332 ));
333 }
334 let hashes = read_tile(&self.store, &tile)?;
335 let parent = self.tile(level + 1, index / TILE_WIDTH)?;
336 let expected = parent[(index % TILE_WIDTH) as usize];
337 let top = tile_root(&hashes);
338 if top != expected {
339 return Err(Error::Corrupt {
340 path: tile.path(),
341 reason: format!(
342 "folds to {}, the level above holds {}",
343 rfc9162::hex(&top),
344 rfc9162::hex(&expected)
345 ),
346 });
347 }
348 let hashes = Rc::new(hashes);
349 self.cache
350 .borrow_mut()
351 .insert((level, index), Rc::clone(&hashes));
352 Ok(hashes)
353 }
354
355 pub fn hash(&self, level: u8, index: u64) -> Result<Hash> {
358 let tile = self.tile(level, index / TILE_WIDTH)?;
359 let offset = (index % TILE_WIDTH) as usize;
360 tile.get(offset).copied().ok_or_else(|| {
361 Error::OutOfRange(format!(
362 "hash {index} at level {level} at size {}",
363 self.size
364 ))
365 })
366 }
367
368 pub fn perfect(&self, start: u64, height: u32) -> Result<Hash> {
371 let span = 1u64 << height;
372 if start % span != 0 || start.saturating_add(span) > self.size {
373 return Err(Error::OutOfRange(format!(
374 "perfect subtree at {start} of height {height} at size {}",
375 self.size
376 )));
377 }
378 let level = (height / TILE_HEIGHT) as u8;
379 let within = height % TILE_HEIGHT;
380 let first = start >> (u32::from(level) * TILE_HEIGHT);
381 let count = 1usize << within;
382 let tile = self.tile(level, first / TILE_WIDTH)?;
383 let offset = (first % TILE_WIDTH) as usize;
384 Ok(rfc9162::root(&tile[offset..offset + count]))
385 }
386
387 pub fn range_root(&self, lo: u64, hi: u64) -> Result<Hash> {
390 if lo > hi || hi > self.size {
391 return Err(Error::OutOfRange(format!(
392 "leaves {lo}..{hi} at size {}",
393 self.size
394 )));
395 }
396 let width = hi - lo;
397 if width == 0 {
398 return Ok(rfc9162::root(&[]));
399 }
400 if width.is_power_of_two() && lo % width == 0 {
401 return self.perfect(lo, width.trailing_zeros());
402 }
403 let split = split_point(width);
404 let left = self.range_root(lo, lo + split)?;
405 let right = self.range_root(lo + split, hi)?;
406 Ok(rfc9162::node_hash(&left, &right))
407 }
408 pub fn inclusion_proof(&self, index: u64) -> Result<Option<Vec<Hash>>> {
412 if index >= self.size {
413 return Ok(None);
414 }
415 let mut path = Vec::new();
416 self.audit_path(0, self.size, index, &mut path)?;
417 Ok(Some(path))
418 }
419
420 fn audit_path(&self, lo: u64, hi: u64, index: u64, path: &mut Vec<Hash>) -> Result<()> {
421 if hi - lo == 1 {
422 return Ok(());
423 }
424 let split = lo + split_point(hi - lo);
425 if index < split {
426 self.audit_path(lo, split, index, path)?;
427 path.push(self.range_root(split, hi)?);
428 } else {
429 self.audit_path(split, hi, index, path)?;
430 path.push(self.range_root(lo, split)?);
431 }
432 Ok(())
433 }
434
435 pub fn consistency_proof(&self, old: u64) -> Result<Option<Vec<Hash>>> {
440 if old == 0 || old > self.size {
441 return Ok(None);
442 }
443 let mut path = Vec::new();
444 self.subproof(0, self.size, old, true, &mut path)?;
445 Ok(Some(path))
446 }
447
448 fn subproof(
451 &self,
452 lo: u64,
453 hi: u64,
454 old: u64,
455 known: bool,
456 path: &mut Vec<Hash>,
457 ) -> Result<()> {
458 if old == hi - lo {
459 if !known {
460 path.push(self.range_root(lo, hi)?);
461 }
462 return Ok(());
463 }
464 let split = split_point(hi - lo);
465 if old <= split {
466 self.subproof(lo, lo + split, old, known, path)?;
467 path.push(self.range_root(lo + split, hi)?);
468 } else {
469 self.subproof(lo + split, hi, old - split, false, path)?;
470 path.push(self.range_root(lo, lo + split)?);
471 }
472 Ok(())
473 }
474}
475
476#[must_use]
479pub fn split_point(width: u64) -> u64 {
480 let mut split = 1u64;
481 while split * 2 < width {
482 split *= 2;
483 }
484 split
485}
486
487#[cfg(test)]
488mod tests {
489 use super::*;
490
491 #[test]
492 fn a_split_is_the_rfc_one() {
493 assert_eq!(split_point(2), 1);
494 assert_eq!(split_point(3), 2);
495 assert_eq!(split_point(4), 2);
496 assert_eq!(split_point(5), 4);
497 assert_eq!(split_point(257), 256);
498 }
499
500 #[test]
501 fn a_tile_round_trips_and_a_ragged_one_is_refused() {
502 let hashes: Vec<Hash> = (0..3u8).map(|i| [i; 32]).collect();
503 let bytes = encode_tile(&hashes);
504 assert_eq!(decode_tile("t", &bytes).unwrap(), hashes);
505 assert!(decode_tile("t", &bytes[..40]).is_err());
506 assert!(decode_tile("t", &[]).is_err());
507 }
508}