Skip to content

Commit 4b735cf

Browse files
apollo_starknet_os_program: add security checks for virtual OS
1 parent 6ac4778 commit 4b735cf

File tree

7 files changed

+113
-9
lines changed

7 files changed

+113
-9
lines changed

crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/execution/execute_transactions.cairo

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ from starkware.starknet.core.os.execution.execute_transaction_utils import (
6666
update_class_hash_in_execution_context,
6767
)
6868
from starkware.starknet.core.os.execution.revert import init_revert_log
69+
from starkware.starknet.core.os.execution.execution_constraints import check_n_txs, check_tx_type
6970
from starkware.starknet.core.os.output import (
7071
MessageToL2Header,
7172
OsCarriedOutputs,
@@ -144,6 +145,9 @@ func execute_transactions{
144145
local n_txs;
145146
%{ OsInputTransactions %}
146147
%{ EnterScopeExecuteTransactionsInner %}
148+
149+
check_n_txs(n_txs=n_txs);
150+
147151
execute_transactions_inner{
148152
builtin_ptrs=builtin_ptrs,
149153
contract_state_changes=contract_state_changes,
@@ -206,6 +210,8 @@ func execute_transactions_inner{
206210
// Guess the current transaction's type.
207211
%{ LoadNextTx %}
208212

213+
check_tx_type(tx_type=tx_type);
214+
209215
if (tx_type == 'INVOKE_FUNCTION') {
210216
// Handle the invoke-function transaction.
211217
execute_invoke_function_transaction(block_context=block_context);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Execution constraints for transaction execution.
2+
// These are no-op implementations for the Sequencer OS.
3+
4+
func check_n_txs(n_txs: felt) {
5+
return ();
6+
}
7+
8+
func check_tx_type(tx_type: felt) {
9+
return ();
10+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Execution constraints for transaction execution (virtual OS version).
2+
3+
// Checks that the number of transactions is one.
4+
func check_n_txs(n_txs: felt) {
5+
with_attr error_message("Expected exactly one transaction") {
6+
assert n_txs = 1;
7+
}
8+
return ();
9+
}
10+
11+
// Checks that the transaction type is INVOKE_FUNCTION.
12+
func check_tx_type(tx_type: felt) {
13+
with_attr error_message("Expected INVOKE_FUNCTION transaction") {
14+
assert tx_type = 'INVOKE_FUNCTION';
15+
}
16+
return ();
17+
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"os": "0x9b874b12cf82d72e25947a557a77a581acb240348275db64e70947e63c1478",
3-
"virtual_os": "0x5c40917ea9e822a504ad48f7a57f2485a07d5cdfe1e8b1b38b7ca6627d8daf4",
2+
"os": "0x78179950037794503935395abfcc7446fbdee452d388a9cc047ef7201bc2278",
3+
"virtual_os": "0x6c33b6ac546a56d5f79fe7367a3c4ec550ba8bf627d97f5cfe0f6a6cf120e5c",
44
"aggregator": "0x63868c8dbf389119d95bae88b80043f5d237d3ddec072fb8f2095fcfacb9b1c",
55
"aggregator_with_prefix": "0xd892fd2fd978d8c2749a6678457ca99161f3d1822e4c3b8c03914817c6de1a"
66
}

crates/apollo_starknet_os_program/src/virtual_os_test.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ use crate::{OS_PROGRAM, VIRTUAL_OS_PROGRAM, VIRTUAL_OS_SWAPPED_FILES};
55
/// Asserts the list of swapped virtual OS files matches the expected list.
66
#[test]
77
fn test_virtual_os_swapped_files() {
8-
expect!["starkware/starknet/core/os/os_utils.cairo"]
8+
expect![[r#"
9+
starkware/starknet/core/os/execution/execution_constraints.cairo
10+
starkware/starknet/core/os/os_utils.cairo"#]]
911
.assert_eq(&VIRTUAL_OS_SWAPPED_FILES.join("\n"));
1012
}
1113

@@ -14,11 +16,11 @@ fn test_virtual_os_swapped_files() {
1416
#[test]
1517
fn test_program_bytecode_lengths() {
1618
expect![[r#"
17-
15527
18-
"#]]
19+
15535
20+
"#]]
1921
.assert_debug_eq(&OS_PROGRAM.data_len());
2022
expect![[r#"
21-
13172
22-
"#]]
23+
13184
24+
"#]]
2325
.assert_debug_eq(&VIRTUAL_OS_PROGRAM.data_len());
2426
}

crates/starknet_os_flow_tests/src/virtual_os_test.rs

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ use blockifier_test_utils::cairo_versions::{CairoVersion, RunnableCairo1};
22
use blockifier_test_utils::calldata::create_calldata;
33
use blockifier_test_utils::contracts::FeatureContract;
44
use rstest::rstest;
5-
use starknet_api::core::EthAddress;
6-
use starknet_api::transaction::{L2ToL1Payload, MessageToL1};
5+
use starknet_api::abi::abi_utils::selector_from_name;
6+
use starknet_api::core::{EthAddress, Nonce};
7+
use starknet_api::executable_transaction::L1HandlerTransaction as ExecutableL1HandlerTransaction;
8+
use starknet_api::transaction::fields::Fee;
9+
use starknet_api::transaction::{L1HandlerTransaction, L2ToL1Payload, MessageToL1};
710
use starknet_api::{calldata, invoke_tx_args};
811
use starknet_types_core::felt::Felt;
912

@@ -34,3 +37,57 @@ async fn test_basic_happy_flow() {
3437

3538
test_builder.build().await.run_virtual_and_validate();
3639
}
40+
41+
/// Security tests.
42+
/// Note that it's important to construct the hints correctly and get the error directly from Cairo
43+
/// (and not from the blockifier), as users can submit virtual OS proofs with arbitrary hints.
44+
45+
#[rstest]
46+
#[tokio::test]
47+
/// Test that the virtual OS fails when more than one transaction is added.
48+
async fn test_two_txs_os_error() {
49+
let test_contract = FeatureContract::TestContract(CairoVersion::Cairo1(RunnableCairo1::Casm));
50+
51+
let (mut test_builder, [contract_address]) =
52+
TestBuilder::create_standard_virtual([(test_contract, calldata![Felt::ONE, Felt::TWO])])
53+
.await;
54+
55+
// Add first invoke transaction.
56+
let calldata = create_calldata(contract_address, "test_storage_read", &[Felt::ONE]);
57+
test_builder.add_funded_account_invoke(invoke_tx_args! { calldata: calldata.clone() });
58+
59+
// Add second invoke transaction - this should cause the virtual OS to fail.
60+
test_builder.add_funded_account_invoke(invoke_tx_args! { calldata });
61+
62+
test_builder.build().await.run_virtual_expect_error("Expected exactly one transaction");
63+
}
64+
65+
#[rstest]
66+
#[tokio::test]
67+
/// Test that the virtual OS fails when a non-invoke transaction is added.
68+
async fn test_non_invoke_tx_os_error() {
69+
let test_contract = FeatureContract::TestContract(CairoVersion::Cairo1(RunnableCairo1::Casm));
70+
71+
let (mut test_builder, [contract_address]) =
72+
TestBuilder::create_standard_virtual([(test_contract, calldata![Felt::ONE, Felt::TWO])])
73+
.await;
74+
75+
// Add an L1 handler transaction instead of an invoke.
76+
// TODO(Yoni): parameterize other transaction types.
77+
let tx = ExecutableL1HandlerTransaction::create(
78+
L1HandlerTransaction {
79+
version: L1HandlerTransaction::VERSION,
80+
nonce: Nonce::default(),
81+
contract_address,
82+
entry_point_selector: selector_from_name("l1_handle"),
83+
// from_address, arg.
84+
calldata: calldata![Felt::ONE, Felt::TWO],
85+
},
86+
&test_builder.chain_id(),
87+
Fee(1_000_000),
88+
)
89+
.unwrap();
90+
test_builder.add_l1_handler_tx(tx, None);
91+
92+
test_builder.build().await.run_virtual_expect_error("Expected INVOKE_FUNCTION transaction");
93+
}

crates/starknet_os_flow_tests/src/virtual_os_test_manager.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,16 @@ impl<S: FlowTestState> TestRunner<S> {
5353
pub(crate) fn run_virtual_and_validate(self) {
5454
self.run_virtual().validate();
5555
}
56+
57+
/// Runs the virtual OS and expects it to fail with an error containing the given string.
58+
pub(crate) fn run_virtual_expect_error(self, expected_error: &str) {
59+
let err = run_virtual_os(self.os_hints).expect_err("Expected virtual OS to fail");
60+
let err_string = err.to_string();
61+
assert!(
62+
err_string.contains(expected_error),
63+
"Expected error to contain '{}', got: {}",
64+
expected_error,
65+
err_string
66+
);
67+
}
5668
}

0 commit comments

Comments
 (0)