diff --git a/Cargo.lock b/Cargo.lock
index ccfb1bc70..8ae37fcef 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -300,28 +300,16 @@ dependencies = [
  "radium 0.3.0",
 ]
 
-[[package]]
-name = "bitvec"
-version = "0.22.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527"
-dependencies = [
- "funty 1.2.0",
- "radium 0.6.2",
- "tap",
- "wyz 0.4.0",
-]
-
 [[package]]
 name = "bitvec"
 version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
 dependencies = [
- "funty 2.0.0",
+ "funty",
  "radium 0.7.0",
  "tap",
- "wyz 0.5.1",
+ "wyz",
 ]
 
 [[package]]
@@ -2159,11 +2147,6 @@ dependencies = [
  "pkg-config",
 ]
 
-[[package]]
-name = "funty"
-version = "1.2.0"
-source = "git+https://github.com/ferrilab/funty/?rev=7ef0d890fbcd8b3def1635ac1a877fc298488446#7ef0d890fbcd8b3def1635ac1a877fc298488446"
-
 [[package]]
 name = "funty"
 version = "2.0.0"
@@ -2440,12 +2423,11 @@ checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
 [[package]]
 name = "halo2-mpt-circuits"
 version = "0.1.0"
-source = "git+https://github.com/scroll-tech/mpt-circuit.git?branch=scroll-dev-0111-halo2-upgrade#1aa8aa43289d9f29c2c8dbd5b2384f56bfd90fff"
+source = "git+https://github.com/scroll-tech/mpt-circuit.git?branch=scroll-dev-0129#631d71fd4dbe070fee34416efbfd12241d78c62f"
 dependencies = [
  "halo2_proofs",
  "hex",
  "lazy_static",
- "log",
  "num-bigint",
  "poseidon-circuit",
  "rand 0.8.5",
@@ -3908,9 +3890,9 @@ dependencies = [
 [[package]]
 name = "poseidon-circuit"
 version = "0.1.0"
-source = "git+https://github.com/scroll-tech/poseidon-circuit.git?branch=scroll-dev-0111-halo2-upgrade#b6761f8fc778c9851d874aa21fb3c4271071b717"
+source = "git+https://github.com/scroll-tech/poseidon-circuit.git?branch=scroll-dev-0201#ceda6061aa5e5d09ca5dfd5d90c581eb54077fc0"
 dependencies = [
- "bitvec 0.22.3",
+ "bitvec 1.0.1",
  "halo2_proofs",
  "lazy_static",
  "thiserror",
@@ -4070,12 +4052,6 @@ version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "def50a86306165861203e7f84ecffbbdfdea79f0e51039b33de1e952358c47ac"
 
-[[package]]
-name = "radium"
-version = "0.6.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb"
-
 [[package]]
 name = "radium"
 version = "0.7.0"
@@ -6422,15 +6398,6 @@ dependencies = [
  "web-sys",
 ]
 
-[[package]]
-name = "wyz"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "129e027ad65ce1453680623c3fb5163cbf7107bfe1aa32257e7d0e63f9ced188"
-dependencies = [
- "tap",
-]
-
 [[package]]
 name = "wyz"
 version = "0.5.1"
diff --git a/Cargo.toml b/Cargo.toml
index fe38ed5e3..f6dd3565d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,10 +13,6 @@ members = [
     "mock",
 ]
 
-[patch.crates-io]
-# temporary solution to funty@1.2.0 being yanked, tracking issue: https://github.com/ferrilab/funty/issues/7
-funty = { git = "https://github.com/ferrilab/funty/", rev = "7ef0d890fbcd8b3def1635ac1a877fc298488446" }
-
 [patch."https://github.com/privacy-scaling-explorations/halo2.git"]
 halo2_proofs = { git = "https://github.com/scroll-tech/halo2.git", branch = "scroll-dev-0220" }
 
diff --git a/eth-types/Cargo.toml b/eth-types/Cargo.toml
index 79d7cc432..a374bb9ba 100644
--- a/eth-types/Cargo.toml
+++ b/eth-types/Cargo.toml
@@ -24,7 +24,7 @@ num = "0.4"
 num-bigint = { version = "0.4" }
 strum_macros = "0.24"
 strum = "0.24"
-mpt-circuits = { package = "halo2-mpt-circuits", git = "https://github.com/scroll-tech/mpt-circuit.git", branch = "scroll-dev-0111-halo2-upgrade" }
+mpt-circuits = { package = "halo2-mpt-circuits", git = "https://github.com/scroll-tech/mpt-circuit.git", branch = "scroll-dev-0129" }
 [features]
 default = ["warn-unimplemented"]
 warn-unimplemented = []
diff --git a/zkevm-circuits/Cargo.toml b/zkevm-circuits/Cargo.toml
index befb78ee8..82780dafa 100644
--- a/zkevm-circuits/Cargo.toml
+++ b/zkevm-circuits/Cargo.toml
@@ -61,3 +61,4 @@ zktrie = []
 enable-sign-verify = []
 codehash = []
 reject-eip2718 = []
+poseidon-codehash = []
diff --git a/zkevm-circuits/src/bytecode_circuit/bytecode_unroller.rs b/zkevm-circuits/src/bytecode_circuit/bytecode_unroller.rs
index fd5322177..a00e4e882 100644
--- a/zkevm-circuits/src/bytecode_circuit/bytecode_unroller.rs
+++ b/zkevm-circuits/src/bytecode_circuit/bytecode_unroller.rs
@@ -58,3 +58,15 @@ pub fn unroll_with_codehash<F: Field>(code_hash: U256, bytes: Vec<u8>) -> Unroll
     }
     UnrolledBytecode { bytes, rows }
 }
+
+/// re-export bytes wrapped in hash field
+pub use super::circuit::to_poseidon_hash::HASHBLOCK_BYTES_IN_FIELD;
+use crate::table::PoseidonTable;
+
+/// Apply default constants in mod
+pub fn unroll_to_hash_input_default<F: Field>(
+    code: impl ExactSizeIterator<Item = u8>,
+) -> Vec<[F; PoseidonTable::INPUT_WIDTH]> {
+    use super::circuit::to_poseidon_hash::unroll_to_hash_input;
+    unroll_to_hash_input::<F, HASHBLOCK_BYTES_IN_FIELD, { PoseidonTable::INPUT_WIDTH }>(code)
+}
diff --git a/zkevm-circuits/src/bytecode_circuit/circuit.rs b/zkevm-circuits/src/bytecode_circuit/circuit.rs
index 7740163b4..d13e3539a 100644
--- a/zkevm-circuits/src/bytecode_circuit/circuit.rs
+++ b/zkevm-circuits/src/bytecode_circuit/circuit.rs
@@ -19,6 +19,9 @@ use super::{
     param::PUSH_TABLE_WIDTH,
 };
 
+/// An extended circuit for binding with poseidon
+pub mod to_poseidon_hash;
+
 #[cfg(feature = "onephase")]
 use halo2_proofs::plonk::FirstPhase as SecondPhase;
 #[cfg(not(feature = "onephase"))]
@@ -755,6 +758,12 @@ impl<F: Field> BytecodeCircuit<F> {
 }
 
 impl<F: Field> SubCircuit<F> for BytecodeCircuit<F> {
+    #[cfg(feature = "poseidon-codehash")]
+    type Config = to_poseidon_hash::ToHashBlockCircuitConfig<
+        F,
+        { to_poseidon_hash::HASHBLOCK_BYTES_IN_FIELD },
+    >;
+    #[cfg(not(feature = "poseidon-codehash"))]
     type Config = BytecodeCircuitConfig<F>;
 
     fn new_from_block(block: &witness::Block<F>) -> Self {
diff --git a/zkevm-circuits/src/bytecode_circuit/circuit/to_poseidon_hash.rs b/zkevm-circuits/src/bytecode_circuit/circuit/to_poseidon_hash.rs
new file mode 100644
index 000000000..3baffb81f
--- /dev/null
+++ b/zkevm-circuits/src/bytecode_circuit/circuit/to_poseidon_hash.rs
@@ -0,0 +1,752 @@
+use crate::{
+    evm_circuit::util::{and, constraint_builder::BaseConstraintBuilder, not, or, rlc, select},
+    table::{BytecodeFieldTag, DynamicTableColumns, KeccakTable, PoseidonTable},
+    util::{Challenges, Expr, SubCircuitConfig},
+};
+use eth_types::Field;
+use gadgets::is_zero::IsZeroChip;
+use halo2_proofs::{
+    circuit::{Layouter, Region, Value},
+    plonk::{Advice, Column, ConstraintSystem, Error, VirtualCells},
+    poly::Rotation,
+};
+use keccak256::EMPTY_HASH_LE;
+use log::trace;
+use std::vec;
+
+use super::super::bytecode_unroller::{BytecodeRow, UnrolledBytecode};
+use super::{BytecodeCircuitConfig, BytecodeCircuitConfigArgs};
+
+/// specify byte in field for encoding bytecode
+pub const HASHBLOCK_BYTES_IN_FIELD: usize = 16;
+
+#[derive(Clone, Debug)]
+/// Bytecode circuit (for hash block) configuration
+/// basically the BytecodeCircuit include two parts:
+/// a) marking and proving bytcodetable for bytecodes
+/// b) mapping the bytes to keccaktable
+/// and we re-useing the a) part and put additional
+/// controlling cols to enable lookup from poseidon table
+pub struct ToHashBlockCircuitConfig<F, const BYTES_IN_FIELD: usize> {
+    base_conf: BytecodeCircuitConfig<F>,
+    control_length: Column<Advice>,
+    field_input: Column<Advice>,
+    bytes_in_field_index: Column<Advice>,
+    bytes_in_field_inv: Column<Advice>,
+    is_field_border: Column<Advice>,
+    padding_shift: Column<Advice>,
+    field_index: Column<Advice>,
+    field_index_inv: Column<Advice>,
+    // External table
+    pub(crate) poseidon_table: PoseidonTable,
+    pub(crate) keccak_table: KeccakTable,
+}
+
+impl<F: Field, const BYTES_IN_FIELD: usize> ToHashBlockCircuitConfig<F, BYTES_IN_FIELD> {
+    pub(crate) fn configure(
+        meta: &mut ConstraintSystem<F>,
+        base_conf: BytecodeCircuitConfig<F>,
+        poseidon_table: PoseidonTable,
+    ) -> Self {
+        let base_conf_cl = base_conf.clone();
+        let bytecode_table = base_conf.bytecode_table;
+        //TODO: does this col still used for storing poseidon hash?
+        let code_hash = bytecode_table.code_hash;
+
+        let q_enable = base_conf.q_enable; //from 0 to last avaliable row
+
+        let control_length = meta.advice_column();
+        let field_input = meta.advice_column();
+        let bytes_in_field_index = meta.advice_column();
+        let bytes_in_field_inv = meta.advice_column();
+        let is_field_border = meta.advice_column();
+        let padding_shift = meta.advice_column();
+        let field_index = meta.advice_column();
+        let field_index_inv = meta.advice_column();
+
+        // some composited selectors are grepped from base
+        // Does the current row have bytecode field tag == Byte?
+        let is_row_tag_byte =
+            |meta: &mut VirtualCells<F>| meta.query_advice(bytecode_table.tag, Rotation::cur());
+
+        // Does the current row have bytecode field tag == Length (Now header)?
+        let is_row_tag_length = |meta: &mut VirtualCells<F>| {
+            not::expr(meta.query_advice(bytecode_table.tag, Rotation::cur()))
+        };
+
+        // Does the current row is final of a bytecode
+        let is_byte_to_header = |meta: &mut VirtualCells<F>| {
+            and::expr(vec![
+                meta.query_advice(bytecode_table.tag, Rotation::cur()),
+                not::expr(meta.query_advice(bytecode_table.tag, Rotation::next())),
+            ])
+        };
+
+        meta.create_gate("always", |meta| {
+            let mut cb = BaseConstraintBuilder::default();
+
+            cb.require_boolean(
+                "is_field_border",
+                meta.query_advice(is_field_border, Rotation::cur()),
+            );
+
+            // Conditions:
+            // - always
+            cb.gate(meta.query_fixed(q_enable, Rotation::cur()))
+        });
+
+        // current byte_in_field index is not the last one: i.e BYTES_IN_FIELD
+        let q_byte_in_field_not_last = |meta: &mut VirtualCells<F>| {
+            (BYTES_IN_FIELD.expr() - meta.query_advice(bytes_in_field_index, Rotation::cur()))
+                * meta.query_advice(bytes_in_field_inv, Rotation::cur())
+        };
+
+        // current field index is not the last one of the input: i.e
+        // PoseidonTable::INPUT_WIDTH
+        let q_field_not_last = |meta: &mut VirtualCells<F>| {
+            (PoseidonTable::INPUT_WIDTH.expr() - meta.query_advice(field_index, Rotation::cur()))
+                * meta.query_advice(field_index_inv, Rotation::cur())
+        };
+
+        meta.create_gate("field byte cycling", |meta| {
+            let mut cb = BaseConstraintBuilder::default();
+            cb.condition(BYTES_IN_FIELD.expr() - meta.query_advice(bytes_in_field_index, Rotation::cur()), |cb|{
+                cb.require_equal("q_byte_in_field_not_last = 1 except for BYTES_IN_FIELD",
+                    1.expr(),
+                    q_byte_in_field_not_last(meta),
+                )
+            });
+
+            cb.require_equal("is_field_border := !q_byte_in_field_not_last or is_byte_to_header",
+                meta.query_advice(is_field_border, Rotation::cur()),
+                or::expr(vec![
+                    not::expr(q_byte_in_field_not_last(meta)),
+                    is_byte_to_header(meta),
+                ]),
+            );
+
+            cb.require_equal(
+                "byte_in_field_index := 1 if is_field_border_prev else (byte_in_field_index_prev + 1)",
+                meta.query_advice(bytes_in_field_index, Rotation::cur()),
+                select::expr(
+                    meta.query_advice(is_field_border, Rotation::prev()),
+                    1.expr(),
+                    meta.query_advice(bytes_in_field_index, Rotation::prev()) + 1.expr(),
+                )
+            );
+
+            let shifted_byte = meta.query_advice(bytecode_table.value, Rotation::cur()) *
+                meta.query_advice(padding_shift, Rotation::cur());
+
+            cb.require_equal(
+                "field_input = byte * padding_shift if is_field_border_prev else field_input_prev + byte * padding_shift",
+                meta.query_advice(field_input, Rotation::cur()),
+                select::expr(
+                    meta.query_advice(is_field_border, Rotation::prev()),
+                    shifted_byte.clone(),
+                    meta.query_advice(field_input, Rotation::prev()) + shifted_byte
+                ),
+            );
+
+            cb.condition(not::expr(meta.query_advice(is_field_border, Rotation::prev())), |cb|{
+
+                cb.require_equal(
+                    "if field_continue (not is_field_border_prev) padding_shift := padding_shift_prev / 256",
+                    meta.query_advice(padding_shift, Rotation::cur()) * 256.expr(),
+                    meta.query_advice(padding_shift, Rotation::prev()),
+                );
+            });
+
+            cb.condition(not::expr(q_byte_in_field_not_last(meta)), |cb|{
+
+                cb.require_equal(
+                    "if !q_byte_in_field_not_last padding_shift := 1",
+                    meta.query_advice(padding_shift, Rotation::cur()),
+                    1.expr(),
+                );
+            });
+
+            // Conditions:
+            // - Byte tag
+            cb.gate(and::expr(vec![
+                meta.query_fixed(q_enable, Rotation::cur()),
+                is_row_tag_byte(meta),
+            ]))
+        });
+
+        meta.create_gate("field input cycling", |meta| {
+            let mut cb = BaseConstraintBuilder::default();
+
+            cb.condition(PoseidonTable::INPUT_WIDTH.expr() - meta.query_advice(field_index, Rotation::cur()), |cb|{
+                cb.require_equal("q_field_not_last = 1 except for PoseidonTable::INPUT_WIDTH",
+                    1.expr(),
+                    q_field_not_last(meta),
+                )
+            });
+
+            let q_input_continue =
+                (PoseidonTable::INPUT_WIDTH.expr() - meta.query_advice(field_index, Rotation::prev()))
+                * meta.query_advice(field_index_inv, Rotation::prev());
+
+            let q_input_border_last = and::expr([
+                meta.query_advice(is_field_border, Rotation::prev()),
+                not::expr(q_input_continue),
+            ]);
+
+            cb.require_equal(
+                "control_length := base.length - bytecode_table.index if q_input_border_last else control_length_prev",
+                meta.query_advice(control_length, Rotation::cur()),
+                select::expr(
+                    q_input_border_last.clone(),
+                    meta.query_advice(base_conf.length, Rotation::cur()) -
+                    meta.query_advice(bytecode_table.index, Rotation::cur()),
+                    meta.query_advice(control_length, Rotation::prev())
+                ),
+            );
+
+            cb.condition(q_input_border_last.clone(), |cb|{
+                cb.require_equal(
+                    "field_index = 1 on q_input_border_last",
+                    1.expr(),
+                    meta.query_advice(field_index, Rotation::cur())
+                )
+            });
+
+            cb.condition(not::expr(q_input_border_last), |cb|{
+                cb.require_equal(
+                    "field_index := if is_field_border_last then field_index_prev + 1 else field_index_prev",
+                    meta.query_advice(field_index, Rotation::cur()),
+                    select::expr(
+                        meta.query_advice(is_field_border, Rotation::prev()),
+                        meta.query_advice(field_index, Rotation::prev()) + 1.expr(),
+                        meta.query_advice(field_index, Rotation::prev()),
+                    ),
+                )
+            });
+
+            // Conditions:
+            // - Byte tag
+            cb.gate(and::expr(vec![
+                meta.query_fixed(q_enable, Rotation::cur()),
+                is_row_tag_byte(meta),
+            ]))
+        });
+
+        meta.create_gate("start of bytecode", |meta| {
+            let mut cb = BaseConstraintBuilder::default();
+
+            cb.require_zero(
+                "enforce not is_field_border",
+                meta.query_advice(is_field_border, Rotation::cur()),
+            );
+
+            // enforce the next bytes_in_field_index is 1
+            cb.require_zero(
+                "enforce bytes_in_field_index is 0",
+                meta.query_advice(bytes_in_field_index, Rotation::cur()),
+            );
+
+            // enforce the next field_index is 1
+            cb.require_equal(
+                "enforce field_index is 1",
+                1.expr(),
+                meta.query_advice(field_index, Rotation::cur()),
+            );
+
+            // the next field_index is code_length (the starting of ctrl_length)
+            cb.require_equal(
+                "control_length := base.length - bytecode_table.index",
+                meta.query_advice(control_length, Rotation::cur()),
+                meta.query_advice(base_conf.length, Rotation::cur())
+                    - meta.query_advice(bytecode_table.index, Rotation::cur()),
+            );
+
+            // Conditions:
+            // - Length (Now Header) tag
+            cb.gate(and::expr(vec![
+                meta.query_fixed(q_enable, Rotation::cur()),
+                is_row_tag_length(meta),
+            ]))
+        });
+
+        /* not need
+        meta.create_gate("padding", |meta| {
+            let mut cb = BaseConstraintBuilder::default();
+
+            cb.require_zero(
+                "enforce not is_field_border",
+                meta.query_advice(is_field_border, Rotation::cur()),
+            );
+            // Conditions:
+            // - Padding
+            cb.gate(and::expr(vec![
+                meta.query_fixed(q_enable, Rotation::cur()),
+                meta.query_advice(base_conf.padding, Rotation::cur()),
+            ]))
+        });
+         */
+
+        let lookup_columns = [code_hash, field_input, control_length];
+        let pick_hash_tbl_cols = |inp_i| {
+            let cols = poseidon_table.columns();
+            [cols[0], cols[inp_i + 1], cols[cols.len() - 2]]
+        };
+
+        // we use a special selection exp for only 2 indexs
+        let field_selector = |meta: &mut VirtualCells<F>| {
+            let field_index = meta.query_advice(field_index, Rotation::cur()) - 1.expr();
+            [1.expr() - field_index.clone(), field_index]
+        };
+
+        // poseidon lookup:
+        //  * PoseidonTable::INPUT_WIDTH lookups for each input field
+        //  * PoseidonTable::INPUT_WIDTH -1 lookups for the padded zero input
+        //  so we have 2*PoseidonTable::INPUT_WIDTH -1 lookups
+        for i in 0..PoseidonTable::INPUT_WIDTH {
+            meta.lookup_any("poseidon input", |meta| {
+                // Conditions:
+                // - On the row at **field border** (`is_field_border == 1`)
+                // - the field_index match current i
+                let enable = and::expr(vec![
+                    meta.query_advice(is_field_border, Rotation::cur()),
+                    field_selector(meta)[i].clone(),
+                ]);
+                let mut constraints = Vec::new(); /*vec![(
+                                                      enable.clone(),
+                                                      meta.query_advice(keccak_table.is_enabled, Rotation::cur()),
+                                                  )];*/
+                for (l_col, tbl_col) in lookup_columns.into_iter().zip(pick_hash_tbl_cols(i)) {
+                    constraints.push((
+                        enable.clone() * meta.query_advice(l_col, Rotation::cur()),
+                        meta.query_advice(tbl_col, Rotation::cur()),
+                    ))
+                }
+                constraints
+            });
+        }
+
+        //the canonical form should be `for i in 1..PoseidonTable::INPUT_WIDTH{...}`
+        meta.lookup_any("poseidon input padding zero for final", |meta| {
+            // Conditions:
+            // - On the row with the last byte (`is_byte_to_header == 1`)
+            // - Not padding
+            // - the (0 begin) field_index is 1 (for we have only 2 input field)
+            let enable = and::expr(vec![
+                is_byte_to_header(meta),
+                2.expr() - meta.query_advice(field_index, Rotation::cur()),
+            ]);
+            let mut constraints = Vec::new();
+            for (l_exp, tbl_col) in [
+                meta.query_advice(code_hash, Rotation::cur()),
+                0.expr(),
+                meta.query_advice(control_length, Rotation::cur()),
+            ]
+            .into_iter()
+            .zip(pick_hash_tbl_cols(1))
+            {
+                constraints.push((
+                    enable.clone() * l_exp,
+                    meta.query_advice(tbl_col, Rotation::cur()),
+                ))
+            }
+            constraints
+        });
+
+        // re-export keccak table in extended config
+        let keccak_table = base_conf.keccak_table.clone();
+        Self {
+            base_conf: base_conf_cl,
+            control_length,
+            field_input,
+            bytes_in_field_index,
+            bytes_in_field_inv,
+            is_field_border,
+            padding_shift,
+            field_index,
+            field_index_inv,
+            poseidon_table,
+            keccak_table,
+        }
+    }
+
+    pub(crate) fn assign(
+        &self,
+        layouter: &mut impl Layouter<F>,
+        size: usize,
+        witness: &[UnrolledBytecode<F>],
+        challenges: &Challenges<Value<F>>,
+    ) -> Result<(), Error> {
+        self.assign_internal(layouter, size, witness, challenges, true)
+    }
+
+    pub(crate) fn assign_internal(
+        &self,
+        layouter: &mut impl Layouter<F>,
+        size: usize,
+        witness: &[UnrolledBytecode<F>],
+        challenges: &Challenges<Value<F>>,
+        fail_fast: bool,
+    ) -> Result<(), Error> {
+        let base_conf = &self.base_conf;
+        let push_data_left_is_zero_chip =
+            IsZeroChip::construct(base_conf.push_data_left_is_zero.clone());
+
+        // Subtract the unusable rows from the size
+        assert!(size > base_conf.minimum_rows);
+        let last_row_offset = size - base_conf.minimum_rows + 1;
+
+        trace!(
+            "size: {}, minimum_rows: {}, last_row_offset:{}",
+            size,
+            base_conf.minimum_rows,
+            last_row_offset
+        );
+
+        let empty_hash = challenges
+            .evm_word()
+            .map(|challenge| rlc::value(EMPTY_HASH_LE.as_ref(), challenge));
+
+        layouter.assign_region(
+            || "assign bytecode with poseidon hash extension",
+            |mut region| {
+                let mut offset = 0;
+                let mut row_input = F::zero();
+                for bytecode in witness.iter() {
+                    let bytecode_offset_begin = offset;
+                    base_conf.assign_bytecode(
+                        &mut region,
+                        bytecode,
+                        challenges,
+                        &push_data_left_is_zero_chip,
+                        empty_hash,
+                        &mut offset,
+                        last_row_offset,
+                        fail_fast,
+                    )?;
+
+                    for (idx, row) in bytecode.rows.iter().enumerate() {
+                        // if the base_conf's assignment not fail fast,
+                        // we also avoid the failure of "NotEnoughRowsAvailable"
+                        // in prover creation (so bytecode_incomplete test could pass)
+                        let offset = bytecode_offset_begin + idx;
+                        if offset <= last_row_offset {
+                            row_input = self.assign_extended_row(
+                                &mut region,
+                                offset,
+                                row,
+                                row_input,
+                                bytecode.bytes.len(),
+                            )?;
+                        }
+                    }
+                }
+
+                // Padding
+                for idx in offset..=last_row_offset {
+                    base_conf.set_padding_row(
+                        &mut region,
+                        &push_data_left_is_zero_chip,
+                        empty_hash,
+                        idx,
+                        last_row_offset,
+                    )?;
+                    self.set_header_row(&mut region, 0, idx)?;
+                }
+                Ok(())
+            },
+        )
+    }
+
+    /// Assign a header row (at padding or start line of each bytecodes)
+    fn set_header_row(
+        &self,
+        region: &mut Region<F>,
+        code_length: usize,
+        offset: usize,
+    ) -> Result<(), Error> {
+        for (name, column) in [
+            ("control length header", self.control_length),
+            ("field input header", self.field_input),
+            ("bytes in field header", self.bytes_in_field_index),
+            ("bytes in field inv header", self.bytes_in_field_inv),
+            ("field border header", self.is_field_border),
+            ("padding shift header", self.padding_shift),
+            ("field index header", self.field_index),
+            ("field index inv header", self.field_index_inv),
+        ] {
+            region.assign_advice(
+                || format!("assign {name} {offset}"),
+                column,
+                offset,
+                || Value::known(F::zero()),
+            )?;
+        }
+
+        for (name, column, val) in [
+            (
+                "control length header",
+                self.control_length,
+                F::from(code_length as u64),
+            ),
+            (
+                "padding shift header",
+                self.padding_shift,
+                F::from(256 as u64).pow_vartime([BYTES_IN_FIELD as u64]),
+            ),
+            ("field index header", self.field_index, F::one()),
+        ] {
+            region.assign_advice(
+                || format!("assign {name} {offset}"),
+                column,
+                offset,
+                || Value::known(val),
+            )?;
+        }
+
+        Ok(())
+    }
+
+    /// Assign a row, all of the value is determinded by current bytes progress
+    /// and the hash width
+    fn assign_extended_row(
+        &self,
+        region: &mut Region<F>,
+        offset: usize,
+        row: &BytecodeRow<F>,
+        input_prev: F,
+        code_length: usize,
+    ) -> Result<F, Error> {
+        let code_index = row.index.get_lower_128() as usize;
+        let tag = row.tag.get_lower_32();
+        let row_input = match tag {
+            i if i == BytecodeFieldTag::Byte as u32 => {
+                let block_size = BYTES_IN_FIELD * PoseidonTable::INPUT_WIDTH;
+
+                let prog_block = code_index / block_size;
+                let control_length = code_length - prog_block * block_size;
+                let bytes_in_field_index = (code_index + 1) % BYTES_IN_FIELD;
+                let field_border = bytes_in_field_index == 0;
+                let bytes_in_field_index = if field_border {
+                    BYTES_IN_FIELD
+                } else {
+                    bytes_in_field_index
+                };
+                let bytes_in_field_index_inv_f =
+                    F::from((BYTES_IN_FIELD - bytes_in_field_index) as u64)
+                        .invert()
+                        .unwrap_or(F::zero());
+                let padding_shift_f = F::from(256 as u64)
+                    .pow_vartime([(BYTES_IN_FIELD - bytes_in_field_index) as u64]);
+                let input_f = row.value * padding_shift_f + input_prev;
+                // relax field_border for code end
+                let field_border = field_border || code_index + 1 == code_length;
+
+                let field_index = (code_index % block_size) / BYTES_IN_FIELD + 1;
+                let field_index_inv_f = F::from((PoseidonTable::INPUT_WIDTH - field_index) as u64)
+                    .invert()
+                    .unwrap_or(F::zero());
+
+                trace!(
+                    "bytecode_extend.set_row({}): cl:{} inp:{:?} bif:{} br:{} pd:{:x} fi:{}",
+                    offset,
+                    control_length,
+                    input_f,
+                    bytes_in_field_index,
+                    field_border,
+                    padding_shift_f.get_lower_128(),
+                    field_index,
+                );
+
+                for (tip, column, val) in [
+                    (
+                        "control length",
+                        self.control_length,
+                        F::from(control_length as u64),
+                    ),
+                    ("field input", self.field_input, input_f),
+                    (
+                        "bytes in field",
+                        self.bytes_in_field_index,
+                        F::from(bytes_in_field_index as u64),
+                    ),
+                    (
+                        "bytes in field inv",
+                        self.bytes_in_field_inv,
+                        bytes_in_field_index_inv_f,
+                    ),
+                    (
+                        "field border",
+                        self.is_field_border,
+                        F::from(field_border as u64),
+                    ),
+                    ("padding shift", self.padding_shift, padding_shift_f),
+                    ("field index", self.field_index, F::from(field_index as u64)),
+                    ("field index inv", self.field_index_inv, field_index_inv_f),
+                ] {
+                    region.assign_advice(
+                        || format!("assign {} {}", tip, offset),
+                        column,
+                        offset,
+                        || Value::known(val),
+                    )?;
+                }
+
+                if field_border {
+                    F::zero()
+                } else {
+                    input_f
+                }
+            }
+            i if i == BytecodeFieldTag::Header as u32 => {
+                trace!("bytecode_extend.set_header_row({offset}): cl:{code_length}",);
+                self.set_header_row(region, code_length, offset)?;
+                F::zero()
+            }
+            _ => unreachable!("unexpected tag number"),
+        };
+
+        Ok(row_input)
+    }
+
+    /// re-export load fixed tables
+    pub(crate) fn load_aux_tables(&self, layouter: &mut impl Layouter<F>) -> Result<(), Error> {
+        self.base_conf.load_aux_tables(layouter)
+    }
+}
+
+/// Circuit configuration arguments
+pub struct ToHashBlockBytecodeCircuitConfigArgs<F: Field> {
+    /// arg for base config
+    pub base_args: BytecodeCircuitConfigArgs<F>,
+    /// BytecodeTable
+    pub poseidon_table: PoseidonTable,
+}
+
+impl<F: Field> SubCircuitConfig<F> for ToHashBlockCircuitConfig<F, HASHBLOCK_BYTES_IN_FIELD> {
+    type ConfigArgs = ToHashBlockBytecodeCircuitConfigArgs<F>;
+
+    /// Return a new BytecodeCircuitConfig
+    fn new(
+        meta: &mut ConstraintSystem<F>,
+        Self::ConfigArgs {
+            base_args,
+            poseidon_table,
+        }: Self::ConfigArgs,
+    ) -> Self {
+        let base_conf = BytecodeCircuitConfig::new(meta, base_args);
+        Self::configure(meta, base_conf, poseidon_table)
+    }
+}
+
+/// Get unrolled hash inputs as inputs to hash circuit
+pub fn unroll_to_hash_input<F: Field, const BYTES_IN_FIELD: usize, const INPUT_LEN: usize>(
+    code: impl ExactSizeIterator<Item = u8>,
+) -> Vec<[F; INPUT_LEN]> {
+    use eth_types::U256;
+
+    let fl_cnt = code.len() / BYTES_IN_FIELD;
+    let fl_cnt = if code.len() % BYTES_IN_FIELD != 0 {
+        fl_cnt + 1
+    } else {
+        fl_cnt
+    };
+
+    let (msgs, _) = code
+        .chain(std::iter::repeat(0))
+        .take(fl_cnt * BYTES_IN_FIELD)
+        .fold((Vec::new(), Vec::new()), |(mut msgs, mut cache), bt| {
+            cache.push(bt);
+            if cache.len() == BYTES_IN_FIELD {
+                let mut buf: [u8; 64] = [0; 64];
+                U256::from_big_endian(&cache).to_little_endian(&mut buf[0..32]);
+                msgs.push(F::from_bytes_wide(&buf));
+                cache.clear();
+            }
+            (msgs, cache)
+        });
+
+    let input_cnt = msgs.len() / INPUT_LEN;
+    let input_cnt = if msgs.len() % INPUT_LEN != 0 {
+        input_cnt + 1
+    } else {
+        input_cnt
+    };
+    if input_cnt == 0 {
+        return Vec::new();
+    }
+
+    let (mut inputs, last) = msgs
+        .into_iter()
+        .chain(std::iter::repeat(F::zero()))
+        .take(input_cnt * INPUT_LEN)
+        .fold(
+            (Vec::new(), [None; INPUT_LEN]),
+            |(mut msgs, mut v_arr), f| {
+                if let Some(v) = v_arr.iter_mut().find(|v| v.is_none()) {
+                    v.replace(f);
+                    (msgs, v_arr)
+                } else {
+                    msgs.push(v_arr.map(|v| v.unwrap()));
+                    let mut v_arr = [None; INPUT_LEN];
+                    v_arr[0].replace(f);
+                    (msgs, v_arr)
+                }
+            },
+        );
+
+    inputs.push(last.map(|v| v.unwrap()));
+    inputs
+}
+
+/// test module
+#[cfg(any(feature = "test", test))]
+#[cfg(test)]
+pub mod tests {
+    use super::*;
+    //use super::super::tests::get_randomness;
+    //use crate::{bytecode_circuit::dev::test_bytecode_circuit_unrolled,
+    // util::DEFAULT_RAND}; use eth_types::Bytecode;
+    use halo2_proofs::halo2curves::bn256::Fr;
+
+    #[test]
+    fn bytecode_unrolling_to_input() {
+        let bt = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
+
+        let out = unroll_to_hash_input::<Fr, 4, 2>(bt.iter().copied().take(6));
+        assert_eq!(out.len(), 1);
+        assert_eq!(out[0][0], Fr::from(0x01020304));
+        assert_eq!(out[0][1], Fr::from(0x05060000));
+
+        let out = unroll_to_hash_input::<Fr, 3, 2>(bt.iter().copied().take(9));
+        assert_eq!(out.len(), 2);
+        assert_eq!(out[0][0], Fr::from(0x010203));
+        assert_eq!(out[0][1], Fr::from(0x040506));
+        assert_eq!(out[1][0], Fr::from(0x070809));
+        assert_eq!(out[1][1], Fr::zero());
+
+        let out = unroll_to_hash_input::<Fr, 3, 2>(bt.iter().copied().take(12));
+        assert_eq!(out.len(), 2);
+        assert_eq!(out[0][0], Fr::from(0x010203));
+        assert_eq!(out[0][1], Fr::from(0x040506));
+        assert_eq!(out[1][0], Fr::from(0x070809));
+        assert_eq!(out[1][1], Fr::from(0x0A0B0C));
+
+        let out = unroll_to_hash_input::<Fr, 3, 3>(bt.iter().copied().take(12));
+        assert_eq!(out.len(), 2);
+        assert_eq!(out[0][0], Fr::from(0x010203));
+        assert_eq!(out[0][1], Fr::from(0x040506));
+        assert_eq!(out[0][2], Fr::from(0x070809));
+        assert_eq!(out[1][0], Fr::from(0x0A0B0C));
+        assert_eq!(out[1][1], Fr::zero());
+        assert_eq!(out[1][2], Fr::zero());
+
+        let out = unroll_to_hash_input::<Fr, 3, 3>(bt.iter().copied().take(14));
+        assert_eq!(out.len(), 2);
+        assert_eq!(out[0][0], Fr::from(0x010203));
+        assert_eq!(out[0][1], Fr::from(0x040506));
+        assert_eq!(out[0][2], Fr::from(0x070809));
+        assert_eq!(out[1][0], Fr::from(0x0A0B0C));
+        assert_eq!(out[1][1], Fr::from(0x0D0E00));
+        assert_eq!(out[1][2], Fr::zero());
+    }
+}
diff --git a/zkevm-circuits/src/bytecode_circuit/dev.rs b/zkevm-circuits/src/bytecode_circuit/dev.rs
index a8ac0b55a..e936fad20 100644
--- a/zkevm-circuits/src/bytecode_circuit/dev.rs
+++ b/zkevm-circuits/src/bytecode_circuit/dev.rs
@@ -1,5 +1,13 @@
 use super::bytecode_unroller::{unroll, UnrolledBytecode};
-use super::circuit::{BytecodeCircuit, BytecodeCircuitConfig, BytecodeCircuitConfigArgs};
+#[cfg(feature = "poseidon-codehash")]
+use super::circuit::to_poseidon_hash::{
+    ToHashBlockBytecodeCircuitConfigArgs, ToHashBlockCircuitConfig, HASHBLOCK_BYTES_IN_FIELD,
+};
+#[cfg(not(feature = "poseidon-codehash"))]
+use super::circuit::BytecodeCircuitConfig;
+use super::circuit::{BytecodeCircuit, BytecodeCircuitConfigArgs};
+#[cfg(feature = "poseidon-codehash")]
+use crate::table::PoseidonTable;
 use crate::table::{BytecodeTable, KeccakTable};
 use crate::util::{Challenges, SubCircuit, SubCircuitConfig};
 use eth_types::Field;
@@ -10,8 +18,15 @@ use halo2_proofs::{
 use halo2_proofs::{circuit::SimpleFloorPlanner, dev::MockProver, plonk::Circuit};
 use log::error;
 
+#[cfg(feature = "poseidon-codehash")]
+///alias for circuit config
+pub type CircuitConfig<F> = ToHashBlockCircuitConfig<F, HASHBLOCK_BYTES_IN_FIELD>;
+#[cfg(not(feature = "poseidon-codehash"))]
+///alias for circuit config
+pub type CircuitConfig<F> = BytecodeCircuitConfig<F>;
+
 impl<F: Field> Circuit<F> for BytecodeCircuit<F> {
-    type Config = (BytecodeCircuitConfig<F>, Challenges);
+    type Config = (CircuitConfig<F>, Challenges);
     type FloorPlanner = SimpleFloorPlanner;
 
     fn without_witnesses(&self) -> Self {
@@ -22,17 +37,23 @@ impl<F: Field> Circuit<F> for BytecodeCircuit<F> {
         let bytecode_table = BytecodeTable::construct(meta);
         let keccak_table = KeccakTable::construct(meta);
         let challenges = Challenges::construct(meta);
+        #[cfg(feature = "poseidon-codehash")]
+        let poseidon_table = PoseidonTable::construct(meta);
 
         let config = {
             let challenges = challenges.exprs(meta);
-            BytecodeCircuitConfig::new(
-                meta,
-                BytecodeCircuitConfigArgs {
-                    bytecode_table,
-                    keccak_table,
-                    challenges,
-                },
-            )
+            let args = BytecodeCircuitConfigArgs {
+                bytecode_table,
+                keccak_table,
+                challenges,
+            };
+            #[cfg(feature = "poseidon-codehash")]
+            let args = ToHashBlockBytecodeCircuitConfigArgs {
+                base_args: args,
+                poseidon_table,
+            };
+
+            CircuitConfig::new(meta, args)
         };
 
         (config, challenges)
@@ -50,6 +71,13 @@ impl<F: Field> Circuit<F> for BytecodeCircuit<F> {
             self.bytecodes.iter().map(|b| &b.bytes),
             &challenges,
         )?;
+        #[cfg(feature = "poseidon-codehash")]
+        config.poseidon_table.dev_load(
+            &mut layouter,
+            self.bytecodes.iter().map(|b| &b.bytes),
+            &challenges,
+        )?;
+
         self.synthesize_sub(&config, &challenges, &mut layouter)?;
         Ok(())
     }
diff --git a/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs b/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs
index fd11cb874..6336c0fa3 100644
--- a/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs
+++ b/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs
@@ -21,7 +21,7 @@ use crate::{
     table::{AccountFieldTag, CallContextFieldTag, TxFieldTag as TxContextFieldTag},
     util::Expr,
 };
-use eth_types::{evm_types::GasCost, Field, ToLittleEndian, ToScalar};
+use eth_types::{Field, ToLittleEndian, ToScalar};
 use ethers_core::utils::{get_contract_address, keccak256};
 use gadgets::util::{expr_from_bytes, or, select};
 use halo2_proofs::plonk::Error;
@@ -170,8 +170,8 @@ impl<F: Field> ExecutionGadget<F> for BeginTxGadget<F> {
             intrinsic_gas_cost.expr(),
             select::expr(
                 tx_is_create.expr(),
-                GasCost::CREATION_TX.expr(),
-                GasCost::TX.expr(),
+                eth_types::evm_types::GasCost::CREATION_TX.expr(),
+                eth_types::evm_types::GasCost::TX.expr(),
             ) + tx_call_data_gas_cost.expr(),
         );
         let gas_left = tx_gas.expr() - intrinsic_gas_cost.expr();
diff --git a/zkevm-circuits/src/lib.rs b/zkevm-circuits/src/lib.rs
index 542c7918c..0fa9313aa 100644
--- a/zkevm-circuits/src/lib.rs
+++ b/zkevm-circuits/src/lib.rs
@@ -25,6 +25,7 @@ pub mod exp_circuit;
 pub mod keccak_circuit;
 pub mod mpt_circuit;
 pub mod pi_circuit;
+pub mod poseidon_circuit;
 pub mod rlp_circuit;
 pub mod state_circuit;
 pub mod super_circuit;
diff --git a/zkevm-circuits/src/mpt_circuit.rs b/zkevm-circuits/src/mpt_circuit.rs
index 353abac06..106746249 100644
--- a/zkevm-circuits/src/mpt_circuit.rs
+++ b/zkevm-circuits/src/mpt_circuit.rs
@@ -65,7 +65,11 @@ impl<F: Field + Hashable> SubCircuit<F> for MptCircuit<F> {
                 .map(|tr| AccountOp::try_from(tr).unwrap()),
         );
         let (circuit, _) = eth_trie.to_circuits(
-            (block.circuits_params.max_rws / 3, None),
+            (
+                // notice we do not use the accompanied hash circuit so just assign any size
+                100usize,
+                Some(block.evm_circuit_pad_to),
+            ),
             &block.mpt_updates.proof_types,
         );
         MptCircuit(circuit)
@@ -123,7 +127,7 @@ impl<F: Field + Hashable> Circuit<F> for MptCircuit<F> {
     }
 
     fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
-        let poseidon_table = PoseidonTable::construct(meta);
+        let poseidon_table = PoseidonTable::dev_construct(meta);
         let mpt_table = MptTable::construct(meta);
         let challenges = Challenges::construct(meta);
 
@@ -149,7 +153,7 @@ impl<F: Field + Hashable> Circuit<F> for MptCircuit<F> {
         mut layouter: impl Layouter<F>,
     ) -> Result<(), Error> {
         let challenges = challenges.values(&mut layouter);
-        config.0.load_hash_table(
+        config.0.dev_load_hash_table(
             &mut layouter,
             self.0.ops.iter().flat_map(|op| op.hash_traces()),
             self.0.calcs,
diff --git a/zkevm-circuits/src/poseidon_circuit.rs b/zkevm-circuits/src/poseidon_circuit.rs
new file mode 100644
index 000000000..3efaf5a6e
--- /dev/null
+++ b/zkevm-circuits/src/poseidon_circuit.rs
@@ -0,0 +1,173 @@
+//! wrapping of mpt-circuit
+use crate::{
+    bytecode_circuit::bytecode_unroller::{self, HASHBLOCK_BYTES_IN_FIELD},
+    table::PoseidonTable,
+    util::{Challenges, SubCircuit, SubCircuitConfig},
+    witness,
+};
+use eth_types::Field;
+use halo2_proofs::{
+    circuit::{Layouter, SimpleFloorPlanner, Value},
+    plonk::{Circuit, ConstraintSystem, Error},
+};
+use mpt_zktrie::hash::{Hashable, PoseidonHashChip, PoseidonHashConfig, PoseidonHashTable};
+
+/// re-wrapping for mpt circuit
+#[derive(Default, Clone, Debug)]
+pub struct PoseidonCircuit<F: Field>(pub(crate) PoseidonHashTable<F>, usize);
+
+/// Circuit configuration argumen ts
+pub struct PoseidonCircuitConfigArgs {
+    /// PoseidonTable
+    pub poseidon_table: PoseidonTable,
+}
+
+/// re-wrapping for poseidon config
+#[derive(Debug, Clone)]
+pub struct PoseidonCircuitConfig<F: Field>(pub(crate) PoseidonHashConfig<F>);
+
+const HASH_BLOCK_STEP_SIZE: usize = HASHBLOCK_BYTES_IN_FIELD * PoseidonTable::INPUT_WIDTH;
+
+impl<F: Field> SubCircuitConfig<F> for PoseidonCircuitConfig<F> {
+    type ConfigArgs = PoseidonCircuitConfigArgs;
+
+    fn new(
+        meta: &mut ConstraintSystem<F>,
+        Self::ConfigArgs { poseidon_table }: Self::ConfigArgs,
+    ) -> Self {
+        let conf = PoseidonHashConfig::configure_sub(meta, poseidon_table.0, HASH_BLOCK_STEP_SIZE);
+        Self(conf)
+    }
+}
+
+#[cfg(any(feature = "test", test))]
+impl<F: Field> SubCircuit<F> for PoseidonCircuit<F> {
+    type Config = PoseidonCircuitConfig<F>;
+
+    fn new_from_block(block: &witness::Block<F>) -> Self {
+        let max_hashes = block.evm_circuit_pad_to / F::hash_block_size();
+        #[allow(unused_mut)]
+        let mut poseidon_table_data = PoseidonHashTable::default();
+        // without any feature we just synthesis an empty poseidon circuit
+        #[cfg(feature = "zktrie")]
+        {
+            use mpt_zktrie::{operation::AccountOp, EthTrie};
+            let mut eth_trie: EthTrie<F> = Default::default();
+            eth_trie.add_ops(
+                block
+                    .mpt_updates
+                    .smt_traces
+                    .iter()
+                    .map(|tr| AccountOp::try_from(tr).unwrap()),
+            );
+            poseidon_table_data.constant_inputs_with_check(eth_trie.hash_traces());
+        }
+        #[cfg(feature = "poseidon-codehash")]
+        {
+            use bytecode_unroller::unroll_to_hash_input_default;
+            for bytecode in block.bytecodes.values() {
+                // must skip empty bytecode
+                if !bytecode.bytes.is_empty() {
+                    poseidon_table_data.stream_inputs(
+                        &unroll_to_hash_input_default::<F>(bytecode.bytes.iter().copied()),
+                        bytecode.bytes.len() as u64,
+                        HASH_BLOCK_STEP_SIZE,
+                    );
+                }
+            }
+        }
+
+        Self(poseidon_table_data, max_hashes)
+    }
+
+    fn min_num_rows_block(block: &witness::Block<F>) -> (usize, usize) {
+        let acc = 0;
+        #[cfg(feature = "zktrie")]
+        let acc = {
+            let mut cnt = acc;
+            use mpt_zktrie::{operation::AccountOp, EthTrie};
+            let mut eth_trie: EthTrie<F> = Default::default();
+            eth_trie.add_ops(
+                block
+                    .mpt_updates
+                    .smt_traces
+                    .iter()
+                    .map(|tr| AccountOp::try_from(tr).unwrap()),
+            );
+            cnt += eth_trie.hash_traces().count();
+            cnt
+        };
+        #[cfg(feature = "poseidon-codehash")]
+        let acc = {
+            let mut cnt = acc;
+            use bytecode_unroller::unroll_to_hash_input_default;
+            for bytecode in block.bytecodes.values() {
+                cnt += unroll_to_hash_input_default::<F>(bytecode.bytes.iter().copied()).len();
+            }
+            cnt
+        };
+        let acc = acc * F::hash_block_size();
+        (acc, block.evm_circuit_pad_to.max(acc))
+    }
+
+    /// Make the assignments to the MptCircuit, notice it fill mpt table
+    /// but not fill hash table
+    fn synthesize_sub(
+        &self,
+        config: &Self::Config,
+        challenges: &Challenges<Value<F>>,
+        layouter: &mut impl Layouter<F>,
+    ) -> Result<(), Error> {
+        // for single codehash we sitll use keccak256(nil)
+        use crate::evm_circuit::util::rlc;
+        use keccak256::EMPTY_HASH_LE;
+        let empty_hash = challenges
+            .evm_word()
+            .map(|challenge| rlc::value(EMPTY_HASH_LE.as_ref(), challenge));
+
+        let chip =
+            PoseidonHashChip::<_, { bytecode_unroller::HASHBLOCK_BYTES_IN_FIELD }>::construct(
+                config.0.clone(),
+                &self.0,
+                self.1,
+                false,
+                empty_hash.inner,
+            );
+
+        chip.load(layouter)
+    }
+
+    /// powers of randomness for instance columns
+    fn instance(&self) -> Vec<Vec<F>> {
+        vec![]
+    }
+}
+
+#[cfg(any(feature = "test", test))]
+impl<F: Field + Hashable> Circuit<F> for PoseidonCircuit<F> {
+    type Config = (PoseidonCircuitConfig<F>, Challenges);
+    type FloorPlanner = SimpleFloorPlanner;
+
+    fn without_witnesses(&self) -> Self {
+        Self(Default::default(), self.1)
+    }
+
+    fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
+        let poseidon_table = PoseidonTable::construct(meta);
+        let challenges = Challenges::construct(meta);
+
+        let config =
+            { PoseidonCircuitConfig::new(meta, PoseidonCircuitConfigArgs { poseidon_table }) };
+
+        (config, challenges)
+    }
+
+    fn synthesize(
+        &self,
+        (config, challenges): Self::Config,
+        mut layouter: impl Layouter<F>,
+    ) -> Result<(), Error> {
+        let challenges = challenges.values(&mut layouter);
+        self.synthesize_sub(&config, &challenges, &mut layouter)
+    }
+}
diff --git a/zkevm-circuits/src/super_circuit.rs b/zkevm-circuits/src/super_circuit.rs
index 9b4c694b3..516566eed 100644
--- a/zkevm-circuits/src/super_circuit.rs
+++ b/zkevm-circuits/src/super_circuit.rs
@@ -51,20 +51,23 @@
 //!   - [x] Tx Circuit
 //!   - [ ] MPT Circuit
 
-use crate::bytecode_circuit::circuit::{
-    BytecodeCircuit, BytecodeCircuitConfig, BytecodeCircuitConfigArgs,
+#[cfg(feature = "poseidon-codehash")]
+use crate::bytecode_circuit::circuit::to_poseidon_hash::{
+    ToHashBlockBytecodeCircuitConfigArgs, ToHashBlockCircuitConfig, HASHBLOCK_BYTES_IN_FIELD,
 };
+#[cfg(not(feature = "poseidon-codehash"))]
+use crate::bytecode_circuit::circuit::BytecodeCircuitConfig;
+use crate::bytecode_circuit::circuit::{BytecodeCircuit, BytecodeCircuitConfigArgs};
 use crate::copy_circuit::{CopyCircuit, CopyCircuitConfig, CopyCircuitConfigArgs};
 use crate::evm_circuit::{EvmCircuit, EvmCircuitConfig, EvmCircuitConfigArgs};
 use crate::exp_circuit::{ExpCircuit, ExpCircuitConfig};
 use crate::keccak_circuit::keccak_packed_multi::{
     KeccakCircuit, KeccakCircuitConfig, KeccakCircuitConfigArgs,
 };
+use crate::poseidon_circuit::{PoseidonCircuit, PoseidonCircuitConfig, PoseidonCircuitConfigArgs};
 
 #[cfg(feature = "zktrie")]
 use crate::mpt_circuit::{MptCircuit, MptCircuitConfig, MptCircuitConfigArgs};
-#[cfg(feature = "zktrie")]
-use crate::table::PoseidonTable;
 
 #[cfg(not(feature = "onephase"))]
 use crate::util::Challenges;
@@ -73,8 +76,8 @@ use crate::util::MockChallenges as Challenges;
 
 use crate::state_circuit::{StateCircuit, StateCircuitConfig, StateCircuitConfigArgs};
 use crate::table::{
-    BlockTable, BytecodeTable, CopyTable, ExpTable, KeccakTable, MptTable, RlpTable, RwTable,
-    TxTable,
+    BlockTable, BytecodeTable, CopyTable, ExpTable, KeccakTable, MptTable, PoseidonTable, RlpTable,
+    RwTable, TxTable,
 };
 
 use crate::util::{circuit_stats, log2_ceil, SubCircuit, SubCircuitConfig};
@@ -101,12 +104,17 @@ pub struct SuperCircuitConfig<F: Field> {
     mpt_table: MptTable,
     rlp_table: RlpTable,
     tx_table: TxTable,
+    poseidon_table: PoseidonTable,
     evm_circuit: EvmCircuitConfig<F>,
     state_circuit: StateCircuitConfig<F>,
     tx_circuit: TxCircuitConfig<F>,
+    #[cfg(not(feature = "poseidon-codehash"))]
     bytecode_circuit: BytecodeCircuitConfig<F>,
+    #[cfg(feature = "poseidon-codehash")]
+    bytecode_circuit: ToHashBlockCircuitConfig<F, HASHBLOCK_BYTES_IN_FIELD>,
     copy_circuit: CopyCircuitConfig<F>,
     keccak_circuit: KeccakCircuitConfig<F>,
+    poseidon_circuit: PoseidonCircuitConfig<F>,
     pi_circuit: PiCircuitConfig<F>,
     exp_circuit: ExpCircuitConfig<F>,
     rlp_circuit: RlpCircuitConfig<F>,
@@ -153,10 +161,7 @@ impl<F: Field> SubCircuitConfig<F> for SuperCircuitConfig<F> {
 
         let mpt_table = MptTable::construct(meta);
         log_circuit_info(meta, "mpt table");
-
-        #[cfg(feature = "zktrie")]
         let poseidon_table = PoseidonTable::construct(meta);
-        #[cfg(feature = "zktrie")]
         log_circuit_info(meta, "poseidon table");
 
         let bytecode_table = BytecodeTable::construct(meta);
@@ -183,6 +188,9 @@ impl<F: Field> SubCircuitConfig<F> for SuperCircuitConfig<F> {
         );
         log_circuit_info(meta, "keccak circuit");
 
+        let poseidon_circuit =
+            PoseidonCircuitConfig::new(meta, PoseidonCircuitConfigArgs { poseidon_table });
+
         let rlp_circuit = RlpCircuitConfig::configure(meta, &rlp_table, &challenges);
         log_circuit_info(meta, "rlp circuit");
 
@@ -211,6 +219,7 @@ impl<F: Field> SubCircuitConfig<F> for SuperCircuitConfig<F> {
         );
         log_circuit_info(meta, "tx circuit");
 
+        #[cfg(not(feature = "poseidon-codehash"))]
         let bytecode_circuit = BytecodeCircuitConfig::new(
             meta,
             BytecodeCircuitConfigArgs {
@@ -219,6 +228,19 @@ impl<F: Field> SubCircuitConfig<F> for SuperCircuitConfig<F> {
                 challenges: challenges.clone(),
             },
         );
+        #[cfg(feature = "poseidon-codehash")]
+        let bytecode_circuit = ToHashBlockCircuitConfig::new(
+            meta,
+            ToHashBlockBytecodeCircuitConfigArgs {
+                base_args: BytecodeCircuitConfigArgs {
+                    bytecode_table: bytecode_table.clone(),
+                    keccak_table: keccak_table.clone(),
+                    challenges: challenges.clone(),
+                },
+                poseidon_table,
+            },
+        );
+
         log_circuit_info(meta, "bytecode circuit");
 
         let copy_circuit = CopyCircuitConfig::new(
@@ -282,11 +304,13 @@ impl<F: Field> SubCircuitConfig<F> for SuperCircuitConfig<F> {
             mpt_table,
             tx_table,
             rlp_table,
+            poseidon_table,
             evm_circuit,
             state_circuit,
             copy_circuit,
             bytecode_circuit,
             keccak_circuit,
+            poseidon_circuit,
             pi_circuit,
             rlp_circuit,
             tx_circuit,
@@ -322,6 +346,8 @@ pub struct SuperCircuit<
     pub exp_circuit: ExpCircuit<F>,
     /// Keccak Circuit
     pub keccak_circuit: KeccakCircuit<F>,
+    /// Poseidon hash Circuit
+    pub poseidon_circuit: PoseidonCircuit<F>,
     /// Rlp Circuit
     pub rlp_circuit: RlpCircuit<F, SignedTransaction>,
     /// Mpt Circuit
@@ -405,6 +431,7 @@ impl<
         let copy_circuit = CopyCircuit::new_from_block_no_external(block);
         let exp_circuit = ExpCircuit::new_from_block(block);
         let keccak_circuit = KeccakCircuit::new_from_block(block);
+        let poseidon_circuit = PoseidonCircuit::new_from_block(block);
         let rlp_circuit = RlpCircuit::new_from_block(block);
         #[cfg(feature = "zktrie")]
         let mpt_circuit = MptCircuit::new_from_block(block);
@@ -417,6 +444,7 @@ impl<
             copy_circuit,
             exp_circuit,
             keccak_circuit,
+            poseidon_circuit,
             rlp_circuit,
             #[cfg(feature = "zktrie")]
             mpt_circuit,
@@ -456,6 +484,8 @@ impl<
     ) -> Result<(), Error> {
         self.keccak_circuit
             .synthesize_sub(&config.keccak_circuit, challenges, layouter)?;
+        self.poseidon_circuit
+            .synthesize_sub(&config.poseidon_circuit, challenges, layouter)?;
         self.bytecode_circuit
             .synthesize_sub(&config.bytecode_circuit, challenges, layouter)?;
         self.tx_circuit
@@ -480,20 +510,9 @@ impl<
             .synthesize_sub(&config.rlp_circuit, challenges, layouter)?;
         // load both poseidon table and zktrie table
         #[cfg(feature = "zktrie")]
-        {
-            // TODO: wrap this as `poseidon_table.load`
-            config.mpt_circuit.0.load_hash_table(
-                layouter,
-                self.mpt_circuit
-                    .0
-                    .ops
-                    .iter()
-                    .flat_map(|op| op.hash_traces()),
-                self.mpt_circuit.0.calcs,
-            )?;
-            self.mpt_circuit
-                .synthesize_sub(&config.mpt_circuit, challenges, layouter)?;
-        }
+        self.mpt_circuit
+            .synthesize_sub(&config.mpt_circuit, challenges, layouter)?;
+
         Ok(())
     }
 }
diff --git a/zkevm-circuits/src/table.rs b/zkevm-circuits/src/table.rs
index ff1027691..894d5a7a2 100644
--- a/zkevm-circuits/src/table.rs
+++ b/zkevm-circuits/src/table.rs
@@ -22,6 +22,7 @@ use halo2_proofs::{
     plonk::{Advice, Column, ConstraintSystem, Error},
 };
 use halo2_proofs::{circuit::Layouter, poly::Rotation};
+use std::iter::repeat;
 
 #[cfg(feature = "onephase")]
 use halo2_proofs::plonk::FirstPhase as SecondPhase;
@@ -612,8 +613,10 @@ impl MptTable {
 
 /// The Poseidon hash table shared between Hash Circuit, Mpt Circuit and
 /// Bytecode Circuit
+/// the 5 cols represent [index(final hash of inputs), input0, input1, control,
+/// heading mark]
 #[derive(Clone, Copy, Debug)]
-pub struct PoseidonTable(pub [Column<Advice>; 4]);
+pub struct PoseidonTable(pub [Column<Advice>; 5]);
 
 impl DynamicTableColumns for PoseidonTable {
     fn columns(&self) -> Vec<Column<Advice>> {
@@ -622,9 +625,26 @@ impl DynamicTableColumns for PoseidonTable {
 }
 
 impl PoseidonTable {
+    /// the permutation width of current poseidon table
+    pub(crate) const WIDTH: usize = 3;
+
+    /// the input width of current poseidon table
+    pub(crate) const INPUT_WIDTH: usize = Self::WIDTH - 1;
+
     /// Construct a new PoseidonTable
     pub(crate) fn construct<F: FieldExt>(meta: &mut ConstraintSystem<F>) -> Self {
-        Self([0; 4].map(|_| meta.advice_column()))
+        Self([
+            meta.advice_column_in(SecondPhase),
+            meta.advice_column(),
+            meta.advice_column(),
+            meta.advice_column(),
+            meta.advice_column(),
+        ])
+    }
+
+    /// Construct a new PoseidonTable for dev (no secondphase, mpt only)
+    pub(crate) fn dev_construct<F: FieldExt>(meta: &mut ConstraintSystem<F>) -> Self {
+        Self([0; 5].map(|_| meta.advice_column()))
     }
 
     pub(crate) fn assign<F: Field>(
@@ -661,6 +681,97 @@ impl PoseidonTable {
         }
         Ok(())
     }
+
+    /// Provide this function for the case that we want to consume a poseidon
+    /// table but without running the full poseidon circuit
+    pub fn dev_load<'a, F: Field>(
+        &self,
+        layouter: &mut impl Layouter<F>,
+        inputs: impl IntoIterator<Item = &'a Vec<u8>> + Clone,
+        challenges: &Challenges<Value<F>>,
+    ) -> Result<(), Error> {
+        use crate::bytecode_circuit::bytecode_unroller::{
+            unroll_to_hash_input_default, HASHBLOCK_BYTES_IN_FIELD,
+        };
+
+        layouter.assign_region(
+            || "poseidon table",
+            |mut region| {
+                let mut offset = 0;
+                let poseidon_table_columns = self.columns();
+                for column in poseidon_table_columns.iter().copied() {
+                    region.assign_advice(
+                        || "poseidon table all-zero row",
+                        column,
+                        offset,
+                        || Value::known(F::zero()),
+                    )?;
+                }
+                offset += 1;
+                let nil_hash = KeccakTable::assignments(&[], challenges)[0][3];
+                for (column, value) in poseidon_table_columns
+                    .iter()
+                    .copied()
+                    .zip(once(nil_hash).chain(repeat(Value::known(F::zero()))))
+                {
+                    region.assign_advice(
+                        || "poseidon table nil input row",
+                        column,
+                        offset,
+                        || value,
+                    )?;
+                }
+                offset += 1;
+
+                for input in inputs.clone() {
+                    let mut control_len = input.len();
+                    let mut first_row = true;
+                    let ref_hash = KeccakTable::assignments(input, challenges)[0][3];
+                    for row in unroll_to_hash_input_default::<F>(input.iter().copied()) {
+                        assert_ne!(
+                            control_len,
+                            0,
+                            "must have enough len left (original size {})",
+                            input.len()
+                        );
+                        let block_size = HASHBLOCK_BYTES_IN_FIELD * row.len();
+
+                        for (column, value) in poseidon_table_columns.iter().zip_eq(
+                            once(ref_hash)
+                                .chain(row.map(Value::known))
+                                .chain(once(Value::known(F::from(control_len as u64))))
+                                .chain(once(Value::known(if first_row {
+                                    F::one()
+                                } else {
+                                    F::zero()
+                                }))),
+                        ) {
+                            region.assign_advice(
+                                || format!("poseidon table row {}", offset),
+                                *column,
+                                offset,
+                                || value,
+                            )?;
+                        }
+                        first_row = false;
+                        offset += 1;
+                        control_len = if control_len > block_size {
+                            control_len - block_size
+                        } else {
+                            0
+                        };
+                    }
+                    assert_eq!(
+                        control_len,
+                        0,
+                        "should have exhaust all bytes (original size {})",
+                        input.len()
+                    );
+                }
+                Ok(())
+            },
+        )
+    }
 }
 
 /// Tag to identify the field in a Bytecode Table row
diff --git a/zktrie/Cargo.toml b/zktrie/Cargo.toml
index 1d49f41f5..c6547f724 100644
--- a/zktrie/Cargo.toml
+++ b/zktrie/Cargo.toml
@@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0"
 
 [dependencies]
 halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2.git", tag = "v2022_09_10" }
-mpt-circuits = { package = "halo2-mpt-circuits", git = "https://github.com/scroll-tech/mpt-circuit.git", branch = "scroll-dev-0111-halo2-upgrade" }
+mpt-circuits = { package = "halo2-mpt-circuits", git = "https://github.com/scroll-tech/mpt-circuit.git", branch = "scroll-dev-0129" }
 zktrie = { git = "https://github.com/scroll-tech/zktrie.git", branch = "dev-1102" }
 bus-mapping = { path = "../bus-mapping" }
 eth-types = { path = "../eth-types" }
diff --git a/zktrie/src/lib.rs b/zktrie/src/lib.rs
index 69d601e4b..e3895c922 100644
--- a/zktrie/src/lib.rs
+++ b/zktrie/src/lib.rs
@@ -2,19 +2,15 @@
 //
 #![deny(missing_docs)]
 
+pub use mpt_circuits::hash;
 pub use mpt_circuits::operation;
 pub use mpt_circuits::serde;
+pub use mpt_circuits::CommitmentIndexs;
 pub use mpt_circuits::EthTrie;
 pub use mpt_circuits::EthTrieCircuit;
 pub use mpt_circuits::EthTrieConfig;
 pub use mpt_circuits::MPTProofType;
 
-/// the hash scheme (poseidon) used by mpt-zktrie
-pub mod hash {
-    pub use mpt_circuits::hash::Hashable;
-    pub use mpt_circuits::HashCircuit;
-}
-
 //pub use mpt_circuits::hash;
 //use mpt_circuits::{hash::Hashable, operation::AccountOp, EthTrie,
 // EthTrieCircuit, HashCircuit, MPTProofType};
diff --git a/zktrie/src/state/builder.rs b/zktrie/src/state/builder.rs
index c6afe5881..655bfe80c 100644
--- a/zktrie/src/state/builder.rs
+++ b/zktrie/src/state/builder.rs
@@ -220,127 +220,3 @@ pub(crate) fn verify_proof_leaf<T: Default>(inp: TrieProof<T>, key_buf: &[u8; 32
         inp
     }
 }
-
-/*
-pub fn build_statedb_and_codedb(blocks: &[BlockTrace]) -> Result<(StateDB, CodeDB), anyhow::Error> {
-    let mut sdb = StateDB::new();
-    let mut cdb =
-        CodeDB::new_with_code_hasher(Box::new(PoseidonCodeHash::new(POSEIDONHASH_BYTES_IN_FIELD)));
-
-    // step1: insert proof into statedb
-    for block in blocks.iter().rev() {
-        let storage_trace = &block.storage_trace;
-        if let Some(acc_proofs) = &storage_trace.proofs {
-            for (addr, acc) in acc_proofs.iter() {
-                let acc_proof: mpt::AccountProof = acc.as_slice().try_into()?;
-                let acc = verify_proof_leaf(acc_proof, &mpt::extend_address_to_h256(addr));
-                if acc.key.is_some() {
-                    // a valid leaf
-                    let (_, acc_mut) = sdb.get_account_mut(addr);
-                    acc_mut.nonce = acc.data.nonce.into();
-                    acc_mut.code_hash = acc.data.code_hash;
-                    acc_mut.balance = acc.data.balance;
-                } else {
-                    // it is essential to set it as default (i.e. not existed account data)
-                    sdb.set_account(
-                        addr,
-                        Account {
-                            nonce: Default::default(),
-                            balance: Default::default(),
-                            storage: HashMap::new(),
-                            code_hash: Default::default(),
-                        },
-                    );
-                }
-            }
-        }
-
-        for (addr, s_map) in storage_trace.storage_proofs.iter() {
-            let (found, acc) = sdb.get_account_mut(addr);
-            if !found {
-                log::error!("missed address in proof field show in storage: {:?}", addr);
-                continue;
-            }
-
-            for (k, val) in s_map {
-                let mut k_buf: [u8; 32] = [0; 32];
-                k.to_big_endian(&mut k_buf[..]);
-                let val_proof: mpt::StorageProof = val.as_slice().try_into()?;
-                let val = verify_proof_leaf(val_proof, &k_buf);
-
-                if val.key.is_some() {
-                    // a valid leaf
-                    acc.storage.insert(*k, *val.data.as_ref());
-                //                log::info!("set storage {:?} {:?} {:?}", addr, k, val.data);
-                } else {
-                    // add 0
-                    acc.storage.insert(*k, Default::default());
-                    //                log::info!("set empty storage {:?} {:?}", addr, k);
-                }
-            }
-        }
-
-        // step2: insert code into codedb
-        // notice empty codehash always kept as keccak256(nil)
-        cdb.insert(Vec::new());
-
-        for execution_result in &block.execution_results {
-            if let Some(bytecode) = &execution_result.byte_code {
-                if execution_result.account_created.is_none() {
-                    cdb.0.insert(
-                        execution_result
-                            .code_hash
-                            .ok_or_else(|| anyhow!("empty code hash in result"))?,
-                        decode_bytecode(bytecode)?.to_vec(),
-                    );
-                }
-            }
-
-            for step in execution_result.exec_steps.iter().rev() {
-                if let Some(data) = &step.extra_data {
-                    match step.op {
-                        OpcodeId::CALL
-                        | OpcodeId::CALLCODE
-                        | OpcodeId::DELEGATECALL
-                        | OpcodeId::STATICCALL => {
-                            let callee_code = data.get_code_at(1);
-                            trace_code(&mut cdb, step, &sdb, callee_code, 1);
-                        }
-                        OpcodeId::CREATE | OpcodeId::CREATE2 => {
-                            // notice we do not need to insert code for CREATE,
-                            // bustmapping do this job
-                        }
-                        OpcodeId::EXTCODESIZE | OpcodeId::EXTCODECOPY => {
-                            let code = data.get_code_at(0);
-                            trace_code(&mut cdb, step, &sdb, code, 0);
-                        }
-
-                        _ => {}
-                    }
-                }
-            }
-        }
-    }
-
-    // A temporary fix: zkgeth do not trace 0 address if it is only refered as coinbase
-    // (For it is not the "real" coinbase address in PoA) but would still refer it for
-    // other reasons (like being transferred or called), in the other way, busmapping
-    // seems always refer it as coinbase (?)
-    // here we just add it as unexisted account and consider fix it in zkgeth later (always
-    // record 0 addr inside storageTrace field)
-    let (zero_coinbase_exist, _) = sdb.get_account(&Default::default());
-    if !zero_coinbase_exist {
-        sdb.set_account(
-            &Default::default(),
-            Account {
-                nonce: Default::default(),
-                balance: Default::default(),
-                storage: HashMap::new(),
-                code_hash: Default::default(),
-            },
-        );
-    }
-
-    Ok((sdb, cdb))
-}
- */