From dd3060e0b053f4b7b8ff40857c9f2c5e20ec9959 Mon Sep 17 00:00:00 2001 From: glihm Date: Fri, 3 Nov 2023 13:04:07 -0600 Subject: [PATCH] fix: remove MsgToL2 code and rework MsgToL1 hash function --- starknet-core/src/types/messaging.rs | 216 --------------------------- starknet-core/src/types/mod.rs | 2 - starknet-core/src/types/msg.rs | 67 +++++++++ 3 files changed, 67 insertions(+), 218 deletions(-) delete mode 100644 starknet-core/src/types/messaging.rs diff --git a/starknet-core/src/types/messaging.rs b/starknet-core/src/types/messaging.rs deleted file mode 100644 index beb62679..00000000 --- a/starknet-core/src/types/messaging.rs +++ /dev/null @@ -1,216 +0,0 @@ -use alloc::vec::Vec; - -use num_bigint::BigUint; -use sha3::{Digest, Keccak256}; -use starknet_ff::FieldElement; - -use super::{eth_address::EthAddress, L1HandlerTransaction, MsgToL1}; -use crate::utils::biguint_to_felts; - -pub struct MsgToL2 { - from_address: EthAddress, - to_address: FieldElement, - nonce: FieldElement, - selector: FieldElement, - payload: Vec, -} - -#[allow(clippy::vec_init_then_push)] -impl MsgToL2 { - /// Returns the hash as a vector a FieldElements - /// representing a serialized u256: [low, high]. - pub fn hash_as_felts(&self) -> Vec { - // TODO: is it safe to unwrap as keccak returns a valid u256? - biguint_to_felts(self.hash()).unwrap() - } - - /// Computes MsgToL2 hash. - pub fn hash(&self) -> BigUint { - let mut buf: Vec = Vec::new(); - - // As EthAddress is only 20 bytes, we do want the 32 bytes - // to compute the hash as done in solidity `uint256(uint160(fromAddress))`. - let from_felt: FieldElement = self.from_address.clone().into(); - - buf.extend(from_felt.to_bytes_be()); - buf.extend(self.to_address.to_bytes_be()); - buf.extend(self.nonce.to_bytes_be()); - buf.extend(self.selector.to_bytes_be()); - buf.extend(FieldElement::from(self.payload.len()).to_bytes_be()); - for p in &self.payload { - buf.extend(p.to_bytes_be()); - } - - let mut hasher = Keccak256::new(); - hasher.update(buf); - let hash = hasher.finalize(); - BigUint::from_bytes_be(hash.as_slice()) - } -} - -impl L1HandlerTransaction { - /// Parses and returns the `MsgToL2` from - /// the transaction's content. - pub fn parse_msg_to_l2(&self) -> MsgToL2 { - // TODO: is that necessary? As the sequencer - // itself is the one firing this kind of transaction? - assert!(!self.calldata.is_empty()); - - // Ok to unwrap as the sequencer already checks for address ranges - // even if the `from_address` in `l1_handler` is still `felt252` type in cairo. - let from_address = self.calldata[0].try_into().unwrap(); - let to_address = self.contract_address; - let selector = self.entry_point_selector; - let nonce = (self.nonce as u128).into(); - let payload = &self.calldata[1..]; - - MsgToL2 { - from_address, - to_address, - selector, - nonce, - payload: payload.to_vec(), - } - } -} - -impl MsgToL1 { - pub fn hash_as_felts(&self) -> Vec { - // TODO: is it safe to unwrap as keccak returns a valid u256? - biguint_to_felts(self.hash()).unwrap() - } - - /// Computes MsgToL1 hash. - pub fn hash(&self) -> BigUint { - let mut buf: Vec = Vec::new(); - buf.extend(self.from_address.to_bytes_be()); - buf.extend(self.to_address.to_bytes_be()); - buf.extend(FieldElement::from(self.payload.len()).to_bytes_be()); - - for p in &self.payload { - buf.extend(p.to_bytes_be()); - } - - let mut hasher = Keccak256::new(); - hasher.update(buf); - let hash = hasher.finalize(); - BigUint::from_bytes_be(hash.as_slice()) - } -} - -#[cfg(test)] -mod test { - use alloc::vec::Vec; - - use num_traits::Num; - - use super::*; - use crate::types::EthAddress; - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[allow(clippy::vec_init_then_push)] - fn test_msg_to_l2_hash() { - let mut payload = Vec::new(); - payload.push(FieldElement::ONE); - payload.push(FieldElement::TWO); - - // Tx used for this test on goerli: - // 0x46144b600db00853c57a8cf003030ffaa51a810758ef5bfe1bb42bf55b7af38. - let msg = MsgToL2 { - from_address: EthAddress::from_hex("0xbe3c44c09bc1a3566f3e1ca12e5aba0fa4ca72be") - .unwrap(), - to_address: FieldElement::from_hex_be( - "0x039dc79e64f4bb3289240f88e0bae7d21735bef0d1a51b2bf3c4730cb16983e1", - ) - .unwrap(), - selector: FieldElement::from_hex_be( - "0x02f15cff7b0eed8b9beb162696cf4e3e0e35fa7032af69cd1b7d2ac67a13f40f", - ) - .unwrap(), - payload, - nonce: 782870_u128.into(), - }; - - assert!( - msg.hash() - == BigUint::from_str_radix( - "7d56f59fe6cce2fd0620a4b1cce69c488acac84670c38053ffec3763a2eec09d", - 16 - ) - .unwrap() - ); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[allow(clippy::vec_init_then_push)] - fn test_msg_to_l2_handler_tx() { - // Tx used for this test on goerli: - // 0x46144b600db00853c57a8cf003030ffaa51a810758ef5bfe1bb42bf55b7af38. - let mut calldata = Vec::new(); - calldata - .push(FieldElement::from_hex_be("0xbe3c44c09bc1a3566f3e1ca12e5aba0fa4ca72be").unwrap()); - calldata.push(FieldElement::ONE); - calldata.push(FieldElement::TWO); - - let tx = L1HandlerTransaction { - transaction_hash: FieldElement::from_hex_be( - "0x46144b600db00853c57a8cf003030ffaa51a810758ef5bfe1bb42bf55b7af38", - ) - .unwrap(), - version: 0x0, - nonce: 0xbf216, - contract_address: FieldElement::from_hex_be( - "0x39dc79e64f4bb3289240f88e0bae7d21735bef0d1a51b2bf3c4730cb16983e1", - ) - .unwrap(), - entry_point_selector: FieldElement::from_hex_be( - "0x2f15cff7b0eed8b9beb162696cf4e3e0e35fa7032af69cd1b7d2ac67a13f40f", - ) - .unwrap(), - calldata, - }; - - let msg = tx.parse_msg_to_l2(); - - assert!( - msg.hash() - == BigUint::from_str_radix( - "7d56f59fe6cce2fd0620a4b1cce69c488acac84670c38053ffec3763a2eec09d", - 16 - ) - .unwrap() - ); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[allow(clippy::vec_init_then_push)] - fn test_msg_to_l1_hash() { - // Tx used for this test on goerli: - // 0x34a700d53a8aac7fb241d2b3b696d87e5bd1bb291104bb3bc6cd3e3ca0482f5 - let mut payload = Vec::new(); - payload.push(FieldElement::ONE); - payload.push(FieldElement::TWO); - - let msg = MsgToL1 { - from_address: FieldElement::from_hex_be( - "0x39dc79e64f4bb3289240f88e0bae7d21735bef0d1a51b2bf3c4730cb16983e1", - ) - .unwrap(), - to_address: FieldElement::from_hex_be("0xbe3c44c09bc1a3566f3e1ca12e5aba0fa4ca72be") - .unwrap(), - payload, - }; - - assert!( - msg.hash() - == BigUint::from_str_radix( - "27dd75eb3d94688853676deda3b2cf14d2ef1074f81e1f5712d7c3946b9ab727", - 16 - ) - .unwrap() - ); - } -} diff --git a/starknet-core/src/types/mod.rs b/starknet-core/src/types/mod.rs index eabaa7c7..18ff37aa 100644 --- a/starknet-core/src/types/mod.rs +++ b/starknet-core/src/types/mod.rs @@ -54,8 +54,6 @@ pub mod requests; pub mod contract; pub use contract::ContractArtifact; -pub mod messaging; - #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum MaybePendingBlockWithTxHashes { diff --git a/starknet-core/src/types/msg.rs b/starknet-core/src/types/msg.rs index da5ad5e4..f3a4dab9 100644 --- a/starknet-core/src/types/msg.rs +++ b/starknet-core/src/types/msg.rs @@ -4,6 +4,7 @@ use sha3::{Digest, Keccak256}; use starknet_ff::FieldElement; use super::{EthAddress, Hash256}; +use crate::types::MsgToL1; #[derive(Debug, Clone)] pub struct MsgToL2 { @@ -51,6 +52,42 @@ impl MsgToL2 { } } +impl MsgToL1 { + /// Calculates the message hash based on the algorithm documented here: + /// + /// https://docs.starknet.io/documentation/architecture_and_concepts/Network_Architecture/messaging-mechanism/#structure_and_hashing_l2-l1 + /// + /// In the current version of the documentation, it's not stipulated + /// that `ToAddress` has to be padded, but it needs to. + /// In fact, the L1 Starknet Core Contract is padding the message + /// sender to compute the message hash. + /// + /// Can be found in the file `StarknetMessaging.sol`, line 153 here: https://vscode.blockscan.com/ethereum/0x16938e4b59297060484fa56a12594d8d6f4177e8 + pub fn hash(&self) -> Hash256 { + let mut hasher = Keccak256::new(); + + // FromAddress + hasher.update(self.from_address.to_bytes_be()); + + // ToAddress + hasher.update(self.to_address.to_bytes_be()); + + // Payload.length + hasher.update([0u8; 24]); + hasher.update((self.payload.len() as u64).to_be_bytes()); + + // Payload + for item in self.payload.iter() { + hasher.update(item.to_bytes_be()); + } + + let hash = hasher.finalize(); + + // Because we know hash is always 32 bytes + Hash256::from_bytes(unsafe { *(hash[..].as_ptr() as *const [u8; 32]) }) + } +} + #[cfg(test)] mod tests { use super::*; @@ -88,4 +125,34 @@ mod tests { assert_eq!(msg.hash(), expected_hash); } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn test_msg_to_l1_hash() { + // Goerli-1 tx (L1): 59e37da138dcf7ab9ca4fc7fde15d3f113a06781c4181dfcf0d74753023060b1 Log #40. + // Goerli-1 tx (L2): 4e0bbc07ff29e5df13dfbcb7e4746fdde52c3649a6a69bd86b15397769722fd + + let msg = MsgToL1 { + from_address: FieldElement::from_hex_be("0x0164cba33fb7152531f6b4cfc3fff26b4d7b26b4900e0881042edd607b428a92") + .unwrap(), + to_address: FieldElement::from_hex_be( + "0x000000000000000000000000b6dbfaa86bb683152e4fc2401260f9ca249519c0", + ) + .unwrap(), + payload: vec![ + FieldElement::from_hex_be("0x0").unwrap(), + FieldElement::from_hex_be("0x0").unwrap(), + FieldElement::from_hex_be("0x0182b8").unwrap(), + FieldElement::from_hex_be("0x0").unwrap(), + FieldElement::from_hex_be("0x0384").unwrap(), + FieldElement::from_hex_be("0x0").unwrap(), + ], + }; + + let expected_hash = + Hash256::from_hex("0x326a04493fc8f24ac6c6ae7bdba23243ce03ec3aae53f0ed3a0d686eb8cac930") + .unwrap(); + + assert_eq!(msg.hash(), expected_hash); + } }