use crate::merkle::{build_tree_from_leaves, get_padded_proof_for_leaf, MerkleTree, F};
use crate::poseidon::calculate_root_commitment;
use crate::KontorPoRError;
use ff::Field;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::fs;
use std::path::Path;
pub trait FileDescriptor {
fn file_id(&self) -> &str;
fn root(&self) -> F;
fn depth(&self) -> usize;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileLedgerEntry {
pub root: F,
pub depth: usize,
pub rc: F,
}
impl<T: FileDescriptor> From<&T> for FileLedgerEntry {
fn from(entry: &T) -> Self {
let rc = calculate_root_commitment(entry.root(), F::from(entry.depth() as u64));
FileLedgerEntry {
root: entry.root(),
depth: entry.depth(),
rc,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct LedgerData {
version: u16,
files: BTreeMap<String, FileLedgerEntry>,
root: F,
#[serde(default)]
historical_roots: Vec<[u8; 32]>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileLedger {
pub files: BTreeMap<String, FileLedgerEntry>,
#[serde(skip)]
pub tree: MerkleTree,
#[serde(default)]
pub historical_roots: Vec<[u8; 32]>,
}
impl Default for FileLedger {
fn default() -> Self {
Self {
files: BTreeMap::new(),
tree: MerkleTree {
layers: vec![vec![]],
},
historical_roots: Vec::new(),
}
}
}
impl FileLedger {
pub fn new() -> Self {
Self::default()
}
pub fn root(&self) -> F {
self.tree.root()
}
pub fn record_current_root(&mut self) {
use ff::PrimeField;
let root = self.tree.root();
let repr: [u8; 32] = root.to_repr().into();
self.historical_roots.push(repr);
}
pub fn is_valid_root(&self, root: F) -> bool {
use ff::PrimeField;
if root == self.tree.root() {
return true;
}
let repr: [u8; 32] = root.to_repr().into();
self.historical_roots.iter().any(|r| r == &repr)
}
pub fn set_historical_roots(&mut self, roots: Vec<[u8; 32]>) {
self.historical_roots = roots;
}
pub fn add_file(&mut self, entry: &impl FileDescriptor) -> Result<(), KontorPoRError> {
self.files
.insert(entry.file_id().to_string(), FileLedgerEntry::from(entry));
self.rebuild_tree()?;
self.record_current_root();
Ok(())
}
pub fn add_files<'a, T: FileDescriptor + 'a>(
&mut self,
files: impl IntoIterator<Item = &'a T>,
) -> Result<(), KontorPoRError> {
for entry in files {
self.files
.insert(entry.file_id().to_string(), FileLedgerEntry::from(entry));
}
self.rebuild_tree()
}
fn rebuild_tree(&mut self) -> Result<(), KontorPoRError> {
let rc_values: Vec<F> = self.files.values().map(|entry| entry.rc).collect();
if rc_values.is_empty() {
self.tree = build_tree_from_leaves(&[F::ZERO])?;
return Ok(());
}
let padded_len = rc_values.len().next_power_of_two();
let mut padded_rcs = rc_values;
padded_rcs.resize(padded_len, F::ZERO);
self.tree = build_tree_from_leaves(&padded_rcs)?;
Ok(())
}
pub fn get_canonical_index_for_rc(&self, rc: F) -> Option<usize> {
self.files.values().position(|entry| entry.rc == rc)
}
pub fn save(&self, path: &Path) -> Result<(), KontorPoRError> {
let data = LedgerData {
version: crate::config::LEDGER_FORMAT_VERSION,
files: self.files.clone(),
root: self.tree.root(),
historical_roots: self.historical_roots.clone(),
};
let encoded = bincode::serialize(&data).map_err(|e| {
KontorPoRError::Serialization(format!("Failed to serialize ledger: {}", e))
})?;
if encoded.len() > crate::config::MAX_LEDGER_SIZE_BYTES {
return Err(KontorPoRError::InvalidInput(format!(
"Serialized ledger size {} bytes exceeds maximum {} bytes",
encoded.len(),
crate::config::MAX_LEDGER_SIZE_BYTES
)));
}
fs::write(path, encoded).map_err(|e| {
KontorPoRError::IO(format!(
"Failed to write ledger to {}: {}",
path.display(),
e
))
})
}
pub fn load(path: &Path) -> Result<Self, KontorPoRError> {
let encoded = fs::read(path).map_err(|e| {
KontorPoRError::IO(format!(
"Failed to read ledger from {}: {}",
path.display(),
e
))
})?;
if encoded.len() > crate::config::MAX_LEDGER_SIZE_BYTES {
return Err(KontorPoRError::InvalidInput(format!(
"Ledger file size {} bytes exceeds maximum {} bytes",
encoded.len(),
crate::config::MAX_LEDGER_SIZE_BYTES
)));
}
let data: LedgerData = bincode::deserialize(&encoded).map_err(|e| {
KontorPoRError::Serialization(format!("Failed to deserialize ledger: {}", e))
})?;
if data.version != crate::config::LEDGER_FORMAT_VERSION {
return Err(KontorPoRError::InvalidInput(format!(
"Ledger format version {} is not compatible with current version {}",
data.version,
crate::config::LEDGER_FORMAT_VERSION
)));
}
let mut ledger = FileLedger {
files: data.files,
tree: MerkleTree::default(),
historical_roots: data.historical_roots,
};
ledger.rebuild_tree()?;
if ledger.tree.root() != data.root {
return Err(KontorPoRError::LedgerValidation {
reason: "computed root does not match stored root".to_string(),
});
}
Ok(ledger)
}
pub fn depth(&self) -> usize {
self.tree.layers.len().saturating_sub(1)
}
pub fn lookup(&self, file_id: &str) -> Option<(usize, F)> {
if let Some(entry) = self.files.get(file_id) {
self.files
.keys()
.position(|k| k == file_id)
.map(|index| (index, entry.rc))
} else {
None
}
}
pub fn get_aggregation_proof(
&self,
file_id: &str,
) -> Option<crate::merkle::CircuitMerkleProof> {
let (index, _rc) = self.lookup(file_id)?;
let depth = self.depth();
get_padded_proof_for_leaf(&self.tree, index, depth).ok()
}
}