From b246ca01148bafabc36c78681df2b3800614992f Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Mon, 20 May 2024 15:53:00 +0900 Subject: [PATCH 01/11] add multiple operators support Signed-off-by: Jun Kimura --- Cargo.lock | 4 + .../src/light_client/aggregate_messages.rs | 2 +- .../src/light_client/init_client.rs | 2 +- .../src/light_client/update_client.rs | 4 +- .../src/light_client/verify_state.rs | 4 +- modules/commitments/src/message.rs | 16 +- modules/commitments/src/proof.rs | 18 +- modules/commitments/src/prover.rs | 9 +- modules/ecall-commands/src/msgs.rs | 5 - modules/lcp-client/Cargo.toml | 4 + modules/lcp-client/src/client_def.rs | 300 ++++++++++++------ modules/lcp-client/src/client_state.rs | 19 +- modules/lcp-client/src/message.rs | 79 ++++- modules/light-client/src/context.rs | 2 +- .../ibc/lightclients/lcp/v1/lcp.proto | 14 +- proto/definitions/lcp/service/elc/v1/tx.proto | 15 +- proto/src/descriptor.bin | Bin 99708 -> 100119 bytes proto/src/prost/ibc.lightclients.lcp.v1.rs | 26 +- proto/src/prost/lcp.service.elc.v1.rs | 10 - 19 files changed, 352 insertions(+), 181 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7a28dd5b..0761c037 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2689,19 +2689,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/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/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/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..b6062acb 100644 --- a/modules/lcp-client/src/client_def.rs +++ b/modules/lcp-client/src/client_def.rs @@ -1,25 +1,50 @@ use crate::client_state::ClientState; use crate::consensus_state::ConsensusState; use crate::errors::Error; -use crate::message::{ClientMessage, RegisterEnclaveKeyMessage}; +use crate::message::{ClientMessage, CommitmentProofs, RegisterEnclaveKeyMessage}; use attestation_report::EndorsedAttestationVerificationReport; 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"; +pub const DOMAIN_SEPARATOR_REGISTER_ENCLAVE_KEY: [u8; 32] = + hex!("e33d217bff42bc015bf037be8386bf5055ec6019e58e8c5e89b5c74b8225fa6a"); + /// 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 +59,20 @@ 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.len() == 0 + || client_state.operators_threshold_denominator != 0 + && client_state.operators_threshold_numerator != 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,22 +95,12 @@ 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) => { @@ -86,8 +115,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 +135,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_operator_proofs( + 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,14 +167,19 @@ 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)?; + let (ek, attestation_time) = + verify_report(ctx.host_timestamp(), &client_state, &message.report)?; - self.add_enclave_key( + let commitment = compute_eip712_register_enclave_key(&message.report.avr); + let operator = verify_signature_address(&commitment, &message.operator_signature)?; + self.set_enclave_operator_info( ctx, &client_id, - key, - (attestation_time + client_state.key_expiration)?.as_unix_timestamp_secs(), + ek, + EKOperatorInfo::new( + (attestation_time + client_state.key_expiration)?.as_unix_timestamp_secs(), + operator, + ), ); Ok(()) } @@ -160,8 +190,7 @@ impl LCPClient { client_id: ClientId, client_state: ClientState, message: MisbehaviourProxyMessage, - signer: Address, - signature: Vec, + signatures: Vec>, ) -> Result<(), Error> { message.validate()?; @@ -174,18 +203,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_operator_proofs(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 +228,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,58 +246,135 @@ 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_operator_proofs( + 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_operator_proofs( + &self, + ctx: &T, + client_id: &ClientId, + client_state: &ClientState, + sign_bytes: &[u8], + signatures: Vec>, + ) -> Result<(), Error> { + if client_state.operators.len() == 0 { + 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.len() > 0) + { + // 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(DOMAIN_SEPARATOR_REGISTER_ENCLAVE_KEY.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)) +} + // verify_report // - verifies the Attestation Verification Report // - calculate a key expiration with client_state and report's timestamp @@ -313,12 +411,20 @@ fn verify_report( Ok((quote.get_enclave_key_address()?, 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::*; @@ -330,7 +436,7 @@ mod tests { 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 +445,19 @@ 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!("8f91cceaa6275e6fbe0f8b586a24cb050b882cb8d59c4995d5143755401400d8"); + let got = compute_eip712_register_enclave_key_hash(avr); + assert_eq!(got, expected); + } + #[test] fn test_client() { // ek is a signing key to prove LCP's commitments @@ -353,6 +467,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 +483,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 +509,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, + }); let res = lcp_client.update_client(&mut ctx, lcp_client_id.clone(), header); assert!(res.is_ok(), "res={:?}", res); } @@ -452,11 +576,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 +590,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 +620,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 +629,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()); diff --git a/modules/lcp-client/src/client_state.rs b/modules/lcp-client/src/client_state.rs index 13b39755..b26021e7 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 { @@ -49,6 +54,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 +72,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/message.rs b/modules/lcp-client/src/message.rs index 45cb7936..e0c472b6 100644 --- a/modules/lcp-client/src/message.rs +++ b/modules/lcp-client/src/message.rs @@ -1,8 +1,8 @@ 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, @@ -55,37 +55,41 @@ impl From for Any { } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] -pub struct RegisterEnclaveKeyMessage(pub EndorsedAttestationVerificationReport); +pub struct RegisterEnclaveKeyMessage { + pub report: EndorsedAttestationVerificationReport, + pub operator_signature: Vec, +} impl Protobuf for RegisterEnclaveKeyMessage {} impl TryFrom for RegisterEnclaveKeyMessage { type Error = Error; fn try_from(value: RawRegisterEnclaveKeyMessage) -> Result { - Ok(RegisterEnclaveKeyMessage( - EndorsedAttestationVerificationReport { + Ok(RegisterEnclaveKeyMessage { + report: EndorsedAttestationVerificationReport { avr: value.report, signature: value.signature, signing_cert: value.signing_cert, }, - )) + operator_signature: 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).try_into().unwrap(), + signature: value.report.signature, + signing_cert: value.report.signing_cert, + operator_signature: value.operator_signature, } } } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct UpdateClientMessage { - pub signer: Address, - pub signature: Vec, + pub signatures: Vec>, pub proxy_message: ProxyMessage, } @@ -95,8 +99,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 +109,54 @@ 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(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..b7ca747a 100644 --- a/proto/definitions/ibc/lightclients/lcp/v1/lcp.proto +++ b/proto/definitions/ibc/lightclients/lcp/v1/lcp.proto @@ -9,14 +9,20 @@ 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 signature = 2; bytes signing_cert = 3; + bytes operator_signature = 4; +} + +message UpdateOperatorsMessage { + uint64 nonce = 1; + repeated bytes new_operators = 2; + repeated bytes signatures = 3; } message ClientState { @@ -28,6 +34,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 2dc55d1d2069c4e89234262a1c97ddabbb97dfd0..bee5b601b5275f30d31fc558102d5e4675aece4d 100644 GIT binary patch delta 4297 zcmYjUOKcp+5#8>Yotf-;-vMMDvABge`_utwOUpcF)lKlRGs3f07qNM$wf4(W+lj?W9 zDi>xmnV*W}%_-6Te0)pX&1PQ~UDvD0{hBBwzups{U+z?mD(eam%%w;G`S}z% zKuB0nN_j2a7BWylU;hJhuKC%6O|Ch|gAEzNxm=f8|h&hv?TBnIQ_evi(OgXF!=8FEbW z2vYLM^oBL*x8)*`^f& zLH!)707CpZP9_jk%psYE2h+T)Fip6GREiK}zM(ZmWL@|6{?zav!Q!8^R7uq7G8J#0sYijJoVfB)LD}df|L+4y<<(W z&In<8vMiNzk;@4H6^on@AjDtfasolcB9}8u0+*fs*YO>(1Lk&$KPvZPdnnEM-rd3Zh+_cH^;J=AvQJ>~Q zcPq1RDxHJZmfx?-nL{}rayfIR(gdl}WZJeSU1@?;=^Ryhm)kT3g6dsX0fhLw+^0ZL zvCDm$qq6LAp8|lkXK7UFJ?>N3K-=R!^+WeC6C3e9ybk?}R_H_Fk&Mj;$2aPaSUuT3 zVD)}Zt0ze6eNz93`xF2w9&zIUA^s!oQy{2#r2CX;FdcK70)Tc*;%R~cl95vn1l}?C zsZV|Sm@5nb-eY?l#eB?t3L9vTx!-&OC){rUsP_r?8xUwG+;1@;xpKn+q2W%r;Rxx5 zJK=`&Y0f>dYB0cnOuxZ|uur((fDrWw_nS|$Pi0%zg=)0Dwzcv>RDBQ~SL-|b)#UHT zq6s3Xbx&<5YTZ+t4{F_0*@x~t*j(RUIXv2r4vMns zj5N-_aHsE6k^JRU9LC|e3U_v+{guO={bkE2x>W%}KD8{Qq52L~=UcnAmBZ*ZlMZI1 zgM*dzC~Q~R?Vatls3^d7=M4z^RnvC#!{szkDy0!ODS}JqCbM-2r)Jii@_3nC87 zDmW+PwD$8G;jn74)@5^~ua9iDw{MY<@HN$K?<^l~AQ2loTeaowqle~si-D$q{JBx#gO|xn=ruTL zITN#@F?sJyyvaL2M$#tHz&ijC8oGgZ03bAU1MdJpXy^vs0ni`KnKm^cBAE^lcRGVhx;5jN2B*k=vRi9;upJ^FJc^hO(69u1n%?@Ddph21oK^$$Qt=7M=@ zir#^tO!4$O-^pVV05UrHse$0w8Kes`J#;6gLTq@j6=c6YO6pP+xD-H$Qs7bmk!Rkl ze?W**=iZDYI;sxiDtgE^}e9LWfSbG(R6^*Y4(w0Kpb_2WDw9%XW8HNk5E31+DO?27vqu z|DXVYSIL=01qkXZ{NZp2^vGGA0|0zDdicWu1X@q|VrnDLOXH&{#D}A2;-=nI;A%b` zJ>R;a-bGyd+ojMIh*)> z@8A30)|d3%jQae!?eAyk6QXiFeTkm!ZVJ5e;{2PlE6u8%dh)Lb3R{9K|9-Kux-#2r z#~;qoj`-_K^o?B(3UPmh3h@WCRC+4<&$D!$*5ctgI{JjXZ5&%o^j7?6o{l^rN2mJH z0{vm%2-@P4OVk#BJO5Of7PpTt(zwe0(ivnz*RmdNKU$(Cmk!1GYxE=4$G=^p(pYtn z4_t>7*`a-)E(~&k1EfOxplXtkav5b3L_mIBfXz_5%Ylx$Ag-{XSV;>KOAm;G1#xS19LF$E7QR}gQ`(BApWtbB|%s}xU)Uz((9pr zD85^!?ma`wlNAcM)>Vtd)3)qUl8%cRwo)JkLG4Mvj9yZ%!RO|r&+GtASDvgRg5Cd%# z_6VTY37yl{tE9y^SasTOF@I;m$UY;=93z?`i*_J-EDdSV3|X|pqQ^OVBoBZr#w80N z>_0Ba10joXN#5ZE&#E?|k;5no5{O;S=4unfOY4dW!Y*f9TIFz*U_!M^NMV-=m2AQV znMgO`^Cs#_$x3W8(Z zATAeVQd|IlEGDHOK-hj#TmXbDCdCCV5{&FADI^ehQz?&KFeQaV47@2Rq{}Xt78d}3 zHl5N~^t8ADG0>*P1zzY}wB9f-@UZHlUxI!p^Pp@-lzB!pLl(`U*VB*|&5%WVEP7TI zba?@g#jIQyK-hm)E({=KF^dbs&=8sv&7O)_^qgexDHc5^qJhAh6VV=vo>%4$0sw73 zJ&r}sXR9O?7Cm2EC&vLSq%^)E7ShvjLqzt1pYZsKSV(y=)}k7fEgg@Mv683w8(M7X zNn)Ta_U%m&uEWLt5q%wErT-^ihl~4$B^#W7InBm(!#+TBzk7;}csb3+1@AJl;cL!Q zIR6S?b3M_ObUiomN?I%r>#v|#?C(tIEL+!2t20=&?04#RX3);7($0+OG(+w*hpwd| z-D!s0vJ7{+EcT9oAfhZ;0Ac^K_!|gWl*Qi}Zc9a44FFmtrE#Y#l00IdRix7y?)0kI z8vwM`lm^29ioX#AZx#N|L9`#bYt}ntRQp)A=C^5SKBT=Z(tM+uA*<%ljWncHGi231 zt6mp>10aoc7%t*V4-osWi@|}A#=02XXVn{`8UVZvNgs$~q8bRa4N>j0>P@i*0BD=y zZy?Y%jlUxt&Z;-Xs6K;8j0ymwL}FAR@FFoP5U&)Y0>P+}7?qJWY6PR^wRpT4TZD9~ zv|PS10W(2WkHN$kUX?Ij)Gb0rJVN-$-MK~V*0#vRVRZb$bj^m*0Z|5lVBaFtA&)L* zL+>_O57EXP`jf@V+vIl}09)T7w5J*)O;$J3V7(H-yN`q1O5g~qSx*+tbnX#6%trzt zAwZK5Adb05FtJ3zy+?4dp(1#n&`=XZLm4JibQDmuWVxk)S__~00w%Ku1TPydR|YWQ zqOE{1vqZstKn;yE-qn@m%JQM$K}~#qoho!S{%M__Km25qwjRPCkKVf~ao+}2>|GzL z?Gu|+r1;<#-Op}i1H1>8)fIoXL=Ew;TXc%5;@vmtOl015OFK7*T-oqIuu@K(3j{0W z#JLtbH!t5S0GyjoY3$s*e6EOrmY3%U+H0(O^R@EkHGYTw?jqt3uU4)b0PI&QUjh(T z)@IF21%&--<+i~f9olu01^{Svk_HgRBn>lh*XdhBChky~Q3K<*dm^;Ml*fC9z8UWj z#Bm`ePrhQ&*hZ_(q|d7weKXqW0lG;w#lPpM6q#Wizk%5#C+e@cpwjUdi55g3LfER1 zHIFI~XoVdmT^Ge=Zk7uI0JP@+O~}m|^N=A1S~K4d7hrvjit3c^BLFUlqFfL_IIh^z zo5a8??&UAWWg0Gs;=WNC?B#+eJ~I+b>Rs_Zmf}y}rE`OdCrrB}Qy36ZE9DaD{8*Mk z^H?H=BT9XHgI#(LENqPr+{QftJ=mi)-2*p<-J0&fFJo)iEs62V*xI{Cw*j9_yVYgd zz+&2cGiK<)yhHxuFoSuAbkz*z9ln`0&0t>LQQr{sAScY*q1_q(aEH43&0O9oy#eBo P-1%lM?`&*JKGy#L`!s+s diff --git a/proto/src/prost/ibc.lightclients.lcp.v1.rs b/proto/src/prost/ibc.lightclients.lcp.v1.rs index 68c47087..0c92eb50 100644 --- a/proto/src/prost/ibc.lightclients.lcp.v1.rs +++ b/proto/src/prost/ibc.lightclients.lcp.v1.rs @@ -3,10 +3,8 @@ 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)] @@ -17,6 +15,18 @@ pub struct RegisterEnclaveKeyMessage { 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(bytes = "vec", repeated, tag = "3")] + pub signatures: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -37,6 +47,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. From d39b62f238b0badaca97b22c4d97b57807ba8742 Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Mon, 10 Jun 2024 19:02:17 +0900 Subject: [PATCH 02/11] fix report data format to support version and operator key * lcp-client: fix to check if matches operator key from report data * fix to `ias_remote_attestation` command to allow `operator` to be included in report data * fix to set operator address to report data Signed-off-by: Jun Kimura --- Cargo.lock | 1 + app/src/commands/attestation.rs | 51 ++++++++++++- .../src/enclave_manage/attestation.rs | 6 +- .../remote-attestation/src/attestation.rs | 3 +- enclave/Cargo.lock | 1 + modules/attestation-report/Cargo.toml | 1 + modules/attestation-report/src/errors.rs | 9 +++ modules/attestation-report/src/lib.rs | 4 +- modules/attestation-report/src/report.rs | 76 ++++++++++++++++--- modules/crypto/src/key.rs | 11 +-- modules/ecall-commands/src/enclave_manage.rs | 2 + modules/lcp-client/src/client_def.rs | 19 +++-- tests/integration/src/lib.rs | 1 + 13 files changed, 154 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0761c037..a9589690 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -261,6 +261,7 @@ dependencies = [ "chrono", "crypto", "flex-error", + "hex", "lcp-types", "pem", "rustls 0.19.0", diff --git a/app/src/commands/attestation.rs b/app/src/commands/attestation.rs index 2513bbde..22637c42 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,16 @@ pub struct SimulateRemoteAttestation { pub isv_enclave_quote_status: String, } +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 +229,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/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/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..d33495ca 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, "ReportData(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. @@ -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/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/lcp-client/src/client_def.rs b/modules/lcp-client/src/client_def.rs index b6062acb..ec7f0c4c 100644 --- a/modules/lcp-client/src/client_def.rs +++ b/modules/lcp-client/src/client_def.rs @@ -2,7 +2,7 @@ use crate::client_state::ClientState; use crate::consensus_state::ConsensusState; use crate::errors::Error; use crate::message::{ClientMessage, CommitmentProofs, RegisterEnclaveKeyMessage}; -use attestation_report::EndorsedAttestationVerificationReport; +use attestation_report::{EndorsedAttestationVerificationReport, ReportData}; use crypto::{verify_signature_address, Address, Keccak256}; use hex_literal::hex; use light_client::commitments::{ @@ -167,15 +167,18 @@ impl LCPClient { ) -> Result<(), Error> { // TODO return an error instead of assertion - let (ek, attestation_time) = + let (report_data, attestation_time) = verify_report(ctx.host_timestamp(), &client_state, &message.report)?; let commitment = compute_eip712_register_enclave_key(&message.report.avr); let operator = verify_signature_address(&commitment, &message.operator_signature)?; + 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, - ek, + report_data.enclave_key(), EKOperatorInfo::new( (attestation_time + client_state.key_expiration)?.as_unix_timestamp_secs(), operator, @@ -382,7 +385,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))] @@ -408,7 +411,9 @@ 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, ek: Address) -> Vec { @@ -431,7 +436,7 @@ mod tests { 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; @@ -648,7 +653,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/tests/integration/src/lib.rs b/tests/integration/src/lib.rs index 6ddef752..afcf808c 100644 --- a/tests/integration/src/lib.rs +++ b/tests/integration/src/lib.rs @@ -134,6 +134,7 @@ mod tests { let _ = match enclave.simulate_remote_attestation( ecall_commands::SimulateRemoteAttestationInput { target_enclave_key: signer, + operator: None, advisory_ids: vec![], isv_enclave_quote_status: "OK".to_string(), }, From e716e347a59936525d8eaa6b0db4e1a2d69f80b3 Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Mon, 10 Jun 2024 23:28:08 +0900 Subject: [PATCH 03/11] tests: add tests for `operator` option in `ias_remote_attestation` Signed-off-by: Jun Kimura --- Cargo.lock | 1 + tests/integration/Cargo.toml | 1 + tests/integration/src/lib.rs | 46 ++++++++++++++++++++++++++++++++++-- 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a9589690..1858d066 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2537,6 +2537,7 @@ dependencies = [ "anyhow", "attestation-report", "commitments", + "crypto", "ecall-commands", "enclave-api", "env_logger", 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 afcf808c..f448e4ce 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,29 @@ 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(), + }) { + Ok(res) => res.report, + Err(e) => { + bail!("IAS Remote Attestation Failed {:?}!", e); + } + }; + let report_data = res.report.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(), }) { @@ -126,12 +144,33 @@ mod tests { bail!("IAS Remote Attestation Failed {:?}!", e); } }; + let report_data = res.report.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, @@ -146,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) = { From 927c7948077e741404d652fe8411f329d42b6a69 Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Tue, 11 Jun 2024 11:24:03 +0900 Subject: [PATCH 04/11] add `report_data` to `list_keys`'s output Signed-off-by: Jun Kimura --- app/src/commands/enclave.rs | 2 ++ modules/attestation-report/src/report.rs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) 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/modules/attestation-report/src/report.rs b/modules/attestation-report/src/report.rs index d33495ca..e1164b20 100644 --- a/modules/attestation-report/src/report.rs +++ b/modules/attestation-report/src/report.rs @@ -55,7 +55,7 @@ impl Default for ReportData { impl Display for ReportData { fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), FmtError> { - write!(f, "ReportData(0x{})", hex::encode(&self.0)) + write!(f, "0x{}", hex::encode(&self.0)) } } From b7b6f02570e76435cc8d0dcb170bfa8de15ca261 Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Tue, 11 Jun 2024 11:36:19 +0900 Subject: [PATCH 05/11] fix compile error Signed-off-by: Jun Kimura --- app/src/commands/attestation.rs | 1 + tests/integration/src/lib.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/commands/attestation.rs b/app/src/commands/attestation.rs index 22637c42..c37a9128 100644 --- a/app/src/commands/attestation.rs +++ b/app/src/commands/attestation.rs @@ -167,6 +167,7 @@ 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 { diff --git a/tests/integration/src/lib.rs b/tests/integration/src/lib.rs index f448e4ce..c2c8627c 100644 --- a/tests/integration/src/lib.rs +++ b/tests/integration/src/lib.rs @@ -129,7 +129,7 @@ mod tests { bail!("IAS Remote Attestation Failed {:?}!", e); } }; - let report_data = res.report.get_avr()?.parse_quote()?.report_data(); + 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 = @@ -144,7 +144,7 @@ mod tests { bail!("IAS Remote Attestation Failed {:?}!", e); } }; - let report_data = res.report.get_avr()?.parse_quote()?.report_data(); + let report_data = res.get_avr()?.parse_quote()?.report_data(); assert_eq!(report_data.enclave_key(), signer); assert!(report_data.operator().is_zero()); } From 6035db826495651e725914d30883a637c6afeaaf Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Tue, 11 Jun 2024 12:36:28 +0900 Subject: [PATCH 06/11] fix lint errors Signed-off-by: Jun Kimura --- modules/attestation-report/src/report.rs | 2 +- modules/lcp-client/src/client_def.rs | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/modules/attestation-report/src/report.rs b/modules/attestation-report/src/report.rs index e1164b20..51d333c6 100644 --- a/modules/attestation-report/src/report.rs +++ b/modules/attestation-report/src/report.rs @@ -55,7 +55,7 @@ impl Default for ReportData { impl Display for ReportData { fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), FmtError> { - write!(f, "0x{}", hex::encode(&self.0)) + write!(f, "0x{}", hex::encode(self.0)) } } diff --git a/modules/lcp-client/src/client_def.rs b/modules/lcp-client/src/client_def.rs index ec7f0c4c..888489c7 100644 --- a/modules/lcp-client/src/client_def.rs +++ b/modules/lcp-client/src/client_def.rs @@ -61,7 +61,7 @@ impl LCPClient { assert!(client_state.mr_enclave.len() == 32); // operators_threshold_denominator and operators_threshold_numerator must not be 0 assert!( - client_state.operators.len() == 0 + client_state.operators.is_empty() || client_state.operators_threshold_denominator != 0 && client_state.operators_threshold_numerator != 0 ); @@ -270,7 +270,7 @@ impl LCPClient { sign_bytes: &[u8], signatures: Vec>, ) -> Result<(), Error> { - if client_state.operators.len() == 0 { + 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)); @@ -279,14 +279,12 @@ impl LCPClient { for (signature, operator) in signatures .into_iter() .zip(client_state.operators.clone().into_iter()) - .filter(|(sig, _)| sig.len() > 0) + .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) - ); + assert!(self.is_active_enclave_key_and_check_operator(ctx, client_id, ek, operator)); success += 1; } assert!( From c9ffbe23a1bdf58a07659fc84a1c536301d2cd32 Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Thu, 13 Jun 2024 20:10:57 +0900 Subject: [PATCH 07/11] add `new_operators_threshold` to `UpdateOperatorsMessage` Signed-off-by: Jun Kimura --- .../ibc/lightclients/lcp/v1/lcp.proto | 4 +++- proto/src/descriptor.bin | Bin 100119 -> 100384 bytes proto/src/prost/ibc.lightclients.lcp.v1.rs | 6 +++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/proto/definitions/ibc/lightclients/lcp/v1/lcp.proto b/proto/definitions/ibc/lightclients/lcp/v1/lcp.proto index b7ca747a..e4e98bf8 100644 --- a/proto/definitions/ibc/lightclients/lcp/v1/lcp.proto +++ b/proto/definitions/ibc/lightclients/lcp/v1/lcp.proto @@ -22,7 +22,9 @@ message RegisterEnclaveKeyMessage { message UpdateOperatorsMessage { uint64 nonce = 1; repeated bytes new_operators = 2; - repeated bytes signatures = 3; + uint64 new_operators_threshold_numerator = 3; + uint64 new_operators_threshold_denominator = 4; + repeated bytes signatures = 5; } message ClientState { diff --git a/proto/src/descriptor.bin b/proto/src/descriptor.bin index bee5b601b5275f30d31fc558102d5e4675aece4d..d1334fd84516714c1d7684dd10f69f9d19dfc98a 100644 GIT binary patch delta 1093 zcmX|1gh6oox^#``l)M7;TB*IsWhw%3U_PLu*3fCfQ8LXi*>0)eoI1=x|307YOU z1V|Jh8ngusv@|qGDJf|qBnlpZh8LjW%zDP%o;mkBJNM4b-dUgCTSwp6^+WoxczolK zJ!bTlvXf_Q+rB852fZh^vo9$NOFUw+xEA8zYIc&cz}}Me-2I!f=$y#iNV&Vc(VhL_ zXm5LG)Z5z`KD>9KHyA$Z57eO-O1am+kM9GROU2{O=j;u+->0SJi9%sL`}}~-9sfRJ zzZeU$uP@jK*3GU=u^?TMQ?e+Ch_qU)tSxHITbt}(n6j9fxM7x zqfi(+Wup)m&dqBKR*fp!V!&#;7fy_&knW-}!PWdgbL8=eJZ%xUh%BR!8QIfD0gGnJ z8iQD-Ep2g2DfEdpIjMG*D{Z4xWv7!E=|HYsmZzknN}WIdl;6E1Z{kHbVVL#o<4bny ze4WNK#ThPDFPW|d((7KV&Nx9(Kt4Y3m!V{gs`gr-& zJUh-pehs0PFQNH74+43KUOp%kCeC=Zp%9nM&dC^q7X+(Ii+&It4Ej%_{eJfIH49r^ Z-2!}7;EuNp3T$_N;jXni8txA_{{z6#Y<>U$ delta 908 zcmX|<&2G~`6ooT)#?E+BL^Dm&68|N{aniUB3pR*H;Ae+~Dj^mukSe4WL0W-S2nYc? z#0Gf_5|4nC9q-TuD1Tg)c+SokdtO|; zV)u&pF^e~UUbC0vU2G{=i&kq{e0#!ro7G$Pi?J)kt#|CpdZf0LBZx?7zDLR`2dcAe z6@%*fR&l5(R!WiJTKKIw!#5WkRleL&9VG;(mgWHsDg**?0ky3{VIaGeTEj$M(CZrx zUPytdWZ*)QR+XN&YZb6?=o^eQk4PJffJNk51uSY`vINz=; z^Tffv^}xjkxm6Nsc}HDV60>so_g`sWyR9Bn!(*4@;`@8{@W$Azl#@VH<4!#_6vD@P zZsM>NqT1~>KgtOsuD2ZqC1Qp$`afu#*5fu)}9SprL& zo;}THv^1CpY*t}w2g~)q%{;p)58N!vtK$<|ELzL@_{x1Q>, - #[prost(bytes = "vec", repeated, tag = "3")] + #[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)] From 2c441cc4617a80809fff0b737cf3382a94069e9a Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Fri, 14 Jun 2024 11:37:20 +0900 Subject: [PATCH 08/11] lcp-client: fix `operator_signature` type to `Option>` Signed-off-by: Jun Kimura --- modules/lcp-client/src/client_def.rs | 12 +++++++++--- modules/lcp-client/src/message.rs | 7 ++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/modules/lcp-client/src/client_def.rs b/modules/lcp-client/src/client_def.rs index 888489c7..64d103f9 100644 --- a/modules/lcp-client/src/client_def.rs +++ b/modules/lcp-client/src/client_def.rs @@ -170,8 +170,14 @@ impl LCPClient { let (report_data, attestation_time) = verify_report(ctx.host_timestamp(), &client_state, &message.report)?; - let commitment = compute_eip712_register_enclave_key(&message.report.avr); - let operator = verify_signature_address(&commitment, &message.operator_signature)?; + 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); @@ -518,7 +524,7 @@ mod tests { .unwrap(); let header = ClientMessage::RegisterEnclaveKey(RegisterEnclaveKeyMessage { report, - operator_signature, + operator_signature: Some(operator_signature), }); let res = lcp_client.update_client(&mut ctx, lcp_client_id.clone(), header); assert!(res.is_ok(), "res={:?}", res); diff --git a/modules/lcp-client/src/message.rs b/modules/lcp-client/src/message.rs index e0c472b6..881d0dcd 100644 --- a/modules/lcp-client/src/message.rs +++ b/modules/lcp-client/src/message.rs @@ -57,7 +57,7 @@ impl From for Any { #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct RegisterEnclaveKeyMessage { pub report: EndorsedAttestationVerificationReport, - pub operator_signature: Vec, + pub operator_signature: Option>, } impl Protobuf for RegisterEnclaveKeyMessage {} @@ -71,7 +71,8 @@ impl TryFrom for RegisterEnclaveKeyMessage { signature: value.signature, signing_cert: value.signing_cert, }, - operator_signature: value.operator_signature, + operator_signature: (!value.operator_signature.is_empty()) + .then_some(value.operator_signature), }) } } @@ -82,7 +83,7 @@ impl From for RawRegisterEnclaveKeyMessage { report: (&value.report.avr).try_into().unwrap(), signature: value.report.signature, signing_cert: value.report.signing_cert, - operator_signature: value.operator_signature, + operator_signature: value.operator_signature.unwrap_or_default(), } } } From 96ca5ca949e93b7d824ac9ffede7c28ab2998f9d Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Sun, 16 Jun 2024 20:39:30 +0900 Subject: [PATCH 09/11] fix `registerEnclaveKey`'s report type Signed-off-by: Jun Kimura --- modules/attestation-report/src/report.rs | 2 +- .../attestation-report/src/verification.rs | 2 +- modules/lcp-client/src/errors.rs | 12 +++++++++++- modules/lcp-client/src/message.rs | 4 ++-- .../ibc/lightclients/lcp/v1/lcp.proto | 2 +- proto/src/descriptor.bin | Bin 100384 -> 100384 bytes proto/src/prost/ibc.lightclients.lcp.v1.rs | 4 ++-- 7 files changed, 18 insertions(+), 8 deletions(-) diff --git a/modules/attestation-report/src/report.rs b/modules/attestation-report/src/report.rs index 51d333c6..d422fdba 100644 --- a/modules/attestation-report/src/report.rs +++ b/modules/attestation-report/src/report.rs @@ -88,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) } } 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/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 881d0dcd..4352819c 100644 --- a/modules/lcp-client/src/message.rs +++ b/modules/lcp-client/src/message.rs @@ -67,7 +67,7 @@ impl TryFrom for RegisterEnclaveKeyMessage { fn try_from(value: RawRegisterEnclaveKeyMessage) -> Result { Ok(RegisterEnclaveKeyMessage { report: EndorsedAttestationVerificationReport { - avr: value.report, + avr: String::from_utf8(value.report)?, signature: value.signature, signing_cert: value.signing_cert, }, @@ -80,7 +80,7 @@ impl TryFrom for RegisterEnclaveKeyMessage { impl From for RawRegisterEnclaveKeyMessage { fn from(value: RegisterEnclaveKeyMessage) -> Self { RawRegisterEnclaveKeyMessage { - report: (&value.report.avr).try_into().unwrap(), + report: value.report.avr.into_bytes(), signature: value.report.signature, signing_cert: value.report.signing_cert, operator_signature: value.operator_signature.unwrap_or_default(), diff --git a/proto/definitions/ibc/lightclients/lcp/v1/lcp.proto b/proto/definitions/ibc/lightclients/lcp/v1/lcp.proto index e4e98bf8..d9e52489 100644 --- a/proto/definitions/ibc/lightclients/lcp/v1/lcp.proto +++ b/proto/definitions/ibc/lightclients/lcp/v1/lcp.proto @@ -13,7 +13,7 @@ message UpdateClientMessage { } message RegisterEnclaveKeyMessage { - string report = 1; + bytes report = 1; bytes signature = 2; bytes signing_cert = 3; bytes operator_signature = 4; diff --git a/proto/src/descriptor.bin b/proto/src/descriptor.bin index d1334fd84516714c1d7684dd10f69f9d19dfc98a..cf8cc6293b7b6675c19bf344fca160c12eaf50b7 100644 GIT binary patch delta 82 zcmZ25fo;JAwuUW?p|zYmL2N~-1^GoK(, #[prost(bytes = "vec", tag = "2")] pub signature: ::prost::alloc::vec::Vec, #[prost(bytes = "vec", tag = "3")] From f139292d6c2255463b59ab927e4b177bfc2a83b8 Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Tue, 18 Jun 2024 18:12:56 +0900 Subject: [PATCH 10/11] implement `update_operators` for lcp-client Signed-off-by: Jun Kimura --- modules/lcp-client/src/client_def.rs | 140 +++++++++++++++++++++++-- modules/lcp-client/src/client_state.rs | 14 +++ modules/lcp-client/src/message.rs | 56 ++++++++++ 3 files changed, 201 insertions(+), 9 deletions(-) diff --git a/modules/lcp-client/src/client_def.rs b/modules/lcp-client/src/client_def.rs index 64d103f9..1f9e312f 100644 --- a/modules/lcp-client/src/client_def.rs +++ b/modules/lcp-client/src/client_def.rs @@ -1,7 +1,9 @@ use crate::client_state::ClientState; use crate::consensus_state::ConsensusState; use crate::errors::Error; -use crate::message::{ClientMessage, CommitmentProofs, RegisterEnclaveKeyMessage}; +use crate::message::{ + ClientMessage, CommitmentProofs, RegisterEnclaveKeyMessage, UpdateOperatorsMessage, +}; use attestation_report::{EndorsedAttestationVerificationReport, ReportData}; use crypto::{verify_signature_address, Address, Keccak256}; use hex_literal::hex; @@ -15,8 +17,18 @@ use tiny_keccak::Keccak; pub const LCP_CLIENT_TYPE: &str = "0000-lcp"; -pub const DOMAIN_SEPARATOR_REGISTER_ENCLAVE_KEY: [u8; 32] = - hex!("e33d217bff42bc015bf037be8386bf5055ec6019e58e8c5e89b5c74b8225fa6a"); +/// keccak256( +/// abi.encode( +/// keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)"), +/// keccak256("LCPClient"), +/// keccak256("1"), +/// 0, +/// address(0), +/// 0 +/// ) +/// ) +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 @@ -65,6 +77,10 @@ impl LCPClient { || 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 @@ -106,6 +122,9 @@ impl LCPClient { 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) + } } } @@ -135,7 +154,7 @@ impl LCPClient { assert!(prev_consensus_state.state_id == message.prev_state_id.unwrap()); } - self.verify_operator_proofs( + self.verify_ek_signatures( ctx, &client_id, &client_state, @@ -167,6 +186,8 @@ impl LCPClient { ) -> Result<(), Error> { // TODO return an error instead of assertion + assert!(!client_state.frozen); + let (report_data, attestation_time) = verify_report(ctx.host_timestamp(), &client_state, &message.report)?; @@ -193,6 +214,56 @@ impl LCPClient { 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(()) + } + fn submit_misbehaviour( &self, ctx: &mut dyn HostClientKeeper, @@ -215,7 +286,7 @@ impl LCPClient { // check if proxy's validation context matches our's context message.context.validate(ctx.host_timestamp())?; let sign_bytes = ProxyMessage::from(message).to_bytes(); - self.verify_operator_proofs(ctx, &client_id, &client_state, &sign_bytes, signatures)?; + 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())?; @@ -257,7 +328,7 @@ impl LCPClient { let client_state = ClientState::try_from(ctx.client_state(&client_id)?)?; - self.verify_operator_proofs( + self.verify_ek_signatures( ctx, &client_id, &client_state, @@ -268,7 +339,7 @@ impl LCPClient { Ok(()) } - fn verify_operator_proofs( + fn verify_ek_signatures( &self, ctx: &T, client_id: &ClientId, @@ -373,7 +444,7 @@ pub fn compute_eip712_register_enclave_key(avr: &str) -> Vec { }; [0x19, 0x01] .into_iter() - .chain(DOMAIN_SEPARATOR_REGISTER_ENCLAVE_KEY.into_iter()) + .chain(LCP_CLIENT_DOMAIN_SEPARATOR.into_iter()) .chain(type_hash.into_iter()) .collect() } @@ -382,6 +453,57 @@ 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 { + // 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 mut h = Keccak::new_keccak256(); + h.update(&keccak256(b"UpdateOperators(string clientId,uint64 nonce,address[] newOperators,uint64 thresholdNumerator,uint64 thresholdDenominator)")); + h.update(&keccak256(client_id.as_bytes())); + h.update(&nonce.to_be_bytes()); + h.update(&keccak256( + new_operators + .iter() + .fold(Vec::new(), |mut acc, x| { + acc.extend_from_slice(x.0.as_ref()); + acc + }) + .as_ref(), + )); + h.update(&threshold_numerator.to_be_bytes()); + h.update(&threshold_denominator.to_be_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_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 // - verifies the Attestation Verification Report // - calculate a key expiration with client_state and report's timestamp @@ -462,7 +584,7 @@ mod tests { #[test] fn test_compute_eip712_register_enclave_key() { let avr = "{}"; - let expected = hex!("8f91cceaa6275e6fbe0f8b586a24cb050b882cb8d59c4995d5143755401400d8"); + let expected = hex!("2ab70eb55dea90c4d477a7e668812653ca37c079036e92e31d4d092bcacf61cb"); let got = compute_eip712_register_enclave_key_hash(avr); assert_eq!(got, expected); } diff --git a/modules/lcp-client/src/client_state.rs b/modules/lcp-client/src/client_state.rs index b26021e7..e4f25837 100644 --- a/modules/lcp-client/src/client_state.rs +++ b/modules/lcp-client/src/client_state.rs @@ -40,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 { diff --git a/modules/lcp-client/src/message.rs b/modules/lcp-client/src/message.rs index 4352819c..0a71fc56 100644 --- a/modules/lcp-client/src/message.rs +++ b/modules/lcp-client/src/message.rs @@ -2,10 +2,12 @@ use crate::errors::Error; use crate::prelude::*; use alloy_sol_types::{sol, SolValue}; use attestation_report::EndorsedAttestationVerificationReport; +use crypto::Address; 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,6 +58,10 @@ 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(), + ), } } } @@ -115,6 +127,50 @@ impl From for RawUpdateClientMessage { } } +#[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, From 6220d898fdade47e3fbb237fd1f65834c0cd65be Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Tue, 18 Jun 2024 19:52:48 +0900 Subject: [PATCH 11/11] fix eip712 commitment calculation of update operators Signed-off-by: Jun Kimura --- modules/lcp-client/src/client_def.rs | 66 +++++++++++++++++++++------- 1 file changed, 50 insertions(+), 16 deletions(-) diff --git a/modules/lcp-client/src/client_def.rs b/modules/lcp-client/src/client_def.rs index 1f9e312f..f8384090 100644 --- a/modules/lcp-client/src/client_def.rs +++ b/modules/lcp-client/src/client_def.rs @@ -4,6 +4,7 @@ use crate::errors::Error; 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; @@ -22,9 +23,9 @@ pub const LCP_CLIENT_TYPE: &str = "0000-lcp"; /// keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)"), /// keccak256("LCPClient"), /// keccak256("1"), -/// 0, +/// uint256(0), /// address(0), -/// 0 +/// bytes32(0) // salt /// ) /// ) pub const LCP_CLIENT_DOMAIN_SEPARATOR: [u8; 32] = @@ -460,23 +461,37 @@ pub fn compute_eip712_update_operators( 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(); - h.update(&keccak256(b"UpdateOperators(string clientId,uint64 nonce,address[] newOperators,uint64 thresholdNumerator,uint64 thresholdDenominator)")); - h.update(&keccak256(client_id.as_bytes())); - h.update(&nonce.to_be_bytes()); - h.update(&keccak256( - new_operators - .iter() - .fold(Vec::new(), |mut acc, x| { - acc.extend_from_slice(x.0.as_ref()); - acc - }) - .as_ref(), - )); - h.update(&threshold_numerator.to_be_bytes()); - h.update(&threshold_denominator.to_be_bytes()); + let bz = eip712_update_operator.abi_encode(); + h.update(&bz); let mut result = [0u8; 32]; h.finalize(result.as_mut()); result @@ -589,6 +604,25 @@ mod tests { 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