diff --git a/src/starkware/cairo/bootloaders/simple_bootloader/execute_task.cairo b/src/starkware/cairo/bootloaders/simple_bootloader/execute_task.cairo index 3535809b..42f9d32a 100644 --- a/src/starkware/cairo/bootloaders/simple_bootloader/execute_task.cairo +++ b/src/starkware/cairo/bootloaders/simple_bootloader/execute_task.cairo @@ -28,14 +28,8 @@ struct BuiltinData { output: felt, pedersen: felt, range_check: felt, - ecdsa: felt, bitwise: felt, - ec_op: felt, - keccak: felt, poseidon: felt, - range_check96: felt, - add_mod: felt, - mul_mod: felt, } // Computes the hash of a program. @@ -133,14 +127,8 @@ func execute_task{builtin_ptrs: BuiltinData*, self_range_check_ptr}( output=output_ptr + 2, pedersen=cast(pedersen_ptr, felt), range_check=input_builtin_ptrs.range_check, - ecdsa=input_builtin_ptrs.ecdsa, bitwise=input_builtin_ptrs.bitwise, - ec_op=input_builtin_ptrs.ec_op, - keccak=input_builtin_ptrs.keccak, poseidon=cast(poseidon_ptr, felt), - range_check96=input_builtin_ptrs.range_check96, - add_mod=input_builtin_ptrs.add_mod, - mul_mod=input_builtin_ptrs.mul_mod, ); // Call select_input_builtins to get the relevant input builtin pointers for the task. diff --git a/src/starkware/cairo/bootloaders/simple_bootloader/objects.py b/src/starkware/cairo/bootloaders/simple_bootloader/objects.py new file mode 100644 index 00000000..e5495f0d --- /dev/null +++ b/src/starkware/cairo/bootloaders/simple_bootloader/objects.py @@ -0,0 +1,99 @@ +import dataclasses +from abc import abstractmethod +from dataclasses import field +from typing import ClassVar, Dict, List, Optional, Type +import marshmallow +import marshmallow.fields as mfields +import marshmallow_dataclass +from marshmallow_oneofschema import OneOfSchema +from starkware.cairo.lang.compiler.program import Program, ProgramBase, StrippedProgram +from starkware.cairo.lang.vm.cairo_pie import CairoPie +from starkware.starkware_utils.marshmallow_dataclass_fields import additional_metadata +from starkware.starkware_utils.validated_dataclass import ValidatedMarshmallowDataclass + + +class Task: + @abstractmethod + def get_program(self) -> ProgramBase: + """ + Returns the task's Cairo program. + """ + + +class TaskSpec: + """ + Contains task's specification. + """ + + @abstractmethod + def load_task(self, memory=None, args_start=None, args_len=None) -> "Task": + """ + Returns the corresponding task. + """ + + +@marshmallow_dataclass.dataclass(frozen=True) +class RunProgramTask(TaskSpec, Task): + TYPE: ClassVar[str] = "RunProgramTask" + program: Program + program_input: dict + use_poseidon: bool + + def get_program(self) -> Program: + return self.program + + def load_task(self, memory=None, args_start=None, args_len=None) -> "Task": + return self + + +@marshmallow_dataclass.dataclass(frozen=True) +class CairoPiePath(TaskSpec): + TYPE: ClassVar[str] = "CairoPiePath" + path: str + use_poseidon: bool + + def load_task(self, memory=None, args_start=None, args_len=None) -> "CairoPieTask": + """ + Loads the PIE to memory. + """ + return CairoPieTask( + cairo_pie=CairoPie.from_file(self.path), use_poseidon=self.use_poseidon + ) + + +class TaskSchema(OneOfSchema): + """ + Schema for Task/CairoPiePath/Cairo1ProgramPath/CairoSierra + OneOfSchema adds a "type" field. + """ + + type_schemas: Dict[str, Type[marshmallow.Schema]] = { + RunProgramTask.TYPE: RunProgramTask.Schema, + CairoPiePath.TYPE: CairoPiePath.Schema, + } + + def get_obj_type(self, obj): + return obj.TYPE + + +@dataclasses.dataclass(frozen=True) +class CairoPieTask(Task): + cairo_pie: CairoPie + use_poseidon: bool + + def get_program(self) -> StrippedProgram: + return self.cairo_pie.program + + +@marshmallow_dataclass.dataclass(frozen=True) +class SimpleBootloaderInput(ValidatedMarshmallowDataclass): + tasks: List[TaskSpec] = field( + metadata=additional_metadata( + marshmallow_field=mfields.List(mfields.Nested(TaskSchema)) + ) + ) + fact_topologies_path: Optional[str] + + # If true, the bootloader will put all the outputs in a single page, ignoring the + # tasks' fact topologies. + single_page: bool \ No newline at end of file diff --git a/src/starkware/cairo/bootloaders/simple_bootloader/run_simple_bootloader.cairo b/src/starkware/cairo/bootloaders/simple_bootloader/run_simple_bootloader.cairo index 701bcb9d..15ccaf40 100644 --- a/src/starkware/cairo/bootloaders/simple_bootloader/run_simple_bootloader.cairo +++ b/src/starkware/cairo/bootloaders/simple_bootloader/run_simple_bootloader.cairo @@ -14,14 +14,8 @@ func run_simple_bootloader{ output_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, - ecdsa_ptr, bitwise_ptr, - ec_op_ptr, - keccak_ptr, poseidon_ptr: PoseidonBuiltin*, - range_check96_ptr, - add_mod_ptr, - mul_mod_ptr, }() { alloc_locals; local task_range_check_ptr; @@ -47,14 +41,8 @@ func run_simple_bootloader{ output=cast(output_ptr, felt), pedersen=cast(pedersen_ptr, felt), range_check=task_range_check_ptr, - ecdsa=ecdsa_ptr, bitwise=bitwise_ptr, - ec_op=ec_op_ptr, - keccak=keccak_ptr, poseidon=cast(poseidon_ptr, felt), - range_check96=range_check96_ptr, - add_mod=add_mod_ptr, - mul_mod=mul_mod_ptr, ); // A struct containing the encoding of each builtin. @@ -62,28 +50,16 @@ func run_simple_bootloader{ output='output', pedersen='pedersen', range_check='range_check', - ecdsa='ecdsa', bitwise='bitwise', - ec_op='ec_op', - keccak='keccak', poseidon='poseidon', - range_check96='range_check96', - add_mod='add_mod', - mul_mod='mul_mod', ); local builtin_instance_sizes: BuiltinData = BuiltinData( output=1, pedersen=3, range_check=1, - ecdsa=2, bitwise=5, - ec_op=7, - keccak=16, poseidon=6, - range_check96=1, - add_mod=7, - mul_mod=7, ); // Call execute_tasks. @@ -108,14 +84,8 @@ func run_simple_bootloader{ let output_ptr = cast(builtin_ptrs.output, felt*); let pedersen_ptr = cast(builtin_ptrs.pedersen, HashBuiltin*); let range_check_ptr = builtin_ptrs.range_check; - let ecdsa_ptr = builtin_ptrs.ecdsa; let bitwise_ptr = builtin_ptrs.bitwise; - let ec_op_ptr = builtin_ptrs.ec_op; - let keccak_ptr = builtin_ptrs.keccak; let poseidon_ptr = cast(builtin_ptrs.poseidon, PoseidonBuiltin*); - let range_check96_ptr = builtin_ptrs.range_check96; - let add_mod_ptr = builtin_ptrs.add_mod; - let mul_mod_ptr = builtin_ptrs.mul_mod; // 'execute_tasks' runs untrusted code and uses the range_check builtin to verify that // the builtin pointers were advanced correctly by said code. diff --git a/src/starkware/cairo/bootloaders/simple_bootloader/simple_bootloader.cairo b/src/starkware/cairo/bootloaders/simple_bootloader/simple_bootloader.cairo index d58d6beb..ede07c23 100644 --- a/src/starkware/cairo/bootloaders/simple_bootloader/simple_bootloader.cairo +++ b/src/starkware/cairo/bootloaders/simple_bootloader/simple_bootloader.cairo @@ -1,4 +1,4 @@ -%builtins output pedersen range_check ecdsa bitwise ec_op keccak poseidon range_check96 add_mod mul_mod +%builtins output pedersen range_check bitwise poseidon from starkware.cairo.bootloaders.simple_bootloader.run_simple_bootloader import ( run_simple_bootloader, @@ -10,14 +10,8 @@ func main{ output_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, - ecdsa_ptr, bitwise_ptr, - ec_op_ptr, - keccak_ptr, poseidon_ptr: PoseidonBuiltin*, - range_check96_ptr, - add_mod_ptr, - mul_mod_ptr, }() { %{ from starkware.cairo.bootloaders.simple_bootloader.objects import SimpleBootloaderInput diff --git a/src/starkware/cairo/bootloaders/simple_bootloader/utils.py b/src/starkware/cairo/bootloaders/simple_bootloader/utils.py new file mode 100644 index 00000000..f721f303 --- /dev/null +++ b/src/starkware/cairo/bootloaders/simple_bootloader/utils.py @@ -0,0 +1,293 @@ +import json +from typing import Any, List, Union +from starkware.cairo.bootloaders.fact_topology import ( + FactTopologiesFile, + FactTopology, + get_fact_topology_from_additional_data, +) +from starkware.cairo.bootloaders.simple_bootloader.objects import CairoPieTask, RunProgramTask, Task +from starkware.cairo.lang.vm.cairo_pie import CairoPie, ExecutionResources +from starkware.cairo.lang.vm.output_builtin_runner import OutputBuiltinRunner +from starkware.cairo.lang.vm.relocatable import ( + MaybeRelocatable, + RelocatableValue, + relocate_value, +) +from starkware.python.utils import WriteOnceDict, from_bytes +from starkware.cairo.lang.builtins.all_builtins import ALL_BUILTINS + +# Upper bounds on the numbers of builtin instances and steps that the simple_bootloader uses. +SIMPLE_BOOTLOADER_N_OUTPUT = 2 +SIMPLE_BOOTLOADER_N_PEDERSEN = 20 +SIMPLE_BOOTLOADER_N_RANGE_CHECKS = 20 +SIMPLE_BOOTLOADER_N_STEPS_CONSTANT = 400 +SIMPLE_BOOTLOADER_N_STEPS_RATIO = 8 + + +def load_program(task: Task, memory, program_header, builtins_offset): + """ + Fills the memory with the following: + 1. program header. + 2. program code. + Returns the program address and the size of the written program data. + """ + + builtins = task.get_program().builtins + n_builtins = len(builtins) + program_data = task.get_program().data + + # Fill in the program header. + header_address = program_header.address_ + # The program header ends with a list of builtins used by the program. + header_size = builtins_offset + n_builtins + # data_length does not include the data_length header field in the calculation. + program_header.data_length = (header_size - 1) + len(program_data) + program_header.program_main = task.get_program().main + program_header.n_builtins = n_builtins + # Fill in the builtin list in memory. + builtins_address = header_address + builtins_offset + for index, builtin in enumerate(builtins): + assert isinstance(builtin, str) + memory[builtins_address + index] = from_bytes(builtin.encode("ascii")) + + # Fill in the program code in memory. + program_address = header_address + header_size + for index, opcode in enumerate(program_data): + memory[program_address + index] = opcode + + return program_address, header_size + len(program_data) + + +def write_return_builtins( + memory, + return_builtins_addr, + used_builtins, + used_builtins_addr, + pre_execution_builtins_addr, + task, +): + """ + Writes the updated builtin pointers after the program execution to the given return builtins + address. + used_builtins is the list of builtins used by the program and thus updated by it. + """ + + used_builtin_offset = 0 + for index, builtin in enumerate(ALL_BUILTINS): + if builtin in used_builtins: + memory[return_builtins_addr + index] = memory[ + used_builtins_addr + used_builtin_offset + ] + used_builtin_offset += 1 + + if isinstance(task, CairoPie): + assert task.metadata.builtin_segments[builtin].size == ( + memory[return_builtins_addr + index] + - memory[pre_execution_builtins_addr + index] + ), "Builtin usage is inconsistent with the CairoPie." + else: + # The builtin is unused, hence its value is the same as before calling the program. + memory[return_builtins_addr + index] = memory[ + pre_execution_builtins_addr + index + ] + + +def load_cairo_pie( + task: CairoPie, + memory, + segments, + program_address, + execution_segment_address, + builtin_runners, + ret_fp, + ret_pc, +): + """ + Load memory entries of the inner program. + This replaces executing hints in a non-trusted program. + """ + segment_offsets = WriteOnceDict() + + segment_offsets[task.metadata.program_segment.index] = program_address + segment_offsets[task.metadata.execution_segment.index] = execution_segment_address + segment_offsets[task.metadata.ret_fp_segment.index] = ret_fp + segment_offsets[task.metadata.ret_pc_segment.index] = ret_pc + + def extract_segment(value: MaybeRelocatable, value_name: str): + """ + Returns the segment index for the given value. + Verifies that value is a RelocatableValue with offset 0. + """ + assert isinstance(value, RelocatableValue), f"{value_name} is not relocatable." + assert value.offset == 0, f"{value_name} has a non-zero offset." + return value.segment_index + + orig_execution_segment = RelocatableValue( + segment_index=task.metadata.execution_segment.index, offset=0 + ) + + # Set initial stack relocations. + for idx, name in enumerate(task.program.builtins): + segment_offsets[ + extract_segment( + value=task.memory[orig_execution_segment + idx], + value_name=f"{name} builtin start address", + ) + ] = memory[execution_segment_address + idx] + + for segment_info in task.metadata.extra_segments: + segment_offsets[segment_info.index] = segments.add(size=segment_info.size) + + def local_relocate_value(value): + return relocate_value(value, segment_offsets, task.program.prime) + + # Relocate builtin additional data. + # This should occur before the memory relocation, since the signature builtin assumes that a + # signature is added before the corresponding public key and message are both written to memory. + esdsa_additional_data = task.additional_data.get("ecdsa_builtin") + if esdsa_additional_data is not None: + ecdsa_builtin = builtin_runners.get("ecdsa_builtin") + assert ( + ecdsa_builtin is not None + ), "The task requires the ecdsa builtin but it is missing." + ecdsa_builtin.extend_additional_data( + esdsa_additional_data, local_relocate_value + ) + + for addr, val in task.memory.items(): + memory[local_relocate_value(addr)] = local_relocate_value(val) + + +def prepare_output_runner( + task: Task, output_builtin: OutputBuiltinRunner, output_ptr: RelocatableValue +): + """ + Prepares the output builtin if the type of task is Task, so that pages of the inner program + will be recorded separately. + If the type of task is CairoPie, nothing should be done, as the program does not contain + hints that may affect the output builtin. + The return value of this function should be later passed to get_task_fact_topology(). + """ + + if isinstance(task, RunProgramTask): + output_state = output_builtin.get_state() + output_builtin.new_state(base=output_ptr) + return output_state + elif isinstance(task, CairoPieTask): + return None + else: + raise NotImplementedError(f"Unexpected task type: {type(task).__name__}.") + + +def get_task_fact_topology( + output_size: int, + task: Union[RunProgramTask, CairoPie], + output_builtin: OutputBuiltinRunner, + output_runner_data: Any, +) -> FactTopology: + """ + Returns the fact_topology that corresponds to 'task'. Restores output builtin state if 'task' is + a RunProgramTask. + """ + + # Obtain the fact_toplogy of 'task'. + if isinstance(task, RunProgramTask): + assert output_runner_data is not None + fact_topology = get_fact_topology_from_additional_data( + output_size=output_size, + output_builtin_additional_data=output_builtin.get_additional_data(), + ) + # Restore the output builtin runner to its original state. + output_builtin.set_state(output_runner_data) + elif isinstance(task, CairoPieTask): + assert output_runner_data is None + fact_topology = get_fact_topology_from_additional_data( + output_size=output_size, + output_builtin_additional_data=task.cairo_pie.additional_data[ + "output_builtin" + ], + ) + else: + raise NotImplementedError(f"Unexpected task type: {type(task).__name__}.") + + return fact_topology + + +def add_consecutive_output_pages( + fact_topology: FactTopology, + output_builtin: OutputBuiltinRunner, + cur_page_id: int, + output_start: MaybeRelocatable, +) -> int: + offset = 0 + for i, page_size in enumerate(fact_topology.page_sizes): + output_builtin.add_page( + page_id=cur_page_id + i, + page_start=output_start + offset, + page_size=page_size, + ) + offset += page_size + + return len(fact_topology.page_sizes) + + +def configure_fact_topologies( + fact_topologies: List[FactTopology], + output_start: MaybeRelocatable, + output_builtin: OutputBuiltinRunner, +): + """ + Given the fact_topologies of the tasks that were run by bootloader, configure the + corresponding pages in the output builtin. Assumes that the bootloader output 2 words per task. + """ + # Each task may use a few memory pages. Start from page 1 (as page 0 is reserved for the + # bootloader program and arguments). + cur_page_id = 1 + for fact_topology in fact_topologies: + # Skip bootloader output for each task. + output_start += 2 + cur_page_id += add_consecutive_output_pages( + fact_topology=fact_topology, + output_builtin=output_builtin, + cur_page_id=cur_page_id, + output_start=output_start, + ) + output_start += sum(fact_topology.page_sizes) + + +def write_to_fact_topologies_file( + fact_topologies_path: str, fact_topologies: List[FactTopology] +): + with open(fact_topologies_path, "w") as fp: + json.dump( + FactTopologiesFile.Schema().dump( + FactTopologiesFile(fact_topologies=fact_topologies) + ), + fp, + indent=4, + sort_keys=True, + ) + fp.write("\n") + + +def calc_simple_bootloader_execution_resources( + program_length: int, +) -> ExecutionResources: + """ + Returns an upper bound on the number of steps and builtin instances that the simple bootloader + uses. + """ + n_steps = ( + SIMPLE_BOOTLOADER_N_STEPS_RATIO * program_length + + SIMPLE_BOOTLOADER_N_STEPS_CONSTANT + ) + builtin_instance_counter = { + "pedersen_builtin": SIMPLE_BOOTLOADER_N_PEDERSEN + program_length, + "range_check_builtin": SIMPLE_BOOTLOADER_N_RANGE_CHECKS, + "output_builtin": SIMPLE_BOOTLOADER_N_OUTPUT, + } + return ExecutionResources( + n_steps=n_steps, + builtin_instance_counter=builtin_instance_counter, + n_memory_holes=0, + ) \ No newline at end of file diff --git a/src/starkware/cairo/lang/builtins/all_builtins.py b/src/starkware/cairo/lang/builtins/all_builtins.py index e7d2214f..5432caf0 100644 --- a/src/starkware/cairo/lang/builtins/all_builtins.py +++ b/src/starkware/cairo/lang/builtins/all_builtins.py @@ -55,14 +55,8 @@ def with_suffix(self): OUTPUT_BUILTIN, PEDERSEN_BUILTIN, RANGE_CHECK_BUILTIN, - ECDSA_BUILTIN, BITWISE_BUILTIN, - EC_OP_BUILTIN, - KECCAK_BUILTIN, POSEIDON_BUILTIN, - RANGE_CHECK96_BUILTIN, - ADD_MOD_BUILTIN, - MUL_MOD_BUILTIN, ] ) # The list of builtins which may have ratio < 1 (and implement BuiltinInstanceDefWithLowRatio).