diff --git a/Cargo.lock b/Cargo.lock index 4a54ed8b..5557d752 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4614,10 +4614,7 @@ dependencies = [ "sbv-kv", "sbv-primitives", "sbv-trie", - "serde", - "serde_json", "thiserror 1.0.69", - "tiny-keccak", "tracing", "tracing-subscriber", ] @@ -5118,7 +5115,6 @@ version = "2.0.0" dependencies = [ "anyhow", "clap", - "futures", "pprof", "sbv", "serde", diff --git a/crates/bin/Cargo.toml b/crates/bin/Cargo.toml index 76f87fb5..c158c0e2 100644 --- a/crates/bin/Cargo.toml +++ b/crates/bin/Cargo.toml @@ -15,7 +15,6 @@ workspace = true [dependencies] anyhow.workspace = true clap = { workspace = true, features = ["derive"] } -futures.workspace = true serde = { workspace = true, features = ["derive"] } serde_json.workspace = true serde_path_to_error.workspace = true diff --git a/crates/bin/src/commands/run_file.rs b/crates/bin/src/commands/run_file.rs index bd998ce7..e9d437d0 100644 --- a/crates/bin/src/commands/run_file.rs +++ b/crates/bin/src/commands/run_file.rs @@ -13,6 +13,8 @@ pub struct RunFileCommand { #[cfg(feature = "scroll")] #[arg(short, long)] chunk_mode: bool, + #[cfg(feature = "scroll")] + prev_msg_queue_hash: Option, } impl RunFileCommand { @@ -42,13 +44,13 @@ impl RunFileCommand { fn run_chunk(self) -> anyhow::Result<()> { use anyhow::bail; use sbv::{ - core::{ChunkInfo, EvmDatabase, EvmExecutor}, + core::{EvmDatabase, EvmExecutor}, kv::{nohash::NoHashMap, null::NullProvider}, primitives::{ BlockWitness as _, chainspec::{Chain, get_chain_spec}, - ext::{BlockWitnessChunkExt, BlockWitnessExt, TxBytesHashExt}, - types::BlockWitness, + ext::{BlockWitnessChunkExt, BlockWitnessExt}, + types::{BlockWitness, ChunkInfoBuilder}, }, trie::BlockWitnessTrieExt, }; @@ -71,10 +73,15 @@ impl RunFileCommand { .iter() .map(|w| w.build_reth_block()) .collect::, _>>()?; - let chunk_info = - ChunkInfo::from_blocks(witnesses[0].chain_id, witnesses[0].pre_state_root, &blocks); - let chain_spec = get_chain_spec(Chain::from_id(chunk_info.chain_id())).unwrap(); + let chain_id = witnesses[0].chain_id; + let chain_spec = get_chain_spec(Chain::from_id(chain_id)).unwrap(); + + let mut chunk_info_builder = ChunkInfoBuilder::new(&chain_spec, &blocks); + if let Some(prev_msg_queue_hash) = self.prev_msg_queue_hash { + chunk_info_builder.prev_msg_queue_hash(prev_msg_queue_hash); + } + let mut code_db = NoHashMap::default(); witnesses.import_codes(&mut code_db); let mut nodes_provider = NoHashMap::default(); @@ -82,7 +89,7 @@ impl RunFileCommand { let mut db = EvmDatabase::new_from_root( &code_db, - chunk_info.prev_state_root(), + chunk_info_builder.get_prev_state_root(), &nodes_provider, &NullProvider, )?; @@ -91,16 +98,12 @@ impl RunFileCommand { db.update(&nodes_provider, output.state.state.iter())?; } let post_state_root = db.commit_changes(); - if post_state_root != chunk_info.post_state_root() { + if post_state_root != chunk_info_builder.get_post_state_root() { bail!("post state root mismatch"); } - let withdraw_root = db.withdraw_root()?; - let tx_bytes_hash = blocks - .iter() - .flat_map(|b| b.body().transactions.iter()) - .tx_bytes_hash(); - let _public_input_hash = chunk_info.public_input_hash(&withdraw_root, &tx_bytes_hash); + let chunk_info = chunk_info_builder.build(db.withdraw_root()?); + let _public_input_hash = chunk_info.pi_hash(); dev_info!("[chunk mode] public input hash: {_public_input_hash:?}"); Ok(()) diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 569f1789..ca617710 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -18,10 +18,8 @@ reth-evm-ethereum.workspace = true reth-execution-types.workspace = true reth-scroll-evm = { workspace = true, optional = true } reth-storage-errors.workspace = true -serde.workspace = true -serde_json.workspace = true + thiserror.workspace = true -tiny-keccak.workspace = true sbv-primitives.workspace = true sbv-helpers.workspace = true @@ -30,8 +28,6 @@ sbv-trie.workspace = true [dev-dependencies] ctor.workspace = true -serde.workspace = true -serde_json.workspace = true tracing.workspace = true tracing-subscriber.workspace = true diff --git a/crates/core/src/chunk.rs b/crates/core/src/chunk.rs index 8ca6042c..e69de29b 100644 --- a/crates/core/src/chunk.rs +++ b/crates/core/src/chunk.rs @@ -1,119 +0,0 @@ -use sbv_primitives::{B256, BlockChunkExt, RecoveredBlock, types::reth::Block}; -use tiny_keccak::{Hasher, Keccak}; - -/// A chunk is a set of continuous blocks. -/// ChunkInfo is metadata of chunk, with following fields: -/// - state root before this chunk -/// - state root after this chunk -/// - the withdraw root after this chunk -/// - the data hash of this chunk -/// - the tx data hash of this chunk -/// - flattened L2 tx bytes hash -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] -pub struct ChunkInfo { - chain_id: u64, - prev_state_root: B256, - post_state_root: B256, - data_hash: B256, -} - -impl ChunkInfo { - /// Construct by block traces - #[must_use] - pub fn from_blocks( - chain_id: u64, - prev_state_root: B256, - blocks: &[RecoveredBlock], - ) -> Self { - let last_block = blocks.last().expect("at least one block"); - - let data_hash = cycle_track!( - { - let mut data_hasher = Keccak::v256(); - for block in blocks.iter() { - block.hash_da_header(&mut data_hasher); - } - for block in blocks.iter() { - block.hash_l1_msg(&mut data_hasher); - } - let mut data_hash = B256::ZERO; - data_hasher.finalize(&mut data_hash.0); - data_hash - }, - "Keccak::v256" - ); - - ChunkInfo { - chain_id, - prev_state_root, - post_state_root: last_block.state_root, - data_hash, - } - } - - /// Public input hash for a given chunk is defined as - /// keccak( - /// chain id || - /// prev state root || - /// post state root || - /// withdraw root || - /// chunk data hash || - /// chunk txdata hash - /// ) - pub fn public_input_hash(&self, withdraw_root: &B256, tx_bytes_hash: &B256) -> B256 { - let mut hasher = Keccak::v256(); - - hasher.update(&self.chain_id.to_be_bytes()); - hasher.update(self.prev_state_root.as_ref()); - hasher.update(self.post_state_root.as_slice()); - hasher.update(withdraw_root.as_slice()); - hasher.update(self.data_hash.as_slice()); - hasher.update(tx_bytes_hash.as_slice()); - - let mut public_input_hash = B256::ZERO; - hasher.finalize(&mut public_input_hash.0); - public_input_hash - } - - /// Chain ID of this chunk - pub fn chain_id(&self) -> u64 { - self.chain_id - } - - /// State root before this chunk - pub fn prev_state_root(&self) -> B256 { - self.prev_state_root - } - - /// State root after this chunk - pub fn post_state_root(&self) -> B256 { - self.post_state_root - } - - /// Data hash of this chunk - pub fn data_hash(&self) -> B256 { - self.data_hash - } -} - -#[cfg(test)] -mod tests { - use super::*; - use sbv_primitives::{BlockWitness as _, RecoveredBlock, types::BlockWitness}; - - const TRACES_STR: [&str; 4] = [ - include_str!("../../../testdata/holesky_witness/2971844.json"), - include_str!("../../../testdata/holesky_witness/2971845.json"), - include_str!("../../../testdata/holesky_witness/2971846.json"), - include_str!("../../../testdata/holesky_witness/2971847.json"), - ]; - - #[test] - fn test_public_input_hash() { - let witnesses: [BlockWitness; 4] = TRACES_STR.map(|s| serde_json::from_str(s).unwrap()); - let blocks: [RecoveredBlock; 4] = - witnesses.clone().map(|s| s.build_reth_block().unwrap()); - - let _ = ChunkInfo::from_blocks(1, witnesses[0].pre_state_root, &blocks); - } -} diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 0d311458..2420ab72 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -4,11 +4,6 @@ extern crate sbv_helpers; extern crate core; -#[cfg(feature = "scroll")] -mod chunk; -#[cfg(feature = "scroll")] -pub use chunk::ChunkInfo; - mod database; pub use database::{DatabaseError, DatabaseRef, EvmDatabase}; diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 22194d74..ecab315e 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -51,6 +51,7 @@ scroll = [ "dep:scroll-alloy-rpc-types", "dep:scroll-alloy-network", "reth-scroll-primitives/serde", + "reth-scroll-primitives/scroll", "revm/scroll-default-handler", "revm/optional_no_base_fee", ] diff --git a/crates/primitives/src/ext.rs b/crates/primitives/src/ext.rs index 693e34ac..44ed0080 100644 --- a/crates/primitives/src/ext.rs +++ b/crates/primitives/src/ext.rs @@ -95,31 +95,33 @@ impl BlockWitnessExt for [T] { #[cfg(feature = "scroll")] pub trait TxBytesHashExt { /// Hash the transaction bytes. - fn tx_bytes_hash(self) -> B256; + fn tx_bytes_hash(self) -> (usize, B256); /// Hash the transaction bytes. - fn tx_bytes_hash_in(self, rlp_buffer: &mut Vec) -> B256; + fn tx_bytes_hash_in(self, rlp_buffer: &mut Vec) -> (usize, B256); } #[cfg(feature = "scroll")] impl<'a, I: IntoIterator, Tx: alloy_eips::eip2718::Encodable2718 + 'a> TxBytesHashExt for I { - fn tx_bytes_hash(self) -> B256 { + fn tx_bytes_hash(self) -> (usize, B256) { let mut rlp_buffer = Vec::new(); self.tx_bytes_hash_in(&mut rlp_buffer) } - fn tx_bytes_hash_in(self, rlp_buffer: &mut Vec) -> B256 { + fn tx_bytes_hash_in(self, rlp_buffer: &mut Vec) -> (usize, B256) { use tiny_keccak::{Hasher, Keccak}; let mut tx_bytes_hasher = Keccak::v256(); + let mut len = 0; for tx in self.into_iter() { tx.encode_2718(rlp_buffer); + len += rlp_buffer.len(); tx_bytes_hasher.update(rlp_buffer); rlp_buffer.clear(); } let mut tx_bytes_hash = B256::ZERO; tx_bytes_hasher.finalize(&mut tx_bytes_hash.0); - tx_bytes_hash + (len, tx_bytes_hash) } } diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 4e50699b..f65d59d7 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -200,17 +200,22 @@ pub trait Withdrawal: fmt::Debug { } /// Chunk related extension methods for Block -/// FIXME: gate this behind scroll feature +#[cfg(feature = "scroll")] pub trait BlockChunkExt { /// Hash the header of the block - fn hash_da_header(&self, hasher: &mut impl tiny_keccak::Hasher); + fn legacy_hash_da_header(&self, hasher: &mut impl tiny_keccak::Hasher); + /// Hash the l1 messages of the block + fn legacy_hash_l1_msg(&self, hasher: &mut impl tiny_keccak::Hasher); /// Hash the l1 messages of the block - fn hash_l1_msg(&self, hasher: &mut impl tiny_keccak::Hasher); + fn hash_msg_queue(&self, initial_queue_hash: &B256) -> B256; + /// Number of L1 msg txs in the block + fn num_l1_msgs(&self) -> usize; } +#[cfg(feature = "scroll")] impl BlockChunkExt for RecoveredBlock { #[inline] - fn hash_da_header(&self, hasher: &mut impl tiny_keccak::Hasher) { + fn legacy_hash_da_header(&self, hasher: &mut impl tiny_keccak::Hasher) { hasher.update(&self.number.to_be_bytes()); hasher.update(&self.timestamp.to_be_bytes()); hasher.update( @@ -218,15 +223,59 @@ impl BlockChunkExt for RecoveredBlock { .to_be_bytes::<{ U256::BYTES }>(), ); hasher.update(&self.gas_limit.to_be_bytes()); - hasher.update(&(self.body().transactions.len() as u16).to_be_bytes()); // FIXME: l1 tx could be skipped, the actual tx count needs to be calculated + // FIXME: l1 tx could be skipped, the actual tx count needs to be calculated + hasher.update(&(self.body().transactions.len() as u16).to_be_bytes()); } #[inline] - fn hash_l1_msg(&self, hasher: &mut impl tiny_keccak::Hasher) { + fn legacy_hash_l1_msg(&self, hasher: &mut impl tiny_keccak::Hasher) { use reth_primitives_traits::SignedTransaction; - use types::consensus::Typed2718; - for tx in self.body().transactions.iter().filter(|tx| tx.ty() == 0x7e) { + for tx in self + .body() + .transactions + .iter() + .filter(|tx| tx.is_l1_message()) + { hasher.update(tx.tx_hash().as_slice()) } } + + #[inline] + fn hash_msg_queue(&self, initial_queue_hash: &B256) -> B256 { + use reth_primitives_traits::SignedTransaction; + use tiny_keccak::Hasher; + + let mut rolling_hash = *initial_queue_hash; + for tx in self + .body() + .transactions + .iter() + .filter(|tx| tx.is_l1_message()) + { + let mut hasher = tiny_keccak::Keccak::v256(); + hasher.update(rolling_hash.as_slice()); + hasher.update(tx.tx_hash().as_slice()); + + hasher.finalize(rolling_hash.as_mut_slice()); + + // clear last 32 bits, i.e. 4 bytes. + // https://github.com/scroll-tech/da-codec/blob/26dc8d575244560611548fada6a3a2745c60fe83/encoding/da.go#L817-L825 + // see also https://github.com/scroll-tech/da-codec/pull/42 + rolling_hash.0[28] = 0; + rolling_hash.0[29] = 0; + rolling_hash.0[30] = 0; + rolling_hash.0[31] = 0; + } + + rolling_hash + } + + #[inline] + fn num_l1_msgs(&self) -> usize { + self.body() + .transactions + .iter() + .filter(|tx| tx.is_l1_message()) + .count() + } } diff --git a/crates/primitives/src/types/scroll/chunk.rs b/crates/primitives/src/types/scroll/chunk.rs new file mode 100644 index 00000000..693e0c80 --- /dev/null +++ b/crates/primitives/src/types/scroll/chunk.rs @@ -0,0 +1,506 @@ +use crate::{ + B256, BlockChunkExt, RecoveredBlock, chainspec::ChainSpec, ext::TxBytesHashExt, + types::reth::Block, +}; +use sbv_helpers::cycle_track; +use tiny_keccak::{Hasher, Keccak}; + +/// Builder for ChunkInfo +#[derive(Clone, Debug)] +pub struct ChunkInfoBuilder<'a> { + chain_spec: &'a ChainSpec, + blocks: &'a [RecoveredBlock], + prev_msg_queue_hash: Option, +} + +/// ChunkInfo is metadata of chunk. +#[derive( + Clone, + Debug, + PartialEq, + Eq, + serde::Serialize, + serde::Deserialize, + rkyv::Archive, + rkyv::Serialize, + rkyv::Deserialize, +)] +#[rkyv(derive(Debug, PartialEq, Eq))] +pub enum ChunkInfo { + /// ChunkInfo before EuclidV2 hardfork + Legacy(LegacyChunkInfo), + /// ChunkInfo after EuclidV2 hardfork + EuclidV2(EuclidV2ChunkInfo), +} + +/// ChunkInfo before EuclidV2 hardfork +#[derive( + Clone, + Debug, + PartialEq, + Eq, + serde::Serialize, + serde::Deserialize, + rkyv::Archive, + rkyv::Serialize, + rkyv::Deserialize, +)] +#[rkyv(derive(Debug, PartialEq, Eq))] +pub struct LegacyChunkInfo { + /// The EIP-155 chain ID for all txs in the chunk. + #[rkyv(attr(doc = "The EIP-155 chain ID for all txs in the chunk."))] + pub chain_id: u64, + /// The state root before applying the chunk. + #[rkyv(attr(doc = "The state root before applying the chunk."))] + pub prev_state_root: B256, + /// The state root after applying the chunk. + #[rkyv(attr(doc = "The state root after applying the chunk."))] + pub post_state_root: B256, + /// The withdrawals root after applying the chunk. + #[rkyv(attr(doc = "The withdrawals root after applying the chunk."))] + pub withdraw_root: B256, + /// Digest of L1 message txs force included in the chunk. + #[rkyv(attr(doc = "Digest of L1 message txs force included in the chunk."))] + pub data_hash: B256, + /// Digest of L2 tx data flattened over all L2 txs in the chunk. + #[rkyv(attr(doc = "Digest of L2 tx data flattened over all L2 txs in the chunk."))] + pub tx_data_digest: B256, +} + +/// ChunkInfo after EuclidV2 hardfork +#[derive( + Clone, + Debug, + PartialEq, + Eq, + serde::Serialize, + serde::Deserialize, + rkyv::Archive, + rkyv::Serialize, + rkyv::Deserialize, +)] +#[rkyv(derive(Debug, PartialEq, Eq))] +pub struct EuclidV2ChunkInfo { + /// The EIP-155 chain ID for all txs in the chunk. + #[rkyv(attr(doc = "The EIP-155 chain ID for all txs in the chunk."))] + pub chain_id: u64, + /// The state root before applying the chunk. + #[rkyv(attr(doc = "The state root before applying the chunk."))] + pub prev_state_root: B256, + /// The state root after applying the chunk. + #[rkyv(attr(doc = "The state root after applying the chunk."))] + pub post_state_root: B256, + /// The withdrawals root after applying the chunk. + #[rkyv(attr(doc = "The withdrawals root after applying the chunk."))] + pub withdraw_root: B256, + /// length of L2 tx data (rlp encoded) flattened over all L2 txs in the chunk. + #[rkyv(attr(doc = "Digest of L2 tx data flattened over all L2 txs in the chunk."))] + pub tx_data_length: usize, + /// Digest of L2 tx data flattened over all L2 txs in the chunk. + #[rkyv(attr(doc = "Digest of L2 tx data flattened over all L2 txs in the chunk."))] + pub tx_data_digest: B256, + /// Rolling hash of message queue before applying the chunk. + #[rkyv(attr(doc = "Rolling hash of message queue before applying the chunk."))] + pub prev_msg_queue_hash: B256, + /// Rolling hash of message queue after applying the chunk. + #[rkyv(attr(doc = "Rolling hash of message queue after applying the chunk."))] + pub post_msg_queue_hash: B256, +} + +impl<'a> ChunkInfoBuilder<'a> { + /// Create a new ChunkInfoBuilder + pub fn new(chain_spec: &'a ChainSpec, blocks: &'a [RecoveredBlock]) -> Self { + assert!(!blocks.is_empty(), "blocks must not be empty"); + + ChunkInfoBuilder { + chain_spec, + blocks, + prev_msg_queue_hash: None, + } + } + + /// Check if EuclidV2 is enabled on this chunk + #[inline] + pub fn is_euclid_v2(&self) -> bool { + todo!("waiting for reth hardfork implementation") + } + + /// Set the prev msg queue hash + #[inline] + pub fn prev_msg_queue_hash(&mut self, prev_msg_queue_hash: B256) -> &mut Self { + assert!( + self.is_euclid_v2(), + "prev_msg_queue_hash is only for EuclidV2" + ); + + self.prev_msg_queue_hash = Some(prev_msg_queue_hash); + self + } + + /// Get the previous state root + #[inline] + pub fn get_prev_state_root(&self) -> B256 { + self.blocks.first().expect("at least one block").state_root + } + + /// Get the post state root + #[inline] + pub fn get_post_state_root(&self) -> B256 { + self.blocks.last().expect("at least one block").state_root + } + + /// Build the chunk info + pub fn build(self, withdraw_root: B256) -> ChunkInfo { + let chain_id = self.chain_spec.chain.id(); + let prev_state_root = self.get_prev_state_root(); + let post_state_root = self.get_post_state_root(); + + let (tx_data_length, tx_data_digest) = self + .blocks + .iter() + .flat_map(|b| b.body().transactions.iter()) + .tx_bytes_hash(); + + if self.is_euclid_v2() { + let prev_msg_queue_hash = self + .prev_msg_queue_hash + .expect("msg queue hash is required"); + let post_msg_queue_hash = cycle_track!( + { + let mut rolling_hash = prev_msg_queue_hash; + for block in self.blocks.iter() { + rolling_hash = block.hash_msg_queue(&rolling_hash); + } + rolling_hash + }, + "Keccak::v256" + ); + ChunkInfo::EuclidV2(EuclidV2ChunkInfo { + chain_id, + prev_state_root, + post_state_root, + withdraw_root, + tx_data_length, + tx_data_digest, + prev_msg_queue_hash, + post_msg_queue_hash, + }) + } else { + let data_hash = cycle_track!( + { + let mut data_hasher = Keccak::v256(); + for block in self.blocks.iter() { + block.legacy_hash_da_header(&mut data_hasher); + } + for block in self.blocks.iter() { + block.legacy_hash_l1_msg(&mut data_hasher); + } + let mut data_hash = B256::ZERO; + data_hasher.finalize(&mut data_hash.0); + data_hash + }, + "Keccak::v256" + ); + ChunkInfo::Legacy(LegacyChunkInfo { + chain_id, + prev_state_root, + post_state_root, + withdraw_root, + data_hash, + tx_data_digest, + }) + } + } +} + +impl ChunkInfo { + /// Get the chain id + pub fn chain_id(&self) -> u64 { + match self { + ChunkInfo::Legacy(info) => info.chain_id, + ChunkInfo::EuclidV2(info) => info.chain_id, + } + } + + /// Get the prev state root + pub fn prev_state_root(&self) -> B256 { + match self { + ChunkInfo::Legacy(info) => info.prev_state_root, + ChunkInfo::EuclidV2(info) => info.prev_state_root, + } + } + + /// Get the post state root + pub fn post_state_root(&self) -> B256 { + match self { + ChunkInfo::Legacy(info) => info.post_state_root, + ChunkInfo::EuclidV2(info) => info.post_state_root, + } + } + + /// Get the withdraw root + pub fn withdraw_root(&self) -> B256 { + match self { + ChunkInfo::Legacy(info) => info.withdraw_root, + ChunkInfo::EuclidV2(info) => info.withdraw_root, + } + } + + /// Get the tx data digest + pub fn tx_data_digest(&self) -> B256 { + match self { + ChunkInfo::Legacy(info) => info.tx_data_digest, + ChunkInfo::EuclidV2(info) => info.tx_data_digest, + } + } + + /// As legacy chunk info + pub fn as_legacy(&self) -> Option<&LegacyChunkInfo> { + match self { + ChunkInfo::Legacy(info) => Some(info), + _ => None, + } + } + + /// As EuclidV2 chunk info + pub fn as_euclid_v2(&self) -> Option<&EuclidV2ChunkInfo> { + match self { + ChunkInfo::EuclidV2(info) => Some(info), + _ => None, + } + } + + /// Into legacy chunk info + pub fn into_legacy(self) -> Option { + match self { + ChunkInfo::Legacy(info) => Some(info), + _ => None, + } + } + + /// Into EuclidV2 chunk info + pub fn into_euclid_v2(self) -> Option { + match self { + ChunkInfo::EuclidV2(info) => Some(info), + _ => None, + } + } + + /// Public input hash for a given chunk is defined as + /// + /// - Before EuclidV2: + /// ```text + /// keccak( + /// chain id || + /// prev state root || + /// post state root || + /// withdraw root || + /// chunk data hash || + /// chunk txdata hash + /// ) + /// ``` + /// + /// - After EuclidV2: + /// ```text + /// keccak( + /// chain id || + /// prev state root || + /// post state root || + /// withdraw root || + /// tx data hash || + /// prev msg queue hash || + /// post msg queue hash + /// ) + /// ``` + pub fn pi_hash(&self) -> B256 { + match self { + ChunkInfo::Legacy(info) => info.pi_hash(), + ChunkInfo::EuclidV2(info) => info.pi_hash(), + } + } +} + +impl ArchivedChunkInfo { + /// Public input hash for a given chunk is defined as + /// + /// - Before EuclidV2: + /// ```text + /// keccak( + /// chain id || + /// prev state root || + /// post state root || + /// withdraw root || + /// chunk data hash || + /// chunk txdata hash + /// ) + /// ``` + /// + /// - After EuclidV2: + /// ```text + /// keccak( + /// chain id || + /// prev state root || + /// post state root || + /// withdraw root || + /// tx data hash || + /// prev msg queue hash || + /// post msg queue hash + /// ) + /// ``` + pub fn pi_hash(&self) -> B256 { + match self { + ArchivedChunkInfo::Legacy(info) => info.pi_hash(), + ArchivedChunkInfo::EuclidV2(info) => info.pi_hash(), + } + } +} + +impl LegacyChunkInfo { + /// Public input hash for a given chunk is defined as + /// ```text + /// keccak( + /// chain id || + /// prev state root || + /// post state root || + /// withdraw root || + /// chunk data hash || + /// chunk txdata hash + /// ) + /// ``` + pub fn pi_hash(&self) -> B256 { + let mut hasher = Keccak::v256(); + + hasher.update(&self.chain_id.to_be_bytes()); + hasher.update(self.prev_state_root.as_ref()); + hasher.update(self.post_state_root.as_ref()); + hasher.update(self.withdraw_root.as_ref()); + hasher.update(self.data_hash.as_ref()); + hasher.update(self.tx_data_digest.as_ref()); + + let mut public_input_hash = B256::ZERO; + hasher.finalize(&mut public_input_hash.0); + public_input_hash + } +} + +impl EuclidV2ChunkInfo { + /// Public input hash for a given chunk is defined as + /// ```text + /// keccak( + /// chain id || + /// prev state root || + /// post state root || + /// withdraw root || + /// tx data hash || + /// prev msg queue hash || + /// post msg queue hash + /// ) + /// ``` + pub fn pi_hash(&self) -> B256 { + let mut hasher = Keccak::v256(); + + hasher.update(&self.chain_id.to_be_bytes()); + hasher.update(self.prev_state_root.as_ref()); + hasher.update(self.post_state_root.as_ref()); + hasher.update(self.withdraw_root.as_ref()); + hasher.update(self.tx_data_digest.as_ref()); + hasher.update(self.prev_msg_queue_hash.as_ref()); + hasher.update(self.post_msg_queue_hash.as_ref()); + + let mut public_input_hash = B256::ZERO; + hasher.finalize(&mut public_input_hash.0); + public_input_hash + } +} + +impl ArchivedLegacyChunkInfo { + /// Public input hash for a given chunk is defined as + /// ```text + /// keccak( + /// chain id || + /// prev state root || + /// post state root || + /// withdraw root || + /// chunk data hash || + /// chunk txdata hash + /// ) + /// ``` + pub fn pi_hash(&self) -> B256 { + let mut hasher = Keccak::v256(); + + hasher.update(&self.chain_id.to_native().to_be_bytes()); + hasher.update(self.prev_state_root.0.as_ref()); + hasher.update(self.post_state_root.0.as_ref()); + hasher.update(self.withdraw_root.0.as_ref()); + hasher.update(self.data_hash.0.as_ref()); + hasher.update(self.tx_data_digest.0.as_ref()); + + let mut public_input_hash = B256::ZERO; + hasher.finalize(&mut public_input_hash.0); + public_input_hash + } +} + +impl ArchivedEuclidV2ChunkInfo { + /// Public input hash for a given chunk is defined as + /// ```text + /// keccak( + /// chain id || + /// prev state root || + /// post state root || + /// withdraw root || + /// tx data hash || + /// prev msg queue hash || + /// post msg queue hash + /// ) + /// ``` + pub fn pi_hash(&self) -> B256 { + let mut hasher = Keccak::v256(); + + hasher.update(&self.chain_id.to_native().to_be_bytes()); + hasher.update(self.prev_state_root.0.as_ref()); + hasher.update(self.post_state_root.0.as_ref()); + hasher.update(self.withdraw_root.0.as_ref()); + hasher.update(self.tx_data_digest.0.as_ref()); + hasher.update(self.prev_msg_queue_hash.0.as_ref()); + hasher.update(self.post_msg_queue_hash.0.as_ref()); + + let mut public_input_hash = B256::ZERO; + hasher.finalize(&mut public_input_hash.0); + public_input_hash + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rkyv::rancor; + + #[test] + fn test_rkyv_chunk_info() { + const LEGACY: ChunkInfo = ChunkInfo::Legacy(LegacyChunkInfo { + chain_id: 1, + prev_state_root: B256::new([1; 32]), + post_state_root: B256::new([2; 32]), + withdraw_root: B256::new([3; 32]), + data_hash: B256::new([4; 32]), + tx_data_digest: B256::new([5; 32]), + }); + + const EUCLID_V2: ChunkInfo = ChunkInfo::EuclidV2(EuclidV2ChunkInfo { + chain_id: 1, + prev_state_root: B256::new([1; 32]), + post_state_root: B256::new([2; 32]), + withdraw_root: B256::new([3; 32]), + tx_data_length: 100, + tx_data_digest: B256::new([5; 32]), + prev_msg_queue_hash: B256::new([6; 32]), + post_msg_queue_hash: B256::new([7; 32]), + }); + + for chunk_info in &[LEGACY, EUCLID_V2] { + let serialized = rkyv::to_bytes::(chunk_info).unwrap(); + let _ = rkyv::access::(&serialized[..]).unwrap(); + } + } +} diff --git a/crates/primitives/src/types/scroll.rs b/crates/primitives/src/types/scroll/mod.rs similarity index 92% rename from crates/primitives/src/types/scroll.rs rename to crates/primitives/src/types/scroll/mod.rs index d6cea5a0..7705dd2f 100644 --- a/crates/primitives/src/types/scroll.rs +++ b/crates/primitives/src/types/scroll/mod.rs @@ -1,6 +1,9 @@ use crate::B256; use serde::{Deserialize, Serialize}; +mod chunk; +pub use chunk::*; + /// RPC response of the `scroll_diskRoot` method. #[derive(Debug, Copy, Clone, Serialize, Deserialize)] pub struct DiskRoot {