use alloc::{collections::VecDeque, vec::Vec};
use miden_core::{
Felt, WORD_SIZE, Word,
advice::{AdviceInputs, AdviceMap},
crypto::merkle::{InnerNodeInfo, MerklePath, MerkleStore, NodeIndex},
precompile::PrecompileRequest,
};
#[cfg(test)]
use miden_core::{crypto::hash::Blake3_256, serde::Serializable};
mod errors;
pub use errors::AdviceError;
use crate::{ExecutionOptions, host::AdviceMutation, processor::AdviceProviderInterface};
const MAX_ADVICE_STACK_SIZE: usize = 1 << 17;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AdviceProvider {
stack: VecDeque<Felt>,
map: AdviceMap,
map_element_count: usize,
max_map_value_size: usize,
max_map_elements: usize,
store: MerkleStore,
pc_requests: Vec<PrecompileRequest>,
}
impl Default for AdviceProvider {
fn default() -> Self {
Self::empty(&ExecutionOptions::default())
}
}
impl AdviceProvider {
pub fn new(inputs: AdviceInputs, options: &ExecutionOptions) -> Result<Self, AdviceError> {
let AdviceInputs { stack, map, store } = inputs;
let mut provider = Self::empty(options);
provider.extend_stack(stack)?;
provider.extend_merkle_store(store.inner_nodes());
provider.extend_map(&map)?;
Ok(provider)
}
fn empty(options: &ExecutionOptions) -> Self {
Self {
stack: VecDeque::new(),
map: AdviceMap::default(),
map_element_count: 0,
max_map_value_size: options.max_adv_map_value_size(),
max_map_elements: options.max_adv_map_elements(),
store: MerkleStore::default(),
pc_requests: Vec::new(),
}
}
pub(crate) fn set_options(&mut self, options: &ExecutionOptions) -> Result<(), AdviceError> {
Self::validate_map_values(&self.map, options.max_adv_map_value_size())?;
let map_element_count =
self.map.total_element_count().ok_or(AdviceError::AdvMapElementBudgetExceeded {
current: self.map_element_count,
added: usize::MAX,
max: options.max_adv_map_elements(),
})?;
if map_element_count > options.max_adv_map_elements() {
return Err(AdviceError::AdvMapElementBudgetExceeded {
current: 0,
added: map_element_count,
max: options.max_adv_map_elements(),
});
}
self.map_element_count = map_element_count;
self.max_map_value_size = options.max_adv_map_value_size();
self.max_map_elements = options.max_adv_map_elements();
Ok(())
}
#[cfg(test)]
#[expect(dead_code)]
pub(crate) fn merkle_store(&self) -> &MerkleStore {
&self.store
}
pub fn apply_mutations(
&mut self,
mutations: impl IntoIterator<Item = AdviceMutation>,
) -> Result<(), AdviceError> {
mutations.into_iter().try_for_each(|mutation| self.apply_mutation(mutation))
}
fn apply_mutation(&mut self, mutation: AdviceMutation) -> Result<(), AdviceError> {
match mutation {
AdviceMutation::ExtendStack { values } => {
self.extend_stack(values)?;
},
AdviceMutation::ExtendMap { other } => {
self.extend_map(&other)?;
},
AdviceMutation::ExtendMerkleStore { infos } => {
self.extend_merkle_store(infos);
},
AdviceMutation::ExtendPrecompileRequests { data } => {
self.extend_precompile_requests(data);
},
}
Ok(())
}
#[cfg(test)]
#[must_use]
pub(crate) fn fingerprint(&self) -> [u8; 32] {
let stack = self.stack.iter().copied().collect::<Vec<_>>().to_bytes();
let map = self.map.to_bytes();
let mut store_nodes = self
.store
.inner_nodes()
.map(|info| (info.value, info.left, info.right))
.collect::<Vec<_>>();
store_nodes.sort_unstable_by(|lhs, rhs| {
lhs.0
.cmp(&rhs.0)
.then_with(|| lhs.1.cmp(&rhs.1))
.then_with(|| lhs.2.cmp(&rhs.2))
});
let store = store_nodes
.into_iter()
.flat_map(|(value, left, right)| [value, left, right])
.collect::<Vec<_>>()
.to_bytes();
let precompile_requests = self.pc_requests.to_bytes();
Blake3_256::hash_iter(
[
stack.as_slice(),
map.as_slice(),
store.as_slice(),
precompile_requests.as_slice(),
]
.into_iter(),
)
.into()
}
fn pop_stack(&mut self) -> Result<Felt, AdviceError> {
self.stack.pop_front().ok_or(AdviceError::StackReadFailed)
}
fn pop_stack_word(&mut self) -> Result<Word, AdviceError> {
if self.stack.len() < 4 {
return Err(AdviceError::StackReadFailed);
}
let w0 = self.stack.pop_front().expect("checked len");
let w1 = self.stack.pop_front().expect("checked len");
let w2 = self.stack.pop_front().expect("checked len");
let w3 = self.stack.pop_front().expect("checked len");
Ok(Word::new([w0, w1, w2, w3]))
}
fn pop_stack_dword(&mut self) -> Result<[Word; 2], AdviceError> {
let word0 = self.pop_stack_word()?;
let word1 = self.pop_stack_word()?;
Ok([word0, word1])
}
fn check_stack_capacity(&self, count: usize) -> Result<(), AdviceError> {
let resulting_size =
self.stack.len().checked_add(count).ok_or(AdviceError::StackSizeExceeded {
push_count: count,
max: MAX_ADVICE_STACK_SIZE,
})?;
if resulting_size > MAX_ADVICE_STACK_SIZE {
return Err(AdviceError::StackSizeExceeded {
push_count: count,
max: MAX_ADVICE_STACK_SIZE,
});
}
Ok(())
}
pub fn push_stack(&mut self, value: Felt) -> Result<(), AdviceError> {
self.check_stack_capacity(1)?;
self.stack.push_front(value);
Ok(())
}
pub fn push_stack_word(&mut self, word: &Word) -> Result<(), AdviceError> {
self.check_stack_capacity(4)?;
for &value in word.iter().rev() {
self.stack.push_front(value);
}
Ok(())
}
pub fn push_from_map(
&mut self,
key: Word,
include_len: bool,
pad_to: u8,
) -> Result<(), AdviceError> {
let values = self.map.get(&key).ok_or(AdviceError::MapKeyNotFound { key })?;
let num_pad_elements = if pad_to != 0 {
values.len().next_multiple_of(pad_to as usize) - values.len()
} else {
0
};
let total_push = values
.len()
.checked_add(num_pad_elements)
.and_then(|n| n.checked_add(if include_len { 1 } else { 0 }))
.ok_or(AdviceError::StackSizeExceeded {
push_count: usize::MAX,
max: MAX_ADVICE_STACK_SIZE,
})?;
self.check_stack_capacity(total_push)?;
for _ in 0..num_pad_elements {
self.stack.push_front(Felt::default());
}
for &value in values.iter().rev() {
self.stack.push_front(value);
}
if include_len {
self.stack.push_front(Felt::new_unchecked(values.len() as u64));
}
Ok(())
}
pub fn stack(&self) -> Vec<Felt> {
self.stack.iter().copied().collect()
}
pub fn extend_stack<I>(&mut self, iter: I) -> Result<(), AdviceError>
where
I: IntoIterator<Item = Felt>,
{
let values: Vec<Felt> = iter.into_iter().collect();
self.check_stack_capacity(values.len())?;
for value in values.into_iter().rev() {
self.stack.push_front(value);
}
Ok(())
}
pub fn contains_map_key(&self, key: &Word) -> bool {
self.map.contains_key(key)
}
pub fn get_mapped_values(&self, key: &Word) -> Option<&[Felt]> {
self.map.get(key).map(AsRef::as_ref)
}
fn validate_map_values(map: &AdviceMap, max_value_size: usize) -> Result<(), AdviceError> {
for (_, values) in map.iter() {
if values.len() > max_value_size {
return Err(AdviceError::AdvMapValueSizeExceeded {
size: values.len(),
max: max_value_size,
});
}
}
Ok(())
}
fn entry_element_count(value_len: usize) -> Option<usize> {
WORD_SIZE.checked_add(value_len)
}
fn check_map_value_size(&self, size: usize) -> Result<(), AdviceError> {
if size > self.max_map_value_size {
return Err(AdviceError::AdvMapValueSizeExceeded {
size,
max: self.max_map_value_size,
});
}
Ok(())
}
fn check_map_element_budget(&self, added: usize) -> Result<(), AdviceError> {
let Some(new_total) = self.map_element_count.checked_add(added) else {
return Err(AdviceError::AdvMapElementBudgetExceeded {
current: self.map_element_count,
added,
max: self.max_map_elements,
});
};
if new_total > self.max_map_elements {
return Err(AdviceError::AdvMapElementBudgetExceeded {
current: self.map_element_count,
added,
max: self.max_map_elements,
});
}
Ok(())
}
pub fn insert_into_map(&mut self, key: Word, values: Vec<Felt>) -> Result<(), AdviceError> {
match self.map.get(&key) {
Some(existing_values) => {
let existing_values = existing_values.as_ref();
if existing_values != values {
return Err(AdviceError::MapKeyAlreadyPresent {
key,
prev_values: existing_values.to_vec(),
new_values: values,
});
}
},
None => {
self.check_map_value_size(values.len())?;
let added = Self::entry_element_count(values.len()).ok_or(
AdviceError::AdvMapElementBudgetExceeded {
current: self.map_element_count,
added: usize::MAX,
max: self.max_map_elements,
},
)?;
self.check_map_element_budget(added)?;
self.map.insert(key, values);
self.map_element_count += added;
},
}
Ok(())
}
pub fn extend_map(&mut self, other: &AdviceMap) -> Result<(), AdviceError> {
let mut added = 0usize;
for (key, values) in other.iter() {
if let Some(existing_values) = self.map.get(key) {
if existing_values.as_ref() != values.as_ref() {
return Err(AdviceError::MapKeyAlreadyPresent {
key: *key,
prev_values: existing_values.to_vec(),
new_values: values.to_vec(),
});
}
continue;
}
self.check_map_value_size(values.len())?;
let entry_elements = Self::entry_element_count(values.len()).ok_or(
AdviceError::AdvMapElementBudgetExceeded {
current: self.map_element_count,
added: usize::MAX,
max: self.max_map_elements,
},
)?;
added = added.checked_add(entry_elements).ok_or(
AdviceError::AdvMapElementBudgetExceeded {
current: self.map_element_count,
added: usize::MAX,
max: self.max_map_elements,
},
)?;
}
self.check_map_element_budget(added)?;
self.map.merge(other).map_err(|((key, prev_values), new_values)| {
AdviceError::MapKeyAlreadyPresent {
key,
prev_values: prev_values.to_vec(),
new_values: new_values.to_vec(),
}
})?;
self.map_element_count += added;
Ok(())
}
pub fn get_tree_node(&self, root: Word, depth: Felt, index: Felt) -> Result<Word, AdviceError> {
let index = NodeIndex::from_elements(&depth, &index)
.map_err(|_| AdviceError::InvalidMerkleTreeNodeIndex { depth, index })?;
self.store.get_node(root, index).map_err(AdviceError::MerkleStoreLookupFailed)
}
pub fn has_merkle_path(
&self,
root: Word,
depth: Felt,
index: Felt,
) -> Result<bool, AdviceError> {
let index = NodeIndex::from_elements(&depth, &index)
.map_err(|_| AdviceError::InvalidMerkleTreeNodeIndex { depth, index })?;
Ok(self.store.has_path(root, index))
}
pub fn get_merkle_path(
&self,
root: Word,
depth: Felt,
index: Felt,
) -> Result<MerklePath, AdviceError> {
let index = NodeIndex::from_elements(&depth, &index)
.map_err(|_| AdviceError::InvalidMerkleTreeNodeIndex { depth, index })?;
self.store
.get_path(root, index)
.map(|value| value.path)
.map_err(AdviceError::MerkleStoreLookupFailed)
}
pub fn update_merkle_node(
&mut self,
root: Word,
depth: Felt,
index: Felt,
value: Word,
) -> Result<(MerklePath, Word), AdviceError> {
let node_index = NodeIndex::from_elements(&depth, &index)
.map_err(|_| AdviceError::InvalidMerkleTreeNodeIndex { depth, index })?;
self.store
.set_node(root, node_index, value)
.map(|root| (root.path, root.root))
.map_err(AdviceError::MerkleStoreUpdateFailed)
}
pub fn merge_roots(&mut self, lhs: Word, rhs: Word) -> Result<Word, AdviceError> {
self.store.merge_roots(lhs, rhs).map_err(AdviceError::MerkleStoreMergeFailed)
}
pub fn has_merkle_root(&self, root: Word) -> bool {
self.store.get_node(root, NodeIndex::root()).is_ok()
}
pub fn extend_merkle_store<I>(&mut self, iter: I)
where
I: IntoIterator<Item = InnerNodeInfo>,
{
self.store.extend(iter);
}
pub fn precompile_requests(&self) -> &[PrecompileRequest] {
&self.pc_requests
}
pub fn extend_precompile_requests<I>(&mut self, iter: I)
where
I: IntoIterator<Item = PrecompileRequest>,
{
self.pc_requests.extend(iter);
}
pub fn take_precompile_requests(&mut self) -> Vec<PrecompileRequest> {
core::mem::take(&mut self.pc_requests)
}
pub fn extend_from_inputs(&mut self, inputs: &AdviceInputs) -> Result<(), AdviceError> {
self.extend_stack(inputs.stack.iter().cloned())?;
self.extend_merkle_store(inputs.store.inner_nodes());
self.extend_map(&inputs.map)
}
pub fn into_parts(self) -> (Vec<Felt>, AdviceMap, MerkleStore, Vec<PrecompileRequest>) {
(self.stack.into_iter().collect(), self.map, self.store, self.pc_requests)
}
}
impl AdviceProviderInterface for AdviceProvider {
#[inline(always)]
fn pop_stack(&mut self) -> Result<Felt, AdviceError> {
self.pop_stack()
}
#[inline(always)]
fn pop_stack_word(&mut self) -> Result<Word, AdviceError> {
self.pop_stack_word()
}
#[inline(always)]
fn pop_stack_dword(&mut self) -> Result<[Word; 2], AdviceError> {
self.pop_stack_dword()
}
#[inline(always)]
fn get_merkle_path(
&self,
root: Word,
depth: Felt,
index: Felt,
) -> Result<Option<MerklePath>, AdviceError> {
self.get_merkle_path(root, depth, index).map(Some)
}
#[inline(always)]
fn update_merkle_node(
&mut self,
root: Word,
depth: Felt,
index: Felt,
value: Word,
) -> Result<Option<MerklePath>, AdviceError> {
self.update_merkle_node(root, depth, index, value).map(|(path, _)| Some(path))
}
}
#[cfg(test)]
mod tests {
use alloc::{collections::BTreeMap, vec, vec::Vec};
use miden_core::WORD_SIZE;
use super::AdviceProvider;
use crate::{
AdviceInputs, ExecutionOptions, Felt, Word,
advice::{AdviceError, AdviceMap},
crypto::merkle::{MerkleStore, MerkleTree},
};
fn make_leaf(seed: u64) -> Word {
[
Felt::new_unchecked(seed),
Felt::new_unchecked(seed + 1),
Felt::new_unchecked(seed + 2),
Felt::new_unchecked(seed + 3),
]
.into()
}
#[test]
fn fingerprint_is_stable_across_merkle_store_insertion_order() {
let tree_a =
MerkleTree::new([make_leaf(1), make_leaf(5), make_leaf(9), make_leaf(13)]).unwrap();
let tree_b =
MerkleTree::new([make_leaf(17), make_leaf(21), make_leaf(25), make_leaf(29)]).unwrap();
let mut store_a = MerkleStore::default();
store_a.extend(tree_a.inner_nodes());
store_a.extend(tree_b.inner_nodes());
let mut store_b = MerkleStore::default();
store_b.extend(tree_b.inner_nodes());
store_b.extend(tree_a.inner_nodes());
assert_eq!(store_a, store_b);
let provider_a = AdviceProvider::new(
AdviceInputs::default().with_merkle_store(store_a),
&Default::default(),
)
.unwrap();
let provider_b = AdviceProvider::new(
AdviceInputs::default().with_merkle_store(store_b),
&Default::default(),
)
.unwrap();
assert_eq!(provider_a, provider_b);
assert_eq!(provider_a.fingerprint(), provider_b.fingerprint());
}
#[test]
fn advice_map_insert_respects_element_budget() {
let options = ExecutionOptions::default().with_max_adv_map_elements(WORD_SIZE + 1);
let mut provider = AdviceProvider::new(AdviceInputs::default(), &options).unwrap();
provider.insert_into_map(make_leaf(0), vec![Felt::ONE]).unwrap();
let err = provider.insert_into_map(make_leaf(1), vec![Felt::ONE]).unwrap_err();
assert!(matches!(
err,
AdviceError::AdvMapElementBudgetExceeded { current: 5, added: 5, max: 5 }
));
assert_eq!(provider.map.len(), 1);
assert!(provider.contains_map_key(&make_leaf(0)));
assert!(!provider.contains_map_key(&make_leaf(1)));
}
#[test]
fn advice_map_insert_respects_value_limit() {
let options = ExecutionOptions::default().with_max_adv_map_value_size(1);
let mut provider = AdviceProvider::new(AdviceInputs::default(), &options).unwrap();
let values = vec![Felt::ONE, Felt::new_unchecked(2)];
let err = provider.insert_into_map(make_leaf(0), values).unwrap_err();
assert!(matches!(err, AdviceError::AdvMapValueSizeExceeded { size: 2, max: 1 }));
assert_eq!(provider.map.len(), 0);
}
#[test]
fn advice_map_extend_respects_element_budget_atomically() {
let options = ExecutionOptions::default().with_max_adv_map_elements(2 * (WORD_SIZE + 1));
let mut provider = AdviceProvider::new(AdviceInputs::default(), &options).unwrap();
provider.insert_into_map(make_leaf(0), vec![Felt::ONE]).unwrap();
let other = advice_map_from_entries(1..3, 1);
let err = provider.extend_map(&other).unwrap_err();
assert!(matches!(
err,
AdviceError::AdvMapElementBudgetExceeded { current: 5, added: 10, max: 10 }
));
assert_eq!(provider.map.len(), 1);
assert!(provider.contains_map_key(&make_leaf(0)));
assert!(!provider.contains_map_key(&make_leaf(1)));
assert!(!provider.contains_map_key(&make_leaf(2)));
}
#[test]
fn advice_map_extend_respects_value_limit_atomically() {
let options = ExecutionOptions::default().with_max_adv_map_value_size(1);
let mut provider = AdviceProvider::new(AdviceInputs::default(), &options).unwrap();
let other = advice_map_from_entries(0..2, 2);
let err = provider.extend_map(&other).unwrap_err();
assert!(matches!(err, AdviceError::AdvMapValueSizeExceeded { size: 2, max: 1 }));
assert_eq!(provider.map.len(), 0);
}
#[test]
fn initial_advice_map_respects_element_budget() {
let options = ExecutionOptions::default().with_max_adv_map_elements(WORD_SIZE);
let inputs = AdviceInputs::default().with_map([(make_leaf(0), vec![Felt::ONE])]);
let err = AdviceProvider::new(inputs, &options).unwrap_err();
assert!(matches!(
err,
AdviceError::AdvMapElementBudgetExceeded { current: 0, added: 5, max: 4 }
));
}
fn advice_map_from_entries(keys: impl Iterator<Item = u64>, value_len: usize) -> AdviceMap {
keys.map(|key| {
let values = (0..value_len)
.map(|offset| Felt::new_unchecked(key + offset as u64))
.collect::<Vec<_>>();
(make_leaf(key), values)
})
.collect::<BTreeMap<_, _>>()
.into()
}
}