diff --git a/Cargo.lock b/Cargo.lock index 4f34706b175..3558980bf88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1924,7 +1924,10 @@ version = "0.15.0-rc.0" dependencies = [ "apollo_infra_utils", "cairo-vm", + "serde", "serde_json", + "starknet-types-core", + "thiserror 1.0.69", ] [[package]] diff --git a/crates/apollo_starknet_os_program/Cargo.toml b/crates/apollo_starknet_os_program/Cargo.toml index 766bbaaf95f..ff53d25c7e2 100644 --- a/crates/apollo_starknet_os_program/Cargo.toml +++ b/crates/apollo_starknet_os_program/Cargo.toml @@ -15,7 +15,10 @@ workspace = true [dependencies] cairo-vm.workspace = true +serde = { workspace = true, features = ["derive"] } serde_json.workspace = true +starknet-types-core.workspace = true +thiserror.workspace = true [build-dependencies] apollo_infra_utils.workspace = true diff --git a/crates/apollo_starknet_os_program/src/lib.rs b/crates/apollo_starknet_os_program/src/lib.rs index 2ec4167af8e..0c3a9f28a83 100644 --- a/crates/apollo_starknet_os_program/src/lib.rs +++ b/crates/apollo_starknet_os_program/src/lib.rs @@ -4,6 +4,10 @@ use std::sync::LazyLock; use cairo_vm::types::program::Program; +use crate::program_hash::ProgramHash; + +pub mod program_hash; + #[cfg(feature = "dump_source_files")] pub static CAIRO_FILES_MAP: LazyLock> = LazyLock::new(|| { serde_json::from_str(include_str!(concat!(env!("OUT_DIR"), "/cairo_files_map.json"))) @@ -17,3 +21,8 @@ pub static OS_PROGRAM: LazyLock = LazyLock::new(|| { ) .expect("Failed to load the OS bytes.") }); + +pub static PROGRAM_HASH: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("program_hash.json")) + .expect("Failed to deserialize program_hash.json.") +}); diff --git a/crates/apollo_starknet_os_program/src/program_hash.json b/crates/apollo_starknet_os_program/src/program_hash.json new file mode 100644 index 00000000000..fd91955fb07 --- /dev/null +++ b/crates/apollo_starknet_os_program/src/program_hash.json @@ -0,0 +1,3 @@ +{ + "os": "0x1a2e53ac2b85c63107ef7232a6b96ee3d7b7a3fc25e0ba6e223e01a79e3739b" +} \ No newline at end of file diff --git a/crates/apollo_starknet_os_program/src/program_hash.rs b/crates/apollo_starknet_os_program/src/program_hash.rs new file mode 100644 index 00000000000..773b2db834f --- /dev/null +++ b/crates/apollo_starknet_os_program/src/program_hash.rs @@ -0,0 +1,76 @@ +use cairo_vm::types::builtin_name::BuiltinName; +use cairo_vm::types::errors::program_errors::ProgramError; +use serde::{Deserialize, Serialize}; +use starknet_types_core::felt::Felt; +use starknet_types_core::hash::{Pedersen, StarkHash}; + +use crate::OS_PROGRAM; + +#[cfg(test)] +#[path = "program_hash_test.rs"] +pub mod test; + +#[derive(thiserror::Error, Debug)] +pub enum ProgramHashError { + #[error("Builtin name of builtin {builtin} is too long: '{name}'.")] + BuiltinNameTooLong { builtin: BuiltinName, name: String }, + #[error(transparent)] + Program(#[from] ProgramError), + #[error("Program data contains unexpected relocatable.")] + UnexpectedRelocatable, +} + +#[derive(Deserialize, Serialize)] +pub struct ProgramHash { + os: Felt, +} + +const BOOTLOADER_VERSION: u8 = 0; + +fn pedersen_hash_chain(data: Vec) -> Felt { + let length = Felt::from(data.len()); + vec![length] + .into_iter() + .chain(data) + .rev() + .reduce(|x, y| Pedersen::hash(&y, &x)) + .expect("Hash data chain is not empty.") +} + +pub fn compute_os_program_hash() -> Result { + let builtins = OS_PROGRAM + .iter_builtins() + .map(|builtin| { + let builtin_bytes = builtin.to_str().to_string().into_bytes(); + if builtin_bytes.len() > 32 { + Err(ProgramHashError::BuiltinNameTooLong { + builtin: *builtin, + name: builtin.to_str().to_string(), + }) + } else { + let mut padded_builtin_bytes = [0].repeat(32 - builtin_bytes.len()); + padded_builtin_bytes.extend(builtin_bytes); + Ok(Felt::from_bytes_be( + padded_builtin_bytes + .as_slice() + .try_into() + .expect("Padded bytes are 32 bytes long."), + )) + } + }) + .collect::, _>>()?; + let program_header = vec![ + Felt::from(BOOTLOADER_VERSION), + // TODO(Dori): When [available](https://github.com/lambdaclass/cairo-vm/pull/2101), use the + // Program::get_main() getter instead of the get_stripped_program() method. + Felt::from(OS_PROGRAM.get_stripped_program()?.main), + Felt::from(builtins.len()), + ]; + let data = OS_PROGRAM + .iter_data() + .map(|data| data.get_int().ok_or(ProgramHashError::UnexpectedRelocatable)) + .collect::, _>>()?; + + let data_chain: Vec = program_header.into_iter().chain(builtins).chain(data).collect(); + Ok(pedersen_hash_chain(data_chain)) +} diff --git a/crates/apollo_starknet_os_program/src/program_hash_test.rs b/crates/apollo_starknet_os_program/src/program_hash_test.rs new file mode 100644 index 00000000000..2327492bd71 --- /dev/null +++ b/crates/apollo_starknet_os_program/src/program_hash_test.rs @@ -0,0 +1,7 @@ +use crate::program_hash::compute_os_program_hash; +use crate::PROGRAM_HASH; + +#[test] +fn test_program_hash() { + assert_eq!(compute_os_program_hash().unwrap(), PROGRAM_HASH.os) +}