use std::collections::HashMap;
use cairo_vm::{
hint_processor::{
builtin_hint_processor::hint_utils::{
get_integer_from_var_name, get_ptr_from_var_name, insert_value_into_ap,
},
hint_processor_definition::HintReference,
},
serde::deserialize_program::ApTracking,
types::exec_scope::ExecutionScopes,
vm::{errors::hint_errors::HintError, vm_core::VirtualMachine},
Felt252,
};
use num_traits::ToPrimitive;
use super::types::PedersenMerkleInput;
use super::utils::get_program_input_value;
const AUTH_PATH: &str = "auth_path";
pub fn pedersen_merkle_load_input(
vm: &mut VirtualMachine,
exec_scopes: &mut ExecutionScopes,
ids_data: &HashMap<String, HintReference>,
ap_tracking: &ApTracking,
) -> Result<(), HintError> {
let pedersen_merkle_input: PedersenMerkleInput = get_program_input_value(exec_scopes)?;
let output = get_ptr_from_var_name("output", vm, ids_data, ap_tracking)?;
vm.insert_value(output, pedersen_merkle_input.height)?;
vm.insert_value((output + 1)?, pedersen_merkle_input.prev_leaf)?;
vm.insert_value((output + 2)?, pedersen_merkle_input.new_leaf)?;
vm.insert_value(
(output + 3)?,
Felt252::from_dec_str(&pedersen_merkle_input.node_index.to_string()).map_err(|_| {
HintError::CustomHint("Failed to convert node_index to Felt252.".into())
})?,
)?;
let reversed_path: Vec<Felt252> = pedersen_merkle_input.path.into_iter().rev().collect();
exec_scopes.insert_value(AUTH_PATH, reversed_path);
Ok(())
}
pub fn pedersen_merkle_verify_auth_path_len(
exec_scopes: &mut ExecutionScopes,
) -> Result<(), HintError> {
let auth_path: &Vec<Felt252> = exec_scopes.get_ref(AUTH_PATH)?;
if !auth_path.is_empty() {
return Err(HintError::CustomHint(
"Got too many values in auth_path.".into(),
));
}
Ok(())
}
pub fn pedersen_merkle_idx_parity_to_ap(
vm: &mut VirtualMachine,
ids_data: &HashMap<String, HintReference>,
ap_tracking: &ApTracking,
) -> Result<(), HintError> {
let index = get_integer_from_var_name("index", vm, ids_data, ap_tracking)?
.to_u128()
.ok_or_else(|| HintError::CustomHint("Failed to convert index to u128.".into()))?;
insert_value_into_ap(vm, (index % 2) as usize)
}
pub fn pedersen_merkle_update(
vm: &mut VirtualMachine,
exec_scopes: &mut ExecutionScopes,
ids_data: &HashMap<String, HintReference>,
ap_tracking: &ApTracking,
is_left_sibling: bool,
) -> Result<(), HintError> {
let auth_path: &mut Vec<Felt252> = exec_scopes.get_mut_ref(AUTH_PATH)?;
let sibling = auth_path.pop().ok_or_else(|| {
HintError::CustomHint("auth_path is empty. Cannot pop a sibling value.".into())
})?;
let prev_node_hash_ptr = get_ptr_from_var_name("prev_node_hash", vm, ids_data, ap_tracking)?;
let new_node_hash_ptr = get_ptr_from_var_name("new_node_hash", vm, ids_data, ap_tracking)?;
let is_left_sibling_usize = is_left_sibling as usize;
vm.insert_value((prev_node_hash_ptr + is_left_sibling_usize)?, sibling)?;
vm.insert_value((new_node_hash_ptr + is_left_sibling_usize)?, sibling)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::fill_ids_data_for_test;
use crate::{ProgramInput, PROGRAM_INPUT};
use cairo_vm::types::relocatable::{MaybeRelocatable, Relocatable};
use cairo_vm::vm::vm_core::VirtualMachine;
use num_bigint::BigUint;
use rstest::{fixture, rstest};
#[fixture]
fn pedersen_merkle_input() -> String {
let input = PedersenMerkleInput {
height: 3,
prev_leaf: Felt252::from(123456789u64),
new_leaf: Felt252::from(987654321u64),
node_index: 5,
path: vec![
Felt252::from(
BigUint::parse_bytes(b"1111111111111111111111111111111111111111", 10).unwrap(),
),
Felt252::from(
BigUint::parse_bytes(b"2222222222222222222222222222222222222222", 10).unwrap(),
),
Felt252::from(
BigUint::parse_bytes(b"3333333333333333333333333333333333333333", 10).unwrap(),
),
],
};
serde_json::to_string(&input).unwrap()
}
#[rstest]
fn test_pedersen_merkle_load_input(pedersen_merkle_input: String) {
let mut vm = VirtualMachine::new(false, false);
let mut exec_scopes = ExecutionScopes::new();
exec_scopes.insert_value(
PROGRAM_INPUT,
ProgramInput::Json(pedersen_merkle_input.clone()),
);
let ids_data = fill_ids_data_for_test(&["output"]);
let ap_tracking = ApTracking::new();
vm.add_memory_segment();
vm.add_memory_segment();
vm.add_memory_segment();
vm.set_fp(1);
vm.set_ap(2);
vm.load_data(Relocatable::from((1, 0)), &[MaybeRelocatable::from((2, 0))])
.expect("Failed to load initial data into VM memory.");
pedersen_merkle_load_input(&mut vm, &mut exec_scopes, &ids_data, &ap_tracking)
.expect("Hint execution failed unexpectedly");
let expected_pedersen_merkle_input: PedersenMerkleInput =
serde_json::from_str(&pedersen_merkle_input)
.expect("Failed to deserialize pedersen_merkle_input");
let got_height = *vm
.get_integer(Relocatable::from((2, 0)))
.expect("Failed to read height from VM memory")
.as_ref();
assert_eq!(
got_height,
Felt252::from(expected_pedersen_merkle_input.height),
"height mismatch in VM memory"
);
let auth_path: &Vec<Felt252> = exec_scopes.get_ref(AUTH_PATH).unwrap();
let expected_reversed_path: Vec<Felt252> = expected_pedersen_merkle_input
.path
.into_iter()
.rev()
.collect();
assert_eq!(auth_path, &expected_reversed_path);
}
#[test]
fn test_pedersen_merkle_verify_auth_path_len() {
let mut exec_scopes = ExecutionScopes::new();
exec_scopes.insert_value::<Vec<Felt252>>(AUTH_PATH, vec![]);
assert!(pedersen_merkle_verify_auth_path_len(&mut exec_scopes).is_ok());
exec_scopes.insert_value::<Vec<Felt252>>(AUTH_PATH, vec![Felt252::from(1)]);
let result = pedersen_merkle_verify_auth_path_len(&mut exec_scopes);
assert!(result.is_err());
match result.unwrap_err() {
HintError::CustomHint(ref msg) => assert!(msg.contains("Got too many values")),
_ => panic!("Expected CustomHint"),
}
}
#[rstest]
#[case(4)]
#[case(5)]
fn test_pedersen_merkle_idx_parity_to_ap(#[case] index: usize) {
let mut vm = VirtualMachine::new(false, false);
let ids_data = fill_ids_data_for_test(&["index"]);
let ap_tracking = ApTracking::new();
vm.add_memory_segment();
vm.add_memory_segment();
vm.set_fp(1);
vm.set_ap(1);
vm.load_data(Relocatable::from((1, 0)), &[MaybeRelocatable::from(index)])
.expect("Failed to load initial data into VM memory.");
pedersen_merkle_idx_parity_to_ap(&mut vm, &ids_data, &ap_tracking)
.expect("Hint execution failed unexpectedly");
assert_eq!(
vm.get_integer(Relocatable::from((1, 1))).unwrap().as_ref(),
&Felt252::from(index % 2)
);
}
#[test]
fn test_pedersen_merkle_update_empty_auth_path_errors() {
let mut vm = VirtualMachine::new(false, false);
let mut exec_scopes = ExecutionScopes::new();
let ids_data = fill_ids_data_for_test(&["prev_node_hash", "new_node_hash"]);
let ap_tracking = ApTracking::new();
exec_scopes.insert_value::<Vec<Felt252>>(AUTH_PATH, vec![]);
vm.add_memory_segment();
vm.add_memory_segment();
vm.set_fp(1);
vm.set_ap(1);
vm.load_data(
Relocatable::from((1, 0)),
&[
MaybeRelocatable::from((1, 0)),
MaybeRelocatable::from((1, 2)),
],
)
.expect("Failed to load initial data");
let result =
pedersen_merkle_update(&mut vm, &mut exec_scopes, &ids_data, &ap_tracking, true);
match result {
Err(HintError::CustomHint(msg)) => assert!(msg.contains("auth_path is empty")),
_ => panic!("Expected a CustomHint error when auth_path is empty"),
}
}
#[rstest]
#[case(true)]
#[case(false)]
fn test_pedersen_merkle_update(#[case] is_left_sibling: bool) {
let mut vm = VirtualMachine::new(false, false);
let mut exec_scopes = ExecutionScopes::new();
let ids_data = fill_ids_data_for_test(&["prev_node_hash", "new_node_hash"]);
let ap_tracking = ApTracking::new();
let sibling1 = Felt252::from(111u64);
let sibling2 = Felt252::from(222u64);
exec_scopes.insert_value::<Vec<Felt252>>(AUTH_PATH, vec![sibling1, sibling2]);
vm.add_memory_segment();
vm.add_memory_segment();
vm.add_memory_segment();
vm.set_fp(2);
vm.set_ap(2);
vm.load_data(
Relocatable::from((1, 0)),
&[
MaybeRelocatable::from((2, 0)),
MaybeRelocatable::from((2, 2)),
],
)
.expect("Failed to load initial data into VM memory.");
pedersen_merkle_update(
&mut vm,
&mut exec_scopes,
&ids_data,
&ap_tracking,
is_left_sibling,
)
.expect("Hint execution failed unexpectedly");
let hash_offset = if is_left_sibling { 1 } else { 0 };
assert_eq!(
vm.get_integer(Relocatable::from((2, hash_offset)))
.unwrap()
.as_ref(),
&Felt252::from(222u64)
);
assert_eq!(
vm.get_integer(Relocatable::from((2, 2 + hash_offset)))
.unwrap()
.as_ref(),
&Felt252::from(222u64)
);
}
}