diff --git a/Cargo.lock b/Cargo.lock index 11907dd2540..03c2088eae7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12230,6 +12230,7 @@ dependencies = [ "ark-secp256k1 0.4.0", "ark-secp256r1 0.4.0", "assert_matches", + "blake2s", "blockifier", "blockifier_test_utils", "c-kzg", diff --git a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/contract_class/blake_compiled_class_hash.cairo b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/contract_class/blake_compiled_class_hash.cairo new file mode 100644 index 00000000000..a1a49b70317 --- /dev/null +++ b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/contract_class/blake_compiled_class_hash.cairo @@ -0,0 +1,236 @@ +from starkware.cairo.common.alloc import alloc +from starkware.cairo.common.bool import FALSE +from starkware.cairo.common.cairo_blake2s.blake2s import ( + encode_felt252_data_and_calc_224_bit_blake_hash, +) +from starkware.cairo.common.math import assert_lt_felt +from starkware.cairo.common.registers import get_fp_and_pc +from starkware.starknet.core.os.constants import ( + ADD_MOD_GAS_COST, + BITWISE_BUILTIN_GAS_COST, + ECOP_GAS_COST, + MUL_MOD_GAS_COST, + PEDERSEN_GAS_COST, + POSEIDON_GAS_COST, +) +from starkware.starknet.core.os.contract_class.compiled_class_struct import ( + COMPILED_CLASS_VERSION, + CompiledClass, + CompiledClassEntryPoint, +) +from starkware.starknet.core.os.hash.hash_state_blake import ( + HashState, + hash_finalize, + hash_init, + hash_update_single, + hash_update_with_nested_hash, +) + +func compiled_class_hash{range_check_ptr}(compiled_class: CompiledClass*) -> (hash: felt) { + alloc_locals; + assert compiled_class.compiled_class_version = COMPILED_CLASS_VERSION; + + let hash_state: HashState = hash_init(); + with hash_state { + hash_update_single(item=compiled_class.compiled_class_version); + + // Hash external entry points. + hash_entry_points( + entry_points=compiled_class.external_functions, + n_entry_points=compiled_class.n_external_functions, + ); + + // Hash L1 handler entry points. + hash_entry_points( + entry_points=compiled_class.l1_handlers, n_entry_points=compiled_class.n_l1_handlers + ); + + // Hash constructor entry points. + hash_entry_points( + entry_points=compiled_class.constructors, n_entry_points=compiled_class.n_constructors + ); + + // Hash bytecode. + let bytecode_hash = bytecode_hash_node( + data_ptr=compiled_class.bytecode_ptr, data_length=compiled_class.bytecode_length + ); + hash_update_single(item=bytecode_hash); + } + + let hash: felt = hash_finalize{range_check_ptr=range_check_ptr}(hash_state=hash_state); + return (hash=hash); +} + +// Returns the hash of the contract class bytecode according to its segments. +// +// The hash is computed according to a segment tree. Each segment may be either a leaf or divided +// into smaller segments (internal node). +// For example, the bytecode may be divided into functions and each function can be divided +// according to its branches. +// +// The hash of a leaf is the Blake2s hash of the data. +// The hash of an internal node is `1 + blake2s(len0, hash0, len1, hash1, ...)` where +// len0 is the total length of the first segment, hash0 is the hash of the first segment, and so on. +// +// For each segment, the *prover* can choose whether to load or skip the segment. +// +// * Loaded segment: +// For leaves, the data will be fully loaded into memory. +// For internal nodes, the prover can choose to load/skip each of the children separately. +// +// * Skipped segment: +// The inner structure of that segment is ignored. +// The only guarantee is that the first field element is enforced to be -1. +// The rest of the field elements are unconstrained. +// The fact that a skipped segment is guaranteed to begin with -1 implies that the execution of +// the program cannot visit the start of the segment, as -1 is not a valid Cairo opcode. +// +// In the example above of division according to functions and branches, a function may be skipped +// entirely or partially. +// As long as one function does not jump into the middle of another function and as long as there +// are no jumps into the middle of a branch segment, the loading process described above will be +// sound. +// +// Hint arguments: +// bytecode_segment_structure: A BytecodeSegmentStructure object that describes the bytecode +// structure. +// is_segment_used_callback: A callback that returns whether a segment is used. +func bytecode_hash_node{range_check_ptr}(data_ptr: felt*, data_length: felt) -> felt { + alloc_locals; + + local is_leaf; + + %{ + from starkware.starknet.core.os.contract_class.compiled_class_hash_objects import ( + BytecodeLeaf, + ) + ids.is_leaf = 1 if isinstance(bytecode_segment_structure, BytecodeLeaf) else 0 + %} + + // Guess if the bytecode is a leaf or an internal node in the tree. + if (is_leaf != FALSE) { + // If the bytecode is a leaf, it must be loaded into memory. Compute its hash. + let (hash) = encode_felt252_data_and_calc_224_bit_blake_hash( + data_len=data_length, data=data_ptr + ); + return hash; + } + + %{ bytecode_segments = iter(bytecode_segment_structure.segments) %} + + // Initialize Blake2s hash state for internal node. + let hash_state: HashState = hash_init(); + with hash_state { + bytecode_hash_internal_node(data_ptr=data_ptr, data_length=data_length); + } + + let segmented_hash = hash_finalize{range_check_ptr=range_check_ptr}(hash_state=hash_state); + + // Add 1 to segmented_hash to avoid collisions with the hash of a leaf (domain separation). + return segmented_hash + 1; +} + +// Helper function for bytecode_hash_node. +// Computes the hash of an internal node by adding its children to the hash state. +func bytecode_hash_internal_node{range_check_ptr, hash_state: HashState}( + data_ptr: felt*, data_length: felt +) { + if (data_length == 0) { + %{ assert next(bytecode_segments, None) is None %} + return (); + } + + alloc_locals; + local is_used_leaf; + local is_segment_used; + local segment_length; + + %{ + current_segment_info = next(bytecode_segments) + + is_used = is_segment_used_callback(ids.data_ptr, current_segment_info.segment_length) + ids.is_segment_used = 1 if is_used else 0 + + is_used_leaf = is_used and isinstance(current_segment_info.inner_structure, BytecodeLeaf) + ids.is_used_leaf = 1 if is_used_leaf else 0 + + ids.segment_length = current_segment_info.segment_length + vm_enter_scope(new_scope_locals={ + "bytecode_segment_structure": current_segment_info.inner_structure, + "is_segment_used_callback": is_segment_used_callback + }) + %} + + if (is_used_leaf != FALSE) { + // Repeat the code of bytecode_hash_node() for performance reasons, instead of calling it. + let (current_segment_hash) = encode_felt252_data_and_calc_224_bit_blake_hash( + data_len=segment_length, data=data_ptr + ); + tempvar range_check_ptr = range_check_ptr; + tempvar current_segment_hash = current_segment_hash; + } else { + if (is_segment_used != FALSE) { + let current_segment_hash = bytecode_hash_node( + data_ptr=data_ptr, data_length=segment_length + ); + } else { + // Set the first felt of the bytecode to -1 to make sure that the execution cannot jump + // to this segment (-1 is an invalid opcode). + // The hash in this case is guessed and the actual bytecode is unconstrained (except for + // the first felt). + %{ + # Sanity check. + assert not is_accessed(ids.data_ptr), "The segment is skipped but was accessed." + del memory.data[ids.data_ptr] + %} + assert data_ptr[0] = -1; + + assert [range_check_ptr] = segment_length; + tempvar range_check_ptr = range_check_ptr + 1; + tempvar current_segment_hash = nondet %{ bytecode_segment_structure.hash() %}; + } + } + + // Add the segment length and hash to the hash state. + hash_update_single(item=segment_length); + hash_update_single(item=current_segment_hash); + + %{ vm_exit_scope() %} + + return bytecode_hash_internal_node( + data_ptr=&data_ptr[segment_length], data_length=data_length - segment_length + ); +} + +func hash_entry_points{hash_state: HashState, range_check_ptr: felt}( + entry_points: CompiledClassEntryPoint*, n_entry_points: felt +) { + let inner_hash_state = hash_init(); + hash_entry_points_inner{hash_state=inner_hash_state, range_check_ptr=range_check_ptr}( + entry_points=entry_points, n_entry_points=n_entry_points + ); + let hash: felt = hash_finalize{range_check_ptr=range_check_ptr}(hash_state=inner_hash_state); + hash_update_single(item=hash); + + return (); +} + +func hash_entry_points_inner{hash_state: HashState, range_check_ptr: felt}( + entry_points: CompiledClassEntryPoint*, n_entry_points: felt +) { + if (n_entry_points == 0) { + return (); + } + + hash_update_single(item=entry_points.selector); + hash_update_single(item=entry_points.offset); + + // Hash builtins. + hash_update_with_nested_hash{hash_state=hash_state, range_check_ptr=range_check_ptr}( + data_ptr=entry_points.builtin_list, data_length=entry_points.n_builtins + ); + + return hash_entry_points_inner( + entry_points=&entry_points[1], n_entry_points=n_entry_points - 1 + ); +} diff --git a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/contract_class/compiled_class.cairo b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/contract_class/compiled_class.cairo index fd69e84ec3d..1a62876a595 100644 --- a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/contract_class/compiled_class.cairo +++ b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/contract_class/compiled_class.cairo @@ -20,40 +20,13 @@ from starkware.starknet.core.os.constants import ( PEDERSEN_GAS_COST, POSEIDON_GAS_COST, ) - -const COMPILED_CLASS_VERSION = 'COMPILED_CLASS_V1'; - -struct CompiledClassEntryPoint { - // A field element that encodes the signature of the called function. - selector: felt, - // The offset of the instruction that should be called within the contract bytecode. - offset: felt, - // The number of builtins in 'builtin_list'. - n_builtins: felt, - // 'builtin_list' is a continuous memory segment containing the ASCII encoding of the (ordered) - // builtins used by the function. - builtin_list: felt*, -} - -struct CompiledClass { - compiled_class_version: felt, - - // The length and pointer to the external entry point table of the contract. - n_external_functions: felt, - external_functions: CompiledClassEntryPoint*, - - // The length and pointer to the L1 handler entry point table of the contract. - n_l1_handlers: felt, - l1_handlers: CompiledClassEntryPoint*, - - // The length and pointer to the constructor entry point table of the contract. - n_constructors: felt, - constructors: CompiledClassEntryPoint*, - - // The length and pointer of the bytecode. - bytecode_length: felt, - bytecode_ptr: felt*, -} +from starkware.starknet.core.os.contract_class.compiled_class_struct import ( + COMPILED_CLASS_VERSION, + CompiledClass, + CompiledClassEntryPoint, + CompiledClassFact, +) +from starkware.starknet.core.os.contract_class.blake_compiled_class_hash import compiled_class_hash // Checks that the list of selectors is sorted. func validate_entry_points{range_check_ptr}( @@ -87,242 +60,12 @@ func validate_entry_points_inner{range_check_ptr}( ); } -func compiled_class_hash{range_check_ptr, poseidon_ptr: PoseidonBuiltin*}( - compiled_class: CompiledClass* -) -> (hash: felt) { - alloc_locals; - assert compiled_class.compiled_class_version = COMPILED_CLASS_VERSION; - - let hash_state: HashState = hash_init(); - with hash_state { - hash_update_single(item=compiled_class.compiled_class_version); - - // Hash external entry points. - hash_entry_points( - entry_points=compiled_class.external_functions, - n_entry_points=compiled_class.n_external_functions, - ); - - // Hash L1 handler entry points. - hash_entry_points( - entry_points=compiled_class.l1_handlers, n_entry_points=compiled_class.n_l1_handlers - ); - - // Hash constructor entry points. - hash_entry_points( - entry_points=compiled_class.constructors, n_entry_points=compiled_class.n_constructors - ); - - // Hash bytecode. - let bytecode_hash = bytecode_hash_node( - data_ptr=compiled_class.bytecode_ptr, data_length=compiled_class.bytecode_length - ); - hash_update_single(item=bytecode_hash); - } - - let hash: felt = hash_finalize(hash_state=hash_state); - return (hash=hash); -} - -// Returns the hash of the contract class bytecode according to its segments. -// -// The hash is computed according to a segment tree. Each segment may be either a leaf or divided -// into smaller segments (internal node). -// For example, the bytecode may be divided into functions and each function can be divided -// according to its branches. -// -// The hash of a leaf is the Poseidon hash the data. -// The hash of an internal node is `1 + poseidon(len0, hash0, len1, hash1, ...)` where -// len0 is the total length of the first segment, hash0 is the hash of the first segment, and so on. -// -// For each segment, the *prover* can choose whether to load or skip the segment. -// -// * Loaded segment: -// For leaves, the data will be fully loaded into memory. -// For internal nodes, the prover can choose to load/skip each of the children separately. -// -// * Skipped segment: -// The inner structure of that segment is ignored. -// The only guarantee is that the first field element is enforced to be -1. -// The rest of the field elements are unconstrained. -// The fact that a skipped segment is guaranteed to begin with -1 implies that the execution of -// the program cannot visit the start of the segment, as -1 is not a valid Cairo opcode. -// -// In the example above of division according to functions and branches, a function may be skipped -// entirely or partially. -// As long as one function does not jump into the middle of another function and as long as there -// are no jumps into the middle of a branch segment, the loading process described above will be -// sound. -// -// Hint arguments: -// bytecode_segment_structure: A BytecodeSegmentStructure object that describes the bytecode -// structure. -// is_segment_used_callback: A callback that returns whether a segment is used. -func bytecode_hash_node{range_check_ptr, poseidon_ptr: PoseidonBuiltin*}( - data_ptr: felt*, data_length: felt -) -> felt { - alloc_locals; - - local is_leaf; - - %{ - from starkware.starknet.core.os.contract_class.compiled_class_hash_objects import ( - BytecodeLeaf, - ) - ids.is_leaf = 1 if isinstance(bytecode_segment_structure, BytecodeLeaf) else 0 - %} - - // Guess if the bytecode is a leaf or an internal node in the tree. - if (is_leaf != FALSE) { - // If the bytecode is a leaf, it must be loaded into memory. Compute its hash. - let (hash) = poseidon_hash_many(n=data_length, elements=data_ptr); - return hash; - } - - %{ bytecode_segments = iter(bytecode_segment_structure.segments) %} - - // Use the poseidon builtin directly for performance reasons. - let poseidon_state = PoseidonBuiltinState(s0=0, s1=0, s2=0); - bytecode_hash_internal_node{poseidon_state=poseidon_state}( - data_ptr=data_ptr, data_length=data_length - ); - - // Pad input with [1, 0]. See implementation of poseidon_hash_many(). - assert poseidon_ptr.input = PoseidonBuiltinState( - s0=poseidon_state.s0 + 1, s1=poseidon_state.s1, s2=poseidon_state.s2 - ); - let segmented_hash = poseidon_ptr.output.s0; - let poseidon_ptr = &poseidon_ptr[1]; - - // Add 1 to segmented_hash to avoid collisions with the hash of a leaf (domain separation). - return segmented_hash + 1; -} - -// Helper function for bytecode_hash_node. -// Computes the hash of an internal node by adding its children to the hash state. -func bytecode_hash_internal_node{ - range_check_ptr, poseidon_ptr: PoseidonBuiltin*, poseidon_state: PoseidonBuiltinState -}(data_ptr: felt*, data_length: felt) { - if (data_length == 0) { - %{ assert next(bytecode_segments, None) is None %} - return (); - } - - alloc_locals; - local is_used_leaf; - local is_segment_used; - local segment_length; - - %{ - current_segment_info = next(bytecode_segments) - - is_used = is_segment_used_callback(ids.data_ptr, current_segment_info.segment_length) - ids.is_segment_used = 1 if is_used else 0 - - is_used_leaf = is_used and isinstance(current_segment_info.inner_structure, BytecodeLeaf) - ids.is_used_leaf = 1 if is_used_leaf else 0 - - ids.segment_length = current_segment_info.segment_length - vm_enter_scope(new_scope_locals={ - "bytecode_segment_structure": current_segment_info.inner_structure, - "is_segment_used_callback": is_segment_used_callback - }) - %} - - if (is_used_leaf != FALSE) { - // Repeat the code of bytecode_hash_node() for performance reasons, instead of calling it. - let (current_segment_hash) = poseidon_hash_many(n=segment_length, elements=data_ptr); - tempvar range_check_ptr = range_check_ptr; - tempvar poseidon_ptr = poseidon_ptr; - tempvar current_segment_hash = current_segment_hash; - } else { - if (is_segment_used != FALSE) { - let current_segment_hash = bytecode_hash_node( - data_ptr=data_ptr, data_length=segment_length - ); - } else { - // Set the first felt of the bytecode to -1 to make sure that the execution cannot jump - // to this segment (-1 is an invalid opcode). - // The hash in this case is guessed and the actual bytecode is unconstrained (except for - // the first felt). - %{ - # Sanity check. - assert not is_accessed(ids.data_ptr), "The segment is skipped but was accessed." - del memory.data[ids.data_ptr] - %} - assert data_ptr[0] = -1; - - assert [range_check_ptr] = segment_length; - tempvar range_check_ptr = range_check_ptr + 1; - tempvar poseidon_ptr = poseidon_ptr; - tempvar current_segment_hash = nondet %{ bytecode_segment_structure.hash() %}; - } - } - - // Add the segment length and hash to the hash state. - // Use the poseidon builtin directly for performance reasons. - assert poseidon_ptr.input = PoseidonBuiltinState( - s0=poseidon_state.s0 + segment_length, - s1=poseidon_state.s1 + current_segment_hash, - s2=poseidon_state.s2, - ); - let poseidon_state = poseidon_ptr.output; - let poseidon_ptr = &poseidon_ptr[1]; - - %{ vm_exit_scope() %} - - return bytecode_hash_internal_node( - data_ptr=&data_ptr[segment_length], data_length=data_length - segment_length - ); -} - -func hash_entry_points{poseidon_ptr: PoseidonBuiltin*, hash_state: HashState}( - entry_points: CompiledClassEntryPoint*, n_entry_points: felt -) { - let inner_hash_state = hash_init(); - hash_entry_points_inner{hash_state=inner_hash_state}( - entry_points=entry_points, n_entry_points=n_entry_points - ); - let hash: felt = hash_finalize(hash_state=inner_hash_state); - hash_update_single(item=hash); - - return (); -} - -func hash_entry_points_inner{poseidon_ptr: PoseidonBuiltin*, hash_state: HashState}( - entry_points: CompiledClassEntryPoint*, n_entry_points: felt -) { - if (n_entry_points == 0) { - return (); - } - - hash_update_single(item=entry_points.selector); - hash_update_single(item=entry_points.offset); - - // Hash builtins. - hash_update_with_nested_hash( - data_ptr=entry_points.builtin_list, data_length=entry_points.n_builtins - ); - - return hash_entry_points_inner( - entry_points=&entry_points[1], n_entry_points=n_entry_points - 1 - ); -} - -// A list entry that maps a hash to the corresponding contract classes. -struct CompiledClassFact { - // The hash of the contract. This member should be first, so that we can lookup items - // with the hash as key, using find_element(). - hash: felt, - compiled_class: CompiledClass*, -} - // Guesses the contract classes from the 'os_input' hint variable without validating their hashes. // Returns CompiledClassFact list that maps a hash to a CompiledClass, and the builtin costs list // which is appended to every contract. // // Note: `validate_compiled_class_facts` must be called eventually to complete the validation. -func guess_compiled_class_facts{poseidon_ptr: PoseidonBuiltin*, range_check_ptr}() -> ( +func guess_compiled_class_facts{range_check_ptr}() -> ( n_compiled_class_facts: felt, compiled_class_facts: CompiledClassFact*, builtin_costs: felt* ) { alloc_locals; @@ -385,7 +128,7 @@ func guess_compiled_class_facts{poseidon_ptr: PoseidonBuiltin*, range_check_ptr} // Validates the compiled class facts structure and hash after the execution. // Uses the execution info to optimize hash computation. -func validate_compiled_class_facts_post_execution{poseidon_ptr: PoseidonBuiltin*, range_check_ptr}( +func validate_compiled_class_facts_post_execution{range_check_ptr}( n_compiled_class_facts, compiled_class_facts: CompiledClassFact*, builtin_costs: felt* ) { %{ @@ -418,7 +161,7 @@ func validate_compiled_class_facts_post_execution{poseidon_ptr: PoseidonBuiltin* // Validates the compiled class facts structure and hash, using the hint variable // `bytecode_segment_structures` - a mapping from compilied class hash to the structure. -func validate_compiled_class_facts{poseidon_ptr: PoseidonBuiltin*, range_check_ptr}( +func validate_compiled_class_facts{range_check_ptr}( n_compiled_class_facts, compiled_class_facts: CompiledClassFact*, builtin_costs: felt* ) { if (n_compiled_class_facts == 0) { diff --git a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/contract_class/compiled_class_struct.cairo b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/contract_class/compiled_class_struct.cairo new file mode 100644 index 00000000000..a583b9b2102 --- /dev/null +++ b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/contract_class/compiled_class_struct.cairo @@ -0,0 +1,41 @@ +const COMPILED_CLASS_VERSION = 'COMPILED_CLASS_V1'; + +struct CompiledClassEntryPoint { + // A field element that encodes the signature of the called function. + selector: felt, + // The offset of the instruction that should be called within the contract bytecode. + offset: felt, + // The number of builtins in 'builtin_list'. + n_builtins: felt, + // 'builtin_list' is a continuous memory segment containing the ASCII encoding of the (ordered) + // builtins used by the function. + builtin_list: felt*, +} + +struct CompiledClass { + compiled_class_version: felt, + + // The length and pointer to the external entry point table of the contract. + n_external_functions: felt, + external_functions: CompiledClassEntryPoint*, + + // The length and pointer to the L1 handler entry point table of the contract. + n_l1_handlers: felt, + l1_handlers: CompiledClassEntryPoint*, + + // The length and pointer to the constructor entry point table of the contract. + n_constructors: felt, + constructors: CompiledClassEntryPoint*, + + // The length and pointer of the bytecode. + bytecode_length: felt, + bytecode_ptr: felt*, +} + +// A list entry that maps a hash to the corresponding contract classes. +struct CompiledClassFact { + // The hash of the contract. This member should be first, so that we can lookup items + // with the hash as key, using find_element(). + hash: felt, + compiled_class: CompiledClass*, +} diff --git a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/contract_class/poseidon_compiled_class_hash.cairo b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/contract_class/poseidon_compiled_class_hash.cairo new file mode 100644 index 00000000000..672d85b2f18 --- /dev/null +++ b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/contract_class/poseidon_compiled_class_hash.cairo @@ -0,0 +1,239 @@ +from starkware.cairo.common.alloc import alloc +from starkware.cairo.common.bool import FALSE +from starkware.cairo.common.cairo_builtins import PoseidonBuiltin +from starkware.cairo.common.hash_state_poseidon import ( + HashState, + hash_finalize, + hash_init, + hash_update_single, + hash_update_with_nested_hash, + poseidon_hash_many, +) +from starkware.cairo.common.poseidon_state import PoseidonBuiltinState +from starkware.starknet.core.os.contract_class.compiled_class_struct import ( + COMPILED_CLASS_VERSION, + CompiledClass, + CompiledClassEntryPoint, +) + +func compiled_class_hash{range_check_ptr, poseidon_ptr: PoseidonBuiltin*}( + compiled_class: CompiledClass* +) -> (hash: felt) { + alloc_locals; + assert compiled_class.compiled_class_version = COMPILED_CLASS_VERSION; + + let hash_state: HashState = hash_init(); + with hash_state { + hash_update_single(item=compiled_class.compiled_class_version); + + // Hash external entry points. + hash_entry_points( + entry_points=compiled_class.external_functions, + n_entry_points=compiled_class.n_external_functions, + ); + + // Hash L1 handler entry points. + hash_entry_points( + entry_points=compiled_class.l1_handlers, n_entry_points=compiled_class.n_l1_handlers + ); + + // Hash constructor entry points. + hash_entry_points( + entry_points=compiled_class.constructors, n_entry_points=compiled_class.n_constructors + ); + + // Hash bytecode. + let bytecode_hash = bytecode_hash_node( + data_ptr=compiled_class.bytecode_ptr, data_length=compiled_class.bytecode_length + ); + hash_update_single(item=bytecode_hash); + } + + let hash: felt = hash_finalize(hash_state=hash_state); + return (hash=hash); +} + +// Returns the hash of the contract class bytecode according to its segments. +// +// The hash is computed according to a segment tree. Each segment may be either a leaf or divided +// into smaller segments (internal node). +// For example, the bytecode may be divided into functions and each function can be divided +// according to its branches. +// +// The hash of a leaf is the Poseidon hash the data. +// The hash of an internal node is `1 + poseidon(len0, hash0, len1, hash1, ...)` where +// len0 is the total length of the first segment, hash0 is the hash of the first segment, and so on. +// +// For each segment, the *prover* can choose whether to load or skip the segment. +// +// * Loaded segment: +// For leaves, the data will be fully loaded into memory. +// For internal nodes, the prover can choose to load/skip each of the children separately. +// +// * Skipped segment: +// The inner structure of that segment is ignored. +// The only guarantee is that the first field element is enforced to be -1. +// The rest of the field elements are unconstrained. +// The fact that a skipped segment is guaranteed to begin with -1 implies that the execution of +// the program cannot visit the start of the segment, as -1 is not a valid Cairo opcode. +// +// In the example above of division according to functions and branches, a function may be skipped +// entirely or partially. +// As long as one function does not jump into the middle of another function and as long as there +// are no jumps into the middle of a branch segment, the loading process described above will be +// sound. +// +// Hint arguments: +// bytecode_segment_structure: A BytecodeSegmentStructure object that describes the bytecode +// structure. +// is_segment_used_callback: A callback that returns whether a segment is used. +func bytecode_hash_node{range_check_ptr, poseidon_ptr: PoseidonBuiltin*}( + data_ptr: felt*, data_length: felt +) -> felt { + alloc_locals; + + local is_leaf; + + %{ + from starkware.starknet.core.os.contract_class.compiled_class_hash_objects import ( + BytecodeLeaf, + ) + ids.is_leaf = 1 if isinstance(bytecode_segment_structure, BytecodeLeaf) else 0 + %} + + // Guess if the bytecode is a leaf or an internal node in the tree. + if (is_leaf != FALSE) { + // If the bytecode is a leaf, it must be loaded into memory. Compute its hash. + let (hash) = poseidon_hash_many(n=data_length, elements=data_ptr); + return hash; + } + + %{ bytecode_segments = iter(bytecode_segment_structure.segments) %} + + // Use the poseidon builtin directly for performance reasons. + let poseidon_state = PoseidonBuiltinState(s0=0, s1=0, s2=0); + bytecode_hash_internal_node{poseidon_state=poseidon_state}( + data_ptr=data_ptr, data_length=data_length + ); + + // Pad input with [1, 0]. See implementation of poseidon_hash_many(). + assert poseidon_ptr.input = PoseidonBuiltinState( + s0=poseidon_state.s0 + 1, s1=poseidon_state.s1, s2=poseidon_state.s2 + ); + let segmented_hash = poseidon_ptr.output.s0; + let poseidon_ptr = &poseidon_ptr[1]; + + // Add 1 to segmented_hash to avoid collisions with the hash of a leaf (domain separation). + return segmented_hash + 1; +} + +// Helper function for bytecode_hash_node. +// Computes the hash of an internal node by adding its children to the hash state. +func bytecode_hash_internal_node{ + range_check_ptr, poseidon_ptr: PoseidonBuiltin*, poseidon_state: PoseidonBuiltinState +}(data_ptr: felt*, data_length: felt) { + if (data_length == 0) { + %{ assert next(bytecode_segments, None) is None %} + return (); + } + + alloc_locals; + local is_used_leaf; + local is_segment_used; + local segment_length; + + %{ + current_segment_info = next(bytecode_segments) + + is_used = is_segment_used_callback(ids.data_ptr, current_segment_info.segment_length) + ids.is_segment_used = 1 if is_used else 0 + + is_used_leaf = is_used and isinstance(current_segment_info.inner_structure, BytecodeLeaf) + ids.is_used_leaf = 1 if is_used_leaf else 0 + + ids.segment_length = current_segment_info.segment_length + vm_enter_scope(new_scope_locals={ + "bytecode_segment_structure": current_segment_info.inner_structure, + "is_segment_used_callback": is_segment_used_callback + }) + %} + + if (is_used_leaf != FALSE) { + // Repeat the code of bytecode_hash_node() for performance reasons, instead of calling it. + let (current_segment_hash) = poseidon_hash_many(n=segment_length, elements=data_ptr); + tempvar range_check_ptr = range_check_ptr; + tempvar poseidon_ptr = poseidon_ptr; + tempvar current_segment_hash = current_segment_hash; + } else { + if (is_segment_used != FALSE) { + let current_segment_hash = bytecode_hash_node( + data_ptr=data_ptr, data_length=segment_length + ); + } else { + // Set the first felt of the bytecode to -1 to make sure that the execution cannot jump + // to this segment (-1 is an invalid opcode). + // The hash in this case is guessed and the actual bytecode is unconstrained (except for + // the first felt). + %{ + # Sanity check. + assert not is_accessed(ids.data_ptr), "The segment is skipped but was accessed." + del memory.data[ids.data_ptr] + %} + assert data_ptr[0] = -1; + + assert [range_check_ptr] = segment_length; + tempvar range_check_ptr = range_check_ptr + 1; + tempvar poseidon_ptr = poseidon_ptr; + tempvar current_segment_hash = nondet %{ bytecode_segment_structure.hash() %}; + } + } + + // Add the segment length and hash to the hash state. + // Use the poseidon builtin directly for performance reasons. + assert poseidon_ptr.input = PoseidonBuiltinState( + s0=poseidon_state.s0 + segment_length, + s1=poseidon_state.s1 + current_segment_hash, + s2=poseidon_state.s2, + ); + let poseidon_state = poseidon_ptr.output; + let poseidon_ptr = &poseidon_ptr[1]; + + %{ vm_exit_scope() %} + + return bytecode_hash_internal_node( + data_ptr=&data_ptr[segment_length], data_length=data_length - segment_length + ); +} + +func hash_entry_points{poseidon_ptr: PoseidonBuiltin*, hash_state: HashState}( + entry_points: CompiledClassEntryPoint*, n_entry_points: felt +) { + let inner_hash_state = hash_init(); + hash_entry_points_inner{hash_state=inner_hash_state}( + entry_points=entry_points, n_entry_points=n_entry_points + ); + let hash: felt = hash_finalize(hash_state=inner_hash_state); + hash_update_single(item=hash); + + return (); +} + +func hash_entry_points_inner{poseidon_ptr: PoseidonBuiltin*, hash_state: HashState}( + entry_points: CompiledClassEntryPoint*, n_entry_points: felt +) { + if (n_entry_points == 0) { + return (); + } + + hash_update_single(item=entry_points.selector); + hash_update_single(item=entry_points.offset); + + // Hash builtins. + hash_update_with_nested_hash( + data_ptr=entry_points.builtin_list, data_length=entry_points.n_builtins + ); + + return hash_entry_points_inner( + entry_points=&entry_points[1], n_entry_points=n_entry_points - 1 + ); +} diff --git a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/hash/blake2s.cairo b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/hash/blake2s.cairo new file mode 100644 index 00000000000..946ffabbd5e --- /dev/null +++ b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/hash/blake2s.cairo @@ -0,0 +1,764 @@ +// This module provides a set of functions to compute the blake2s hash function. +// +// This module is similar to the keccak.cairo module. See more info there. + +from starkware.cairo.common.alloc import alloc +from starkware.cairo.common.cairo_blake2s.packed_blake2s import N_PACKED_INSTANCES, blake2s_compress +from starkware.cairo.common.cairo_builtins import BitwiseBuiltin +from starkware.cairo.common.math import assert_nn_le, split_felt, unsigned_div_rem +from starkware.cairo.common.math_cmp import is_le +from starkware.cairo.common.memcpy import memcpy +from starkware.cairo.common.memset import memset +from starkware.cairo.common.pow import pow +from starkware.cairo.common.registers import get_fp_and_pc, get_label_location +from starkware.cairo.common.uint256 import Uint256 + +const INPUT_BLOCK_FELTS = 16; +const INPUT_BLOCK_BYTES = 64; +const STATE_SIZE_FELTS = 8; +// Each instance consists of 8 words for the input state, 16 words of message, +// 2 words for t0 and f0, and 8 words for the output state. +const INSTANCE_SIZE = STATE_SIZE_FELTS + INPUT_BLOCK_FELTS + 2 + STATE_SIZE_FELTS; + +// Computes blake2s of 'input'. +// To use this function, split the input into words of 32 bits (little endian). +// For example, to compute blake2s('Hello world'), use: +// input = [1819043144, 1870078063, 6581362] +// where: +// 1819043144 == int.from_bytes(b'Hell', 'little') +// 1870078063 == int.from_bytes(b'o wo', 'little') +// 6581362 == int.from_bytes(b'rld', 'little') +// +// Returns the hash as a Uint256. +// +// Note: You must call finalize_blake2s() at the end of the program. Otherwise, this function +// is not sound and a malicious prover may return a wrong result. +// Note: the interface of this function may change in the future. +// Note: Each input word is verified to be in the range [0, 2 ** 32) by this function. +func blake2s{range_check_ptr, blake2s_ptr: felt*}(data: felt*, n_bytes: felt) -> (res: Uint256) { + let (output) = blake2s_as_words(data=data, n_bytes=n_bytes); + let res_low = output[3] * 2 ** 96 + output[2] * 2 ** 64 + output[1] * 2 ** 32 + output[0]; + let res_high = output[7] * 2 ** 96 + output[6] * 2 ** 64 + output[5] * 2 ** 32 + output[4]; + return (res=Uint256(low=res_low, high=res_high)); +} + +// Computes blake2s of 'input', and returns the hash in big endian representation. +// See blake2s(). +// Note that the input is still treated as little endian. +func blake2s_bigend{bitwise_ptr: BitwiseBuiltin*, range_check_ptr, blake2s_ptr: felt*}( + data: felt*, n_bytes: felt +) -> (res: Uint256) { + let (num) = blake2s(data=data, n_bytes=n_bytes); + + // Reverse byte endianness of 128-bit words. + tempvar value = num.high; + assert bitwise_ptr[0].x = value; + assert bitwise_ptr[0].y = 0x00ff00ff00ff00ff00ff00ff00ff00ff; + tempvar value = value + (2 ** 16 - 1) * bitwise_ptr[0].x_and_y; + assert bitwise_ptr[1].x = value; + assert bitwise_ptr[1].y = 0x00ffff0000ffff0000ffff0000ffff00; + tempvar value = value + (2 ** 32 - 1) * bitwise_ptr[1].x_and_y; + assert bitwise_ptr[2].x = value; + assert bitwise_ptr[2].y = 0x00ffffffff00000000ffffffff000000; + tempvar value = value + (2 ** 64 - 1) * bitwise_ptr[2].x_and_y; + assert bitwise_ptr[3].x = value; + assert bitwise_ptr[3].y = 0x00ffffffffffffffff00000000000000; + tempvar value = value + (2 ** 128 - 1) * bitwise_ptr[3].x_and_y; + tempvar high = value / 2 ** (8 + 16 + 32 + 64); + let bitwise_ptr = bitwise_ptr + 4 * BitwiseBuiltin.SIZE; + + tempvar value = num.low; + assert bitwise_ptr[0].x = value; + assert bitwise_ptr[0].y = 0x00ff00ff00ff00ff00ff00ff00ff00ff; + tempvar value = value + (2 ** 16 - 1) * bitwise_ptr[0].x_and_y; + assert bitwise_ptr[1].x = value; + assert bitwise_ptr[1].y = 0x00ffff0000ffff0000ffff0000ffff00; + tempvar value = value + (2 ** 32 - 1) * bitwise_ptr[1].x_and_y; + assert bitwise_ptr[2].x = value; + assert bitwise_ptr[2].y = 0x00ffffffff00000000ffffffff000000; + tempvar value = value + (2 ** 64 - 1) * bitwise_ptr[2].x_and_y; + assert bitwise_ptr[3].x = value; + assert bitwise_ptr[3].y = 0x00ffffffffffffffff00000000000000; + tempvar value = value + (2 ** 128 - 1) * bitwise_ptr[3].x_and_y; + tempvar low = value / 2 ** (8 + 16 + 32 + 64); + let bitwise_ptr = bitwise_ptr + 4 * BitwiseBuiltin.SIZE; + + return (res=Uint256(low=high, high=low)); +} + +// Same as blake2s, but outputs a pointer to 8 32-bit little endian words instead. +func blake2s_as_words{range_check_ptr, blake2s_ptr: felt*}(data: felt*, n_bytes: felt) -> ( + output: felt* +) { + // Set the initial state to IV (IV[0] is modified). + assert blake2s_ptr[0] = 0x6B08E647; // IV[0] ^ 0x01010020 (config: no key, 32 bytes output). + assert blake2s_ptr[1] = 0xBB67AE85; + assert blake2s_ptr[2] = 0x3C6EF372; + assert blake2s_ptr[3] = 0xA54FF53A; + assert blake2s_ptr[4] = 0x510E527F; + assert blake2s_ptr[5] = 0x9B05688C; + assert blake2s_ptr[6] = 0x1F83D9AB; + assert blake2s_ptr[7] = 0x5BE0CD19; + static_assert STATE_SIZE_FELTS == 8; + let blake2s_ptr = blake2s_ptr + STATE_SIZE_FELTS; + + let (output) = blake2s_inner(data=data, n_bytes=n_bytes, counter=0); + return (output=output); +} + +// Inner loop for blake2s. blake2s_ptr points to the middle of an instance: after the initial state, +// before the message. +func blake2s_inner{range_check_ptr, blake2s_ptr: felt*}( + data: felt*, n_bytes: felt, counter: felt +) -> (output: felt*) { + alloc_locals; + let is_last_block = is_le(n_bytes, INPUT_BLOCK_BYTES); + if (is_last_block != 0) { + return blake2s_last_block(data=data, n_bytes=n_bytes, counter=counter); + } + + memcpy(blake2s_ptr, data, INPUT_BLOCK_FELTS); + let blake2s_ptr = blake2s_ptr + INPUT_BLOCK_FELTS; + + assert blake2s_ptr[0] = counter + INPUT_BLOCK_BYTES; // n_bytes. + assert blake2s_ptr[1] = 0; // Is last byte = False. + let blake2s_ptr = blake2s_ptr + 2; + + // Write output. + let output = blake2s_ptr; + %{ + from starkware.cairo.common.cairo_blake2s.blake2s_utils import compute_blake2s_func + compute_blake2s_func(segments=segments, output_ptr=ids.output) + %} + let blake2s_ptr = blake2s_ptr + STATE_SIZE_FELTS; + + // Write the current output to the input state for the next instance. + memcpy(blake2s_ptr, output, STATE_SIZE_FELTS); + let blake2s_ptr = blake2s_ptr + STATE_SIZE_FELTS; + return blake2s_inner( + data=data + INPUT_BLOCK_FELTS, + n_bytes=n_bytes - INPUT_BLOCK_BYTES, + counter=counter + INPUT_BLOCK_BYTES, + ); +} + +func blake2s_last_block{range_check_ptr, blake2s_ptr: felt*}( + data: felt*, n_bytes: felt, counter: felt +) -> (output: felt*) { + alloc_locals; + let (n_felts, _) = unsigned_div_rem(n_bytes + 3, 4); + memcpy(blake2s_ptr, data, n_felts); + memset(blake2s_ptr + n_felts, 0, INPUT_BLOCK_FELTS - n_felts); + let blake2s_ptr = blake2s_ptr + INPUT_BLOCK_FELTS; + + assert blake2s_ptr[0] = counter + n_bytes; // n_bytes. + assert blake2s_ptr[1] = 0xffffffff; // Is last byte = True. + let blake2s_ptr = blake2s_ptr + 2; + + // Write output. + let output = blake2s_ptr; + %{ + from starkware.cairo.common.cairo_blake2s.blake2s_utils import compute_blake2s_func + compute_blake2s_func(segments=segments, output_ptr=ids.output) + %} + let blake2s_ptr = blake2s_ptr + STATE_SIZE_FELTS; + + return (output=output); +} + +// Verifies that the results of blake2s() are valid. +func finalize_blake2s{range_check_ptr, bitwise_ptr: BitwiseBuiltin*}( + blake2s_ptr_start: felt*, blake2s_ptr_end: felt* +) { + alloc_locals; + + let (__fp__, _) = get_fp_and_pc(); + + let (sigma) = _get_sigma(); + + tempvar n = (blake2s_ptr_end - blake2s_ptr_start) / INSTANCE_SIZE; + if (n == 0) { + return (); + } + + %{ + # Add dummy pairs of input and output. + from starkware.cairo.common.cairo_blake2s.blake2s_utils import IV, blake2s_compress + + _n_packed_instances = int(ids.N_PACKED_INSTANCES) + assert 0 <= _n_packed_instances < 20 + _blake2s_input_chunk_size_felts = int(ids.INPUT_BLOCK_FELTS) + assert 0 <= _blake2s_input_chunk_size_felts < 100 + + message = [0] * _blake2s_input_chunk_size_felts + modified_iv = [IV[0] ^ 0x01010020] + IV[1:] + output = blake2s_compress( + message=message, + h=modified_iv, + t0=0, + t1=0, + f0=0xffffffff, + f1=0, + ) + padding = (modified_iv + message + [0, 0xffffffff] + output) * (_n_packed_instances - 1) + segments.write_arg(ids.blake2s_ptr_end, padding) + %} + + // Compute the amount of chunks (rounded up). + let (local n_chunks, _) = unsigned_div_rem(n + N_PACKED_INSTANCES - 1, N_PACKED_INSTANCES); + let blake2s_ptr = blake2s_ptr_start; + _finalize_blake2s_inner{blake2s_ptr=blake2s_ptr}(n=n_chunks, sigma=sigma); + return (); +} + +func _get_sigma() -> (sigma: felt*) { + alloc_locals; + let (sigma_address) = get_label_location(data); + return (sigma=cast(sigma_address, felt*)); + + data: + dw 0; + dw 1; + dw 2; + dw 3; + dw 4; + dw 5; + dw 6; + dw 7; + dw 8; + dw 9; + dw 10; + dw 11; + dw 12; + dw 13; + dw 14; + dw 15; + dw 14; + dw 10; + dw 4; + dw 8; + dw 9; + dw 15; + dw 13; + dw 6; + dw 1; + dw 12; + dw 0; + dw 2; + dw 11; + dw 7; + dw 5; + dw 3; + dw 11; + dw 8; + dw 12; + dw 0; + dw 5; + dw 2; + dw 15; + dw 13; + dw 10; + dw 14; + dw 3; + dw 6; + dw 7; + dw 1; + dw 9; + dw 4; + dw 7; + dw 9; + dw 3; + dw 1; + dw 13; + dw 12; + dw 11; + dw 14; + dw 2; + dw 6; + dw 5; + dw 10; + dw 4; + dw 0; + dw 15; + dw 8; + dw 9; + dw 0; + dw 5; + dw 7; + dw 2; + dw 4; + dw 10; + dw 15; + dw 14; + dw 1; + dw 11; + dw 12; + dw 6; + dw 8; + dw 3; + dw 13; + dw 2; + dw 12; + dw 6; + dw 10; + dw 0; + dw 11; + dw 8; + dw 3; + dw 4; + dw 13; + dw 7; + dw 5; + dw 15; + dw 14; + dw 1; + dw 9; + dw 12; + dw 5; + dw 1; + dw 15; + dw 14; + dw 13; + dw 4; + dw 10; + dw 0; + dw 7; + dw 6; + dw 3; + dw 9; + dw 2; + dw 8; + dw 11; + dw 13; + dw 11; + dw 7; + dw 14; + dw 12; + dw 1; + dw 3; + dw 9; + dw 5; + dw 0; + dw 15; + dw 4; + dw 8; + dw 6; + dw 2; + dw 10; + dw 6; + dw 15; + dw 14; + dw 9; + dw 11; + dw 3; + dw 0; + dw 8; + dw 12; + dw 2; + dw 13; + dw 7; + dw 1; + dw 4; + dw 10; + dw 5; + dw 10; + dw 2; + dw 8; + dw 4; + dw 7; + dw 6; + dw 1; + dw 5; + dw 15; + dw 11; + dw 9; + dw 14; + dw 3; + dw 12; + dw 13; + dw 0; +} + +// Handles n chunks of N_PACKED_INSTANCES blake2s instances. +func _finalize_blake2s_inner{range_check_ptr, bitwise_ptr: BitwiseBuiltin*, blake2s_ptr: felt*}( + n: felt, sigma: felt* +) { + if (n == 0) { + return (); + } + + alloc_locals; + let blake2s_start = blake2s_ptr; + + // Load instance data. + let (local data: felt*) = alloc(); + _pack_ints(INSTANCE_SIZE, data); + + let input_state: felt* = data; + let message: felt* = input_state + STATE_SIZE_FELTS; + let t0_and_f0: felt* = message + INPUT_BLOCK_FELTS; + let output_state: felt* = t0_and_f0 + 2; + + // Run blake2s on N_PACKED_INSTANCES instances. + blake2s_compress( + h=input_state, + message=message, + t0=t0_and_f0[0], + f0=t0_and_f0[1], + sigma=sigma, + output=output_state, + ); + let blake2s_ptr = blake2s_start + INSTANCE_SIZE * N_PACKED_INSTANCES; + + return _finalize_blake2s_inner(n=n - 1, sigma=sigma); +} + +// Given N_PACKED_INSTANCES sets of m (32-bit) integers in the blake2s implicit argument, +// where each set starts at offset INSTANCE_SIZE from the previous set, +// computes m packed integers. +// blake2s_ptr is advanced m steps (just after the first set). +func _pack_ints{range_check_ptr, blake2s_ptr: felt*}(m, packed_values: felt*) { + static_assert N_PACKED_INSTANCES == 7; + alloc_locals; + + local MAX_VALUE = 2 ** 32 - 1; + + tempvar packed_values = packed_values; + tempvar blake2s_ptr = blake2s_ptr; + tempvar range_check_ptr = range_check_ptr; + tempvar m = m; + + loop: + tempvar x0 = blake2s_ptr[0 * INSTANCE_SIZE]; + assert [range_check_ptr + 0] = x0; + assert [range_check_ptr + 1] = MAX_VALUE - x0; + tempvar x1 = blake2s_ptr[1 * INSTANCE_SIZE]; + assert [range_check_ptr + 2] = x1; + assert [range_check_ptr + 3] = MAX_VALUE - x1; + tempvar x2 = blake2s_ptr[2 * INSTANCE_SIZE]; + assert [range_check_ptr + 4] = x2; + assert [range_check_ptr + 5] = MAX_VALUE - x2; + tempvar x3 = blake2s_ptr[3 * INSTANCE_SIZE]; + assert [range_check_ptr + 6] = x3; + assert [range_check_ptr + 7] = MAX_VALUE - x3; + tempvar x4 = blake2s_ptr[4 * INSTANCE_SIZE]; + assert [range_check_ptr + 8] = x4; + assert [range_check_ptr + 9] = MAX_VALUE - x4; + tempvar x5 = blake2s_ptr[5 * INSTANCE_SIZE]; + assert [range_check_ptr + 10] = x5; + assert [range_check_ptr + 11] = MAX_VALUE - x5; + tempvar x6 = blake2s_ptr[6 * INSTANCE_SIZE]; + assert [range_check_ptr + 12] = x6; + assert [range_check_ptr + 13] = MAX_VALUE - x6; + assert packed_values[0] = ( + x0 + + 2 ** 35 * x1 + + 2 ** (35 * 2) * x2 + + 2 ** (35 * 3) * x3 + + 2 ** (35 * 4) * x4 + + 2 ** (35 * 5) * x5 + + 2 ** (35 * 6) * x6 + ); + + tempvar packed_values = packed_values + 1; + tempvar blake2s_ptr = blake2s_ptr + 1; + tempvar range_check_ptr = range_check_ptr + 14; + tempvar m = m - 1; + jmp loop if m != 0; + + return (); +} + +// Helper functions. +// These functions serialize data to a data array to be used with blake2s(). +// They use the property that each data word is verified by blake2s() to be in range [0, 2 ** 32). + +// Serializes a uint256 number in a blake2s compatible way (little-endian). +func blake2s_add_uint256{data: felt*}(num: Uint256) { + let high = num.high; + let low = num.low; + %{ + B = 32 + MASK = 2 ** 32 - 1 + segments.write_arg(ids.data, [(ids.low >> (B * i)) & MASK for i in range(4)]) + segments.write_arg(ids.data + 4, [(ids.high >> (B * i)) & MASK for i in range(4)]) + %} + assert data[3] * 2 ** 96 + data[2] * 2 ** 64 + data[1] * 2 ** 32 + data[0] = low; + assert data[7] * 2 ** 96 + data[6] * 2 ** 64 + data[5] * 2 ** 32 + data[4] = high; + let data = data + 8; + return (); +} + +// Serializes a uint256 number in a blake2s compatible way (big-endian). +func blake2s_add_uint256_bigend{bitwise_ptr: BitwiseBuiltin*, data: felt*}(num: Uint256) { + // Reverse byte endianness of 32-bit chunks. + tempvar value = num.high; + assert bitwise_ptr[0].x = value; + assert bitwise_ptr[0].y = 0x00ff00ff00ff00ff00ff00ff00ff00ff; + tempvar value = value + (2 ** 16 - 1) * bitwise_ptr[0].x_and_y; + assert bitwise_ptr[1].x = value; + assert bitwise_ptr[1].y = 0x00ffff0000ffff0000ffff0000ffff00; + tempvar value = value + (2 ** 32 - 1) * bitwise_ptr[1].x_and_y; + tempvar high = value / 2 ** (8 + 16); + + tempvar value = num.low; + assert bitwise_ptr[2].x = value; + assert bitwise_ptr[2].y = 0x00ff00ff00ff00ff00ff00ff00ff00ff; + tempvar value = value + (2 ** 16 - 1) * bitwise_ptr[2].x_and_y; + assert bitwise_ptr[3].x = value; + assert bitwise_ptr[3].y = 0x00ffff0000ffff0000ffff0000ffff00; + tempvar value = value + (2 ** 32 - 1) * bitwise_ptr[3].x_and_y; + tempvar low = value / 2 ** (8 + 16); + + let bitwise_ptr = bitwise_ptr + 4 * BitwiseBuiltin.SIZE; + + %{ + B = 32 + MASK = 2 ** 32 - 1 + segments.write_arg(ids.data, [(ids.high >> (B * (3 - i))) & MASK for i in range(4)]) + segments.write_arg(ids.data + 4, [(ids.low >> (B * (3 - i))) & MASK for i in range(4)]) + %} + + assert data[0] * 2 ** 96 + data[1] * 2 ** 64 + data[2] * 2 ** 32 + data[3] = high; + assert data[4] * 2 ** 96 + data[5] * 2 ** 64 + data[6] * 2 ** 32 + data[7] = low; + let data = data + 8; + return (); +} + +// Serializes a field element in a blake2s compatible way. +func blake2s_add_felt{range_check_ptr, bitwise_ptr: BitwiseBuiltin*, data: felt*}( + num: felt, bigend: felt +) { + let (high, low) = split_felt(num); + if (bigend != 0) { + blake2s_add_uint256_bigend(Uint256(low=low, high=high)); + return (); + } else { + blake2s_add_uint256(Uint256(low=low, high=high)); + return (); + } +} + +// Serializes multiple field elements in a blake2s compatible way. +// Note: This function does not serialize the number of elements. If desired, this is the caller's +// responsibility. +func blake2s_add_felts{range_check_ptr, bitwise_ptr: BitwiseBuiltin*, data: felt*}( + n_elements: felt, elements: felt*, bigend: felt +) -> () { + if (n_elements == 0) { + return (); + } + blake2s_add_felt(num=elements[0], bigend=bigend); + return blake2s_add_felts(n_elements=n_elements - 1, elements=&elements[1], bigend=bigend); +} + +// Computes the blake2s hash for multiple field elements. +func blake2s_felts{range_check_ptr, bitwise_ptr: BitwiseBuiltin*, blake2s_ptr: felt*}( + n_elements: felt, elements: felt*, bigend: felt +) -> (res: Uint256) { + alloc_locals; + let (data) = alloc(); + let data_start = data; + with data { + blake2s_add_felts(n_elements=n_elements, elements=elements, bigend=bigend); + } + let (res) = blake2s(data=data_start, n_bytes=n_elements * 32); + return (res=res); +} + +// Takes an array of `packed_values_len` felt252s at `packed_values` and encodes them into an array +// of u32s at `unpacked_u32s` in the following way: +// * If a felt is less than 2^63, it's unpacked to 2 felts, each representing 32 bits. +// * Otherwise, it's unpacked into 8 felts, each under 2^32, where the most significant +// limb has its MSB set (Note that the prime is less than 2^255 so the MSB could not be +// set prior to this intervention). +// All 32-bit limbs are arranged in big-endian order. +// Returns the size of the unpacked array in felts. +// Assumes: +// * All output felts in `upnacked_u32s` are extrenally verified to be in [0, 2^32). +// Note: This function can nondeterministically choose between several encodings of felts, +// x < PRIME can be encoded as x + PRIME, x + 2 * PRIME, etc. The canonical encoding is +// given when x < PRIME. +func encode_felt252_to_u32s{range_check_ptr: felt}( + packed_values_len: felt, packed_values: felt*, unpacked_u32s: felt* +) -> felt { + alloc_locals; + + local U63_MAX = 2 ** 63 - 1; + local EXP31 = 2 ** 31; + local end = cast(packed_values, felt) + packed_values_len; + + %{ + offset = 0 + for i in range(ids.packed_values_len): + val = (memory[ids.packed_values + i] % PRIME) + val_len = 2 if val < 2**63 else 8 + if val_len == 8: + val += 2**255 + for i in range(val_len - 1, -1, -1): + val, memory[ids.unpacked_u32s + offset + i] = divmod(val, 2**32) + assert val == 0 + offset += val_len + %} + tempvar out = unpacked_u32s; + tempvar packed_values = packed_values; + tempvar range_check_ptr = range_check_ptr; + + loop: + // Guess if number is small or big. + if (nondet %{ (ids.end != ids.packed_values) and (memory[ids.packed_values] < 2**63) %} != 0) { + // Unpack small felt. + + tempvar current_val = packed_values[0]; + // Assert that the value is in [0, 2^63). + assert [range_check_ptr] = U63_MAX - current_val; + // Assert that the limbs represent the number. + assert current_val = out[1] + 2 ** 32 * out[0]; + + tempvar out = &out[2]; + tempvar packed_values = &packed_values[1]; + tempvar range_check_ptr = range_check_ptr + 1; + jmp loop; + } + + if (end - cast(packed_values, felt) == 0) { + return out - unpacked_u32s; + } + + // Handle big felt. + // Assert that the top limb is over 2^31, as its MSB is artificially set for encoding. + tempvar raw_out_0 = out[0] - EXP31; + assert [range_check_ptr] = raw_out_0; + // Assert that the limbs represent the number. Set the MSB of the most significant limb. + assert packed_values[0] = ( + (out[7] + (2 ** 32 * out[6])) + + 2 ** (32 * 2) * (out[5] + 2 ** 32 * out[4]) + + 2 ** (32 * 4) * (out[3] + 2 ** 32 * out[2]) + + 2 ** (32 * 6) * (out[1] + 2 ** 32 * raw_out_0) + ); + + tempvar out = &out[8]; + tempvar packed_values = &packed_values[1]; + tempvar range_check_ptr = range_check_ptr + 1; + jmp loop; +} + +const OP1_AP = 4; +const BLAKE2S_OPCODE_EXT = 1; +const BLAKE2S_FINALIZE_OPCODE_EXT = 2; +const BLAKE2S_AP_FLAGS = OP1_AP * (2 ** 2); + +const OFF_MINUS_1 = 2 ** 15 - 1; +const OFF_MINUS_2 = 2 ** 15 - 2; +const OFF_MINUS_3 = 2 ** 15 - 3; +const OFF_MINUS_4 = 2 ** 15 - 4; + +const COUNTER_OFFSET = 1; +const STATE_OFFSET = 2 ** 16; +const MESSAGE_OFFSET = 2 ** 32; +const FLAGS_OFFSET = 2 ** 48; +const OPCODE_EXT_OFFSET = 2 ** 63; + +const BLAKE2S_INSTRUCTION = OFF_MINUS_1 * COUNTER_OFFSET + OFF_MINUS_4 * STATE_OFFSET + + OFF_MINUS_3 * MESSAGE_OFFSET + BLAKE2S_AP_FLAGS * FLAGS_OFFSET + BLAKE2S_OPCODE_EXT * + OPCODE_EXT_OFFSET; +const BLAKE2S_FINALIZE_INSTRUCTION = OFF_MINUS_1 * COUNTER_OFFSET + OFF_MINUS_3 * STATE_OFFSET + + OFF_MINUS_2 * MESSAGE_OFFSET + BLAKE2S_AP_FLAGS * FLAGS_OFFSET + BLAKE2S_FINALIZE_OPCODE_EXT * + OPCODE_EXT_OFFSET; + +// Computes blake2s of `input` of size `len` felts, representing 32 bits each. +// Note: this function guarantees that len > 0. +func blake_with_opcode{range_check_ptr}(len: felt, data: felt*, out: felt*) { + alloc_locals; + if (len == 0) { + // hash32 = [105,33,122,48, 121,144,128,148, 225,17,33,208, 66,53,74,124, + // 31,85,182,72, 44,161,165,30, 27,37,13,253, 30,208,238,249] + // as little-endian u32s: + assert [out + 0] = 813310313; // 0x307A2169 + assert [out + 1] = 2491453561; // 0x94809079 + assert [out + 2] = 3491828193; // 0xD02111E1 + assert [out + 3] = 2085238082; // 0x7C4A3542 + assert [out + 4] = 1219908895; // 0x48B6551F + assert [out + 5] = 514171180; // 0x1EA5A12C + assert [out + 6] = 4245497115; // 0xFD0D251B + assert [out + 7] = 4193177630; // 0xF9EED01E + return (); + } + + let (local state: felt*) = alloc(); + assert state[0] = 0x6B08E647; // IV[0] ^ 0x01010020 (config: no key, 32 bytes output). + assert state[1] = 0xBB67AE85; + assert state[2] = 0x3C6EF372; + assert state[3] = 0xA54FF53A; + assert state[4] = 0x510E527F; + assert state[5] = 0x9B05688C; + assert state[6] = 0x1F83D9AB; + assert state[7] = 0x5BE0CD19; + + // Express the length in bytes, subtract the remainder for finalize. + let (_, rem) = unsigned_div_rem(len - 1, 16); + local rem = rem + 1; + local len_in_bytes = (len - rem) * 4; + + local range_check_ptr = range_check_ptr; + + // Copy remaining data and pad with zeroes. + let (local final_data: felt*) = alloc(); + memcpy(final_data, &data[len - rem], rem); + memset(&final_data[rem], 0, 16 - rem); + + tempvar counter = 0; + tempvar state = state; + tempvar data = data; + + loop: + if (counter - len_in_bytes == 0) { + // Add remainder bytes to counter. + tempvar counter = counter + (rem * 4); + [ap] = state, ap++; + [ap] = final_data, ap++; + [ap] = counter, ap++; + [ap] = out; + dw BLAKE2S_FINALIZE_INSTRUCTION; + // Increment AP after blake opcode. + ap += 1; + + let range_check_ptr = [fp + 3]; + return (); + } + + tempvar counter = counter + 64; + + // Blake output pointer / the next state. + [ap] = &state[8]; + dw BLAKE2S_INSTRUCTION; + + let state = cast([ap - 4], felt*); + let data = cast([ap - 3], felt*); + + // Increment AP after blake opcode. + ap += 1; + + tempvar data = data + 16; + jmp loop; +} + +// Given `data_len` felt252s at `data`, encodes them as u32s as defined in `encode_felt252_to_u32s` +// and computes the blake2s hash of the result using the dedicated opcodes. +// The result is then returned as a 224-bit felt, ignoring the last 32 bits. +func encode_felt252_data_and_calc_224_bit_blake_hash{range_check_ptr: felt}( + data_len: felt, data: felt* +) -> (hash: felt) { + alloc_locals; + let (local encoded_data: felt*) = alloc(); + let encoded_data_len = encode_felt252_to_u32s( + packed_values_len=data_len, packed_values=data, unpacked_u32s=encoded_data + ); + let (local blake_output: felt*) = alloc(); + blake_with_opcode(len=encoded_data_len, data=encoded_data, out=blake_output); + return ( + hash=blake_output[6] * 2 ** 192 + blake_output[5] * 2 ** 160 + blake_output[4] * 2 ** 128 + + blake_output[3] * 2 ** 96 + blake_output[2] * 2 ** 64 + blake_output[1] * 2 ** 32 + + blake_output[0], + ); +} diff --git a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/hash/hash_state_blake.cairo b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/hash/hash_state_blake.cairo new file mode 100644 index 00000000000..b7376080288 --- /dev/null +++ b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/hash/hash_state_blake.cairo @@ -0,0 +1,45 @@ +from starkware.cairo.common.alloc import alloc +from starkware.cairo.common.cairo_blake2s.blake2s import ( + encode_felt252_data_and_calc_224_bit_blake_hash, +) +from starkware.cairo.common.memcpy import memcpy + +// Stores a sequence of elements. New elements can be added to the hash state using +// hash_update() and hash_update_single(). +// The final hash of the entire sequence can be obtained using hash_finalize(). +struct HashState { + start: felt*, + end: felt*, +} + +// Initializes a new HashState with no elements and returns it. +func hash_init() -> HashState { + let (start: felt*) = alloc(); + return (HashState(start=start, end=start)); +} + +// Adds a single item to the HashState. +func hash_update_single{hash_state: HashState}(item: felt) { + let current_end = hash_state.end; + assert [current_end] = item; + let hash_state = HashState(start=hash_state.start, end=current_end + 1); + return (); +} + +func hash_update_with_nested_hash{hash_state: HashState, range_check_ptr: felt}( + data_ptr: felt*, data_length: felt +) { + let (hash) = encode_felt252_data_and_calc_224_bit_blake_hash( + data_len=data_length, data=data_ptr + ); + hash_update_single(item=hash); + return (); +} + +func hash_finalize{range_check_ptr: felt}(hash_state: HashState) -> felt { + let data_length = hash_state.end - hash_state.start; + let (hash) = encode_felt252_data_and_calc_224_bit_blake_hash( + data_len=data_length, data=hash_state.start + ); + return (hash); +} diff --git a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/os.cairo b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/os.cairo index f9c667d0ab2..0b2106621dd 100644 --- a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/os.cairo +++ b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/os.cairo @@ -46,6 +46,7 @@ from starkware.starknet.core.os.output import ( ) from starkware.starknet.core.os.state.commitment import StateEntry from starkware.starknet.core.os.state.state import OsStateUpdate, state_update +from starkware.starknet.core.os.hash.blake2s import encode_felt252_data_and_calc_224_bit_blake_hash // Executes transactions on Starknet. func main{ diff --git a/crates/apollo_starknet_os_program/src/program_hash.json b/crates/apollo_starknet_os_program/src/program_hash.json index ec5ae3ca0f2..802832d30f7 100644 --- a/crates/apollo_starknet_os_program/src/program_hash.json +++ b/crates/apollo_starknet_os_program/src/program_hash.json @@ -1,5 +1,5 @@ { - "os": "0x1476ad28ba9b7927f7be86f42ad1ec3a6b357c8cadccbef4850ed1cc5ad0d8", + "os": "0x4d52c0138abffbde5666bef62efb969583fe3cc07d5b936191f7b8bcc40e1e0", "aggregator": "0x181986bfe23bbfdac56c40522bd5c2b1b64c824c74bd6722a3ccacc8cc888e2", "aggregator_with_prefix": "0x1ae51bd7157e0754f1e1729806ad5579a7ac6926cd0c8dd342ad1e7a3758c2e" -} +} \ No newline at end of file diff --git a/crates/starknet_os/Cargo.toml b/crates/starknet_os/Cargo.toml index 286b72dac04..3d00bd2e7df 100644 --- a/crates/starknet_os/Cargo.toml +++ b/crates/starknet_os/Cargo.toml @@ -23,6 +23,7 @@ ark-ff.workspace = true ark-poly.workspace = true ark-secp256k1.workspace = true ark-secp256r1.workspace = true +blake2s.workspace = true blockifier.workspace = true c-kzg.workspace = true cairo-lang-casm.workspace = true diff --git a/crates/starknet_os/src/hints/enum_definition.rs b/crates/starknet_os/src/hints/enum_definition.rs index 871ae74575b..7f470062691 100644 --- a/crates/starknet_os/src/hints/enum_definition.rs +++ b/crates/starknet_os/src/hints/enum_definition.rs @@ -19,6 +19,7 @@ use crate::hints::hint_implementation::aggregator::{ set_state_update_pointers_to_none, write_da_segment, }; +use crate::hints::hint_implementation::blake2s::implementation::{check_packed_values_end_and_size, unpack_felts_to_u32s}; use crate::hints::hint_implementation::block_context::{ block_number, block_timestamp, @@ -882,6 +883,26 @@ ids.initial_carried_outputs = segments.gen_arg( [segments.add_temp_segment(), segments.add_temp_segment()] )"# ), + ( + UnpackFeltsToU32s, + unpack_felts_to_u32s, + indoc! {r#"offset = 0 +for i in range(ids.packed_values_len): + val = (memory[ids.packed_values + i] % PRIME) + val_len = 2 if val < 2**63 else 8 + if val_len == 8: + val += 2**255 + for i in range(val_len - 1, -1, -1): + val, memory[ids.unpacked_u32s + offset + i] = divmod(val, 2**32) + assert val == 0 + offset += val_len"#} + ), + ( + CheckPackedValuesEndAndSize, + check_packed_values_end_and_size, + "memory[ap] = to_felt_or_relocatable((ids.end != ids.packed_values) and \ + (memory[ids.packed_values] < 2**63))" + ), ); define_common_hint_enum!( @@ -1458,7 +1479,6 @@ ids.contract_class_component_hashes = segments.gen_arg(class_component_hashes)"# execution_helper.os_logger.enter_syscall( n_steps=current_step, builtin_ptrs=ids.builtin_ptrs, - range_check_ptr=ids.range_check_ptr, deprecated=False, selector=ids.selector, ) diff --git a/crates/starknet_os/src/hints/hint_implementation.rs b/crates/starknet_os/src/hints/hint_implementation.rs index e39413cfd0b..2c0fedcb5fd 100644 --- a/crates/starknet_os/src/hints/hint_implementation.rs +++ b/crates/starknet_os/src/hints/hint_implementation.rs @@ -1,4 +1,5 @@ pub(crate) mod aggregator; +pub(crate) mod blake2s; pub(crate) mod block_context; pub(crate) mod bls_field; pub(crate) mod builtins; diff --git a/crates/starknet_os/src/hints/hint_implementation/blake2s.rs b/crates/starknet_os/src/hints/hint_implementation/blake2s.rs new file mode 100644 index 00000000000..f5a5ba67a28 --- /dev/null +++ b/crates/starknet_os/src/hints/hint_implementation/blake2s.rs @@ -0,0 +1,3 @@ +#[cfg(test)] +pub(crate) mod blake2s_test; +pub(crate) mod implementation; diff --git a/crates/starknet_os/src/hints/hint_implementation/blake2s/blake2s_test.rs b/crates/starknet_os/src/hints/hint_implementation/blake2s/blake2s_test.rs new file mode 100644 index 00000000000..78a12f01b2f --- /dev/null +++ b/crates/starknet_os/src/hints/hint_implementation/blake2s/blake2s_test.rs @@ -0,0 +1,118 @@ +use std::collections::HashMap; + +use apollo_starknet_os_program::OS_PROGRAM_BYTES; +use blake2s::encode_felt252_data_and_calc_224_bit_blake_hash; +use cairo_vm::types::builtin_name::BuiltinName; +use cairo_vm::types::layout_name::LayoutName; +use cairo_vm::types::relocatable::MaybeRelocatable; +use rstest::rstest; +use starknet_types_core::felt::Felt; + +use crate::test_utils::cairo_runner::{ + initialize_and_run_cairo_0_entry_point, + EndpointArg, + EntryPointRunnerConfig, + ImplicitArg, + PointerArg, + ValueArg, +}; + +/// Test that compares Cairo and Rust implementations of +/// encode_felt252_data_and_calc_224_bit_blake_hash. +#[rstest] +#[case::empty(vec![])] +#[case::boundary_under_2_63(vec![Felt::from((1u64 << 63) - 1)])] +#[case::boundary_at_2_63(vec![Felt::from(1u64 << 63)])] +#[case::very_large_felt(vec![Felt::from_hex("0x800000000000011000000000000000000000000000000000000000000000000").unwrap()])] +#[case::many_felts( + vec![ + Felt::from(1), + Felt::from(2), + Felt::from(3), + Felt::from(4), + Felt::from(5), + Felt::from(6), + Felt::from(7), + Felt::from(8), + Felt::from(9), + Felt::from(10), + Felt::from(11), + Felt::from(12), + Felt::from(13), + Felt::from(14), + Felt::from(15), + ] +)] +#[case::mixed_small_large(vec![Felt::from(42), Felt::from(1u64 << 63), Felt::from(1337)])] +#[case::max_u64(vec![Felt::from(u64::MAX)])] +fn test_cairo_vs_rust_blake2s_implementation(#[case] test_data: Vec) { + let runner_config = EntryPointRunnerConfig { + layout: LayoutName::all_cairo, + trace_enabled: false, + verify_secure: false, + proof_mode: false, + add_main_prefix_to_entrypoint: false, + }; + + // Get the OS program as bytes + let program_bytes = OS_PROGRAM_BYTES; + + // Calculate hash using Rust implementation + let rust_hash = encode_felt252_data_and_calc_224_bit_blake_hash(&test_data); + + // Calculate hash using Cairo implementation + let data_len = test_data.len(); + let explicit_args = vec![ + EndpointArg::from(Felt::from(data_len)), // data_len + EndpointArg::Pointer(PointerArg::Array( + test_data.iter().map(|&felt| MaybeRelocatable::from(felt)).collect(), + )), // data array + ]; + + let implicit_args = vec![ImplicitArg::Builtin(BuiltinName::range_check)]; + + let expected_return_values = vec![EndpointArg::from(Felt::ZERO)]; // Placeholder + + let hint_locals: HashMap> = HashMap::new(); + + // Call the Cairo function + let result = initialize_and_run_cairo_0_entry_point( + &runner_config, + program_bytes, + "starkware.starknet.core.os.hash.blake2s.encode_felt252_data_and_calc_224_bit_blake_hash", + &explicit_args, + &implicit_args, + &expected_return_values, + hint_locals, + None, // state_reader + ); + + match result { + Ok((_explicit_return_values, implicit_return_values, _)) => { + assert_eq!( + implicit_return_values.len(), + 1, + "Expected exactly one implicit return value" + ); + + match &implicit_return_values[0] { + EndpointArg::Value(ValueArg::Single(cairo_hash)) => { + if let MaybeRelocatable::Int(cairo_hash_felt) = cairo_hash { + println!("Rust hash: {}, Cairo hash: {}", rust_hash, cairo_hash_felt); + assert_eq!( + rust_hash, *cairo_hash_felt, + "Blake2s hash mismatch: Rust={}, Cairo={}", + rust_hash, cairo_hash_felt + ); + } else { + panic!("Expected an integer value, got relocatable"); + } + } + _ => panic!("Expected a single felt return value"), + } + } + Err(e) => { + panic!("Failed to run Cairo blake2s function: {:?}", e); + } + } +} diff --git a/crates/starknet_os/src/hints/hint_implementation/blake2s/implementation.rs b/crates/starknet_os/src/hints/hint_implementation/blake2s/implementation.rs new file mode 100644 index 00000000000..5bd25660038 --- /dev/null +++ b/crates/starknet_os/src/hints/hint_implementation/blake2s/implementation.rs @@ -0,0 +1,74 @@ +use cairo_vm::hint_processor::builtin_hint_processor::hint_utils::{ + get_integer_from_var_name, + get_ptr_from_var_name, + insert_value_into_ap, +}; +use cairo_vm::hint_processor::hint_processor_utils::felt_to_usize; +use cairo_vm::types::relocatable::MaybeRelocatable; +use cairo_vm::vm::errors::hint_errors::HintError; +use cairo_vm::Felt252; +use num_bigint::BigUint; +use num_integer::Integer; + +use crate::hints::error::OsHintResult; +use crate::hints::types::HintArgs; + +/// Unpacks felt values into u32 arrays for Blake2s processing. +/// This implements the Cairo hint that converts felt values to u32 arrays +/// following the Blake2s encoding scheme. +pub(crate) fn unpack_felts_to_u32s( + HintArgs { vm, ids_data, ap_tracking, .. }: HintArgs<'_>, +) -> OsHintResult { + let packed_values_len = + get_integer_from_var_name("packed_values_len", vm, ids_data, ap_tracking)?; + let packed_values = get_ptr_from_var_name("packed_values", vm, ids_data, ap_tracking)?; + let unpacked_u32s = get_ptr_from_var_name("unpacked_u32s", vm, ids_data, ap_tracking)?; + + let vals = vm.get_integer_range(packed_values, felt_to_usize(&packed_values_len)?)?; + let pow2_32 = BigUint::from(1_u32) << 32; + let pow2_63 = BigUint::from(1_u32) << 63; + let pow2_255 = BigUint::from(1_u32) << 255; + + // Split value into either 2 or 8 32-bit limbs. + let out: Vec = vals + .into_iter() + .map(|val| val.to_biguint()) + .flat_map(|val| { + if val < pow2_63 { + let (high, low) = val.div_rem(&pow2_32); + vec![high, low] + } else { + let mut limbs = vec![BigUint::from(0_u32); 8]; + let mut val: BigUint = val + &pow2_255; + for limb in limbs.iter_mut().rev() { + let (q, r) = val.div_rem(&pow2_32); + *limb = r; + val = q; + } + limbs + } + }) + .map(Felt252::from) + .map(MaybeRelocatable::from) + .collect(); + + vm.load_data(unpacked_u32s, &out).map_err(HintError::Memory)?; + Ok(()) +} + +/// Checks if we've reached the end of packed_values and if the current value is small (< 2^63). +/// This implements the Cairo hint that determines loop continuation and value size. +pub(crate) fn check_packed_values_end_and_size( + HintArgs { vm, ids_data, ap_tracking, .. }: HintArgs<'_>, +) -> OsHintResult { + let end = get_ptr_from_var_name("end", vm, ids_data, ap_tracking)?; + let packed_values = get_ptr_from_var_name("packed_values", vm, ids_data, ap_tracking)?; + + if end == packed_values { + insert_value_into_ap(vm, 0)? + } else { + let val = vm.get_integer(packed_values)?; + insert_value_into_ap(vm, (val.to_biguint() < (BigUint::from(1_u32) << 63)) as usize)? + } + Ok(()) +} diff --git a/crates/starknet_os/src/hints/vars.rs b/crates/starknet_os/src/hints/vars.rs index bf11fe5784a..7464a3dc922 100644 --- a/crates/starknet_os/src/hints/vars.rs +++ b/crates/starknet_os/src/hints/vars.rs @@ -158,6 +158,7 @@ define_string_enum! { (Edge), (ElmBound), (ElmSize), + (End), (EntryPointReturnValues), (Evals), (ExecutionContext), @@ -206,6 +207,8 @@ define_string_enum! { (OsStateUpdate), (OutputPtr), (PackedFelt), + (PackedValues), + (PackedValuesLen), (Path), (PrevOffset), (PrevAliasesStateEntry), @@ -251,6 +254,7 @@ define_string_enum! { (TxInfo), (TxType), (UpdatePtr), + (UnpackedU32s), (UseKzgDa), (Value), (Word), @@ -294,7 +298,7 @@ define_string_enum! { ), ( CompiledClassVersion, - "starkware.starknet.core.os.contract_class.compiled_class.COMPILED_CLASS_VERSION" + "starkware.starknet.core.os.contract_class.compiled_class_struct.COMPILED_CLASS_VERSION" ), ( DeprecatedCompiledClassVersion, @@ -364,14 +368,14 @@ define_string_enum! { (BuiltinParamsPtr, "starkware.starknet.core.os.builtins.BuiltinParams*"), (BuiltinPointersPtr, "starkware.starknet.core.os.builtins.BuiltinPointers*"), (CallContractResponse, "starkware.starknet.common.new_syscalls.CallContractResponse"), - (CompiledClass, "starkware.starknet.core.os.contract_class.compiled_class.CompiledClass"), + (CompiledClass, "starkware.starknet.core.os.contract_class.compiled_class_struct.CompiledClass"), ( CompiledClassEntryPoint, - "starkware.starknet.core.os.contract_class.compiled_class.CompiledClassEntryPoint" + "starkware.starknet.core.os.contract_class.compiled_class_struct.CompiledClassEntryPoint" ), ( CompiledClassFact, - "starkware.starknet.core.os.contract_class.compiled_class.CompiledClassFact" + "starkware.starknet.core.os.contract_class.compiled_class_struct.CompiledClassFact" ), (DeployResponse, "starkware.starknet.common.new_syscalls.DeployResponse"), (DeprecatedCallContractResponse, "starkware.starknet.common.syscalls.CallContractResponse"),