Skip to content

Commit

Permalink
implement update_operators for lcp-client
Browse files Browse the repository at this point in the history
Signed-off-by: Jun Kimura <[email protected]>
  • Loading branch information
bluele committed Jun 18, 2024
1 parent 96ca5ca commit f139292
Show file tree
Hide file tree
Showing 3 changed files with 201 additions and 9 deletions.
140 changes: 131 additions & 9 deletions modules/lcp-client/src/client_def.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
}
}

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)?;

Expand All @@ -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,
Expand All @@ -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())?;
Expand Down Expand Up @@ -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,
Expand All @@ -268,7 +339,7 @@ impl LCPClient {
Ok(())
}

fn verify_operator_proofs<T: HostClientReader + ?Sized>(
fn verify_ek_signatures<T: HostClientReader + ?Sized>(
&self,
ctx: &T,
client_id: &ClientId,
Expand Down Expand Up @@ -373,7 +444,7 @@ pub fn compute_eip712_register_enclave_key(avr: &str) -> Vec<u8> {
};
[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()
}
Expand All @@ -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<Address>,
threshold_numerator: u64,
threshold_denominator: u64,
) -> Vec<u8> {
// 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<Address>,
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
Expand Down Expand Up @@ -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);
}
Expand Down
14 changes: 14 additions & 0 deletions modules/lcp-client/src/client_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,20 @@ impl ClientState {
self.frozen = true;
self
}

pub fn with_operators(
mut self,
operators: Vec<Address>,
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<ClientState> for RawClientState {
Expand Down
56 changes: 56 additions & 0 deletions modules/lcp-client/src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,28 @@ 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};

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<Any> for ClientMessage {}
Expand All @@ -34,6 +39,9 @@ impl TryFrom<Any> 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())),
}
}
Expand All @@ -50,6 +58,10 @@ impl From<ClientMessage> 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(),
),
}
}
}
Expand Down Expand Up @@ -115,6 +127,50 @@ impl From<UpdateClientMessage> for RawUpdateClientMessage {
}
}

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct UpdateOperatorsMessage {
pub nonce: u64,
pub new_operators: Vec<Address>,
pub new_operators_threshold_numerator: u64,
pub new_operators_threshold_denominator: u64,
pub signatures: Vec<Vec<u8>>,
}

impl Protobuf<RawUpdateOperatorsMessage> for UpdateOperatorsMessage {}

impl TryFrom<RawUpdateOperatorsMessage> for UpdateOperatorsMessage {
type Error = Error;
fn try_from(value: RawUpdateOperatorsMessage) -> Result<Self, Self::Error> {
Ok(UpdateOperatorsMessage {
nonce: value.nonce,
new_operators: value
.new_operators
.iter()
.map(|op| Address::try_from(op.as_slice()))
.collect::<Result<_, _>>()?,
new_operators_threshold_numerator: value.new_operators_threshold_numerator,
new_operators_threshold_denominator: value.new_operators_threshold_denominator,
signatures: value.signatures,
})
}
}

impl From<UpdateOperatorsMessage> 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<u8>,
Expand Down

0 comments on commit f139292

Please sign in to comment.