use alloc::vec::Vec;
use miden_core::{
Felt, WORD_SIZE, Word, ZERO,
crypto::hash::Poseidon2,
events::SystemEvent,
field::{BasedVectorSpace, Field, PrimeCharacteristicRing, QuadFelt},
};
use crate::{MemoryError, advice::AdviceError, errors::OperationError, fast::FastProcessor};
pub const HDWORD_TO_MAP_WITH_DOMAIN_DOMAIN_OFFSET: usize = 9;
#[derive(Debug, thiserror::Error)]
pub enum SystemEventError {
#[error(transparent)]
Advice(#[from] AdviceError),
#[error(transparent)]
Operation(#[from] OperationError),
#[error(transparent)]
Memory(#[from] MemoryError),
}
pub fn handle_system_event(
processor: &mut FastProcessor,
system_event: SystemEvent,
) -> Result<(), SystemEventError> {
match system_event {
SystemEvent::MerkleNodeMerge => merge_merkle_nodes(processor),
SystemEvent::MerkleNodeToStack => copy_merkle_node_to_adv_stack(processor),
SystemEvent::MapValueToStack => copy_map_value_to_adv_stack(processor, false, 0),
SystemEvent::MapValueCountToStack => copy_map_value_length_to_adv_stack(processor),
SystemEvent::MapValueToStackN0 => copy_map_value_to_adv_stack(processor, true, 0),
SystemEvent::MapValueToStackN4 => copy_map_value_to_adv_stack(processor, true, 4),
SystemEvent::MapValueToStackN8 => copy_map_value_to_adv_stack(processor, true, 8),
SystemEvent::HasMapKey => push_key_presence_flag(processor),
SystemEvent::Ext2Inv => push_ext2_inv_result(processor),
SystemEvent::U32Clz => push_leading_zeros(processor),
SystemEvent::U32Ctz => push_trailing_zeros(processor),
SystemEvent::U32Clo => push_leading_ones(processor),
SystemEvent::U32Cto => push_trailing_ones(processor),
SystemEvent::ILog2 => push_ilog2(processor),
SystemEvent::MemToMap => insert_mem_values_into_adv_map(processor),
SystemEvent::HdwordToMap => insert_hdword_into_adv_map(processor, ZERO),
SystemEvent::HdwordToMapWithDomain => {
let domain = processor.stack_get(HDWORD_TO_MAP_WITH_DOMAIN_DOMAIN_OFFSET);
insert_hdword_into_adv_map(processor, domain)
},
SystemEvent::HqwordToMap => insert_hqword_into_adv_map(processor),
SystemEvent::HpermToMap => insert_hperm_into_adv_map(processor),
}
}
fn insert_mem_values_into_adv_map(processor: &mut FastProcessor) -> Result<(), SystemEventError> {
let start_addr = processor.stack_get(5).as_canonical_u64();
let end_addr = processor.stack_get(6).as_canonical_u64();
if start_addr > u32::MAX as u64 {
return Err(MemoryError::AddressOutOfBounds { addr: start_addr }.into());
}
if end_addr > u32::MAX as u64 {
return Err(MemoryError::AddressOutOfBounds { addr: end_addr }.into());
}
if start_addr > end_addr {
return Err(MemoryError::InvalidMemoryRange { start_addr, end_addr }.into());
}
let addr_range = start_addr as u32..end_addr as u32;
let max_value_size = processor.options.max_adv_map_value_size();
if addr_range.len() > max_value_size {
return Err(AdviceError::AdvMapValueSizeExceeded {
size: addr_range.len(),
max: max_value_size,
}
.into());
}
let ctx = processor.ctx;
let values: Vec<Felt> = addr_range
.map(|addr| processor.memory().read_element_impl(ctx, addr).unwrap_or(ZERO))
.collect();
let key = processor.stack_get_word(1);
processor.advice.insert_into_map(key, values)?;
Ok(())
}
fn insert_hdword_into_adv_map(
processor: &mut FastProcessor,
domain: Felt,
) -> Result<(), SystemEventError> {
let a = processor.stack_get_word(1);
let b = processor.stack_get_word(5);
let key = Poseidon2::merge_in_domain(&[a, b], domain);
let mut values = Vec::with_capacity(2 * WORD_SIZE);
values.extend_from_slice(&Into::<[Felt; WORD_SIZE]>::into(a));
values.extend_from_slice(&Into::<[Felt; WORD_SIZE]>::into(b));
processor.advice.insert_into_map(key, values)?;
Ok(())
}
fn insert_hqword_into_adv_map(processor: &mut FastProcessor) -> Result<(), SystemEventError> {
let a = processor.stack_get_word(1);
let b = processor.stack_get_word(5);
let c = processor.stack_get_word(9);
let d = processor.stack_get_word_safe(13);
let key = Poseidon2::hash_elements(&[*a, *b, *c, *d].concat());
let mut values = Vec::with_capacity(4 * WORD_SIZE);
values.extend_from_slice(&Into::<[Felt; WORD_SIZE]>::into(a));
values.extend_from_slice(&Into::<[Felt; WORD_SIZE]>::into(b));
values.extend_from_slice(&Into::<[Felt; WORD_SIZE]>::into(c));
values.extend_from_slice(&Into::<[Felt; WORD_SIZE]>::into(d));
processor.advice.insert_into_map(key, values)?;
Ok(())
}
fn insert_hperm_into_adv_map(processor: &mut FastProcessor) -> Result<(), SystemEventError> {
let mut state = [
processor.stack_get(1),
processor.stack_get(2),
processor.stack_get(3),
processor.stack_get(4),
processor.stack_get(5),
processor.stack_get(6),
processor.stack_get(7),
processor.stack_get(8),
processor.stack_get(9),
processor.stack_get(10),
processor.stack_get(11),
processor.stack_get(12),
];
let values = state[Poseidon2::RATE_RANGE].to_vec();
Poseidon2::apply_permutation(&mut state);
let key = Word::new(
state[Poseidon2::DIGEST_RANGE]
.try_into()
.expect("failed to extract digest from state"),
);
processor.advice.insert_into_map(key, values)?;
Ok(())
}
fn merge_merkle_nodes(processor: &mut FastProcessor) -> Result<(), SystemEventError> {
let lhs = processor.stack_get_word(1);
let rhs = processor.stack_get_word(5);
processor.advice.merge_roots(lhs, rhs)?;
Ok(())
}
fn copy_merkle_node_to_adv_stack(processor: &mut FastProcessor) -> Result<(), SystemEventError> {
let depth = processor.stack_get(1);
let index = processor.stack_get(2);
let root = processor.stack_get_word(3);
let node = processor.advice.get_tree_node(root, depth, index)?;
processor.advice.push_stack_word(&node)?;
Ok(())
}
fn copy_map_value_to_adv_stack(
processor: &mut FastProcessor,
include_len: bool,
pad_to: u8,
) -> Result<(), SystemEventError> {
let key = processor.stack_get_word(1);
processor.advice.push_from_map(key, include_len, pad_to)?;
Ok(())
}
fn copy_map_value_length_to_adv_stack(
processor: &mut FastProcessor,
) -> Result<(), SystemEventError> {
let key = processor.stack_get_word(1);
let values_len = processor
.advice
.get_mapped_values(&key)
.ok_or(AdviceError::MapKeyNotFound { key })?
.len();
processor.advice.push_stack(Felt::new(values_len as u64))?;
Ok(())
}
pub fn push_key_presence_flag(processor: &mut FastProcessor) -> Result<(), SystemEventError> {
let map_key = processor.stack_get_word(1);
let presence_flag = processor.advice.contains_map_key(&map_key);
processor.advice.push_stack(Felt::from_bool(presence_flag))?;
Ok(())
}
fn push_ext2_inv_result(processor: &mut FastProcessor) -> Result<(), SystemEventError> {
let coef0 = processor.stack_get(1); let coef1 = processor.stack_get(2);
let element = QuadFelt::from_basis_coefficients_fn(|i: usize| [coef0, coef1][i]);
if element == QuadFelt::ZERO {
return Err(OperationError::DivideByZero.into());
}
let result = element.inverse();
let result = result.as_basis_coefficients_slice();
processor.advice.push_stack(result[0])?;
processor.advice.push_stack(result[1])?;
Ok(())
}
fn push_leading_zeros(processor: &mut FastProcessor) -> Result<(), SystemEventError> {
push_transformed_stack_top(processor, |stack_top| Felt::from_u32(stack_top.leading_zeros()))
}
fn push_trailing_zeros(processor: &mut FastProcessor) -> Result<(), SystemEventError> {
push_transformed_stack_top(processor, |stack_top| Felt::from_u32(stack_top.trailing_zeros()))
}
fn push_leading_ones(processor: &mut FastProcessor) -> Result<(), SystemEventError> {
push_transformed_stack_top(processor, |stack_top| Felt::from_u32(stack_top.leading_ones()))
}
fn push_trailing_ones(processor: &mut FastProcessor) -> Result<(), SystemEventError> {
push_transformed_stack_top(processor, |stack_top| Felt::from_u32(stack_top.trailing_ones()))
}
fn push_ilog2(processor: &mut FastProcessor) -> Result<(), SystemEventError> {
let n = processor.stack_get(1).as_canonical_u64();
if n == 0 {
return Err(OperationError::LogArgumentZero.into());
}
let ilog2 = Felt::from_u32(n.ilog2());
processor.advice.push_stack(ilog2)?;
Ok(())
}
fn push_transformed_stack_top(
processor: &mut FastProcessor,
f: impl FnOnce(u32) -> Felt,
) -> Result<(), SystemEventError> {
let stack_top = processor.stack_get(1);
let stack_top: u32 = stack_top
.as_canonical_u64()
.try_into()
.map_err(|_| OperationError::NotU32Values { values: vec![stack_top] })?;
let transformed_stack_top = f(stack_top);
processor.advice.push_stack(transformed_stack_top)?;
Ok(())
}
#[cfg(test)]
mod tests {
use alloc::vec;
use miden_core::{Felt, ZERO, crypto::hash::Poseidon2};
use super::*;
use crate::{StackInputs, fast::FastProcessor};
#[test]
fn insert_hperm_into_adv_map_consistent_with_permutation() {
let state_felts: [Felt; 12] = core::array::from_fn(|i| Felt::new((i + 1) as u64));
let mut stack_values = vec![ZERO]; stack_values.extend_from_slice(&state_felts);
let mut processor = FastProcessor::new(StackInputs::new(&stack_values).unwrap());
insert_hperm_into_adv_map(&mut processor).unwrap();
let mut expected_state_after_perm = state_felts;
Poseidon2::apply_permutation(&mut expected_state_after_perm);
let expected_key = miden_core::Word::new(
expected_state_after_perm[Poseidon2::DIGEST_RANGE].try_into().unwrap(),
);
let expected_values = state_felts[Poseidon2::RATE_RANGE].to_vec();
let stored_values = processor
.advice
.get_mapped_values(&expected_key)
.expect("key should be present in advice map");
assert_eq!(stored_values, expected_values.as_slice());
}
}