use std::collections::HashMap;
use std::path::PathBuf;
use cairo_vm::hint_processor::builtin_hint_processor::hint_utils::get_ptr_from_var_name;
use cairo_vm::hint_processor::hint_processor_definition::HintReference;
use cairo_vm::serde::deserialize_program::ApTracking;
use cairo_vm::types::exec_scope::ExecutionScopes;
use cairo_vm::vm::errors::hint_errors::HintError;
use cairo_vm::vm::vm_core::VirtualMachine;
use starknet_types_core::felt::Felt;
use crate::hints::types::PrivacySimpleBootloaderInput;
use crate::hints::vars;
use crate::hints::SIMPLE_BOOTLOADER_INPUT;
use super::utils::get_program_input_value;
pub fn load_privacy_simple_bootloader_input(
exec_scopes: &mut ExecutionScopes,
) -> Result<(), HintError> {
let privacy_input: PrivacySimpleBootloaderInput = get_program_input_value(exec_scopes)?;
exec_scopes.insert_value(
SIMPLE_BOOTLOADER_INPUT,
privacy_input.simple_bootloader_input,
);
exec_scopes.insert_value(
vars::OUTPUT_PREIMAGE_DUMP_PATH,
privacy_input.output_preimage_dump_path,
);
Ok(())
}
pub fn dump_privacy_simple_bootloader_output_preimage(
vm: &mut VirtualMachine,
exec_scopes: &mut ExecutionScopes,
ids_data: &HashMap<String, HintReference>,
ap_tracking: &ApTracking,
) -> Result<(), HintError> {
let output_start = get_ptr_from_var_name("simple_bl_output_start", vm, ids_data, ap_tracking)?;
let output_end = get_ptr_from_var_name("simple_bl_output", vm, ids_data, ap_tracking)?;
let size = (output_end - output_start)?;
let elements: Vec<Felt> = vm
.get_integer_range(output_start, size)?
.into_iter()
.map(|v| v.into_owned())
.collect();
let dump_path: PathBuf = exec_scopes.get(vars::OUTPUT_PREIMAGE_DUMP_PATH)?;
let json = serde_json::to_string_pretty(&elements).map_err(|e| {
HintError::CustomHint(format!("Failed to serialize output preimage: {e}").into())
})?;
std::fs::write(&dump_path, json).map_err(|e| {
HintError::CustomHint(
format!("Failed to write output preimage to {dump_path:?}: {e}").into(),
)
})?;
Ok(())
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use cairo_vm::math_utils::signed_felt;
use cairo_vm::serde::deserialize_program::ApTracking;
use cairo_vm::types::exec_scope::ExecutionScopes;
use cairo_vm::types::layout_name::LayoutName;
use cairo_vm::types::program::Program;
use cairo_vm::types::relocatable::{MaybeRelocatable, Relocatable};
use cairo_vm::vm::vm_core::VirtualMachine;
use cairo_vm::Felt252;
use starknet_types_core::felt::Felt;
use starknet_types_core::hash::Blake2Felt252;
use super::*;
use crate::hints::program_hash::compute_program_hash_chain;
use crate::hints::types::{HashFunc, SimpleBootloaderInput};
use crate::test_utils::prepare_non_continuous_ids_data_for_test;
use crate::types::RunMode;
use crate::{cairo_run_program, ProgramInput, PROGRAM_INPUT};
#[test]
fn test_load_privacy_simple_bootloader_input() {
let mut exec_scopes = ExecutionScopes::new();
let input_json = r#"{
"tasks": [],
"single_page": true,
"fact_topologies_path": null,
"output_preimage_dump_path": "/tmp/test_dump.json"
}"#;
exec_scopes.insert_value(PROGRAM_INPUT, ProgramInput::Json(input_json.to_string()));
load_privacy_simple_bootloader_input(&mut exec_scopes).expect("Hint failed unexpectedly");
let simple_bl_input: SimpleBootloaderInput = exec_scopes
.get(vars::SIMPLE_BOOTLOADER_INPUT)
.expect("SimpleBootloaderInput not found in exec scopes");
assert!(simple_bl_input.tasks.is_empty());
assert!(simple_bl_input.single_page);
let dump_path: PathBuf = exec_scopes
.get(vars::OUTPUT_PREIMAGE_DUMP_PATH)
.expect("OUTPUT_PREIMAGE_DUMP_PATH not found in exec scopes");
assert_eq!(dump_path, PathBuf::from("/tmp/test_dump.json"));
}
#[test]
fn test_dump_privacy_simple_bootloader_output_preimage() {
let expected_felts: Vec<Felt> =
vec![Felt::from(10u64), Felt::from(20u64), Felt::from(30u64)];
let mut vm = VirtualMachine::new(false, false);
vm.add_memory_segment();
vm.add_memory_segment();
vm.load_data(
Relocatable::from((1, 0)),
&[
MaybeRelocatable::from((0, 0)),
MaybeRelocatable::from((0, expected_felts.len())),
],
)
.expect("Failed to load pointer data");
let output_data: Vec<MaybeRelocatable> = expected_felts
.iter()
.map(|f| MaybeRelocatable::from(*f))
.collect();
vm.load_data(Relocatable::from((0, 0)), &output_data)
.expect("Failed to load output data");
vm.set_fp(2);
let ids_data = prepare_non_continuous_ids_data_for_test(&[
("simple_bl_output_start", -2),
("simple_bl_output", -1),
]);
let ap_tracking = ApTracking::new();
let dump_file = tempfile::NamedTempFile::new().expect("Failed to create temp file");
let dump_path = dump_file.path().to_path_buf();
let mut exec_scopes = ExecutionScopes::new();
exec_scopes.insert_value(vars::OUTPUT_PREIMAGE_DUMP_PATH, dump_path.clone());
dump_privacy_simple_bootloader_output_preimage(
&mut vm,
&mut exec_scopes,
&ids_data,
&ap_tracking,
)
.expect("Hint failed unexpectedly");
let contents = std::fs::read_to_string(&dump_path).expect("Failed to read dump file");
let felts: Vec<Felt> =
serde_json::from_str(&contents).expect("Failed to parse dumped JSON");
assert_eq!(felts, expected_felts);
}
#[test]
fn test_privacy_simple_bootloader_output_hash() {
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let privacy_bl_path = manifest_dir.join(
"resources/compiled_programs/bootloaders/privacy_simple_bootloader_compiled.json",
);
let simple_output_path = manifest_dir
.join("resources/compiled_programs/test_programs/simple_output_compiled.json");
let privacy_bl_program = Program::from_file(privacy_bl_path.as_path(), Some("main"))
.expect("Could not load privacy simple bootloader program.");
let simple_output_program = Program::from_file(simple_output_path.as_path(), Some("main"))
.expect("Could not load simple output program.");
let stripped_program = simple_output_program
.get_stripped_program()
.expect("Could not get stripped program.");
let dump_file = tempfile::NamedTempFile::new().expect("Failed to create temp file");
let dump_path = dump_file.path().to_path_buf();
let task_output: Vec<u64> = vec![0, 1, 2];
let task_output_json: String = serde_json::to_string(&task_output).unwrap();
let program_input_contents = format!(
r#"{{
"tasks": [
{{
"path": "{}",
"program_input": {{
"output": {task_output_json}
}},
"program_hash_function": "blake",
"type": "RunProgramTask"
}}
],
"single_page": true,
"output_preimage_dump_path": "{}"
}}"#,
simple_output_path.display(),
dump_path.display(),
);
let cairo_run_config = RunMode::Proof {
layout: LayoutName::starknet_with_keccak,
dynamic_layout_params: None,
disable_trace_padding: false,
relocate_mem: true,
}
.create_config();
let mut runner = cairo_run_program(
&privacy_bl_program,
Some(ProgramInput::Json(program_input_contents)),
cairo_run_config,
None,
)
.expect("Privacy simple bootloader run failed.");
let dump_contents =
std::fs::read_to_string(&dump_path).expect("Failed to read output preimage dump file");
let preimage_felts: Vec<Felt> =
serde_json::from_str(&dump_contents).expect("Failed to parse preimage felts from JSON");
let expected_program_hash =
compute_program_hash_chain(&stripped_program, 0, HashFunc::Blake)
.expect("Failed to compute program hash.");
let expected_task_size = 2 + task_output.len();
assert_eq!(
preimage_felts[0],
Felt::ONE,
"First preimage element should be n_tasks = 1"
);
assert_eq!(
preimage_felts[1],
Felt::from(expected_task_size as u64),
"Second preimage element should be task size = 2 + output len"
);
assert_eq!(
preimage_felts[2], expected_program_hash,
"Third preimage element should be the Blake program hash"
);
for (i, &expected_val) in task_output.iter().enumerate() {
assert_eq!(
preimage_felts[3 + i],
Felt::from(expected_val),
"Preimage element at index {} should match task output",
3 + i
);
}
assert_eq!(
preimage_felts.len(),
1 + expected_task_size,
"Preimage length should be 1 + task_size (1 being n_tasks in this case)"
);
let expected_hash = Blake2Felt252::encode_felt252_data_and_calc_blake_hash(&preimage_felts);
let mut output_buffer = String::new();
runner
.vm
.write_output(&mut output_buffer)
.expect("Failed to write VM output.");
let expected_hash_str =
signed_felt(Felt252::from_bytes_be(&expected_hash.to_bytes_be())).to_string();
let output_lines: Vec<&str> = output_buffer.lines().collect();
assert_eq!(
output_lines.len(),
1,
"Expected exactly 1 output line (blake hash). Got:\n{output_buffer}"
);
assert_eq!(
output_lines[0], expected_hash_str,
"Privacy bootloader output should equal Blake hash of dumped preimage",
);
}
}