use alloc::boxed::Box;
use miden_air::trace::chiplets::hasher::{MAX_MERKLE_DEPTH, STATE_WIDTH};
use miden_core::{
chiplets::{
eidos_compression,
hasher::{BLOCK_HI_RANGE, BLOCK_LO_RANGE, CV_RANGE},
},
crypto::merkle::MerklePath,
deferred::DEFERRED_AND_INIT_CV,
};
use super::{DOUBLE_WORD_SIZE, WORD_SIZE_FELT};
use crate::{
BaseHost, ContextId, ExecutionError, Felt, MemoryError, ONE, RowIndex, Word, ZERO,
errors::{
CryptoError, IoError, MapExecErrWithOpIdx, MerklePathVerificationFailedInner,
OperationError, PackageSourceDebugContext,
},
field::{BasedVectorSpace, QuadFelt},
processor::{
AdviceProviderInterface, HasherInterface, MemoryInterface, Processor, StackInterface,
SystemInterface,
},
tracer::{OperationHelperRegisters, Tracer},
};
#[cfg(test)]
mod tests;
#[inline(always)]
fn read_hasher_state<P: Processor>(processor: &P) -> [Felt; STATE_WIDTH] {
let double_word: [Felt; 8] = processor.stack().get_double_word(0);
let word: Word = processor.stack().get_word(8);
[
double_word[0],
double_word[1],
double_word[2],
double_word[3],
double_word[4],
double_word[5],
double_word[6],
double_word[7],
word[0],
word[1],
word[2],
word[3],
]
}
#[inline(always)]
pub(super) fn op_compress<P: Processor, T: Tracer>(
processor: &mut P,
tracer: &mut T,
) -> Result<OperationHelperRegisters, OperationError> {
let input_state = read_hasher_state(processor);
let (addr, output_state) = processor.hasher().compress(input_state)?;
let cv_next: Word =
output_state[CV_RANGE].try_into().expect("chaining-value slice has length 4");
processor.stack_mut().set_word(8, &cv_next);
tracer.record_hasher_compress(input_state, output_state);
Ok(OperationHelperRegisters::Compress { addr })
}
fn validate_merkle_depth(depth: Felt) -> Result<(), CryptoError> {
let depth_u64 = depth.as_canonical_u64();
if depth_u64 == 0 || depth_u64 > MAX_MERKLE_DEPTH.into() {
return Err(OperationError::MerkleDepthOutOfRange { depth }.into());
}
Ok(())
}
fn validate_merkle_path_length(
path: Option<&MerklePath>,
expected_depth: Felt,
) -> Result<(), CryptoError> {
if let Some(path) = path {
validate_materialized_merkle_path_length(path.nodes().len(), expected_depth)?;
}
Ok(())
}
fn validate_materialized_merkle_path_length(
path_len: usize,
expected_depth: Felt,
) -> Result<(), CryptoError> {
if u64::try_from(path_len).ok() != Some(expected_depth.as_canonical_u64()) {
return Err(
OperationError::InvalidMerklePathLength { path_len, depth: expected_depth }.into()
);
}
Ok(())
}
#[inline(always)]
pub(super) fn op_mpverify<P: Processor, T: Tracer>(
processor: &mut P,
err_code: Felt,
tracer: &mut T,
) -> Result<OperationHelperRegisters, CryptoError> {
let node = processor.stack().get_word(0);
let depth = processor.stack().get(4);
let index = processor.stack().get(5);
let root = processor.stack().get_word(6);
validate_merkle_depth(depth)?;
let path = processor.advice_provider().get_merkle_path(root, depth, index)?;
validate_merkle_path_length(path.as_ref(), depth)?;
tracer.record_hasher_build_merkle_root(node, path.as_ref(), depth, index, root);
let addr = processor.hasher().verify_merkle_root(root, node, path.as_ref(), index, || {
OperationError::MerklePathVerificationFailed {
inner: Box::new(MerklePathVerificationFailedInner {
value: node,
index,
root,
err_code,
err_msg: None,
}),
}
})?;
Ok(OperationHelperRegisters::MerklePath { addr, index })
}
#[inline(always)]
pub(super) fn op_mrupdate<P: Processor, T: Tracer>(
processor: &mut P,
tracer: &mut T,
) -> Result<OperationHelperRegisters, CryptoError> {
let old_value = processor.stack().get_word(0);
let depth = processor.stack().get(4);
let index = processor.stack().get(5);
let claimed_old_root = processor.stack().get_word(6);
let new_value = processor.stack().get_word(10);
validate_merkle_depth(depth)?;
let path = processor.advice_provider_mut().update_merkle_node(
claimed_old_root,
depth,
index,
new_value,
)?;
validate_merkle_path_length(path.as_ref(), depth)?;
let (addr, new_root) = processor.hasher().update_merkle_root(
claimed_old_root,
old_value,
new_value,
path.as_ref(),
index,
|| OperationError::MerklePathVerificationFailed {
inner: Box::new(MerklePathVerificationFailedInner {
value: old_value,
index,
root: claimed_old_root,
err_code: ZERO,
err_msg: None,
}),
},
)?;
tracer.record_hasher_update_merkle_root(
old_value,
new_value,
path.as_ref(),
depth,
index,
claimed_old_root,
new_root,
);
processor.stack_mut().set_word(0, &new_root);
Ok(OperationHelperRegisters::MerklePath { addr, index })
}
fn read_horner_eval_point<P: Processor, T: Tracer>(
processor: &mut P,
tracer: &mut T,
addr: Felt,
) -> Result<QuadFelt, IoError> {
let clk = processor.system().clock();
let ctx = processor.system().ctx();
let word = processor.memory_mut().read_word(ctx, addr, clk)?;
if word[2] != ZERO || word[3] != ZERO {
return Err(OperationError::InvalidHornerEvaluationPointWord {
ctx,
addr: addr.as_canonical_u64(),
}
.into());
}
tracer.record_memory_read_word(word, addr, ctx, clk);
Ok(QuadFelt::new([word[0], word[1]]))
}
#[inline(always)]
pub(super) fn op_horner_eval_base<P: Processor, T: Tracer>(
processor: &mut P,
tracer: &mut T,
) -> Result<OperationHelperRegisters, IoError> {
const ALPHA_ADDR_INDEX: usize = 13;
const ACC_LOW_INDEX: usize = 14;
const ACC_HIGH_INDEX: usize = 15;
let alpha_addr = processor.stack().get(ALPHA_ADDR_INDEX);
let alpha = read_horner_eval_point(processor, tracer, alpha_addr)?;
let coef: [Felt; 8] = processor.stack().get_double_word(0);
let c0 = QuadFelt::from(coef[0]);
let c1 = QuadFelt::from(coef[1]);
let c2 = QuadFelt::from(coef[2]);
let c3 = QuadFelt::from(coef[3]);
let c4 = QuadFelt::from(coef[4]);
let c5 = QuadFelt::from(coef[5]);
let c6 = QuadFelt::from(coef[6]);
let c7 = QuadFelt::from(coef[7]);
let acc_low = processor.stack().get(ACC_LOW_INDEX);
let acc_high = processor.stack().get(ACC_HIGH_INDEX);
let acc = QuadFelt::from_basis_coefficients_fn(|i: usize| [acc_low, acc_high][i]);
let tmp0 = (acc * alpha + c0) * alpha + c1;
let tmp1 = ((tmp0 * alpha + c2) * alpha + c3) * alpha + c4;
let acc_new = ((tmp1 * alpha + c5) * alpha + c6) * alpha + c7;
let acc_new_base_elements = acc_new.as_basis_coefficients_slice();
processor.stack_mut().set(ACC_HIGH_INDEX, acc_new_base_elements[1]);
processor.stack_mut().set(ACC_LOW_INDEX, acc_new_base_elements[0]);
Ok(OperationHelperRegisters::HornerEvalBase { alpha, tmp0, tmp1 })
}
#[inline(always)]
pub(super) fn op_horner_eval_ext<P: Processor, T: Tracer>(
processor: &mut P,
tracer: &mut T,
) -> Result<OperationHelperRegisters, IoError> {
const ALPHA_ADDR_INDEX: usize = 13;
const ACC_LOW_INDEX: usize = 14;
const ACC_HIGH_INDEX: usize = 15;
let coef: [QuadFelt; 4] = core::array::from_fn(|j| {
let lo = processor.stack().get(2 * j);
let hi = processor.stack().get(2 * j + 1);
QuadFelt::from_basis_coefficients_fn(|i: usize| [lo, hi][i])
});
let alpha_addr = processor.stack().get(ALPHA_ADDR_INDEX);
let alpha = read_horner_eval_point(processor, tracer, alpha_addr)?;
let acc_low = processor.stack().get(ACC_LOW_INDEX);
let acc_high = processor.stack().get(ACC_HIGH_INDEX);
let acc_old = QuadFelt::from_basis_coefficients_fn(|i: usize| [acc_low, acc_high][i]);
let acc_tmp = coef.iter().take(2).fold(acc_old, |acc, coef| *coef + alpha * acc);
let acc_new = coef.iter().skip(2).fold(acc_tmp, |acc, coef| *coef + alpha * acc);
let acc_new_base_elements = acc_new.as_basis_coefficients_slice();
processor.stack_mut().set(ACC_HIGH_INDEX, acc_new_base_elements[1]);
processor.stack_mut().set(ACC_LOW_INDEX, acc_new_base_elements[0]);
Ok(OperationHelperRegisters::HornerEvalExt { alpha, acc_tmp })
}
#[inline(always)]
pub(super) fn op_log_deferred<P: Processor, T: Tracer>(
processor: &mut P,
tracer: &mut T,
) -> Result<OperationHelperRegisters, OperationError> {
let statement_digest: Word = processor.stack().get_word(0);
let state_prev = processor.system().deferred_root();
let mut hasher_state: [Felt; STATE_WIDTH] = [ZERO; 12];
hasher_state[BLOCK_LO_RANGE].copy_from_slice(state_prev.as_slice());
hasher_state[BLOCK_HI_RANGE].copy_from_slice(statement_digest.as_slice());
hasher_state[CV_RANGE].copy_from_slice(DEFERRED_AND_INIT_CV.as_slice());
let (addr, output_state) = processor.hasher().compress(hasher_state)?;
let state_new: Word = output_state[CV_RANGE].try_into().unwrap();
processor.system_mut().log_deferred_statement(statement_digest, state_new)?;
processor.stack_mut().set_word(0, &state_new);
tracer.record_hasher_compress(hasher_state, output_state);
Ok(OperationHelperRegisters::LogDeferred { addr, state_prev })
}
#[derive(Debug)]
pub(super) enum AeadStreamError {
Memory(MemoryError),
Operation(OperationError),
}
impl From<MemoryError> for AeadStreamError {
fn from(err: MemoryError) -> Self {
Self::Memory(err)
}
}
impl From<OperationError> for AeadStreamError {
fn from(err: OperationError) -> Self {
Self::Operation(err)
}
}
impl<T> MapExecErrWithOpIdx<T> for Result<T, AeadStreamError> {
fn map_exec_err_with_op_idx(self) -> Result<T, ExecutionError> {
match self {
Ok(result) => Ok(result),
Err(AeadStreamError::Memory(err)) => {
Result::<T, MemoryError>::Err(err).map_exec_err_with_op_idx()
},
Err(AeadStreamError::Operation(err)) => {
Result::<T, OperationError>::Err(err).map_exec_err_with_op_idx()
},
}
}
fn map_exec_err_with_package_source_op_idx(
self,
context: Option<PackageSourceDebugContext<'_>>,
host: &(dyn BaseHost + '_),
op_idx: usize,
) -> Result<T, ExecutionError> {
match self {
Ok(result) => Ok(result),
Err(AeadStreamError::Memory(err)) => Result::<T, MemoryError>::Err(err)
.map_exec_err_with_package_source_op_idx(context, host, op_idx),
Err(AeadStreamError::Operation(err)) => Result::<T, OperationError>::Err(err)
.map_exec_err_with_package_source_op_idx(context, host, op_idx),
}
}
}
#[inline(always)]
pub(super) fn op_aead_stream<P: Processor, T: Tracer>(
processor: &mut P,
tracer: &mut T,
) -> Result<OperationHelperRegisters, AeadStreamError> {
const K_CTR_IDX: usize = 0;
const COUNTER_IDX: usize = 4;
const SRC_PTR_IDX: usize = 5;
const DST_PTR_IDX: usize = 6;
const REMAINING_IDX: usize = 7;
let ctx = processor.system().ctx();
let clk = processor.system().clock();
let k_ctr = processor.stack().get_word(K_CTR_IDX);
let counter = processor.stack().get(COUNTER_IDX);
let src_addr = processor.stack().get(SRC_PTR_IDX);
let dst_addr = processor.stack().get(DST_PTR_IDX);
let counter_next = counter + ONE;
let remaining_next = processor.stack().get(REMAINING_IDX) - ONE;
let input_state = [
counter, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, k_ctr[0], k_ctr[1], k_ctr[2], k_ctr[3],
];
let keystream = processor.hasher().compress_aead_xof(ctx, clk, input_state)?;
tracer.record_hasher_aead_xof(ctx, clk, input_state);
validate_aead_stream_addrs(src_addr, dst_addr, ctx, clk)?;
let plaintext0 = processor.memory_mut().read_word(ctx, src_addr, clk)?;
let plaintext0_dup = processor.memory_mut().read_word(ctx, src_addr, clk)?;
let plaintext1 = processor.memory_mut().read_word(ctx, src_addr + WORD_SIZE_FELT, clk)?;
let plaintext1_dup = processor.memory_mut().read_word(ctx, src_addr + WORD_SIZE_FELT, clk)?;
debug_assert_eq!(plaintext0, plaintext0_dup);
debug_assert_eq!(plaintext1, plaintext1_dup);
let plaintext = [plaintext0, plaintext1];
let mut ciphertext = [ZERO; 16];
for i in 0..8 {
let (lo, hi) = eidos_compression::unpack(plaintext[i / 4][i % 4]);
ciphertext[2 * i] = Felt::from_u32(lo ^ keystream[2 * i].as_canonical_u64() as u32);
ciphertext[2 * i + 1] = Felt::from_u32(hi ^ keystream[2 * i + 1].as_canonical_u64() as u32);
}
for word_idx in 0..4 {
let base = 4 * word_idx;
let word: Word = [
ciphertext[base],
ciphertext[base + 1],
ciphertext[base + 2],
ciphertext[base + 3],
]
.into();
processor.memory_mut().write_word(
ctx,
dst_addr + Felt::new_unchecked((4 * word_idx) as u64),
clk,
word,
)?;
}
tracer.record_aead_stream(plaintext, src_addr, keystream, ciphertext, dst_addr, ctx, clk);
processor.stack_mut().set(COUNTER_IDX, counter_next);
processor.stack_mut().set(SRC_PTR_IDX, src_addr + DOUBLE_WORD_SIZE);
processor.stack_mut().set(DST_PTR_IDX, dst_addr + Felt::new_unchecked(16));
processor.stack_mut().set(REMAINING_IDX, remaining_next);
Ok(OperationHelperRegisters::Empty)
}
#[inline(always)]
fn validate_aead_stream_addrs(
src_addr: Felt,
dst_addr: Felt,
ctx: ContextId,
clk: RowIndex,
) -> Result<(), MemoryError> {
let src_addr_u64 = src_addr.as_canonical_u64();
let dst_addr_u64 = dst_addr.as_canonical_u64();
const MEMORY_SIZE: u64 = (u32::MAX as u64) + 1;
u32::try_from(src_addr_u64)
.map_err(|_| MemoryError::AddressOutOfBounds { addr: src_addr_u64 })?;
let src_end = src_addr_u64 + 8;
if src_end > MEMORY_SIZE {
return Err(MemoryError::AddressOutOfBounds { addr: src_addr_u64 });
}
let dst_addr_u32 = u32::try_from(dst_addr_u64)
.map_err(|_| MemoryError::AddressOutOfBounds { addr: dst_addr_u64 })?;
let dst_end = dst_addr_u64 + 16;
if dst_end > MEMORY_SIZE {
return Err(MemoryError::AddressOutOfBounds { addr: dst_addr_u64 });
}
if src_addr_u64 < dst_end && dst_addr_u64 < src_end {
let offending_addr = [dst_addr_u32, dst_addr_u32 + 4, dst_addr_u32 + 8, dst_addr_u32 + 12]
.into_iter()
.find(|addr| u64::from(*addr) >= src_addr_u64 && u64::from(*addr) < src_end)
.unwrap_or(dst_addr_u32);
return Err(MemoryError::IllegalMemoryAccess {
ctx,
addr: offending_addr,
clk: Felt::from(clk),
});
}
Ok(())
}