From b5d565cdce393ea73fbf1cca6f3d18284ad318c1 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 27 Aug 2026 14:37:19 +0000 Subject: [PATCH] apollo_starknet_os_program,starknet_os,blockifier: emit the single-proof digest in the OS output The OS records each invoke transaction's proof facts during execution. At block end it asserts at most one transaction contributed, computes the contributing transaction's processed-proof output digest, and emits it packed in OsOutputHeader together with the contributing-transaction count. A block with no contributing transactions emits zeros; the virtual OS always emits zeros. The multiblock OS and the aggregator sum the per-block counts and packed values, assert the total is at most one, and carry the single contributing block's values through. The Rust side parses the new header fields, writes them in the aggregator, and asserts every OS run's emitted values against the Rust digest mirror in the flow tests. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01XmPJM3Wph4QLmFmhcxVsh4 --- .../core/aggregator/combine_blocks.cairo | 62 ++++++++++++++++++- .../starknet/core/os/constants.cairo | 2 +- .../os/execution/execute_transactions.cairo | 2 + .../execute_transactions_inner.cairo | 2 + .../execute_transactions_inner__virtual.cairo | 2 + .../core/os/execution/transaction_impls.cairo | 7 +++ .../cairo/starkware/starknet/core/os/os.cairo | 21 ++++++- .../starkware/starknet/core/os/os_utils.cairo | 6 ++ .../starknet/core/os/os_utils__virtual.cairo | 7 +++ .../starkware/starknet/core/os/output.cairo | 6 ++ .../starknet/core/os/proof_fact_fold.cairo | 51 +++++++++++++++ .../src/program_hash.json | 8 +-- .../src/virtual_os_test.rs | 4 +- ...blockifier_versioned_constants_0_14_5.json | 4 +- .../hint_implementation/aggregator/test.rs | 9 +++ .../hint_implementation/aggregator/utils.rs | 3 + crates/starknet_os/src/io/os_output.rs | 12 ++++ crates/starknet_os/src/proof_fact_fold.rs | 9 +++ .../starknet_os/src/proof_fact_fold_test.rs | 28 +++++++++ .../src/test_manager.rs | 46 ++++++++++++++ 20 files changed, 279 insertions(+), 12 deletions(-) diff --git a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/aggregator/combine_blocks.cairo b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/aggregator/combine_blocks.cairo index 2316ec66f4f..10d727622a6 100644 --- a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/aggregator/combine_blocks.cairo +++ b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/aggregator/combine_blocks.cairo @@ -89,6 +89,9 @@ func combine_blocks{range_check_ptr}( starknet_os_config_hash=first.header.starknet_os_config_hash, use_kzg_da=use_kzg_da, full_output=full_output, + processed_proof_output_low=0, + processed_proof_output_high=0, + n_proof_facts_transactions=0, ), squashed_os_state_update=first.squashed_os_state_update, initial_carried_outputs=initial_carried_outputs, @@ -98,6 +101,13 @@ func combine_blocks{range_check_ptr}( let res = combine_blocks_inner(aggregated=aggregated, n=n - 1, os_outputs=&os_outputs[1]); local res_state_update: SquashedOsStateUpdate = [res.squashed_os_state_update]; + let ( + local n_proof_facts_transactions, + local processed_proof_output_low, + local processed_proof_output_high, + ) = combine_proof_facts_folds(n=n, os_outputs=os_outputs); + local res_header: OsOutputHeader* = res.header; + %{ SetStateUpdatePointersToNone %} // Squash the contract state diff dict. @@ -117,7 +127,20 @@ func combine_blocks{range_check_ptr}( ); tempvar squashed_res = new OsOutput( - header=res.header, + header=new OsOutputHeader( + state_update_output=res_header.state_update_output, + prev_block_number=res_header.prev_block_number, + new_block_number=res_header.new_block_number, + prev_block_hash=res_header.prev_block_hash, + new_block_hash=res_header.new_block_hash, + os_program_hash=res_header.os_program_hash, + starknet_os_config_hash=res_header.starknet_os_config_hash, + use_kzg_da=res_header.use_kzg_da, + full_output=res_header.full_output, + processed_proof_output_low=processed_proof_output_low, + processed_proof_output_high=processed_proof_output_high, + n_proof_facts_transactions=n_proof_facts_transactions, + ), squashed_os_state_update=new SquashedOsStateUpdate( contract_state_changes=squashed_contract_state_dict, n_contract_state_changes=n_contract_state_changes, @@ -146,7 +169,7 @@ func combine_blocks_inner(aggregated: OsOutput*, n: felt, os_outputs: OsOutput*) // Check the size of `OsOutput` and `OsOutputHeader` to ensure that if new fields are added // they are handled by the aggregator. static_assert OsOutput.SIZE == 4; - static_assert OsOutputHeader.SIZE == 9; + static_assert OsOutputHeader.SIZE == 12; // Validate fields of the inner OS output of a single task. assert current_header.use_kzg_da = 0; @@ -192,6 +215,9 @@ func combine_blocks_inner(aggregated: OsOutput*, n: felt, os_outputs: OsOutput*) starknet_os_config_hash=aggregated_header.starknet_os_config_hash, use_kzg_da=aggregated_header.use_kzg_da, full_output=aggregated_header.full_output, + processed_proof_output_low=0, + processed_proof_output_high=0, + n_proof_facts_transactions=0, ), squashed_os_state_update=new SquashedOsStateUpdate( contract_state_changes=aggregated_update.contract_state_changes, @@ -207,3 +233,35 @@ func combine_blocks_inner(aggregated: OsOutput*, n: felt, os_outputs: OsOutput*) return combine_blocks_inner(aggregated=new_aggregated, n=n - 1, os_outputs=&os_outputs[1]); } + +func combine_proof_facts_folds(n: felt, os_outputs: OsOutput*) -> ( + n_proof_facts_transactions: felt, root_output_low: felt, root_output_high: felt +) { + alloc_locals; + let ( + local n_proof_facts_transactions, local root_output_low, local root_output_high + ) = sum_block_proof_facts_outputs(n=n, os_outputs=os_outputs); + assert n_proof_facts_transactions * (n_proof_facts_transactions - 1) = 0; + return ( + n_proof_facts_transactions=n_proof_facts_transactions, + root_output_low=root_output_low, + root_output_high=root_output_high, + ); +} + +func sum_block_proof_facts_outputs(n: felt, os_outputs: OsOutput*) -> ( + n_proof_facts_transactions: felt, root_output_low: felt, root_output_high: felt +) { + if (n == 0) { + return (n_proof_facts_transactions=0, root_output_low=0, root_output_high=0); + } + let ( + rest_n_transactions, rest_root_output_low, rest_root_output_high + ) = sum_block_proof_facts_outputs(n=n - 1, os_outputs=&os_outputs[1]); + let header = os_outputs[0].header; + return ( + n_proof_facts_transactions=rest_n_transactions + header.n_proof_facts_transactions, + root_output_low=rest_root_output_low + header.processed_proof_output_low, + root_output_high=rest_root_output_high + header.processed_proof_output_high, + ); +} diff --git a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/constants.cairo b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/constants.cairo index 96835943c9d..b1280eca975 100644 --- a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/constants.cairo +++ b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/constants.cairo @@ -66,7 +66,7 @@ const STORED_BLOCK_HASH_BUFFER = 10; // Allowed virtual OS program hashes for client-side proving. const ALLOWED_VIRTUAL_OS_PROGRAM_HASHES_0 = ( - 0x053f6c9fcfd31d27279ff7d7e422b44623550a732b59fe193354a7316a96daa1 + 0x07dc1bd0f3938f5f2f5e7c14118402cd79f2867419dccd484532651dda9a36a4 ); const ALLOWED_VIRTUAL_OS_PROGRAM_HASHES_1 = ( 0x01c7be3225dfb33359b3ba9ffbe1542b7da27879b6d89f47b967e512463fd324 diff --git a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/execution/execute_transactions.cairo b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/execution/execute_transactions.cairo index 804b9d7f053..b29c728f880 100644 --- a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/execution/execute_transactions.cairo +++ b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/execution/execute_transactions.cairo @@ -24,6 +24,7 @@ from starkware.starknet.core.os.execution.execute_transactions_inner import ( execute_transactions_inner, ) from starkware.starknet.core.os.output import OsCarriedOutputs +from starkware.starknet.core.os.proof_fact_fold import ProofFactsReference // Executes the transactions in the hint variable block_input.transactions. // @@ -49,6 +50,7 @@ func execute_transactions{ contract_state_changes: DictAccess*, contract_class_changes: DictAccess*, outputs: OsCarriedOutputs*, + proof_facts_references: ProofFactsReference*, txs_range_check_ptr, }(block_context: BlockContext*) { alloc_locals; diff --git a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/execution/execute_transactions_inner.cairo b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/execution/execute_transactions_inner.cairo index 24c53b27682..9fa8adc4aac 100644 --- a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/execution/execute_transactions_inner.cairo +++ b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/execution/execute_transactions_inner.cairo @@ -8,6 +8,7 @@ from starkware.starknet.core.os.execution.transaction_impls import ( execute_l1_handler_transaction, ) from starkware.starknet.core.os.output import OsCarriedOutputs +from starkware.starknet.core.os.proof_fact_fold import ProofFactsReference // Inner function for execute_transactions. // Arguments: @@ -26,6 +27,7 @@ func execute_transactions_inner{ contract_state_changes: DictAccess*, contract_class_changes: DictAccess*, outputs: OsCarriedOutputs*, + proof_facts_references: ProofFactsReference*, }(block_context: BlockContext*, n_txs) { %{ LogRemainingTxs %} if (n_txs == 0) { diff --git a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/execution/execute_transactions_inner__virtual.cairo b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/execution/execute_transactions_inner__virtual.cairo index eefa6e62ccc..c33919429fa 100644 --- a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/execution/execute_transactions_inner__virtual.cairo +++ b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/execution/execute_transactions_inner__virtual.cairo @@ -7,6 +7,7 @@ from starkware.starknet.core.os.execution.transaction_impls import ( execute_invoke_function_transaction, ) from starkware.starknet.core.os.output import OsCarriedOutputs +from starkware.starknet.core.os.proof_fact_fold import ProofFactsReference // In virtual OS mode, we only support a single INVOKE_FUNCTION transaction. func execute_transactions_inner{ @@ -15,6 +16,7 @@ func execute_transactions_inner{ contract_state_changes: DictAccess*, contract_class_changes: DictAccess*, outputs: OsCarriedOutputs*, + proof_facts_references: ProofFactsReference*, }(block_context: BlockContext*, n_txs) { // Part of the VIRTUAL_SNOS0 version contract. Changes must trigger a version bump. with_attr error_message("Expected exactly one transaction") { diff --git a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/execution/transaction_impls.cairo b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/execution/transaction_impls.cairo index b89cdd2abde..adb9bb3a4d2 100644 --- a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/execution/transaction_impls.cairo +++ b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/execution/transaction_impls.cairo @@ -60,6 +60,10 @@ from starkware.starknet.core.os.output import ( OsCarriedOutputs, os_carried_outputs_new, ) +from starkware.starknet.core.os.proof_fact_fold import ( + ProofFactsReference, + record_proof_facts_reference, +) from starkware.starknet.core.os.state.commitment import StateEntry from starkware.starknet.core.os.transaction_hash.transaction_hash import ( CommonTxFields, @@ -254,6 +258,7 @@ func execute_invoke_function_transaction{ contract_state_changes: DictAccess*, contract_class_changes: DictAccess*, outputs: OsCarriedOutputs*, + proof_facts_references: ProofFactsReference*, }(block_context: BlockContext*) { alloc_locals; @@ -362,6 +367,8 @@ func execute_invoke_function_transaction{ %{ EndTx %} + record_proof_facts_reference(proof_facts_size=proof_facts_size, proof_facts=proof_facts); + return (); } 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 632f8fac31d..33e08cebc7a 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 @@ -49,6 +49,10 @@ from starkware.starknet.core.os.output import ( OsCarriedOutputs, OsOutput, ) +from starkware.starknet.core.os.proof_fact_fold import ( + ProofFactsReference, + single_processed_proof_output, +) from starkware.starknet.core.os.state.state import OsStateUpdate, state_update // The main entry point of the Starknet OS. @@ -219,12 +223,24 @@ func execute_blocks{ } // Execute transactions. + let (proof_facts_references_start: ProofFactsReference*) = alloc(); let outputs = initial_carried_outputs; - with contract_state_changes, contract_class_changes, outputs { + let proof_facts_references = proof_facts_references_start; + with contract_state_changes, contract_class_changes, outputs, proof_facts_references { execute_transactions(block_context=block_context); } let final_carried_outputs = outputs; + let ( + n_proof_facts_transactions, processed_proof_output_low, processed_proof_output_high + ) = single_processed_proof_output( + proof_facts_references_start=proof_facts_references_start, + proof_facts_references_end=proof_facts_references, + ); + local n_proof_facts_transactions = n_proof_facts_transactions; + local processed_proof_output_low = processed_proof_output_low; + local processed_proof_output_high = processed_proof_output_high; + // Update the state. %{ EnterScopeWithAliases %} let (squashed_os_state_update, state_update_output) = state_update{hash_ptr=pedersen_ptr}( @@ -243,6 +259,9 @@ func execute_blocks{ block_context=block_context, state_update_output=state_update_output, os_global_context=os_global_context, + n_proof_facts_transactions=n_proof_facts_transactions, + processed_proof_output_low=processed_proof_output_low, + processed_proof_output_high=processed_proof_output_high, ); assert os_output_per_block_dst[0] = OsOutput( header=os_output_header, diff --git a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/os_utils.cairo b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/os_utils.cairo index b1a5d68c91e..b6839fca491 100644 --- a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/os_utils.cairo +++ b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/os_utils.cairo @@ -122,6 +122,9 @@ func get_block_os_output_header{poseidon_ptr: PoseidonBuiltin*}( block_context: BlockContext*, state_update_output: CommitmentUpdate*, os_global_context: OsGlobalContext*, + n_proof_facts_transactions: felt, + processed_proof_output_low: felt, + processed_proof_output_high: felt, ) -> OsOutputHeader* { // Calculate the block hash based on the block info and state root. // NOTE: both the previous block hash and previous state root are guessed, and the OS @@ -145,6 +148,9 @@ func get_block_os_output_header{poseidon_ptr: PoseidonBuiltin*}( starknet_os_config_hash=os_global_context.starknet_os_config_hash, use_kzg_da=FALSE, full_output=TRUE, + processed_proof_output_low=processed_proof_output_low, + processed_proof_output_high=processed_proof_output_high, + n_proof_facts_transactions=n_proof_facts_transactions, ); return os_output_header; } diff --git a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/os_utils__virtual.cairo b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/os_utils__virtual.cairo index b9deacca9ed..1f35dd7da67 100644 --- a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/os_utils__virtual.cairo +++ b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/os_utils__virtual.cairo @@ -52,7 +52,11 @@ func get_block_os_output_header{poseidon_ptr: PoseidonBuiltin*}( block_context: BlockContext*, state_update_output: CommitmentUpdate*, os_global_context: OsGlobalContext*, + n_proof_facts_transactions: felt, + processed_proof_output_low: felt, + processed_proof_output_high: felt, ) -> OsOutputHeader* { + assert n_proof_facts_transactions = 0; // Calculate the previous block hash based on the block info and the **initial** state root. let (_prev_prev_block_hash, prev_block_hash) = get_block_hashes{poseidon_ptr=poseidon_ptr}( block_info=block_context.block_info_for_execute, state_root=state_update_output.initial_root @@ -68,6 +72,9 @@ func get_block_os_output_header{poseidon_ptr: PoseidonBuiltin*}( starknet_os_config_hash=os_global_context.starknet_os_config_hash, use_kzg_da=FALSE, full_output=TRUE, + processed_proof_output_low=0, + processed_proof_output_high=0, + n_proof_facts_transactions=0, ); return os_output_header; } diff --git a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/output.cairo b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/output.cairo index a6831bf4770..f8e043ec3d2 100644 --- a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/output.cairo +++ b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/output.cairo @@ -46,6 +46,9 @@ struct OsOutputHeader { use_kzg_da: felt, // Indicates whether previous state values are included in the state update information. full_output: felt, + processed_proof_output_low: felt, + processed_proof_output_high: felt, + n_proof_facts_transactions: felt, } // An L2 to L1 message header, the message payload is concatenated to the end of the header. @@ -168,6 +171,9 @@ func serialize_output_header{output_ptr: felt*}(os_output_header: OsOutputHeader serialize_word(os_output_header.starknet_os_config_hash); serialize_word(os_output_header.use_kzg_da); serialize_word(os_output_header.full_output); + serialize_word(os_output_header.processed_proof_output_low); + serialize_word(os_output_header.processed_proof_output_high); + serialize_word(os_output_header.n_proof_facts_transactions); return (); } diff --git a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/proof_fact_fold.cairo b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/proof_fact_fold.cairo index b6e4a30e96b..4d67b4e06b4 100644 --- a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/proof_fact_fold.cairo +++ b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/proof_fact_fold.cairo @@ -6,6 +6,57 @@ from starkware.cairo.common.registers import get_label_location const BLAKE2S_DIGEST_N_WORDS = 8; const PROOF_ENTRY_N_WORDS = 2 * BLAKE2S_DIGEST_N_WORDS; +struct ProofFactsReference { + proof_facts_size: felt, + proof_facts: felt*, +} + +func record_proof_facts_reference{proof_facts_references: ProofFactsReference*}( + proof_facts_size: felt, proof_facts: felt* +) { + if (proof_facts_size == 0) { + return (); + } + assert [proof_facts_references] = ProofFactsReference( + proof_facts_size=proof_facts_size, proof_facts=proof_facts + ); + let proof_facts_references = &proof_facts_references[1]; + return (); +} + +func single_processed_proof_output{range_check_ptr}( + proof_facts_references_start: ProofFactsReference*, + proof_facts_references_end: ProofFactsReference*, +) -> ( + n_proof_facts_transactions: felt, + processed_proof_output_low: felt, + processed_proof_output_high: felt, +) { + alloc_locals; + local n_proof_facts_transactions = (proof_facts_references_end - proof_facts_references_start) / + ProofFactsReference.SIZE; + if (n_proof_facts_transactions == 0) { + return ( + n_proof_facts_transactions=0, + processed_proof_output_low=0, + processed_proof_output_high=0, + ); + } + assert n_proof_facts_transactions = 1; + let (output_digest) = compute_processed_proof_output_digest( + proof_facts_size=proof_facts_references_start.proof_facts_size, + proof_facts=proof_facts_references_start.proof_facts, + ); + let (processed_proof_output_low, processed_proof_output_high) = pack_output_digest( + output_digest=output_digest + ); + return ( + n_proof_facts_transactions=1, + processed_proof_output_low=processed_proof_output_low, + processed_proof_output_high=processed_proof_output_high, + ); +} + func compute_processed_proof_output_digest{range_check_ptr}( proof_facts_size: felt, proof_facts: felt* ) -> (output_digest: felt*) { diff --git a/crates/apollo_starknet_os_program/src/program_hash.json b/crates/apollo_starknet_os_program/src/program_hash.json index 7423fed0631..c4f91b4e343 100644 --- a/crates/apollo_starknet_os_program/src/program_hash.json +++ b/crates/apollo_starknet_os_program/src/program_hash.json @@ -1,6 +1,6 @@ { - "os": "0x1420d23187254d4dcf4006fa6ad997b0728f845416bcfd1478c7e8f804fb429", - "virtual_os": "0x1c7be3225dfb33359b3ba9ffbe1542b7da27879b6d89f47b967e512463fd324", - "aggregator": "0x3e4ce8340259e374200ed856e597a0c0b268d1021119d33573d7c759c5320f9", - "aggregator_with_prefix": "0x2526c12112a2d7f57f7ae74af7be1fe1b2766e4c34b0c88ceda16b70ed2d6c2" + "os": "0x3932c2db6cc720a69e8932bf62e07d90263ae1c2308ef68a096a7cb081bdd13", + "virtual_os": "0x7dc1bd0f3938f5f2f5e7c14118402cd79f2867419dccd484532651dda9a36a4", + "aggregator": "0x38b41528311efc46b895d4326121ebe25042cb694deabc4ec461b1653b89fc0", + "aggregator_with_prefix": "0x1015fe136b9f9ba7728018f2b4d40edb1ebe1dbccbcb8524b543640f8f1e724" } \ No newline at end of file diff --git a/crates/apollo_starknet_os_program/src/virtual_os_test.rs b/crates/apollo_starknet_os_program/src/virtual_os_test.rs index 32cd714d78a..df1eba618b2 100644 --- a/crates/apollo_starknet_os_program/src/virtual_os_test.rs +++ b/crates/apollo_starknet_os_program/src/virtual_os_test.rs @@ -19,11 +19,11 @@ fn test_virtual_os_swapped_files() { #[test] fn test_program_bytecode_lengths() { expect![[r#" - 16376 + 16663 "#]] .assert_debug_eq(&OS_PROGRAM.data_len()); expect![[r#" - 11426 + 11626 "#]] .assert_debug_eq(&VIRTUAL_OS_PROGRAM.data_len()); } diff --git a/crates/blockifier/resources/blockifier_versioned_constants_0_14_5.json b/crates/blockifier/resources/blockifier_versioned_constants_0_14_5.json index c9254ca531e..cd4efc4b73d 100644 --- a/crates/blockifier/resources/blockifier_versioned_constants_0_14_5.json +++ b/crates/blockifier/resources/blockifier_versioned_constants_0_14_5.json @@ -127,7 +127,7 @@ "segment_arena_cells": false, "os_constants": { "allowed_virtual_os_program_hashes": [ - "0x53f6c9fcfd31d27279ff7d7e422b44623550a732b59fe193354a7316a96daa1", + "0x7dc1bd0f3938f5f2f5e7c14118402cd79f2867419dccd484532651dda9a36a4", "0x1c7be3225dfb33359b3ba9ffbe1542b7da27879b6d89f47b967e512463fd324" ], "allowed_proof_versions": [ @@ -536,7 +536,7 @@ }, "InvokeFunction": { "constant": { - "n_steps": 4779, + "n_steps": 4794, "n_memory_holes": 0, "builtin_instance_counter": { "range_check_builtin": 110, diff --git a/crates/starknet_os/src/hints/hint_implementation/aggregator/test.rs b/crates/starknet_os/src/hints/hint_implementation/aggregator/test.rs index 095a46fac1e..504a55ff2ee 100644 --- a/crates/starknet_os/src/hints/hint_implementation/aggregator/test.rs +++ b/crates/starknet_os/src/hints/hint_implementation/aggregator/test.rs @@ -311,6 +311,9 @@ fn multi_block0_output(full_output: bool) -> Vec { Felt::ZERO, // full_output. Felt::from(full_output), + Felt::ZERO, + Felt::ZERO, + Felt::ZERO, // Messages to L1. MSG_TO_L1_0.len().into(), ], @@ -403,6 +406,9 @@ fn multi_block1_output(full_output: bool, modifier: FailureModifier) -> Vec Vec { Felt::from(use_kzg_da), // full_output. Felt::from(full_output), + Felt::ZERO, + Felt::ZERO, + Felt::ZERO, ], // KZG info. if use_kzg_da { combined_kzg_info(&da) } else { vec![] }, diff --git a/crates/starknet_os/src/hints/hint_implementation/aggregator/utils.rs b/crates/starknet_os/src/hints/hint_implementation/aggregator/utils.rs index af102a2866f..cc23c344dcb 100644 --- a/crates/starknet_os/src/hints/hint_implementation/aggregator/utils.rs +++ b/crates/starknet_os/src/hints/hint_implementation/aggregator/utils.rs @@ -271,6 +271,9 @@ pub(crate) fn write_full_os_output( common_os_output.starknet_os_config_hash.into(), Felt::ZERO.into(), // use_kzg_da field (False in the aggregator input). Felt::from(full_output_flag).into(), + common_os_output.processed_proof_output_low.into(), + common_os_output.processed_proof_output_high.into(), + Felt::from(common_os_output.n_proof_facts_transactions).into(), ])?; let squashed_os_state_update = vm.gen_arg(&vec![ diff --git a/crates/starknet_os/src/io/os_output.rs b/crates/starknet_os/src/io/os_output.rs index 717072ba3e4..a1f1ced63cc 100644 --- a/crates/starknet_os/src/io/os_output.rs +++ b/crates/starknet_os/src/io/os_output.rs @@ -257,6 +257,12 @@ impl TryFromOutputIter for OutputIterParsedData { let starknet_os_config_hash = wrap_missing(output_iter.next(), "starknet_os_config_hash")?; let use_kzg_da = wrap_missing_as_bool(output_iter.next(), "use_kzg_da")?; let full_output = wrap_missing_as_bool(output_iter.next(), "full_output")?; + let processed_proof_output_low = + wrap_missing(output_iter.next(), "processed_proof_output_low")?; + let processed_proof_output_high = + wrap_missing(output_iter.next(), "processed_proof_output_high")?; + let n_proof_facts_transactions = + wrap_missing_as(output_iter.next(), "n_proof_facts_transactions")?; let kzg_commitment_info = if use_kzg_da { Some(OsKzgCommitmentInfo::try_from_output_iter(output_iter, private_keys)?) @@ -300,6 +306,9 @@ impl TryFromOutputIter for OutputIterParsedData { new_block_hash, os_program_hash, starknet_os_config_hash, + processed_proof_output_low, + processed_proof_output_high, + n_proof_facts_transactions, messages_to_l1, messages_to_l2, }, @@ -328,6 +337,9 @@ pub struct CommonOsOutput { pub os_program_hash: StarkHash, // The hash of the OS config. pub starknet_os_config_hash: StarkHash, + pub processed_proof_output_low: StarkHash, + pub processed_proof_output_high: StarkHash, + pub n_proof_facts_transactions: usize, // Messages from L2 to L1. pub messages_to_l1: Vec, // Messages from L1 to L2. diff --git a/crates/starknet_os/src/proof_fact_fold.rs b/crates/starknet_os/src/proof_fact_fold.rs index fe543ce7a97..491d413e00b 100644 --- a/crates/starknet_os/src/proof_fact_fold.rs +++ b/crates/starknet_os/src/proof_fact_fold.rs @@ -58,3 +58,12 @@ pub fn compute_verification_digest( &[MULTIVERIFIER_CIRCUIT_HASH.as_slice(), processed_proof_output_digest.as_slice()].concat(), ) } + +pub fn pack_output_digest(output_digest: &Blake2sDigestWords) -> (Felt, Felt) { + let pack_half = |words: &[u32]| { + words.iter().rev().fold(Felt::ZERO, |packed_half, word| { + packed_half * Felt::from(1u64 << 32) + Felt::from(*word) + }) + }; + (pack_half(&output_digest[..4]), pack_half(&output_digest[4..])) +} diff --git a/crates/starknet_os/src/proof_fact_fold_test.rs b/crates/starknet_os/src/proof_fact_fold_test.rs index 01b10f3d278..4850a11f616 100644 --- a/crates/starknet_os/src/proof_fact_fold_test.rs +++ b/crates/starknet_os/src/proof_fact_fold_test.rs @@ -11,6 +11,7 @@ use super::{ compute_leaf_output_digest, compute_processed_proof_output_digest, compute_verification_digest, + pack_output_digest, Blake2sDigestWords, BLAKE2S_DIGEST_N_WORDS, LEAF_VERIFIER_CIRCUIT_HASH, @@ -22,6 +23,7 @@ use crate::test_utils::cairo_runner::{ EntryPointRunnerConfig, ImplicitArg, PointerArg, + ValueArg, }; fn entrypoint_runner_config() -> EntryPointRunnerConfig { @@ -213,3 +215,29 @@ fn test_cairo_circuit_hashes_match_rust() { MULTIVERIFIER_CIRCUIT_HASH ); } + +#[test] +fn test_cairo_pack_output_digest_matches_rust() { + let output_digest = compute_processed_proof_output_digest(&synthetic_proof_facts(0)); + let (expected_low, expected_high) = pack_output_digest(&output_digest); + let expected_return_values = vec![EndpointArg::from(Felt::ZERO), EndpointArg::from(Felt::ZERO)]; + let (_, packed_return_values, _) = initialize_and_run_cairo_0_entry_point( + &entrypoint_runner_config(), + PROOF_FACT_FOLD_BYTES, + "pack_output_digest", + &[felt_array_arg(&output_digest.map(Felt::from))], + &[], + &expected_return_values, + HashMap::new(), + None, + ) + .unwrap_or_else(|error| panic!("Failed to run pack_output_digest: {error:?}")); + let [ + EndpointArg::Value(ValueArg::Single(MaybeRelocatable::Int(cairo_low))), + EndpointArg::Value(ValueArg::Single(MaybeRelocatable::Int(cairo_high))), + ] = packed_return_values.as_slice() + else { + panic!("Expected pack_output_digest to return two felts."); + }; + assert_eq!((*cairo_low, *cairo_high), (expected_low, expected_high)); +} diff --git a/crates/starknet_os_flow_tests/src/test_manager.rs b/crates/starknet_os_flow_tests/src/test_manager.rs index 565054c065a..e13df975fa1 100644 --- a/crates/starknet_os_flow_tests/src/test_manager.rs +++ b/crates/starknet_os_flow_tests/src/test_manager.rs @@ -73,6 +73,7 @@ use starknet_os::io::os_output_types::{ TryFromOutputIter, }; use starknet_os::io::test_utils::validate_kzg_segment; +use starknet_os::proof_fact_fold::{compute_processed_proof_output_digest, pack_output_digest}; use starknet_os::runner::{run_os_stateless, DEFAULT_OS_LAYOUT}; use starknet_os::test_utils::coverage::expect_hint_coverage; use starknet_transaction_prover::running::committer_utils::{ @@ -158,6 +159,9 @@ pub(crate) struct OsTestExpectedValues { pub(crate) messages_to_l1: Vec, pub(crate) messages_to_l2: Vec, pub(crate) committed_state_diff: StateDiff, + pub(crate) processed_proof_output_low: Felt, + pub(crate) processed_proof_output_high: Felt, + pub(crate) n_proof_facts_transactions: usize, } impl OsTestExpectedValues { @@ -191,6 +195,9 @@ impl OsTestExpectedValues { first_block.block_info.starknet_version, ) .unwrap(); + let (processed_proof_output_low, processed_proof_output_high, n_proof_facts_transactions) = + expected_processed_proof_output(&os_hints.os_input.os_block_inputs); + Self { previous_global_root, new_global_root, @@ -205,7 +212,33 @@ impl OsTestExpectedValues { messages_to_l1, messages_to_l2, committed_state_diff, + processed_proof_output_low, + processed_proof_output_high, + n_proof_facts_transactions, + } + } +} + +fn expected_processed_proof_output(os_block_inputs: &[OsBlockInput]) -> (Felt, Felt, usize) { + let contributing_proof_facts: Vec<_> = os_block_inputs + .iter() + .flat_map(|block_input| &block_input.transactions) + .filter_map(|transaction| match transaction { + ExecutableTransaction::Account(AccountTransaction::Invoke(invoke_transaction)) => { + let proof_facts = invoke_transaction.proof_facts(); + (!proof_facts.0.is_empty()).then_some(proof_facts) + } + _ => None, + }) + .collect(); + match contributing_proof_facts.as_slice() { + [] => (Felt::ZERO, Felt::ZERO, 0), + [proof_facts] => { + let output_digest = compute_processed_proof_output_digest(&proof_facts.0); + let (output_low, output_high) = pack_output_digest(&output_digest); + (output_low, output_high, 1) } + _ => panic!("At most one transaction with proof facts is supported."), } } @@ -324,6 +357,19 @@ impl OsTestOutput { assert_eq!(os_output.use_kzg_da(), self.expected_values.use_kzg_da); assert_eq!(os_output.full_output(), self.expected_values.full_output); + assert_eq!( + os_output.common_os_output.processed_proof_output_low, + self.expected_values.processed_proof_output_low + ); + assert_eq!( + os_output.common_os_output.processed_proof_output_high, + self.expected_values.processed_proof_output_high + ); + assert_eq!( + os_output.common_os_output.n_proof_facts_transactions, + self.expected_values.n_proof_facts_transactions + ); + // KZG commitment. if os_output.use_kzg_da() { let OsStateDiff::PartialCommitment(PartialCommitmentOsStateDiff(