diff --git a/Cargo.lock b/Cargo.lock index 7a28dd5b..1858d066 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -261,6 +261,7 @@ dependencies = [ "chrono", "crypto", "flex-error", + "hex", "lcp-types", "pem", "rustls 0.19.0", @@ -2536,6 +2537,7 @@ dependencies = [ "anyhow", "attestation-report", "commitments", + "crypto", "ecall-commands", "enclave-api", "env_logger", @@ -2689,19 +2691,23 @@ dependencies = [ name = "lcp-client" version = "0.1.0" dependencies = [ + "alloy-sol-types", "attestation-report", "base64 0.20.0-alpha.1", "chrono", "context", "crypto", "flex-error", + "hex-literal", "ibc", "light-client", "mock-lc", "prost", "serde", + "serde_json", "sgx_types", "store", + "tiny-keccak 1.5.0", ] [[package]] diff --git a/app/src/commands/attestation.rs b/app/src/commands/attestation.rs index 2513bbde..c37a9128 100644 --- a/app/src/commands/attestation.rs +++ b/app/src/commands/attestation.rs @@ -7,6 +7,7 @@ use clap::Parser; use crypto::Address; use ecall_commands::IASRemoteAttestationInput; use enclave_api::{Enclave, EnclaveCommandAPI, EnclaveProtoAPI}; +use log::info; use store::transaction::CommitStore; /// `attestation` subcommand @@ -63,6 +64,22 @@ pub struct IASRemoteAttestation { help = "An enclave key attested by Remote Attestation" )] pub enclave_key: String, + /// An operator address to perform `registerEnclaveKey` transaction on-chain + #[clap( + long = "operator", + help = "An operator address to perform `registerEnclaveKey` transaction on-chain" + )] + pub operator: Option, +} + +impl IASRemoteAttestation { + fn get_operator(&self) -> Result> { + if let Some(operator) = &self.operator { + Ok(Some(Address::from_hex_string(operator)?)) + } else { + Ok(None) + } + } } fn run_ias_remote_attestation, S: CommitStore>( @@ -74,10 +91,18 @@ fn run_ias_remote_attestation, S: CommitStore>( let target_enclave_key = Address::from_hex_string(&cmd.enclave_key)?; match enclave.ias_remote_attestation(IASRemoteAttestationInput { target_enclave_key, + operator: cmd.get_operator()?, spid: spid.as_bytes().to_vec(), ias_key: ias_key.as_bytes().to_vec(), }) { - Ok(_) => Ok(()), + Ok(res) => { + info!("AVR: {:?}", res.report.avr); + info!( + "report_data: {}", + res.report.get_avr()?.parse_quote()?.report_data() + ); + Ok(()) + } Err(e) => bail!("failed to perform IAS Remote Attestation: {:?}!", e), } } @@ -96,6 +121,13 @@ pub struct SimulateRemoteAttestation { )] pub enclave_key: String, + /// An operator address to perform `registerEnclaveKey` transaction on-chain + #[clap( + long = "operator", + help = "An operator address to perform `registerEnclaveKey` transaction on-chain" + )] + pub operator: Option, + /// Path to a der-encoded file that contains X.509 certificate #[clap( long = "signing_cert_path", @@ -135,6 +167,17 @@ pub struct SimulateRemoteAttestation { pub isv_enclave_quote_status: String, } +#[cfg(feature = "sgx-sw")] +impl SimulateRemoteAttestation { + fn get_operator(&self) -> Result> { + if let Some(operator) = &self.operator { + Ok(Some(Address::from_hex_string(operator)?)) + } else { + Ok(None) + } + } +} + #[cfg(feature = "sgx-sw")] fn run_simulate_remote_attestation, S: CommitStore>( enclave: E, @@ -187,13 +230,18 @@ fn run_simulate_remote_attestation, S: CommitStore>( match enclave.simulate_remote_attestation( ecall_commands::SimulateRemoteAttestationInput { target_enclave_key, + operator: cmd.get_operator()?, advisory_ids: cmd.advisory_ids.clone(), isv_enclave_quote_status: cmd.isv_enclave_quote_status.clone(), }, signing_key, signing_cert, ) { - Ok(_) => Ok(()), + Ok(res) => { + info!("AVR: {:?}", res.avr); + info!("report_data: {}", res.avr.parse_quote()?.report_data()); + Ok(()) + } Err(e) => bail!("failed to simulate Remote Attestation: {:?}!", e), } } diff --git a/app/src/commands/enclave.rs b/app/src/commands/enclave.rs index 37de84f6..8e92969b 100644 --- a/app/src/commands/enclave.rs +++ b/app/src/commands/enclave.rs @@ -104,9 +104,11 @@ fn run_list_keys, S: CommitStore>( match eki.avr { Some(eavr) => { let avr = eavr.get_avr()?; + let report_data = avr.parse_quote()?.report_data(); list_json.push(json! {{ "address": eki.address.to_hex_string(), "attested": true, + "report_data": report_data.to_string(), "isv_enclave_quote_status": avr.isv_enclave_quote_status, "advisory_ids": avr.advisory_ids, "attested_at": avr.timestamp diff --git a/enclave-modules/ecall-handler/src/enclave_manage/attestation.rs b/enclave-modules/ecall-handler/src/enclave_manage/attestation.rs index 774b43da..6848a0e4 100644 --- a/enclave-modules/ecall-handler/src/enclave_manage/attestation.rs +++ b/enclave-modules/ecall-handler/src/enclave_manage/attestation.rs @@ -1,6 +1,6 @@ use crate::enclave_manage::errors::Error; use crate::prelude::*; -use attestation_report::verify_report; +use attestation_report::{verify_report, ReportData}; use crypto::{EnclaveKey, SealingKey}; use ecall_commands::{CommandContext, IASRemoteAttestationInput, IASRemoteAttestationResponse}; use enclave_remote_attestation::{ @@ -18,7 +18,7 @@ pub(crate) fn ias_remote_attestation( let report = { let spid = decode_spid(&input.spid); let report = create_attestation_report( - pub_key.as_report_data(), + ReportData::new(pub_key.as_address(), input.operator).into(), sgx_quote_sign_type_t::SGX_UNLINKABLE_SIGNATURE, spid, &input.ias_key, @@ -39,7 +39,7 @@ pub(crate) fn simulate_remote_attestation( let pub_key = EnclaveKey::unseal(&cctx.sealed_ek.ok_or(Error::enclave_key_not_found())?)?.get_pubkey(); let avr = enclave_remote_attestation::simulate::create_attestation_report( - pub_key.as_report_data(), + ReportData::new(pub_key.as_address(), input.operator).into(), sgx_quote_sign_type_t::SGX_UNLINKABLE_SIGNATURE, input.advisory_ids, input.isv_enclave_quote_status, diff --git a/enclave-modules/ecall-handler/src/light_client/aggregate_messages.rs b/enclave-modules/ecall-handler/src/light_client/aggregate_messages.rs index 6e7b4c41..3bd3f5a2 100644 --- a/enclave-modules/ecall-handler/src/light_client/aggregate_messages.rs +++ b/enclave-modules/ecall-handler/src/light_client/aggregate_messages.rs @@ -44,7 +44,7 @@ pub fn aggregate_messages( .collect::, _>>()?; let message = ProxyMessage::from(commitments::aggregate_messages(messages)?); - let proof = prove_commitment(ek, input.signer, message)?; + let proof = prove_commitment(ek, message)?; Ok(LightClientResponse::AggregateMessages( AggregateMessagesResponse(proof), diff --git a/enclave-modules/ecall-handler/src/light_client/init_client.rs b/enclave-modules/ecall-handler/src/light_client/init_client.rs index b4ec6226..1f15aa26 100644 --- a/enclave-modules/ecall-handler/src/light_client/init_client.rs +++ b/enclave-modules/ecall-handler/src/light_client/init_client.rs @@ -32,7 +32,7 @@ pub fn init_client( ctx.store_any_consensus_state(client_id.clone(), res.height, any_consensus_state)?; let proof = if res.prove { - prove_commitment(ek, input.signer, res.message)? + prove_commitment(ek, res.message)? } else { CommitmentProof::new_with_no_signature(res.message.to_bytes()) }; diff --git a/enclave-modules/ecall-handler/src/light_client/update_client.rs b/enclave-modules/ecall-handler/src/light_client/update_client.rs index 171fa832..9358b795 100644 --- a/enclave-modules/ecall-handler/src/light_client/update_client.rs +++ b/enclave-modules/ecall-handler/src/light_client/update_client.rs @@ -34,7 +34,7 @@ pub fn update_client( )?; let proof = if data.prove { - prove_commitment(ek, input.signer, message)? + prove_commitment(ek, message)? } else { CommitmentProof::new_with_no_signature(message.to_bytes()) }; @@ -45,7 +45,7 @@ pub fn update_client( UpdateClientResult::Misbehaviour(data) => { ctx.store_any_client_state(input.client_id, data.new_any_client_state)?; - let proof = prove_commitment(ek, input.signer, data.message.into())?; + let proof = prove_commitment(ek, data.message.into())?; Ok(LightClientResponse::UpdateClient(UpdateClientResponse( proof, ))) diff --git a/enclave-modules/ecall-handler/src/light_client/verify_state.rs b/enclave-modules/ecall-handler/src/light_client/verify_state.rs index f3b18c70..cab2b0dc 100644 --- a/enclave-modules/ecall-handler/src/light_client/verify_state.rs +++ b/enclave-modules/ecall-handler/src/light_client/verify_state.rs @@ -28,7 +28,7 @@ pub fn verify_membership( )?; Ok(LightClientResponse::VerifyMembership( - VerifyMembershipResponse(prove_commitment(ek, input.signer, res.message.into())?), + VerifyMembershipResponse(prove_commitment(ek, res.message.into())?), )) } @@ -49,6 +49,6 @@ pub fn verify_non_membership( )?; Ok(LightClientResponse::VerifyNonMembership( - VerifyNonMembershipResponse(prove_commitment(ek, input.signer, res.message.into())?), + VerifyNonMembershipResponse(prove_commitment(ek, res.message.into())?), )) } diff --git a/enclave-modules/remote-attestation/src/attestation.rs b/enclave-modules/remote-attestation/src/attestation.rs index dee123b2..e745bc04 100644 --- a/enclave-modules/remote-attestation/src/attestation.rs +++ b/enclave-modules/remote-attestation/src/attestation.rs @@ -15,8 +15,7 @@ use sgx_tstd::{ net::TcpStream, sync::Arc, }; -use sgx_types::{c_int, sgx_spid_t}; -use sgx_types::{sgx_quote_nonce_t, sgx_quote_sign_type_t, sgx_report_data_t}; +use sgx_types::{c_int, sgx_quote_nonce_t, sgx_quote_sign_type_t, sgx_report_data_t, sgx_spid_t}; pub const REPORT_DATA_SIZE: usize = 32; diff --git a/enclave/Cargo.lock b/enclave/Cargo.lock index 5d1985fb..d65df866 100644 --- a/enclave/Cargo.lock +++ b/enclave/Cargo.lock @@ -91,6 +91,7 @@ dependencies = [ "chrono", "crypto", "flex-error", + "hex", "lcp-types", "pem", "rustls", diff --git a/modules/attestation-report/Cargo.toml b/modules/attestation-report/Cargo.toml index f44da2c6..8d74c6b4 100644 --- a/modules/attestation-report/Cargo.toml +++ b/modules/attestation-report/Cargo.toml @@ -12,6 +12,7 @@ serde_json = { version = "1.0", default-features = false, features = ["alloc"] } crypto = { path = "../crypto", default-features = false } lcp-types = { path = "../types", default-features = false } flex-error = { version = "0.4.4", default-features = false } +hex = { version = "0.4", default-features = false, features = ["alloc"] } base64 = { git = "https://github.com/marshallpierce/rust-base64", default-features = false, features = ["alloc"] } pem = { version = "2.0", default-features = false } diff --git a/modules/attestation-report/src/errors.rs b/modules/attestation-report/src/errors.rs index 2a623ee5..27029501 100644 --- a/modules/attestation-report/src/errors.rs +++ b/modules/attestation-report/src/errors.rs @@ -14,6 +14,15 @@ define_error! { format_args!("unexpected attestation report version: expected={} actual={}", e.expected, e.actual) }, + UnexpectedReportDataVersion + { + expected: u8, + actual: u8 + } + |e| { + format_args!("unexpected report data version: expected={} actual={}", e.expected, e.actual) + }, + InvalidReportDataSize { size: usize diff --git a/modules/attestation-report/src/lib.rs b/modules/attestation-report/src/lib.rs index a3ecf1a3..a3d7501d 100644 --- a/modules/attestation-report/src/lib.rs +++ b/modules/attestation-report/src/lib.rs @@ -22,7 +22,9 @@ mod prelude { pub use errors::Error; mod errors; -pub use report::{AttestationVerificationReport, EndorsedAttestationVerificationReport, Quote}; +pub use report::{ + AttestationVerificationReport, EndorsedAttestationVerificationReport, Quote, ReportData, +}; mod report; #[cfg(any(feature = "std", feature = "sgx"))] diff --git a/modules/attestation-report/src/report.rs b/modules/attestation-report/src/report.rs index 484675fc..d422fdba 100644 --- a/modules/attestation-report/src/report.rs +++ b/modules/attestation-report/src/report.rs @@ -1,13 +1,76 @@ use crate::errors::Error; use crate::prelude::*; use chrono::prelude::DateTime; -use core::fmt::Debug; +use core::fmt::{Debug, Display, Error as FmtError}; use crypto::Address; use lcp_types::Time; use serde::{Deserialize, Serialize}; -use sgx_types::{metadata::metadata_t, sgx_measurement_t, sgx_quote_t}; +use sgx_types::{metadata::metadata_t, sgx_measurement_t, sgx_quote_t, sgx_report_data_t}; use tendermint::Time as TmTime; +pub const REPORT_DATA_V1: u8 = 1; + +/// ReportData is a 64-byte value that is embedded in the Quote +/// | version: 1 byte | enclave key: 20 bytes | operator: 20 bytes | nonce: 22 bytes | +#[derive(Debug, Clone, PartialEq)] +pub struct ReportData([u8; 64]); + +impl ReportData { + pub fn new(ek: Address, operator: Option
) -> Self { + let mut data: ReportData = Default::default(); + data.0[0] = REPORT_DATA_V1; + data.0[1..21].copy_from_slice(ek.0.as_ref()); + if let Some(operator) = operator { + data.0[21..41].copy_from_slice(operator.0.as_ref()); + } + data + } + + pub fn enclave_key(&self) -> Address { + // Unwrap is safe because the size of the slice is 20 + Address::try_from(&self.0[1..21]).unwrap() + } + + pub fn operator(&self) -> Address { + // Unwrap is safe because the size of the slice is 20 + Address::try_from(&self.0[21..41]).unwrap() + } + + pub fn validate(&self) -> Result<(), Error> { + if self.0[0] != REPORT_DATA_V1 { + return Err(Error::unexpected_report_data_version( + REPORT_DATA_V1, + self.0[0], + )); + } + Ok(()) + } +} + +impl Default for ReportData { + fn default() -> Self { + ReportData([0; 64]) + } +} + +impl Display for ReportData { + fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), FmtError> { + write!(f, "0x{}", hex::encode(self.0)) + } +} + +impl From for sgx_report_data_t { + fn from(data: ReportData) -> Self { + sgx_report_data_t { d: data.0 } + } +} + +impl From for ReportData { + fn from(data: sgx_report_data_t) -> Self { + ReportData(data.d) + } +} + /// AttestationReport can be endorsed by either the Intel Attestation Service /// using EPID or Data Center Attestation /// Service (platform dependent) using ECDSA. @@ -25,7 +88,7 @@ pub struct EndorsedAttestationVerificationReport { impl EndorsedAttestationVerificationReport { pub fn get_avr(&self) -> Result { - serde_json::from_slice(self.avr.as_bytes()).map_err(Error::serde_json) + serde_json::from_slice(self.avr.as_ref()).map_err(Error::serde_json) } } @@ -119,13 +182,8 @@ pub struct Quote { } impl Quote { - pub fn get_enclave_key_address(&self) -> Result { - let data = self.raw.report_body.report_data.d; - if data.len() < 20 { - Err(Error::invalid_report_data_size(data.len())) - } else { - Ok(Address::try_from(&data[..20])?) - } + pub fn report_data(&self) -> ReportData { + self.raw.report_body.report_data.into() } pub fn get_mrenclave(&self) -> sgx_measurement_t { diff --git a/modules/attestation-report/src/verification.rs b/modules/attestation-report/src/verification.rs index e0685bf1..84485a77 100644 --- a/modules/attestation-report/src/verification.rs +++ b/modules/attestation-report/src/verification.rs @@ -71,7 +71,7 @@ pub fn verify_report( report_cert .verify_signature( &webpki::RSA_PKCS1_2048_8192_SHA256, - report.avr.as_bytes(), + report.avr.as_ref(), &report.signature, ) .map_err(|e| Error::web_pki(e.to_string()))?; diff --git a/modules/commitments/src/message.rs b/modules/commitments/src/message.rs index 17c6f602..05d65136 100644 --- a/modules/commitments/src/message.rs +++ b/modules/commitments/src/message.rs @@ -200,7 +200,6 @@ mod tests { message::verify_membership::CommitmentPrefix, CommitmentProof, StateID, TrustingPeriodContext, }; - use crypto::Address; use lcp_types::{nanos_to_duration, Any, Height, Time, MAX_UNIX_TIMESTAMP_NANOS}; use proptest::prelude::*; @@ -208,18 +207,13 @@ mod tests { Height::new(tuple.0, tuple.1) } - fn test_update_client_message( - c1: UpdateStateProxyMessage, - proof_signer: Address, - proof_signature: Vec, - ) { + fn test_update_client_message(c1: UpdateStateProxyMessage, proof_signature: Vec) { let v = c1.clone().ethabi_encode(); let c2 = UpdateStateProxyMessage::ethabi_decode(&v).unwrap(); assert_eq!(c1, c2); let p1 = CommitmentProof { message: ProxyMessage::from(c1).to_bytes(), - signer: proof_signer, signature: proof_signature.to_vec(), }; // TODO uncomment this line when we want to generate the test data @@ -237,7 +231,6 @@ mod tests { post_state_id in any::<[u8; 32]>().prop_map(StateID::from), emitted_states in any::))>>(), timestamp in ..=MAX_UNIX_TIMESTAMP_NANOS, - proof_signer in any::<[u8; 20]>(), proof_signature in any::<[u8; 65]>() ) { let c1 = UpdateStateProxyMessage { @@ -251,7 +244,7 @@ mod tests { timestamp: Time::from_unix_timestamp_nanos(timestamp).unwrap(), context: Default::default(), }; - test_update_client_message(c1, Address(proof_signer), proof_signature.to_vec()); + test_update_client_message(c1, proof_signature.to_vec()); } #[test] @@ -266,7 +259,6 @@ mod tests { clock_drift in ..=MAX_UNIX_TIMESTAMP_NANOS, untrusted_header_timestamp in ..=MAX_UNIX_TIMESTAMP_NANOS, trusted_state_timestamp in ..=MAX_UNIX_TIMESTAMP_NANOS, - proof_signer in any::<[u8; 20]>(), proof_signature in any::<[u8; 65]>(), ) { let c1 = UpdateStateProxyMessage { @@ -285,7 +277,7 @@ mod tests { Time::from_unix_timestamp_nanos(trusted_state_timestamp).unwrap(), ).into(), }; - test_update_client_message(c1, Address(proof_signer), proof_signature.to_vec()); + test_update_client_message(c1, proof_signature.to_vec()); } #[test] @@ -295,7 +287,6 @@ mod tests { value in any::>(), height in any::<(u64, u64)>().prop_map(height_from_tuple), state_id in any::<[u8; 32]>().prop_map(StateID::from), - proof_signer in any::<[u8; 20]>(), proof_signature in any::<[u8; 65]>() ) { let c1 = VerifyMembershipProxyMessage { @@ -311,7 +302,6 @@ mod tests { let p1 = CommitmentProof { message: ProxyMessage::from(c1).to_bytes(), - signer: Address(proof_signer), signature: proof_signature.to_vec(), }; let p2 = CommitmentProof::ethabi_decode(&p1.clone().ethabi_encode()).unwrap(); diff --git a/modules/commitments/src/proof.rs b/modules/commitments/src/proof.rs index 9363840a..ea9898f9 100644 --- a/modules/commitments/src/proof.rs +++ b/modules/commitments/src/proof.rs @@ -1,28 +1,21 @@ use crate::{encoder::EthABIEncoder, prelude::*, Error, ProxyMessage}; -use alloy_sol_types::{private::Address as SolAddress, sol, SolValue}; -use crypto::Address; +use alloy_sol_types::{sol, SolValue}; use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] pub struct CommitmentProof { pub message: Vec, - pub signer: Address, pub signature: Vec, } impl CommitmentProof { - pub fn new(message: Vec, signer: Address, signature: Vec) -> Self { - Self { - message, - signer, - signature, - } + pub fn new(message: Vec, signature: Vec) -> Self { + Self { message, signature } } pub fn new_with_no_signature(message: Vec) -> Self { Self { message, - signer: Default::default(), signature: Default::default(), } } @@ -49,7 +42,6 @@ impl EthABIEncoder for CommitmentProof { sol! { struct EthABICommitmentProof { bytes message; - address signer; bytes signature; } } @@ -58,7 +50,6 @@ impl From for CommitmentProof { fn from(value: EthABICommitmentProof) -> Self { Self { message: value.message, - signer: Address(*value.signer.0), signature: value.signature, } } @@ -68,7 +59,6 @@ impl From for EthABICommitmentProof { fn from(value: CommitmentProof) -> Self { Self { message: value.message, - signer: SolAddress::from(value.signer.0), signature: value.signature, } } diff --git a/modules/commitments/src/prover.rs b/modules/commitments/src/prover.rs index cb44bbb1..22544dd8 100644 --- a/modules/commitments/src/prover.rs +++ b/modules/commitments/src/prover.rs @@ -1,19 +1,14 @@ use crate::errors::Error; use crate::{prelude::*, CommitmentProof, ProxyMessage}; -use crypto::{Address, Signer}; +use crypto::Signer; /// Calculate the commitment of a message and sign it pub fn prove_commitment( signer: &dyn Signer, - signer_address: Address, message: ProxyMessage, ) -> Result { message.validate()?; let message_bytes = message.to_bytes(); let signature = signer.sign(&message_bytes).map_err(Error::crypto)?; - Ok(CommitmentProof::new( - message_bytes, - signer_address, - signature, - )) + Ok(CommitmentProof::new(message_bytes, signature)) } diff --git a/modules/crypto/src/key.rs b/modules/crypto/src/key.rs index 6e8d5610..221aec86 100644 --- a/modules/crypto/src/key.rs +++ b/modules/crypto/src/key.rs @@ -10,7 +10,7 @@ use libsecp256k1::{ }; use serde::{Deserialize, Serialize}; use serde_big_array::BigArray; -use sgx_types::{sgx_report_data_t, sgx_sealed_data_t}; +use sgx_types::sgx_sealed_data_t; use tiny_keccak::Keccak; #[derive(Default)] @@ -115,12 +115,6 @@ impl EnclavePublicKey { self.0.serialize_compressed() } - pub fn as_report_data(&self) -> sgx_report_data_t { - let mut report_data = sgx_report_data_t::default(); - report_data.d[..20].copy_from_slice(&self.as_address().0); - report_data - } - pub fn as_address(&self) -> Address { let pubkey = &self.0.serialize()[1..]; let mut addr: Address = Default::default(); @@ -143,6 +137,9 @@ impl Address { let bz = hex::decode(s.strip_prefix("0x").unwrap_or(s))?; Address::try_from(bz.as_slice()) } + pub fn is_zero(&self) -> bool { + self.0 == [0u8; 20] + } } impl Display for Address { diff --git a/modules/ecall-commands/src/enclave_manage.rs b/modules/ecall-commands/src/enclave_manage.rs index f0008925..d5108256 100644 --- a/modules/ecall-commands/src/enclave_manage.rs +++ b/modules/ecall-commands/src/enclave_manage.rs @@ -28,6 +28,7 @@ pub struct GenerateEnclaveKeyInput; #[derive(Serialize, Deserialize, Debug)] pub struct IASRemoteAttestationInput { pub target_enclave_key: Address, + pub operator: Option
, pub spid: Vec, pub ias_key: Vec, } @@ -48,6 +49,7 @@ impl IASRemoteAttestationInput { #[derive(Serialize, Deserialize, Debug)] pub struct SimulateRemoteAttestationInput { pub target_enclave_key: Address, + pub operator: Option
, pub advisory_ids: Vec, pub isv_enclave_quote_status: String, } diff --git a/modules/ecall-commands/src/msgs.rs b/modules/ecall-commands/src/msgs.rs index 8efe8a36..6d55104b 100644 --- a/modules/ecall-commands/src/msgs.rs +++ b/modules/ecall-commands/src/msgs.rs @@ -118,7 +118,6 @@ impl From for MsgCreateClientResponse { fn from(res: InitClientResponse) -> Self { Self { message: res.proof.message, - signer: res.proof.signer.into(), signature: res.proof.signature, } } @@ -128,7 +127,6 @@ impl From for MsgUpdateClientResponse { fn from(res: UpdateClientResponse) -> Self { Self { message: res.0.message, - signer: res.0.signer.into(), signature: res.0.signature, } } @@ -138,7 +136,6 @@ impl From for MsgAggregateMessagesResponse { fn from(res: AggregateMessagesResponse) -> Self { Self { message: res.0.message, - signer: res.0.signer.into(), signature: res.0.signature, } } @@ -148,7 +145,6 @@ impl From for MsgVerifyMembershipResponse { fn from(res: VerifyMembershipResponse) -> Self { Self { message: res.0.message, - signer: res.0.signer.to_vec(), signature: res.0.signature, } } @@ -158,7 +154,6 @@ impl From for MsgVerifyNonMembershipResponse { fn from(res: VerifyNonMembershipResponse) -> Self { Self { message: res.0.message, - signer: res.0.signer.to_vec(), signature: res.0.signature, } } diff --git a/modules/lcp-client/Cargo.toml b/modules/lcp-client/Cargo.toml index 8f4e4cab..f896ab1d 100644 --- a/modules/lcp-client/Cargo.toml +++ b/modules/lcp-client/Cargo.toml @@ -6,7 +6,11 @@ edition = "2021" [dependencies] prost = { version = "0.11", default-features = false } serde = { version = "1.0.184", default-features = false, features = ["alloc", "derive"] } +serde_json = { version = "1.0", default-features = false } flex-error = { version = "0.4.4", default-features = false } +tiny-keccak = "1.4" +hex-literal = "0.4.1" +alloy-sol-types = { version = "0.6.0", default-features = false } attestation-report = { path = "../attestation-report", default-features = false } light-client = { path = "../light-client", default-features = false } diff --git a/modules/lcp-client/src/client_def.rs b/modules/lcp-client/src/client_def.rs index 700204a8..f8384090 100644 --- a/modules/lcp-client/src/client_def.rs +++ b/modules/lcp-client/src/client_def.rs @@ -1,25 +1,63 @@ use crate::client_state::ClientState; use crate::consensus_state::ConsensusState; use crate::errors::Error; -use crate::message::{ClientMessage, RegisterEnclaveKeyMessage}; -use attestation_report::EndorsedAttestationVerificationReport; +use crate::message::{ + ClientMessage, CommitmentProofs, RegisterEnclaveKeyMessage, UpdateOperatorsMessage, +}; +use alloy_sol_types::{sol, SolValue}; +use attestation_report::{EndorsedAttestationVerificationReport, ReportData}; use crypto::{verify_signature_address, Address, Keccak256}; +use hex_literal::hex; use light_client::commitments::{ - CommitmentPrefix, CommitmentProof, EthABIEncoder, MisbehaviourProxyMessage, ProxyMessage, + CommitmentPrefix, EthABIEncoder, MisbehaviourProxyMessage, ProxyMessage, UpdateStateProxyMessage, VerifyMembershipProxyMessage, }; use light_client::types::{ClientId, Height, Time}; -use light_client::{ClientKeeper, ClientReader, HostClientKeeper, HostClientReader}; +use light_client::{HostClientKeeper, HostClientReader}; +use tiny_keccak::Keccak; pub const LCP_CLIENT_TYPE: &str = "0000-lcp"; +/// keccak256( +/// abi.encode( +/// keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)"), +/// keccak256("LCPClient"), +/// keccak256("1"), +/// uint256(0), +/// address(0), +/// bytes32(0) // salt +/// ) +/// ) +pub const LCP_CLIENT_DOMAIN_SEPARATOR: [u8; 32] = + hex!("7fd21c2453e80741907e7ff11fd62ae1daa34c6fc0c2eced821f1c1d3fe88a4c"); + /// LCPClient is a PoC implementation of LCP Client /// This is aimed to testing purposes only for now #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct LCPClient; +#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize)] +struct EKOperatorInfo { + expired_at: u64, + operator: Address, +} + +impl EKOperatorInfo { + fn new(expired_at: u64, operator: Address) -> Self { + Self { + expired_at, + operator, + } + } +} + #[allow(clippy::too_many_arguments)] impl LCPClient { + /// client_type returns the client type + pub fn client_type(&self) -> String { + LCP_CLIENT_TYPE.to_owned() + } + /// initialse initialises a client state with an initial client state and consensus state pub fn initialise( &self, @@ -34,6 +72,24 @@ impl LCPClient { assert!(client_state.latest_height.is_zero()); // mr_enclave length must be 32 assert!(client_state.mr_enclave.len() == 32); + // operators_threshold_denominator and operators_threshold_numerator must not be 0 + assert!( + client_state.operators.is_empty() + || client_state.operators_threshold_denominator != 0 + && client_state.operators_threshold_numerator != 0 + ); + // check if the operators order is sorted + client_state.operators.windows(2).for_each(|pair| { + assert!(pair[0].0 < pair[1].0); + }); + // operators_threshold_numerator must be less than or equal to operators_threshold_denominator + assert!( + client_state.operators_threshold_numerator + <= client_state.operators_threshold_denominator + ); + // operators_nonce must be 0 + assert!(client_state.operators_nonce == 0); + // An initial consensus state must be empty assert!(consensus_state.is_empty()); @@ -56,27 +112,20 @@ impl LCPClient { let client_state = ctx.client_state(&client_id)?.try_into()?; match message { ClientMessage::UpdateClient(msg) => match msg.proxy_message { - ProxyMessage::UpdateState(pmsg) => self.update_state( - ctx, - client_id, - client_state, - pmsg, - msg.signer, - msg.signature, - ), - ProxyMessage::Misbehaviour(pmsg) => self.submit_misbehaviour( - ctx, - client_id, - client_state, - pmsg, - msg.signer, - msg.signature, - ), + ProxyMessage::UpdateState(pmsg) => { + self.update_state(ctx, client_id, client_state, pmsg, msg.signatures) + } + ProxyMessage::Misbehaviour(pmsg) => { + self.submit_misbehaviour(ctx, client_id, client_state, pmsg, msg.signatures) + } _ => Err(Error::unexpected_header_type(format!("{:?}", msg))), }, ClientMessage::RegisterEnclaveKey(msg) => { self.register_enclave_key(ctx, client_id, client_state, msg) } + ClientMessage::UpdateOperators(msg) => { + self.update_operators(ctx, client_id, client_state, msg) + } } } @@ -86,8 +135,7 @@ impl LCPClient { client_id: ClientId, client_state: ClientState, message: UpdateStateProxyMessage, - signer: Address, - signature: Vec, + signatures: Vec>, ) -> Result<(), Error> { message.validate()?; // TODO return an error instead of assertion @@ -107,16 +155,13 @@ impl LCPClient { assert!(prev_consensus_state.state_id == message.prev_state_id.unwrap()); } - // check if the specified signer exists in the client state - assert!(self.contains_enclave_key(ctx, &client_id, signer)); - - // check if the `header.signer` matches the commitment prover - let signer2 = verify_signature_address( + self.verify_ek_signatures( + ctx, + &client_id, + &client_state, ProxyMessage::from(message.clone()).to_bytes().as_slice(), - &signature, - ) - .unwrap(); - assert!(signer == signer2); + signatures, + )?; // check if proxy's validation context matches our's context message.context.validate(ctx.host_timestamp())?; @@ -142,15 +187,81 @@ impl LCPClient { ) -> Result<(), Error> { // TODO return an error instead of assertion - let eavr = message.0; - let (key, attestation_time) = verify_report(ctx.host_timestamp(), &client_state, &eavr)?; + assert!(!client_state.frozen); + + let (report_data, attestation_time) = + verify_report(ctx.host_timestamp(), &client_state, &message.report)?; - self.add_enclave_key( + let operator = if let Some(operator_signature) = message.operator_signature { + verify_signature_address( + compute_eip712_register_enclave_key(&message.report.avr).as_ref(), + operator_signature.as_ref(), + )? + } else { + Default::default() + }; + let expected_operator = report_data.operator(); + // check if the operator matches the expected operator in the report data + assert!(expected_operator.is_zero() || operator == expected_operator); + self.set_enclave_operator_info( ctx, &client_id, - key, - (attestation_time + client_state.key_expiration)?.as_unix_timestamp_secs(), + report_data.enclave_key(), + EKOperatorInfo::new( + (attestation_time + client_state.key_expiration)?.as_unix_timestamp_secs(), + operator, + ), + ); + Ok(()) + } + + fn update_operators( + &self, + ctx: &mut dyn HostClientKeeper, + client_id: ClientId, + client_state: ClientState, + message: UpdateOperatorsMessage, + ) -> Result<(), Error> { + // TODO return an error instead of assertion + + assert!(!client_state.frozen); + + assert_eq!(message.nonce, client_state.operators_nonce + 1); + + let sign_bytes = compute_eip712_update_operators( + client_id.clone(), + message.nonce, + message.new_operators.clone(), + message.new_operators_threshold_numerator, + message.new_operators_threshold_denominator, ); + + let mut success = 0u64; + for (op, sig) in client_state + .operators + .clone() + .into_iter() + .zip(message.signatures.iter()) + .filter(|(_, sig)| !sig.is_empty()) + { + // check if the operator's signature is valid + let operator = verify_signature_address(sign_bytes.as_ref(), sig.as_ref())?; + assert_eq!(op, operator); + success += 1; + } + assert!( + success * client_state.operators_threshold_denominator + >= message.new_operators_threshold_numerator * client_state.operators.len() as u64 + ); + + let new_client_state = client_state.with_operators( + message.new_operators, + message.nonce, + message.new_operators_threshold_numerator, + message.new_operators_threshold_denominator, + ); + ctx.store_any_client_state(client_id, new_client_state.into())?; + Ok(()) } @@ -160,8 +271,7 @@ impl LCPClient { client_id: ClientId, client_state: ClientState, message: MisbehaviourProxyMessage, - signer: Address, - signature: Vec, + signatures: Vec>, ) -> Result<(), Error> { message.validate()?; @@ -174,18 +284,10 @@ impl LCPClient { assert!(prev_consensus_state.state_id == state.state_id); } - // check if the specified signer exists in the client state - assert!(self.contains_enclave_key(ctx, &client_id, signer)); - // check if proxy's validation context matches our's context message.context.validate(ctx.host_timestamp())?; - - // check if the `header.signer` matches the commitment prover - let signer2 = verify_signature_address( - ProxyMessage::from(message).to_bytes().as_slice(), - &signature, - )?; - assert!(signer == signer2); + let sign_bytes = ProxyMessage::from(message).to_bytes(); + self.verify_ek_signatures(ctx, &client_id, &client_state, &sign_bytes, signatures)?; let new_client_state = client_state.with_frozen(); ctx.store_any_client_state(client_id, new_client_state.into())?; @@ -207,8 +309,8 @@ impl LCPClient { // TODO return an error instead of assertion // convert `proof` to CommitmentProof - let commitment_proof = CommitmentProof::ethabi_decode(proof.as_slice()).unwrap(); - let msg: VerifyMembershipProxyMessage = commitment_proof.message()?.try_into()?; + let commitment_proofs = CommitmentProofs::ethabi_decode(proof.as_slice()).unwrap(); + let msg: VerifyMembershipProxyMessage = commitment_proofs.message()?.try_into()?; // check if `.prefix` matches the counterparty connection's prefix assert!(msg.prefix == prefix); @@ -225,56 +327,196 @@ impl LCPClient { ConsensusState::try_from(ctx.consensus_state(&client_id, &proof_height)?)?; assert!(consensus_state.state_id == msg.state_id); - // check if the `commitment_proof.signer` matches the commitment prover - let signer = - verify_signature_address(&commitment_proof.message, &commitment_proof.signature)?; - assert!(commitment_proof.signer == signer); + let client_state = ClientState::try_from(ctx.client_state(&client_id)?)?; - // check if the specified signer is not expired and exists in the client state - assert!(self.is_active_enclave_key(ctx, &client_id, signer)); + self.verify_ek_signatures( + ctx, + &client_id, + &client_state, + &commitment_proofs.message, + commitment_proofs.signatures, + )?; Ok(()) } - pub fn client_type(&self) -> String { - LCP_CLIENT_TYPE.to_owned() + fn verify_ek_signatures( + &self, + ctx: &T, + client_id: &ClientId, + client_state: &ClientState, + sign_bytes: &[u8], + signatures: Vec>, + ) -> Result<(), Error> { + if client_state.operators.is_empty() { + assert!(signatures.len() == 1); + let ek = verify_signature_address(sign_bytes, &signatures[0])?; + assert!(self.is_active_enclave_key(ctx, client_id, ek)); + } else { + let mut success = 0u64; + for (signature, operator) in signatures + .into_iter() + .zip(client_state.operators.clone().into_iter()) + .filter(|(sig, _)| !sig.is_empty()) + { + // check if the `header.signer` matches the commitment prover + let ek = verify_signature_address(sign_bytes, &signature)?; + // check if the specified signer exists in the client state + assert!(self.is_active_enclave_key_and_check_operator(ctx, client_id, ek, operator)); + success += 1; + } + assert!( + success * client_state.operators_threshold_denominator + >= client_state.operators_threshold_numerator + * client_state.operators.len() as u64 + ); + } + Ok(()) } - fn contains_enclave_key( + fn is_active_enclave_key_and_check_operator( &self, ctx: &T, client_id: &ClientId, - key: Address, + ek: Address, + operator: Address, ) -> bool { - ctx.get(enclave_key_path(client_id, key).as_slice()) - .is_some() + let info = match self.get_enclave_operator_info(ctx, client_id, ek) { + Some(info) => info, + None => return false, + }; + assert!(info.operator == operator); + ctx.host_timestamp().as_unix_timestamp_secs() < info.expired_at } fn is_active_enclave_key( &self, ctx: &T, client_id: &ClientId, - key: Address, + ek: Address, ) -> bool { - let expired_at = match ctx.get(enclave_key_path(client_id, key).as_slice()) { - Some(bz) => u64::from_be_bytes(bz.as_slice().try_into().unwrap()), + let info = match self.get_enclave_operator_info(ctx, client_id, ek) { + Some(info) => info, None => return false, }; - ctx.host_timestamp().as_unix_timestamp_secs() < expired_at + ctx.host_timestamp().as_unix_timestamp_secs() < info.expired_at } - fn add_enclave_key( + fn set_enclave_operator_info( &self, ctx: &mut T, client_id: &ClientId, - key: Address, - expired_at: u64, + ek: Address, + info: EKOperatorInfo, ) { - ctx.set( - enclave_key_path(client_id, key), - expired_at.to_be_bytes().to_vec(), - ); + match self.get_enclave_operator_info(ctx, client_id, ek) { + Some(v) => { + assert!(v.expired_at == info.expired_at && v.operator == info.operator); + } + None => { + ctx.set( + enclave_key_path(client_id, ek), + serde_json::to_string(&info).unwrap().into_bytes(), + ); + } + } + } + + fn get_enclave_operator_info( + &self, + ctx: &T, + client_id: &ClientId, + ek: Address, + ) -> Option { + let info = ctx.get(enclave_key_path(client_id, ek).as_slice())?; + Some(serde_json::from_slice(info.as_slice()).unwrap()) + } +} + +pub fn compute_eip712_register_enclave_key(avr: &str) -> Vec { + // 0x1901 | DOMAIN_SEPARATOR_REGISTER_ENCLAVE_KEY | keccak256(keccak256("RegisterEnclaveKey(string avr)") | keccak256(avr)) + let type_hash = { + let mut h = Keccak::new_keccak256(); + h.update(&keccak256(b"RegisterEnclaveKey(string avr)")); + h.update(&keccak256(avr.as_bytes())); + let mut result = [0u8; 32]; + h.finalize(result.as_mut()); + result + }; + [0x19, 0x01] + .into_iter() + .chain(LCP_CLIENT_DOMAIN_SEPARATOR.into_iter()) + .chain(type_hash.into_iter()) + .collect() +} + +pub fn compute_eip712_register_enclave_key_hash(avr: &str) -> [u8; 32] { + keccak256(&compute_eip712_register_enclave_key(avr)) +} + +pub fn compute_eip712_update_operators( + client_id: ClientId, + nonce: u64, + new_operators: Vec
, + threshold_numerator: u64, + threshold_denominator: u64, +) -> Vec { + sol! { + struct EIP712UpdateOperator { + bytes32 typeHash; + bytes32 clientIdHash; + uint64 nonce; + bytes32 newOperatorsHash; + uint64 thresholdNumerator; + uint64 thresholdDenominator; + } } + // 0x1901 | DOMAIN_SEPARATOR_UPDATE_OPERATORS | keccak256(keccak256("UpdateOperators(string clientId,uint64 nonce,address[] newOperators,uint64 thresholdNumerator,uint64 thresholdDenominator)") | keccak256(client_id) | nonce | keccak256(new_operators) | threshold_numerator | threshold_denominator) + let type_hash = { + let eip712_update_operator = EIP712UpdateOperator { + typeHash: keccak256(b"UpdateOperators(string clientId,uint64 nonce,address[] newOperators,uint64 thresholdNumerator,uint64 thresholdDenominator)").into(), + clientIdHash: keccak256(client_id.as_bytes()).into(), + nonce, + newOperatorsHash: keccak256( + new_operators + .iter() + .fold(Vec::new(), |mut acc, x| { + acc.extend_from_slice([0u8; 12].as_ref()); + acc.extend_from_slice(x.0.as_ref()); + acc + }).as_ref(), + ).into(), + thresholdNumerator: threshold_numerator, + thresholdDenominator: threshold_denominator, + }; + let mut h = Keccak::new_keccak256(); + let bz = eip712_update_operator.abi_encode(); + h.update(&bz); + let mut result = [0u8; 32]; + h.finalize(result.as_mut()); + result + }; + [0x19, 0x01] + .into_iter() + .chain(LCP_CLIENT_DOMAIN_SEPARATOR.into_iter()) + .chain(type_hash.into_iter()) + .collect() +} + +pub fn compute_eip712_update_operators_hash( + client_id: ClientId, + nonce: u64, + new_operators: Vec
, + threshold_numerator: u64, + threshold_denominator: u64, +) -> [u8; 32] { + keccak256(&compute_eip712_update_operators( + client_id, + nonce, + new_operators, + threshold_numerator, + threshold_denominator, + )) } // verify_report @@ -284,7 +526,7 @@ fn verify_report( current_timestamp: Time, client_state: &ClientState, eavr: &EndorsedAttestationVerificationReport, -) -> Result<(Address, Time), Error> { +) -> Result<(ReportData, Time), Error> { // verify AVR with Intel SGX Attestation Report Signing CA // NOTE: This verification is skipped in tests because the CA is not available in the test environment #[cfg(not(test))] @@ -310,27 +552,37 @@ fn verify_report( )); } - Ok((quote.get_enclave_key_address()?, quote.attestation_time)) + let report_data = quote.report_data(); + report_data.validate()?; + Ok((report_data, quote.attestation_time)) } -fn enclave_key_path(client_id: &ClientId, key: Address) -> Vec { - format!("clients/{}/aux/enclave_keys/{}", client_id, key) +fn enclave_key_path(client_id: &ClientId, ek: Address) -> Vec { + format!("clients/{}/aux/enclave_keys/{}", client_id, ek) .as_bytes() .to_vec() } +fn keccak256(bz: &[u8]) -> [u8; 32] { + let mut keccak = Keccak::new_keccak256(); + let mut result = [0u8; 32]; + keccak.update(bz); + keccak.finalize(result.as_mut()); + result +} + #[cfg(test)] mod tests { use super::*; use crate::message::UpdateClientMessage; use alloc::rc::Rc; use alloc::sync::Arc; - use attestation_report::AttestationVerificationReport; + use attestation_report::{AttestationVerificationReport, ReportData}; use context::Context; use core::cell::RefCell; use core::str::FromStr; use core::time::Duration; - use crypto::{EnclaveKey, EnclavePublicKey}; + use crypto::{EnclaveKey, EnclavePublicKey, Signer}; use ibc::{ mock::{ client_state::MockClientState, consensus_state::MockConsensusState, header::MockHeader, @@ -339,11 +591,38 @@ mod tests { Height as ICS02Height, }; use light_client::{commitments::prove_commitment, UpdateClientResult}; - use light_client::{LightClient, LightClientResolver, MapLightClientRegistry}; + use light_client::{ClientKeeper, LightClient, LightClientResolver, MapLightClientRegistry}; use mock_lc::MockLightClient; use sgx_types::{sgx_quote_t, sgx_report_body_t}; use store::memory::MemStore; + #[test] + fn test_compute_eip712_register_enclave_key() { + let avr = "{}"; + let expected = hex!("2ab70eb55dea90c4d477a7e668812653ca37c079036e92e31d4d092bcacf61cb"); + let got = compute_eip712_register_enclave_key_hash(avr); + assert_eq!(got, expected); + } + + #[test] + fn test_compute_eip712_update_operators() { + let client_id = ClientId::from_str("lcp-client-0").unwrap(); + let nonce = 1; + let new_operators = + vec![Address::from_hex_string("0xcb96F8d6C2d543102184d679D7829b39434E4EEc").unwrap()]; + let threshold_numerator = 1; + let threshold_denominator = 1; + let expected = hex!("19017fd21c2453e80741907e7ff11fd62ae1daa34c6fc0c2eced821f1c1d3fe88a4cd9ff770a728b1198fc341496b4aca7383efe5836cf011691da8008d5232e3a24"); + let got = compute_eip712_update_operators( + client_id, + nonce, + new_operators, + threshold_numerator, + threshold_denominator, + ); + assert_eq!(got, expected); + } + #[test] fn test_client() { // ek is a signing key to prove LCP's commitments @@ -353,6 +632,10 @@ mod tests { // ibc_store is a store to keeps downstream's state let ibc_store = Rc::new(RefCell::new(MemStore::default())); + // pseudo operator key + type OperatorKey = EnclaveKey; + let op_key = OperatorKey::new().unwrap(); + let registry = build_lc_registry(); let lcp_client = LCPClient::default(); let mock_client = MockLightClient::default(); @@ -365,6 +648,7 @@ mod tests { key_expiration: Duration::from_secs(60 * 60 * 24 * 7), frozen: false, latest_height: Height::zero(), + ..Default::default() }; let initial_consensus_state = ConsensusState { state_id: Default::default(), @@ -390,9 +674,14 @@ mod tests { { let mut ctx = Context::new(registry.clone(), ibc_store.clone(), &ek); ctx.set_timestamp(Time::now()); - let header = ClientMessage::RegisterEnclaveKey(RegisterEnclaveKeyMessage( - generate_dummy_eavr(&ek.get_pubkey()), - )); + let report = generate_dummy_eavr(&ek.get_pubkey()); + let operator_signature = op_key + .sign(compute_eip712_register_enclave_key(report.avr.as_str()).as_slice()) + .unwrap(); + let header = ClientMessage::RegisterEnclaveKey(RegisterEnclaveKeyMessage { + report, + operator_signature: Some(operator_signature), + }); let res = lcp_client.update_client(&mut ctx, lcp_client_id.clone(), header); assert!(res.is_ok(), "res={:?}", res); } @@ -452,11 +741,7 @@ mod tests { ) }; - let res = prove_commitment( - ctx.get_enclave_key(), - ctx.get_enclave_key().pubkey().unwrap().as_address(), - res.message.into(), - ); + let res = prove_commitment(ctx.get_enclave_key(), res.message.into()); assert!(res.is_ok(), "res={:?}", res); ctx.store_any_client_state(upstream_client_id.clone(), client_state) @@ -470,8 +755,7 @@ mod tests { { let header = ClientMessage::UpdateClient(UpdateClientMessage { proxy_message: proof1.message().unwrap(), - signer: proof1.signer, - signature: proof1.signature, + signatures: vec![proof1.signature], }); let mut ctx = Context::new(registry.clone(), ibc_store.clone(), &ek); ctx.set_timestamp((Time::now() + Duration::from_secs(60)).unwrap()); @@ -501,11 +785,7 @@ mod tests { UpdateClientResult::Misbehaviour(data) => data, _ => unreachable!(), }; - let res = prove_commitment( - ctx.get_enclave_key(), - ctx.get_enclave_key().pubkey().unwrap().as_address(), - data.message.into(), - ); + let res = prove_commitment(ctx.get_enclave_key(), data.message.into()); assert!(res.is_ok(), "res={:?}", res); res.unwrap() }; @@ -514,8 +794,7 @@ mod tests { { let header = ClientMessage::UpdateClient(UpdateClientMessage { proxy_message: misbehaviour_proof.message().unwrap(), - signer: misbehaviour_proof.signer, - signature: misbehaviour_proof.signature, + signatures: vec![misbehaviour_proof.signature], }); let mut ctx = Context::new(registry, ibc_store, &ek); ctx.set_timestamp((Time::now() + Duration::from_secs(60)).unwrap()); @@ -534,7 +813,7 @@ mod tests { let quote = sgx_quote_t { version: 4, report_body: sgx_report_body_t { - report_data: key.as_report_data(), + report_data: ReportData::new(key.as_address(), None).into(), ..Default::default() }, ..Default::default() diff --git a/modules/lcp-client/src/client_state.rs b/modules/lcp-client/src/client_state.rs index 13b39755..e4f25837 100644 --- a/modules/lcp-client/src/client_state.rs +++ b/modules/lcp-client/src/client_state.rs @@ -1,6 +1,7 @@ use crate::errors::Error; use crate::prelude::*; use core::time::Duration; +use crypto::Address; use light_client::commitments::UpdateStateProxyMessage; use light_client::types::proto::{ ibc::{ @@ -15,12 +16,16 @@ use serde::{Deserialize, Serialize}; pub const LCP_CLIENT_STATE_TYPE_URL: &str = "/ibc.lightclients.lcp.v1.ClientState"; -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] pub struct ClientState { pub mr_enclave: Vec, pub key_expiration: Duration, pub latest_height: Height, pub frozen: bool, + pub operators: Vec
, + pub operators_nonce: u64, + pub operators_threshold_numerator: u64, + pub operators_threshold_denominator: u64, } impl ClientState { @@ -35,6 +40,20 @@ impl ClientState { self.frozen = true; self } + + pub fn with_operators( + mut self, + operators: Vec
, + nonce: u64, + threshold_numerator: u64, + threshold_denominator: u64, + ) -> Self { + self.operators = operators; + self.operators_nonce = nonce; + self.operators_threshold_numerator = threshold_numerator; + self.operators_threshold_denominator = threshold_denominator; + self + } } impl From for RawClientState { @@ -49,6 +68,10 @@ impl From for RawClientState { }), allowed_quote_statuses: Default::default(), allowed_advisory_ids: Default::default(), + operators: Default::default(), + operators_nonce: 0, + operators_threshold_numerator: 0, + operators_threshold_denominator: 0, } } } @@ -63,6 +86,14 @@ impl TryFrom for ClientState { key_expiration: Duration::from_secs(raw.key_expiration), frozen: raw.frozen, latest_height: Height::new(height.revision_number, height.revision_height), + operators: raw + .operators + .into_iter() + .map(|addr| Address::try_from(addr.as_slice())) + .collect::>()?, + operators_nonce: raw.operators_nonce, + operators_threshold_numerator: raw.operators_threshold_numerator, + operators_threshold_denominator: raw.operators_threshold_denominator, }) } } diff --git a/modules/lcp-client/src/errors.rs b/modules/lcp-client/src/errors.rs index 56f487b4..773f5245 100644 --- a/modules/lcp-client/src/errors.rs +++ b/modules/lcp-client/src/errors.rs @@ -58,7 +58,11 @@ define_error! { IbcProto [TraceError] - |_| { "IBCProto error" } + |_| { "IBCProto error" }, + + StringFromUtf8Error + [TraceError] + |_| { "FromUtf8 error" }, } } @@ -91,3 +95,9 @@ impl From for Error { Self::commitment_proof(value) } } + +impl From for Error { + fn from(value: alloc::string::FromUtf8Error) -> Self { + Self::string_from_utf8_error(value) + } +} diff --git a/modules/lcp-client/src/message.rs b/modules/lcp-client/src/message.rs index 45cb7936..0a71fc56 100644 --- a/modules/lcp-client/src/message.rs +++ b/modules/lcp-client/src/message.rs @@ -1,11 +1,13 @@ use crate::errors::Error; use crate::prelude::*; +use alloy_sol_types::{sol, SolValue}; use attestation_report::EndorsedAttestationVerificationReport; use crypto::Address; -use light_client::commitments::ProxyMessage; +use light_client::commitments::{Error as CommitmentError, EthABIEncoder, ProxyMessage}; use light_client::types::proto::ibc::lightclients::lcp::v1::{ RegisterEnclaveKeyMessage as RawRegisterEnclaveKeyMessage, UpdateClientMessage as RawUpdateClientMessage, + UpdateOperatorsMessage as RawUpdateOperatorsMessage, }; use light_client::types::{proto::protobuf::Protobuf, Any}; use serde::{Deserialize, Serialize}; @@ -13,12 +15,15 @@ use serde::{Deserialize, Serialize}; pub const LCP_REGISTER_ENCLAVE_KEY_MESSAGE_TYPE_URL: &str = "/ibc.lightclients.lcp.v1.RegisterEnclaveKeyMessage"; pub const LCP_UPDATE_CLIENT_MESSAGE_TYPE_URL: &str = "/ibc.lightclients.lcp.v1.UpdateClientMessage"; +pub const LCP_UPDATE_OPERATORS_MESSAGE_TYPE_URL: &str = + "/ibc.lightclients.lcp.v1.UpdateOperatorsMessage"; #[allow(clippy::large_enum_variant)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub enum ClientMessage { RegisterEnclaveKey(RegisterEnclaveKeyMessage), UpdateClient(UpdateClientMessage), + UpdateOperators(UpdateOperatorsMessage), } impl Protobuf for ClientMessage {} @@ -34,6 +39,9 @@ impl TryFrom for ClientMessage { LCP_UPDATE_CLIENT_MESSAGE_TYPE_URL => Ok(ClientMessage::UpdateClient( UpdateClientMessage::decode_vec(&raw.value).map_err(Error::ibc_proto)?, )), + LCP_UPDATE_OPERATORS_MESSAGE_TYPE_URL => Ok(ClientMessage::UpdateOperators( + UpdateOperatorsMessage::decode_vec(&raw.value).map_err(Error::ibc_proto)?, + )), type_url => Err(Error::unexpected_header_type(type_url.to_owned())), } } @@ -50,42 +58,51 @@ impl From for Any { LCP_UPDATE_CLIENT_MESSAGE_TYPE_URL.to_string(), h.encode_vec().unwrap(), ), + ClientMessage::UpdateOperators(h) => Any::new( + LCP_UPDATE_OPERATORS_MESSAGE_TYPE_URL.to_string(), + h.encode_vec().unwrap(), + ), } } } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] -pub struct RegisterEnclaveKeyMessage(pub EndorsedAttestationVerificationReport); +pub struct RegisterEnclaveKeyMessage { + pub report: EndorsedAttestationVerificationReport, + pub operator_signature: Option>, +} impl Protobuf for RegisterEnclaveKeyMessage {} impl TryFrom for RegisterEnclaveKeyMessage { type Error = Error; fn try_from(value: RawRegisterEnclaveKeyMessage) -> Result { - Ok(RegisterEnclaveKeyMessage( - EndorsedAttestationVerificationReport { - avr: value.report, + Ok(RegisterEnclaveKeyMessage { + report: EndorsedAttestationVerificationReport { + avr: String::from_utf8(value.report)?, signature: value.signature, signing_cert: value.signing_cert, }, - )) + operator_signature: (!value.operator_signature.is_empty()) + .then_some(value.operator_signature), + }) } } impl From for RawRegisterEnclaveKeyMessage { fn from(value: RegisterEnclaveKeyMessage) -> Self { RawRegisterEnclaveKeyMessage { - report: (&value.0.avr).try_into().unwrap(), - signature: value.0.signature, - signing_cert: value.0.signing_cert, + report: value.report.avr.into_bytes(), + signature: value.report.signature, + signing_cert: value.report.signing_cert, + operator_signature: value.operator_signature.unwrap_or_default(), } } } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct UpdateClientMessage { - pub signer: Address, - pub signature: Vec, + pub signatures: Vec>, pub proxy_message: ProxyMessage, } @@ -95,8 +112,7 @@ impl TryFrom for UpdateClientMessage { type Error = Error; fn try_from(value: RawUpdateClientMessage) -> Result { Ok(UpdateClientMessage { - signer: Address::try_from(value.signer.as_slice())?, - signature: value.signature, + signatures: value.signatures, proxy_message: ProxyMessage::from_bytes(&value.proxy_message)?, }) } @@ -106,8 +122,98 @@ impl From for RawUpdateClientMessage { fn from(value: UpdateClientMessage) -> Self { RawUpdateClientMessage { proxy_message: Into::::into(value.proxy_message).to_bytes(), - signer: value.signer.into(), - signature: value.signature, + signatures: value.signatures, + } + } +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct UpdateOperatorsMessage { + pub nonce: u64, + pub new_operators: Vec
, + pub new_operators_threshold_numerator: u64, + pub new_operators_threshold_denominator: u64, + pub signatures: Vec>, +} + +impl Protobuf for UpdateOperatorsMessage {} + +impl TryFrom for UpdateOperatorsMessage { + type Error = Error; + fn try_from(value: RawUpdateOperatorsMessage) -> Result { + Ok(UpdateOperatorsMessage { + nonce: value.nonce, + new_operators: value + .new_operators + .iter() + .map(|op| Address::try_from(op.as_slice())) + .collect::>()?, + new_operators_threshold_numerator: value.new_operators_threshold_numerator, + new_operators_threshold_denominator: value.new_operators_threshold_denominator, + signatures: value.signatures, + }) + } +} + +impl From for RawUpdateOperatorsMessage { + fn from(value: UpdateOperatorsMessage) -> Self { + RawUpdateOperatorsMessage { + nonce: value.nonce, + new_operators: value + .new_operators + .into_iter() + .map(|op| op.to_vec()) + .collect(), + new_operators_threshold_numerator: value.new_operators_threshold_numerator, + new_operators_threshold_denominator: value.new_operators_threshold_denominator, + signatures: value.signatures, + } + } +} + +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +pub struct CommitmentProofs { + pub message: Vec, + pub signatures: Vec>, +} + +impl CommitmentProofs { + pub fn message(&self) -> Result { + ProxyMessage::from_bytes(&self.message) + } +} + +sol! { + struct EthABICommitmentProofs { + bytes message; + bytes[] signatures; + } +} + +impl EthABIEncoder for CommitmentProofs { + fn ethabi_encode(self) -> Vec { + Into::::into(self).abi_encode() + } + + fn ethabi_decode(bz: &[u8]) -> Result { + Ok(EthABICommitmentProofs::abi_decode(bz, true).unwrap().into()) + } +} + +impl From for CommitmentProofs { + fn from(value: EthABICommitmentProofs) -> Self { + Self { + message: value.message, + signatures: value.signatures, + } + } +} + +impl From for EthABICommitmentProofs { + fn from(value: CommitmentProofs) -> Self { + Self { + message: value.message, + signatures: value.signatures, } } } diff --git a/modules/light-client/src/context.rs b/modules/light-client/src/context.rs index 2ce4dbbd..08dd5c72 100644 --- a/modules/light-client/src/context.rs +++ b/modules/light-client/src/context.rs @@ -103,4 +103,4 @@ pub trait ClientKeeper: ClientReader { pub trait HostClientReader: HostContext + ClientReader {} -pub trait HostClientKeeper: HostContext + ClientKeeper {} +pub trait HostClientKeeper: HostClientReader + HostContext + ClientKeeper {} diff --git a/proto/definitions/ibc/lightclients/lcp/v1/lcp.proto b/proto/definitions/ibc/lightclients/lcp/v1/lcp.proto index eeef1c7c..d9e52489 100644 --- a/proto/definitions/ibc/lightclients/lcp/v1/lcp.proto +++ b/proto/definitions/ibc/lightclients/lcp/v1/lcp.proto @@ -9,14 +9,22 @@ option (gogoproto.goproto_getters_all) = false; message UpdateClientMessage { bytes proxy_message = 1; - bytes signer = 2; - bytes signature = 3; + repeated bytes signatures = 2; } message RegisterEnclaveKeyMessage { - string report = 1; + bytes report = 1; bytes signature = 2; bytes signing_cert = 3; + bytes operator_signature = 4; +} + +message UpdateOperatorsMessage { + uint64 nonce = 1; + repeated bytes new_operators = 2; + uint64 new_operators_threshold_numerator = 3; + uint64 new_operators_threshold_denominator = 4; + repeated bytes signatures = 5; } message ClientState { @@ -28,6 +36,10 @@ message ClientState { repeated string allowed_quote_statuses = 5; // e.g. INTEL-SA-XXXXX repeated string allowed_advisory_ids = 6; + repeated bytes operators = 7; + uint64 operators_nonce = 8; + uint64 operators_threshold_numerator = 9; + uint64 operators_threshold_denominator = 10; } message ConsensusState { diff --git a/proto/definitions/lcp/service/elc/v1/tx.proto b/proto/definitions/lcp/service/elc/v1/tx.proto index 9d9bc976..7dbeb795 100644 --- a/proto/definitions/lcp/service/elc/v1/tx.proto +++ b/proto/definitions/lcp/service/elc/v1/tx.proto @@ -47,8 +47,7 @@ message MsgCreateClientResponse { option (gogoproto.goproto_getters) = false; bytes message = 1; - bytes signer = 2; - bytes signature = 3; + bytes signature = 2; } // MsgUpdateClient defines an sdk.Msg to update a IBC client state using @@ -73,8 +72,7 @@ message MsgUpdateClientResponse { option (gogoproto.goproto_getters) = false; bytes message = 1; - bytes signer = 2; - bytes signature = 3; + bytes signature = 2; } message MsgAggregateMessages { @@ -91,8 +89,7 @@ message MsgAggregateMessagesResponse { option (gogoproto.goproto_getters) = false; bytes message = 1; - bytes signer = 2; - bytes signature = 3; + bytes signature = 2; } message MsgVerifyMembership { @@ -114,8 +111,7 @@ message MsgVerifyMembershipResponse { option (gogoproto.goproto_getters) = false; bytes message = 1; - bytes signer = 2; - bytes signature = 3; + bytes signature = 2; } message MsgVerifyNonMembership { @@ -136,6 +132,5 @@ message MsgVerifyNonMembershipResponse { option (gogoproto.goproto_getters) = false; bytes message = 1; - bytes signer = 2; - bytes signature = 3; + bytes signature = 2; } diff --git a/proto/src/descriptor.bin b/proto/src/descriptor.bin index 2dc55d1d..cf8cc629 100644 Binary files a/proto/src/descriptor.bin and b/proto/src/descriptor.bin differ diff --git a/proto/src/prost/ibc.lightclients.lcp.v1.rs b/proto/src/prost/ibc.lightclients.lcp.v1.rs index 68c47087..8a24045e 100644 --- a/proto/src/prost/ibc.lightclients.lcp.v1.rs +++ b/proto/src/prost/ibc.lightclients.lcp.v1.rs @@ -3,20 +3,34 @@ pub struct UpdateClientMessage { #[prost(bytes = "vec", tag = "1")] pub proxy_message: ::prost::alloc::vec::Vec, - #[prost(bytes = "vec", tag = "2")] - pub signer: ::prost::alloc::vec::Vec, - #[prost(bytes = "vec", tag = "3")] - pub signature: ::prost::alloc::vec::Vec, + #[prost(bytes = "vec", repeated, tag = "2")] + pub signatures: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RegisterEnclaveKeyMessage { - #[prost(string, tag = "1")] - pub report: ::prost::alloc::string::String, + #[prost(bytes = "vec", tag = "1")] + pub report: ::prost::alloc::vec::Vec, #[prost(bytes = "vec", tag = "2")] pub signature: ::prost::alloc::vec::Vec, #[prost(bytes = "vec", tag = "3")] pub signing_cert: ::prost::alloc::vec::Vec, + #[prost(bytes = "vec", tag = "4")] + pub operator_signature: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct UpdateOperatorsMessage { + #[prost(uint64, tag = "1")] + pub nonce: u64, + #[prost(bytes = "vec", repeated, tag = "2")] + pub new_operators: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, + #[prost(uint64, tag = "3")] + pub new_operators_threshold_numerator: u64, + #[prost(uint64, tag = "4")] + pub new_operators_threshold_denominator: u64, + #[prost(bytes = "vec", repeated, tag = "5")] + pub signatures: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -37,6 +51,14 @@ pub struct ClientState { /// e.g. INTEL-SA-XXXXX #[prost(string, repeated, tag = "6")] pub allowed_advisory_ids: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(bytes = "vec", repeated, tag = "7")] + pub operators: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, + #[prost(uint64, tag = "8")] + pub operators_nonce: u64, + #[prost(uint64, tag = "9")] + pub operators_threshold_numerator: u64, + #[prost(uint64, tag = "10")] + pub operators_threshold_denominator: u64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/proto/src/prost/lcp.service.elc.v1.rs b/proto/src/prost/lcp.service.elc.v1.rs index a1838678..62e03d8d 100644 --- a/proto/src/prost/lcp.service.elc.v1.rs +++ b/proto/src/prost/lcp.service.elc.v1.rs @@ -294,8 +294,6 @@ pub struct MsgCreateClientResponse { #[prost(bytes = "vec", tag = "1")] pub message: ::prost::alloc::vec::Vec, #[prost(bytes = "vec", tag = "2")] - pub signer: ::prost::alloc::vec::Vec, - #[prost(bytes = "vec", tag = "3")] pub signature: ::prost::alloc::vec::Vec, } /// MsgUpdateClient defines an sdk.Msg to update a IBC client state using @@ -327,8 +325,6 @@ pub struct MsgUpdateClientResponse { #[prost(bytes = "vec", tag = "1")] pub message: ::prost::alloc::vec::Vec, #[prost(bytes = "vec", tag = "2")] - pub signer: ::prost::alloc::vec::Vec, - #[prost(bytes = "vec", tag = "3")] pub signature: ::prost::alloc::vec::Vec, } #[derive(::serde::Serialize, ::serde::Deserialize)] @@ -349,8 +345,6 @@ pub struct MsgAggregateMessagesResponse { #[prost(bytes = "vec", tag = "1")] pub message: ::prost::alloc::vec::Vec, #[prost(bytes = "vec", tag = "2")] - pub signer: ::prost::alloc::vec::Vec, - #[prost(bytes = "vec", tag = "3")] pub signature: ::prost::alloc::vec::Vec, } #[derive(::serde::Serialize, ::serde::Deserialize)] @@ -382,8 +376,6 @@ pub struct MsgVerifyMembershipResponse { #[prost(bytes = "vec", tag = "1")] pub message: ::prost::alloc::vec::Vec, #[prost(bytes = "vec", tag = "2")] - pub signer: ::prost::alloc::vec::Vec, - #[prost(bytes = "vec", tag = "3")] pub signature: ::prost::alloc::vec::Vec, } #[derive(::serde::Serialize, ::serde::Deserialize)] @@ -413,8 +405,6 @@ pub struct MsgVerifyNonMembershipResponse { #[prost(bytes = "vec", tag = "1")] pub message: ::prost::alloc::vec::Vec, #[prost(bytes = "vec", tag = "2")] - pub signer: ::prost::alloc::vec::Vec, - #[prost(bytes = "vec", tag = "3")] pub signature: ::prost::alloc::vec::Vec, } /// Generated client implementations. diff --git a/tests/integration/Cargo.toml b/tests/integration/Cargo.toml index 9ad1f73d..e87c248f 100644 --- a/tests/integration/Cargo.toml +++ b/tests/integration/Cargo.toml @@ -28,6 +28,7 @@ host = { path = "../../modules/host" } host-environment = { path = "../../modules/host-environment" } store = { path = "../../modules/store" } commitments = { path = "../../modules/commitments" } +crypto = { path = "../../modules/crypto" } ocall-handler = { path = "../../modules/ocall-handler" } enclave-api = { path = "../../modules/enclave-api" } ecall-commands = { path = "../../modules/ecall-commands" } diff --git a/tests/integration/src/lib.rs b/tests/integration/src/lib.rs index 6ddef752..c2c8627c 100644 --- a/tests/integration/src/lib.rs +++ b/tests/integration/src/lib.rs @@ -11,6 +11,7 @@ mod tests { use crate::relayer::Relayer; use anyhow::{anyhow, bail}; use commitments::UpdateStateProxyMessage; + use crypto::Address; use ecall_commands::{ AggregateMessagesInput, CommitmentProofPair, GenerateEnclaveKeyInput, InitClientInput, UpdateClientInput, VerifyMembershipInput, @@ -112,12 +113,14 @@ mod tests { bail!("failed to generate an enclave key: {:?}!", e); } }; + let operator = Address::from_hex_string("0x396e1ccc2f11cd6d2114c2449dad7751357e413e")?; #[cfg(not(feature = "sgx-sw"))] { - let _ = + let res = match enclave.ias_remote_attestation(ecall_commands::IASRemoteAttestationInput { target_enclave_key: signer, + operator: Some(operator), spid: std::env::var("SPID")?.as_bytes().to_vec(), ias_key: std::env::var("IAS_KEY")?.as_bytes().to_vec(), }) { @@ -126,14 +129,51 @@ mod tests { bail!("IAS Remote Attestation Failed {:?}!", e); } }; + let report_data = res.get_avr()?.parse_quote()?.report_data(); + assert_eq!(report_data.enclave_key(), signer); + assert_eq!(report_data.operator(), operator); + let res = + match enclave.ias_remote_attestation(ecall_commands::IASRemoteAttestationInput { + target_enclave_key: signer, + operator: None, + spid: std::env::var("SPID")?.as_bytes().to_vec(), + ias_key: std::env::var("IAS_KEY")?.as_bytes().to_vec(), + }) { + Ok(res) => res.report, + Err(e) => { + bail!("IAS Remote Attestation Failed {:?}!", e); + } + }; + let report_data = res.get_avr()?.parse_quote()?.report_data(); + assert_eq!(report_data.enclave_key(), signer); + assert!(report_data.operator().is_zero()); } #[cfg(feature = "sgx-sw")] { use enclave_api::rsa::{pkcs1v15::SigningKey, rand_core::OsRng}; use enclave_api::sha2::Sha256; - let _ = match enclave.simulate_remote_attestation( + let res = match enclave.simulate_remote_attestation( + ecall_commands::SimulateRemoteAttestationInput { + target_enclave_key: signer, + operator: Some(operator), + advisory_ids: vec![], + isv_enclave_quote_status: "OK".to_string(), + }, + SigningKey::::random(&mut OsRng, 3072)?, + Default::default(), // TODO set valid certificate + ) { + Ok(res) => res.avr, + Err(e) => { + bail!("Simulate Remote Attestation Failed {:?}!", e); + } + }; + let report_data = res.parse_quote()?.report_data(); + assert_eq!(report_data.enclave_key(), signer); + assert_eq!(report_data.operator(), operator); + let res = match enclave.simulate_remote_attestation( ecall_commands::SimulateRemoteAttestationInput { target_enclave_key: signer, + operator: None, advisory_ids: vec![], isv_enclave_quote_status: "OK".to_string(), }, @@ -145,6 +185,9 @@ mod tests { bail!("Simulate Remote Attestation Failed {:?}!", e); } }; + let report_data = res.parse_quote()?.report_data(); + assert_eq!(report_data.enclave_key(), signer); + assert!(report_data.operator().is_zero()); } let (client_id, last_height) = {