Skip to content

Commit 28ece19

Browse files
chudkowskyglihm
andauthored
feat: ensure snos program hash is checked (#46)
* snos program hash validation * merge snos info with program info * regenerate bindings * Program info struct * reword comments and cleanup inline documentation --------- Co-authored-by: glihm <[email protected]>
1 parent ac2f6a2 commit 28ece19

File tree

6 files changed

+191
-246
lines changed

6 files changed

+191
-246
lines changed

bindings/src/lib.rs

+105-190
Large diffs are not rendered by default.

src/appchain.cairo

+23-18
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,18 @@ mod appchain {
1717
use core::poseidon::{Poseidon, PoseidonImpl, HashStateImpl, poseidon_hash_span};
1818
use openzeppelin::access::ownable::{
1919
OwnableComponent as ownable_cpt, OwnableComponent::InternalTrait as OwnableInternal,
20-
interface::IOwnable
20+
interface::IOwnable,
2121
};
2222
use openzeppelin::security::reentrancyguard::{
2323
ReentrancyGuardComponent,
24-
ReentrancyGuardComponent::InternalTrait as InternalReentrancyGuardImpl
24+
ReentrancyGuardComponent::InternalTrait as InternalReentrancyGuardImpl,
2525
};
2626
use openzeppelin::upgrades::{
2727
UpgradeableComponent as upgradeable_cpt,
28-
UpgradeableComponent::InternalTrait as UpgradeableInternal, interface::IUpgradeable
28+
UpgradeableComponent::InternalTrait as UpgradeableInternal, interface::IUpgradeable,
2929
};
3030
use piltover::components::onchain_data_fact_tree_encoder::{
31-
encode_fact_with_onchain_data, DataAvailabilityFact
31+
encode_fact_with_onchain_data, DataAvailabilityFact,
3232
};
3333
use piltover::config::{config_cpt, config_cpt::InternalTrait as ConfigInternal, IConfig};
3434
use piltover::fact_registry::{IFactRegistryDispatcher, IFactRegistryDispatcherTrait};
@@ -52,7 +52,7 @@ mod appchain {
5252
component!(path: messaging_cpt, storage: messaging, event: MessagingEvent);
5353
component!(path: state_cpt, storage: state, event: StateEvent);
5454
component!(
55-
path: ReentrancyGuardComponent, storage: reentrancy_guard, event: ReentrancyGuardEvent
55+
path: ReentrancyGuardComponent, storage: reentrancy_guard, event: ReentrancyGuardEvent,
5656
);
5757

5858
#[abi(embed_v0)]
@@ -134,42 +134,47 @@ mod appchain {
134134
snos_output: Span<felt252>,
135135
program_output: Span<felt252>,
136136
onchain_data_hash: felt252,
137-
onchain_data_size: u256
137+
onchain_data_size: u256,
138138
) {
139139
self.reentrancy_guard.start();
140140
self.config.assert_only_owner_or_operator();
141141

142+
let program_info = self.config.program_info.read();
143+
144+
// StarknetOS (SNOS) proof is wrapped in bootloader so 3rd element is the program hash
145+
// of bootloaded program, in our case SNOS.
146+
let snos_program_hash = snos_output.at(2);
147+
assert!(program_info.snos_program_hash == *snos_program_hash);
148+
142149
let snos_output_hash = poseidon_hash_span(snos_output);
150+
// Layout bridge program is also bootloaded, and the 5th element is the hash of the
151+
// output of the program that has been layout-bridged.
143152
let snos_output_hash_in_bridge_output = program_output.at(4);
144153
assert!(snos_output_hash == *snos_output_hash_in_bridge_output);
154+
145155
let output_hash = poseidon_hash_span(program_output);
146156

147157
let mut snos_output_iter = snos_output.into_iter();
148158
let program_output_struct = deserialize_os_output(ref snos_output_iter);
149159

150-
let (current_program_hash, current_config_hash): (felt252, felt252) = self
151-
.config
152-
.program_info
153-
.read();
154-
155160
let data_availability_fact: DataAvailabilityFact = DataAvailabilityFact {
156-
onchain_data_hash, onchain_data_size
161+
onchain_data_hash, onchain_data_size,
157162
};
158163
let state_transition_fact: u256 = encode_fact_with_onchain_data(
159-
program_output, data_availability_fact
164+
program_output, data_availability_fact,
160165
);
161166

162167
assert(
163-
program_output_struct.starknet_os_config_hash == current_config_hash,
164-
errors::SNOS_INVALID_CONFIG_HASH
168+
program_output_struct.starknet_os_config_hash == program_info.config_hash,
169+
errors::SNOS_INVALID_CONFIG_HASH,
165170
);
166171

167-
let fact = poseidon_hash_span(array![current_program_hash, output_hash].span());
172+
let fact = poseidon_hash_span(array![program_info.program_hash, output_hash].span());
168173
assert!(
169174
*IFactRegistryDispatcher { contract_address: self.config.get_facts_registry() }
170175
.get_all_verifications_for_fact_hash(fact)
171176
.at(0)
172-
.security_bits > 50
177+
.security_bits > 50,
173178
);
174179

175180
self.emit(LogStateTransitionFact { state_transition_fact });
@@ -191,7 +196,7 @@ mod appchain {
191196
state_root: self.state.state_root.read(),
192197
block_number: self.state.block_number.read(),
193198
block_hash: self.state.block_hash.read(),
194-
}
199+
},
195200
);
196201
}
197202
}

src/config/component.cairo

+26-16
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,28 @@ mod config_cpt {
2828
struct Storage {
2929
/// Appchain operators that are allowed to update the state.
3030
operators: Map<ContractAddress, bool>,
31-
/// Program info (StarknetOS), with program hash and config hash.
32-
program_info: (felt252, felt252),
31+
/// The information of the program verified to apply the state transition.
32+
program_info: ProgramInfo,
3333
/// Facts registry contract address.
3434
facts_registry: ContractAddress,
3535
}
3636

37+
/// Information of the program verified onchain to apply the state transition.
38+
///
39+
/// In the current design, the StarknetOS (SNOS) is executed and proven first.
40+
/// Since the layout used by SNOS is not verifiable onchain, a bridge layout
41+
/// program is also executed on the proof generated from SNOS execution.
42+
///
43+
/// For this reason, the program info contains the hash of the SNOS program,
44+
/// additionally to the `program_hash`, which in this case is the bridge layout program hash.
45+
/// This ensures that the correct programs have been executed.
46+
#[derive(starknet::Store, Drop, Serde, Copy, PartialEq)]
47+
struct ProgramInfo {
48+
program_hash: felt252,
49+
config_hash: felt252,
50+
snos_program_hash: felt252,
51+
}
52+
3753
#[event]
3854
#[derive(Copy, Drop, starknet::Event)]
3955
enum Event {
@@ -43,10 +59,8 @@ mod config_cpt {
4359
#[derive(Copy, Drop, starknet::Event)]
4460
struct ProgramInfoChanged {
4561
changed_by: ContractAddress,
46-
old_program_hash: felt252,
47-
new_program_hash: felt252,
48-
old_config_hash: felt252,
49-
new_config_hash: felt252,
62+
old_program_info: ProgramInfo,
63+
new_program_info: ProgramInfo,
5064
}
5165

5266
#[embeddable_as(ConfigImpl)]
@@ -71,25 +85,21 @@ mod config_cpt {
7185
self.operators.read(address)
7286
}
7387

74-
fn set_program_info(
75-
ref self: ComponentState<TContractState>, program_hash: felt252, config_hash: felt252
76-
) {
88+
fn set_program_info(ref self: ComponentState<TContractState>, program_info: ProgramInfo) {
7789
self.assert_only_owner_or_operator();
78-
let (old_program_hash, old_config_hash): (felt252, felt252) = self.program_info.read();
79-
self.program_info.write((program_hash, config_hash));
90+
let old_program_info = self.program_info.read();
91+
self.program_info.write(program_info);
8092
self
8193
.emit(
8294
ProgramInfoChanged {
8395
changed_by: starknet::get_caller_address(),
84-
old_program_hash: old_program_hash,
85-
new_program_hash: program_hash,
86-
old_config_hash: old_config_hash,
87-
new_config_hash: config_hash,
96+
old_program_info,
97+
new_program_info: program_info,
8898
}
8999
);
90100
}
91101

92-
fn get_program_info(self: @ComponentState<TContractState>) -> (felt252, felt252) {
102+
fn get_program_info(self: @ComponentState<TContractState>) -> ProgramInfo {
93103
self.program_info.read()
94104
}
95105

src/config/interface.cairo

+9-9
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
//!
33
//! Interface for appchain settlement contract configuration.
44
use starknet::ContractAddress;
5+
use super::component::config_cpt::ProgramInfo;
56

67
#[starknet::interface]
78
trait IConfig<T> {
@@ -28,22 +29,21 @@ trait IConfig<T> {
2829
/// True if the address is an operator, false otherwise.
2930
fn is_operator(self: @T, address: ContractAddress) -> bool;
3031

31-
/// Sets the information of the program that generates the
32-
/// state transition trace (namely StarknetOS).
32+
/// Sets the information of the program verified onchain to
33+
/// execute the state transition.
3334
///
3435
/// # Arguments
3536
///
36-
/// * `program_hash` - The program hash.
37-
/// * `config_hash` - The program's config hash.
38-
fn set_program_info(ref self: T, program_hash: felt252, config_hash: felt252);
37+
/// * `program_info` - The program information.
38+
fn set_program_info(ref self: T, program_info: ProgramInfo);
3939

40-
/// Gets the information of the program that generates the
41-
/// state transition trace (namely StarknetOS).
40+
/// Gets the information of the program verified onchain to
41+
/// execute the state transition.
4242
///
4343
/// # Returns
4444
///
45-
/// The program hash and it's configuration hash.
46-
fn get_program_info(self: @T) -> (felt252, felt252);
45+
/// The program information.
46+
fn get_program_info(self: @T) -> ProgramInfo;
4747

4848
/// Sets the facts registry contract address, which is already
4949
/// initialized with the verifier information.

src/config/tests/test_config.cairo

+14-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use openzeppelin_testing::constants as c;
22
use piltover::config::{
3-
config_cpt, config_cpt::InternalTrait as ConfigInternal, IConfig, IConfigDispatcherTrait,
4-
IConfigDispatcher, config_mock
3+
config_cpt, config_cpt::ProgramInfo, config_cpt::InternalTrait as ConfigInternal, IConfig,
4+
IConfigDispatcherTrait, IConfigDispatcher, config_mock
55
};
66
use snforge_std as snf;
77
use snforge_std::ContractClassTrait;
@@ -88,16 +88,20 @@ fn config_set_program_info_ok() {
8888
snf::start_cheat_caller_address(mock.contract_address, c::OWNER());
8989

9090
// Owner sets the info.
91-
mock.set_program_info(0x1, 0x2);
92-
assert(mock.get_program_info() == (0x1, 0x2), 'expect correct hashes');
91+
let program_info = ProgramInfo { program_hash: 0x1, config_hash: 0x2, snos_program_hash: 0x3, };
92+
mock.set_program_info(program_info);
93+
assert(mock.get_program_info() == program_info, 'expect correct hashes');
9394

9495
mock.register_operator(c::OPERATOR());
9596

9697
// Operator can also set the program info.
9798
snf::start_cheat_caller_address(mock.contract_address, c::OPERATOR());
98-
mock.set_program_info(0x11, 0x22);
99+
let program_info = ProgramInfo {
100+
program_hash: 0x11, config_hash: 0x22, snos_program_hash: 0x33,
101+
};
102+
mock.set_program_info(program_info);
99103

100-
assert(mock.get_program_info() == (0x11, 0x22), 'expect operator hashes');
104+
assert(mock.get_program_info() == program_info, 'expect operator hashes');
101105
}
102106

103107
#[test]
@@ -106,7 +110,10 @@ fn config_set_program_info_unauthorized() {
106110
let mock = deploy_mock();
107111

108112
snf::start_cheat_caller_address(mock.contract_address, c::OPERATOR());
109-
mock.set_program_info(0x11, 0x22);
113+
mock
114+
.set_program_info(
115+
ProgramInfo { program_hash: 0x1, config_hash: 0x2, snos_program_hash: 0x3, }
116+
);
110117
}
111118

112119
#[test]

tests/test_appchain.cairo

+14-6
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use core::result::ResultTrait;
55
//!
66
use openzeppelin_testing::constants as c;
77
use piltover::appchain::appchain::{Event, LogStateUpdate, LogStateTransitionFact};
8-
use piltover::config::{IConfig, IConfigDispatcherTrait, IConfigDispatcher};
8+
use piltover::config::{IConfig, config_cpt::ProgramInfo, IConfigDispatcherTrait, IConfigDispatcher};
99
use piltover::fact_registry::{
1010
IFactRegistryDispatcher, IFactRegistryDispatcherTrait, fact_registry_mock
1111
};
@@ -171,7 +171,10 @@ fn appchain_owner_ok() {
171171
let iconfig = IConfigDispatcher { contract_address: appchain.contract_address };
172172

173173
snf::start_cheat_caller_address(appchain.contract_address, c::OWNER());
174-
iconfig.set_program_info(0x11, 0x22);
174+
iconfig
175+
.set_program_info(
176+
ProgramInfo { program_hash: 0x11, config_hash: 0x22, snos_program_hash: 0x33 }
177+
);
175178
}
176179

177180
#[test]
@@ -180,7 +183,10 @@ fn appchain_owner_only() {
180183
let (appchain, _spy) = deploy_with_owner(c::OWNER().into());
181184

182185
let iconfig = IConfigDispatcher { contract_address: appchain.contract_address };
183-
iconfig.set_program_info(0x11, 0x22);
186+
iconfig
187+
.set_program_info(
188+
ProgramInfo { program_hash: 0x11, config_hash: 0x22, snos_program_hash: 0x33 }
189+
);
184190
}
185191

186192
#[test]
@@ -219,11 +225,13 @@ fn update_state_ok() {
219225
snf::start_cheat_caller_address(appchain.contract_address, c::OWNER());
220226
iconfig
221227
.set_program_info(
222-
program_hash: 0,
223-
config_hash: 8868593919264901768958912247765226517850727970326290266005120699201631282
228+
ProgramInfo {
229+
program_hash: 0,
230+
config_hash: 8868593919264901768958912247765226517850727970326290266005120699201631282,
231+
snos_program_hash: 3
232+
}
224233
);
225234
iconfig.set_facts_registry(address: fact_registry_mock.contract_address);
226-
227235
// The state update contains a message to appchain, therefore, before
228236
// being sealed, it must be sent first.
229237
// The nonce must be adjusted to ensure the correct message to be sent.

0 commit comments

Comments
 (0)