use crate::merkle::{build_tree_from_leaves, get_padded_proof_for_leaf, MerkleTree, F};
use crate::KontorPoRError;
use ff::Field;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::fs;
use std::path::Path;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileEntry {
pub root: F,
pub depth: usize,
pub rc: F,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct LedgerData {
version: u16,
files: BTreeMap<String, FileEntry>,
root: F,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileLedger {
pub files: BTreeMap<String, FileEntry>,
#[serde(skip)]
pub tree: MerkleTree,
}
impl Default for FileLedger {
fn default() -> Self {
Self {
files: BTreeMap::new(),
tree: MerkleTree {
layers: vec![vec![]],
},
}
}
}
impl FileLedger {
pub fn new() -> Self {
Self::default()
}
pub fn add_file(
&mut self,
file_id: String,
file_root: F,
file_depth: usize,
) -> Result<(), KontorPoRError> {
use crate::poseidon::calculate_root_commitment;
let rc = calculate_root_commitment(file_root, F::from(file_depth as u64));
let entry = FileEntry {
root: file_root,
depth: file_depth,
rc,
};
self.files.insert(file_id, 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(),
};
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(),
};
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()
}
}