Skip to main content

Enclave Light Client (ELC)

Overview

ELC is a Light Client implemented within the LCP Enclave. The ELC must be implemented for each consensus algorithm of the target chain to be verified.

Each ELC must implement the functions defined in this section. Each function returns messages that indicate state transitions or verification results. LCP generates commitments and signatures using the EK for these messages to make them verifiable by external verifiers. These signatures and messages are verified by the on-chain LCP Client.

Desired Properties

The ELC must satisfy properties similar to those in ICS-02.

In addition, it must satisfy the following properties:

  • State Transition Commitments: Generate commitments to state transitions by verifying the consensus of the target chain based on the ClientState and ConsensusState.
  • State Verification Commitments: Generate commitments to the state and the results of its verification by verifying the state of the target chain based on the ClientState and ConsensusState.
  • Misbehavior Handling: When misbehavior is detected during consensus verification, the ELC is frozen and generates a commitment to that misbehavior.

The commitments must uniquely indicate the states referenced by the ELC and the states after updates. Unlike on-chain light clients that perform verification on-chain, such as in IBC, the ELC does not have trust assumptions regarding the consistency of the storage it references, so it must uniquely represent all referenced states in the commitments.

Data Structures

Client ID

Client ID is an identifier that uniquely indicates an instance of the ELC. It is specified when a new instance is created with init_client. In functions such as update_client and verify_client, this identifier is used to specify the target ELC instance.

State ID

State ID is an identifier that uniquely indicates the state of the ELC corresponding to a certain height. The State ID is the keccak256 hash value of the concatenation of the encoded ClientState and ConsensusState.

The encoding method is as follows:

  • Encode the ClientState using Protobuf and let the resulting byte array be client_bytes.
  • Use the Protobuf Any type to encode an object with value set to client_bytes and type_url set to the type name of the ClientState.

Here, the type_url of Any must be unique among ELCs within a single LCP Enclave. This ensures that different ELCs' ClientState/ConsensusState do not have the same State ID.

Furthermore, the State ID at a certain height in an ELC instance initialized with the same ClientState must always be unique.

In general, in ICS-02 compliant clients, the ConsensusState includes the state root and timestamp of the chain, and the ClientState includes parameters related to verification. By including the ClientState as well as the ConsensusState in the calculation of the State ID, it ensures that the state is consistently tracked with specific verification parameters. Therefore, note that in light clients like 07-tendermint that have state such as latest_height in the ClientState, this value must be excluded when calculating the State ID.

Commitment Hash

A commitment hash indicates the state transitions or verification results of the ELC. It is defined as follows:

commitment = keccak256(abi.encode(| version (2 bytes) | message type (2 bytes) | reserved (28 bytes) |), message bytes)

  • version indicates the version of the LCP message schema. Currently, 0x0001 is used.
  • message type indicates the type of message. 0x0001 represents UpdateStateProxyMessage, 0x0002 represents VerifyMembershipProxyMessage, and 0x0003 represents MisbehaviourProxyMessage.
  • reserved is reserved for future extensions.
  • message bytes are the message encoded with the Ethereum ABI.

Also, | version (2 bytes) | message type (2 bytes) | reserved (28 bytes) | is referred to as the Header, and the concatenation with message bytes is referred to as the HeaderedMessage.

Commitment Proof

A commitment proof (simply called proof) is a proof that indicates the state transitions or verification results of the ELC. It is the Commitment Hash signed by the EK generated in the Enclave. The proof is used to enable external verifiers to verify the state transitions or verification results of the ELC.

Validation Context

Validation Context consists of environmental information such as the current time used during Header verification by the ELC's client.

Typically, during Header verification, a light client checks the following two things:

  1. Signature Verification: Verification that sufficient signatures are present on the given Header based on the validator set corresponding to a certain height.
  2. State Validity Confirmation: Confirmation that the state of the client being verified is currently valid.

Regarding 1, this is the verification of signatures such as ECDSA. This is performed by the ELC within the Enclave.

Regarding 2, it is defined as a function that returns a boolean value indicating validity, taking parameters such as the current time, the timestamp of the block to be accepted, and the timestamp of the state referenced during verification. For example, in 07-tendermint, there is validation of the validity of the state using the trusting period. However, since there is generally no reliable timestamp in TEE, verification referencing the current time within the TEE is unreliable. Therefore, the ELC generates a Validation Context that includes information about the verification function used and its parameters and includes it in the commitment so that the verifier can reproduce the verification. The verifier can confirm that the state transition of the ELC is valid by performing verification using information such as the latest block's timestamp and the Validation Context.

Message

Message or Proxy Message is a message that indicates the state transitions or verification results of the ELC.

UpdateStateProxyMessage

UpdateStateProxyMessage is a message that indicates the state transition of the ELC's client. The downstream chain tracks the state transitions of the upstream chain by verifying this message using the LCP Client.

The message is defined as follows:

struct UpdateStateProxyMessage {
Height prev_height;
bytes32 prev_state_id;
Height post_height;
bytes32 post_state_id;
uint128 timestamp;
bytes context;
EmittedState[] emitted_states;
}

struct Height {
uint64 revision_number;
uint64 revision_height;
}

struct EmittedState {
Height height;
bytes state;
}
  • prev_height is the height of the ConsensusState referenced. Note that in init_client, prev_height is set to (0,0).
  • post_height is the height after applying the Header.
  • prev_state_id is the State ID indicating the referenced state. In init_client, prev_state_id is set to 0.
  • post_state_id is the State ID indicating the state after the update.
  • timestamp is the timestamp of the applied Header.
  • context is the Validation Context indicating the context of the environment during verification.
  • emitted_states indicates the new ClientState generated during the update. It is mainly used when setting a new state through init_client.

The keccak256 hash of the UpdateStateProxyMessage encoded with the Ethereum ABI is used as the commitment and is signed by the EK to generate a proof of the state transition.

This message includes both the pre-update and post-update State ID, allowing the LCP Client to confirm that the message corresponds to a specific state transition. Specifically, the initial post_state_id obtained from the message is trusted during initialization, and thereafter, by verifying proofs and accepting messages where the prev_state_id is verified or accepting the post_state_id of the ConsensusState at initialization, it is ensured that accurate state transitions are being tracked. (This process is repeated subsequently.)

The verifier of the message can confirm the correctness of the state transition by confirming that the prev_state_id is trustworthy and verifying the signature by the EK and the context. The UpdateState function of the LCP Client implements this verification.

VerifyMembershipProxyMessage

VerifyMembershipProxyMessage is a message that indicates the proof of existence of a value based on the ConsensusState at a certain height of the ELC. The downstream chain verifies a specific state of the upstream chain by verifying this message and proof using the LCP Client.

The message is defined as follows:

struct VerifyMembershipProxyMessage {
bytes prefix;
bytes path;
bytes32 value;
Height height;
bytes32 state_id;
}
  • prefix is the prefix of the upstream chain's state store, applied to path during verification.
  • path is the path in the state tree corresponding to the value to be verified.
  • value is the value corresponding to path. In the case of verifyMembership, the value must be non-zero. In the case of verifyNonMembership, it must be zero.
  • height is the height corresponding to state_id.
  • state_id is the State ID of the referenced state.

The keccak256 hash of the VerifyMembershipProxyMessage encoded according to the Ethereum ABI is used as the commitment and is signed by the EK to serve as proof of the existence of the state.

The verifier can confirm the existence/non-existence of the state to be verified by confirming that the state_id of the message is trustworthy and verifying the signature by the EK. The VerifyMembership function of the LCP Client implements this verification.

MisbehaviourProxyMessage

MisbehaviourProxyMessage is a message that indicates misbehavior of the chain detected by the ELC based on the ConsensusState at a certain height. An example of misbehavior is the existence of two valid Headers corresponding to the same height.

struct MisbehaviourProxyMessage {
PrevState[] prev_states;
bytes context;
bytes client_message;
}

struct PrevState {
Height height;
bytes32 state_id;
}

The keccak256 hash of the MisbehaviourProxyMessage encoded according to the Ethereum ABI is used as the commitment and is signed by the EK to serve as proof of misbehavior.

The verifier can confirm the correctness of the misbehavior by confirming that each state_id in prev_states of the message is trustworthy and verifying the signature by the EK and the context. The Misbehaviour function of the LCP Client implements this verification.

Functions

ELC

init_client

init_client is a function that creates a new instance of the ELC based on the given Client ID, ClientState, and ConsensusState. This function returns an UpdateStateProxyMessage and proof.

If an ELC instance corresponding to a certain Client ID already exists or invalid ClientState or ConsensusState is given, it must return an error.

This function corresponds to the initialise function in ICS-02.

update_client

update_client is a function that updates the state of the ELC instance corresponding to the specified Client ID with the given Header. This function returns an UpdateStateProxyMessage or a MisbehaviourProxyMessage and proof.

This function corresponds to the update function in ICS-02.

verify_membership

verify_membership is a function that verifies the proof of existence based on the ConsensusState corresponding to the given height. This function returns a VerifyMembershipProxyMessage and proof.

This function corresponds to the verifyMembership function in ICS-02.

verify_non_membership

verify_non_membership is a function that verifies the proof of non-existence based on the ConsensusState corresponding to the given height. This function returns a VerifyMembershipProxyMessage and proof.

This function corresponds to the verifyNonMembership function in ICS-02.

Message Aggregation

UpdateStateProxyMessage usually indicates state transitions of the ELC concerning two heights. For some light clients, it is often impossible to update to an arbitrary height based on the ConsensusState at a certain height. This means that if the interval between two heights is large, the verifier may need to verify many messages. This can result in very high gas costs in on-chain LCP Clients. Therefore, LCP provides a message aggregation function aggregate_message that aggregates multiple messages into one. This allows for gas-efficient updates by verifying multiple messages through a single message.

The array of messages is verified according to the following rules:

  • Confirm that all signatures and verifications of each message are valid.
  • The prev_height and prev_state_id of the 0-th message match the existing consensus height and its State ID.
  • The post_height of the index-th message matches the prev_height of the index+1-th message.
  • The post_state_id of the index-th message matches the prev_state_id of the index+1-th message.

Then, they are aggregated into one UpdateStateProxyMessage according to the following rules:

  • prev_height is the smallest prev_height among the concatenated messages, and post_height is the largest post_height.
  • prev_state_id is the prev_state_id of the first message, and post_state_id is the post_state_id of the last message.
  • The timestamp of the aggregated message is the most recent timestamp among the concatenated messages.