Skip to content

Commit

Permalink
fix hash computation for l1 -> l2 messaging
Browse files Browse the repository at this point in the history
  • Loading branch information
kariy committed May 20, 2024
1 parent 477e78e commit 7d017bb
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 28 deletions.
52 changes: 33 additions & 19 deletions crates/katana/core/src/service/messaging/ethereum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ use async_trait::async_trait;
use katana_primitives::chain::ChainId;
use katana_primitives::receipt::MessageToL1;
use katana_primitives::transaction::L1HandlerTx;
use katana_primitives::utils::transaction::compute_l1_message_hash;
use katana_primitives::utils::transaction::{
compute_l1_to_l2_message_hash, compute_l2_to_l1_message_hash,
};
use katana_primitives::FieldElement;
use starknet::core::types::EthAddress;
use tracing::{debug, trace, warn};

use super::{Error, MessagingConfig, Messenger, MessengerResult, LOG_TARGET};
Expand Down Expand Up @@ -190,6 +193,7 @@ impl Messenger for EthereumMessaging {
}
}

// TODO: refactor this as a method of the message log struct
fn l1_handler_tx_from_log(log: Log, chain_id: ChainId) -> MessengerResult<L1HandlerTx> {
let parsed_log = LogMessageToL2::LogMessageToL2Event::decode_log(
&alloy_primitives::Log::<LogData>::new(
Expand All @@ -202,23 +206,33 @@ fn l1_handler_tx_from_log(log: Log, chain_id: ChainId) -> MessengerResult<L1Hand
)
.unwrap();

let from_address = felt_from_address(parsed_log.from_address);
let from_address =
EthAddress::try_from(parsed_log.from_address.as_slice()).expect("valid address");
let contract_address = felt_from_u256(parsed_log.to_address);
let entry_point_selector = felt_from_u256(parsed_log.selector);
let nonce = felt_from_u256(parsed_log.nonce);
let nonce: u64 = parsed_log.nonce.try_into().expect("nonce does not fit into u64.");
let paid_fee_on_l1: u128 = parsed_log.fee.try_into().expect("Fee does not fit into u128.");
let payload = parsed_log.payload.clone().into_iter().map(felt_from_u256).collect::<Vec<_>>();

let mut calldata = vec![from_address];
calldata.extend(parsed_log.payload.clone().into_iter().map(felt_from_u256));
let message_hash = compute_l1_to_l2_message_hash(
from_address.clone(),
contract_address,
entry_point_selector,
&payload,
nonce,
);

let message_hash = compute_l1_message_hash(from_address, contract_address, &calldata);
// In an l1_handler transaction, the first element of the calldata is always the Ethereum
// address of the sender (msg.sender). https://docs.starknet.io/documentation/architecture_and_concepts/Network_Architecture/messaging-mechanism/#l1-l2-messages
let mut calldata = vec![FieldElement::from(from_address)];
calldata.extend(payload.clone());

Ok(L1HandlerTx {
nonce,
calldata,
chain_id,
message_hash,
paid_fee_on_l1,
nonce: nonce.into(),
entry_point_selector,
version: FieldElement::ZERO,
contract_address: contract_address.into(),
Expand All @@ -230,10 +244,12 @@ fn parse_messages(messages: &[MessageToL1]) -> Vec<U256> {
messages
.iter()
.map(|msg| {
U256::from_be_bytes(
compute_l1_message_hash(msg.from_address.into(), msg.to_address, &msg.payload)
.into(),
)
let hash = compute_l2_to_l1_message_hash(
msg.from_address.into(),
msg.to_address,
&msg.payload,
);
U256::from_be_bytes(hash.into())
})
.collect()
}
Expand All @@ -242,10 +258,6 @@ fn felt_from_u256(v: U256) -> FieldElement {
FieldElement::from_str(format!("{:#064x}", v).as_str()).unwrap()
}

fn felt_from_address(v: Address) -> FieldElement {
FieldElement::from_str(format!("{:#064x}", v).as_str()).unwrap()
}

#[cfg(test)]
mod tests {

Expand All @@ -260,7 +272,7 @@ mod tests {
let from_address = "0x000000000000000000000000be3C44c09bc1a3566F3e1CA12e5AbA0fA4Ca72Be";
let to_address = "0x039dc79e64f4bb3289240f88e0bae7d21735bef0d1a51b2bf3c4730cb16983e1";
let selector = "0x02f15cff7b0eed8b9beb162696cf4e3e0e35fa7032af69cd1b7d2ac67a13f40f";
let nonce = 783082_u128;
let nonce = 783082_u64;
let fee = 30000_u128;

// Payload two values: [1, 2].
Expand Down Expand Up @@ -298,9 +310,11 @@ mod tests {
// SN_GOERLI.
let chain_id = ChainId::Named(NamedChainId::Goerli);
let to_address = FieldElement::from_hex_be(to_address).unwrap();
let from_address = FieldElement::from_hex_be(from_address).unwrap();
let from_address = EthAddress::from_str(from_address).unwrap();
let selector = FieldElement::from_hex_be(selector).unwrap();

let message_hash = compute_l1_message_hash(from_address, to_address, &calldata);
let message_hash =
compute_l1_to_l2_message_hash(from_address, to_address, selector, &calldata, nonce);

let expected = L1HandlerTx {
calldata,
Expand All @@ -310,7 +324,7 @@ mod tests {
version: FieldElement::ZERO,
nonce: FieldElement::from(nonce),
contract_address: to_address.into(),
entry_point_selector: FieldElement::from_hex_be(selector).unwrap(),
entry_point_selector: selector,
};
let tx_hash = expected.calculate_hash();

Expand Down
7 changes: 4 additions & 3 deletions crates/katana/core/src/service/messaging/starknet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use async_trait::async_trait;
use katana_primitives::chain::ChainId;
use katana_primitives::receipt::MessageToL1;
use katana_primitives::transaction::L1HandlerTx;
use katana_primitives::utils::transaction::compute_l1_message_hash;
use katana_primitives::utils::transaction::compute_l2_to_l1_message_hash;
use starknet::accounts::{Account, Call, ExecutionEncoding, SingleOwnerAccount};
use starknet::core::types::{BlockId, BlockTag, EmittedEvent, EventFilter, FieldElement};
use starknet::core::utils::starknet_keccak;
Expand Down Expand Up @@ -336,7 +336,8 @@ fn l1_handler_tx_from_event(event: &EmittedEvent, chain_id: ChainId) -> Result<L
let mut calldata = vec![from_address];
calldata.extend(&event.data[3..]);

let message_hash = compute_l1_message_hash(from_address, to_address, &calldata);
// TODO: this should be using the l1 -> l2 hash computation instead.
let message_hash = compute_l2_to_l1_message_hash(from_address, to_address, &calldata);

Ok(L1HandlerTx {
nonce,
Expand Down Expand Up @@ -469,7 +470,7 @@ mod tests {
transaction_hash,
};

let message_hash = compute_l1_message_hash(from_address, to_address, &calldata);
let message_hash = compute_l2_to_l1_message_hash(from_address, to_address, &calldata);

let expected = L1HandlerTx {
nonce,
Expand Down
19 changes: 16 additions & 3 deletions crates/katana/primitives/src/utils/transaction.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use alloy_primitives::B256;
use starknet::core::crypto::compute_hash_on_elements;
use starknet::core::types::{DataAvailabilityMode, MsgToL1, ResourceBounds};
use starknet::core::types::{DataAvailabilityMode, EthAddress, MsgToL1, MsgToL2, ResourceBounds};
use starknet_crypto::poseidon_hash_many;

use crate::FieldElement;
Expand Down Expand Up @@ -261,16 +261,29 @@ pub fn compute_l1_handler_tx_hash(
])
}

/// Computes the hash of a L1 message.
/// Computes the hash of a L2 to L1 message.
///
/// The hash that is used to consume the message in L1.
pub fn compute_l1_message_hash(
pub fn compute_l2_to_l1_message_hash(
from_address: FieldElement,
to_address: FieldElement,
payload: &[FieldElement],
) -> B256 {
let msg = MsgToL1 { from_address, to_address, payload: payload.to_vec() };
B256::from_slice(msg.hash().as_bytes())
}

// TODO: standardize the usage of eth types. prefer to use alloy (for its convenience) instead of
// starknet-rs's types.
/// Computes the hash of a L1 to L2 message.
pub fn compute_l1_to_l2_message_hash(
from_address: EthAddress,
to_address: FieldElement,
selector: FieldElement,
payload: &[FieldElement],
nonce: u64,
) -> B256 {
let msg = MsgToL2 { from_address, to_address, selector, payload: payload.to_vec(), nonce };
B256::from_slice(msg.hash().as_bytes())
}

Expand Down
10 changes: 7 additions & 3 deletions crates/katana/rpc/rpc-types/src/message.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use katana_primitives::chain::ChainId;
use katana_primitives::transaction::L1HandlerTx;
use katana_primitives::utils::transaction::compute_l1_message_hash;
use katana_primitives::utils::transaction::compute_l2_to_l1_message_hash;
use katana_primitives::FieldElement;
use serde::{Deserialize, Serialize};

Expand All @@ -9,7 +9,11 @@ pub struct MsgFromL1(starknet::core::types::MsgFromL1);

impl MsgFromL1 {
pub fn into_tx_with_chain_id(self, chain_id: ChainId) -> L1HandlerTx {
let message_hash = compute_l1_message_hash(
// Set the L1 to L2 message nonce to 0, because this is just used
// for the `estimateMessageFee` RPC.
let nonce = FieldElement::ZERO;

let message_hash = compute_l2_to_l1_message_hash(
// This conversion will never fail bcs `from_address` is 20 bytes and the it will only
// fail if the slice is > 32 bytes
FieldElement::from_byte_slice_be(self.0.from_address.as_bytes()).unwrap(),
Expand All @@ -18,10 +22,10 @@ impl MsgFromL1 {
);

L1HandlerTx {
nonce,
chain_id,
message_hash,
calldata: self.0.payload,
nonce: Default::default(),
version: FieldElement::ZERO,
paid_fee_on_l1: Default::default(),
contract_address: self.0.to_address.into(),
Expand Down

0 comments on commit 7d017bb

Please sign in to comment.