use super::types::{Challenge, FieldElement, PreparedFile};
use crate::{
circuit::{CircuitWitness, FileProofWitness},
commitment::{domain_tags, poseidon_hash_tagged},
config, get_padded_proof_for_leaf,
ledger::FileLedger,
NovaPoRError, Result,
};
use ff::Field;
use std::collections::BTreeMap;
use tracing::{debug, debug_span};
#[allow(clippy::too_many_arguments)]
pub fn generate_circuit_witness(
sorted_challenges: &[&Challenge],
files: Option<&BTreeMap<String, &PreparedFile>>,
ledger: &FileLedger,
file_tree_depth: usize, max_supported_depth: usize, current_state: FieldElement,
aggregated_tree_depth: usize,
step_num: usize,
precomputed_ledger_indices: &[usize], ) -> Result<(CircuitWitness<FieldElement>, FieldElement)> {
let _span = debug_span!(
"generate_circuit_witness",
step_num,
num_challenges = sorted_challenges.len(),
has_files = files.is_some()
)
.entered();
let mut file_witnesses = Vec::new();
let mut local_state = current_state;
debug!("generate_circuit_witness - Step {}:", step_num);
debug!(" - Input state: {:?}", current_state);
debug!(" - Processing {} challenges", sorted_challenges.len());
debug!(" - Aggregated tree depth: {}", aggregated_tree_depth);
if let Some(files) = files {
for (file_idx, challenge) in sorted_challenges.iter().enumerate() {
let file = files.get(&challenge.file_metadata.file_id).ok_or_else(|| {
NovaPoRError::InvalidInput(format!(
"File {} not found",
challenge.file_metadata.file_id
))
})?;
let (witness, new_state) = create_single_file_witness(
challenge,
file,
file_idx,
local_state,
file_tree_depth,
aggregated_tree_depth,
ledger,
precomputed_ledger_indices,
)?;
file_witnesses.push(witness);
local_state = new_state;
}
} else {
let num_dummies = sorted_challenges.len().max(1);
for i in 0..num_dummies {
let mut dummy_witness = create_padding_witness(file_tree_depth, aggregated_tree_depth);
if i == 0 {
dummy_witness.actual_depth = max_supported_depth;
}
file_witnesses.push(dummy_witness);
}
}
let num_real_files = if let Some(_files) = files {
sorted_challenges.len() } else {
file_witnesses.iter().filter(|w| w.actual_depth > 0).count()
};
let (target_witness_count, _) =
config::derive_shape(sorted_challenges.len(), max_supported_depth);
while file_witnesses.len() < target_witness_count {
file_witnesses.push(create_padding_witness(
file_tree_depth,
aggregated_tree_depth,
));
}
assert_eq!(
file_witnesses.len(),
target_witness_count,
"CircuitWitness must have exactly {} witnesses, got {}",
target_witness_count,
file_witnesses.len()
);
debug!("generate_circuit_witness complete:");
debug!(" - Real files: {}", num_real_files);
debug!(" - Total witnesses: {}", file_witnesses.len());
debug!(" - Output state: {:?}", local_state);
let circuit_witness = CircuitWitness {
witnesses: file_witnesses,
num_real_files,
};
Ok((circuit_witness, local_state))
}
#[allow(clippy::too_many_arguments)]
fn create_single_file_witness(
challenge: &Challenge,
file: &PreparedFile,
file_idx: usize,
current_state: FieldElement,
file_tree_depth: usize,
aggregated_tree_depth: usize,
ledger: &FileLedger,
precomputed_ledger_indices: &[usize],
) -> Result<(FileProofWitness<FieldElement>, FieldElement)> {
let file_depth = file.tree.layers.len() - 1;
let challenge_hash =
poseidon_hash_tagged(domain_tags::challenge(), challenge.seed, current_state);
let hash = if aggregated_tree_depth > 0 {
poseidon_hash_tagged(
domain_tags::challenge_per_file(),
challenge_hash,
FieldElement::from(file_idx as u64),
)
} else {
challenge_hash
};
let leaf_index = crate::utils::derive_index_unbiased(hash, 1usize << file_depth);
let merkle_proof = get_padded_proof_for_leaf(&file.tree, leaf_index, file_tree_depth)?;
let agg_proof = if aggregated_tree_depth > 0 {
ledger
.get_aggregation_proof(&challenge.file_metadata.file_id)
.ok_or_else(|| NovaPoRError::InvalidInput("File not found in ledger".to_string()))?
} else {
crate::merkle::CircuitMerkleProof {
leaf: FieldElement::ZERO,
siblings: vec![],
path_indices: vec![],
}
};
let ledger_index = precomputed_ledger_indices[file_idx];
let witness = FileProofWitness {
leaf: merkle_proof.leaf,
file_siblings: merkle_proof.siblings,
file_root: challenge.file_metadata.root,
actual_depth: file_depth,
agg_siblings: agg_proof.siblings,
ledger_index,
};
let new_state = poseidon_hash_tagged(
domain_tags::state_update(),
current_state,
merkle_proof.leaf,
);
Ok((witness, new_state))
}
fn create_padding_witness(
file_tree_depth: usize,
aggregated_tree_depth: usize,
) -> FileProofWitness<FieldElement> {
FileProofWitness {
leaf: FieldElement::ZERO,
file_siblings: vec![FieldElement::ZERO; file_tree_depth],
file_root: FieldElement::ZERO,
actual_depth: 0, agg_siblings: vec![FieldElement::ZERO; aggregated_tree_depth],
ledger_index: 0,
}
}