mirror of
https://github.com/matter-labs/teepot.git
synced 2025-09-13 20:24:06 +02:00
refactor(verify-era-proof-attestation): modularize and restructure proof verification logic
- Split `verify-era-proof-attestation` into modular subcomponents for maintainability. - Moved client, proof handling, and core types into dedicated modules.
This commit is contained in:
parent
1e853f653a
commit
2605e2ae3a
34 changed files with 2918 additions and 2304 deletions
|
@ -0,0 +1,35 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023-2025 Matter Labs
|
||||
|
||||
use teepot::quote::{
|
||||
error::QuoteContext, tee_qv_get_collateral, verify_quote_with_collateral,
|
||||
QuoteVerificationResult,
|
||||
};
|
||||
|
||||
use crate::error;
|
||||
|
||||
/// Handles verification of attestation quotes
|
||||
pub struct AttestationVerifier;
|
||||
|
||||
impl AttestationVerifier {
|
||||
/// Verify an attestation quote
|
||||
pub fn verify_quote(attestation_quote_bytes: &[u8]) -> error::Result<QuoteVerificationResult> {
|
||||
// Get collateral for the quote
|
||||
let collateral = QuoteContext::context(
|
||||
tee_qv_get_collateral(attestation_quote_bytes),
|
||||
"Failed to get collateral!",
|
||||
)?;
|
||||
|
||||
// Get current time for verification
|
||||
let unix_time: i64 = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map_err(|e| error::Error::internal(format!("Failed to get system time: {}", e)))?
|
||||
.as_secs() as _;
|
||||
|
||||
// Verify the quote with the collateral
|
||||
let res =
|
||||
verify_quote_with_collateral(attestation_quote_bytes, Some(&collateral), unix_time)?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
141
bin/verify-era-proof-attestation/src/verification/batch.rs
Normal file
141
bin/verify-era-proof-attestation/src/verification/batch.rs
Normal file
|
@ -0,0 +1,141 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2025 Matter Labs
|
||||
|
||||
use crate::{
|
||||
client::JsonRpcClient,
|
||||
core::AttestationPolicy,
|
||||
error,
|
||||
proof::Proof,
|
||||
verification::{AttestationVerifier, PolicyEnforcer, SignatureVerifier, VerificationReporter},
|
||||
};
|
||||
use tokio::sync::watch;
|
||||
use zksync_basic_types::L1BatchNumber;
|
||||
|
||||
/// Result of a batch verification
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BatchVerificationResult {
|
||||
/// Total number of proofs processed
|
||||
pub total_count: u32,
|
||||
/// Number of proofs that were verified successfully
|
||||
pub verified_count: u32,
|
||||
/// Number of proofs that failed verification
|
||||
pub unverified_count: u32,
|
||||
}
|
||||
|
||||
/// Handles the batch verification process
|
||||
pub struct BatchVerifier<C: JsonRpcClient> {
|
||||
node_client: C,
|
||||
attestation_policy: AttestationPolicy,
|
||||
}
|
||||
|
||||
impl<C: JsonRpcClient> BatchVerifier<C> {
|
||||
/// Create a new batch verifier
|
||||
pub fn new(node_client: C, attestation_policy: AttestationPolicy) -> Self {
|
||||
Self {
|
||||
node_client,
|
||||
attestation_policy,
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify proofs for a batch
|
||||
pub async fn verify_batch_proofs(
|
||||
&self,
|
||||
stop_receiver: &mut watch::Receiver<bool>,
|
||||
batch_number: L1BatchNumber,
|
||||
proofs: Vec<Proof>,
|
||||
) -> error::Result<BatchVerificationResult> {
|
||||
let batch_no = batch_number.0;
|
||||
let mut total_proofs_count: u32 = 0;
|
||||
let mut verified_proofs_count: u32 = 0;
|
||||
|
||||
for proof in proofs.into_iter() {
|
||||
if *stop_receiver.borrow() {
|
||||
tracing::warn!("Stop signal received during batch verification");
|
||||
return Ok(BatchVerificationResult {
|
||||
total_count: total_proofs_count,
|
||||
verified_count: verified_proofs_count,
|
||||
unverified_count: total_proofs_count - verified_proofs_count,
|
||||
});
|
||||
}
|
||||
|
||||
total_proofs_count += 1;
|
||||
let tee_type = proof.tee_type.to_uppercase();
|
||||
|
||||
if proof.is_permanently_ignored() {
|
||||
tracing::debug!(
|
||||
batch_no,
|
||||
tee_type,
|
||||
"Proof is marked as permanently ignored. Skipping."
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
tracing::debug!(batch_no, tee_type, proof.proved_at, "Verifying proof.");
|
||||
|
||||
let attestation_bytes = proof.attestation_bytes();
|
||||
let signature_bytes = proof.signature_bytes();
|
||||
|
||||
tracing::debug!(
|
||||
batch_no,
|
||||
"Verifying quote ({} bytes)...",
|
||||
attestation_bytes.len()
|
||||
);
|
||||
|
||||
// Verify attestation
|
||||
let quote_verification_result = AttestationVerifier::verify_quote(&attestation_bytes)?;
|
||||
|
||||
// Log verification results
|
||||
VerificationReporter::log_quote_verification_summary("e_verification_result);
|
||||
|
||||
// Check if attestation matches policy
|
||||
let policy_matches = PolicyEnforcer::validate_policy(
|
||||
&self.attestation_policy,
|
||||
"e_verification_result,
|
||||
);
|
||||
|
||||
if let Err(e) = policy_matches {
|
||||
tracing::error!(batch_no, tee_type, "Attestation policy check failed: {e}");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Verify signature
|
||||
let root_hash = self
|
||||
.node_client
|
||||
.get_root_hash(L1BatchNumber(proof.l1_batch_number))
|
||||
.await?;
|
||||
|
||||
let signature_verified = SignatureVerifier::verify_batch_proof(
|
||||
"e_verification_result,
|
||||
root_hash,
|
||||
&signature_bytes,
|
||||
)?;
|
||||
|
||||
if signature_verified {
|
||||
tracing::info!(
|
||||
batch_no,
|
||||
proof.proved_at,
|
||||
tee_type,
|
||||
"Verification succeeded.",
|
||||
);
|
||||
verified_proofs_count += 1;
|
||||
} else {
|
||||
tracing::warn!(batch_no, proof.proved_at, tee_type, "Verification failed!",);
|
||||
}
|
||||
}
|
||||
|
||||
let unverified_proofs_count = total_proofs_count.saturating_sub(verified_proofs_count);
|
||||
|
||||
// Log batch verification results
|
||||
VerificationReporter::log_batch_verification_results(
|
||||
batch_no,
|
||||
verified_proofs_count,
|
||||
unverified_proofs_count,
|
||||
);
|
||||
|
||||
Ok(BatchVerificationResult {
|
||||
total_count: total_proofs_count,
|
||||
verified_count: verified_proofs_count,
|
||||
unverified_count: unverified_proofs_count,
|
||||
})
|
||||
}
|
||||
}
|
14
bin/verify-era-proof-attestation/src/verification/mod.rs
Normal file
14
bin/verify-era-proof-attestation/src/verification/mod.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023-2025 Matter Labs
|
||||
|
||||
mod attestation;
|
||||
mod batch;
|
||||
mod policy;
|
||||
mod reporting;
|
||||
mod signature;
|
||||
|
||||
pub use attestation::AttestationVerifier;
|
||||
pub use batch::BatchVerifier;
|
||||
pub use policy::PolicyEnforcer;
|
||||
pub use reporting::VerificationReporter;
|
||||
pub use signature::SignatureVerifier;
|
212
bin/verify-era-proof-attestation/src/verification/policy.rs
Normal file
212
bin/verify-era-proof-attestation/src/verification/policy.rs
Normal file
|
@ -0,0 +1,212 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023-2025 Matter Labs
|
||||
|
||||
use crate::{
|
||||
core::AttestationPolicy,
|
||||
error::{Error, Result},
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use enumset::EnumSet;
|
||||
use teepot::quote::{tcblevel::TcbLevel, QuoteVerificationResult, Report};
|
||||
|
||||
/// Enforces policy requirements on attestation quotes
|
||||
pub struct PolicyEnforcer;
|
||||
|
||||
impl PolicyEnforcer {
|
||||
/// Check if a quote matches the attestation policy
|
||||
pub fn validate_policy(
|
||||
attestation_policy: &AttestationPolicy,
|
||||
quote_verification_result: &QuoteVerificationResult,
|
||||
) -> Result<()> {
|
||||
let quote = "e_verification_result.quote;
|
||||
let tcblevel = TcbLevel::from(quote_verification_result.result);
|
||||
|
||||
match "e.report {
|
||||
Report::SgxEnclave(report_body) => {
|
||||
// Validate TCB level
|
||||
Self::validate_tcb_level(&attestation_policy.sgx_allowed_tcb_levels, tcblevel)?;
|
||||
|
||||
// Validate SGX Advisories
|
||||
for advisory in "e_verification_result.advisories {
|
||||
Self::check_policy(
|
||||
attestation_policy.sgx_allowed_advisory_ids.as_deref(),
|
||||
advisory,
|
||||
"advisories",
|
||||
)?;
|
||||
}
|
||||
|
||||
// Validate SGX policies
|
||||
Self::check_policy_hash(
|
||||
attestation_policy.sgx_mrsigners.as_deref(),
|
||||
&report_body.mr_signer,
|
||||
"mrsigner",
|
||||
)?;
|
||||
|
||||
Self::check_policy_hash(
|
||||
attestation_policy.sgx_mrenclaves.as_deref(),
|
||||
&report_body.mr_enclave,
|
||||
"mrenclave",
|
||||
)
|
||||
}
|
||||
Report::TD10(report_body) => {
|
||||
// Validate TCB level
|
||||
Self::validate_tcb_level(&attestation_policy.tdx_allowed_tcb_levels, tcblevel)?;
|
||||
|
||||
// Validate TDX Advisories
|
||||
for advisory in "e_verification_result.advisories {
|
||||
Self::check_policy(
|
||||
attestation_policy.tdx_allowed_advisory_ids.as_deref(),
|
||||
advisory,
|
||||
"mrsigner",
|
||||
)?;
|
||||
}
|
||||
|
||||
// Build combined TDX MR and validate
|
||||
let tdx_mr = Self::build_tdx_mr([
|
||||
&report_body.mr_td,
|
||||
&report_body.rt_mr0,
|
||||
&report_body.rt_mr1,
|
||||
&report_body.rt_mr2,
|
||||
&report_body.rt_mr3,
|
||||
]);
|
||||
|
||||
Self::check_policy_hash(attestation_policy.tdx_mrs.as_deref(), &tdx_mr, "tdxmr")
|
||||
}
|
||||
Report::TD15(report_body) => {
|
||||
// Validate TCB level
|
||||
Self::validate_tcb_level(&attestation_policy.tdx_allowed_tcb_levels, tcblevel)?;
|
||||
|
||||
// Validate TDX Advisories
|
||||
for advisory in "e_verification_result.advisories {
|
||||
Self::check_policy(
|
||||
attestation_policy.tdx_allowed_advisory_ids.as_deref(),
|
||||
advisory,
|
||||
"advisories",
|
||||
)?;
|
||||
}
|
||||
|
||||
// Build combined TDX MR and validate
|
||||
let tdx_mr = Self::build_tdx_mr([
|
||||
&report_body.base.mr_td,
|
||||
&report_body.base.rt_mr0,
|
||||
&report_body.base.rt_mr1,
|
||||
&report_body.base.rt_mr2,
|
||||
&report_body.base.rt_mr3,
|
||||
]);
|
||||
|
||||
Self::check_policy_hash(attestation_policy.tdx_mrs.as_deref(), &tdx_mr, "tdxmr")
|
||||
}
|
||||
_ => Err(Error::policy_violation("Unknown quote report format")),
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper method to validate TCB levels
|
||||
fn validate_tcb_level(
|
||||
allowed_levels: &EnumSet<TcbLevel>,
|
||||
actual_level: TcbLevel,
|
||||
) -> Result<()> {
|
||||
if !allowed_levels.contains(actual_level) {
|
||||
let error_msg = format!(
|
||||
"Quote verification failed: TCB level mismatch (expected one of: {:?}, actual: {})",
|
||||
allowed_levels, actual_level
|
||||
);
|
||||
return Err(Error::policy_violation(error_msg));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Helper method to build combined TDX measurement register
|
||||
fn build_tdx_mr<const N: usize>(parts: [&[u8]; N]) -> Vec<u8> {
|
||||
parts.into_iter().flatten().cloned().collect()
|
||||
}
|
||||
|
||||
/// Check if a policy value matches the actual value
|
||||
fn check_policy(policy: Option<&[String]>, actual_value: &str, field_name: &str) -> Result<()> {
|
||||
if let Some(valid_values) = policy {
|
||||
if !valid_values.iter().any(|value| value == actual_value) {
|
||||
let error_msg =
|
||||
format!(
|
||||
"Quote verification failed: {} mismatch (expected one of: [ {} ], actual: {})",
|
||||
field_name, valid_values.join(", "), actual_value
|
||||
);
|
||||
return Err(Error::policy_violation(error_msg));
|
||||
}
|
||||
|
||||
tracing::debug!(field_name, actual_value, "Attestation policy check passed");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_policy_hash(
|
||||
policy: Option<&[Bytes]>,
|
||||
actual_value: &[u8],
|
||||
field_name: &str,
|
||||
) -> Result<()> {
|
||||
if let Some(valid_values) = policy {
|
||||
let actual_value = Bytes::copy_from_slice(actual_value);
|
||||
if !valid_values.contains(&actual_value) {
|
||||
let valid_values = valid_values
|
||||
.iter()
|
||||
.map(hex::encode)
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let error_msg = format!(
|
||||
"Quote verification failed: {} mismatch (expected one of: [ {} ], actual: {:x})",
|
||||
field_name, valid_values, actual_value
|
||||
);
|
||||
return Err(Error::policy_violation(error_msg));
|
||||
}
|
||||
|
||||
tracing::debug!(
|
||||
field_name,
|
||||
actual_value = format!("{actual_value:x}"),
|
||||
"Attestation policy check passed"
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_check_policy() {
|
||||
// Test with no policy (should pass)
|
||||
PolicyEnforcer::check_policy_hash(None, &[1, 2, 3], "test").unwrap();
|
||||
|
||||
// Test with matching policy
|
||||
let actual_value: Bytes = hex::decode("01020304").unwrap().into();
|
||||
PolicyEnforcer::check_policy_hash(
|
||||
Some(vec![actual_value.clone()]).as_deref(),
|
||||
&actual_value,
|
||||
"test",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
//.clone() Test with matching policy (multiple values)
|
||||
PolicyEnforcer::check_policy_hash(
|
||||
Some(vec![
|
||||
"aabbcc".into(),
|
||||
"01020304".into(),
|
||||
"ddeeff".into(),
|
||||
actual_value.clone(),
|
||||
])
|
||||
.as_deref(),
|
||||
&actual_value,
|
||||
"test",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Test with non-matching policy
|
||||
PolicyEnforcer::check_policy_hash(
|
||||
Some(vec!["aabbcc".into(), "ddeeff".into()]).as_deref(),
|
||||
&actual_value,
|
||||
"test",
|
||||
)
|
||||
.unwrap_err();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023-2025 Matter Labs
|
||||
|
||||
use teepot::quote::{tcblevel::TcbLevel, QuoteVerificationResult};
|
||||
|
||||
/// Handles reporting and logging of verification results
|
||||
pub struct VerificationReporter;
|
||||
|
||||
impl VerificationReporter {
|
||||
/// Log summary of a quote verification
|
||||
pub fn log_quote_verification_summary(quote_verification_result: &QuoteVerificationResult) {
|
||||
let QuoteVerificationResult {
|
||||
collateral_expired,
|
||||
result,
|
||||
quote,
|
||||
advisories,
|
||||
..
|
||||
} = quote_verification_result;
|
||||
|
||||
if *collateral_expired {
|
||||
tracing::warn!("Freshly fetched collateral expired!");
|
||||
}
|
||||
|
||||
let tcblevel = TcbLevel::from(*result);
|
||||
let advisories = if advisories.is_empty() {
|
||||
"None".to_string()
|
||||
} else {
|
||||
advisories
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
};
|
||||
|
||||
tracing::debug!(
|
||||
"Quote verification result: {tcblevel}. {report}. Advisory IDs: {advisories}.",
|
||||
report = "e.report
|
||||
);
|
||||
}
|
||||
|
||||
/// Log the results of batch verification
|
||||
pub fn log_batch_verification_results(
|
||||
batch_no: u32,
|
||||
verified_proofs_count: u32,
|
||||
unverified_proofs_count: u32,
|
||||
) {
|
||||
if unverified_proofs_count > 0 {
|
||||
if verified_proofs_count == 0 {
|
||||
tracing::error!(
|
||||
batch_no,
|
||||
"All {} proofs failed verification!",
|
||||
unverified_proofs_count
|
||||
);
|
||||
} else {
|
||||
tracing::warn!(
|
||||
batch_no,
|
||||
"Some proofs failed verification. Unverified proofs: {}. Verified proofs: {}.",
|
||||
unverified_proofs_count,
|
||||
verified_proofs_count
|
||||
);
|
||||
}
|
||||
} else if verified_proofs_count > 0 {
|
||||
tracing::info!(
|
||||
batch_no,
|
||||
"All {} proofs verified successfully!",
|
||||
verified_proofs_count
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Log overall verification results for multiple batches
|
||||
pub fn log_overall_verification_results(
|
||||
verified_batches_count: u32,
|
||||
unverified_batches_count: u32,
|
||||
) {
|
||||
if unverified_batches_count > 0 {
|
||||
if verified_batches_count == 0 {
|
||||
tracing::error!(
|
||||
"All {} batches failed verification!",
|
||||
unverified_batches_count
|
||||
);
|
||||
} else {
|
||||
tracing::error!(
|
||||
"Some batches failed verification! Unverified batches: {}. Verified batches: {}.",
|
||||
unverified_batches_count,
|
||||
verified_batches_count
|
||||
);
|
||||
}
|
||||
} else {
|
||||
tracing::info!("{} batches verified successfully!", verified_batches_count);
|
||||
}
|
||||
}
|
||||
}
|
157
bin/verify-era-proof-attestation/src/verification/signature.rs
Normal file
157
bin/verify-era-proof-attestation/src/verification/signature.rs
Normal file
|
@ -0,0 +1,157 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023-2025 Matter Labs
|
||||
|
||||
use secp256k1::{
|
||||
ecdsa::{RecoverableSignature, RecoveryId, Signature},
|
||||
Message, SECP256K1,
|
||||
};
|
||||
use teepot::{
|
||||
ethereum::{public_key_to_ethereum_address, recover_signer},
|
||||
prover::reportdata::ReportData,
|
||||
quote::QuoteVerificationResult,
|
||||
};
|
||||
use zksync_basic_types::H256;
|
||||
|
||||
use crate::error;
|
||||
|
||||
const SIGNATURE_LENGTH_WITH_RECOVERY_ID: usize = 65;
|
||||
const SIGNATURE_LENGTH_WITHOUT_RECOVERY_ID: usize = 64;
|
||||
|
||||
/// Handles verification of signatures in proofs
|
||||
pub struct SignatureVerifier;
|
||||
|
||||
impl SignatureVerifier {
|
||||
/// Verify a batch proof signature
|
||||
pub fn verify_batch_proof(
|
||||
quote_verification_result: &QuoteVerificationResult,
|
||||
root_hash: H256,
|
||||
signature: &[u8],
|
||||
) -> error::Result<bool> {
|
||||
let report_data_bytes = quote_verification_result.quote.get_report_data();
|
||||
tracing::trace!(?report_data_bytes);
|
||||
|
||||
let report_data = ReportData::try_from(report_data_bytes).map_err(|e| {
|
||||
error::Error::internal(format!("Could not convert to ReportData: {}", e))
|
||||
})?;
|
||||
|
||||
Self::verify(&report_data, &root_hash, signature)
|
||||
}
|
||||
|
||||
/// Verify signature against report data and root hash
|
||||
pub fn verify(
|
||||
report_data: &ReportData,
|
||||
root_hash: &H256,
|
||||
signature: &[u8],
|
||||
) -> error::Result<bool> {
|
||||
match report_data {
|
||||
ReportData::V0(report) => Self::verify_v0(report, root_hash, signature),
|
||||
ReportData::V1(report) => Self::verify_v1(report, root_hash, signature),
|
||||
ReportData::Unknown(_) => Ok(false),
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify a V0 report
|
||||
fn verify_v0(
|
||||
report: &teepot::prover::reportdata::ReportDataV0,
|
||||
root_hash: &H256,
|
||||
signature: &[u8],
|
||||
) -> error::Result<bool> {
|
||||
tracing::debug!("ReportData::V0");
|
||||
let signature = Signature::from_compact(signature)
|
||||
.map_err(|e| error::Error::signature_verification(e.to_string()))?;
|
||||
let root_hash_msg = Message::from_digest(root_hash.0);
|
||||
Ok(signature.verify(&root_hash_msg, &report.pubkey).is_ok())
|
||||
}
|
||||
|
||||
/// Verify a V1 report
|
||||
fn verify_v1(
|
||||
report: &teepot::prover::reportdata::ReportDataV1,
|
||||
root_hash: &H256,
|
||||
signature: &[u8],
|
||||
) -> error::Result<bool> {
|
||||
tracing::debug!("ReportData::V1");
|
||||
let ethereum_address_from_report = report.ethereum_address;
|
||||
|
||||
let root_hash_msg = Message::from_digest(
|
||||
root_hash
|
||||
.as_bytes()
|
||||
.try_into()
|
||||
.map_err(|_| error::Error::signature_verification("root hash not 32 bytes"))?,
|
||||
);
|
||||
|
||||
tracing::trace!("sig len = {}", signature.len());
|
||||
|
||||
// Try to recover Ethereum address from signature
|
||||
let ethereum_address_from_signature = match signature.len() {
|
||||
// Handle 64-byte signature case (missing recovery ID)
|
||||
SIGNATURE_LENGTH_WITHOUT_RECOVERY_ID => {
|
||||
SignatureVerifier::recover_address_with_missing_recovery_id(
|
||||
signature,
|
||||
&root_hash_msg,
|
||||
)?
|
||||
}
|
||||
// Standard 65-byte signature case
|
||||
SIGNATURE_LENGTH_WITH_RECOVERY_ID => {
|
||||
let signature_bytes: [u8; SIGNATURE_LENGTH_WITH_RECOVERY_ID] =
|
||||
signature.try_into().map_err(|_| {
|
||||
error::Error::signature_verification(
|
||||
"Expected 65-byte signature but got a different length",
|
||||
)
|
||||
})?;
|
||||
|
||||
recover_signer(&signature_bytes, &root_hash_msg).map_err(|e| {
|
||||
error::Error::signature_verification(format!("Failed to recover signer: {}", e))
|
||||
})?
|
||||
}
|
||||
// Any other length is invalid
|
||||
len => {
|
||||
return Err(error::Error::signature_verification(format!(
|
||||
"Invalid signature length: {len} bytes"
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
// Log verification details
|
||||
tracing::debug!(
|
||||
"Root hash: {}. Ethereum address from the attestation quote: {}. Ethereum address from the signature: {}.",
|
||||
root_hash,
|
||||
hex::encode(ethereum_address_from_report),
|
||||
hex::encode(ethereum_address_from_signature),
|
||||
);
|
||||
|
||||
Ok(ethereum_address_from_signature == ethereum_address_from_report)
|
||||
}
|
||||
|
||||
/// Helper function to recover Ethereum address when recovery ID is missing
|
||||
fn recover_address_with_missing_recovery_id(
|
||||
signature: &[u8],
|
||||
message: &Message,
|
||||
) -> error::Result<[u8; 20]> {
|
||||
tracing::info!("Signature is missing RecoveryId!");
|
||||
|
||||
// Try all possible recovery IDs
|
||||
for rec_id in [
|
||||
RecoveryId::Zero,
|
||||
RecoveryId::One,
|
||||
RecoveryId::Two,
|
||||
RecoveryId::Three,
|
||||
] {
|
||||
let Ok(rec_sig) = RecoverableSignature::from_compact(signature, rec_id) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Ok(public) = SECP256K1.recover_ecdsa(message, &rec_sig) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let ethereum_address = public_key_to_ethereum_address(&public);
|
||||
tracing::info!("Had to use RecoveryId::{rec_id:?}");
|
||||
return Ok(ethereum_address);
|
||||
}
|
||||
|
||||
// No valid recovery ID found
|
||||
Err(error::Error::signature_verification(
|
||||
"Could not find valid recovery ID",
|
||||
))
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue