Enclave Light Client (ELC)
Overview
ELCは、LCP Enclave内に実装されるLight Clientである。ELCは、検証対象のChainのコンセンサスアルゴリズムごとに実装されなければならない。
各ELCはこのセクションで定義される関数を実装しなければならない。各関数は状態遷移や検証の結果を示すメッセージを返す。LCPは、外部の検証者がこれらを検証可能にするために、メッセージに対するコミットメントとEKによる署名を生成する。この署名やメッセージは、on-chainのLCP Clientにより検証される。
Desired Properties
ELCは、ICS-02と同様の性質を満たす必要がある。
加えて、以下の性質を満たす必要がある:
- ClientState, ConsensusStateを元に対象chainのコンセンサスを検証し、状態遷移に対するコミットメントを生成する。
- ClientState, ConsensusStateを元に対象chainの状態を検証し、自身の状態とその結果に対するコミットメントを生成する。
- コンセンサスの検証時にMisbehaviorが検出された場合、ELCは凍結され、その誤動作に対するコミットメントを生成する。
コミットメントは、ELCが参照した状態および更新後の状態を一意に示す値を示す必要がある。ELCはIBC等のオンチェーンで検証を行うLight Clientとは異なり、ELCが参照するストレージの一貫性に対する信頼仮定を持たないため、ELCが参照した全ての状態を一意にコミットメントで示さなければならない。
Data Structures
Client ID
Client ID
は、ELCのインスタンスを一意に示す識別子である。これはinit_client
で新しいインスタンスを生成する際に指定される。update_client
やverify_client
等の関数においては、この識別子を指定することで対象のELCインスタンスを特定する。
State ID
State ID
は、ELCのある高さに対応する状態を一意に示す識別子である。State ID
はClientState
とConsensusState
をそれぞれエンコードしてconcatenateしたバイト列のkeccak256ハッシュ値である。
エンコード方法は以下の通りである。
- Protobufを用いて
ClientState
をエンコードし、そのバイト列をclient_bytes
とする。 - Protobuf Any typeを用いて
value
にclient_bytes
を、type_url
にClientState
の型名を設定したものをProtobufでエンコードする
ここで、Anyのtype_url
は1つのLCP Enclave内のELC間で一意である必要がある。これにより、異なるELCのClientState
/ConsensusState
が同じState ID
を持たないことが保証される。
また、ELCのある高さにおけるState ID
は、同一のClientState
で初期化されたELCインスタンスにおいては必ず一意にならなければならない。
通常、ICS-02準拠のClientにおいて、ConsensusState
はchainの状態ルートやタイムスタンプを含み、ClientStateは検証に関するパラメータを含む。State IDの計算に、ConsensusState
だけでなく、ClientState
を含めることで、特定の検証パラメータで一貫して状態が追跡されていることを保証する。このため、07-tendermintのようにClientState
にlatest_height
のような状態を持つLight Clientにおいては、State ID
の計算時にこの値を除外する必要があることに注意する。
Commitment Hash
Commitment Hash
は、ELCの状態遷移や検証の結果を示す。以下のように定義される。
commitment = keccak256(abi.encode(| version (2 bytes) | message type (2 bytes) | reseved (28 bytes)|), message bytes)
version
はLCPのメッセージスキーマのバージョンを示す。現在、0x0001
が使用される。message type
はメッセージの種類を示す。それぞれ、0x0001
がUpdateStateProxyMessage
、0x0002
がVerifyMembershipProxyMessage
、0x0003
がMisbehaviourProxyMessage
を示す。reserved
は将来の拡張のために予約されている。message bytes
はメッセージをEthereum ABIに従いエンコードしたものである。
また、| version (2 bytes) | message type (2 bytes) | reseved (28 bytes)|
をHeader
とも呼び、message bytes
とconcatenateしたものをHeadered Message
と呼ぶ。
Commitment Proof
Commitment Proof
(単にproof, 証明)は、ELCの状態遷移や検証の結果を示す証明である。コミットメントに対して、Enclaveで生成されたEKにより署名したものである。証明は、外部の検証者がELCの状態遷移や検証の結果を検証可能にするために用いられる。
Validation Context
Validation Context
は、ELCのClientによるHeader検証に用いた現在時刻等の環境に関する情報から構成される。
通常、Light ClientはHeader検証時には次の2つを確認する:
- ある高さに対応するバリデータのセットを元に与えられたHeaderに対する署名が十分に集まっていることの検証
- 検証するClientの状態が現在有効であることの確認
1.については、ECDSA等の署名の検証である。これはEnclave内ELCにより行われる。
2.については、現在時刻と受け入れるブロックのタイムスタンプや検証時に参照した状態のタイムスタンプをパラメータとして有効性の真偽値を返す関数として定義される。例えば、07-tendermintにおけるtrusting periodを用いた状態の有効性の検証がある。しかし、一般的にTEEにおいて信頼できるタイムスタンプは存在しないため、TEE内での現在時刻を参照する検証は信頼できない。そのため、ELCは用いた検証関数の情報とそのパラメータを含むValidation Context
を生成し、それをコミットメントに含めることで、検証者がその検証を再現できるようにする。検証者は、最新のブロックのタイムスタンプ等の情報とValidation Context
を用いて検証を行うことで、ELCの状態遷移が有効であることを確認できる。
Message
Messageは、ELCの状態遷移や検証の結果を示すメッセージである。
UpdateStateProxyMessage
UpdateStateProxyMessage
は、ELCのClientの状態遷移を示すメッセージである。Downstream chainはLCP Clientを用いてこのメッセージを検証することでUpstream chainの状態遷移を追跡する。
メッセージの定義は以下の通りである。
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
は参照したConsensusState
の高さである。なお、init_client
においては、prev_height
は(0,0)とする。post_height
はそのHeaderを適用した更新後の高さである。prev_state_id
は、参照した状態を示すState ID
である。なお、init_client
においては、prev_state_id
は0とする。post_state_id
は、更新後の状態を示すState ID
である。timestamp
は適用したHeaderのタイムスタンプである。context
は検証時の環境のコンテキストを示すValidation Context
である。emitted_states
は、更新時に生成された新しいClientState
を示す。主にinit_client
による新規状態をセットする場合に用いられる。
UpdateStateProxyMessage
をEthereum ABIに従いエンコードしたもののkeccak256ハッシュをコミットメントとし、EKにより署名されることで状態遷移の証明とする。
このメッセージには、更新前と更新後の両方のState IDが含まれており、LCP Clientはメッセージが特定の状態遷移に対応していることを確認できる。具体的には、初期化時にメッセージから得られたpost_state_id
を信頼し、その後は証明を検証し受け入れたメッセージのpre_state_id
が検証済み、もしくは初期化時のConsensusState
のstate_id
であるpost_state_id
を受け入れることで、正確な状態遷移を追跡していることが保証される。(以降、これを繰り返す)
メッセージの検証者はprev_state_id
が信頼できるものであることを確認し、EKによる署名とcontext
を検証することで、状態遷移の正しさを確認することができる。LCP ClientのUpdateState関数はこの検証を実装する。
VerifyMembershipProxyMessage
VerifyMembershipProxyMessage
は、ELCのある高さのconsensus stateに基づく値の存在証明を示すメッセージである。Downstream chainはLCP Clientを用いてこのメッセージとproofを検証することでUpstream chainの特定の状態を検証する。
メッセージの定義は以下の通りである。
struct VerifyMembershipProxyMessage {
bytes prefix;
bytes path;
bytes32 value;
Height height;
bytes32 state_id;
}
prefix
はUpstream chainの状態ストアの接頭辞で、検証時にpath
に適用される。path
は、検証対象の値に対応する状態ツリーのpathである。value
はpath
に対応する値である。verifyMembership
の場合は非0のvalueである必要がある。一方で、verifyNonMembership
の場合は0である必要がある。height
はstate_id
に対応する高さである。state_id
は参照した状態のState ID
である。
VerifyMembershipProxyMessage
をEthereum ABIに従いエンコードしたもののkeccak256ハッシュをコミットメントとし、EKにより署名されることで状態の存在証明とする。
検証者は、メッセージのstate_id
が信頼できるものであることを確認し、EKによる署名を検証することで、検証対象の状態の存在/非存在を確認することができる。LCP ClientのVerifyMembership関数はこの検証を実装する。
MisbehaviourProxyMessage
MisbehaviourProxyMessage
は、ELCがある高さのconsensus stateに基づき検知したChainのmisbehaviourを示すメッセージである。Misbehaviourの例としては、同じ高さに対応するValidな2つのHeaderが存在することが挙げられる。
struct MisbehaviourProxyMessage {
PrevState[] prev_states;
bytes context;
bytes client_message;
}
struct PrevState {
Height height;
bytes32 state_id;
}
MisbehaviourProxyMessage
をEthereum ABIに従いエンコードしたもののkeccak256ハッシュをコミットメントとし、EKにより署名されることでMisbehaviourの証明とする。
検証者は、メッセージのprev_states
の各state_id
が信頼できるものであることを確認し、EKによる署名とcontext
を検証することで、Misbehaviourの正しさを確認することができる。LCP ClientのMisbehaviour関数はこの検証を実装する。
Functions
ELC
init_client
init_client
は与えられたClient IDやClientStateやConsensusStateを元にELCの新しいインスタンスを作成する関数である。この関数はUpdateStateProxyMessage
とproofを返す。
あるClient IDに対応するELCのインスタンスが既に存在する場合や無効なClientStateやConsensusStateが与えられた場合はエラーを返さなければならない。
この関数は、ICS-02のinitialise関数に相当する。
update_client
update_client
は指定するClient IDに対応するELCのインスタンスを与えられたHeaderで状態を更新する関数である。この関数はUpdateStateProxyMessage
もしくはMisbehaviourProxyMessage
とそのproofを返す。
この関数は、ICS-02のupdate関数に相当する。
verify_membership
verify_membership
は与えられた高さに対応するConsensusStateに基づいた存在証明を検証する関数である。この関数はVerifyMembershipProxyMessage
とproofを返す。
この関数は、ICS-02のverifyMembership関数に相当する。
verify_non_membership
verify_non_membership
は与えられた高さに対応するConsensusStateに基づいた非存在証明を検証する関数である。この関数はVerifyMembershipProxyMessage
とproofを返す。
この関数は、ICS-02のverifyNonMembership関数に相当する。
Message aggregation
UpdateStateProxyMessage
は、通常2つの高さに関するELCの状態遷移を示す。Light Clientによってはある高さのConsensusState
を元に任意の高さに対して更新することは不可能であるケースが多い。これにより、2つの高さの間隔が大きい場合、多くのメッセージを検証者は検証する必要が生じることを意味する。これは、オンチェーンのLCP ClientではGasコストが非常に高くなる可能性がある。このため、LCPでは複数のメッセージを1つのメッセージに集約するaggregate_message
機能を提供する。これにより、複数のメッセージの検証を1つのメッセージにより行うことができるため、Gas効率の高い更新が可能となる。
messagesの配列は以下のルールに従って検証される:
- 各メッセージの署名や検証が全て有効であることを確認する
- 0番目のメッセージの
prev_height
とprev_state_id
はすでに存在するconsensus heightとそのState ID
と等しい index
番目のメッセージのpost_height
は、index+1
番目のメッセージのprev_height
と等しいindex
番目のメッセージのpost_state_id
は、index+1
番目のメッセージのprev_state_id
と等しい
そして、以下のルールに従って1つのUpdateStateProxyMessage
に集約される:
prev_height
は連結されたメッセージの中で最も小さいprev_height
、post_height
は最も大きいpost_height
となるprev_state_id
は連結されたメッセージの中で最も小さいprev_state_id
、post_state_id
は最も大きいpost_state_id
となる- 連結されたメッセージの
timestamp
は連結されたメッセージの中で最も新しいtimestamp
となる