Skip to content

Commit

Permalink
feat: add MsgToL2 and MsgToL1 hash computation
Browse files Browse the repository at this point in the history
  • Loading branch information
glihm authored and xJonathanLEI committed Nov 3, 2023
1 parent 925eb66 commit 3a6bb66
Show file tree
Hide file tree
Showing 5 changed files with 291 additions and 2 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions starknet-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ starknet-ff = { version = "0.3.5", path = "../starknet-ff", default-features = f
base64 = { version = "0.21.0", default-features = false, features = ["alloc"] }
flate2 = { version = "1.0.25", optional = true }
hex = { version = "0.4.3", default-features = false, features = ["alloc"] }
num-bigint = { version = "0.4.3", default-features = false }
num-traits = { version = "0.2.15", default-features = false }
serde = { version = "1.0.160", default-features = false, features = ["derive"] }
serde_json = { version = "1.0.96", default-features = false, features = ["alloc", "raw_value"] }
serde_json_pythonic = { version = "0.1.2", default-features = false, features = ["alloc", "raw_value"] }
Expand Down
216 changes: 216 additions & 0 deletions starknet-core/src/types/messaging.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
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<FieldElement>,
}

#[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<FieldElement> {
// 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<u8> = 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<FieldElement> {
// 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<u8> = 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()
);
}
}
2 changes: 2 additions & 0 deletions starknet-core/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ pub mod requests;
pub mod contract;
pub use contract::ContractArtifact;

pub mod messaging;

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum MaybePendingBlockWithTxHashes {
Expand Down
71 changes: 69 additions & 2 deletions starknet-core/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use alloc::string::String;
use alloc::{string::String, vec::Vec};

use crate::{crypto::compute_hash_on_elements, types::FieldElement};

use num_bigint::BigUint;
use num_traits::identities::One;
use sha3::{Digest, Keccak256};
use starknet_crypto::pedersen_hash;

Expand Down Expand Up @@ -40,6 +42,9 @@ pub struct UdcUniqueSettings {
mod errors {
use core::fmt::{Display, Formatter, Result};

#[derive(Debug)]
pub struct U256OutOfRange;

#[derive(Debug)]
pub struct NonAsciiNameError;

Expand All @@ -64,6 +69,15 @@ mod errors {
}
}

#[cfg(feature = "std")]
impl std::error::Error for U256OutOfRange {}

impl Display for U256OutOfRange {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "u256 out of range")
}
}

#[cfg(feature = "std")]
impl std::error::Error for CairoShortStringToFeltError {}

Expand Down Expand Up @@ -92,7 +106,9 @@ mod errors {
}
}
}
pub use errors::{CairoShortStringToFeltError, NonAsciiNameError, ParseCairoShortStringError};
pub use errors::{
CairoShortStringToFeltError, NonAsciiNameError, ParseCairoShortStringError, U256OutOfRange,
};

/// A variant of eth-keccak that computes a value that fits in a Starknet field element.
pub fn starknet_keccak(data: &[u8]) -> FieldElement {
Expand Down Expand Up @@ -223,9 +239,33 @@ pub fn normalize_address(address: FieldElement) -> FieldElement {
address % ADDR_BOUND
}

#[allow(clippy::vec_init_then_push)]
pub fn biguint_to_felts(v: BigUint) -> Result<Vec<FieldElement>, U256OutOfRange> {
// TODO: is it better to have the following code for u256->Vec<FieldElement> in utils.rs?
let u128_max_plus_1: BigUint = BigUint::one() << 128;

let high = &v / &u128_max_plus_1;

if high >= u128_max_plus_1 {
return Err(U256OutOfRange);
}

let low = &v % &u128_max_plus_1;

// Unwrapping is safe as these are never out of range
let high = FieldElement::from_byte_slice_be(&high.to_bytes_be()).unwrap();
let low = FieldElement::from_byte_slice_be(&low.to_bytes_be()).unwrap();

let mut u256 = Vec::new();
u256.push(low);
u256.push(high);
Ok(u256)
}

#[cfg(test)]
mod tests {
use super::*;
use num_traits::Num;

#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
Expand Down Expand Up @@ -487,4 +527,31 @@ mod tests {
address
);
}

#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
fn test_u256_to_felts() {
let v = BigUint::from_str_radix(
"7d56f59fe6cce2fd0620a4b1cce69c488acac84670c38053ffec3763a2eec09d",
16,
)
.unwrap();

let felts = biguint_to_felts(v).unwrap();

assert!(
felts[0]
== FieldElement::from_hex_be(
"0x000000000000000000000000000000008acac84670c38053ffec3763a2eec09d"
)
.unwrap()
);
assert!(
felts[1]
== FieldElement::from_hex_be(
"0x000000000000000000000000000000007d56f59fe6cce2fd0620a4b1cce69c48"
)
.unwrap()
);
}
}

0 comments on commit 3a6bb66

Please sign in to comment.