use alloc::vec::Vec;
use miden_ace_codegen::{
AceConfig, AceError, FactoredCircuitFactory, LayoutKind, ShuffleEncodeBuffer,
};
use miden_core::{Felt, Word, crypto::hash::Poseidon2, field::QuadFelt};
use miden_crypto::merkle::MerklePath;
use super::multi_air::build_factored_multi_air_ace_circuit;
use crate::{AIRS, MIDEN_AIR_COUNT, ProofOrder};
fn recursive_verifier_num_quotient_chunks() -> usize {
let max_log_quotient_degree = AIRS
.iter()
.map(miden_crypto::stark::log_quotient_degree::<Felt, QuadFelt, _>)
.max()
.expect("the Miden AIR set is non-empty");
1usize << max_log_quotient_degree
}
fn recursive_verifier_ace_config() -> AceConfig {
AceConfig {
num_quotient_chunks: recursive_verifier_num_quotient_chunks(),
layout: LayoutKind::Masm,
num_airs: MIDEN_AIR_COUNT,
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RecursiveAceCircuit {
pub num_inputs: usize,
pub num_eval_gates: usize,
pub stream_len: usize,
pub shuffle_prefix_len: usize,
pub shuffle_commitment: Word,
pub common_commitment: Word,
pub commitment: Word,
pub instructions: Vec<Felt>,
}
pub struct RecursiveAceCircuitFactory {
inner: FactoredCircuitFactory<QuadFelt>,
}
impl RecursiveAceCircuitFactory {
pub fn new() -> Result<Self, AceError> {
let factored = build_factored_multi_air_ace_circuit(recursive_verifier_ace_config())?;
let inner = FactoredCircuitFactory::new(factored.into_inner())?;
Ok(Self { inner })
}
fn order_indices(order: &ProofOrder) -> Vec<usize> {
order.airs().iter().map(|air| air.instance_index()).collect()
}
pub fn num_quotient_chunks(&self) -> usize {
self.inner.factored().layout().counts.num_quotient_chunks
}
pub fn leaf_for_order(
&self,
order: &ProofOrder,
buffer: &mut ShuffleEncodeBuffer,
) -> Result<Word, AceError> {
self.inner.leaf_for_order(&Self::order_indices(order), buffer)
}
pub fn circuit_for_order(&self, order: &ProofOrder) -> Result<RecursiveAceCircuit, AceError> {
let circuit = self.inner.circuit_for_order(&Self::order_indices(order))?;
Ok(RecursiveAceCircuit {
num_inputs: circuit.encoded.num_vars(),
num_eval_gates: circuit.encoded.num_eval_rows(),
stream_len: circuit.encoded.size_in_felt(),
shuffle_prefix_len: circuit.shuffle_prefix_len,
shuffle_commitment: circuit.shuffle_commitment,
common_commitment: circuit.common_commitment,
commitment: circuit.commitment,
instructions: circuit.encoded.instructions().to_vec(),
})
}
}
#[cfg(feature = "std")]
pub(crate) fn shared_recursive_factory() -> &'static RecursiveAceCircuitFactory {
static FACTORY: std::sync::OnceLock<RecursiveAceCircuitFactory> = std::sync::OnceLock::new();
FACTORY.get_or_init(|| {
RecursiveAceCircuitFactory::new().expect("recursive-verifier ACE composition must build")
})
}
pub fn recursive_registry_entry(
order: &ProofOrder,
) -> Result<(RecursiveAceCircuit, MerklePath), AceError> {
#[cfg(feature = "std")]
{
let circuit = shared_recursive_factory().circuit_for_order(order)?;
let (leaf, path) = crate::config::ace_registry_path(order.tag())
.expect("proof-order tags always address registry slots");
assert_eq!(
circuit.commitment, leaf,
"ACE registry tree drifted from the factory's circuits"
);
Ok((circuit, path))
}
#[cfg(not(feature = "std"))]
{
let factory = RecursiveAceCircuitFactory::new()?;
let circuit = factory.circuit_for_order(order)?;
let tree = crate::config::build_miden_vm_ace_registry_with(&factory);
let (leaf, path) = crate::config::registry_path_in(&tree, order.tag())
.expect("proof-order tags always address registry slots");
assert_eq!(
circuit.commitment, leaf,
"ACE registry tree drifted from the factory's circuits"
);
Ok((circuit, path))
}
}
pub fn build_recursive_verifier_ace_circuit(
order: &ProofOrder,
) -> Result<RecursiveAceCircuit, AceError> {
let factored = build_factored_multi_air_ace_circuit(recursive_verifier_ace_config())?;
let circuit = factored.circuit_for_order(order)?;
let encoded = circuit.to_ace()?;
let instructions = encoded.instructions();
let stream_len = encoded.size_in_felt();
if stream_len != instructions.len() {
return Err(AceError::InvalidInputLayout {
message: format!(
"ACE circuit stream length ({stream_len}) does not match instruction count ({})",
instructions.len()
),
});
}
if !stream_len.is_multiple_of(8) {
return Err(AceError::InvalidInputLayout {
message: "ACE circuit stream must be 8-felt aligned for adv_pipe".into(),
});
}
let const_felts = encoded.num_constants() * miden_ace_codegen::EXT_DEGREE;
let shuffle_prefix_len = const_felts + factored.num_shuffle_ops();
if !shuffle_prefix_len.is_multiple_of(8) || shuffle_prefix_len >= stream_len {
return Err(AceError::InvalidInputLayout {
message: format!(
"ACE shuffle prefix ({shuffle_prefix_len} of {stream_len} felts) must be a \
proper 8-felt-aligned stream prefix"
),
});
}
let shuffle_commitment = Poseidon2::hash_elements(&instructions[..shuffle_prefix_len]);
let common_commitment = Poseidon2::hash_elements(&instructions[shuffle_prefix_len..]);
let commitment = Poseidon2::merge(&[shuffle_commitment, common_commitment]);
Ok(RecursiveAceCircuit {
num_inputs: encoded.num_vars(),
num_eval_gates: encoded.num_eval_rows(),
stream_len,
shuffle_prefix_len,
shuffle_commitment,
common_commitment,
commitment,
instructions: instructions.to_vec(),
})
}