use ff::Field;
use kontor_crypto::{
api::{self, Challenge, FieldElement, PorSystem},
config,
};
use rand::Rng;
use std::collections::BTreeMap;
mod common;
use common::{
create_single_file_ledger,
fixtures::{create_ledger_from_metadatas, create_test_files},
};
#[test]
fn test_awkward_file_count_padding() {
println!("Testing aggregation with 5 files (awkward padding to 8)");
let num_files = 5;
let (files, metadatas) = create_test_files(num_files, 40, 1000);
let metadata_refs: Vec<&_> = metadatas.iter().collect();
let ledger = create_ledger_from_metadatas(&metadata_refs);
let seed = FieldElement::from(12345u64);
let challenges: Vec<Challenge> = metadatas
.iter()
.map(|m| Challenge::new_test(m.clone(), 1000, 1, seed))
.collect();
let max_depth = challenges
.iter()
.map(|c| api::tree_depth_from_metadata(&c.file_metadata))
.max()
.unwrap();
let (files_per_step, file_tree_depth) = config::derive_shape(num_files, max_depth);
assert_eq!(
files_per_step, 8,
"5 files should pad to 8 (next power of 2)"
);
let file_refs: BTreeMap<String, &_> = files.iter().map(|(k, v)| (k.clone(), v)).collect();
let system = PorSystem::new(&ledger);
let files_vec: Vec<&_> = file_refs.values().copied().collect();
let proof = system
.prove(files_vec, &challenges)
.expect("Should generate proof with 5 files");
let is_valid = system
.verify(&proof, &challenges)
.expect("Verification should complete");
assert!(is_valid, "Proof with 5 files (padded to 8) should verify");
println!("✓ Successfully proved and verified with 5 files padded to 8");
let dummy_ledger_indices = vec![0, 1, 2, 3, 4, 0, 0, 0]; let (witness, _) = api::generate_circuit_witness(
&challenges.iter().collect::<Vec<_>>(),
Some(&file_refs),
&ledger,
file_tree_depth,
file_tree_depth,
FieldElement::ZERO,
ledger.tree.layers.len() - 1,
0,
&dummy_ledger_indices,
)
.expect("Failed to generate witness");
assert_eq!(
witness.witnesses().len(),
8,
"Should have 8 witnesses (5 real + 3 padding)"
);
let real_count = witness
.witnesses()
.iter()
.filter(|w| w.actual_depth > 0)
.count();
assert_eq!(real_count, 5, "Should have exactly 5 real witnesses");
let padding_count = witness
.witnesses()
.iter()
.filter(|w| w.actual_depth == 0)
.count();
assert_eq!(padding_count, 3, "Should have exactly 3 padding witnesses");
println!("✓ Witness structure correct: 5 real + 3 padding = 8 total");
}
#[test]
fn test_highly_heterogeneous_depths() {
println!("Testing aggregation with highly heterogeneous depths (0, 1, 3, 5)");
let target_depths = vec![0, 1, 3, 5];
let _target_depths = target_depths; let file_sizes = [
100, 30_000, 500_000, 2_000_000, ];
let mut files = BTreeMap::new();
let mut metadatas = vec![];
let mut ledger = kontor_crypto::ledger::FileLedger::new();
let mut actual_depths = vec![];
for (i, size) in file_sizes.iter().enumerate() {
let data = vec![(i * 10) as u8; *size];
let nonce: [u8; 16] = rand::thread_rng().gen();
let (prepared, metadata) =
api::prepare_file(&data, &format!("test_file_{}.dat", i), &nonce)
.expect("Failed to prepare file");
let depth = api::tree_depth_from_metadata(&metadata);
actual_depths.push(depth);
println!(
" File {}: size={}, total_symbols={}, padded_len={}, depth={}",
i,
size,
metadata.total_symbols(),
metadata.padded_len,
depth
);
files.insert(metadata.file_id.clone(), prepared);
ledger.add_file(&metadata).expect("Failed to add to ledger");
metadatas.push(metadata);
}
let min_depth = *actual_depths.iter().min().unwrap();
let max_depth = *actual_depths.iter().max().unwrap();
let depth_spread = max_depth - min_depth;
println!(
" Achieved depths: {:?}, spread: {}",
actual_depths, depth_spread
);
assert!(
depth_spread >= 3,
"Should have significant depth variation (spread >= 3)"
);
let seed = FieldElement::from(99999u64);
let challenges: Vec<Challenge> = metadatas
.iter()
.map(|m| Challenge::new_test(m.clone(), 1000, 1, seed))
.collect();
let file_refs: BTreeMap<String, &_> = files.iter().map(|(k, v)| (k.clone(), v)).collect();
let system = PorSystem::new(&ledger);
let files_vec: Vec<&_> = file_refs.values().copied().collect();
let proof = system
.prove(files_vec, &challenges)
.expect("Should generate proof with heterogeneous depths");
let is_valid = system
.verify(&proof, &challenges)
.expect("Verification should complete");
assert!(
is_valid,
"Proof with highly heterogeneous depths should verify"
);
println!(
"✓ Successfully proved and verified with depth spread of {}",
depth_spread
);
println!("✓ Circuit gating correctly handles heterogeneous depths");
}
#[test]
fn test_maximum_file_aggregation() {
println!("Testing aggregation with 7 files");
let num_files = 7;
let mut files = BTreeMap::new();
let mut metadatas = vec![];
let mut ledger = kontor_crypto::ledger::FileLedger::new();
for i in 0..num_files {
let size = 30 + i * 20;
let data = vec![(i * 5) as u8; size];
let (prepared, metadata) =
api::prepare_file(&data, "test_file.dat", b"").expect("Failed to prepare file");
files.insert(metadata.file_id.clone(), prepared);
ledger.add_file(&metadata).expect("Failed to add to ledger");
metadatas.push(metadata);
}
let seed = FieldElement::from(54321u64);
let challenges: Vec<Challenge> = metadatas
.iter()
.map(|m| Challenge::new_test(m.clone(), 1000, 2, seed)) .collect();
let file_refs: BTreeMap<String, &_> = files.iter().map(|(k, v)| (k.clone(), v)).collect();
let system = PorSystem::new(&ledger);
let files_vec: Vec<&_> = file_refs.values().copied().collect();
let proof = system
.prove(files_vec, &challenges)
.expect("Should generate proof with 7 files");
let is_valid = system
.verify(&proof, &challenges)
.expect("Verification should complete");
assert!(is_valid, "Proof with 7 files should verify");
let agg_depth = ledger.tree.layers.len() - 1;
let expected_agg_depth = (num_files as f64).log2().ceil() as usize;
println!("✓ 7 files aggregated successfully");
println!(
" Aggregation tree depth: {} (expected ~{})",
agg_depth, expected_agg_depth
);
}
#[test]
fn test_single_file_with_various_depths() {
println!("Testing single-file proofs at various depths");
let test_sizes = vec![10, 35, 70, 150, 300];
for size in test_sizes {
let data = vec![42u8; size];
let (prepared, metadata) =
api::prepare_file(&data, "test_file.dat", b"").expect("Failed to prepare file");
let depth = api::tree_depth_from_metadata(&metadata);
let challenge =
Challenge::new_test(metadata.clone(), 1000, 1, FieldElement::from(size as u64));
let mut files = BTreeMap::new();
files.insert(metadata.file_id.clone(), &prepared);
let ledger = create_single_file_ledger(&metadata);
let system = PorSystem::new(&ledger);
let files_vec: Vec<&_> = files.values().copied().collect();
let proof = system
.prove(files_vec, std::slice::from_ref(&challenge))
.unwrap_or_else(|e| {
panic!(
"Should generate proof for size {} (depth {}): {}",
size, depth, e
)
});
let is_valid = system
.verify(&proof, &[challenge])
.expect("Verification should complete");
assert!(
is_valid,
"Single-file proof at depth {} should verify",
depth
);
println!(" ✓ Size {} -> depth {} verified", size, depth);
}
println!("✓ All single-file depths verified successfully");
}
#[test]
fn test_aggregate_proofs_from_different_blocks() {
println!("Testing aggregation of proofs from different block heights");
let num_files = 3;
let (files, metadatas) = create_test_files(num_files, 50, 1000);
let metadata_refs: Vec<&_> = metadatas.iter().collect();
let ledger = create_ledger_from_metadatas(&metadata_refs);
let challenges: Vec<Challenge> = vec![
Challenge::new(
metadatas[0].clone(),
1000, 2,
FieldElement::from(1000 * 1000), "prover0".to_string(),
),
Challenge::new(
metadatas[1].clone(),
1005, 2,
FieldElement::from(1005 * 1000 + 1), "prover1".to_string(),
),
Challenge::new(
metadatas[2].clone(),
1010, 2,
FieldElement::from(1010 * 1000 + 2), "prover2".to_string(),
),
];
println!(
" Challenge 0: block_height={}, seed={:?}",
challenges[0].block_height, challenges[0].seed
);
println!(
" Challenge 1: block_height={}, seed={:?}",
challenges[1].block_height, challenges[1].seed
);
println!(
" Challenge 2: block_height={}, seed={:?}",
challenges[2].block_height, challenges[2].seed
);
let file_refs: BTreeMap<String, &_> = files.iter().map(|(k, v)| (k.clone(), v)).collect();
let system = PorSystem::new(&ledger);
let files_vec: Vec<&_> = file_refs.values().copied().collect();
let proof = system
.prove(files_vec, &challenges)
.expect("Should generate aggregated proof from different blocks");
let is_valid = system
.verify(&proof, &challenges)
.expect("Verification should complete");
assert!(
is_valid,
"Aggregated proof from blocks 1000, 1005, 1010 should verify"
);
println!("✓ Successfully aggregated and verified proofs from blocks 1000, 1005, 1010");
println!("✓ Multi-block aggregation with per-file seeds works correctly");
}