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となる