Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: validate relayer submitted full sync committee #383

Merged
merged 8 commits into from
Mar 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified e2e/interchaintestv8/wasm/cw_ics08_wasm_eth.wasm.gz
Binary file not shown.
55 changes: 53 additions & 2 deletions packages/ethereum/ethereum-light-client/src/consensus_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};

use ethereum_types::consensus::sync_committee::SyncCommittee;

use crate::header::ActiveSyncCommittee;
use crate::{error::EthereumIBCError, header::ActiveSyncCommittee, verify::BlsVerify};

/// The consensus state of the Ethereum light client
#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Debug, Clone)]
Expand Down Expand Up @@ -42,10 +42,61 @@ pub struct TrustedConsensusState {
/// This sync committee can either be the current sync committee or the next sync
/// committee. That's because the verifier uses next or current sync committee's
/// public keys to verify the signature against.
pub sync_committee: ActiveSyncCommittee,
sync_committee: ActiveSyncCommittee,
}

impl TrustedConsensusState {
/// Creates a new trusted consensus state
/// # Errors
/// Returns an error if the untrusted sync committee does not match the trusted state
pub fn new<V: BlsVerify>(
trusted_state: ConsensusState,
untrusted_sync_committee: ActiveSyncCommittee,
bls_verifier: &V,
) -> Result<Self, EthereumIBCError> {
let full_committee = match untrusted_sync_committee {
ActiveSyncCommittee::Current(ref committee) => {
ensure!(
committee.aggregate_pubkey == trusted_state.current_sync_committee,
EthereumIBCError::CurrenttSyncCommitteeMismatch {
expected: trusted_state.current_sync_committee,
found: committee.aggregate_pubkey
}
);
committee
}
ActiveSyncCommittee::Next(ref committee) => {
let trusted_next_sync_committee = trusted_state
.next_sync_committee
.ok_or(EthereumIBCError::NextSyncCommitteeUnknown)?;
ensure!(
committee.aggregate_pubkey == trusted_next_sync_committee,
EthereumIBCError::NextSyncCommitteeMismatch {
expected: trusted_next_sync_committee,
found: committee.aggregate_pubkey
}
);
committee
}
};

let aggregate_pubkey = bls_verifier
.aggregate(&full_committee.pubkeys)
.map_err(|e| EthereumIBCError::FastAggregateVerifyError(e.to_string()))?;
ensure!(
aggregate_pubkey == full_committee.aggregate_pubkey,
EthereumIBCError::AggregatePubkeyMismatch {
expected: aggregate_pubkey,
found: full_committee.aggregate_pubkey
}
);

Ok(Self {
state: trusted_state,
sync_committee: untrusted_sync_committee,
})
}

/// Returns the finalized slot of the trusted consensus state
#[must_use]
pub const fn finalized_slot(&self) -> u64 {
Expand Down
22 changes: 21 additions & 1 deletion packages/ethereum/ethereum-light-client/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,20 @@ pub enum EthereumIBCError {
found: BlsPublicKey,
},

#[error(
"current sync committee ({found}) does not match with the one in the current state ({expected})"
)]
CurrenttSyncCommitteeMismatch {
expected: BlsPublicKey,
found: BlsPublicKey,
},

#[error("aggregate public key mismatch: expected {expected} but found {found}")]
AggregatePubkeyMismatch {
expected: BlsPublicKey,
found: BlsPublicKey,
},

#[error(
"expected current sync committee to be provided since `update_period == current_period`"
)]
Expand All @@ -109,8 +123,14 @@ pub enum EthereumIBCError {
#[error("expected next sync committee to be provided since `update_period > current_period`")]
ExpectedNextSyncCommittee,

#[error("expected next sync committee to be known and stored in state")]
NextSyncCommitteeUnknown,

#[error("fast aggregate verify error: {0}")]
FastAggregateVerify(String),
FastAggregateVerifyError(String),

#[error("bls aggregate error: {0}")]
BlsAggregateError(String),

#[error("not enough signatures")]
NotEnoughSignatures,
Expand Down
19 changes: 14 additions & 5 deletions packages/ethereum/ethereum-light-client/src/misbehaviour.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@ use ethereum_types::consensus::{

use crate::{
client_state::ClientState,
consensus_state::TrustedConsensusState,
consensus_state::{ConsensusState, TrustedConsensusState},
error::EthereumIBCError,
header::ActiveSyncCommittee,
verify::{validate_light_client_update, BlsVerify},
};

/// Verifies if a consensus misbehaviour is valid by checking if the two conflicting light client updates are valid.
///
/// * `client_state`: The current client state.
/// * `trusted_consensus_state`: The trusted consensus state (previously verified and stored)
/// * `consensus_state`: The current consensus state (previously verified and stored)
/// * `full_sync_committee`: The full sync committee data. (untrusted)
/// * `update_1`: The first light client update.
/// * `update_2`: The second light client update.
/// * `current_slot`: The slot number computed based on the current timestamp.
Expand All @@ -26,12 +28,19 @@ use crate::{
#[allow(clippy::module_name_repetitions, clippy::needless_pass_by_value)]
pub fn verify_misbehaviour<V: BlsVerify>(
client_state: &ClientState,
trusted_consensus_state: &TrustedConsensusState,
consensus_state: &ConsensusState,
full_sync_committee: &ActiveSyncCommittee,
update_1: &LightClientUpdate,
update_2: &LightClientUpdate,
current_timestamp: u64,
bls_verifier: V,
) -> Result<(), EthereumIBCError> {
let trusted_consensus_state = TrustedConsensusState::new(
consensus_state.clone(),
full_sync_committee.clone(),
&bls_verifier,
)?;

// There is no point to check for misbehaviour when the headers are not for the same height
let (slot_1, slot_2) = (
update_1.finalized_header.beacon.slot,
Expand Down Expand Up @@ -65,15 +74,15 @@ pub fn verify_misbehaviour<V: BlsVerify>(

validate_light_client_update::<V>(
client_state,
trusted_consensus_state,
&trusted_consensus_state,
update_1,
current_slot,
&bls_verifier,
)?;

validate_light_client_update::<V>(
client_state,
trusted_consensus_state,
&trusted_consensus_state,
update_2,
current_slot,
&bls_verifier,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ pub fn fast_aggregate_verify(
/// Returns an error if the signature is invalid
/// # Returns
/// Returns the aggregated public key
pub fn aggreagate(public_keys: &[&BlsPublicKey]) -> Result<BlsPublicKey, BlsError> {
pub fn aggreagate(public_keys: &[BlsPublicKey]) -> Result<BlsPublicKey, BlsError> {
let public_keys = public_keys
.iter()
.map(|pk| milagro_bls::PublicKey::from_bytes(pk.as_ref()))
Expand Down
22 changes: 16 additions & 6 deletions packages/ethereum/ethereum-light-client/src/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ pub trait BlsVerify {
msg: B256,
signature: BlsSignature,
) -> Result<(), Self::Error>;

/// Aggregate public keys.
/// # Errors
/// Returns an error if the public keys cannot be aggregated.
fn aggregate(&self, public_keys: &[BlsPublicKey]) -> Result<BlsPublicKey, Self::Error>;
}

/// Verifies the header of the light client.
Expand All @@ -53,10 +58,11 @@ pub fn verify_header<V: BlsVerify>(
header: &Header,
bls_verifier: V,
) -> Result<(), EthereumIBCError> {
let trusted_consensus_state = TrustedConsensusState {
state: consensus_state.clone(),
sync_committee: header.trusted_sync_committee.sync_committee.clone(),
};
let trusted_consensus_state = TrustedConsensusState::new(
consensus_state.clone(),
header.trusted_sync_committee.sync_committee.clone(),
&bls_verifier,
)?;

// Ethereum consensus-spec says that we should use the slot at the current timestamp.
let current_slot = compute_slot_at_timestamp(
Expand Down Expand Up @@ -318,7 +324,7 @@ pub fn validate_light_client_update<V: BlsVerify>(
signing_root,
update.sync_aggregate.sync_committee_signature,
)
.map_err(|err| EthereumIBCError::FastAggregateVerify(err.to_string()))?;
.map_err(|err| EthereumIBCError::FastAggregateVerifyError(err.to_string()))?;

Ok(())
}
Expand Down Expand Up @@ -375,7 +381,7 @@ pub fn get_lc_execution_root(
#[cfg(test)]
mod test {
use crate::test_utils::{
bls_verifier::{fast_aggregate_verify, BlsError},
bls_verifier::{aggreagate, fast_aggregate_verify, BlsError},
fixtures::{self, InitialState, UpdateClient},
};

Expand All @@ -394,6 +400,10 @@ mod test {
) -> Result<(), BlsError> {
fast_aggregate_verify(public_keys, msg, signature)
}

fn aggregate(&self, public_keys: &[BlsPublicKey]) -> Result<BlsPublicKey, BlsError> {
aggreagate(public_keys)
}
}

#[test]
Expand Down
22 changes: 22 additions & 0 deletions programs/cw-ics08-wasm-eth/src/custom_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ pub enum BlsVerifierError {
/// The signature that was verified
signature: BlsSignature,
},

#[error("aggregate public key cannot be deserialized: {0}")]
PublicKeyTryFromError(#[from] core::array::TryFromSliceError),
}

impl BlsVerify for BlsVerifier<'_> {
Expand Down Expand Up @@ -90,4 +93,23 @@ impl BlsVerify for BlsVerifier<'_> {

Ok(())
}

fn aggregate(&self, public_keys: &[BlsPublicKey]) -> Result<BlsPublicKey, Self::Error> {
let binary_public_keys: Vec<Binary> = public_keys
.iter()
.map(|p| Binary::from(p.to_vec()))
.collect();

let request: QueryRequest<EthereumCustomQuery> =
QueryRequest::Custom(EthereumCustomQuery::Aggregate {
public_keys: binary_public_keys,
});

let aggregate_key: Binary = self
.querier
.query(&request)
.map_err(|e| BlsVerifierError::FastAggregateVerify(e.to_string()))?;

Ok(BlsPublicKey::try_from(aggregate_key.as_slice())?)
}
}
13 changes: 4 additions & 9 deletions programs/cw-ics08-wasm-eth/src/query.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
//! This module contains the query message handlers

use cosmwasm_std::{to_json_binary, Binary, Deps, Env};
use ethereum_light_client::consensus_state::TrustedConsensusState;

use crate::{
custom_query::{BlsVerifier, EthereumCustomQuery},
Expand Down Expand Up @@ -54,10 +53,8 @@ pub fn verify_client_message(

ethereum_light_client::misbehaviour::verify_misbehaviour(
&eth_client_state,
&TrustedConsensusState {
state: eth_consensus_state,
sync_committee: misbehaviour.sync_committee,
},
&eth_consensus_state,
&misbehaviour.sync_committee,
&misbehaviour.update_1,
&misbehaviour.update_2,
env.block.time.seconds(),
Expand Down Expand Up @@ -97,10 +94,8 @@ pub fn check_for_misbehaviour(

ethereum_light_client::misbehaviour::verify_misbehaviour(
&eth_client_state,
&TrustedConsensusState {
state: eth_consensus_state,
sync_committee: misbehaviour.sync_committee,
},
&eth_consensus_state,
&misbehaviour.sync_committee,
&misbehaviour.update_1,
&misbehaviour.update_2,
env.block.time.seconds(),
Expand Down
2 changes: 1 addition & 1 deletion programs/cw-ics08-wasm-eth/src/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ pub fn custom_query_handler(query: &EthereumCustomQuery) -> MockQuerierCustomHan
let public_keys = public_keys
.iter()
.map(|pk| pk.as_ref().try_into().unwrap())
.collect::<Vec<&BlsPublicKey>>();
.collect::<Vec<BlsPublicKey>>();

let aggregate_pubkey = aggreagate(&public_keys).unwrap();

Expand Down
Loading