diff --git a/bin/verify-era-proof-attestation/src/verification.rs b/bin/verify-era-proof-attestation/src/verification.rs
index 4c99bf3b..6443fd33 100644
--- a/bin/verify-era-proof-attestation/src/verification.rs
+++ b/bin/verify-era-proof-attestation/src/verification.rs
@@ -2,19 +2,66 @@
 // Copyright (c) 2023-2024 Matter Labs
 
 use crate::{args::AttestationPolicyArgs, client::JsonRpcClient};
-use anyhow::{Context, Result};
+use anyhow::{anyhow, Context, Result};
 use hex::encode;
-use secp256k1::Message;
+use secp256k1::{ecdsa::Signature, Message};
 use teepot::{
     client::TcbLevel,
     ethereum::recover_signer,
+    prover::reportdata::ReportData,
     quote::{
         error::QuoteContext, tee_qv_get_collateral, verify_quote_with_collateral,
         QuoteVerificationResult, Report,
     },
 };
 use tracing::{debug, info, warn};
-use zksync_basic_types::L1BatchNumber;
+use zksync_basic_types::{L1BatchNumber, H256};
+
+struct TeeProof {
+    report: ReportData,
+    root_hash: H256,
+    signature: Vec<u8>,
+}
+
+impl TeeProof {
+    pub fn new(report: ReportData, root_hash: H256, signature: Vec<u8>) -> Self {
+        Self {
+            report,
+            root_hash,
+            signature,
+        }
+    }
+
+    pub fn verify(&self) -> Result<bool> {
+        match &self.report {
+            ReportData::V0(report) => {
+                let signature = Signature::from_compact(&self.signature)?;
+                let root_hash_msg = Message::from_digest_slice(&self.root_hash.0)?;
+                Ok(signature.verify(&root_hash_msg, &report.pubkey).is_ok())
+            }
+            ReportData::V1(report) => {
+                let ethereum_address_from_report = report.ethereum_addr;
+                let root_hash_bytes = self.root_hash.as_bytes();
+                let root_hash_msg = Message::from_digest_slice(root_hash_bytes)?;
+                let signature_bytes: [u8; 65] = self
+                    .signature
+                    .clone()
+                    .try_into()
+                    .map_err(|e| anyhow!("{:?}", e))?;
+                let ethereum_address_from_signature =
+                    recover_signer(&signature_bytes, &root_hash_msg)?;
+                debug!(
+                    "Root hash: {}. Ethereum address from the attestation quote: {}. Ethereum address from the signature: {}.",
+                    self.root_hash,
+                    encode(ethereum_address_from_report),
+                    encode(ethereum_address_from_signature),
+                );
+                Ok(ethereum_address_from_signature == ethereum_address_from_report)
+            }
+            ReportData::Unknown(_) => Ok(false),
+        }
+    }
+}
 
 pub async fn verify_batch_proof(
     quote_verification_result: &QuoteVerificationResult,
@@ -27,38 +74,11 @@ pub async fn verify_batch_proof(
         return Ok(false);
     }
 
-    let batch_no = batch_number.0;
     let root_hash = node_client.get_root_hash(batch_number).await?;
-    let ethereum_address_from_quote = &quote_verification_result.quote.get_report_data()[..20];
-    let signature_bytes: &[u8; 65] = signature.try_into()?;
-    let root_hash_bytes = root_hash.as_bytes();
-    let root_hash_msg = Message::from_digest_slice(root_hash_bytes)?;
-    let ethereum_address_from_signature = recover_signer(signature_bytes, &root_hash_msg)?;
-    let verification_successful = ethereum_address_from_signature == ethereum_address_from_quote;
-    debug!(
-        batch_no,
-        "Root hash: {}. Ethereum address from the attestation quote: {}. Ethereum address from the signature: {}.",
-        root_hash,
-        encode(ethereum_address_from_quote),
-        encode(ethereum_address_from_signature),
-    );
-
-    if verification_successful {
-        info!(
-            batch_no,
-            signature = encode(signature),
-            ethereum_address = encode(ethereum_address_from_quote),
-            "Signature verified successfully."
-        );
-    } else {
-        warn!(
-            batch_no,
-            signature = encode(signature),
-            ethereum_address_from_signature = encode(ethereum_address_from_signature),
-            ethereum_address_from_quote = encode(ethereum_address_from_quote),
-            "Failed to verify signature!"
-        );
-    }
+    let report_data_bytes = quote_verification_result.quote.get_report_data();
+    let report_data = ReportData::try_from(report_data_bytes)?;
+    let tee_proof = TeeProof::new(report_data, root_hash, signature.to_vec());
+    let verification_successful = tee_proof.verify().is_ok();
     Ok(verification_successful)
 }
 
diff --git a/crates/teepot/src/lib.rs b/crates/teepot/src/lib.rs
index 5cdf25b8..8f734899 100644
--- a/crates/teepot/src/lib.rs
+++ b/crates/teepot/src/lib.rs
@@ -10,6 +10,7 @@ pub mod client;
 pub mod ethereum;
 pub mod json;
 pub mod log;
+pub mod prover;
 pub mod quote;
 pub mod server;
 pub mod sgx;
diff --git a/crates/teepot/src/prover/mod.rs b/crates/teepot/src/prover/mod.rs
new file mode 100644
index 00000000..d7b3e632
--- /dev/null
+++ b/crates/teepot/src/prover/mod.rs
@@ -0,0 +1,6 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (c) 2023 Matter Labs
+
+//! Common functionality for TEE provers and verifiers.
+
+pub mod reportdata;
diff --git a/crates/teepot/src/prover/reportdata.rs b/crates/teepot/src/prover/reportdata.rs
new file mode 100644
index 00000000..182b8698
--- /dev/null
+++ b/crates/teepot/src/prover/reportdata.rs
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (c) 2024 Matter Labs
+
+//! Versioning of Intel SGX/TDX quote's report data for TEE prover and verifier.
+
+use core::convert::Into;
+
+use anyhow::{anyhow, Result};
+use secp256k1::{constants::PUBLIC_KEY_SIZE, PublicKey};
+
+/// Report data length for Intel SGX/TDX.
+const REPORT_DATA_LENGTH: usize = 64;
+
+/// Ethereum address length.
+const ETHEREUM_ADDR_LENGTH: usize = 20;
+
+/// Report data version.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum ReportData {
+    /// Legacy version of report data that was initially not intended to be versioned.
+    /// The report_data was on-chain incompatible and consisted of a compressed ECDSA public key.
+    ///
+    /// +-------------------------------------+--------------------------------+------------------+
+    /// | compressed ECDSA pubkey (33 bytes)  | zeroes (30 bytes)              | version (1 byte) |
+    /// +-------------------------------------+--------------------------------+------------------+
+    V0(ReportDataV0),
+    /// Latest version of report data compatible with on-chain verification.
+    ///
+    /// +--------------------------+-------------------------------------------+------------------+
+    /// | Ethereum addr (20 bytes) | zeros (43 bytes)                          | version (1 byte) |
+    /// +--------------------------+-------------------------------------------+------------------+
+    V1(ReportDataV1),
+    /// Unknown version of report data.
+    Unknown(Vec<u8>),
+}
+
+impl TryFrom<&[u8]> for ReportData {
+    type Error = anyhow::Error;
+
+    fn try_from(report_data_bytes: &[u8]) -> Result<Self> {
+        if report_data_bytes.len() != REPORT_DATA_LENGTH {
+            return Err(anyhow!("Invalid byte slice length"));
+        }
+        let version = report_data_bytes[REPORT_DATA_LENGTH - 1];
+        match version {
+            0 => Ok(Self::V0(ReportDataV0::try_from(report_data_bytes)?)),
+            1 => Ok(Self::V1(ReportDataV1::try_from(report_data_bytes)?)),
+            _ => Ok(Self::Unknown(report_data_bytes.into())),
+        }
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+#[allow(missing_docs)]
+pub struct ReportDataV0 {
+    pub pubkey: PublicKey,
+}
+
+impl TryFrom<&[u8]> for ReportDataV0 {
+    type Error = anyhow::Error;
+
+    fn try_from(report_data_bytes: &[u8]) -> Result<Self> {
+        if report_data_bytes.len() != REPORT_DATA_LENGTH {
+            return Err(anyhow!("Invalid byte slice length"));
+        }
+        let pubkey = PublicKey::from_slice(&report_data_bytes[..PUBLIC_KEY_SIZE])?;
+        Ok(Self { pubkey })
+    }
+}
+
+impl Into<[u8; REPORT_DATA_LENGTH]> for ReportDataV0 {
+    fn into(self) -> [u8; REPORT_DATA_LENGTH] {
+        let mut bytes = [0u8; REPORT_DATA_LENGTH];
+        bytes[..PUBLIC_KEY_SIZE].copy_from_slice(&self.pubkey.serialize());
+        bytes
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+#[allow(missing_docs)]
+pub struct ReportDataV1 {
+    pub ethereum_addr: [u8; ETHEREUM_ADDR_LENGTH],
+}
+
+impl TryFrom<&[u8]> for ReportDataV1 {
+    type Error = anyhow::Error;
+
+    fn try_from(report_data_bytes: &[u8]) -> Result<Self> {
+        if report_data_bytes.len() != REPORT_DATA_LENGTH {
+            return Err(anyhow!("Invalid byte slice length"));
+        }
+        let mut ethereum_addr = [0u8; ETHEREUM_ADDR_LENGTH];
+        ethereum_addr.copy_from_slice(&report_data_bytes[..ETHEREUM_ADDR_LENGTH]);
+        Ok(Self { ethereum_addr })
+    }
+}
+
+impl Into<[u8; REPORT_DATA_LENGTH]> for ReportDataV1 {
+    fn into(self) -> [u8; REPORT_DATA_LENGTH] {
+        let mut bytes = [0u8; REPORT_DATA_LENGTH];
+        bytes[..ETHEREUM_ADDR_LENGTH].copy_from_slice(&self.ethereum_addr);
+        bytes[REPORT_DATA_LENGTH - 1] = 1;
+        bytes
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use hex;
+    use secp256k1::{Secp256k1, SecretKey};
+
+    const ETHEREUM_ADDR: [u8; ETHEREUM_ADDR_LENGTH] = [
+        0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+        0x10, 0x11, 0x12, 0x13, 0x14,
+    ];
+
+    fn generate_test_report_data(version_byte: u8) -> [u8; REPORT_DATA_LENGTH] {
+        let mut data = [0u8; REPORT_DATA_LENGTH];
+        data[..ETHEREUM_ADDR.len()].copy_from_slice(&ETHEREUM_ADDR);
+        data[REPORT_DATA_LENGTH - 1] = version_byte;
+        data
+    }
+
+    fn generate_test_pubkey() -> PublicKey {
+        let secp = Secp256k1::new();
+        let secret_key_bytes =
+            hex::decode("c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3")
+                .unwrap();
+        let secret_key = SecretKey::from_slice(&secret_key_bytes).unwrap();
+        PublicKey::from_secret_key(&secp, &secret_key)
+    }
+
+    fn generate_test_report_data_v0(pubkey: PublicKey) -> [u8; REPORT_DATA_LENGTH] {
+        let pubkey_bytes = pubkey.serialize();
+        let mut report_data_bytes = [0u8; REPORT_DATA_LENGTH];
+        report_data_bytes[..PUBLIC_KEY_SIZE].copy_from_slice(&pubkey_bytes);
+        report_data_bytes
+    }
+
+    #[test]
+    fn test_from_bytes_v0() {
+        let pubkey = generate_test_pubkey();
+        let report_data_bytes = generate_test_report_data_v0(pubkey);
+        let report_data = ReportData::try_from(report_data_bytes.as_ref()).unwrap();
+        assert_eq!(report_data, ReportData::V0(ReportDataV0 { pubkey }));
+    }
+
+    #[test]
+    fn report_data_from_bytes_v1() {
+        let data = generate_test_report_data(1);
+        let report_data = ReportData::try_from(data.as_ref()).unwrap();
+        assert_eq!(
+            report_data,
+            ReportData::V1(ReportDataV1 {
+                ethereum_addr: ETHEREUM_ADDR
+            })
+        );
+    }
+
+    #[test]
+    fn report_data_from_bytes_unknown() {
+        let report_data_bytes = generate_test_report_data(99);
+        let report_data = ReportData::try_from(report_data_bytes.as_ref()).unwrap();
+        assert_eq!(report_data, ReportData::Unknown(report_data_bytes.into()));
+    }
+
+    #[test]
+    fn report_data_to_bytes_v0() {
+        let pubkey = generate_test_pubkey();
+        let report_data = ReportDataV0 { pubkey };
+        let report_data: [u8; REPORT_DATA_LENGTH] = report_data.into();
+        assert_eq!(&report_data[..PUBLIC_KEY_SIZE], pubkey.serialize().as_ref());
+        assert_eq!(report_data[REPORT_DATA_LENGTH - 1], 0);
+        assert!(report_data[PUBLIC_KEY_SIZE..REPORT_DATA_LENGTH - 1]
+            .iter()
+            .all(|&byte| byte == 0));
+    }
+
+    #[test]
+    fn report_data_to_bytes_v1() {
+        let report_data = ReportDataV1 {
+            ethereum_addr: ETHEREUM_ADDR,
+        };
+        let report_data: [u8; REPORT_DATA_LENGTH] = report_data.into();
+        assert_eq!(&report_data[..ETHEREUM_ADDR_LENGTH], &ETHEREUM_ADDR);
+        assert_eq!(report_data[REPORT_DATA_LENGTH - 1], 1);
+        assert!(report_data[ETHEREUM_ADDR_LENGTH..REPORT_DATA_LENGTH - 1]
+            .iter()
+            .all(|&byte| byte == 0));
+    }
+}