use starknet_crypto::pedersen_hash;
use crate::Felt252;
use crate::stdlib::vec::Vec;
use crate::types::builtin_name::BuiltinName;
use crate::types::relocatable::MaybeRelocatable;
use crate::vm::runners::cairo_pie::StrippedProgram;
type HashFunction = fn(&Felt252, &Felt252) -> Felt252;
#[derive(thiserror_no_std::Error, Debug)]
pub enum HashChainError {
#[error("Data array must contain at least one element.")]
EmptyData,
}
#[derive(thiserror_no_std::Error, Debug)]
pub enum ProgramHashError {
#[error(transparent)]
HashChain(#[from] HashChainError),
#[error(
"Invalid program builtin: builtin name too long to be converted to field element: {0}"
)]
InvalidProgramBuiltin(&'static str),
#[error("Invalid program data: data contains relocatable(s)")]
InvalidProgramData,
}
fn compute_hash_chain<'a, I>(data: I, hash_func: HashFunction) -> Result<Felt252, HashChainError>
where
I: Iterator<Item = &'a Felt252> + DoubleEndedIterator,
{
match data.copied().rev().reduce(|x, y| hash_func(&y, &x)) {
Some(result) => Ok(result),
None => Err(HashChainError::EmptyData),
}
}
fn builtin_name_to_field_element(builtin_name: &BuiltinName) -> Result<Felt252, ProgramHashError> {
Ok(Felt252::from_bytes_be_slice(
builtin_name.to_str().as_bytes(),
))
}
fn maybe_relocatable_to_field_element(
maybe_relocatable: &MaybeRelocatable,
) -> Result<Felt252, ProgramHashError> {
maybe_relocatable
.get_int_ref()
.copied()
.ok_or(ProgramHashError::InvalidProgramData)
}
pub fn compute_program_hash_chain(
program: &StrippedProgram,
bootloader_version: usize,
) -> Result<Felt252, ProgramHashError> {
let program_main = program.main;
let program_main = Felt252::from(program_main);
let builtin_list: Result<Vec<Felt252>, _> = program
.builtins
.iter()
.map(builtin_name_to_field_element)
.collect();
let builtin_list = builtin_list?;
let program_header = vec![
Felt252::from(bootloader_version),
program_main,
Felt252::from(program.builtins.len()),
];
let program_data: Result<Vec<_>, _> = program
.data
.iter()
.map(maybe_relocatable_to_field_element)
.collect();
let program_data = program_data?;
let data_chain_len = program_header.len() + builtin_list.len() + program_data.len();
let data_chain_len_vec = vec![Felt252::from(data_chain_len)];
let data_chain = [
&data_chain_len_vec,
&program_header,
&builtin_list,
&program_data,
];
let hash = compute_hash_chain(data_chain.iter().flat_map(|&v| v.iter()), pedersen_hash)?;
Ok(hash)
}
#[cfg(test)]
mod tests {
#[cfg(feature = "std")]
use {crate::types::program::Program, rstest::rstest, std::path::PathBuf};
use starknet_crypto::pedersen_hash;
use super::*;
#[test]
fn test_compute_hash_chain() {
let data: Vec<Felt252> = vec![
Felt252::from(1u64),
Felt252::from(2u64),
Felt252::from(3u64),
];
let expected_hash = pedersen_hash(
&Felt252::from(1u64),
&pedersen_hash(&Felt252::from(2u64), &Felt252::from(3u64)),
);
let computed_hash = compute_hash_chain(data.iter(), pedersen_hash)
.expect("Hash computation failed unexpectedly");
assert_eq!(computed_hash, expected_hash);
}
#[cfg(feature = "std")]
#[rstest]
#[case::fibonacci(
"../cairo_programs/fibonacci.json",
"0x43b17e9592f33142246af4c06cd2b574b460dd1f718d76b51341175a62b220f"
)]
#[case::field_arithmetic(
"../cairo_programs/field_arithmetic.json",
"0x1031772ca86e618b058101af9c9a3277bac90712b750bcea1cc69d6c7cad8a7"
)]
#[case::keccak_copy_inputs(
"../cairo_programs/keccak_copy_inputs.json",
"0x49484fdc8e7a85061f9f21b7e21fe276d8a88c8e96681101a2518809e686c6c"
)]
fn test_compute_program_hash_chain(
#[case] program_path: PathBuf,
#[case] expected_program_hash: String,
) {
let program =
Program::from_file(program_path.as_path(), Some("main"))
.expect("Could not load program. Did you compile the sample programs? Run `make test` in the root directory.");
let stripped_program = program.get_stripped_program().unwrap();
let bootloader_version = 0;
let program_hash = compute_program_hash_chain(&stripped_program, bootloader_version)
.expect("Failed to compute program hash.");
let program_hash_hex = format!("{:#x}", program_hash);
assert_eq!(program_hash_hex, expected_program_hash);
}
}