概述

IBC 协议通过使用对手方状态机的客户端来验证数据包消息,从而在不同账本上的应用之间提供安全的数据包流转。ICS-4 定义了两条链之间数据包流转的核心逻辑,以及它们为实现通信必须作出的可证明承诺;而本标准 ICS-2 则规定了链应当如何验证对手方的 IBC 可证明承诺,这对于安全地接收和处理来自对手方的数据包流转消息至关重要。 本标准重点说明如何跟踪对手方共识并验证状态机;它还规定了实现区块链间通信(IBC)协议的状态机之共识算法必须满足的属性。 这些属性对于在更高层协议抽象中实现高效且安全的验证是必要的。 IBC 中用于验证远程状态机状态更新的算法称为有效性谓词。 将有效性谓词与一个可信状态(即验证者假定为正确的状态)配对, 即可在宿主状态机上为远程状态机实现一个轻客户端(通常简称为客户端)。 除状态更新验证之外,每个轻客户端还能够通过错误行为谓词检测共识错误行为。 除本规范所描述的属性外,IBC 不对状态机及其共识算法的内部运行方式施加任何要求。 状态机可以是一个使用私钥签署操作的单一进程(即所谓的 “solo machine”),也可以是一个一致签名的进程仲裁集, 也可以是多个运行拜占庭容错共识算法(例如 Tendermint)的进程,或其他尚未被发明的配置 —— 从 IBC 的视角看,状态机完全由其轻客户端验证逻辑和错误行为检测逻辑来定义。 本标准还规定了如何注册轻客户端的功能,以及 IBC 协议如何存储和更新其数据。 存储的客户端实例可以被第三方参与者检查, 例如用户检查状态机的状态并决定是否发送 IBC 数据包。

动机

IBC 协议需要能够验证另一状态机(即远程状态机)状态的更新。 这意味着只能接受由远程状态机的共识算法达成一致的状态更新。 远程状态机的轻客户端,就是使参与者能够验证该状态机状态更新的算法。 需要注意的是,轻客户端通常不会包含对完整状态转移逻辑的验证 (因为那等同于直接执行另一条状态机),但在特定情况下, 它可以选择验证状态转移的某些部分。 本标准对轻客户端模型及其要求进行了形式化。 因此,只要提供满足所列要求的必要轻客户端算法, IBC 协议就可以轻松集成运行新型共识算法的新状态机。 IBC 协议也可用于与概率终局性共识算法交互。 在这种情况下,不同应用可能需要不同的有效性谓词。对于概率终局性共识,有效性谓词由终局阈值定义(例如,该阈值定义了一个区块之上还需要叠加多少个区块,才可将其视为已终局)。 因此,客户端可以作为其他客户端的阈值视图: 一个仅写客户端可用于存储状态更新(但不具备验证这些更新的能力), 而多个具有不同终局阈值的只读客户端(即在经过相应确认深度后 状态更新才被视为终局)则用于验证状态更新。 客户端接口也应当以安全的方式构造,以便在运行时提供自定义验证逻辑 来定义自定义客户端,前提是底层状态机能够提供适当的 gas 计量机制, 用于对计算和存储收费。例如,在支持 WASM 执行的宿主状态机上, 可以在创建客户端实例时,将有效性谓词和错误行为谓词 作为可执行的 WASM 函数提供。

定义

  • Consensus 是一种生成状态更新的算法。它接收状态机的前一状态, 以及一组消息(即状态机交易),并生成该状态机的有效状态更新。 每个状态机都必须拥有一个 Consensus,从创世状态开始生成唯一且有序的状态更新列表。 本规范要求 Consensus 生成的状态更新 满足以下属性:
    • 每个状态更新都不得在状态更新列表中拥有多于一个直接后继。 换言之,状态机必须保证终局性和安全性。
    • 每个状态更新最终都必须在状态更新列表中拥有一个后继。 换言之,状态机必须保证活性。
    • 每个状态更新都必须是有效的(即有效的状态转移)。 换言之,Consensus 必须是诚实的, 例如,当 Consensus 是拜占庭容错共识算法时, 如 Tendermint,拜占庭式区块生产者的数量可以少于三分之一。
    除非状态机满足上述全部属性,否则 IBC 协议 可能无法按预期工作,例如用户资产可能被窃取。请注意,特定客户端 类型可能还需要额外属性。
  • Height 指定状态机状态更新的顺序,例如序列号。 这意味着每个状态更新都映射到一个 Height。
  • ClientMessage 是由客户端类型定义的任意消息,中继者可以提交该消息以更新客户端。 ClientMessage 可以是常规更新,用于添加新的共识状态以进行证明验证;也可以包含 错误行为,从而应当冻结客户端。
  • ValidityPredicate 是一个函数,用于验证中继者为更新客户端而发送的 ClientMessage。 使用 ValidityPredicate 在计算上应当比执行 Consensus 更高效。
type ValidityPredicate = (clientState: bytes, trustedConsensusState: bytes, trustedHeight: Number) => (newConsensusState: bytes, newHeight: Number, err: Error)
  • ConsensusState 是状态机在某个特定 Height 上状态的可信视图。 它必须包含足够的信息,以使 ValidityPredicate 能够验证未来的状态更新, 然后据此生成新的 ConsensusState。
  • ClientState 是客户端的状态。它必须向更高层协议抽象暴露接口, 例如,用于验证在特定 Height 的特定路径上某个特定值存在性的证明函数。
  • MisbehaviourPredicate 是一个函数,用于检查 Consensus 的规则是否被破坏, 若被破坏,则客户端必须被冻结,即之后不能再生成任何 ConsensusState。 客户端被冻结后,针对它的验证也将失败。
type MisbehaviourPredicate = (clientState: bytes, trustedConsensusState: bytes, trustedHeight: Number, misbehaviour: bytes) => bool
  • Misbehaviour 是 MisbehaviourPredicate 用来判断是否发生 共识协议违规所需的证明。例如,当状态机 是一条区块链时,Misbehaviour 可能由两个已签名的区块头组成, 它们在同一个 Height 上对应不同的 ConsensusState。

期望属性

轻客户端必须提供状态验证函数,以便基于现有的 ConsensusState 安全地验证远程状态机的状态。 这些状态验证函数使更高层协议抽象能够 验证远程状态机状态中的子组件。 ValidityPredicate 必须反映远程状态机及其 Consensus 的行为,也就是说, ValidityPredicate 只能接受包含由远程状态机 Consensus 生成的状态更新的状态更新。 在发生错误行为时,ValidityPredicate 的行为可能与 远程状态机及其 Consensus 的行为不同(因为客户端不会执行远程状态机的 Consensus)。 在这种情况下,应当向宿主状态机提交 Misbehaviour, 从而导致客户端被冻结。客户端一旦被冻结,在客户端处理可以恢复之前, 必须先发生一种用于应对此情况的恢复机制。该恢复机制不属于 IBC 协议的范围,因为所需的具体恢复方式高度依赖具体场景。

技术规范

本规范概述了每种客户端类型必须定义的内容。客户端类型是一组定义, 包括运行轻客户端所需的数据结构、初始化逻辑、有效性谓词和错误行为谓词。 实现 IBC 协议的状态机可以支持任意数量的客户端 类型,并且每种客户端类型都可以使用不同的初始共识状态进行实例化,以跟踪 不同的共识实例。 具体的客户端类型及其规范定义在本仓库的轻客户端章节中。

数据结构

Height

Height 是由客户端类型定义的不透明数据结构。 它必须构成一个偏序集,并提供比较操作。
type Height
enum Ord {
  LT
  EQ
  GT
}

type compare = (h1: Height, h2: Height) => Ord
一个高度相对于另一个高度,可以是 LT(小于)、EQ(等于)或 GT(大于)。 在本规范其余部分中,>=、>、===、<、<= 都被定义为 compare 的别名。 高度类型还必须存在一个零元素,记为 0,它小于所有非零高度。

ConsensusState

ConsensusState 是由客户端类型定义的不透明数据结构,有效性谓词使用它来 验证新的提交和状态根。该结构很可能包含由 共识过程生成的最近一次提交,包括签名和验证者集元数据。 ConsensusState 必须从 Consensus 的实例生成,并且该实例会为每个 ConsensusState 分配唯一高度 (使得每个高度恰好对应一个共识状态)。 同一高度不得存在两个有效的 ConensusState。 此类事件称为“等价双签”(equivocation),并且必须被归类为 错误行为。一旦发生,应生成并提交相应证明,以便冻结客户端, 并在必要时使先前的状态根失效。
type ConsensusState = bytes
ConsensusState 必须定义一个 getTimestamp() 方法,用于返回与该共识状态关联的、以秒为单位的时间戳。 该时间戳必须是对手方状态机中使用并由 Consensus 达成一致的时间戳。
type getTimestamp = ConsensusState => uint64

ClientState

ClientState 是由某种客户端类型定义的不透明数据结构。 它可以维护任意内部状态,以跟踪已验证的根和过去的错误行为。 轻客户端在表示上是不透明的,不同的共识算法可以定义不同的轻客户端更新算法, 但它们必须向 IBC 处理器暴露这一组通用查询函数。
type ClientState = bytes
客户端类型必须定义一种方法,使用提供的客户端标识符、客户端状态和共识状态来初始化客户端状态,并按需写入内部状态。
type initialise = (identifier: Identifier, clientState: ClientState, consensusState: ConsensusState) => Void
客户端类型必须定义一种方法,用于获取当前高度(即最近一次通过验证的状态更新的高度)。
type latestClientHeight = (
  clientState: ClientState)
  => Height
客户端类型必须在客户端状态上定义一种方法,用于获取指定高度处的时间戳。
type getTimestampAtHeight = (
  clientState: ClientState,
  height: Height
) => uint64

ClientMessage

ClientMessage 是由客户端类型定义的不透明数据结构,用于提供更新客户端所需的信息。 可以将 ClientMessage 提交给关联的客户端,以添加新的 ConsensusState 或更新 ClientState。它们通常包含高度、证明、承诺根,以及可能的有效性谓词更新。
type ClientMessage = bytes

CommitmentProof

CommitmentProof 是由客户端类型定义的不透明数据结构。
type CommitmentProof = bytes
它用于验证在某个特定已终局化高度的状态中,某个特定键值对的存在或不存在 (该高度必然对应某个特定的承诺根)。

状态验证

客户端类型必须定义函数,用于认证客户端所跟踪的状态机的内部状态。 内部实现细节可以不同(例如,回环客户端可以直接从状态中读取,因此不需要任何证明)。 verifyMembership 是通用的证明验证方法,用于验证在指定高度下,给定 CommitmentPath 处某个值存在的证明。如果验证不成功,必须返回错误。 调用方应当根据 CommitmentPrefix 和标准化路径(定义见 ICS 4)构造完整的 CommitmentPath。
type verifyMembership = (
  clientState: ClientState,
  height: Height,
  proof: CommitmentProof,
  path: CommitmentPath,
  value: bytes)
  => Error
verifyNonMembership 是通用的证明验证方法,用于验证在指定高度下,给定 CommitmentPath 不存在的证明。如果验证不成功,必须返回错误。 调用方应当根据 CommitmentPrefix 和标准化路径(定义见 ICS 24)构造完整的 CommitmentPath。 由于该验证方法旨在给予客户端实现完整控制权,因此客户端可以通过验证一个非空哨兵值 ABSENCE 的存在,来支持那些不提供不存在性证明的链。因此,在这些特殊情况下,所提供的证明将是存在性证明,而客户端将验证在给定高度下,该路径下存储的是 ABSENCE 值。
type verifyNonMembership = (
  clientState: ClientState,
  height: Height,
  proof: CommitmentProof,
  path: CommitmentPath)
  => Error

实现策略

回环
本地状态机的回环客户端只需从本地状态读取,而它必须能够访问该状态。
简单签名
已知公钥的单体状态机客户端会检查由该本地状态机发送消息上的签名, 这些签名通过 Proof 参数提供。height 参数可以用作防重放保护的 nonce。 多重签名或门限签名方案也可以采用这种方式使用。
代理客户端
代理客户端通过验证另一个(代理)状态机对目标状态机的验证来工作,具体方式是: 在证明中首先包含代理状态机上客户端状态的证明,然后包含目标状态机子状态相对于 代理状态机上该客户端状态的二级证明。这样,代理客户端就无需自行存储和跟踪目标状态机的共识状态, 代价是增加了对代理状态机正确性的安全假设。
默克尔化状态树
对于具有默克尔化状态树的状态机客户端,这些函数可以实现为 MerkleTree 的存在性与不存在性证明。客户端实现可以选择针对对手链使用的特定树来实现这些方法,也可以使用通用树的 ICS-23 verifyMembership 或 verifyNonMembership 方法,结合存储在 ClientState 中的已验证 Merkle 根,以及描述树构造方式的 ProofSpec,来验证任意符合 ICS-23 的树在特定高度下状态中某些特定键值对的存在或不存在。在这种情况下,初始化客户端时必须向其提供 ICS-23 的 ProofSpec。

子协议

IBC 处理器必须实现下文定义的函数。

标识符校验

客户端存储在唯一的 Identifier 前缀下。 本 ICS 不要求客户端标识符必须以特定方式生成,只要求它们是唯一的。 不过,如果有需要,也可以限制 Identifier 的空间。 可以提供校验函数 validateClientIdentifier。
type validateClientIdentifier = (id: Identifier) => boolean
如果未提供,则默认的 validateClientIdentifier 将始终返回 true。
利用历史根
为了避免客户端更新(会改变状态根)与握手或数据包接收中携带证明的交易之间出现竞争条件, 许多 IBC 处理器函数允许调用方指定要引用的某个历史根,并通过高度进行查找。执行此操作的 IBC 处理器函数 必须确保同时对调用方传入的高度执行所有必要检查,以保证逻辑正确性。

CreateClient

使用客户端状态和初始共识状态调用 createClient 会创建一个新客户端。该客户端的发起者负责设置 ClientState 的全部初始参数以及初始信任根 ConsensusState。随后,客户端实现负责基于这些初始参数执行轻客户端的 ValidityPredicate。因此,一旦信任根被实例化,轻客户端便保证在 ClientState 所参数化的安全模型范围内维持该信任。如果用户曾验证某个客户端是对手链的有效客户端一次,那么只要 MisbehaviourPredicate 没有被触发,就可以保证它在未来仍然是有效客户端。然而,如果 MisbehaviourPredicate 被触发,则可以将其作为错误行为提交,以冻结 IBC 轻客户端操作。 CreateClient 输入: clientType: string:这是客户端类型,用于引用链上的某个特定轻客户端实现。CreateClient 消息将创建给定客户端类型的一个新实例。 ClientState: bytes:这是针对给定客户端类型定义的不透明客户端状态。它将包含用于验证客户端更新以及基于某个 ConsensusState 执行证明验证所需的任何参数。ClientState 对由该客户端类型实现的安全模型进行参数化。 ConsensusState: bytes:这是针对给定客户端类型定义的不透明共识状态。它是提供的初始共识状态,并且必须能够被 ValidityPredicate 用于向客户端添加新的 ConsensusState。初始 ConsensusState 也可以用于证明验证,但这并非必需。 Height: Number:这是与初始共识状态关联的高度。 CreateClient 前置条件:
  • 提供的 clientType 被该链支持,并且 IBC 处理器能够将请求路由到它。
CreateClient 后置条件:
  • 为该客户端生成唯一标识符 clientId
  • 提供的 ClientState 会持久化到状态中,并可通过 clientId 取回。
  • 提供的 ConsensusState 会持久化到状态中,并可通过 clientId 和 height 取回。
CreateClient 错误条件:
  • 提供的 ClientState 对于该客户端类型无效。
  • 提供的 ConsensusState 对于该客户端类型无效。
  • Height 不是正数。

RegisterCounterparty

IBC 第 2 版引入了一个 registerCounterparty 过程。使用 clientId 调用 registerCounterparty 将会注册对手方的 clientId,对手方将使用该标识写入面向我们链的 packet 消息。所有通往我们链的 ICS24 可证明路径都将以对手方 clientId 作为键,因此每个客户端都必须知晓对手方的标识符,以便构造用于键验证的路径,并确保客户端之间存在一条经过认证的 packet 数据流,且该数据流不会被其他客户端写入。 registerCounterparty 还包含对手方链要使用的 CommitmentPrefix。大多数链不会将 ICS24 直接存储在 MerkleTree 的根下,而是会在一个自定义前缀下存储标准化路径,因此必须将此信息提供给对手方客户端,才能正确验证证明。CommitmentPrefix 被定义为字节数组的数组,以支持嵌套 Merkle 树。在这种情况下,外层数组中的每个元素都是嵌套结构中各层树的键,顺序从最上层树到最低层树。此时,ICS24 路径会附加到最低层树的键上(即 commitment prefix 的最后一个元素),从而得到用于证明验证的完整 CommitmentPath。 RegisterCounterparty 输入: clientId: bytes:执行链上的 clientId。 counterpartyClientId: bytes:对手方链用于验证执行链的客户端标识符。 counterpartyCommitmentPrefix: []bytes:对手方链使用的前缀。 RegisterCounterparty 前置条件:
  • 已经为 clientId 创建了客户端
RegisterCounterparty 后置条件:
  • 给定 clientId 时,可以检索到 counterpartyClientId。
  • 给定 clientId 时,可以检索到 counterpartyCommitmentPrefix。
RegisterCounterparty 错误条件:
  • 不存在与给定 clientId 对应的客户端
  • 已经针对给定 clientId 调用过 RegisterCounterparty
注意:一旦双方都完成了客户端及对手方的注册,客户端之间的连接即告建立,客户端之间的 packet 流即可开始。预期用户在使用该连接发送 packet 之前,验证客户端和对手方是否设置正确。用户可以自行直接验证,也可以通过社会共识来验证。 注意:RegisterCounterparty 设置的信息对于使用我们客户端正确验证 IBC 消息证明至关重要。因此,必须对其进行适当认证。RegisterCounterparty 消息可以设计为无权限门槛,在这种情况下,这些字段必须通过客户端相对于对手方链进行认证,这可能会比较困难且繁琐。更推荐的做法是确保创建客户端的地址与注册对手方的地址相同。一旦客户端和对手方由同一创建者设置,用户就可以通过链下方式决定该配置是否安全。

Update

更新客户端是通过提交新的 ClientMessage 来完成的。Identifier 用于指向逻辑将要更新的已存储 ClientState。当新的 ClientMessage 使用已存储的 ClientState 和先前存储的 ConsensusState 通过 ValidityPredicate 验证后,客户端随后必须添加一个具有新 Height 的新 ConsensusState。 如果客户端无法再被更新(例如,信任期已经过去),则新的 packet 流将无法处理。此时必须进行人工干预,以重置客户端状态或迁移客户端。这无法完全安全地自动完成,但实现 IBC 的链可以选择允许治理机制执行这些操作(甚至可以在多签或合约中按客户端/连接/channel 细分授权)。 UpdateClient 输入: clientId: bytes:正在更新的客户端标识符。 clientMessage: bytes:根据给定 clientType 定义的、用于更新客户端的不透明 clientMessage。它必须包含我们希望从其更新的 trustedHeight。该 trustedHeight 将用于检索一个受信任的 ConsensusState,我们将利用它和 ValidityPredicate 更新到新的共识状态。 UpdateClient 前置条件:
  • 已经为 clientId 创建了客户端
UpdateClient 后置条件:
  • 一个新的 ConsensusState 被添加到客户端,并以新的 Height 持久化保存
  • 如果更新本身就是恶意行为的证明,实现 MAY 在 UpdateClient 中自动检测恶意行为(例如,给定高度已经存在不同的 ConsensusState,或者时间单调性被破坏)。建议在这种情况下自动冻结客户端,以避免还需要额外发送冗余的 submitMisbehaviour 消息。
UpdateClient 错误条件:
  • ClientMessage 中引用的受信任 ConsensusState 在状态中不存在
  • ValidityPredicate(clientState, trustedConsensusState, trustedHeight) 返回错误

Misbehaviour

如果对手方链的 Consensus 被破坏,那么 relayer 可以提交相关证明作为恶意行为。一旦客户端被冻结,就不能再进行任何更新,且所有证明验证都会失败。当对手方 Consensus 的信任恢复,并且执行链上因 Consensus 破坏而导致的任何无效状态都已回滚后,客户端可以通过链下协议解冻。 SubmitMisbehaviour 输入: clientId: bytes:将被冻结的客户端标识符。 clientMessage: bytes:根据给定 clientType 定义的、用于冻结客户端的不透明 clientMessage。它必须包含我们希望据以验证恶意行为的 trustedHeight。该 trustedHeight 将用于检索一个受信任的 ConsensusState,我们将利用它和 MisbehaviourPredicate 冻结客户端。它还必须包含所提交的恶意行为内容。 SubmitMisbehaviour 前置条件:
  • 已经为 clientId 创建了客户端。
SubmitMisbehaviour 后置条件:
  • 客户端被冻结,在再次解冻之前,更新和证明验证都将失败。
SubmitMisbehaviour 错误条件:
  • ClientMessage 中引用的受信任 ConsensusState 在状态中不存在。
  • MisbehaviourPredicate(clientState, trustedConsensusState, trustedHeight, misbehaviour) 返回 false。

VerifyMembership 和 VerifyNonmembership

IBC 核心 packet 处理器使用在 UpdateClient 中创建的共识状态来验证 ICS-4 标准化路径,以认证 packet 消息。为此,IBC packet 处理器会针对给定的 packet 流消息构造期望的键/值,并将期望的路径和值以及 relayer 提供的证明一并发送给客户端进行验证。需要注意的是,证明由 relayer 提供,但路径和值是 IBC packet 处理器针对给定 packet 构造的。因此,relayer 无法为未被发送的 packet 伪造证明。IBC Packet 处理器还必须能够证明给定路径的不属于性,以支持超时处理。因此,客户端必须暴露以下 verifyMembership 和 verifyNonMembership 方法:
type verifyMembership = (ClientState, Height, CommitmentProof, Path, Value) => boolean
type verifyNonMembership = (ClientState, Height, CommitmentProof, Path) => boolean
ProofVerification 输入:
  • clientId: bytes:将要验证证明的客户端标识符。
  • Height: Number:证明将据以验证的共识状态高度。
  • Path: CommitmentPath:被证明键的路径。在 IBC 协议中,这将是一个以对手方已注册 CommitmentPrefix 为前缀的 ICS24 标准化路径。Path 必须由 IBC 处理器根据 IBC 消息构造,绝不能由 relayer 提供,因为 relayer 不可信。
  • Value: Optional<bytes>:被证明的值。如果它非空,则这是成员证明;如果该值为 nil,则这是非成员证明。
ProofVerification 前置条件:
  • 已经为 clientId 创建了客户端。
  • 已为给定的 Height 存储了一个 ConsensusState。
ProofVerification 后置条件:
  • 在大多数情况下,证明验证应当是无状态的。如果证明验证属于签名检查,我们可能希望递增一个 nonce,以防止重放攻击。
ProofVerification 错误条件:
  • CommitmentProof 无法使用提供的 CommitmentPath 和 Value,结合为所提供 Height 检索到的 ConsensusState,成功完成验证。

属性与不变量

  • 客户端标识符是不可变的,并遵循先到先得原则。客户端不能被删除(允许删除可能会在标识符被重用时,导致未来重放过去的 packet)。

向后兼容性

不适用。

向前兼容性

IBC 实现可以按需添加新的客户端类型,只要它们符合此接口。

示例实现

请参阅 ibc-go 中轻客户端的实现,了解如何实现你自己的客户端示例:(https://github.com/cosmos/ibc-go/blob/main/modules/light-clients)。

历史

2019 年 3 月 5 日 - 初始草稿完成并作为 PR 提交 2019 年 5 月 29 日 - 多项修订,尤其是支持多个 commitment root 2019 年 8 月 15 日 - 为澄清客户端接口进行了重大重构 2020 年 1 月 13 日 - 针对客户端类型分离和路径修改的修订 2020 年 1 月 26 日 - 新增查询接口 2022 年 7 月 27 日 - 新增 verifyClientState 函数,并将 ClientState 移至 provableStore 2022 年 8 月 4 日 - 修改 ClientState 接口及其关联处理器,以对齐 02-client-refactor ADR 中的变更:(https://github.com/cosmos/ibc-go/pull/1871) 2024 年 8 月 22 日 - IBC/TAO V2 变更

版权

本文所有内容均依据 Apache 2.0 许可证授权。

Synopsis

The IBC protocol provides secure packet flow between applications on different ledgers by verifying the packet messages using clients of the counterparty state machines. While ICS-4 defines the core packet flow logic between two chains and the provable commitments they must make in order to communicate, this standard ICS-2 specifies how a chain verifies the IBC provable commitments of the counterparty which is crucial to securely receive and process a packet flow message arriving from the counterparty. This standard focuses on how to keep track of the counterparty consensus and verify the state machine; it also specifies the properties that consensus algorithms of state machines implementing the inter-blockchain communication (IBC) protocol are required to satisfy. These properties are necessary for efficient and safe verification in the higher-level protocol abstractions. The algorithm utilised in IBC to verify the state updates of a remote state machine is referred to as a validity predicate. Pairing a validity predicate with a trusted state (i.e., a state that the verifier assumes to be correct), implements the functionality of a light client (often shortened to client) for a remote state machine on the host state machine. In addition to state update verification, every light client is able to detect consensus misbehaviours through a misbehaviour predicate. Beyond the properties described in this specification, IBC does not impose any requirements on the internal operation of the state machines and their consensus algorithms. A state machine may consist of a single process signing operations with a private key (the so-called “solo machine”), a quorum of processes signing in unison, many processes operating a Byzantine fault-tolerant consensus algorithm (e.g., Tendermint), or other configurations yet to be invented — from the perspective of IBC, a state machine is defined entirely by its light client validation and misbehaviour detection logic. This standard also specifies how the light client’s functionality is registered and how its data is stored and updated by the IBC protocol. The stored client instances can be introspected by a third party actor, such as a user inspecting the state of the state machine and deciding whether or not to send an IBC packet.

Motivation

The IBC protocol needs to be able to verify updates to the state of another state machine (i.e., the remote state machine). This entails accepting only the state updates that were agreed upon by the remote state machine’s consensus algorithm. A light client of the remote state machine is the algorithm that enables the actor to verify state updates of that state machine. Note that light clients will generally not include validation of the entire state transition logic (as that would be equivalent to simply executing the other state machine), but may elect to validate parts of state transitions in particular cases. This standard formalises the light client model and requirements. As a result, the IBC protocol can easily be integrated with new state machines running new consensus algorithms, as long as the necessary light client algorithms fulfilling the listed requirements are provided. The IBC protocol can be used to interact with probabilistic-finality consensus algorithms. In such cases, different validity predicates may be required by different applications. For probabilistic-finality consensus, a validity predicate is defined by a finality threshold (e.g., the threshold defines how many block needs to be on top of a block in order to consider it finalized). As a result, clients could act as thresholding views of other clients: One write-only client could be used to store state updates (without the ability to verify them), while many read-only clients with different finality thresholds (confirmation depths after which state updates are considered final) are used to verify state updates. Client interfaces should also be constructed so that custom validation logic can be provided safely to define a custom client at runtime, as long as the underlying state machine can provide an appropriate gas metering mechanism to charge for compute and storage. On a host state machine which supports WASM execution, for example, the validity predicate and misbehaviour predicate could be provided as executable WASM functions when the client instance is created.

Definitions

  • Consensus is a state update generating algorithm. It takes the previous state of a state machine together with a set of messages (i.e., state machine transactions) and generates a valid state update of the state machine. Every state machine MUST have a Consensus that generates a unique, ordered list of state updates starting from a genesis state. This specification expects that the state updates generated by Consensus satisfy the following properties:
    • Every state update MUST NOT have more than one direct successor in the list of state updates. In other words, the state machine MUST guarantee finality and safety.
    • Every state update MUST eventually have a successor in the list of state updates. In other words, the state machine MUST guarantee liveness.
    • Every state update MUST be valid (i.e., valid state transitions). In other words, Consensus MUST be honest, e.g., in the case Consensus is a Byzantine fault-tolerant consensus algorithm, such as Tendermint, less than a third of block producers MAY be Byzantine.
    Unless the state machine satisfies all of the above properties, the IBC protocol may not work as intended, e.g., users’ assets might be stolen. Note that specific client types may require additional properties.
  • Height specifies the order of the state updates of a state machine, e.g., a sequence number. This entails that each state update is mapped to a Height.
  • ClientMessage is an arbitrary message defined by the client type that relayers can submit in order to update the client. The ClientMessage may be intended as a regular update which may add new consensus state for proof verification, or it may contain misbehaviour which should freeze the client.
  • ValidityPredicate is a function that validates a ClientMessage sent by a relayer in order to update the client. Using the ValidityPredicate SHOULD be more computationally efficient than executing Consensus.
type ValidityPredicate = (clientState: bytes, trustedConsensusState: bytes, trustedHeight: Number) => (newConsensusState: bytes, newHeight: Number, err: Error)
  • ConsensusState is the trusted view of the state of a state machine at a particular Height. It MUST contain sufficient information to enable the ValidityPredicate to validate future state updates, which can then be used to generate new ConsensusStates.
  • ClientState is the state of a client. It MUST expose an interface to higher-level protocol abstractions, e.g., functions to verify proofs of the existence of particular values at particular paths at particular Heights.
  • MisbehaviourPredicate is a function that checks whether the rules of Consensus were broken, in which case the client MUST be frozen, i.e., no subsequent ConsensusStates can be generated. Verification against the client after it is frozen will also fail.
type MisbehaviourPredicate = (clientState: bytes, trustedConsensusState: bytes, trustedHeight: Number, misbehaviour: bytes) => bool
  • Misbehaviour is the proof needed by the MisbehaviourPredicate to determine whether a violation of the consensus protocol occurred. For example, in the case the state machine is a blockchain, a Misbehaviour might consist of two signed block headers with different ConsensusState for the same Height.

Desired Properties

Light clients MUST provide state verification functions that provide a secure way to verify the state of the remote state machines using the existing ConsensusStates. These state verification functions enable higher-level protocol abstractions to verify sub-components of the state of the remote state machines. ValidityPredicates MUST reflect the behaviour of the remote state machine and its Consensus, i.e., ValidityPredicates accept only state updates that contain state updates generated by the Consensus of the remote state machine. In case of misbehavior, the behaviour of the ValidityPredicate might differ from the behaviour of the remote state machine and its Consensus (since clients do not execute the Consensus of the remote state machine). In this case, a Misbehaviour SHOULD be submitted to the host state machine, which would result in the client being frozen. Once the client is frozen, a recovery mechanism to address the situation must occur before client processing can presume. This recovery mechanism is out-of-scope of the IBC protocol as the specific recovery needed is highly case-dependent.

Technical Specification

This specification outlines what each client type must define. A client type is a set of definitions of the data structures, initialisation logic, validity predicate, and misbehaviour predicate required to operate a light client. State machines implementing the IBC protocol can support any number of client types, and each client type can be instantiated with different initial consensus states in order to track different consensus instances. Specific client types and their specifications are defined in the light clients section of this repository.

Data Structures

Height

Height is an opaque data structure defined by a client type. It must form a partially ordered set & provide operations for comparison.
type Height
enum Ord {
  LT
  EQ
  GT
}

type compare = (h1: Height, h2: Height) => Ord
A height is either LT (less than), EQ (equal to), or GT (greater than) another height. >=, >, ===, <, <= are defined through the rest of this specification as aliases to compare. There must also be a zero-element for a height type, referred to as 0, which is less than all non-zero heights.

ConsensusState

ConsensusState is an opaque data structure defined by a client type, used by the validity predicate to verify new commits & state roots. Likely the structure will contain the last commit produced by the consensus process, including signatures and validator set metadata. ConsensusState MUST be generated from an instance of Consensus, which assigns unique heights for each ConsensusState (such that each height has exactly one associated consensus state). There MUST NOT be two valid ConensusStates for the same height. Such an event is called an “equivocation” and MUST be classified as misbehaviour. Should one occur, a proof should be generated and submitted so that the client can be frozen and previous state roots invalidated as necessary.
type ConsensusState = bytes
The ConsensusState MUST define a getTimestamp() method which returns the timestamp in seconds associated with that consensus state. This timestamp MUST be the timestamp used in the counterparty state machine and agreed to by Consensus.
type getTimestamp = ConsensusState => uint64

ClientState

ClientState is an opaque data structure defined by a client type. It may keep arbitrary internal state to track verified roots and past misbehaviours. Light clients are representation-opaque — different consensus algorithms can define different light client update algorithms — but they must expose this common set of query functions to the IBC handler.
type ClientState = bytes
Client types MUST define a method to initialise a client state with the provided client identifier, client state and consensus state, writing to internal state as appropriate.
type initialise = (identifier: Identifier, clientState: ClientState, consensusState: ConsensusState) => Void
Client types MUST define a method to fetch the current height (height of the most recent validated state update).
type latestClientHeight = (
  clientState: ClientState)
  => Height
Client types MUST define a method on the client state to fetch the timestamp at a given height
type getTimestampAtHeight = (
  clientState: ClientState,
  height: Height
) => uint64

ClientMessage

A ClientMessage is an opaque data structure defined by a client type which provides information to update the client. ClientMessages can be submitted to an associated client to add new ConsensusState(s) and/or update the ClientState. They likely contain a height, a proof, a commitment root, and possibly updates to the validity predicate.
type ClientMessage = bytes

CommitmentProof

CommitmentProof is an opaque data structure defined by the client type.
type CommitmentProof = bytes
It is utilised to verify presence or absence of a particular key/value pair in state at a particular finalised height (necessarily associated with a particular commitment root).

State verification

Client types must define functions to authenticate internal state of the state machine which the client tracks. Internal implementation details may differ (for example, a loopback client could simply read directly from the state and require no proofs). verifyMembership is a generic proof verification method which verifies a proof of the existence of a value at a given CommitmentPath at the specified height. It MUST return an error if the verification is not successful. The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 4).
type verifyMembership = (
  clientState: ClientState,
  height: Height,
  proof: CommitmentProof,
  path: CommitmentPath,
  value: bytes)
  => Error
verifyNonMembership is a generic proof verification method which verifies a proof of absence of a given CommitmentPath at the specified height. It MUST return an error if the verification is not successful. The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 24). Since the verification method is designed to give complete control to client implementations, clients can support chains that do not provide absence proofs by verifying the existence of a non-empty sentinel ABSENCE value. Thus in these special cases, the proof provided will be an Existence proof, and the client will verify that the ABSENCE value is stored under the given path for the given height.
type verifyNonMembership = (
  clientState: ClientState,
  height: Height,
  proof: CommitmentProof,
  path: CommitmentPath)
  => Error

Implementation strategies

Loopback
A loopback client of a local state machine merely reads from the local state, to which it must have access.
Simple signatures
A client of a solo state machine with a known public key checks signatures on messages sent by that local state machine, which are provided as the Proof parameter. The height parameter can be used as a replay protection nonce. Multi-signature or threshold signature schemes can also be used in such a fashion.
Proxy clients
Proxy clients verify another (proxy) state machine’s verification of the target state machine, by including in the proof first a proof of the client state on the proxy state machine, and then a secondary proof of the sub-state of the target state machine with respect to the client state on the proxy state machine. This allows the proxy client to avoid storing and tracking the consensus state of the target state machine itself, at the cost of adding security assumptions of proxy state machine correctness.
Merklized state trees
For clients of state machines with Merklized state trees, these functions can be implemented as MerkleTree Existence and NonExistence proofs. Client implementations may choose to implement these methods for the specific tree used by the counterparty chain or they can use the tree-generic ICS-23 verifyMembership or verifyNonMembership methods, using a verified Merkle root stored in the ClientState, to verify presence or absence of particular key/value pairs in state at particular heights for any ICS-23 compliant tree given a ProofSpec that describes how the tree is constructed. In this case, the ICS-23 ProofSpec MUST be provided to the client on initialization.

Sub-protocols

IBC handlers MUST implement the functions defined below.

Identifier validation

Clients are stored under a unique Identifier prefix. This ICS does not require that client identifiers be generated in a particular manner, only that they be unique. However, it is possible to restrict the space of Identifiers if required. The validation function validateClientIdentifier MAY be provided.
type validateClientIdentifier = (id: Identifier) => boolean
If not provided, the default validateClientIdentifier will always return true.
Utilising past roots
To avoid race conditions between client updates (which change the state root) and proof-carrying transactions in handshakes or packet receipt, many IBC handler functions allow the caller to specify a particular past root to reference, which is looked up by height. IBC handler functions which do this must ensure that they also perform any requisite checks on the height passed in by the caller to ensure logical correctness.

CreateClient

Calling createClient with the client state and initial consensus state creates a new client. The intiator of this client is responsible for setting all of the initial parameters of the ClientState and the initial root-of-trust ConsensusState. The client implementation is then responsible for executing the light client ValidityPredicate against these initial parameters. Thus, once a root-of-trust is instantiated; the light client guarantees to preserve that trust within the confines of the security model as parameterized by the ClientState. If a user verifies that a client is a valid client of the counterparty chain once, they can be guaranteed that it will remain a valid client into the future so long as the MisbehaviourPredicate is not triggered. If the MisbehaviourPredicate is triggered however, this can be submitted as misbehaviour to freeze the IBC light client operations. CreateClient Inputs: clientType: string: This is the client-type that references a particular light client implementation on the chain. The CreateClient message will create a new instance of the given client-type. ClientState: bytes: This is the opaque client state as defined for the given client type. It will contain any parameters needed for verifying client updates and proof verification against a ConsensusState. The ClientState parameterizes the security model as implemented by the client type. ConsensusState: bytes: This is the opaque consensus state as defined for the given client type. It is the initial consensus state provided and MUST be capable of being used by the ValidityPredicate to add new ConsensusStates to the client. The initial ConsensusState MAY also be used for proof verification but it is not necessary. Height: Number: This is the height that is associated with the initial consensus state. CreateClient Preconditions:
  • The provided clientType is supported by the chain and can be routed to by the IBC handler.
CreateClient PostConditions:
  • A unique identifier clientId is generated for the client
  • The provided ClientState is persisted to state and retrievable given the clientId.
  • The provided ConsensusState is persisted to state and retrievable given the clientId and height.
CreateClient ErrorConditions:
  • The provided ClientState is invalid given the client type.
  • The provided ConsensusState is invalid given the client type.
  • The Height is not a positive number.

RegisterCounterparty

IBC Version 2 introduces a registerCounterparty procedure. Calling registerCounterparty with the clientId will register the counterparty clientId that the counterparty will use to write packet messages intended for our chain. All ICS24 provable paths to our chain will be keyed on the counterparty clientId, so each client must be aware of the counterparty’s identifier in order to construct the path for key verification and ensure there is an authenticated stream of packet data between the clients that do not get written to by other clients. The registerCounterparty also includes the CommitmentPrefix to use for the counterparty chain. Most chains will not store the ICS24 directly under the root of a MerkleTree and will instead store the standardized paths under a custom prefix, thus the counterparty client must be given this information to verify proofs correctly. The CommitmentPrefix is defined as an array of byte arrays to support nested Merkle trees. In this case, each element of the outer array is a key for each tree in the nested structure ordered from the top-most tree to the lowest level tree. In this case, the ICS24 path is appended to the key of the lowest-level tree (i.e. the last element of the commitment prefix) in order to get the full CommitmentPath for proof verification. RegisterCounterparty Inputs: clientId: bytes: The clientId on the executing chain. counterpartyClientId: bytes: The identifier of the client used by the counterparty chain to verify the executing chain. counterpartyCommitmentPrefix: []bytes: The prefix used by the counterparty chain. RegisterCounterparty Preconditions:
  • A client has already been created for the clientId
RegisterCounterparty Postconditions:
  • The counterpartyClientId is retrievable given the clientId.
  • The counterpartyCommitmentPrefix is retrievable given the clientId.
RegisterCounterparty ErrorConditions:
  • There does not exist a client for the given clientId
  • RegisterCounterparty has already been called for the given clientId
NOTE: Once the clients and counterparties have been registered on both sides, the connection between the clients is established and packet flow between the clients may commence. Users are expected to verify that the clients and counterparties are set correctly before using the connection to send packets. They may do this directly themselves or through social consensus. NOTE: RegisterCounterparty is setting information that will be crucial for proper proof verification of IBC messages using our client. Thus, it must be authenticated properly. The RegisterCounterparty message can be permissionless in which case the fields must be authenticated against the counterparty chain using the client which may prove difficult and cumbersome. It is RECOMMENDED to simply ensure that the client creator address is the same as the one that registers the counterparty. Once the client and counterparty are set by the same creator, users can decide if the configuration is secure out-of-band.

Update

Updating a client is done by submitting a new ClientMessage. The Identifier is used to point to the stored ClientState that the logic will update. When a new ClientMessage is verified using the ValidityPredicate with the stored ClientState and a previously stored ConsensusState, the client MUST then add a new ConsensusState with a new Height. If a client can no longer be updated (if, for example, the trusting period has passed), then new packet flow will not be able to be processed. Manual intervention must take place to reset the client state or migrate the client. This cannot safely be done completely automatically, but chains implementing IBC could elect to allow governance mechanisms to perform these actions (perhaps even per-client/connection/channel in a multi-sig or contract). UpdateClient Inputs: clientId: bytes: The identifier of the client being updated. clientMessage: bytes: The opaque clientMessage to update the client as defined by the given clientType. It MUST include the trustedHeight we wish to update from. This trustedHeight will be used to retrieve a trusted ConsensusState which we will use to update to a new consensus state using the ValidityPredicate. UpdateClient Preconditions:
  • A client has already been created for the clientId
UpdateClient Postconditions:
  • A new ConsensusState is added to the client and persisted with a new Height
  • Implementations MAY automatically detect misbehaviour in UpdateClient if the update itself is proof of misbehaviour (e.g. There is already a different ConsensusState for the given height, or time monotonicity is broken). It is recommended to automatically freeze the client in this case to avoid having to send a redundant submitMisbehaviour message.
UpdateClient ErrorConditions:
  • The trusted ConsensusState referenced in the ClientMessage does not exist in state
  • ValidityPredicate(clientState, trustedConsensusState, trustedHeight) returns an error

Misbehaviour

If Consensus of the counterparty chain is violated, then the relayer can submit proof of this as misbehaviour. Once the client is frozen, no updates may take place and all proof verification will fail. The client may be unfrozen by an out-of-band protocol once trust in the counterparty Consensus is restored and any invalid state caused by the break in Consensus is reverted on the executing chain. SubmitMisbehaviour Inputs: clientId: bytes: The identifier of the client being frozen. clientMessage: bytes: The opaque clientMessage to freeze the client as defined by the given clientType. It MUST include the trustedHeight we wish to verify misbehaviour from. This trustedHeight will be used to retrieve a trusted ConsensusState which we will use to freeze the client given the MisbehaviourPredicate. It MUST also include the misbehaviour being submitted. SubmitMisbehaviour Preconditions:
  • A client has already been created for the clientId.
SubmitMisbehaviour Postconditions:
  • The client is frozen, update and proof verification will fail until client is unfrozen again.
SubmitMisbehaviour ErrorConditions:
  • The trusted ConsensusState referenced in the ClientMessage does not exist in state.
  • MisbehaviourPredicate(clientState, trustedConsensusState, trustedHeight, misbehaviour) returns false.

VerifyMembership and VerifyNonmembership

The IBC core packet handler uses the consensus states created in UpdateClient to verify ICS-4 standardized paths to authenticate packet messages. In order to do this, the IBC packet handler constructs the expected key/value for the given packet flow message and sends the expected path and value to the client along with the relayer-provided proof to the client for verification. Note that the proof is relayer provided, but the path and value are constructed by the IBC packet handler for the given packet. Thus, the relayer cannot forge proofs for packets that did not get sent. IBC Packet handler must also have the ability to prove nonmembership of a given path in order to enable timeout processing. Thus, clients must expose the following verifyMembership and verifyNonMembership methods:
type verifyMembership = (ClientState, Height, CommitmentProof, Path, Value) => boolean
type verifyNonMembership = (ClientState, Height, CommitmentProof, Path) => boolean
ProofVerification Inputs:
  • clientId: bytes: The identifier of the client that will verify the proof.
  • Height: Number: The height for the consensus state that the proof will be verified against.
  • Path: CommitmentPath: The path of the key being proven. In the IBC protocol, this will be an ICS24 standardized path prefixed by the CommitmentPrefix registered on the counterparty. The Path MUST be constructed by the IBC handler given the IBC message, it MUST NOT be provided by the relayer as the relayer is untrusted.
  • Value: Optional<bytes>: The value being proven. If it is non-empty this is a membership proof. If the value is nil, this is a non-membership proof.
ProofVerification Preconditions:
  • A client has already been created for the clientId.
  • A ConsensusState is stored for the given Height.
ProofVerification Postconditions:
  • Proof verification should be stateless in most cases. In the case that the proof verification is a signature check, we may wish to increment a nonce to prevent replay attacks.
ProofVerification Errorconditions:
  • CommitmentProof does not successfully verify with the provided CommitmentPath and Value with the retrieved ConsensusState for the provided Height.

Properties & Invariants

  • Client identifiers are immutable & first-come-first-serve. Clients cannot be deleted (allowing deletion would potentially allow future replay of past packets if identifiers were re-used).

Backwards Compatibility

Not applicable.

Forwards Compatibility

New client types can be added by IBC implementations at-will as long as they conform to this interface.

Example Implementations

Please see the ibc-go implementations of light clients for examples of how to implement your own: (https://github.com/cosmos/ibc-go/blob/main/modules/light-clients).

History

Mar 5, 2019 - Initial draft finished and submitted as a PR May 29, 2019 - Various revisions, notably multiple commitment-roots Aug 15, 2019 - Major rework for clarity around client interface Jan 13, 2020 - Revisions for client type separation & path alterations Jan 26, 2020 - Addition of query interface Jul 27, 2022 - Addition of verifyClientState function, and move ClientState to the provableStore August 4, 2022 - Changes to ClientState interface and associated handler to align with changes in 02-client-refactor ADR: (https://github.com/cosmos/ibc-go/pull/1871) August 22, 2024 - Changes for IBC/TAO V2 All content herein is licensed under Apache 2.0.