@@ -6,14 +6,14 @@ use anyhow::Result;
66use cainome:: cairo_serde:: { CairoSerde , ContractAddress } ;
77use katana_tee_client:: { OnchainProof , StarknetCalldata } ;
88use piltover:: { MessageToAppchain , MessageToStarknet , PiltoverInput , TEEInput } ;
9- use sha3:: { Digest , Keccak256 } ;
109use starknet:: {
1110 accounts:: { Account , ExecutionEncoding , SingleOwnerAccount } ,
1211 core:: types:: { BlockId , BlockTag , Call , Felt , FunctionCall , TransactionReceipt } ,
1312 macros:: selector,
1413 providers:: { jsonrpc:: HttpTransport , JsonRpcClient , Provider } ,
1514 signers:: { LocalWallet , SigningKey } ,
1615} ;
16+ use starknet_types_core:: hash:: { Poseidon , StarkHash } ;
1717use tokio:: sync:: mpsc:: { Receiver , Sender } ;
1818use tracing:: { debug, error} ;
1919use url:: Url ;
@@ -27,22 +27,40 @@ use saya_core::{
2727
2828const POLLING_INTERVAL : Duration = Duration :: from_secs ( 2 ) ;
2929
30- /// Computes the Starknet L1→L2 message hash using keccak256 .
30+ /// Computes the L1→L2 message hash for a Starknet-settled appchain .
3131///
32- /// Matches the Ethereum StarknetMessaging.sol formula:
33- /// `keccak256(abi.encodePacked(from_address, to_address, nonce, selector, payload.length, payload))`
34- /// where `from_address` is a 20-byte Ethereum address (lower 20 bytes of the felt252).
32+ /// `saya-tee` always settles to a Starknet piltover core, so the canonical L1→L2
33+ /// message hash is Poseidon-based (`hash::compute_message_hash_sn_to_appc` in
34+ /// piltover), not the Ethereum `keccak256` `StarknetMessaging.sol` formula. Katana
35+ /// commits to this Poseidon hash in the appchain attestation's `messages_commitment`
36+ /// (it stores the L1-handler `message_hash` produced by its Starknet messaging
37+ /// collector). Using keccak here makes piltover's recomputed commitment mismatch and
38+ /// every block that consumes an L1→L2 message reverts with `'tee: invalid messages'`.
39+ ///
40+ /// Katana hashes over the L1-handler CALLDATA, which is `[from_address, ...payload]`:
41+ ///
42+ /// ```text
43+ /// Poseidon([from_address, to_address, nonce, selector, calldata.len(), ...calldata])
44+ /// where calldata = [from_address, ...payload]
45+ /// ```
46+ ///
47+ /// See katana `crates/messaging/src/stream/collector/starknet.rs`
48+ /// (`compute_starknet_to_appchain_message_hash`).
3549fn compute_l1_to_l2_msg_hash ( msg : & L1ToL2Message ) -> Felt {
36- let mut hasher = Keccak256 :: new ( ) ;
37- hasher. update ( & msg. from_address . to_bytes_be ( ) [ 12 ..] ) ;
38- hasher. update ( msg. to_address . to_bytes_be ( ) ) ;
39- hasher. update ( msg. nonce . to_bytes_be ( ) ) ;
40- hasher. update ( msg. selector . to_bytes_be ( ) ) ;
41- hasher. update ( Felt :: from ( msg. payload . len ( ) as u64 ) . to_bytes_be ( ) ) ;
42- for p in & msg. payload {
43- hasher. update ( p. to_bytes_be ( ) ) ;
44- }
45- Felt :: from_bytes_be ( & hasher. finalize ( ) . into ( ) )
50+ let mut calldata: Vec < Felt > = Vec :: with_capacity ( msg. payload . len ( ) + 1 ) ;
51+ calldata. push ( msg. from_address ) ;
52+ calldata. extend ( msg. payload . iter ( ) . copied ( ) ) ;
53+
54+ let mut buf: Vec < Felt > = vec ! [
55+ msg. from_address,
56+ msg. to_address,
57+ msg. nonce,
58+ msg. selector,
59+ Felt :: from( calldata. len( ) as u64 ) ,
60+ ] ;
61+ buf. extend ( calldata) ;
62+
63+ Poseidon :: hash_array ( & buf)
4664}
4765
4866fn messages_to_starknet ( msgs : & [ L2ToL1Message ] ) -> Vec < MessageToStarknet > {
0 commit comments