Skip to content

Commit 570e45a

Browse files
committed
feat(tee-proof-verifier): add backward compatibility logic
1 parent bdb213c commit 570e45a

File tree

4 files changed

+253
-34
lines changed

4 files changed

+253
-34
lines changed

bin/verify-era-proof-attestation/src/verification.rs

Lines changed: 54 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,66 @@
22
// Copyright (c) 2023-2024 Matter Labs
33

44
use crate::{args::AttestationPolicyArgs, client::JsonRpcClient};
5-
use anyhow::{Context, Result};
5+
use anyhow::{anyhow, Context, Result};
66
use hex::encode;
7-
use secp256k1::Message;
7+
use secp256k1::{ecdsa::Signature, Message};
88
use teepot::{
99
client::TcbLevel,
1010
ethereum::recover_signer,
11+
prover::reportdata::ReportData,
1112
quote::{
1213
error::QuoteContext, tee_qv_get_collateral, verify_quote_with_collateral,
1314
QuoteVerificationResult, Report,
1415
},
1516
};
1617
use tracing::{debug, info, warn};
17-
use zksync_basic_types::L1BatchNumber;
18+
use zksync_basic_types::{L1BatchNumber, H256};
19+
20+
struct TeeProof {
21+
report: ReportData,
22+
root_hash: H256,
23+
signature: Vec<u8>,
24+
}
25+
26+
impl TeeProof {
27+
pub fn new(report: ReportData, root_hash: H256, signature: Vec<u8>) -> Self {
28+
Self {
29+
report,
30+
root_hash,
31+
signature,
32+
}
33+
}
34+
35+
pub fn verify(&self) -> Result<bool> {
36+
match &self.report {
37+
ReportData::V0(report) => {
38+
let signature = Signature::from_compact(&self.signature)?;
39+
let root_hash_msg = Message::from_digest_slice(&self.root_hash.0)?;
40+
Ok(signature.verify(&root_hash_msg, &report.pubkey).is_ok())
41+
}
42+
ReportData::V1(report) => {
43+
let ethereum_address_from_report = report.ethereum_addr;
44+
let root_hash_bytes = self.root_hash.as_bytes();
45+
let root_hash_msg = Message::from_digest_slice(root_hash_bytes)?;
46+
let signature_bytes: [u8; 65] = self
47+
.signature
48+
.clone()
49+
.try_into()
50+
.map_err(|e| anyhow!("{:?}", e))?;
51+
let ethereum_address_from_signature =
52+
recover_signer(&signature_bytes, &root_hash_msg)?;
53+
debug!(
54+
"Root hash: {}. Ethereum address from the attestation quote: {}. Ethereum address from the signature: {}.",
55+
self.root_hash,
56+
encode(ethereum_address_from_report),
57+
encode(ethereum_address_from_signature),
58+
);
59+
Ok(ethereum_address_from_signature == ethereum_address_from_report)
60+
}
61+
ReportData::Unknown(_) => Ok(false),
62+
}
63+
}
64+
}
1865

1966
pub async fn verify_batch_proof(
2067
quote_verification_result: &QuoteVerificationResult,
@@ -27,38 +74,11 @@ pub async fn verify_batch_proof(
2774
return Ok(false);
2875
}
2976

30-
let batch_no = batch_number.0;
3177
let root_hash = node_client.get_root_hash(batch_number).await?;
32-
let ethereum_address_from_quote = &quote_verification_result.quote.get_report_data()[..20];
33-
let signature_bytes: &[u8; 65] = signature.try_into()?;
34-
let root_hash_bytes = root_hash.as_bytes();
35-
let root_hash_msg = Message::from_digest_slice(root_hash_bytes)?;
36-
let ethereum_address_from_signature = recover_signer(signature_bytes, &root_hash_msg)?;
37-
let verification_successful = ethereum_address_from_signature == ethereum_address_from_quote;
38-
debug!(
39-
batch_no,
40-
"Root hash: {}. Ethereum address from the attestation quote: {}. Ethereum address from the signature: {}.",
41-
root_hash,
42-
encode(ethereum_address_from_quote),
43-
encode(ethereum_address_from_signature),
44-
);
45-
46-
if verification_successful {
47-
info!(
48-
batch_no,
49-
signature = encode(signature),
50-
ethereum_address = encode(ethereum_address_from_quote),
51-
"Signature verified successfully."
52-
);
53-
} else {
54-
warn!(
55-
batch_no,
56-
signature = encode(signature),
57-
ethereum_address_from_signature = encode(ethereum_address_from_signature),
58-
ethereum_address_from_quote = encode(ethereum_address_from_quote),
59-
"Failed to verify signature!"
60-
);
61-
}
78+
let report_data_bytes = quote_verification_result.quote.get_report_data();
79+
let report_data = ReportData::try_from(report_data_bytes)?;
80+
let tee_proof = TeeProof::new(report_data, root_hash, signature.to_vec());
81+
let verification_successful = tee_proof.verify().is_ok();
6282
Ok(verification_successful)
6383
}
6484

crates/teepot/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ pub mod client;
1010
pub mod ethereum;
1111
pub mod json;
1212
pub mod log;
13+
pub mod prover;
1314
pub mod quote;
1415
pub mod server;
1516
pub mod sgx;

crates/teepot/src/prover/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// Copyright (c) 2023 Matter Labs
3+
4+
//! Common functionality for TEE provers and verifiers.
5+
6+
pub mod reportdata;
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// Copyright (c) 2024 Matter Labs
3+
4+
//! Versioning of Intel SGX/TDX quote's report data for TEE prover and verifier.
5+
6+
use core::convert::Into;
7+
8+
use anyhow::{anyhow, Result};
9+
use secp256k1::{constants::PUBLIC_KEY_SIZE, PublicKey};
10+
11+
/// Report data length for Intel SGX/TDX.
12+
const REPORT_DATA_LENGTH: usize = 64;
13+
14+
/// Ethereum address length.
15+
const ETHEREUM_ADDR_LENGTH: usize = 20;
16+
17+
/// Report data version.
18+
#[derive(Debug, Clone, PartialEq, Eq)]
19+
pub enum ReportData {
20+
/// Legacy version of report data that was initially not intended to be versioned.
21+
/// The report_data was on-chain incompatible and consisted of a compressed ECDSA public key.
22+
///
23+
/// +-------------------------------------+--------------------------------+------------------+
24+
/// | compressed ECDSA pubkey (33 bytes) | zeroes (30 bytes) | version (1 byte) |
25+
/// +-------------------------------------+--------------------------------+------------------+
26+
V0(ReportDataV0),
27+
/// Latest version of report data compatible with on-chain verification.
28+
///
29+
/// +--------------------------+-------------------------------------------+------------------+
30+
/// | Ethereum addr (20 bytes) | zeros (43 bytes) | version (1 byte) |
31+
/// +--------------------------+-------------------------------------------+------------------+
32+
V1(ReportDataV1),
33+
/// Unknown version of report data.
34+
Unknown(Vec<u8>),
35+
}
36+
37+
impl TryFrom<&[u8]> for ReportData {
38+
type Error = anyhow::Error;
39+
40+
fn try_from(report_data_bytes: &[u8]) -> Result<Self> {
41+
if report_data_bytes.len() != REPORT_DATA_LENGTH {
42+
return Err(anyhow!("Invalid byte slice length"));
43+
}
44+
let version = report_data_bytes[REPORT_DATA_LENGTH - 1];
45+
match version {
46+
0 => Ok(Self::V0(ReportDataV0::try_from(report_data_bytes)?)),
47+
1 => Ok(Self::V1(ReportDataV1::try_from(report_data_bytes)?)),
48+
_ => Ok(Self::Unknown(report_data_bytes.into())),
49+
}
50+
}
51+
}
52+
53+
#[derive(Debug, Clone, PartialEq, Eq)]
54+
#[allow(missing_docs)]
55+
pub struct ReportDataV0 {
56+
pub pubkey: PublicKey,
57+
}
58+
59+
impl TryFrom<&[u8]> for ReportDataV0 {
60+
type Error = anyhow::Error;
61+
62+
fn try_from(report_data_bytes: &[u8]) -> Result<Self> {
63+
if report_data_bytes.len() != REPORT_DATA_LENGTH {
64+
return Err(anyhow!("Invalid byte slice length"));
65+
}
66+
let pubkey = PublicKey::from_slice(&report_data_bytes[..PUBLIC_KEY_SIZE])?;
67+
Ok(Self { pubkey })
68+
}
69+
}
70+
71+
impl Into<[u8; REPORT_DATA_LENGTH]> for ReportDataV0 {
72+
fn into(self) -> [u8; REPORT_DATA_LENGTH] {
73+
let mut bytes = [0u8; REPORT_DATA_LENGTH];
74+
bytes[..PUBLIC_KEY_SIZE].copy_from_slice(&self.pubkey.serialize());
75+
bytes
76+
}
77+
}
78+
79+
#[derive(Debug, Clone, PartialEq, Eq)]
80+
#[allow(missing_docs)]
81+
pub struct ReportDataV1 {
82+
pub ethereum_addr: [u8; ETHEREUM_ADDR_LENGTH],
83+
}
84+
85+
impl TryFrom<&[u8]> for ReportDataV1 {
86+
type Error = anyhow::Error;
87+
88+
fn try_from(report_data_bytes: &[u8]) -> Result<Self> {
89+
if report_data_bytes.len() != REPORT_DATA_LENGTH {
90+
return Err(anyhow!("Invalid byte slice length"));
91+
}
92+
let mut ethereum_addr = [0u8; ETHEREUM_ADDR_LENGTH];
93+
ethereum_addr.copy_from_slice(&report_data_bytes[..ETHEREUM_ADDR_LENGTH]);
94+
Ok(Self { ethereum_addr })
95+
}
96+
}
97+
98+
impl Into<[u8; REPORT_DATA_LENGTH]> for ReportDataV1 {
99+
fn into(self) -> [u8; REPORT_DATA_LENGTH] {
100+
let mut bytes = [0u8; REPORT_DATA_LENGTH];
101+
bytes[..ETHEREUM_ADDR_LENGTH].copy_from_slice(&self.ethereum_addr);
102+
bytes[REPORT_DATA_LENGTH - 1] = 1;
103+
bytes
104+
}
105+
}
106+
107+
#[cfg(test)]
108+
mod tests {
109+
use super::*;
110+
use hex;
111+
use secp256k1::{Secp256k1, SecretKey};
112+
113+
const ETHEREUM_ADDR: [u8; ETHEREUM_ADDR_LENGTH] = [
114+
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
115+
0x10, 0x11, 0x12, 0x13, 0x14,
116+
];
117+
118+
fn generate_test_report_data(version_byte: u8) -> [u8; REPORT_DATA_LENGTH] {
119+
let mut data = [0u8; REPORT_DATA_LENGTH];
120+
data[..ETHEREUM_ADDR.len()].copy_from_slice(&ETHEREUM_ADDR);
121+
data[REPORT_DATA_LENGTH - 1] = version_byte;
122+
data
123+
}
124+
125+
fn generate_test_pubkey() -> PublicKey {
126+
let secp = Secp256k1::new();
127+
let secret_key_bytes =
128+
hex::decode("c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3")
129+
.unwrap();
130+
let secret_key = SecretKey::from_slice(&secret_key_bytes).unwrap();
131+
PublicKey::from_secret_key(&secp, &secret_key)
132+
}
133+
134+
fn generate_test_report_data_v0(pubkey: PublicKey) -> [u8; REPORT_DATA_LENGTH] {
135+
let pubkey_bytes = pubkey.serialize();
136+
let mut report_data_bytes = [0u8; REPORT_DATA_LENGTH];
137+
report_data_bytes[..PUBLIC_KEY_SIZE].copy_from_slice(&pubkey_bytes);
138+
report_data_bytes
139+
}
140+
141+
#[test]
142+
fn test_from_bytes_v0() {
143+
let pubkey = generate_test_pubkey();
144+
let report_data_bytes = generate_test_report_data_v0(pubkey);
145+
let report_data = ReportData::try_from(report_data_bytes.as_ref()).unwrap();
146+
assert_eq!(report_data, ReportData::V0(ReportDataV0 { pubkey }));
147+
}
148+
149+
#[test]
150+
fn report_data_from_bytes_v1() {
151+
let data = generate_test_report_data(1);
152+
let report_data = ReportData::try_from(data.as_ref()).unwrap();
153+
assert_eq!(
154+
report_data,
155+
ReportData::V1(ReportDataV1 {
156+
ethereum_addr: ETHEREUM_ADDR
157+
})
158+
);
159+
}
160+
161+
#[test]
162+
fn report_data_from_bytes_unknown() {
163+
let report_data_bytes = generate_test_report_data(99);
164+
let report_data = ReportData::try_from(report_data_bytes.as_ref()).unwrap();
165+
assert_eq!(report_data, ReportData::Unknown(report_data_bytes.into()));
166+
}
167+
168+
#[test]
169+
fn report_data_to_bytes_v0() {
170+
let pubkey = generate_test_pubkey();
171+
let report_data = ReportDataV0 { pubkey };
172+
let report_data: [u8; REPORT_DATA_LENGTH] = report_data.into();
173+
assert_eq!(&report_data[..PUBLIC_KEY_SIZE], pubkey.serialize().as_ref());
174+
assert_eq!(report_data[REPORT_DATA_LENGTH - 1], 0);
175+
assert!(report_data[PUBLIC_KEY_SIZE..REPORT_DATA_LENGTH - 1]
176+
.iter()
177+
.all(|&byte| byte == 0));
178+
}
179+
180+
#[test]
181+
fn report_data_to_bytes_v1() {
182+
let report_data = ReportDataV1 {
183+
ethereum_addr: ETHEREUM_ADDR,
184+
};
185+
let report_data: [u8; REPORT_DATA_LENGTH] = report_data.into();
186+
assert_eq!(&report_data[..ETHEREUM_ADDR_LENGTH], &ETHEREUM_ADDR);
187+
assert_eq!(report_data[REPORT_DATA_LENGTH - 1], 1);
188+
assert!(report_data[ETHEREUM_ADDR_LENGTH..REPORT_DATA_LENGTH - 1]
189+
.iter()
190+
.all(|&byte| byte == 0));
191+
}
192+
}

0 commit comments

Comments
 (0)