use alloc::vec::Vec;
use vm_core::{
Felt, FieldElement, WORD_SIZE, Word, ZERO,
crypto::{
hash::{Rpo256, RpoDigest},
merkle::{EmptySubtreeRoots, SMT_DEPTH, Smt},
},
mast::MastNodeExt,
sys_events::SystemEvent,
};
use winter_prover::math::fft;
use crate::{
AdviceProvider, AdviceSource, ExecutionError, Ext2InttError, Host, MemoryError, Process,
ProcessState, QuadFelt, errors::ErrorContext,
};
pub const HDWORD_TO_MAP_WITH_DOMAIN_DOMAIN_OFFSET: usize = 8;
const M: u64 = 12289;
impl Process {
pub(super) fn handle_system_event(
&self,
system_event: SystemEvent,
host: &mut impl Host,
err_ctx: &ErrorContext<'_, impl MastNodeExt>,
) -> Result<(), ExecutionError> {
let advice_provider = host.advice_provider_mut();
let process_state: ProcessState = self.into();
match system_event {
SystemEvent::MerkleNodeMerge => {
merge_merkle_nodes(advice_provider, process_state, err_ctx)
},
SystemEvent::MerkleNodeToStack => {
copy_merkle_node_to_adv_stack(advice_provider, process_state, err_ctx)
},
SystemEvent::MapValueToStack => {
copy_map_value_to_adv_stack(advice_provider, process_state, false, err_ctx)
},
SystemEvent::MapValueToStackN => {
copy_map_value_to_adv_stack(advice_provider, process_state, true, err_ctx)
},
SystemEvent::U64Div => push_u64_div_result(advice_provider, process_state, err_ctx),
SystemEvent::FalconDiv => {
push_falcon_mod_result(advice_provider, process_state, err_ctx)
},
SystemEvent::Ext2Inv => push_ext2_inv_result(advice_provider, process_state, err_ctx),
SystemEvent::Ext2Intt => push_ext2_intt_result(advice_provider, process_state, err_ctx),
SystemEvent::SmtPeek => push_smtpeek_result(advice_provider, process_state, err_ctx),
SystemEvent::U32Clz => push_leading_zeros(advice_provider, process_state, err_ctx),
SystemEvent::U32Ctz => push_trailing_zeros(advice_provider, process_state, err_ctx),
SystemEvent::U32Clo => push_leading_ones(advice_provider, process_state, err_ctx),
SystemEvent::U32Cto => push_trailing_ones(advice_provider, process_state, err_ctx),
SystemEvent::ILog2 => push_ilog2(advice_provider, process_state, err_ctx),
SystemEvent::MemToMap => insert_mem_values_into_adv_map(advice_provider, process_state),
SystemEvent::HdwordToMap => {
insert_hdword_into_adv_map(advice_provider, process_state, ZERO)
},
SystemEvent::HdwordToMapWithDomain => {
let domain = self.stack.get(HDWORD_TO_MAP_WITH_DOMAIN_DOMAIN_OFFSET);
insert_hdword_into_adv_map(advice_provider, process_state, domain)
},
SystemEvent::HpermToMap => insert_hperm_into_adv_map(advice_provider, process_state),
}
}
}
pub fn insert_mem_values_into_adv_map(
advice_provider: &mut impl AdviceProvider,
process: ProcessState,
) -> Result<(), ExecutionError> {
let (start_addr, end_addr) = get_mem_addr_range(process, 4, 5)?;
let ctx = process.ctx();
let mut values = Vec::with_capacity(((end_addr - start_addr) as usize) * WORD_SIZE);
for addr in start_addr..end_addr {
let mem_value = process.get_mem_value(ctx, addr).unwrap_or(ZERO);
values.push(mem_value);
}
let key = process.get_stack_word(0);
advice_provider.insert_into_map(key, values);
Ok(())
}
pub fn insert_hdword_into_adv_map(
advice_provider: &mut impl AdviceProvider,
process: ProcessState,
domain: Felt,
) -> Result<(), ExecutionError> {
let word0 = process.get_stack_word(0);
let word1 = process.get_stack_word(1);
let key = Rpo256::merge_in_domain(&[word1.into(), word0.into()], domain);
let mut values = Vec::with_capacity(2 * WORD_SIZE);
values.extend_from_slice(&word1);
values.extend_from_slice(&word0);
advice_provider.insert_into_map(key.into(), values);
Ok(())
}
pub fn insert_hperm_into_adv_map(
advice_provider: &mut impl AdviceProvider,
process: ProcessState,
) -> Result<(), ExecutionError> {
let mut state = [
process.get_stack_item(11),
process.get_stack_item(10),
process.get_stack_item(9),
process.get_stack_item(8),
process.get_stack_item(7),
process.get_stack_item(6),
process.get_stack_item(5),
process.get_stack_item(4),
process.get_stack_item(3),
process.get_stack_item(2),
process.get_stack_item(1),
process.get_stack_item(0),
];
let values = state[Rpo256::RATE_RANGE].to_vec();
Rpo256::apply_permutation(&mut state);
let key = RpoDigest::new(
state[Rpo256::DIGEST_RANGE]
.try_into()
.expect("failed to extract digest from state"),
);
advice_provider.insert_into_map(key.into(), values);
Ok(())
}
pub fn merge_merkle_nodes(
advice_provider: &mut impl AdviceProvider,
process: ProcessState,
err_ctx: &ErrorContext<'_, impl MastNodeExt>,
) -> Result<(), ExecutionError> {
let lhs = process.get_stack_word(1);
let rhs = process.get_stack_word(0);
advice_provider.merge_roots(lhs, rhs, err_ctx)?;
Ok(())
}
pub fn copy_merkle_node_to_adv_stack(
advice_provider: &mut impl AdviceProvider,
process: ProcessState,
err_ctx: &ErrorContext<'_, impl MastNodeExt>,
) -> Result<(), ExecutionError> {
let depth = process.get_stack_item(0);
let index = process.get_stack_item(1);
let root = [
process.get_stack_item(5),
process.get_stack_item(4),
process.get_stack_item(3),
process.get_stack_item(2),
];
let node = advice_provider.get_tree_node(root, &depth, &index, err_ctx)?;
advice_provider.push_stack(AdviceSource::Value(node[3]), err_ctx)?;
advice_provider.push_stack(AdviceSource::Value(node[2]), err_ctx)?;
advice_provider.push_stack(AdviceSource::Value(node[1]), err_ctx)?;
advice_provider.push_stack(AdviceSource::Value(node[0]), err_ctx)?;
Ok(())
}
pub fn copy_map_value_to_adv_stack(
advice_provider: &mut impl AdviceProvider,
process: ProcessState,
include_len: bool,
err_ctx: &ErrorContext<'_, impl MastNodeExt>,
) -> Result<(), ExecutionError> {
let key = [
process.get_stack_item(3),
process.get_stack_item(2),
process.get_stack_item(1),
process.get_stack_item(0),
];
advice_provider.push_stack(AdviceSource::Map { key, include_len }, err_ctx)?;
Ok(())
}
pub fn push_u64_div_result(
advice_provider: &mut impl AdviceProvider,
process: ProcessState,
err_ctx: &ErrorContext<'_, impl MastNodeExt>,
) -> Result<(), ExecutionError> {
let divisor = {
let divisor_hi = process.get_stack_item(0).as_int();
let divisor_lo = process.get_stack_item(1).as_int();
if divisor_hi > u32::MAX.into() {
return Err(ExecutionError::not_u32_value(Felt::new(divisor_hi), ZERO, err_ctx));
}
if divisor_lo > u32::MAX.into() {
return Err(ExecutionError::not_u32_value(Felt::new(divisor_lo), ZERO, err_ctx));
}
let divisor = (divisor_hi << 32) + divisor_lo;
if divisor == 0 {
return Err(ExecutionError::divide_by_zero(process.clk(), err_ctx));
}
divisor
};
let dividend = {
let dividend_hi = process.get_stack_item(2).as_int();
let dividend_lo = process.get_stack_item(3).as_int();
if dividend_hi > u32::MAX.into() {
return Err(ExecutionError::not_u32_value(Felt::new(dividend_hi), ZERO, err_ctx));
}
if dividend_lo > u32::MAX.into() {
return Err(ExecutionError::not_u32_value(Felt::new(dividend_lo), ZERO, err_ctx));
}
(dividend_hi << 32) + dividend_lo
};
let quotient = dividend / divisor;
let remainder = dividend - quotient * divisor;
let (q_hi, q_lo) = u64_to_u32_elements(quotient);
let (r_hi, r_lo) = u64_to_u32_elements(remainder);
advice_provider.push_stack(AdviceSource::Value(r_hi), err_ctx)?;
advice_provider.push_stack(AdviceSource::Value(r_lo), err_ctx)?;
advice_provider.push_stack(AdviceSource::Value(q_hi), err_ctx)?;
advice_provider.push_stack(AdviceSource::Value(q_lo), err_ctx)?;
Ok(())
}
pub fn push_falcon_mod_result(
advice_provider: &mut impl AdviceProvider,
process: ProcessState,
err_ctx: &ErrorContext<'_, impl MastNodeExt>,
) -> Result<(), ExecutionError> {
let dividend_hi = process.get_stack_item(0).as_int();
let dividend_lo = process.get_stack_item(1).as_int();
let dividend = (dividend_hi << 32) + dividend_lo;
let (quotient, remainder) = (dividend / M, dividend % M);
let (q_hi, q_lo) = u64_to_u32_elements(quotient);
let (r_hi, r_lo) = u64_to_u32_elements(remainder);
assert_eq!(r_hi, ZERO);
advice_provider.push_stack(AdviceSource::Value(r_lo), err_ctx)?;
advice_provider.push_stack(AdviceSource::Value(q_lo), err_ctx)?;
advice_provider.push_stack(AdviceSource::Value(q_hi), err_ctx)?;
Ok(())
}
pub fn push_ext2_inv_result(
advice_provider: &mut impl AdviceProvider,
process: ProcessState,
err_ctx: &ErrorContext<'_, impl MastNodeExt>,
) -> Result<(), ExecutionError> {
let coef0 = process.get_stack_item(1);
let coef1 = process.get_stack_item(0);
let element = QuadFelt::new(coef0, coef1);
if element == QuadFelt::ZERO {
return Err(ExecutionError::divide_by_zero(process.clk(), err_ctx));
}
let result = element.inv().to_base_elements();
advice_provider.push_stack(AdviceSource::Value(result[1]), err_ctx)?;
advice_provider.push_stack(AdviceSource::Value(result[0]), err_ctx)?;
Ok(())
}
pub fn push_ext2_intt_result(
advice_provider: &mut impl AdviceProvider,
process: ProcessState,
err_ctx: &ErrorContext<'_, impl MastNodeExt>,
) -> Result<(), ExecutionError> {
let output_size = process.get_stack_item(0).as_int() as usize;
let input_size = process.get_stack_item(1).as_int() as usize;
let input_start_ptr = process.get_stack_item(2).as_int();
if input_size <= 1 {
return Err(Ext2InttError::DomainSizeTooSmall(input_size as u64).into());
}
if !input_size.is_power_of_two() {
return Err(Ext2InttError::DomainSizeNotPowerOf2(input_size as u64).into());
}
if input_start_ptr >= u32::MAX as u64 {
return Err(Ext2InttError::InputStartAddressTooBig(input_start_ptr).into());
}
if input_start_ptr % WORD_SIZE as u64 != 0 {
return Err(Ext2InttError::InputStartNotWordAligned(input_start_ptr).into());
}
if input_size > u32::MAX as usize {
return Err(Ext2InttError::InputSizeTooBig(input_size as u64).into());
}
let input_end_ptr = input_start_ptr + (input_size * 2) as u64;
if input_end_ptr > u32::MAX as u64 {
return Err(Ext2InttError::InputEndAddressTooBig(input_end_ptr).into());
}
if output_size == 0 {
return Err(Ext2InttError::OutputSizeIsZero.into());
}
if output_size > input_size {
return Err(Ext2InttError::OutputSizeTooBig(output_size, input_size).into());
}
let mut poly = Vec::with_capacity(input_size);
for addr in ((input_start_ptr as u32)..(input_end_ptr as u32)).step_by(4) {
let word = process
.get_mem_word(process.ctx(), addr)?
.ok_or(Ext2InttError::UninitializedMemoryAddress(addr))?;
poly.push(QuadFelt::new(word[0], word[1]));
poly.push(QuadFelt::new(word[2], word[3]));
}
let twiddles = fft::get_inv_twiddles::<Felt>(input_size);
fft::interpolate_poly::<Felt, QuadFelt>(&mut poly, &twiddles);
for element in QuadFelt::slice_as_base_elements(&poly[..output_size]).iter().rev() {
advice_provider.push_stack(AdviceSource::Value(*element), err_ctx)?;
}
Ok(())
}
pub fn push_leading_zeros(
advice_provider: &mut impl AdviceProvider,
process: ProcessState,
err_ctx: &ErrorContext<'_, impl MastNodeExt>,
) -> Result<(), ExecutionError> {
push_transformed_stack_top(
advice_provider,
process,
|stack_top| Felt::from(stack_top.leading_zeros()),
err_ctx,
)
}
pub fn push_trailing_zeros(
advice_provider: &mut impl AdviceProvider,
process: ProcessState,
err_ctx: &ErrorContext<'_, impl MastNodeExt>,
) -> Result<(), ExecutionError> {
push_transformed_stack_top(
advice_provider,
process,
|stack_top| Felt::from(stack_top.trailing_zeros()),
err_ctx,
)
}
pub fn push_leading_ones(
advice_provider: &mut impl AdviceProvider,
process: ProcessState,
err_ctx: &ErrorContext<'_, impl MastNodeExt>,
) -> Result<(), ExecutionError> {
push_transformed_stack_top(
advice_provider,
process,
|stack_top| Felt::from(stack_top.leading_ones()),
err_ctx,
)
}
pub fn push_trailing_ones(
advice_provider: &mut impl AdviceProvider,
process: ProcessState,
err_ctx: &ErrorContext<'_, impl MastNodeExt>,
) -> Result<(), ExecutionError> {
push_transformed_stack_top(
advice_provider,
process,
|stack_top| Felt::from(stack_top.trailing_ones()),
err_ctx,
)
}
pub fn push_ilog2(
advice_provider: &mut impl AdviceProvider,
process: ProcessState,
err_ctx: &ErrorContext<'_, impl MastNodeExt>,
) -> Result<(), ExecutionError> {
let n = process.get_stack_item(0).as_int();
if n == 0 {
return Err(ExecutionError::log_argument_zero(process.clk(), err_ctx));
}
let ilog2 = Felt::from(n.ilog2());
advice_provider.push_stack(AdviceSource::Value(ilog2), err_ctx)?;
Ok(())
}
pub fn push_smtpeek_result(
advice_provider: &mut impl AdviceProvider,
process: ProcessState,
err_ctx: &ErrorContext<'_, impl MastNodeExt>,
) -> Result<(), ExecutionError> {
let empty_leaf = EmptySubtreeRoots::entry(SMT_DEPTH, SMT_DEPTH);
let key = process.get_stack_word(0);
let root = process.get_stack_word(1);
let node =
advice_provider.get_tree_node(root, &Felt::new(SMT_DEPTH as u64), &key[3], err_ctx)?;
if node == Word::from(empty_leaf) {
advice_provider.push_stack(AdviceSource::Word(Smt::EMPTY_VALUE), err_ctx)?;
} else {
let leaf_preimage = get_smt_leaf_preimage(advice_provider, node, err_ctx)?;
for (key_in_leaf, value_in_leaf) in leaf_preimage {
if key == key_in_leaf {
advice_provider.push_stack(AdviceSource::Word(value_in_leaf), err_ctx)?;
return Ok(());
}
}
advice_provider.push_stack(AdviceSource::Word(Smt::EMPTY_VALUE), err_ctx)?;
}
Ok(())
}
fn get_mem_addr_range(
process: ProcessState,
start_idx: usize,
end_idx: usize,
) -> Result<(u32, u32), ExecutionError> {
let start_addr = process.get_stack_item(start_idx).as_int();
let end_addr = process.get_stack_item(end_idx).as_int();
if start_addr > u32::MAX as u64 {
return Err(ExecutionError::MemoryError(MemoryError::address_out_of_bounds(
start_addr,
&ErrorContext::default(),
)));
}
if end_addr > u32::MAX as u64 {
return Err(ExecutionError::MemoryError(MemoryError::address_out_of_bounds(
end_addr,
&ErrorContext::default(),
)));
}
if start_addr > end_addr {
return Err(ExecutionError::MemoryError(MemoryError::InvalidMemoryRange {
start_addr,
end_addr,
}));
}
Ok((start_addr as u32, end_addr as u32))
}
fn u64_to_u32_elements(value: u64) -> (Felt, Felt) {
let hi = Felt::from((value >> 32) as u32);
let lo = Felt::from(value as u32);
(hi, lo)
}
fn push_transformed_stack_top<A: AdviceProvider>(
advice_provider: &mut A,
process: ProcessState,
f: impl FnOnce(u32) -> Felt,
err_ctx: &ErrorContext<'_, impl MastNodeExt>,
) -> Result<(), ExecutionError> {
let stack_top = process.get_stack_item(0);
let stack_top: u32 = stack_top
.as_int()
.try_into()
.map_err(|_| ExecutionError::not_u32_value(stack_top, ZERO, err_ctx))?;
let transformed_stack_top = f(stack_top);
advice_provider.push_stack(AdviceSource::Value(transformed_stack_top), err_ctx)?;
Ok(())
}
fn get_smt_leaf_preimage<A: AdviceProvider>(
advice_provider: &A,
node: Word,
err_ctx: &ErrorContext<'_, impl MastNodeExt>,
) -> Result<Vec<(Word, Word)>, ExecutionError> {
let node_bytes = RpoDigest::from(node);
let kv_pairs = advice_provider
.get_mapped_values(&node_bytes)
.ok_or(ExecutionError::smt_node_not_found(node, err_ctx))?;
if kv_pairs.len() % WORD_SIZE * 2 != 0 {
return Err(ExecutionError::smt_node_preimage_not_valid(node, kv_pairs.len(), err_ctx));
}
Ok(kv_pairs
.chunks_exact(WORD_SIZE * 2)
.map(|kv_chunk| {
let key = [kv_chunk[0], kv_chunk[1], kv_chunk[2], kv_chunk[3]];
let value = [kv_chunk[4], kv_chunk[5], kv_chunk[6], kv_chunk[7]];
(key, value)
})
.collect())
}