use alloc::vec::Vec;
use miden_core::{
Felt, Word, ZERO,
chiplets::hasher::{STATE_WIDTH, apply_permutation},
crypto::merkle::{MerklePath, MerkleStore, MerkleTree, NodeIndex},
field::{BasedVectorSpace, QuadFelt},
program::StackInputs,
};
use proptest::prelude::*;
use super::{
op_crypto_stream, op_horner_eval_base, op_horner_eval_ext, op_hperm, op_mpverify, op_mrupdate,
validate_materialized_merkle_path_length, validate_merkle_depth, validate_merkle_path_length,
};
use crate::{
AdviceInputs, ContextId,
errors::{IoError, MemoryError, OperationError},
fast::{FastProcessor, NoopTracer},
processor::{Processor, SystemInterface},
};
const ALPHA_ADDR: u64 = 1000;
proptest! {
#[test]
fn test_op_hperm(
s0 in any::<u64>(),
s1 in any::<u64>(),
s2 in any::<u64>(),
s3 in any::<u64>(),
s4 in any::<u64>(),
s5 in any::<u64>(),
s6 in any::<u64>(),
s7 in any::<u64>(),
s8 in any::<u64>(),
s9 in any::<u64>(),
s10 in any::<u64>(),
s11 in any::<u64>(),
s12 in any::<u64>(),
s13 in any::<u64>(),
s14 in any::<u64>(),
s15 in any::<u64>(),
) {
let stack_inputs = [
felt(s0), felt(s1), felt(s2), felt(s3), felt(s4), felt(s5), felt(s6), felt(s7), felt(s8), felt(s9), felt(s10), felt(s11), felt(s12), felt(s13), felt(s14), felt(s15), ];
let mut processor = FastProcessor::new(StackInputs::new(&stack_inputs).unwrap());
let mut tracer = NoopTracer;
let expected_state = {
let mut expected_state = [
felt(s0),
felt(s1),
felt(s2),
felt(s3),
felt(s4),
felt(s5),
felt(s6),
felt(s7),
felt(s8),
felt(s9),
felt(s10),
felt(s11),
];
apply_permutation(&mut expected_state);
expected_state
};
let _ = op_hperm(&mut processor, &mut tracer);
processor.system_mut().increment_clock();
let stack = processor.stack_top();
for i in 0..STATE_WIDTH {
prop_assert_eq!(
stack[15 - i],
expected_state[i],
"mismatch at position {} (expected_state[{}])",
i,
i
);
}
prop_assert_eq!(stack[3], felt(s12), "s12 at position 12");
prop_assert_eq!(stack[2], felt(s13), "s13 at position 13");
prop_assert_eq!(stack[1], felt(s14), "s14 at position 14");
prop_assert_eq!(stack[0], felt(s15), "s15 at position 15");
}
}
proptest! {
#[test]
fn test_op_crypto_stream(
r0 in any::<u64>(),
r1 in any::<u64>(),
r2 in any::<u64>(),
r3 in any::<u64>(),
r4 in any::<u64>(),
r5 in any::<u64>(),
r6 in any::<u64>(),
r7 in any::<u64>(),
c0 in any::<u64>(),
c1 in any::<u64>(),
c2 in any::<u64>(),
c3 in any::<u64>(),
p0 in any::<u64>(),
p1 in any::<u64>(),
p2 in any::<u64>(),
p3 in any::<u64>(),
p4 in any::<u64>(),
p5 in any::<u64>(),
p6 in any::<u64>(),
p7 in any::<u64>(),
) {
let src_addr: u64 = 1000;
let dst_addr: u64 = 2000;
let stack_inputs = [
felt(r0), felt(r1), felt(r2), felt(r3), felt(r4), felt(r5), felt(r6), felt(r7), felt(c0), felt(c1), felt(c2), felt(c3), felt(src_addr), felt(dst_addr), ZERO, ZERO, ];
let mut processor = FastProcessor::new(StackInputs::new(&stack_inputs).unwrap());
let mut tracer = NoopTracer;
let plaintext_word1: Word = [felt(p0), felt(p1), felt(p2), felt(p3)].into();
let plaintext_word2: Word = [felt(p4), felt(p5), felt(p6), felt(p7)].into();
let clk = processor.clock();
processor.memory_mut().write_word(
ContextId::root(),
felt(src_addr),
clk,
plaintext_word1,
).unwrap();
processor.system_mut().increment_clock();
let clk = processor.clock();
processor.memory_mut().write_word(
ContextId::root(),
felt(src_addr + 4),
clk,
plaintext_word2,
).unwrap();
processor.system_mut().increment_clock();
let result = op_crypto_stream(&mut processor, &mut tracer);
prop_assert!(result.is_ok());
processor.system_mut().increment_clock();
let expected_cipher1 = [
felt(p0) + felt(r0),
felt(p1) + felt(r1),
felt(p2) + felt(r2),
felt(p3) + felt(r3),
];
let expected_cipher2 = [
felt(p4) + felt(r4),
felt(p5) + felt(r5),
felt(p6) + felt(r6),
felt(p7) + felt(r7),
];
let clk = processor.clock();
let cipher_word1 = processor.memory_mut().read_word(ContextId::root(), felt(dst_addr), clk).unwrap();
let cipher_word2 = processor.memory_mut().read_word(ContextId::root(), felt(dst_addr + 4), clk).unwrap();
prop_assert_eq!(cipher_word1[0], expected_cipher1[0], "cipher word1[0]");
prop_assert_eq!(cipher_word1[1], expected_cipher1[1], "cipher word1[1]");
prop_assert_eq!(cipher_word1[2], expected_cipher1[2], "cipher word1[2]");
prop_assert_eq!(cipher_word1[3], expected_cipher1[3], "cipher word1[3]");
prop_assert_eq!(cipher_word2[0], expected_cipher2[0], "cipher word2[0]");
prop_assert_eq!(cipher_word2[1], expected_cipher2[1], "cipher word2[1]");
prop_assert_eq!(cipher_word2[2], expected_cipher2[2], "cipher word2[2]");
prop_assert_eq!(cipher_word2[3], expected_cipher2[3], "cipher word2[3]");
let stack = processor.stack_top();
prop_assert_eq!(stack[15], expected_cipher1[0], "cipher1[0] at position 0");
prop_assert_eq!(stack[14], expected_cipher1[1], "cipher1[1] at position 1");
prop_assert_eq!(stack[13], expected_cipher1[2], "cipher1[2] at position 2");
prop_assert_eq!(stack[12], expected_cipher1[3], "cipher1[3] at position 3");
prop_assert_eq!(stack[11], expected_cipher2[0], "cipher2[0] at position 4");
prop_assert_eq!(stack[10], expected_cipher2[1], "cipher2[1] at position 5");
prop_assert_eq!(stack[9], expected_cipher2[2], "cipher2[2] at position 6");
prop_assert_eq!(stack[8], expected_cipher2[3], "cipher2[3] at position 7");
prop_assert_eq!(stack[7], felt(c0), "c0 at position 8");
prop_assert_eq!(stack[6], felt(c1), "c1 at position 9");
prop_assert_eq!(stack[5], felt(c2), "c2 at position 10");
prop_assert_eq!(stack[4], felt(c3), "c3 at position 11");
prop_assert_eq!(stack[3], felt(src_addr + 8), "src_ptr incremented");
prop_assert_eq!(stack[2], felt(dst_addr + 8), "dst_ptr incremented");
}
}
proptest! {
#[test]
fn test_op_horner_eval_base(
c0 in any::<u64>(),
c1 in any::<u64>(),
c2 in any::<u64>(),
c3 in any::<u64>(),
c4 in any::<u64>(),
c5 in any::<u64>(),
c6 in any::<u64>(),
c7 in any::<u64>(),
s8 in any::<u64>(),
s9 in any::<u64>(),
s10 in any::<u64>(),
s11 in any::<u64>(),
s12 in any::<u64>(),
alpha_0 in any::<u64>(),
alpha_1 in any::<u64>(),
acc_0 in any::<u64>(),
acc_1 in any::<u64>(),
) {
let stack_inputs = [
felt(c0), felt(c1), felt(c2), felt(c3), felt(c4), felt(c5), felt(c6), felt(c7), felt(s8), felt(s9), felt(s10), felt(s11), felt(s12), felt(ALPHA_ADDR), felt(acc_0), felt(acc_1), ];
let mut processor = FastProcessor::new(StackInputs::new(&stack_inputs).unwrap());
let mut tracer = NoopTracer;
let alpha_word: Word = [felt(alpha_0), felt(alpha_1), ZERO, ZERO].into();
let clk = processor.clock();
processor.memory_mut().write_word(
ContextId::root(),
felt(ALPHA_ADDR),
clk,
alpha_word,
).unwrap();
processor.system_mut().increment_clock();
let result = op_horner_eval_base(&mut processor, &mut tracer);
prop_assert!(result.is_ok());
processor.system_mut().increment_clock();
let alpha = QuadFelt::new([felt(alpha_0), felt(alpha_1)]);
let acc_old = QuadFelt::new([felt(acc_0), felt(acc_1)]);
let c0_q = QuadFelt::from(felt(c0));
let c1_q = QuadFelt::from(felt(c1));
let c2_q = QuadFelt::from(felt(c2));
let c3_q = QuadFelt::from(felt(c3));
let c4_q = QuadFelt::from(felt(c4));
let c5_q = QuadFelt::from(felt(c5));
let c6_q = QuadFelt::from(felt(c6));
let c7_q = QuadFelt::from(felt(c7));
let tmp0 = (acc_old * alpha + c0_q) * alpha + c1_q;
let tmp1 = ((tmp0 * alpha + c2_q) * alpha + c3_q) * alpha + c4_q;
let acc_new = ((tmp1 * alpha + c5_q) * alpha + c6_q) * alpha + c7_q;
let stack = processor.stack_top();
prop_assert_eq!(stack[15], felt(c0), "c0 at position 0 (top)");
prop_assert_eq!(stack[14], felt(c1), "c1 at position 1");
prop_assert_eq!(stack[13], felt(c2), "c2 at position 2");
prop_assert_eq!(stack[12], felt(c3), "c3 at position 3");
prop_assert_eq!(stack[11], felt(c4), "c4 at position 4");
prop_assert_eq!(stack[10], felt(c5), "c5 at position 5");
prop_assert_eq!(stack[9], felt(c6), "c6 at position 6");
prop_assert_eq!(stack[8], felt(c7), "c7 at position 7");
prop_assert_eq!(stack[7], felt(s8), "s8 at position 8");
prop_assert_eq!(stack[6], felt(s9), "s9 at position 9");
prop_assert_eq!(stack[5], felt(s10), "s10 at position 10");
prop_assert_eq!(stack[4], felt(s11), "s11 at position 11");
prop_assert_eq!(stack[3], felt(s12), "s12 at position 12");
prop_assert_eq!(stack[2], felt(ALPHA_ADDR), "alpha_addr at position 13");
let acc_new_base: &[Felt] = acc_new.as_basis_coefficients_slice();
prop_assert_eq!(stack[1], acc_new_base[0], "acc_low at position 14");
prop_assert_eq!(stack[0], acc_new_base[1], "acc_high at position 15");
}
#[test]
fn test_op_horner_eval_ext(
c0_0 in any::<u64>(),
c0_1 in any::<u64>(),
c1_0 in any::<u64>(),
c1_1 in any::<u64>(),
c2_0 in any::<u64>(),
c2_1 in any::<u64>(),
c3_0 in any::<u64>(),
c3_1 in any::<u64>(),
s8 in any::<u64>(),
s9 in any::<u64>(),
s10 in any::<u64>(),
s11 in any::<u64>(),
s12 in any::<u64>(),
alpha_0 in any::<u64>(),
alpha_1 in any::<u64>(),
acc_0 in any::<u64>(),
acc_1 in any::<u64>(),
) {
let stack_inputs = [
felt(c0_0), felt(c0_1), felt(c1_0), felt(c1_1), felt(c2_0), felt(c2_1), felt(c3_0), felt(c3_1), felt(s8), felt(s9), felt(s10), felt(s11), felt(s12), felt(ALPHA_ADDR), felt(acc_0), felt(acc_1), ];
let mut processor = FastProcessor::new(StackInputs::new(&stack_inputs).unwrap());
let mut tracer = NoopTracer;
let alpha_word: Word = [felt(alpha_0), felt(alpha_1), ZERO, ZERO].into();
let clk = processor.clock();
processor
.memory_mut()
.write_word(ContextId::root(), felt(ALPHA_ADDR), clk, alpha_word)
.unwrap();
processor.system_mut().increment_clock();
let helpers = op_horner_eval_ext(&mut processor, &mut tracer);
prop_assert!(helpers.is_ok());
let helpers = helpers.unwrap().to_user_op_helpers();
processor.system_mut().increment_clock();
let alpha = QuadFelt::new([felt(alpha_0), felt(alpha_1)]);
let acc_old = QuadFelt::new([felt(acc_0), felt(acc_1)]);
let c0 = QuadFelt::new([felt(c0_0), felt(c0_1)]);
let c1 = QuadFelt::new([felt(c1_0), felt(c1_1)]);
let c2 = QuadFelt::new([felt(c2_0), felt(c2_1)]);
let c3 = QuadFelt::new([felt(c3_0), felt(c3_1)]);
let coefficients = [c0, c1, c2, c3];
let acc_tmp = coefficients.iter().take(2).fold(acc_old, |acc, coef| *coef + alpha * acc);
let acc_new = coefficients.iter().skip(2).fold(acc_tmp, |acc, coef| *coef + alpha * acc);
let acc_tmp_base: &[Felt] = acc_tmp.as_basis_coefficients_slice();
prop_assert_eq!(&helpers[..2], &[felt(alpha_0), felt(alpha_1)]);
prop_assert_eq!(&helpers[4..], acc_tmp_base);
let stack = processor.stack_top();
prop_assert_eq!(stack[15], felt(c0_0), "c0_0 at position 0 (top, low)");
prop_assert_eq!(stack[14], felt(c0_1), "c0_1 at position 1 (high)");
prop_assert_eq!(stack[13], felt(c1_0), "c1_0 at position 2 (low)");
prop_assert_eq!(stack[12], felt(c1_1), "c1_1 at position 3 (high)");
prop_assert_eq!(stack[11], felt(c2_0), "c2_0 at position 4 (low)");
prop_assert_eq!(stack[10], felt(c2_1), "c2_1 at position 5 (high)");
prop_assert_eq!(stack[9], felt(c3_0), "c3_0 at position 6 (low)");
prop_assert_eq!(stack[8], felt(c3_1), "c3_1 at position 7 (high)");
prop_assert_eq!(stack[7], felt(s8), "s8 at position 8");
prop_assert_eq!(stack[6], felt(s9), "s9 at position 9");
prop_assert_eq!(stack[5], felt(s10), "s10 at position 10");
prop_assert_eq!(stack[4], felt(s11), "s11 at position 11");
prop_assert_eq!(stack[3], felt(s12), "s12 at position 12");
prop_assert_eq!(stack[2], felt(ALPHA_ADDR), "alpha_addr at position 13");
let acc_new_base: &[Felt] = acc_new.as_basis_coefficients_slice();
prop_assert_eq!(stack[1], acc_new_base[0], "acc_low at position 14");
prop_assert_eq!(stack[0], acc_new_base[1], "acc_high at position 15");
}
}
#[test]
fn horner_eval_ops_reject_nonzero_eval_point_padding() {
let stack_inputs = [ZERO; 16];
let mut processor = FastProcessor::new(StackInputs::new(&stack_inputs).unwrap());
let mut tracer = NoopTracer;
let clk = processor.clock();
processor
.memory_mut()
.write_word(ContextId::root(), ZERO, clk, [ZERO, ZERO, Felt::ONE, ZERO].into())
.unwrap();
assert!(matches!(
op_horner_eval_base(&mut processor, &mut tracer),
Err(IoError::Operation(OperationError::InvalidHornerEvaluationPointWord {
ctx,
addr,
})) if ctx == ContextId::root() && addr == 0
));
let mut processor = FastProcessor::new(StackInputs::new(&stack_inputs).unwrap());
let clk = processor.clock();
processor
.memory_mut()
.write_word(ContextId::root(), ZERO, clk, [ZERO, ZERO, ZERO, Felt::ONE].into())
.unwrap();
assert!(matches!(
op_horner_eval_ext(&mut processor, &mut tracer),
Err(IoError::Operation(OperationError::InvalidHornerEvaluationPointWord {
ctx,
addr,
})) if ctx == ContextId::root() && addr == 0
));
}
#[test]
fn horner_eval_base_rejects_unaligned_eval_point_address() {
const UNALIGNED_ADDR: u32 = 2;
let mut stack_inputs = [ZERO; 16];
stack_inputs[13] = Felt::from_u32(UNALIGNED_ADDR);
let mut processor = FastProcessor::new(StackInputs::new(&stack_inputs).unwrap());
let mut tracer = NoopTracer;
assert!(matches!(
op_horner_eval_base(&mut processor, &mut tracer),
Err(IoError::Memory(MemoryError::UnalignedWordAccess { addr, ctx }))
if addr == UNALIGNED_ADDR && ctx == ContextId::root()
));
}
proptest! {
#[test]
fn test_op_mpverify(
l0 in any::<u64>(),
l1 in any::<u64>(),
l2 in any::<u64>(),
l3 in any::<u64>(),
l4 in any::<u64>(),
l5 in any::<u64>(),
l6 in any::<u64>(),
l7 in any::<u64>(),
leaf_idx in 0u64..8,
) {
let leaves: Vec<Word> = [l0, l1, l2, l3, l4, l5, l6, l7]
.iter()
.map(|&v| init_node(v))
.collect();
let tree = MerkleTree::new(&leaves).unwrap();
let store = MerkleStore::from(&tree);
let root = tree.root();
let node = leaves[leaf_idx as usize];
let depth = tree.depth() as u64;
let advice_inputs = AdviceInputs::default().with_merkle_store(store);
let stack_inputs = [
node[0], node[1], node[2], node[3], felt(depth), felt(leaf_idx), root[0], root[1], root[2], root[3], ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ];
let mut processor = FastProcessor::new(StackInputs::new(&stack_inputs).unwrap())
.with_advice(advice_inputs).expect("advice inputs should fit advice map limits");
let mut tracer = NoopTracer;
let result = op_mpverify(&mut processor, ZERO, &mut tracer);
prop_assert!(result.is_ok(), "op_mpverify failed: {:?}", result.err());
processor.system_mut().increment_clock();
let stack = processor.stack_top();
prop_assert_eq!(stack[15], node[0], "node[0] at position 0");
prop_assert_eq!(stack[14], node[1], "node[1] at position 1");
prop_assert_eq!(stack[13], node[2], "node[2] at position 2");
prop_assert_eq!(stack[12], node[3], "node[3] at position 3");
prop_assert_eq!(stack[11], felt(depth), "depth at position 4");
prop_assert_eq!(stack[10], felt(leaf_idx), "index at position 5");
prop_assert_eq!(stack[9], root[0], "root[0] at position 6");
prop_assert_eq!(stack[8], root[1], "root[1] at position 7");
prop_assert_eq!(stack[7], root[2], "root[2] at position 8");
prop_assert_eq!(stack[6], root[3], "root[3] at position 9");
}
#[test]
fn test_op_mrupdate(
l0 in any::<u64>(),
l1 in any::<u64>(),
l2 in any::<u64>(),
l3 in any::<u64>(),
l4 in any::<u64>(),
l5 in any::<u64>(),
l6 in any::<u64>(),
l7 in any::<u64>(),
new_leaf_value in any::<u64>(),
leaf_idx in 0u64..8,
) {
let leaves: Vec<Word> = [l0, l1, l2, l3, l4, l5, l6, l7]
.iter()
.map(|&v| init_node(v))
.collect();
let new_leaf = init_node(new_leaf_value);
let mut new_leaves = leaves.clone();
new_leaves[leaf_idx as usize] = new_leaf;
let tree = MerkleTree::new(&leaves).unwrap();
let new_tree = MerkleTree::new(&new_leaves).unwrap();
let store = MerkleStore::from(&tree);
let old_root = tree.root();
let old_node = leaves[leaf_idx as usize];
let depth = tree.depth() as u64;
let expected_new_root = new_tree.root();
let advice_inputs = AdviceInputs::default().with_merkle_store(store);
let stack_inputs = [
old_node[0], old_node[1], old_node[2], old_node[3], felt(depth), felt(leaf_idx), old_root[0], old_root[1], old_root[2], old_root[3], new_leaf[0], new_leaf[1], new_leaf[2], new_leaf[3], ZERO, ZERO, ];
let mut processor = FastProcessor::new(StackInputs::new(&stack_inputs).unwrap())
.with_advice(advice_inputs).expect("advice inputs should fit advice map limits");
let mut tracer = NoopTracer;
let result = op_mrupdate(&mut processor, &mut tracer);
prop_assert!(result.is_ok(), "op_mrupdate failed: {:?}", result.err());
processor.system_mut().increment_clock();
let stack = processor.stack_top();
prop_assert_eq!(stack[15], expected_new_root[0], "new_root[0] at position 0");
prop_assert_eq!(stack[14], expected_new_root[1], "new_root[1] at position 1");
prop_assert_eq!(stack[13], expected_new_root[2], "new_root[2] at position 2");
prop_assert_eq!(stack[12], expected_new_root[3], "new_root[3] at position 3");
prop_assert_eq!(stack[11], felt(depth), "depth at position 4");
prop_assert_eq!(stack[10], felt(leaf_idx), "index at position 5");
prop_assert_eq!(stack[9], old_root[0], "old_root[0] at position 6");
prop_assert_eq!(stack[8], old_root[1], "old_root[1] at position 7");
prop_assert_eq!(stack[7], old_root[2], "old_root[2] at position 8");
prop_assert_eq!(stack[6], old_root[3], "old_root[3] at position 9");
prop_assert_eq!(stack[5], new_leaf[0], "new_leaf[0] at position 10");
prop_assert_eq!(stack[4], new_leaf[1], "new_leaf[1] at position 11");
prop_assert_eq!(stack[3], new_leaf[2], "new_leaf[2] at position 12");
prop_assert_eq!(stack[2], new_leaf[3], "new_leaf[3] at position 13");
assert!(processor.advice_provider().has_merkle_root(tree.root()));
assert!(processor.advice_provider().has_merkle_root(new_tree.root()));
}
}
#[test]
fn test_op_mrupdate_merge_subtree() {
let leaves_a: Vec<Word> = (0..16).map(init_node).collect();
let leaves_b: Vec<Word> = (100..104).map(init_node).collect();
let mut leaves_c = leaves_a.clone();
leaves_c[4..8].copy_from_slice(&leaves_b);
let tree_a = MerkleTree::new(&leaves_a).unwrap();
let tree_b = MerkleTree::new(&leaves_b).unwrap();
let tree_c = MerkleTree::new(&leaves_c).unwrap();
let mut store = MerkleStore::default();
store.extend(tree_a.inner_nodes());
store.extend(tree_b.inner_nodes());
let target_depth = 2_u64;
let target_index = 1_u64;
let target_node = tree_b.root();
let expected_root = tree_c.root();
let replaced_root = tree_a.root();
let replaced_node = store
.get_node(replaced_root, NodeIndex::new(target_depth as u8, target_index).unwrap())
.unwrap();
let advice_inputs = AdviceInputs::default().with_merkle_store(store);
let stack_inputs = [
replaced_node[0], replaced_node[1], replaced_node[2], replaced_node[3], felt(target_depth), felt(target_index), replaced_root[0], replaced_root[1], replaced_root[2], replaced_root[3], target_node[0], target_node[1], target_node[2], target_node[3], ZERO, ZERO, ];
let mut processor = FastProcessor::new(StackInputs::new(&stack_inputs).unwrap())
.with_advice(advice_inputs)
.expect("advice inputs should fit advice map limits");
let mut tracer = NoopTracer;
let result = op_mrupdate(&mut processor, &mut tracer);
assert!(result.is_ok(), "op_mrupdate failed: {:?}", result.err());
processor.system_mut().increment_clock();
let stack = processor.stack_top();
assert_eq!(stack[15], expected_root[0], "expected_root[0] at position 0");
assert_eq!(stack[14], expected_root[1], "expected_root[1] at position 1");
assert_eq!(stack[13], expected_root[2], "expected_root[2] at position 2");
assert_eq!(stack[12], expected_root[3], "expected_root[3] at position 3");
assert_eq!(stack[11], felt(target_depth), "depth at position 4");
assert_eq!(stack[10], felt(target_index), "index at position 5");
assert_eq!(stack[9], replaced_root[0], "replaced_root[0] at position 6");
assert_eq!(stack[8], replaced_root[1], "replaced_root[1] at position 7");
assert_eq!(stack[7], replaced_root[2], "replaced_root[2] at position 8");
assert_eq!(stack[6], replaced_root[3], "replaced_root[3] at position 9");
assert_eq!(stack[5], target_node[0], "target_node[0] at position 10");
assert_eq!(stack[4], target_node[1], "target_node[1] at position 11");
assert_eq!(stack[3], target_node[2], "target_node[2] at position 12");
assert_eq!(stack[2], target_node[3], "target_node[3] at position 13");
assert!(processor.advice_provider().has_merkle_root(expected_root));
}
mod merkle_validation {
use miden_air::trace::chiplets::hasher::MAX_MERKLE_DEPTH;
use miden_core::field::PrimeCharacteristicRing;
use super::*;
use crate::errors::{CryptoError, OperationError};
#[test]
fn validator_accepts_supported_boundaries() {
for depth in [1, u64::from(MAX_MERKLE_DEPTH)] {
validate_merkle_depth(felt(depth))
.unwrap_or_else(|err| panic!("depth {depth} must be accepted: {err}"));
}
}
#[test]
fn materialized_path_length_must_match_depth() {
let path = MerklePath::new(vec![init_node(1); 3]);
validate_merkle_path_length(None, felt(3)).expect("an absent path is allowed");
validate_merkle_path_length(Some(&path), felt(3)).expect("path length should match");
let err = validate_merkle_path_length(Some(&path), felt(2))
.expect_err("a materialized path must match the stack depth");
assert!(matches!(
err,
CryptoError::Operation(OperationError::InvalidMerklePathLength {
path_len: 3,
depth,
}) if depth == felt(2)
));
}
#[test]
fn materialized_path_length_is_not_narrowed() {
let err = validate_materialized_merkle_path_length(257, felt(1))
.expect_err("the actual path length must not wrap to the expected depth");
assert!(matches!(
err,
CryptoError::Operation(OperationError::InvalidMerklePathLength {
path_len: 257,
depth,
}) if depth == felt(1)
));
}
#[test]
fn operations_reject_out_of_range_depths() {
let leaves: Vec<Word> = (1..=8).map(init_node).collect();
let tree = MerkleTree::new(&leaves).unwrap();
let root = tree.root();
let advice = AdviceInputs::default().with_merkle_store(MerkleStore::from(&tree));
let first_unsupported_depth = felt(u64::from(MAX_MERKLE_DEPTH) + 1);
for depth in [ZERO, first_unsupported_depth, Felt::NEG_ONE] {
let node = if depth == ZERO { root } else { leaves[0] };
let stack = mpverify_stack(node, depth, root);
let mut processor = FastProcessor::new(StackInputs::new(&stack).unwrap())
.with_advice(advice.clone())
.expect("advice inputs should fit");
let err = op_mpverify(&mut processor, ZERO, &mut NoopTracer)
.expect_err(&alloc::format!("MPVERIFY must reject depth {depth}"));
assert_merkle_depth_error(err, depth, "MPVERIFY");
let stack = mrupdate_stack(leaves[0], depth, root, init_node(99));
let mut processor = FastProcessor::new(StackInputs::new(&stack).unwrap())
.with_advice(advice.clone())
.expect("advice inputs should fit");
let err = op_mrupdate(&mut processor, &mut NoopTracer)
.expect_err(&alloc::format!("MRUPDATE must reject depth {depth}"));
assert_merkle_depth_error(err, depth, "MRUPDATE");
}
}
#[test]
fn rejected_mrupdate_does_not_mutate_the_advice_tree() {
let leaves: Vec<Word> = (1..=8).map(init_node).collect();
let tree = MerkleTree::new(&leaves).unwrap();
let root = tree.root();
let new_value = init_node(99);
let mut updated_leaves = leaves.clone();
updated_leaves[0] = new_value;
let updated_root = MerkleTree::new(updated_leaves).unwrap().root();
let stack = mrupdate_stack(leaves[0], ZERO, root, new_value);
let mut processor = FastProcessor::new(StackInputs::new(&stack).unwrap())
.with_advice(AdviceInputs::default().with_merkle_store(MerkleStore::from(&tree)))
.expect("advice inputs should fit");
let err =
op_mrupdate(&mut processor, &mut NoopTracer).expect_err("depth zero must be rejected");
assert_merkle_depth_error(err, ZERO, "MRUPDATE");
assert!(processor.advice_provider().has_merkle_root(root));
assert!(
!processor.advice_provider().has_merkle_root(updated_root),
"rejected MRUPDATE inserted the updated tree into the advice store"
);
}
fn mpverify_stack(node: Word, depth: Felt, root: Word) -> [Felt; 16] {
[
node[0], node[1], node[2], node[3], depth, ZERO, root[0], root[1], root[2], root[3],
ZERO, ZERO, ZERO, ZERO, ZERO, ZERO,
]
}
fn mrupdate_stack(old: Word, depth: Felt, root: Word, new: Word) -> [Felt; 16] {
[
old[0], old[1], old[2], old[3], depth, ZERO, root[0], root[1], root[2], root[3],
new[0], new[1], new[2], new[3], ZERO, ZERO,
]
}
fn assert_merkle_depth_error(error: CryptoError, expected_depth: Felt, operation: &str) {
match error {
CryptoError::Operation(OperationError::MerkleDepthOutOfRange { depth }) => {
assert_eq!(depth, expected_depth, "{operation} reported the wrong depth");
},
other => panic!(
"{operation} rejected depth {expected_depth} for the wrong reason: {other:?}"
),
}
}
}
fn felt(value: u64) -> Felt {
Felt::new_unchecked(value % Felt::ORDER)
}
fn init_node(value: u64) -> Word {
[felt(value), ZERO, ZERO, ZERO].into()
}