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 b91112ed..fea7e19c 100644 --- a/enclave-modules/ecall-handler/src/light_client/init_client.rs +++ b/enclave-modules/ecall-handler/src/light_client/init_client.rs @@ -29,9 +29,9 @@ pub fn init_client( ctx.increase_client_counter(); let proof = if res.prove { - prove_commitment(ek, input.signer, res.commitment)? + prove_commitment(ek, input.signer, res.message)? } else { - CommitmentProof::new_with_no_signature(res.commitment.to_commitment_bytes()) + CommitmentProof::new_with_no_signature(res.message.to_bytes()) }; Ok(LightClientResult::InitClient(InitClientResult { client_id, 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 f668b771..a091a4af 100644 --- a/enclave-modules/ecall-handler/src/light_client/update_client.rs +++ b/enclave-modules/ecall-handler/src/light_client/update_client.rs @@ -1,10 +1,11 @@ use super::registry::get_light_client_by_client_id; use crate::light_client::Error; +use crate::prelude::*; use context::Context; use crypto::Signer; use ecall_commands::{LightClientResult, UpdateClientInput, UpdateClientResult}; use light_client::commitments::{ - prove_commitment, Commitment, CommitmentProof, UpdateClientCommitment, + prove_commitment, CommitmentProof, EmittedState, Message, UpdateClientMessage, }; use light_client::{ClientKeeper, LightClientResolver}; use store::KVStore; @@ -19,21 +20,24 @@ pub fn update_client( let ek = ctx.get_enclave_key(); let res = lc.update_client(ctx, input.client_id.clone(), input.any_header.into())?; - let commitment: Commitment = { - let mut commitment = UpdateClientCommitment::try_from(res.commitment)?; - if input.include_state && commitment.new_state.is_none() { - commitment.new_state = Some(res.new_any_client_state.clone()); + let message: Message = { + let mut msg = UpdateClientMessage::try_from(res.message)?; + if input.include_state && !msg.emitted_states.is_empty() { + msg.emitted_states = vec![EmittedState( + res.height, + res.new_any_consensus_state.clone(), + )]; } - commitment.into() + msg.into() }; ctx.store_any_client_state(input.client_id.clone(), res.new_any_client_state)?; ctx.store_any_consensus_state(input.client_id, res.height, res.new_any_consensus_state)?; let proof = if res.prove { - prove_commitment(ek, input.signer, commitment)? + prove_commitment(ek, input.signer, message)? } else { - CommitmentProof::new_with_no_signature(commitment.to_commitment_bytes()) + CommitmentProof::new_with_no_signature(message.to_bytes()) }; Ok(LightClientResult::UpdateClient(UpdateClientResult(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 4db7e498..aa0f0950 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(LightClientResult::VerifyMembership(VerifyMembershipResult( - prove_commitment(ek, input.signer, res.state_commitment)?, + prove_commitment(ek, input.signer, res.message)?, ))) } @@ -49,6 +49,6 @@ pub fn verify_non_membership( )?; Ok(LightClientResult::VerifyNonMembership( - VerifyNonMembershipResult(prove_commitment(ek, input.signer, res.state_commitment)?), + VerifyNonMembershipResult(prove_commitment(ek, input.signer, res.message)?), )) } diff --git a/modules/commitments/src/commitment.rs b/modules/commitments/src/commitment.rs deleted file mode 100644 index 8c666362..00000000 --- a/modules/commitments/src/commitment.rs +++ /dev/null @@ -1,645 +0,0 @@ -use crate::context::CommitmentContext; -use crate::prelude::*; -use crate::{Error, StateID}; -use core::fmt::Display; -use lcp_types::{Any, Height, Time}; -use prost::Message; -use serde::{Deserialize, Serialize}; - -pub const COMMITMENT_SCHEMA_VERSION: u16 = 1; -pub const COMMITMENT_TYPE_UPDATE_CLIENT: u16 = 1; -pub const COMMITMENT_TYPE_STATE: u16 = 2; -pub const COMMITMENT_HEADER_SIZE: usize = 32; - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum Commitment { - UpdateClient(UpdateClientCommitment), - State(StateCommitment), -} - -pub trait EthABIEncoder { - fn ethabi_encode(self) -> Vec; - fn ethabi_decode(bz: &[u8]) -> Result - where - Self: Sized; -} - -impl Display for Commitment { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Commitment::UpdateClient(c) => write!(f, "UpdateClient({})", c), - Commitment::State(c) => write!(f, "State({})", c), - } - } -} - -impl TryFrom for UpdateClientCommitment { - type Error = Error; - fn try_from(value: Commitment) -> Result { - match value { - Commitment::UpdateClient(c) => Ok(c), - _ => Err(Error::unexpected_commitment_type( - COMMITMENT_TYPE_UPDATE_CLIENT, - value.commitment_type(), - )), - } - } -} - -impl TryFrom for StateCommitment { - type Error = Error; - fn try_from(value: Commitment) -> Result { - match value { - Commitment::State(c) => Ok(c), - _ => Err(Error::unexpected_commitment_type( - COMMITMENT_TYPE_STATE, - value.commitment_type(), - )), - } - } -} - -impl Commitment { - pub fn to_commitment_bytes(self) -> Vec { - self.ethabi_encode() - } - - pub fn from_commitment_bytes(bz: &[u8]) -> Result { - Self::ethabi_decode(bz) - } - - // MSB first - // 0-1: version - // 2-3: commitment type - // 4-31: reserved - pub fn header(&self) -> [u8; COMMITMENT_HEADER_SIZE] { - let mut header = [0u8; COMMITMENT_HEADER_SIZE]; - header[0..=1].copy_from_slice(&COMMITMENT_SCHEMA_VERSION.to_be_bytes()); - header[2..=3].copy_from_slice(&self.commitment_type().to_be_bytes()); - header - } - - pub fn commitment_type(&self) -> u16 { - match self { - Commitment::UpdateClient(_) => COMMITMENT_TYPE_UPDATE_CLIENT, - Commitment::State(_) => COMMITMENT_TYPE_STATE, - } - } -} - -impl EthABIEncoder for Commitment { - fn ethabi_encode(self) -> Vec { - EthABIHeaderedCommitment { - header: self.header().as_ref().try_into().unwrap(), - commitment: match self { - Commitment::UpdateClient(c) => c.ethabi_encode(), - Commitment::State(c) => c.ethabi_encode(), - }, - } - .encode() - } - - fn ethabi_decode(bz: &[u8]) -> Result { - let eth_abi_commitment = EthABIHeaderedCommitment::decode(bz)?; - let (version, commitment_type) = { - let header = ð_abi_commitment.header; - if header.len() != COMMITMENT_HEADER_SIZE { - return Err(Error::invalid_commitment_header(format!( - "invalid header length: expected={COMMITMENT_HEADER_SIZE} actual={} header={:?}", - header.len(), - eth_abi_commitment.header - ))); - } - let mut version = [0u8; 2]; - version.copy_from_slice(&header[0..=1]); - let mut commitment_type = [0u8; 2]; - commitment_type.copy_from_slice(&header[2..=3]); - ( - u16::from_be_bytes(version), - u16::from_be_bytes(commitment_type), - ) - }; - if version != COMMITMENT_SCHEMA_VERSION { - return Err(Error::invalid_commitment_header(format!( - "invalid version: expected={} actual={} header={:?}", - COMMITMENT_SCHEMA_VERSION, version, eth_abi_commitment.header - ))); - } - let commitment = eth_abi_commitment.commitment; - match commitment_type { - COMMITMENT_TYPE_UPDATE_CLIENT => { - Ok(UpdateClientCommitment::ethabi_decode(&commitment)?.into()) - } - COMMITMENT_TYPE_STATE => Ok(StateCommitment::ethabi_decode(&commitment)?.into()), - _ => Err(Error::invalid_abi(format!( - "invalid commitment type: {}", - commitment_type - ))), - } - } -} - -// the struct is encoded as a tuple of 2 elements -pub(crate) struct EthABIHeaderedCommitment { - header: ethabi::FixedBytes, // bytes32 - commitment: ethabi::Bytes, // bytes -} - -impl EthABIHeaderedCommitment { - pub fn encode(self) -> Vec { - use ethabi::Token; - ethabi::encode(&[Token::Tuple(vec![ - Token::FixedBytes(self.header), - Token::Bytes(self.commitment), - ])]) - } - - pub fn decode(bytes: &[u8]) -> Result { - use ethabi::ParamType; - let tuple = ethabi::decode( - &[ParamType::Tuple(vec![ - ParamType::FixedBytes(32), - ParamType::Bytes, - ])], - bytes, - )? - .into_iter() - .next() - .unwrap() - .into_tuple() - .unwrap(); - - // if the decoding is successful, the length of the tuple should be 2 - assert!(tuple.len() == 2); - let mut values = tuple.into_iter(); - Ok(Self { - header: values.next().unwrap().into_fixed_bytes().unwrap(), - commitment: values.next().unwrap().into_bytes().unwrap(), - }) - } -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct UpdateClientCommitment { - pub prev_state_id: Option, - pub new_state_id: StateID, - pub new_state: Option, - pub prev_height: Option, - pub new_height: Height, - pub timestamp: Time, - pub context: CommitmentContext, -} - -impl From for Commitment { - fn from(value: UpdateClientCommitment) -> Self { - Self::UpdateClient(value) - } -} - -impl Display for UpdateClientCommitment { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!( - f, - "prev_state_id={} new_state_id={} new_state_include={} prev_height={:?} new_height={:?} timestamp={} context={{{}}}", - self.prev_state_id.map_or("".to_string(), |s| s.to_string()), self.new_state_id, self.new_state.is_some(), self.prev_height.map_or("".to_string(), |h| h.to_string()), self.new_height.to_string(), self.timestamp, self.context - ) - } -} - -// the struct is encoded as a tuple of 7 elements -pub(crate) struct EthABIUpdateClientCommitment { - prev_state_id: ethabi::FixedBytes, // bytes32 - new_state_id: ethabi::FixedBytes, // bytes32 - new_state: ethabi::Bytes, // bytes - prev_height: EthABIHeight, // (u64, u64) - new_height: EthABIHeight, // (u64, u64) - timestamp: ethabi::Uint, // u128 - context: ethabi::Bytes, // bytes -} - -// the height is encoded as a tuple of 2 elements: (u64, u64) -pub(crate) struct EthABIHeight(ethabi::Uint, ethabi::Uint); - -impl EthABIHeight { - pub fn is_zero(&self) -> bool { - self.0 == 0.into() && self.1 == 0.into() - } -} - -impl From for Height { - fn from(value: EthABIHeight) -> Self { - Height::new(value.0.as_u64(), value.1.as_u64()) - } -} - -impl From for Option { - fn from(value: EthABIHeight) -> Self { - if value.is_zero() { - None - } else { - Some(value.into()) - } - } -} - -impl From for Vec { - fn from(value: EthABIHeight) -> Self { - use ethabi::Token; - vec![Token::Uint(value.0), Token::Uint(value.1)] - } -} - -impl From for EthABIHeight { - fn from(value: Height) -> Self { - Self( - value.revision_number().into(), - value.revision_height().into(), - ) - } -} - -impl From> for EthABIHeight { - fn from(value: Option) -> Self { - value.unwrap_or_default().into() - } -} - -impl TryFrom> for EthABIHeight { - type Error = Error; - fn try_from(value: Vec) -> Result { - if value.len() != 2 { - return Err(Error::invalid_abi(format!( - "invalid height tuple length: {}", - value.len() - ))); - } - let mut values = value.into_iter(); - let revision_number = values.next().unwrap().into_uint().unwrap(); - let revision_height = values.next().unwrap().into_uint().unwrap(); - Ok(Self(revision_number, revision_height)) - } -} - -impl EthABIUpdateClientCommitment { - pub fn encode(self) -> Vec { - use ethabi::Token; - ethabi::encode(&[Token::Tuple(vec![ - Token::FixedBytes(self.prev_state_id), - Token::FixedBytes(self.new_state_id), - Token::Bytes(self.new_state), - Token::Tuple(self.prev_height.into()), - Token::Tuple(self.new_height.into()), - Token::Uint(self.timestamp), - Token::Bytes(self.context), - ])]) - } - - pub fn decode(bz: &[u8]) -> Result { - use ethabi::ParamType; - let tuple = ethabi::decode( - &[ParamType::Tuple(vec![ - ParamType::FixedBytes(32), - ParamType::FixedBytes(32), - ParamType::Bytes, - ParamType::Tuple(vec![ParamType::Uint(64), ParamType::Uint(64)]), - ParamType::Tuple(vec![ParamType::Uint(64), ParamType::Uint(64)]), - ParamType::Uint(64), - ParamType::Bytes, - ])], - bz, - )? - .into_iter() - .next() - .unwrap() - .into_tuple() - .unwrap(); - - // if the decoding is successful, the length of the tuple should be 7 - assert!(tuple.len() == 7); - let mut values = tuple.into_iter(); - Ok(Self { - prev_state_id: values.next().unwrap().into_fixed_bytes().unwrap(), - new_state_id: values.next().unwrap().into_fixed_bytes().unwrap(), - new_state: values.next().unwrap().into_bytes().unwrap(), - prev_height: values.next().unwrap().into_tuple().unwrap().try_into()?, - new_height: values.next().unwrap().into_tuple().unwrap().try_into()?, - timestamp: values.next().unwrap().into_uint().unwrap(), - context: values.next().unwrap().into_bytes().unwrap(), - }) - } -} - -impl From for EthABIUpdateClientCommitment { - fn from(value: UpdateClientCommitment) -> Self { - use ethabi::*; - Self { - prev_state_id: FixedBytes::from( - value.prev_state_id.unwrap_or_default().to_vec().as_slice(), - ), - new_state_id: FixedBytes::from(value.new_state_id.to_vec().as_slice()), - new_state: value - .new_state - .map_or(Bytes::default(), |s| s.encode_to_vec()), - prev_height: value.prev_height.into(), - new_height: value.new_height.into(), - timestamp: Uint::from(value.timestamp.as_unix_timestamp_nanos()), - context: value.context.ethabi_encode(), - } - } -} - -impl TryFrom for UpdateClientCommitment { - type Error = Error; - fn try_from(value: EthABIUpdateClientCommitment) -> Result { - Ok(Self { - prev_state_id: bytes_to_bytes32(value.prev_state_id)?.map(StateID::from), - new_state_id: value.new_state_id.as_slice().try_into()?, - new_state: if value.new_state.is_empty() { - None - } else { - Some(Any::try_from(value.new_state)?) - }, - prev_height: value.prev_height.into(), - new_height: value.new_height.into(), - timestamp: Time::from_unix_timestamp_nanos(value.timestamp.as_u128())?, - context: CommitmentContext::ethabi_decode(value.context.as_slice())?, - }) - } -} - -fn bytes_to_bytes32(bytes: Vec) -> Result, Error> { - if bytes == [0u8; 32] { - Ok(None) - } else if bytes.len() == 32 { - // SAFETY: the length of bytes is 32 - Ok(Some(bytes.as_slice().try_into().unwrap())) - } else { - Err(Error::invalid_optional_bytes_length(32, bytes.len())) - } -} - -impl EthABIEncoder for UpdateClientCommitment { - fn ethabi_encode(self) -> Vec { - Into::::into(self).encode() - } - - fn ethabi_decode(bz: &[u8]) -> Result { - EthABIUpdateClientCommitment::decode(bz).and_then(|v| v.try_into()) - } -} - -pub type CommitmentPrefix = Vec; - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct StateCommitment { - pub prefix: CommitmentPrefix, - pub path: String, - pub value: Option<[u8; 32]>, - pub height: Height, - pub state_id: StateID, -} - -impl From for Commitment { - fn from(value: StateCommitment) -> Self { - Self::State(value) - } -} - -pub(crate) struct EthABIStateCommitment { - prefix: ethabi::Bytes, // bytes - path: ethabi::Bytes, // bytes - value: ethabi::FixedBytes, // bytes32 - height: EthABIHeight, // (uint64, uint64) - state_id: ethabi::FixedBytes, // bytes32 -} - -impl EthABIStateCommitment { - pub fn encode(self) -> Vec { - use ethabi::Token; - ethabi::encode(&[Token::Tuple(vec![ - Token::Bytes(self.prefix), - Token::Bytes(self.path), - Token::FixedBytes(self.value), - Token::Tuple(self.height.into()), - Token::FixedBytes(self.state_id), - ])]) - } - - pub fn decode(bz: &[u8]) -> Result { - use ethabi::ParamType; - let tuple = ethabi::decode( - &[ParamType::Tuple(vec![ - ParamType::Bytes, - ParamType::Bytes, - ParamType::FixedBytes(32), - ParamType::Tuple(vec![ParamType::Uint(64), ParamType::Uint(64)]), - ParamType::FixedBytes(32), - ])], - bz, - )? - .into_iter() - .next() - .unwrap() - .into_tuple() - .unwrap(); - - // if the decoding is successful, the length of the tuple should be 5 - assert!(tuple.len() == 5); - let mut values = tuple.into_iter(); - Ok(Self { - prefix: values.next().unwrap().into_bytes().unwrap(), - path: values.next().unwrap().into_bytes().unwrap().to_vec(), - value: values.next().unwrap().into_fixed_bytes().unwrap(), - height: values.next().unwrap().into_tuple().unwrap().try_into()?, - state_id: values.next().unwrap().into_fixed_bytes().unwrap(), - }) - } -} - -impl From for EthABIStateCommitment { - fn from(value: StateCommitment) -> Self { - use ethabi::*; - Self { - prefix: value.prefix, - path: Bytes::from(value.path), - value: FixedBytes::from(value.value.unwrap_or_default()), - height: EthABIHeight::from(value.height), - state_id: value.state_id.to_vec(), - } - } -} - -impl TryFrom for StateCommitment { - type Error = Error; - fn try_from(value: EthABIStateCommitment) -> Result { - Ok(Self { - prefix: value.prefix, - path: String::from_utf8(value.path)?, - value: bytes_to_bytes32(value.value)?, - height: value.height.into(), - state_id: value.state_id.as_slice().try_into()?, - }) - } -} - -impl Display for StateCommitment { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!( - f, - "prefix={:?} path={} value={:?} height={} state_id={}", - self.prefix, self.path, self.value, self.height, self.state_id - ) - } -} - -impl StateCommitment { - pub fn new( - prefix: CommitmentPrefix, - path: String, - value: Option<[u8; 32]>, - height: Height, - state_id: StateID, - ) -> Self { - Self { - prefix, - path, - value, - height, - state_id, - } - } -} - -impl EthABIEncoder for StateCommitment { - fn ethabi_encode(self) -> Vec { - Into::::into(self).encode() - } - - fn ethabi_decode(bz: &[u8]) -> Result { - EthABIStateCommitment::decode(bz).and_then(|v| v.try_into()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{CommitmentProof, TrustingPeriodContext}; - use crypto::Address; - use lcp_types::{nanos_to_duration, Any, MAX_UNIX_TIMESTAMP_NANOS}; - use proptest::prelude::*; - - fn height_from_tuple(tuple: (u64, u64)) -> Height { - Height::new(tuple.0, tuple.1) - } - - fn test_update_client_commitment( - c1: UpdateClientCommitment, - proof_signer: Address, - proof_signature: Vec, - ) { - let v = c1.clone().ethabi_encode(); - let c2 = UpdateClientCommitment::ethabi_decode(&v).unwrap(); - assert_eq!(c1, c2); - - let p1 = CommitmentProof { - commitment_bytes: Commitment::from(c1).to_commitment_bytes(), - signer: proof_signer, - signature: proof_signature.to_vec(), - }; - // TODO uncomment this line when we want to generate the test data - // println!("{{\"{}\"}},", hex::encode(p1.clone().ethabi_encode())); - let p2 = CommitmentProof::ethabi_decode(&p1.clone().ethabi_encode()).unwrap(); - assert_eq!(p1, p2); - } - - proptest! { - #[test] - fn pt_update_client_commitment_with_empty_context( - prev_state_id in any::>().prop_map(|v| v.map(StateID::from)), - new_state_id in any::<[u8; 32]>().prop_map(StateID::from), - new_state in any::)>>().prop_filter("type_url length must be greater than 0", |t| t.is_none() || !t.as_ref().unwrap().0.is_empty()), - prev_height in any::>().prop_map(|v| v.map(height_from_tuple)), - new_height in any::<(u64, u64)>().prop_map(height_from_tuple), - timestamp in ..=MAX_UNIX_TIMESTAMP_NANOS, - proof_signer in any::<[u8; 20]>(), - proof_signature in any::<[u8; 65]>() - ) { - let c1 = UpdateClientCommitment { - prev_state_id, - new_state_id, - new_state: new_state.map(|(type_url, value)| { - Any::new(type_url, value) - }), - prev_height, - new_height, - timestamp: Time::from_unix_timestamp_nanos(timestamp).unwrap(), - context: Default::default(), - }; - test_update_client_commitment(c1, Address(proof_signer), proof_signature.to_vec()); - } - - #[test] - fn pt_update_client_commitment_with_trusting_period_context( - prev_state_id in any::>().prop_map(|v| v.map(StateID::from)), - new_state_id in any::<[u8; 32]>().prop_map(StateID::from), - new_state in any::)>>().prop_filter("type_url length must be greater than 0", |t| t.is_none() || !t.as_ref().unwrap().0.is_empty()), - prev_height in any::>().prop_map(|v| v.map(height_from_tuple)), - new_height in any::<(u64, u64)>().prop_map(height_from_tuple), - timestamp in ..=MAX_UNIX_TIMESTAMP_NANOS, - proof_signer in any::<[u8; 20]>(), - proof_signature in any::<[u8; 65]>(), - trusting_period in ..=MAX_UNIX_TIMESTAMP_NANOS, - clock_drift in ..=MAX_UNIX_TIMESTAMP_NANOS, - untrusted_header_timestamp in ..=MAX_UNIX_TIMESTAMP_NANOS, - trusted_state_timestamp in ..=MAX_UNIX_TIMESTAMP_NANOS - ) { - let c1 = UpdateClientCommitment { - prev_state_id, - new_state_id, - new_state: new_state.map(|(type_url, value)| { - Any::new(type_url, value) - }), - prev_height, - new_height, - timestamp: Time::from_unix_timestamp_nanos(timestamp).unwrap(), - context: TrustingPeriodContext::new( - nanos_to_duration(trusting_period).unwrap(), - nanos_to_duration(clock_drift).unwrap(), - Time::from_unix_timestamp_nanos(untrusted_header_timestamp).unwrap(), - Time::from_unix_timestamp_nanos(trusted_state_timestamp).unwrap(), - ).into(), - }; - test_update_client_commitment(c1, Address(proof_signer), proof_signature.to_vec()); - } - - #[test] - fn pt_state_commitment( - prefix in any::(), - path in any::(), - 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 = StateCommitment { - prefix, - path, - value, - height, - state_id, - }; - let v = c1.clone().ethabi_encode(); - let c2 = StateCommitment::ethabi_decode(&v).unwrap(); - assert_eq!(c1, c2); - - let p1 = CommitmentProof { - commitment_bytes: Commitment::from(c1).to_commitment_bytes(), - signer: Address(proof_signer), - signature: proof_signature.to_vec(), - }; - let p2 = CommitmentProof::ethabi_decode(&p1.clone().ethabi_encode()).unwrap(); - assert_eq!(p1, p2); - } - } -} diff --git a/modules/commitments/src/context.rs b/modules/commitments/src/context.rs index cb685956..6a4e310d 100644 --- a/modules/commitments/src/context.rs +++ b/modules/commitments/src/context.rs @@ -4,68 +4,69 @@ use core::{fmt::Display, time::Duration}; use lcp_types::{nanos_to_duration, Time}; use serde::{Deserialize, Serialize}; -pub const COMMITMENT_CONTEXT_TYPE_EMPTY: u16 = 0; -pub const COMMITMENT_CONTEXT_TYPE_WITHIN_TRUSTING_PERIOD: u16 = 1; -pub const COMMITMENT_CONTEXT_HEADER_SIZE: usize = 32; +pub const VALIDATION_CONTEXT_TYPE_EMPTY_EMPTY: u16 = 0; +pub const VALIDATION_CONTEXT_TYPE_EMPTY_WITHIN_TRUSTING_PERIOD: u16 = 1; +pub const VALIDATION_CONTEXT_HEADER_SIZE: usize = 32; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum CommitmentContext { +pub enum ValidationContext { Empty, TrustingPeriod(TrustingPeriodContext), } -impl CommitmentContext { +impl ValidationContext { pub fn validate(&self, current_timestamp: Time) -> Result<(), Error> { match self { - CommitmentContext::Empty => Ok(()), - CommitmentContext::TrustingPeriod(ctx) => ctx.validate(current_timestamp), + ValidationContext::Empty => Ok(()), + ValidationContext::TrustingPeriod(ctx) => ctx.validate(current_timestamp), } } // MSB first // 0-1: type // 2-31: reserved - pub fn header(&self) -> [u8; COMMITMENT_CONTEXT_HEADER_SIZE] { - let mut header = [0u8; COMMITMENT_CONTEXT_HEADER_SIZE]; + pub fn header(&self) -> [u8; VALIDATION_CONTEXT_HEADER_SIZE] { + let mut header = [0u8; VALIDATION_CONTEXT_HEADER_SIZE]; match self { - CommitmentContext::Empty => { - header[0..=1].copy_from_slice(&COMMITMENT_CONTEXT_TYPE_EMPTY.to_be_bytes()); + ValidationContext::Empty => { + header[0..=1].copy_from_slice(&VALIDATION_CONTEXT_TYPE_EMPTY_EMPTY.to_be_bytes()); } - CommitmentContext::TrustingPeriod(_) => { - header[0..=1] - .copy_from_slice(&COMMITMENT_CONTEXT_TYPE_WITHIN_TRUSTING_PERIOD.to_be_bytes()); + ValidationContext::TrustingPeriod(_) => { + header[0..=1].copy_from_slice( + &VALIDATION_CONTEXT_TYPE_EMPTY_WITHIN_TRUSTING_PERIOD.to_be_bytes(), + ); } } header } fn parse_context_type_from_header(header_bytes: &[u8]) -> Result { - if header_bytes.len() != COMMITMENT_CONTEXT_HEADER_SIZE { - return Err(Error::invalid_commitment_context_header(format!( - "invalid commitment context header length: expected={} actual={}", - COMMITMENT_CONTEXT_HEADER_SIZE, + if header_bytes.len() != VALIDATION_CONTEXT_HEADER_SIZE { + return Err(Error::invalid_validation_context_header(format!( + "invalid validation context header length: expected={} actual={}", + VALIDATION_CONTEXT_HEADER_SIZE, header_bytes.len() ))); } - let mut header = [0u8; COMMITMENT_CONTEXT_HEADER_SIZE]; + let mut header = [0u8; VALIDATION_CONTEXT_HEADER_SIZE]; header.copy_from_slice(header_bytes); Ok(u16::from_be_bytes([header[0], header[1]])) } } -impl EthABIEncoder for CommitmentContext { +impl EthABIEncoder for ValidationContext { fn ethabi_encode(self) -> Vec { let header = self.header().as_ref().try_into().unwrap(); match self { - CommitmentContext::Empty => EthABICommitmentContext { + ValidationContext::Empty => EthABIValidationContext { header, context_bytes: vec![], } .encode(), - CommitmentContext::TrustingPeriod(ctx) => EthABICommitmentContext { + ValidationContext::TrustingPeriod(ctx) => EthABIValidationContext { header, context_bytes: ctx.ethabi_encode(), } @@ -73,34 +74,34 @@ impl EthABIEncoder for CommitmentContext { } } fn ethabi_decode(bz: &[u8]) -> Result { - let EthABICommitmentContext { + let EthABIValidationContext { header, context_bytes, - } = EthABICommitmentContext::decode(bz)?; + } = EthABIValidationContext::decode(bz)?; - match CommitmentContext::parse_context_type_from_header(&header)? { - COMMITMENT_CONTEXT_TYPE_EMPTY => { + match ValidationContext::parse_context_type_from_header(&header)? { + VALIDATION_CONTEXT_TYPE_EMPTY_EMPTY => { assert!(context_bytes.is_empty()); - Ok(CommitmentContext::Empty) + Ok(ValidationContext::Empty) } - COMMITMENT_CONTEXT_TYPE_WITHIN_TRUSTING_PERIOD => { + VALIDATION_CONTEXT_TYPE_EMPTY_WITHIN_TRUSTING_PERIOD => { let ctx = TrustingPeriodContext::ethabi_decode(&context_bytes)?; - Ok(CommitmentContext::TrustingPeriod(ctx)) + Ok(ValidationContext::TrustingPeriod(ctx)) } - type_ => Err(Error::invalid_commitment_context_header(format!( - "unknown commitment context type: {}", + type_ => Err(Error::invalid_validation_context_header(format!( + "unknown validation context type: {}", type_ ))), } } } -pub(crate) struct EthABICommitmentContext { +pub(crate) struct EthABIValidationContext { header: ethabi::FixedBytes, // bytes32 context_bytes: ethabi::Bytes, // bytes } -impl EthABICommitmentContext { +impl EthABIValidationContext { fn encode(&self) -> Vec { use ethabi::Token; ethabi::encode(&[Token::Tuple(vec![ @@ -133,17 +134,17 @@ impl EthABICommitmentContext { } } -impl Default for CommitmentContext { +impl Default for ValidationContext { fn default() -> Self { - CommitmentContext::Empty + ValidationContext::Empty } } -impl Display for CommitmentContext { +impl Display for ValidationContext { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { - CommitmentContext::Empty => write!(f, "Empty"), - CommitmentContext::TrustingPeriod(ctx) => write!(f, "TrustingPeriod {{{}}}", ctx), + ValidationContext::Empty => write!(f, "Empty"), + ValidationContext::TrustingPeriod(ctx) => write!(f, "TrustingPeriod {{{}}}", ctx), } } } @@ -285,9 +286,9 @@ impl EthABIEncoder for TrustingPeriodContext { } } -impl From for CommitmentContext { +impl From for ValidationContext { fn from(ctx: TrustingPeriodContext) -> Self { - CommitmentContext::TrustingPeriod(ctx) + ValidationContext::TrustingPeriod(ctx) } } @@ -351,49 +352,49 @@ mod tests { untrusted_header_timestamp in ..=MAX_UNIX_TIMESTAMP_NANOS, trusted_state_timestamp in ..=MAX_UNIX_TIMESTAMP_NANOS ) { - let ctx: CommitmentContext = TrustingPeriodContext::new( + let ctx: ValidationContext = TrustingPeriodContext::new( nanos_to_duration(trusting_period).unwrap(), nanos_to_duration(clock_drift).unwrap(), Time::from_unix_timestamp_nanos(untrusted_header_timestamp).unwrap(), Time::from_unix_timestamp_nanos(trusted_state_timestamp).unwrap(), ).into(); let bz = ctx.clone().ethabi_encode(); - let ctx2 = CommitmentContext::ethabi_decode(&bz).unwrap(); + let ctx2 = ValidationContext::ethabi_decode(&bz).unwrap(); assert_eq!(ctx, ctx2); } } #[test] fn test_empty_context_serialization() { - let ctx = CommitmentContext::Empty; + let ctx = ValidationContext::Empty; let bz = ctx.clone().ethabi_encode(); - let ctx2 = CommitmentContext::ethabi_decode(&bz).unwrap(); + let ctx2 = ValidationContext::ethabi_decode(&bz).unwrap(); assert_eq!(ctx, ctx2); } #[test] fn test_trusting_period_context_serialization() { - let ctx = CommitmentContext::TrustingPeriod(TrustingPeriodContext::new( + let ctx = ValidationContext::TrustingPeriod(TrustingPeriodContext::new( Duration::new(60 * 60 * 24, 0), Duration::new(60 * 60, 0), Time::now(), Time::now(), )); let bz = ctx.clone().ethabi_encode(); - let ctx2 = CommitmentContext::ethabi_decode(&bz).unwrap(); + let ctx2 = ValidationContext::ethabi_decode(&bz).unwrap(); assert_eq!(ctx, ctx2); } #[test] fn test_context_header() { - let ctx = CommitmentContext::Empty; + let ctx = ValidationContext::Empty; let header = ctx.header(); assert_eq!( - COMMITMENT_CONTEXT_TYPE_EMPTY, - CommitmentContext::parse_context_type_from_header(&header).unwrap() + VALIDATION_CONTEXT_TYPE_EMPTY_EMPTY, + ValidationContext::parse_context_type_from_header(&header).unwrap() ); - let ctx = CommitmentContext::TrustingPeriod(TrustingPeriodContext::new( + let ctx = ValidationContext::TrustingPeriod(TrustingPeriodContext::new( Duration::new(60 * 60 * 24, 0), Duration::new(60 * 60, 0), Time::now(), @@ -401,8 +402,8 @@ mod tests { )); let header = ctx.header(); assert_eq!( - COMMITMENT_CONTEXT_TYPE_WITHIN_TRUSTING_PERIOD, - CommitmentContext::parse_context_type_from_header(&header).unwrap() + VALIDATION_CONTEXT_TYPE_EMPTY_WITHIN_TRUSTING_PERIOD, + ValidationContext::parse_context_type_from_header(&header).unwrap() ); } diff --git a/modules/commitments/src/encoder.rs b/modules/commitments/src/encoder.rs new file mode 100644 index 00000000..3911738a --- /dev/null +++ b/modules/commitments/src/encoder.rs @@ -0,0 +1,115 @@ +use crate::message::EmittedState; +use crate::prelude::*; +use crate::Error; +use lcp_types::Any; +use lcp_types::Height; +use prost::Message; + +pub trait EthABIEncoder { + fn ethabi_encode(self) -> Vec; + fn ethabi_decode(bz: &[u8]) -> Result + where + Self: Sized; +} + +/// the height is encoded as a tuple of 2 elements: (u64, u64) +pub(crate) struct EthABIHeight(ethabi::Uint, ethabi::Uint); + +impl EthABIHeight { + pub fn is_zero(&self) -> bool { + self.0 == 0.into() && self.1 == 0.into() + } +} + +impl From for Height { + fn from(value: EthABIHeight) -> Self { + Height::new(value.0.as_u64(), value.1.as_u64()) + } +} + +impl From for Option { + fn from(value: EthABIHeight) -> Self { + if value.is_zero() { + None + } else { + Some(value.into()) + } + } +} + +impl From for Vec { + fn from(value: EthABIHeight) -> Self { + use ethabi::Token; + vec![Token::Uint(value.0), Token::Uint(value.1)] + } +} + +impl From for EthABIHeight { + fn from(value: Height) -> Self { + Self( + value.revision_number().into(), + value.revision_height().into(), + ) + } +} + +impl From> for EthABIHeight { + fn from(value: Option) -> Self { + value.unwrap_or_default().into() + } +} + +impl TryFrom> for EthABIHeight { + type Error = Error; + fn try_from(value: Vec) -> Result { + if value.len() != 2 { + return Err(Error::invalid_abi(format!( + "invalid height tuple length: {}", + value.len() + ))); + } + let mut values = value.into_iter(); + let revision_number = values.next().unwrap().into_uint().unwrap(); + let revision_height = values.next().unwrap().into_uint().unwrap(); + Ok(Self(revision_number, revision_height)) + } +} + +/// the height is encoded as a tuple of 2 elements: ((u64, u64), bytes) +pub(crate) struct EthABIEmittedState(EthABIHeight, ethabi::Bytes); + +impl From for EthABIEmittedState { + fn from(value: EmittedState) -> Self { + Self(value.0.into(), value.1.encode_to_vec()) + } +} + +impl TryFrom for EmittedState { + type Error = Error; + fn try_from(value: EthABIEmittedState) -> Result { + Ok(Self(value.0.into(), Any::try_from(value.1)?)) + } +} + +impl From for Vec { + fn from(value: EthABIEmittedState) -> Self { + use ethabi::Token; + vec![Token::Tuple(value.0.into()), Token::Bytes(value.1)] + } +} + +impl TryFrom> for EthABIEmittedState { + type Error = Error; + fn try_from(value: Vec) -> Result { + if value.len() != 2 { + return Err(Error::invalid_abi(format!( + "invalid emitted state tuple length: {}", + value.len() + ))); + } + let mut values = value.into_iter(); + let height = values.next().unwrap().into_tuple().unwrap().try_into()?; + let state_id = values.next().unwrap().into_bytes().unwrap(); + Ok(Self(height, state_id)) + } +} diff --git a/modules/commitments/src/errors.rs b/modules/commitments/src/errors.rs index ba126bd4..321bb0fb 100644 --- a/modules/commitments/src/errors.rs +++ b/modules/commitments/src/errors.rs @@ -44,29 +44,29 @@ define_error! { format_args!("invalid bytes length: expected=0or{} actual={}", e.expected, e.actual) }, - UnexpectedCommitmentType + UnexpectedMessageType { expected: u16, actual: u16 } |e| { - format_args!("unexpected commitment type: expected={} actual={}", e.expected, e.actual) + format_args!("unexpected message type: expected={} actual={}", e.expected, e.actual) }, - InvalidCommitmentHeader + InvalidMessageHeader { descr: String } |e| { - format_args!("invalid commitment header: descr={}", e.descr) + format_args!("invalid message header: descr={}", e.descr) }, - InvalidCommitmentContextHeader + InvalidValidationContextHeader { descr: String } |e| { - format_args!("invalid commitment context header: descr={}", e.descr) + format_args!("invalid validation context header: descr={}", e.descr) }, OutOfTrustingPeriod diff --git a/modules/commitments/src/lib.rs b/modules/commitments/src/lib.rs index 81340b62..918cb3b6 100644 --- a/modules/commitments/src/lib.rs +++ b/modules/commitments/src/lib.rs @@ -19,18 +19,20 @@ mod prelude { pub use core::iter::FromIterator; } -pub use commitment::{ - Commitment, CommitmentPrefix, EthABIEncoder, StateCommitment, UpdateClientCommitment, -}; -pub use context::{CommitmentContext, TrustingPeriodContext}; +pub use context::{TrustingPeriodContext, ValidationContext}; +pub use encoder::EthABIEncoder; pub use errors::Error; +pub use message::{ + CommitmentPrefix, EmittedState, Message, UpdateClientMessage, VerifyMembershipMessage, +}; pub use proof::CommitmentProof; pub use prover::prove_commitment; pub use state::{gen_state_id_from_any, gen_state_id_from_bytes, StateID, STATE_ID_SIZE}; -mod commitment; mod context; +mod encoder; mod errors; +mod message; mod proof; mod prover; mod state; diff --git a/modules/commitments/src/message.rs b/modules/commitments/src/message.rs new file mode 100644 index 00000000..7ffc560d --- /dev/null +++ b/modules/commitments/src/message.rs @@ -0,0 +1,323 @@ +pub use self::update_client::{EmittedState, UpdateClientMessage}; +pub use self::verify_membership::{CommitmentPrefix, VerifyMembershipMessage}; +use crate::encoder::EthABIEncoder; +use crate::prelude::*; +use crate::Error; +use core::fmt::Display; +use serde::{Deserialize, Serialize}; +mod update_client; +mod verify_membership; + +pub const MESSAGE_SCHEMA_VERSION: u16 = 1; +pub const MESSAGE_TYPE_UPDATE_CLIENT: u16 = 1; +pub const MESSAGE_TYPE_STATE: u16 = 2; +pub const MESSAGE_HEADER_SIZE: usize = 32; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Message { + UpdateClient(UpdateClientMessage), + VerifyMembership(VerifyMembershipMessage), +} + +impl Message { + pub fn to_bytes(self) -> Vec { + self.ethabi_encode() + } + + pub fn from_bytes(bz: &[u8]) -> Result { + Self::ethabi_decode(bz) + } + + // MSB first + // 0-1: version + // 2-3: message type + // 4-31: reserved + pub fn header(&self) -> [u8; MESSAGE_HEADER_SIZE] { + let mut header = [0u8; MESSAGE_HEADER_SIZE]; + header[0..=1].copy_from_slice(&MESSAGE_SCHEMA_VERSION.to_be_bytes()); + header[2..=3].copy_from_slice(&self.message_type().to_be_bytes()); + header + } + + pub fn message_type(&self) -> u16 { + match self { + Message::UpdateClient(_) => MESSAGE_TYPE_UPDATE_CLIENT, + Message::VerifyMembership(_) => MESSAGE_TYPE_STATE, + } + } +} + +impl Display for Message { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Message::UpdateClient(c) => write!(f, "{}", c), + Message::VerifyMembership(c) => write!(f, "{}", c), + } + } +} + +impl TryFrom for UpdateClientMessage { + type Error = Error; + fn try_from(value: Message) -> Result { + match value { + Message::UpdateClient(m) => Ok(m), + _ => Err(Error::unexpected_message_type( + MESSAGE_TYPE_UPDATE_CLIENT, + value.message_type(), + )), + } + } +} + +impl TryFrom for VerifyMembershipMessage { + type Error = Error; + fn try_from(value: Message) -> Result { + match value { + Message::VerifyMembership(m) => Ok(m), + _ => Err(Error::unexpected_message_type( + MESSAGE_TYPE_STATE, + value.message_type(), + )), + } + } +} + +impl From for Message { + fn from(value: UpdateClientMessage) -> Self { + Message::UpdateClient(value) + } +} + +impl From for Message { + fn from(value: VerifyMembershipMessage) -> Self { + Message::VerifyMembership(value) + } +} + +/// the struct is encoded as a tuple of 2 elements +pub(crate) struct EthABIHeaderedMessage { + header: ethabi::FixedBytes, // bytes32 + message: ethabi::Bytes, // bytes +} + +impl EthABIHeaderedMessage { + pub fn encode(self) -> Vec { + use ethabi::Token; + ethabi::encode(&[Token::Tuple(vec![ + Token::FixedBytes(self.header), + Token::Bytes(self.message), + ])]) + } + + pub fn decode(bytes: &[u8]) -> Result { + use ethabi::ParamType; + let tuple = ethabi::decode( + &[ParamType::Tuple(vec![ + ParamType::FixedBytes(32), + ParamType::Bytes, + ])], + bytes, + )? + .into_iter() + .next() + .unwrap() + .into_tuple() + .unwrap(); + + // if the decoding is successful, the length of the tuple should be 2 + assert!(tuple.len() == 2); + let mut values = tuple.into_iter(); + Ok(Self { + header: values.next().unwrap().into_fixed_bytes().unwrap(), + message: values.next().unwrap().into_bytes().unwrap(), + }) + } +} + +impl EthABIEncoder for Message { + fn ethabi_encode(self) -> Vec { + EthABIHeaderedMessage { + header: self.header().as_ref().try_into().unwrap(), + message: match self { + Message::UpdateClient(c) => c.ethabi_encode(), + Message::VerifyMembership(c) => c.ethabi_encode(), + }, + } + .encode() + } + + fn ethabi_decode(bz: &[u8]) -> Result { + let eth_abi_message = EthABIHeaderedMessage::decode(bz)?; + let (version, message_type) = { + let header = ð_abi_message.header; + if header.len() != MESSAGE_HEADER_SIZE { + return Err(Error::invalid_message_header(format!( + "invalid header length: expected={MESSAGE_HEADER_SIZE} actual={} header={:?}", + header.len(), + eth_abi_message.header + ))); + } + let mut version = [0u8; 2]; + version.copy_from_slice(&header[0..=1]); + let mut commitment_type = [0u8; 2]; + commitment_type.copy_from_slice(&header[2..=3]); + ( + u16::from_be_bytes(version), + u16::from_be_bytes(commitment_type), + ) + }; + if version != MESSAGE_SCHEMA_VERSION { + return Err(Error::invalid_message_header(format!( + "invalid version: expected={} actual={} header={:?}", + MESSAGE_SCHEMA_VERSION, version, eth_abi_message.header + ))); + } + let message = eth_abi_message.message; + match message_type { + MESSAGE_TYPE_UPDATE_CLIENT => Ok(UpdateClientMessage::ethabi_decode(&message)?.into()), + MESSAGE_TYPE_STATE => Ok(VerifyMembershipMessage::ethabi_decode(&message)?.into()), + _ => Err(Error::invalid_abi(format!( + "invalid message type: {}", + message_type + ))), + } + } +} + +pub(crate) fn bytes_to_bytes32(bytes: Vec) -> Result, Error> { + if bytes == [0u8; 32] { + Ok(None) + } else if bytes.len() == 32 { + // SAFETY: the length of bytes is 32 + Ok(Some(bytes.as_slice().try_into().unwrap())) + } else { + Err(Error::invalid_optional_bytes_length(32, bytes.len())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + 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::*; + + fn height_from_tuple(tuple: (u64, u64)) -> Height { + Height::new(tuple.0, tuple.1) + } + + fn test_update_client_message( + c1: UpdateClientMessage, + proof_signer: Address, + proof_signature: Vec, + ) { + let v = c1.clone().ethabi_encode(); + let c2 = UpdateClientMessage::ethabi_decode(&v).unwrap(); + assert_eq!(c1, c2); + + let p1 = CommitmentProof { + message: Message::from(c1).to_bytes(), + signer: proof_signer, + signature: proof_signature.to_vec(), + }; + // TODO uncomment this line when we want to generate the test data + // println!("{{\"{}\"}},", hex::encode(p1.clone().ethabi_encode())); + let p2 = CommitmentProof::ethabi_decode(&p1.clone().ethabi_encode()).unwrap(); + assert_eq!(p1, p2); + } + + proptest! { + #[test] + fn pt_update_client_message_with_empty_context( + prev_height in any::>().prop_map(|v| v.map(height_from_tuple)), + prev_state_id in any::>().prop_map(|v| v.map(StateID::from)), + post_height in any::<(u64, u64)>().prop_map(height_from_tuple), + 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 = UpdateClientMessage { + prev_height, + prev_state_id, + post_height, + post_state_id, + emitted_states: emitted_states.into_iter().map(|(height, (type_url, value))| { + EmittedState(height_from_tuple(height), Any::new(format!("/{}", type_url), value)) + }).collect(), + timestamp: Time::from_unix_timestamp_nanos(timestamp).unwrap(), + context: Default::default(), + }; + test_update_client_message(c1, Address(proof_signer), proof_signature.to_vec()); + } + + #[test] + fn pt_update_client_message_with_trusting_period_context( + prev_height in any::>().prop_map(|v| v.map(height_from_tuple)), + prev_state_id in any::>().prop_map(|v| v.map(StateID::from)), + post_height in any::<(u64, u64)>().prop_map(height_from_tuple), + 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]>(), + trusting_period in ..=MAX_UNIX_TIMESTAMP_NANOS, + clock_drift in ..=MAX_UNIX_TIMESTAMP_NANOS, + untrusted_header_timestamp in ..=MAX_UNIX_TIMESTAMP_NANOS, + trusted_state_timestamp in ..=MAX_UNIX_TIMESTAMP_NANOS + ) { + let c1 = UpdateClientMessage { + prev_height, + prev_state_id, + post_height, + post_state_id, + emitted_states: emitted_states.into_iter().map(|(height, (type_url, value))| { + EmittedState(height_from_tuple(height), Any::new(format!("/{}", type_url), value)) + }).collect(), + timestamp: Time::from_unix_timestamp_nanos(timestamp).unwrap(), + context: TrustingPeriodContext::new( + nanos_to_duration(trusting_period).unwrap(), + nanos_to_duration(clock_drift).unwrap(), + Time::from_unix_timestamp_nanos(untrusted_header_timestamp).unwrap(), + Time::from_unix_timestamp_nanos(trusted_state_timestamp).unwrap(), + ).into(), + }; + test_update_client_message(c1, Address(proof_signer), proof_signature.to_vec()); + } + + #[test] + fn pt_verify_membership( + prefix in any::(), + path in any::(), + 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 = VerifyMembershipMessage { + prefix, + path, + value, + height, + state_id, + }; + let v = c1.clone().ethabi_encode(); + let c2 = VerifyMembershipMessage::ethabi_decode(&v).unwrap(); + assert_eq!(c1, c2); + + let p1 = CommitmentProof { + message: Message::from(c1).to_bytes(), + signer: Address(proof_signer), + signature: proof_signature.to_vec(), + }; + let p2 = CommitmentProof::ethabi_decode(&p1.clone().ethabi_encode()).unwrap(); + assert_eq!(p1, p2); + } + } +} diff --git a/modules/commitments/src/message/update_client.rs b/modules/commitments/src/message/update_client.rs new file mode 100644 index 00000000..eb8c5a25 --- /dev/null +++ b/modules/commitments/src/message/update_client.rs @@ -0,0 +1,163 @@ +use super::bytes_to_bytes32; +use crate::context::ValidationContext; +use crate::encoder::{EthABIEmittedState, EthABIEncoder, EthABIHeight}; +use crate::prelude::*; +use crate::{Error, StateID}; +use core::fmt::Display; +use lcp_types::{Any, Height, Time}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct UpdateClientMessage { + pub prev_height: Option, + pub prev_state_id: Option, + pub post_height: Height, + pub post_state_id: StateID, + pub timestamp: Time, + pub context: ValidationContext, + pub emitted_states: Vec, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct EmittedState(pub Height, pub Any); + +impl Display for UpdateClientMessage { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "UpdateClient(prev_height: {}, prev_state_id: {}, post_height: {}, post_state_id: {}, timestamp: {}, context: {}, emitted_states: {})", + self.prev_height.as_ref().map_or("None".to_string(), |h| h.to_string()), + self.prev_state_id.as_ref().map_or("None".to_string(), |id| id.to_string()), + self.post_height, + self.post_state_id, + self.timestamp, + self.context, + self.emitted_states.len(), + ) + } +} + +/// the struct is encoded as a tuple of 7 elements +pub(crate) struct EthABIUpdateClientMessage { + pub prev_height: EthABIHeight, // (u64, u64) + pub prev_state_id: ethabi::FixedBytes, // bytes32 + pub post_height: EthABIHeight, // (u64, u64) + pub post_state_id: ethabi::FixedBytes, // bytes32 + pub timestamp: ethabi::Uint, // u128 + pub context: ethabi::Bytes, // bytes + pub emitted_states: Vec, // [((u64, u64), bytes)] +} + +impl EthABIUpdateClientMessage { + pub fn encode(self) -> Vec { + use ethabi::Token; + ethabi::encode(&[Token::Tuple(vec![ + Token::Tuple(self.prev_height.into()), + Token::FixedBytes(self.prev_state_id), + Token::Tuple(self.post_height.into()), + Token::FixedBytes(self.post_state_id), + Token::Uint(self.timestamp), + Token::Bytes(self.context), + Token::Array( + self.emitted_states + .into_iter() + .map(|v| Token::Tuple(v.into())) + .collect(), + ), + ])]) + } + + pub fn decode(bz: &[u8]) -> Result { + use ethabi::ParamType; + let tuple = ethabi::decode( + &[ParamType::Tuple(vec![ + ParamType::Tuple(vec![ParamType::Uint(64), ParamType::Uint(64)]), + ParamType::FixedBytes(32), + ParamType::Tuple(vec![ParamType::Uint(64), ParamType::Uint(64)]), + ParamType::FixedBytes(32), + ParamType::Uint(64), + ParamType::Bytes, + ParamType::Array(Box::new(ParamType::Tuple(vec![ + ParamType::Tuple(vec![ParamType::Uint(64), ParamType::Uint(64)]), + ParamType::Bytes, + ]))), + ])], + bz, + )? + .into_iter() + .next() + .unwrap() + .into_tuple() + .unwrap(); + + // if the decoding is successful, the length of the tuple should be 7 + assert!(tuple.len() == 7); + let mut values = tuple.into_iter(); + Ok(Self { + prev_height: values.next().unwrap().into_tuple().unwrap().try_into()?, + prev_state_id: values.next().unwrap().into_fixed_bytes().unwrap(), + post_height: values.next().unwrap().into_tuple().unwrap().try_into()?, + post_state_id: values.next().unwrap().into_fixed_bytes().unwrap(), + timestamp: values.next().unwrap().into_uint().unwrap(), + context: values.next().unwrap().into_bytes().unwrap(), + emitted_states: values + .next() + .unwrap() + .into_array() + .unwrap() + .into_iter() + .map(|v| EthABIEmittedState::try_from(v.into_tuple().unwrap())) + .collect::, _>>()?, + }) + } +} + +impl From for EthABIUpdateClientMessage { + fn from(value: UpdateClientMessage) -> Self { + use ethabi::*; + Self { + prev_height: value.prev_height.into(), + prev_state_id: FixedBytes::from( + value.prev_state_id.unwrap_or_default().to_vec().as_slice(), + ), + post_height: value.post_height.into(), + post_state_id: FixedBytes::from(value.post_state_id.to_vec().as_slice()), + timestamp: Uint::from(value.timestamp.as_unix_timestamp_nanos()), + context: value.context.ethabi_encode(), + emitted_states: value + .emitted_states + .into_iter() + .map(EthABIEmittedState::from) + .collect(), + } + } +} + +impl TryFrom for UpdateClientMessage { + type Error = Error; + fn try_from(value: EthABIUpdateClientMessage) -> Result { + Ok(Self { + prev_height: value.prev_height.into(), + prev_state_id: bytes_to_bytes32(value.prev_state_id)?.map(StateID::from), + post_height: value.post_height.into(), + post_state_id: value.post_state_id.as_slice().try_into()?, + timestamp: Time::from_unix_timestamp_nanos(value.timestamp.as_u128())?, + context: ValidationContext::ethabi_decode(value.context.as_slice())?, + emitted_states: value + .emitted_states + .into_iter() + .map(EmittedState::try_from) + .collect::, _>>()?, + }) + } +} + +impl EthABIEncoder for UpdateClientMessage { + fn ethabi_encode(self) -> Vec { + Into::::into(self).encode() + } + + fn ethabi_decode(bz: &[u8]) -> Result { + EthABIUpdateClientMessage::decode(bz).and_then(|v| v.try_into()) + } +} diff --git a/modules/commitments/src/message/verify_membership.rs b/modules/commitments/src/message/verify_membership.rs new file mode 100644 index 00000000..0f005f3e --- /dev/null +++ b/modules/commitments/src/message/verify_membership.rs @@ -0,0 +1,137 @@ +use super::bytes_to_bytes32; +use crate::encoder::{EthABIEncoder, EthABIHeight}; +use crate::prelude::*; +use crate::{Error, StateID}; +use core::fmt::Display; +use lcp_types::Height; +use serde::{Deserialize, Serialize}; + +pub type CommitmentPrefix = Vec; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct VerifyMembershipMessage { + pub prefix: CommitmentPrefix, + pub path: String, + pub value: Option<[u8; 32]>, + pub height: Height, + pub state_id: StateID, +} + +impl Display for VerifyMembershipMessage { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "VerifyMembership(prefix: {:?}, path: {}, value: {}, height: {}, state_id: {})", + self.prefix, + self.path, + self.value.map_or("None".to_string(), hex::encode), + self.height, + self.state_id, + ) + } +} + +pub(crate) struct EthABIVerifyMembershipMessage { + prefix: ethabi::Bytes, // bytes + path: ethabi::Bytes, // bytes + value: ethabi::FixedBytes, // bytes32 + height: EthABIHeight, // (uint64, uint64) + state_id: ethabi::FixedBytes, // bytes32 +} + +impl EthABIVerifyMembershipMessage { + pub fn encode(self) -> Vec { + use ethabi::Token; + ethabi::encode(&[Token::Tuple(vec![ + Token::Bytes(self.prefix), + Token::Bytes(self.path), + Token::FixedBytes(self.value), + Token::Tuple(self.height.into()), + Token::FixedBytes(self.state_id), + ])]) + } + + pub fn decode(bz: &[u8]) -> Result { + use ethabi::ParamType; + let tuple = ethabi::decode( + &[ParamType::Tuple(vec![ + ParamType::Bytes, + ParamType::Bytes, + ParamType::FixedBytes(32), + ParamType::Tuple(vec![ParamType::Uint(64), ParamType::Uint(64)]), + ParamType::FixedBytes(32), + ])], + bz, + )? + .into_iter() + .next() + .unwrap() + .into_tuple() + .unwrap(); + + // if the decoding is successful, the length of the tuple should be 5 + assert!(tuple.len() == 5); + let mut values = tuple.into_iter(); + Ok(Self { + prefix: values.next().unwrap().into_bytes().unwrap(), + path: values.next().unwrap().into_bytes().unwrap().to_vec(), + value: values.next().unwrap().into_fixed_bytes().unwrap(), + height: values.next().unwrap().into_tuple().unwrap().try_into()?, + state_id: values.next().unwrap().into_fixed_bytes().unwrap(), + }) + } +} + +impl From for EthABIVerifyMembershipMessage { + fn from(value: VerifyMembershipMessage) -> Self { + use ethabi::*; + Self { + prefix: value.prefix, + path: Bytes::from(value.path), + value: FixedBytes::from(value.value.unwrap_or_default()), + height: EthABIHeight::from(value.height), + state_id: value.state_id.to_vec(), + } + } +} + +impl TryFrom for VerifyMembershipMessage { + type Error = Error; + fn try_from(value: EthABIVerifyMembershipMessage) -> Result { + Ok(Self { + prefix: value.prefix, + path: String::from_utf8(value.path)?, + value: bytes_to_bytes32(value.value)?, + height: value.height.into(), + state_id: value.state_id.as_slice().try_into()?, + }) + } +} + +impl VerifyMembershipMessage { + pub fn new( + prefix: CommitmentPrefix, + path: String, + value: Option<[u8; 32]>, + height: Height, + state_id: StateID, + ) -> Self { + Self { + prefix, + path, + value, + height, + state_id, + } + } +} + +impl EthABIEncoder for VerifyMembershipMessage { + fn ethabi_encode(self) -> Vec { + Into::::into(self).encode() + } + + fn ethabi_decode(bz: &[u8]) -> Result { + EthABIVerifyMembershipMessage::decode(bz).and_then(|v| v.try_into()) + } +} diff --git a/modules/commitments/src/proof.rs b/modules/commitments/src/proof.rs index 3b21f0dd..04c736d8 100644 --- a/modules/commitments/src/proof.rs +++ b/modules/commitments/src/proof.rs @@ -1,37 +1,38 @@ -use crate::{commitment::EthABIEncoder, prelude::*, Commitment, Error}; +use crate::{encoder::EthABIEncoder, prelude::*, Error, Message}; use crypto::Address; use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CommitmentProof { - pub commitment_bytes: Vec, + pub message: Vec, pub signer: Address, pub signature: Vec, } impl CommitmentProof { - pub fn new(commitment_bytes: Vec, signer: Address, signature: Vec) -> Self { + pub fn new(message: Vec, signer: Address, signature: Vec) -> Self { Self { - commitment_bytes, + message, signer, signature, } } - pub fn new_with_no_signature(commitment_bytes: Vec) -> Self { + pub fn new_with_no_signature(message: Vec) -> Self { Self { - commitment_bytes, - ..Default::default() + message, + signer: Default::default(), + signature: Default::default(), } } - pub fn commitment(&self) -> Result { - Commitment::from_commitment_bytes(&self.commitment_bytes) - } - pub fn is_proven(&self) -> bool { !self.signature.is_empty() } + + pub fn message(&self) -> Result { + Message::from_bytes(&self.message) + } } impl EthABIEncoder for CommitmentProof { @@ -45,7 +46,7 @@ impl EthABIEncoder for CommitmentProof { } pub(crate) struct EthABICommitmentProof { - pub commitment_bytes: ethabi::Bytes, + pub message: ethabi::Bytes, pub signer: ethabi::Address, pub signature: ethabi::Bytes, } @@ -53,7 +54,7 @@ pub(crate) struct EthABICommitmentProof { impl From for CommitmentProof { fn from(value: EthABICommitmentProof) -> Self { Self { - commitment_bytes: value.commitment_bytes, + message: value.message, signer: Address(value.signer.0), signature: value.signature, } @@ -63,7 +64,7 @@ impl From for CommitmentProof { impl EthABICommitmentProof { pub fn encode(self) -> Vec { ethabi::encode(&[ethabi::Token::Tuple(vec![ - ethabi::Token::Bytes(self.commitment_bytes), + ethabi::Token::Bytes(self.message), ethabi::Token::Address(self.signer), ethabi::Token::Bytes(self.signature), ])]) @@ -88,7 +89,7 @@ impl EthABICommitmentProof { assert!(tuple.len() == 3); let mut values = tuple.into_iter(); Ok(Self { - commitment_bytes: values.next().unwrap().into_bytes().unwrap(), + message: values.next().unwrap().into_bytes().unwrap(), signer: values.next().unwrap().into_address().unwrap(), signature: values.next().unwrap().into_bytes().unwrap(), }) @@ -99,7 +100,7 @@ impl From for EthABICommitmentProof { fn from(value: CommitmentProof) -> Self { use ethabi::*; Self { - commitment_bytes: value.commitment_bytes, + message: value.message, signer: Address::from(value.signer.0), signature: value.signature, } diff --git a/modules/commitments/src/prover.rs b/modules/commitments/src/prover.rs index 6ecb88a5..6108eae1 100644 --- a/modules/commitments/src/prover.rs +++ b/modules/commitments/src/prover.rs @@ -1,16 +1,16 @@ use crate::errors::Error; -use crate::{prelude::*, Commitment, CommitmentProof}; +use crate::{prelude::*, CommitmentProof, Message}; use crypto::{Address, Signer}; pub fn prove_commitment( signer: &dyn Signer, signer_address: Address, - commitment: Commitment, + message: Message, ) -> Result { - let commitment_bytes = commitment.to_commitment_bytes(); - let signature = signer.sign(&commitment_bytes).map_err(Error::crypto)?; + let message_bytes = message.to_bytes(); + let signature = signer.sign(&message_bytes).map_err(Error::crypto)?; Ok(CommitmentProof::new( - commitment_bytes, + message_bytes, signer_address, signature, )) diff --git a/modules/ecall-commands/src/msgs.rs b/modules/ecall-commands/src/msgs.rs index 8196993b..7b3a425a 100644 --- a/modules/ecall-commands/src/msgs.rs +++ b/modules/ecall-commands/src/msgs.rs @@ -103,7 +103,7 @@ impl From for MsgCreateClientResponse { fn from(res: InitClientResult) -> Self { Self { client_id: res.client_id.to_string(), - commitment: res.proof.commitment_bytes, + message: res.proof.message, signer: res.proof.signer.into(), signature: res.proof.signature, } @@ -113,7 +113,7 @@ impl From for MsgCreateClientResponse { impl From for MsgUpdateClientResponse { fn from(res: UpdateClientResult) -> Self { Self { - commitment: res.0.commitment_bytes, + message: res.0.message, signer: res.0.signer.into(), signature: res.0.signature, } @@ -123,7 +123,7 @@ impl From for MsgUpdateClientResponse { impl From for MsgVerifyMembershipResponse { fn from(res: VerifyMembershipResult) -> Self { Self { - commitment: res.0.commitment_bytes, + message: res.0.message, signer: res.0.signer.to_vec(), signature: res.0.signature, } @@ -133,7 +133,7 @@ impl From for MsgVerifyMembershipResponse { impl From for MsgVerifyNonMembershipResponse { fn from(res: VerifyNonMembershipResult) -> Self { Self { - commitment: res.0.commitment_bytes, + message: res.0.message, signer: res.0.signer.to_vec(), signature: res.0.signature, } diff --git a/modules/enclave-api/src/api/command.rs b/modules/enclave-api/src/api/command.rs index cdd25344..993b4240 100644 --- a/modules/enclave-api/src/api/command.rs +++ b/modules/enclave-api/src/api/command.rs @@ -109,7 +109,7 @@ pub trait EnclaveCommandAPI: EnclavePrimitiveAPI { } } - /// verify_membership verifies the existence of the state in the upstream chain and generates the state commitment of its result + /// verify_membership verifies the existence of the state in the upstream chain and generates a message that represents membership of value in the state fn verify_membership(&self, input: VerifyMembershipInput) -> Result { match self.execute_command( Command::LightClient(LightClientCommand::Execute( @@ -122,7 +122,7 @@ pub trait EnclaveCommandAPI: EnclavePrimitiveAPI { } } - /// verify_non_membership verifies the non-existence of the state in the upstream chain and generates the state commitment of its result + /// verify_non_membership verifies the non-existence of the state in the upstream chain and generates a message that represents non-membership of value in the state fn verify_non_membership( &self, input: VerifyNonMembershipInput, diff --git a/modules/enclave-api/src/api/proto.rs b/modules/enclave-api/src/api/proto.rs index 6187d9f0..63870a00 100644 --- a/modules/enclave-api/src/api/proto.rs +++ b/modules/enclave-api/src/api/proto.rs @@ -12,9 +12,9 @@ pub trait EnclaveProtoAPI: EnclaveCommandAPI { fn proto_create_client(&self, msg: MsgCreateClient) -> Result { let res = self.init_client(msg.try_into()?)?; info!( - "create_client: client_id={} commitment={{{}}}", + "create_client: client_id={} message={{{}}}", res.client_id, - res.proof.commitment()? + res.proof.message()? ); Ok(res.into()) } @@ -23,9 +23,9 @@ pub trait EnclaveProtoAPI: EnclaveCommandAPI { let client_id = msg.client_id.clone(); let res = self.update_client(msg.try_into()?)?; info!( - "update_client: client_id={} commitment={{{}}}", + "update_client: client_id={} message={{{}}}", client_id, - res.0.commitment()? + res.0.message()? ); Ok(res.into()) } @@ -37,9 +37,9 @@ pub trait EnclaveProtoAPI: EnclaveCommandAPI { let client_id = msg.client_id.clone(); let res = self.verify_membership(msg.try_into()?)?; info!( - "verify_membership: client_id={} commitment={{{}}}", + "verify_membership: client_id={} message={{{}}}", client_id, - res.0.commitment()? + res.0.message()? ); Ok(res.into()) } @@ -51,9 +51,9 @@ pub trait EnclaveProtoAPI: EnclaveCommandAPI { let client_id = msg.client_id.clone(); let res = self.verify_non_membership(msg.try_into()?)?; info!( - "verify_non_membership: client_id={} commitment={{{}}}", + "verify_non_membership: client_id={} message={{{}}}", client_id, - res.0.commitment()? + res.0.message()? ); Ok(res.into()) } diff --git a/modules/lcp-client/src/client_def.rs b/modules/lcp-client/src/client_def.rs index 2668e4ac..e0ff404e 100644 --- a/modules/lcp-client/src/client_def.rs +++ b/modules/lcp-client/src/client_def.rs @@ -2,12 +2,12 @@ use crate::client_state::ClientState; use crate::consensus_state::ConsensusState; use crate::errors::Error; use crate::message::{ - ClientMessage, CommitmentReader, RegisterEnclaveKeyMessage, UpdateClientMessage, + ClientMessage, ELCMessageReader, RegisterEnclaveKeyMessage, UpdateClientMessage, }; use attestation_report::EndorsedAttestationVerificationReport; use crypto::{verify_signature_address, Address, Keccak256}; use light_client::commitments::{ - CommitmentPrefix, CommitmentProof, EthABIEncoder, StateCommitment, + CommitmentPrefix, CommitmentProof, EthABIEncoder, VerifyMembershipMessage, }; use light_client::types::{ClientId, Height, Time}; use light_client::{ClientKeeper, ClientReader, HostClientKeeper, HostClientReader}; @@ -76,7 +76,7 @@ impl LCPClient { if client_state.latest_height.is_zero() { // if the client state's latest height is zero, the commitment's new_state must be non-nil - assert!(message.commitment.new_state.is_some()); + assert!(!message.elc_message.emitted_states.is_empty()); } else { // if the client state's latest height is non-zero, the commitment's prev_* must be non-nil assert!(message.prev_height().is_some() && message.prev_state_id().is_some()); @@ -92,7 +92,7 @@ impl LCPClient { // check if the `header.signer` matches the commitment prover let signer = - verify_signature_address(&message.commitment_bytes, &message.signature).unwrap(); + verify_signature_address(&message.elc_message_bytes(), &message.signature).unwrap(); assert!(message.signer() == signer); // check if proxy's validation context matches our's context @@ -146,28 +146,26 @@ impl LCPClient { // convert `proof` to CommitmentProof let commitment_proof = CommitmentProof::ethabi_decode(proof.as_slice()).unwrap(); - let commitment: StateCommitment = commitment_proof.commitment()?.try_into()?; + let msg: VerifyMembershipMessage = commitment_proof.message()?.try_into()?; // check if `.prefix` matches the counterparty connection's prefix - assert!(commitment.prefix == prefix); + assert!(msg.prefix == prefix); // check if `.path` matches expected the commitment path - assert!(commitment.path == path); + assert!(msg.path == path); // check if `.height` matches proof height - assert!(commitment.height == proof_height); + assert!(msg.height == proof_height); // check if `.value` matches expected state - assert!(commitment.value == Some(value.keccak256())); + assert!(msg.value == Some(value.keccak256())); // check if `.state_id` matches the corresponding stored consensus state's state_id let consensus_state = ConsensusState::try_from(ctx.consensus_state(&client_id, &proof_height)?)?; - assert!(consensus_state.state_id == commitment.state_id); + 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.commitment_bytes, - &commitment_proof.signature, - )?; + let signer = + verify_signature_address(&commitment_proof.message, &commitment_proof.signature)?; assert!(commitment_proof.signer == signer); // check if the specified signer is not expired and exists in the client state @@ -228,7 +226,7 @@ fn verify_report( // 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))] - attestation_report::verify_report(eavr, current_timestamp)?; + attestation_report::verify_report(current_timestamp, eavr)?; let quote = eavr.get_avr()?.parse_quote()?; @@ -388,7 +386,7 @@ mod tests { let res = prove_commitment( ctx.get_enclave_key(), ctx.get_enclave_key().pubkey().unwrap().as_address(), - res.commitment, + res.message, ); assert!(res.is_ok(), "res={:?}", res); @@ -399,11 +397,10 @@ mod tests { res.unwrap() }; - // 5. on the downstream side, updates LCP Light Client's state with the commitment from the LCP + // 5. on the downstream side, updates LCP Light Client's state with the message from the ELC { let header = ClientMessage::UpdateClient(UpdateClientMessage { - commitment: proof1.commitment().unwrap().try_into().unwrap(), - commitment_bytes: proof1.commitment_bytes, + elc_message: proof1.message().unwrap().try_into().unwrap(), signer: proof1.signer, signature: proof1.signature, }); diff --git a/modules/lcp-client/src/client_state.rs b/modules/lcp-client/src/client_state.rs index c4ad19a0..fc777b62 100644 --- a/modules/lcp-client/src/client_state.rs +++ b/modules/lcp-client/src/client_state.rs @@ -1,6 +1,6 @@ -use crate::errors::Error; -use crate::message::CommitmentReader; +use crate::message::ELCMessageReader; use crate::prelude::*; +use crate::{errors::Error, message::UpdateClientMessage}; use core::time::Duration; use light_client::types::proto::{ ibc::{ @@ -23,7 +23,7 @@ pub struct ClientState { } impl ClientState { - pub fn with_header(mut self, header: &C) -> Self { + pub fn with_header(mut self, header: &UpdateClientMessage) -> Self { if self.latest_height < header.height() { self.latest_height = header.height(); } diff --git a/modules/lcp-client/src/message.rs b/modules/lcp-client/src/message.rs index 9389ad50..7a036912 100644 --- a/modules/lcp-client/src/message.rs +++ b/modules/lcp-client/src/message.rs @@ -2,7 +2,10 @@ use crate::errors::Error; use crate::prelude::*; use attestation_report::EndorsedAttestationVerificationReport; use crypto::Address; -use light_client::commitments::{Commitment, CommitmentContext, StateID, UpdateClientCommitment}; +use light_client::commitments::{ + Message as ELCMessage, StateID, UpdateClientMessage as ELCUpdateClientMessage, + ValidationContext, +}; use light_client::types::proto::ibc::lightclients::lcp::v1::{ RegisterEnclaveKeyMessage as RawRegisterEnclaveKeyMessage, UpdateClientMessage as RawUpdateClientMessage, @@ -85,10 +88,9 @@ impl From for RawRegisterEnclaveKeyMessage { #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct UpdateClientMessage { - pub commitment_bytes: Vec, pub signer: Address, pub signature: Vec, - pub commitment: UpdateClientCommitment, + pub elc_message: ELCUpdateClientMessage, } impl Protobuf for UpdateClientMessage {} @@ -99,8 +101,7 @@ impl TryFrom for UpdateClientMessage { Ok(UpdateClientMessage { signer: Address::try_from(value.signer.as_slice())?, signature: value.signature, - commitment: Commitment::from_commitment_bytes(&value.commitment)?.try_into()?, - commitment_bytes: value.commitment, + elc_message: ELCMessage::from_bytes(&value.elc_message)?.try_into()?, }) } } @@ -108,50 +109,54 @@ impl TryFrom for UpdateClientMessage { impl From for RawUpdateClientMessage { fn from(value: UpdateClientMessage) -> Self { RawUpdateClientMessage { - commitment: Into::::into(value.commitment).to_commitment_bytes(), + elc_message: Into::::into(value.elc_message).to_bytes(), signer: value.signer.into(), signature: value.signature, } } } -impl CommitmentReader for UpdateClientMessage { +impl ELCMessageReader for UpdateClientMessage { fn signer(&self) -> Address { self.signer } - fn commitment(&self) -> &UpdateClientCommitment { - &self.commitment + fn elc_message(&self) -> &ELCUpdateClientMessage { + &self.elc_message } } -pub trait CommitmentReader { +pub trait ELCMessageReader { fn signer(&self) -> Address; - fn commitment(&self) -> &UpdateClientCommitment; + fn elc_message(&self) -> &ELCUpdateClientMessage; + + fn elc_message_bytes(&self) -> Vec { + ELCMessage::from(self.elc_message().clone()).to_bytes() + } fn height(&self) -> Height { - self.commitment().new_height + self.elc_message().post_height } fn prev_height(&self) -> Option { - self.commitment().prev_height + self.elc_message().prev_height } fn prev_state_id(&self) -> Option { - self.commitment().prev_state_id + self.elc_message().prev_state_id } fn state_id(&self) -> StateID { - self.commitment().new_state_id + self.elc_message().post_state_id } fn timestamp(&self) -> Time { - self.commitment().timestamp + self.elc_message().timestamp } - fn context(&self) -> &CommitmentContext { - &self.commitment().context + fn context(&self) -> &ValidationContext { + &self.elc_message().context } } diff --git a/modules/light-client/src/client.rs b/modules/light-client/src/client.rs index 839cc51f..446bba40 100644 --- a/modules/light-client/src/client.rs +++ b/modules/light-client/src/client.rs @@ -1,4 +1,4 @@ -use crate::commitments::{Commitment, CommitmentPrefix}; +use crate::commitments::{CommitmentPrefix, Message}; use crate::context::HostClientReader; use crate::errors::Error; use crate::prelude::*; @@ -42,7 +42,7 @@ pub trait LightClient { value: Vec, proof_height: Height, proof: Vec, - ) -> Result; + ) -> Result; /// verify_non_membership is a generic proof verification method which verifies the absence of a given path at a specified height. fn verify_non_membership( @@ -53,15 +53,15 @@ pub trait LightClient { path: String, proof_height: Height, proof: Vec, - ) -> Result; + ) -> Result; } #[derive(Clone, Debug, PartialEq)] pub struct CreateClientResult { /// height corresponding to the updated state pub height: Height, - /// commitment represents a state transition of the client - pub commitment: Commitment, + /// message represents a state transition of the client + pub message: Message, /// if true, sign the commitment with Enclave Key pub prove: bool, } @@ -74,14 +74,20 @@ pub struct UpdateClientResult { pub new_any_consensus_state: Any, /// height corresponding to the updated state pub height: Height, - /// commitment represents a state transition of the client - pub commitment: Commitment, + /// message represents a state transition of the client + pub message: Message, /// if true, sign the commitment with Enclave Key pub prove: bool, } #[derive(Clone, Debug, PartialEq)] -pub struct StateVerificationResult { - /// state commitment represents a result of the state verification - pub state_commitment: Commitment, +pub struct VerifyMembershipResult { + /// message represents a result of the state verification + pub message: Message, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct VerifyNonMembershipResult { + /// message represents a result of the state verification + pub message: Message, } diff --git a/modules/light-client/src/lib.rs b/modules/light-client/src/lib.rs index 7208a049..0b9b80d0 100644 --- a/modules/light-client/src/lib.rs +++ b/modules/light-client/src/lib.rs @@ -23,7 +23,10 @@ mod prelude { pub use commitments; pub use lcp_types as types; -pub use client::{CreateClientResult, LightClient, StateVerificationResult, UpdateClientResult}; +pub use client::{ + CreateClientResult, LightClient, UpdateClientResult, VerifyMembershipResult, + VerifyNonMembershipResult, +}; pub use context::{ClientKeeper, ClientReader, HostClientKeeper, HostClientReader, HostContext}; pub use errors::{Error, ErrorDetail, LightClientSpecificError, RegistryError}; pub use registry::{LightClientRegistry, LightClientResolver, MapLightClientRegistry}; diff --git a/modules/mock-lc/src/client.rs b/modules/mock-lc/src/client.rs index 2b850cd1..ea93a3c6 100644 --- a/modules/mock-lc/src/client.rs +++ b/modules/mock-lc/src/client.rs @@ -10,11 +10,14 @@ use ibc::core::ics02_client::error::ClientError as ICS02Error; use ibc::core::ics02_client::header::Header as Ics02Header; use ibc::mock::client_state::{client_type, MockClientState, MOCK_CLIENT_STATE_TYPE_URL}; use ibc::mock::consensus_state::MockConsensusState; -use light_client::commitments::{gen_state_id_from_any, CommitmentContext, UpdateClientCommitment}; +use light_client::commitments::{ + gen_state_id_from_any, EmittedState, UpdateClientMessage, ValidationContext, +}; use light_client::types::{Any, ClientId, Height, Time}; +use light_client::VerifyNonMembershipResult; use light_client::{ ibc::IBCContext, CreateClientResult, Error as LightClientError, HostClientReader, LightClient, - LightClientRegistry, StateVerificationResult, UpdateClientResult, + LightClientRegistry, UpdateClientResult, VerifyMembershipResult, }; #[derive(Default)] @@ -49,14 +52,14 @@ impl LightClient for MockLightClient { let timestamp: Time = consensus_state.timestamp().into(); Ok(CreateClientResult { height, - commitment: UpdateClientCommitment { - prev_state_id: None, - new_state_id: state_id, - new_state: Some(any_client_state), + message: UpdateClientMessage { prev_height: None, - new_height: height, + prev_state_id: None, + post_state_id: state_id, + post_height: height, timestamp, - context: CommitmentContext::Empty, + context: ValidationContext::Empty, + emitted_states: vec![EmittedState(height, any_client_state)], } .into(), prove: false, @@ -125,21 +128,21 @@ impl LightClient for MockLightClient { ); let prev_state_id = gen_state_id(client_state, latest_consensus_state)?; - let new_state_id = gen_state_id(new_client_state.clone(), new_consensus_state.clone())?; + let post_state_id = gen_state_id(new_client_state.clone(), new_consensus_state.clone())?; let new_any_client_state = Any::try_from(new_client_state).unwrap(); Ok(UpdateClientResult { new_any_client_state: new_any_client_state.clone(), new_any_consensus_state: Any::try_from(new_consensus_state).unwrap(), height, - commitment: UpdateClientCommitment { - prev_state_id: Some(prev_state_id), - new_state_id, - new_state: new_any_client_state.into(), + message: UpdateClientMessage { prev_height: Some(latest_height.into()), - new_height: height, + prev_state_id: Some(prev_state_id), + post_height: height, + post_state_id, timestamp: header_timestamp, - context: CommitmentContext::Empty, + context: ValidationContext::Empty, + emitted_states: vec![EmittedState(height, new_any_client_state)], } .into(), prove: true, @@ -156,7 +159,7 @@ impl LightClient for MockLightClient { value: Vec, proof_height: Height, proof: Vec, - ) -> Result { + ) -> Result { todo!() } @@ -169,7 +172,7 @@ impl LightClient for MockLightClient { path: String, proof_height: Height, proof: Vec, - ) -> Result { + ) -> Result { todo!() } } diff --git a/modules/tendermint-lc/src/client.rs b/modules/tendermint-lc/src/client.rs index 1ac65809..76504c04 100644 --- a/modules/tendermint-lc/src/client.rs +++ b/modules/tendermint-lc/src/client.rs @@ -26,13 +26,14 @@ use ibc::core::ics23_commitment::merkle::{apply_prefix, MerkleProof}; use ibc::core::ics24_host::Path; use lcp_proto::ibc::core::commitment::v1::MerkleProof as RawMerkleProof; use light_client::commitments::{ - CommitmentContext, CommitmentPrefix, StateCommitment, TrustingPeriodContext, - UpdateClientCommitment, + CommitmentPrefix, EmittedState, TrustingPeriodContext, UpdateClientMessage, ValidationContext, + VerifyMembershipMessage, }; use light_client::types::{Any, ClientId, Height, Time}; +use light_client::VerifyNonMembershipResult; use light_client::{ ibc::IBCContext, CreateClientResult, Error as LightClientError, HostClientReader, LightClient, - LightClientRegistry, StateVerificationResult, UpdateClientResult, + LightClientRegistry, UpdateClientResult, VerifyMembershipResult, }; use log::*; @@ -72,14 +73,14 @@ impl LightClient for TendermintLightClient { Ok(CreateClientResult { height, - commitment: UpdateClientCommitment { - prev_state_id: None, - new_state_id: state_id, - new_state: Some(any_client_state), + message: UpdateClientMessage { prev_height: None, - new_height: height, + prev_state_id: None, + post_height: height, + post_state_id: state_id, timestamp, - context: CommitmentContext::Empty, + context: ValidationContext::Empty, + emitted_states: vec![EmittedState(height, any_client_state)], } .into(), prove: false, @@ -182,7 +183,7 @@ impl LightClient for TendermintLightClient { let prev_state_id = gen_state_id(canonicalize_state(&client_state), trusted_consensus_state)?; - let new_state_id = gen_state_id( + let post_state_id = gen_state_id( canonicalize_state(&new_client_state), new_consensus_state.clone(), )?; @@ -190,12 +191,11 @@ impl LightClient for TendermintLightClient { new_any_client_state: new_client_state.into(), new_any_consensus_state: new_consensus_state.into(), height, - commitment: UpdateClientCommitment { - prev_state_id: Some(prev_state_id), - new_state_id, - new_state: None, + message: UpdateClientMessage { prev_height: Some(header.trusted_height.into()), - new_height: height, + prev_state_id: Some(prev_state_id), + post_height: height, + post_state_id, timestamp: header_timestamp, context: TrustingPeriodContext::new( lc_opts.trusting_period, @@ -204,6 +204,7 @@ impl LightClient for TendermintLightClient { trusted_state_timestamp, ) .into(), + emitted_states: Default::default(), } .into(), prove: true, @@ -219,7 +220,7 @@ impl LightClient for TendermintLightClient { value: Vec, proof_height: Height, proof: Vec, - ) -> Result { + ) -> Result { let (client_state, consensus_state, prefix, path, proof) = Self::validate_args(ctx, client_id.clone(), prefix, path, proof_height, proof)?; @@ -242,8 +243,8 @@ impl LightClient for TendermintLightClient { }) })?; - Ok(StateVerificationResult { - state_commitment: StateCommitment::new( + Ok(VerifyMembershipResult { + message: VerifyMembershipMessage::new( prefix.into_vec(), path.to_string(), Some(value.keccak256()), @@ -262,7 +263,7 @@ impl LightClient for TendermintLightClient { path: String, proof_height: Height, proof: Vec, - ) -> Result { + ) -> Result { let (client_state, consensus_state, prefix, path, proof) = Self::validate_args(ctx, client_id.clone(), prefix, path, proof_height, proof)?; @@ -284,8 +285,8 @@ impl LightClient for TendermintLightClient { }) })?; - Ok(StateVerificationResult { - state_commitment: StateCommitment::new( + Ok(VerifyNonMembershipResult { + message: VerifyMembershipMessage::new( prefix.into_vec(), path.to_string(), None, diff --git a/proto/definitions/ibc/lightclients/lcp/v1/lcp.proto b/proto/definitions/ibc/lightclients/lcp/v1/lcp.proto index aeb06baf..6ea42b1f 100644 --- a/proto/definitions/ibc/lightclients/lcp/v1/lcp.proto +++ b/proto/definitions/ibc/lightclients/lcp/v1/lcp.proto @@ -8,7 +8,7 @@ option go_package = "github.com/datachainlab/lcp/go/light-clients/lcp/types"; option (gogoproto.goproto_getters_all) = false; message UpdateClientMessage { - bytes commitment = 1; + bytes elc_message = 1; bytes signer = 2; bytes signature = 3; } diff --git a/proto/definitions/lcp/service/elc/v1/tx.proto b/proto/definitions/lcp/service/elc/v1/tx.proto index d3f2f365..d413f657 100644 --- a/proto/definitions/lcp/service/elc/v1/tx.proto +++ b/proto/definitions/lcp/service/elc/v1/tx.proto @@ -43,7 +43,7 @@ message MsgCreateClientResponse { option (gogoproto.goproto_getters) = false; string client_id = 1; - bytes commitment = 2; + bytes message = 2; bytes signer = 3; bytes signature = 4; } @@ -58,7 +58,7 @@ message MsgUpdateClient { string client_id = 1 [(gogoproto.moretags) = "yaml:\"client_id\""]; // header to update the light client google.protobuf.Any header = 2; - // request to include state in a commitment + // request to emit state bool include_state = 3; // enclave key for signing bytes signer = 4; @@ -69,7 +69,7 @@ message MsgUpdateClientResponse { option (gogoproto.equal) = false; option (gogoproto.goproto_getters) = false; - bytes commitment = 1; + bytes message = 1; bytes signer = 2; bytes signature = 3; } @@ -92,7 +92,7 @@ message MsgVerifyMembershipResponse { option (gogoproto.equal) = false; option (gogoproto.goproto_getters) = false; - bytes commitment = 1; + bytes message = 1; bytes signer = 2; bytes signature = 3; } @@ -114,7 +114,7 @@ message MsgVerifyNonMembershipResponse { option (gogoproto.equal) = false; option (gogoproto.goproto_getters) = false; - bytes commitment = 1; + bytes message = 1; bytes signer = 2; bytes signature = 3; } diff --git a/proto/src/descriptor.bin b/proto/src/descriptor.bin index e8192182..7f2bc85d 100644 Binary files a/proto/src/descriptor.bin and b/proto/src/descriptor.bin differ diff --git a/proto/src/prost/ibc.lightclients.lcp.v1.rs b/proto/src/prost/ibc.lightclients.lcp.v1.rs index bf328c01..f4101ee7 100644 --- a/proto/src/prost/ibc.lightclients.lcp.v1.rs +++ b/proto/src/prost/ibc.lightclients.lcp.v1.rs @@ -2,7 +2,7 @@ #[derive(Clone, PartialEq, ::prost::Message)] pub struct UpdateClientMessage { #[prost(bytes = "vec", tag = "1")] - pub commitment: ::prost::alloc::vec::Vec, + pub elc_message: ::prost::alloc::vec::Vec, #[prost(bytes = "vec", tag = "2")] pub signer: ::prost::alloc::vec::Vec, #[prost(bytes = "vec", tag = "3")] diff --git a/proto/src/prost/lcp.service.elc.v1.rs b/proto/src/prost/lcp.service.elc.v1.rs index 8b66fbc9..8b684f42 100644 --- a/proto/src/prost/lcp.service.elc.v1.rs +++ b/proto/src/prost/lcp.service.elc.v1.rs @@ -289,7 +289,7 @@ pub struct MsgCreateClientResponse { #[prost(string, tag = "1")] pub client_id: ::prost::alloc::string::String, #[prost(bytes = "vec", tag = "2")] - pub commitment: ::prost::alloc::vec::Vec, + pub message: ::prost::alloc::vec::Vec, #[prost(bytes = "vec", tag = "3")] pub signer: ::prost::alloc::vec::Vec, #[prost(bytes = "vec", tag = "4")] @@ -309,7 +309,7 @@ pub struct MsgUpdateClient { pub header: ::core::option::Option< super::super::super::super::google::protobuf::Any, >, - /// request to include state in a commitment + /// request to emit state #[prost(bool, tag = "3")] pub include_state: bool, /// enclave key for signing @@ -322,7 +322,7 @@ pub struct MsgUpdateClient { #[derive(Clone, PartialEq, ::prost::Message)] pub struct MsgUpdateClientResponse { #[prost(bytes = "vec", tag = "1")] - pub commitment: ::prost::alloc::vec::Vec, + pub message: ::prost::alloc::vec::Vec, #[prost(bytes = "vec", tag = "2")] pub signer: ::prost::alloc::vec::Vec, #[prost(bytes = "vec", tag = "3")] @@ -355,7 +355,7 @@ pub struct MsgVerifyMembership { #[derive(Clone, PartialEq, ::prost::Message)] pub struct MsgVerifyMembershipResponse { #[prost(bytes = "vec", tag = "1")] - pub commitment: ::prost::alloc::vec::Vec, + pub message: ::prost::alloc::vec::Vec, #[prost(bytes = "vec", tag = "2")] pub signer: ::prost::alloc::vec::Vec, #[prost(bytes = "vec", tag = "3")] @@ -386,7 +386,7 @@ pub struct MsgVerifyNonMembership { #[derive(Clone, PartialEq, ::prost::Message)] pub struct MsgVerifyNonMembershipResponse { #[prost(bytes = "vec", tag = "1")] - pub commitment: ::prost::alloc::vec::Vec, + pub message: ::prost::alloc::vec::Vec, #[prost(bytes = "vec", tag = "2")] pub signer: ::prost::alloc::vec::Vec, #[prost(bytes = "vec", tag = "3")] diff --git a/tests/integration/src/lib.rs b/tests/integration/src/lib.rs index 1f5927c4..cbcaa24f 100644 --- a/tests/integration/src/lib.rs +++ b/tests/integration/src/lib.rs @@ -10,7 +10,7 @@ mod tests { use super::*; use crate::relayer::Relayer; use anyhow::{anyhow, bail}; - use commitments::UpdateClientCommitment; + use commitments::UpdateClientMessage; use ecall_commands::{ CommitmentProofPair, GenerateEnclaveKeyInput, InitClientInput, UpdateClientInput, VerifyMembershipInput, @@ -179,8 +179,8 @@ mod tests { info!("update_client's result is {:?}", res); assert!(res.0.is_proven()); - let commitment: UpdateClientCommitment = res.0.commitment().unwrap().try_into()?; - let height = commitment.new_height; + let msg: UpdateClientMessage = res.0.message().unwrap().try_into()?; + let height = msg.post_height; info!("current height is {}", height);