简介

IBC v2 是一种端到端协议,用于在不同分布式账本上的模块之间进行可靠且经过认证的通信。IBC 对共识算法或状态机不做任何假设。只要分布式账本满足ICS-24 主机要求中的最低要求,就能够支持 IBC v2 协议,并与 IBC v2 网络中的任何应用进行通信。 IBC v2 协议可以概念化为三个不同的层次:IBC CLIENTS、IBC CORE 和 IBC APPS。IBC APPS 是希望在 IBC v2 网络中跨不同账本彼此通信的模块。一个例子是 ICS-20 同质化代币转账,它通过使用 IBC CORE 发送代币数据包,并在从对端 ICS20 应用发送或接收 ICS-20 代币数据包时执行托管或铸造逻辑,从而实现代币从一个账本安全发送到另一个账本。 IBC CLIENTS 识别并验证对手方账本的状态。IBC CLIENT 负责跟踪状态机的更新,并针对给定更新验证状态。 IBC CORE 是实现传输、认证和排序语义(下文称为 IBC/TAO)的处理器,它使用 IBC CLIENT 对数据包进行认证,然后将应用数据包数据发送给负责处理应用数据的 IBC APP。因此,每一层都有特定且彼此隔离的职责。IBC CLIENT 只需要验证对手方状态的键值证明。IBC APP 只需要处理来自对手方应用的应用数据。IBC CORE 使用 IBC CLIENT 作为验证预言机,为 IBC APP 提供 SendPacket、RecvPacket、AcknowledgePacket、TimeoutPacket 的经认证 IBC 数据包流转能力。 本文档的目标是对 IBC V2 协议提供一个基础概览。在适当之处,会突出说明它与 IBC v1 的区别。有关各层的详细规范,请参阅 ICS 标准。

规范

IBC 客户端

IBC Client 跟踪对手方状态更新,并向 IBC CORE 暴露对手方状态的验证能力。IBC 客户端实现通过两种不同的数据结构来实现这一点:ClientState 和 ConsensusState。从 IBC 的视角看,它们是不透明字节,由具体的轻客户端实现定义。ClientState 用于封装不应随高度变化而改变的对手方共识参数,这可以包括链标识符以及诸如质押解绑期之类的安全参数。另一方面,ConsensusState 是对手方共识在特定高度下的一个视图或快照。在几乎所有情况下,这个视图都会是对手方共识的高度压缩表示。IBC CLIENT 不会存储对手方链的完整状态,也不会执行对手方链的所有交易,因为那将等同于托管一个全节点。常见模式是由对手方共识在每次状态更新时创建对手方状态的 Merklized 承诺。IBC CLIENT 可以将 Merkle 根哈希加入 ConsensusState,然后针对已存储共识状态中的根哈希验证成员证明或非成员证明。 IBC CLIENT 封装了一种特定的安全模型,它可以是从多重签名桥委员会到对手方共识算法的完全验证轻客户端的任何形式。是否接受这种安全模型,由在该客户端上发送数据包的用户自行决定。 IBC 客户端负责获取对手方共识的初始视图,并使用客户端中实例化的安全模型,从这个可信起点更新该视图。这个初始对手方共识被公理化地信任。因此,IBC 在协议内部并不知道某个特定客户端正在验证哪条链。用户可以检查某个客户端,并自行验证该客户端是否正在跟踪他们关心的链(即验证某个特定共识状态是否与目标对手方链的共识输出相匹配),以及 ClientState 中编码的安全模型参数是否令人满意。用户只需要验证客户端一次,可以自行验证,也可以通过社会共识完成;一旦建立了这种初始信任,IBC CLIENT 就必须基于参数化安全模型,从先前受信任的视图持续更新对手方状态视图。因此,一旦用户信任某个轻客户端,就可以保证这种信任不会被客户端破坏。 如果对手方共识违反了安全模型,IBC CLIENT 实现必须提供冻结客户端并阻止进一步更新和验证的能力。此类违规的证据称为 Misbehaviour,一旦验证通过,客户端将被冻结,并暂停针对该客户端的所有数据包处理。已经造成的损害无法在协议内自动回滚,但这一机制可以确保攻击尽快被阻止,以便带外恢复机制进行干预(例如治理)。在解冻客户端并恢复数据包处理之前,该恢复机制应确保对手方上的共识违规被纠正,并在可能范围内回滚任何无效状态。 IBC CLIENT 必须向中继者提供外部端点,用于初始化客户端、更新客户端以及提交错误行为。中继者是离线进程,能够访问网络中其他链的全节点。 这些端点的具体实现将取决于所针对的具体共识机制。共识算法本身的选择是任意的,它可以是像 CometBFT 这样的权益证明算法,也可以是受信任权威的多重签名,或者是依赖额外底层客户端来验证其共识的 rollup。不过,轻客户端必须能够为给定的状态机快照定义终局性,这可以通过单槽终局性,也可以通过终局性装置来实现。 因此,这些端点本身应接受传入客户端端点参数的任意字节,因为将这些字节反序列化为预期结构的工作,应由各个具体客户端实现自行负责。
// initializes client with a starting client state containing all light client parameters
// and an initial consensus state that will act as a trusted seed from which to verify future headers
function createClient(
    clientState: bytes,
    consensusState: bytes,
): (id: bytes, err: error)

// once a client has been created, it can be referenced with the identifier and passed the header
// to keep the client up-to-date. In most cases, this will cause a new consensus state derived from the header
// to be stored in the client
function updateClient(
    clientId: bytes,
    header: bytes,
): error

// once a client has been created, relayers can submit misbehaviour that proves the counterparty chain violated the trust model.
// The light client must verify the misbehaviour using the trust model of the consensus mechanism
// and execute some custom logic such as freezing the client from accepting future updates and proof verification.
function submitMisbehaviour(
    clientId: bytes,
    misbehaviour: bytes,
): error
随着中继者持续更新客户端并向客户端添加 ConsensusState,IBC CORE 将使用暴露出的验证端点 VerifyMembership 和 VerifyNonMembership 来验证来自对手方的传入数据包流消息。
// verifies a membership of a path and value in the counterparty chain identified by the provided clientId
// against a particular ConsensusState identified by the provided height
function verifyMembership(
    clientId: bytes,
    height: Number,
    proof: bytes,
    path: CommitmentPath,
    value: bytes
): error

// verifies the nonmembership of a path in the counterparty chain identified by the provided clientId
// against a particular ConsensusState identified by the provided height
function verifyNonMembership(
    cliendId: bytes,
    height: Number,
    proof: bytes,
    path: CommitmentPath
): error

Core IBC 功能

IBC 的本质,是让位于不同区块链上、采用不同共识机制的应用,能够通过客户端支撑的安全性彼此通信。因此,IBC 需要上文所描述的客户端,以及定义其希望发送和接收的数据包数据的 IBC 应用。 除了这些层之外,IBC v1 还引入了连接和通道抽象,用于连接这两个基础层。IBC v2 旨在将连接层和通道层中仅有必要的部分压缩为一个无握手的单一数据包处理器,但在此之前,必须先理解它们当前提供了哪些服务。 IBC v1 连接的属性:
  • 验证对手方客户端的有效性
  • 在两侧建立唯一标识符,以表示共享的抽象理解(即连接)
  • 就 IBC 版本和支持的特性达成一致
  • 允许基于同一对客户端构建多个连接
  • 建立延迟周期,从而使这一安全参数能够针对同一客户端配对下的不同连接进行不同实例化。
  • 定义支持哪些通道排序方式
IBC v1 通道的属性:
  • 将应用隔离到专用的一对一通信通道中。这可以防止应用写入彼此的通道。
  • 允许应用就应用参数达成一致(版本协商)。确保双方都能理解彼此的通信,并运行相互兼容的逻辑。这种版本协商是一个多步骤过程,允许最终确定的版本与最初提议的版本存在显著差异
  • 建立通道的排序方式
  • 为任一链上的应用建立唯一标识符,以便它们在发送和接收数据包时彼此引用。
  • 应用协议可以通过升级握手随时间持续升级,从而允许同一通道在可能已经累积状态的情况下,使用新的、双方一致同意的应用数据包数据格式以及相关的新逻辑。
  • 确保数据包流数据报(Send、Receive、Acknowledge、Timeout)恰好一次交付
  • 确保有效的数据包流转:(Send => Receive => Acknowledge) XOR (Send => Timeout)

标识对手方

在核心 IBC 中,连接和通道握手用于确保对手方客户端的有效性,确保 IBC 与应用版本彼此兼容,并为双方提供各自用于引用对手方的唯一标识符。 由于我们在 IBC V2 中移除了握手机制,因此必须采用另一种方式让链获得关于对手方的认知。借助客户端,我们可以证明对手方上的任意键/值路径。然而,如果不知道对手方在向我们发送消息时使用的是哪个标识符;我们就无法区分哪些消息是对手方向本链发送的,哪些消息是对手方向其他链发送的。大多数实现无法直接将 ICS-24 路径作为全局命名空间中的键来存储;而是会写入一个保留的、带前缀的键空间,以避免与其他应用状态写入冲突。因此,我们必须掌握的对手方信息既包括它为我们的链使用的标识符,也包括它写入可证明的 ICS-24 路径时所使用的键前缀。 因此,IBC V2 将引入一条新消息 RegisterCounterparty,用于将我们链上的对手方客户端与我们关于对手方的客户端关联起来。这样一来,如果 RegisterCounterparty 消息被正确地提交到双方,那么双方都会拥有镜像的 <client,client> 对,可将其视为该数据包所关联的发送链和接收链的标识符。假设这些信息是正确的,那么每一侧的客户端都是唯一的,并能在两条链之间提供经过认证的数据包流。如果 RegisterCounterparty 消息提交了错误的 clientID,就可能导致无效行为;但这与中继者本应为目标链提交正确客户端,却提交了无效客户端的情况是等价的。在最简单的情况下,我们可以依赖链下的社会共识,只在有效的 <client, client> 对上发送数据,这些配对代表用户期望的链之间的连接;正如在 IBC V1 中,我们同样依赖链下社会共识来确认某个给定的 clientID 及其之上的通道是目标链有效且规范的标识符。
interface Counterparty {
    clientId: bytes
    counterpartyPrefix: []bytes
}
这个 Counterparty 将以我们链上现有的客户端标识符作为键。这样,双方都可以访问彼此的客户端标识符。这实际上创建了一个连接,在两侧使用唯一标识符来引用彼此的共识。因此,在 IBC V2 中,最终形成的 client, client 配对替代了 IBC V1 中独立存在的连接层。 RegisterCounterparty 方法允许实现进行认证,并可在存储所提供的对手方标识符之前完成校验。最强的认证方式,是在认证中包含我们链的有效 clientState 和 consensus state,并附带证明表明它们被存储在所声明的对手方标识符之下。这等价于连接握手中执行的 validateSelfClient 逻辑。 一种更简单但更弱的认证方式,则只是检查 RegisterCounterparty 消息是否由初始化该客户端的同一个中继者发送。这样一来,客户端参数就完全由中继者初始化。因此,用户在使用这些标识符发送数据包之前,必须验证该客户端确实指向正确的链,并且对手方标识符也是正确的。在实践中,这通常通过社会共识来验证。

IBC V2 数据包处理

IBC V2 只负责在两条通过链上轻客户端进行通信并相互标识的链之间提供数据包传递,这些轻客户端定义见 ICS-02;而应用数据包则按照 ICS-04 中定义的数据包流语义,被路由到各自对应的 IBC 应用。正如上文所述,数据包中的 clientID 将告知 IBC 路由器把数据包发送到哪条链,以及收到的数据包来自哪条链,而负载中的 portID 则指定该数据包应被发送到路由器上的哪个应用。 因此,一旦两条链为彼此建立了带有特定标识符的客户端,它们就可以像下面这样发送 IBC 数据包。
interface Packet {
  sequence: uint64
  timeoutTimestamp: uint64
  sourceClientId: Identifier // identifier of the destination client on sender chain
  destClientId: Identifier // identifier of the sender client on the destination chain
  payload: []Payload
}
由于数据包是通过底层轻客户端被直接寻址的,因此不再需要任何握手。取而代之的是,数据包发送方必须能够提供正确的 <client, client> 配对。 使用错误的源客户端发送数据包,等价于在错误的源通道上发送数据包。在某个客户端上使用错误配置的对手方发送数据包属于错误,数据包会被拒绝。如果为新客户端设置了错误的对手方,那么这就是 IBC V2 配置过程中的误配置。在这种情况下可能出现不可预期的行为,不过预期用户会在使用这些客户端标识符发送数据包之前,先验证双方的对手方配置是否正确。这种验证可以直接完成,也可以通过社会共识完成。 如果客户端和对手方标识符配置正确,那么 IBC 的正确性与健全性属性就成立。IBC 数据包流能够保证成功。如果对手方配置错误,那么正如我们接下来将看到的,预期的目标端将无法正确验证该数据包,因此该数据包最终只会超时。 Payload 包含所有应用特定的信息。其中既包括发送方应用希望发送给接收方应用的不透明应用数据;也包括用于解码和处理该应用数据的 Encoding 与 Version。需要注意的是,这与 IBC V1 不同,在 V1 中,关于如何处理应用数据的这些元数据是在通道握手期间协商出来的。而在这里,每个数据包都携带关于其自身数据应如何处理的信息。这使得 Version 和 Encoding 可以在不同数据包之间变化,从而允许应用进行异步升级,并乐观地向对手方发送采用新编码和新版本的数据包。如果对手方应用支持接收这种新负载,它就会被成功处理;否则接收将直接报错,而发送方应用会在收到 ErrorAcknowledgement 后回滚状态。这会增加应用在处理数据包时出错的可能性,但也极大提升了 IBC 应用随时间升级和演进的灵活性。类似地,发送方和接收方应用上的 portID 也不再通过通道握手预先协商,而是包含在负载中。因此在 IBC v2 中;发送方应用只需在负载中将接收方的 portID 指定为接收者,就可以把数据包路由到接收端上的任何其他应用。应用有责任通过校验负载中提供的源 portID 和目标 portID,来限制自己愿意与哪些对手方应用通信。因此,按数据包携带的 Payload 替代了 IBC V1 中独立存在的通道层。 关于 Payload 结构的更多细节,请参见 ICS-04。

在路由器上注册 IBC 应用

IBC CORE 包含一些路由器,它们将保留的应用 portID 映射到各自的 IBC 应用,同时也维护从 clientID 到各个 IBC 客户端的映射。
type IBCRouter struct {
    apps: portID -> IBCApp
    clients: clientId -> IBCClient
}

数据包流程

有关数据包流程的详细规范,请参阅 ICS-04。 IBC 定义的数据包流程消息包括:SendPacket、ReceivePacket、AcknowledgePacket 和 TimeoutPacket。SendPacket 最常由希望发起跨链操作(例如代币转账)的用户行为触发,它会将一个数据包从发送链上的某个应用发送到目标链上的某个应用。其余所有消息都是对手方操作的结果,因此必须由链下中继器提交;该中继器需要能够提交对手方证明,以认证该消息是有效的。例如,只有当中继器能够通过向我们的链上客户端提交证明,证明对手方确实向我们的链发送了一个数据包时,RecvPacket 消息才能被提交。源链会按照 ICS-04 标准化的 commitment 路径提交该数据包,该路径由 packet.sourceClientId 和 packet.sequence 构造。由于 packet.sourceClientId 是源链上对目标链的唯一引用,因此存储在该路径下的数据包 commitment 可以保证是源链打算发送给目标链的数据包。目标链可以使用其链上客户端(由 packet.destClientId 标识)验证该路径。 类似地,AcknowledgePacket 和 TimeoutPacket 是在尝试接收数据包之后回传到发送链的消息。如果数据包接收成功,则会在 ICS-04 标准化的 acknowledgement 路径下,使用 packet.destClientId 和 packet.sequence 写入一个应用特定的确认。发送链可以使用由 packet.sourceClientId 标识的链上客户端验证该路径,从而确认中继器提供的 acknowledgment 确实已由接收链提交。由于 packet.destClientId 是目标链上对发送链的唯一引用,而 sequence 在从源链到目标链的数据包流中也是唯一的,因此我们可以保证该 acknowledgement 是针对此前使用给定 sourceClientId 和 sequence 发送的数据包所写入的。随后,该 acknowledgement 会交给发送应用,以便针对该确认执行相应的应用逻辑。 当数据包接收失败时,会调用 TimeoutPacket。所有兼容实现都必须在成功接收数据包时,向标准化的 ICS-04 receipt 路径写入一个非空的哨兵值。该 receipt 路径由 packet.destClientId 和 packet.sequence 构造。因此,如果在数据包超时时间已过后该值仍不存在,我们就可以保证该数据包已经超时。发送链会验证中继器为给定数据包的 receipt 路径提供的 NonMembership 证明;如果验证成功,则超时被确认,并执行发送应用的超时逻辑。请注意,非成员证明必须针对一个执行时间晚于该数据包超时戳的共识状态进行验证,并且在超时经过之后,目标链上的数据包接收必须失败。这可确保同一个数据包不会在源链上被判定超时、同时又在目标链上被接收。 因此,数据包处理器通过构造必要的路径和值来实现这些消息的处理逻辑,以便按照 ICS-04 中的规定对消息进行认证;随后,它会按照数据包中的指定,将成员证明或非成员证明的验证路由到相关的 ICS-02 客户端。如果 IBC TAO 检查成功,且客户端验证成功,那么该数据包消息就被认证,其负载中的应用数据便可由应用作为可信数据进行处理。数据包序列确保从源链到目标链的数据包流中的每个数据包都具有唯一标识,并防止重放攻击。有关 IBC TAO 检查和数据包处理器行为的更详细规范,请参阅 ICS-04。

正确性

声明:如果客户端设置正确,那么一条链始终可以验证由有效对手方发送的数据包流程消息。 如果客户端是正确的,那么它们就可以验证任意键值成员证明以及键非成员证明。 所有数据包流程消息(SendPacket、RecvPacket 和 TimeoutPacket)都会携带完整的数据包。该数据包同时包含发送方和接收方标识符。因此,对于发送给接收方的数据包流程消息(RecvPacket),我们使用数据包中的接收方标识符来获取本地客户端,并使用源标识符来确定发送方将数据包存储到了哪条路径下。因此,我们可以使用获取到的客户端来验证一个键值成员证明,从而确认该数据包确实由对手方发送。 类似地,对于发送给发送方的数据包流程消息(AcknowledgePacket、TimeoutPacket),同样会再次提供该数据包。这一次,我们使用发送方标识符来获取本地客户端,并使用目标标识符来确定接收方在接收该数据包时必须写入的键路径。因此,我们可以使用获取到的客户端来验证一个键值成员证明,从而确认该数据包确实由对手方发送。对于超时情形,如果数据包 receipt 没有写入由目标标识符确定的 receipt 路径,那么我们的客户端可以使用键非成员证明来验证这一点。

健全性

声明:如果客户端设置正确,那么一条链不会把原本发往其他链的数据包流程消息误认为是来自有效对手方的有效消息。 我们必须注意,客户端标识符在每条链上都是唯一的,但并不是全局唯一的。先考虑一种情况:用户在数据包中正确指定了源标识符和目标标识符。 我们希望确保,格式良好的数据包(即客户端 id 设置正确的数据包)不会让数据包流程消息在第三方链上成功。格式不良的数据包(即客户端 id 无效的数据包)在某些情况下可能会在无效状态下完成;不过,我们必须确保这些数据包产生的任何已完成状态都不会与其他有效数据包的状态混合。 我们可以保证:源标识符在源链上是唯一的,目标标识符在目标链上是唯一的。此外,目标标识符指向源链的一个有效客户端,而源标识符指向目标链的一个有效客户端。 假设 RecvPacket 被发送到一条并非源链上 sourceClient 所标识的那条链。 对于发送给接收方的数据包流程消息(RecvPacket),数据包发送会通过目标链上的客户端(使用目标标识符获取)以及由源标识符推导出的数据包 commitment 路径来进行验证。只有当由目标客户端标识的那条链,确实在源客户端标识符对应的路径下提交了我们收到的数据包时,这项验证检查才会通过。这只有在以下情况下才可能发生:目标客户端指向原始源链,或者它指向另一条恰好提交了完全相同数据包的链。如果它指向原始源链,就意味着我们将数据包发送到了正确的链。由于发送方只会通过设置唯一的源标识符来发送面向目标链的数据包,我们可以确信该数据包确实是发给我们的。由于接收方上的客户端也正确地指向发送方链,我们是在针对一个我们假定诚实的特定共识算法验证该证明。如果数据包被提交到了错误的键路径下,那么我们不会接受该数据包。同样,如果数据包是由错误的链提交的,那么我们也无法正确完成验证。

Introduction

IBC v2 is an end-to-end protocol for reliable, authenticated communication between modules on separate distributed ledgers. IBC makes NO assumptions about the consensus algorithm or the state machine. So long as the distributed ledger satisfies the minimal requirements in ICS-24 Host Requirements, it can support the IBC v2 protocol and communicate across any application in the IBC v2 network. The IBC v2 protocol can be conceptualized in three distinct layers: IBC CLIENTS, IBC CORE, and IBC APPS. IBC APPS are the modules that wish to communicate with each other across different ledgers in the IBC v2 network. On example is ICS-20 fungible token transfer which facilitates sending tokens securely from one ledger to another by sending token packet data using IBC CORE and executing escrow/mint logic upon sending/receiving the ICS-20 token packet data from counterparty ICS20 applications. IBC CLIENTS identifies and verifies the state of the counterparty ledger. An IBC CLIENT is responsible for tracking updates to the state machine and verifying state against a given update. IBC CORE is the handler that implements the transport, authentication, and ordering semantics (hereafter IBC/TAO), it uses the IBC CLIENT to authenticate the packet and then sends the application packet data to the IBC APP which will handle the application data. Thus, each layer has a specific isolated responsibility. The IBC CLIENT only needs to verify key/value proofs of the counterparty state. The IBC APP only needs to process application data coming from a counterparty application. IBC CORE enables authenticated IBC packet flow of SendPacket, RecvPacket, AcknowledgePacket, TimeoutPacket for the IBC APP using the IBC CLIENT as a verification oracle. The goal of this document is to provide a basic overview of the IBC V2 protocol. Where appropriate, distinctions from IBC v1 will be highlighted. For a detailed specification of each layer please refer to the ICS-standards.

Specification

IBC Clients

The IBC Client keeps track of counterparty state updates and exposes a verifier of the counterparty state to IBC CORE. An IBC client implementation achieves this through the use of two distinct structures: the ClientState and the ConsensusState. From the perspective of IBC, these are opaque bytes and are defined by the specific light client implementation. The ClientState is intended to encapsulate parameters of the counterparty consensus that SHOULD NOT change across heights, this can include a chain identifier and security parameters like a staking unbonding period. The ConsensusState on the other hand is a view or a snapshot of the counterparty consensus at a particular height. This view in almost all cases will be a highly compressed view of the counterparty consensus. The IBC CLIENT will not store the entire state of the counterparty chain, nor will it execute all transactions of the counterparty chain as this would be equivalent to hosting a full node. A common pattern is to have the counterparty Consensus to create a Merklized commitment of the counterparty state on each state update. The IBC CLIENT can add the merkle root hash to the ConsensusState and then verify membership/nonmembership proofs against the root hash in the stored consensus state. The IBC CLIENT encapsulates a particular security model, this can be anything from a multisign bridge committee to a fully verified light client of the counterparty consensus algorithm. It is up to users sending packets on the client to decide whether the security model is acceptable to them or not. The IBC client is responsible for taking an initial view of the counterparty consensus, and updating that view from this trusted point using the security model instantiated in the client. This initial counterparty consensus is trusted axiomatically. Thus, IBC does not have any in-protocol awareness of which chain a particular client is verifying. A user can inspect a client and verify for themselves that the client is tracking the chain that they care about (i.e. validating a specific consensus state matches the consensus output of the desired counterparty chain) and that the parameters of the security model encoded in the ClientState are satisfactory. A user need only verify the client once, either by themselves or through social consensus, once that initial trust is established; the IBC CLIENT MUST continue updating the view of the counterparty state from previously trusted views given the parameterized security model. Thus, once a user trusts a light client they can be guaranteed that the trust will not be violated by the client. If the security model is violated by counterparty consensus, the IBC CLIENT implementation MUST provide the ability to freeze the client and prevent further updates and verification. The evidence for this violation is called Misbehaviour and upon verification the client is frozen and all packet processing against the client is paused. Any damage already done cannot be automatically reverted in-protocol, however this mechanism ensures the attack is stopped as soon as possible so that an out-of-band recovery mechanism can intervene (e.g. governance). This recovery mechanism should ensure the consensus violation is corrected on the counterparty and any invalid state is reverted to the extent possible before resuming packet processing by unfreezing the client. The IBC CLIENT must have external endpoints for relayers (off-chain processes that have full-node access to other chains in the network) to initialize a client, update the client, and submit misbehaviour. The implementation of each of these endpoints will be specific to the particular consensus mechanism targeted. The choice of consensus algorithm itself is arbitrary, it may be a Proof-of-Stake algorithm like CometBFT, or a multisig of trusted authorities, or a rollup that relies on an additional underlying client in order to verify its consensus. However, a light client must have the ability to define finality for a given snapshot of the state machine, this may be either through single-slot finality or a finality gadget. Thus, the endpoints themselves should accept arbitrary bytes for the arguments passed into these client endpoints as it is up to each individual client implementation to unmarshal these bytes into the structures they expect.
// initializes client with a starting client state containing all light client parameters
// and an initial consensus state that will act as a trusted seed from which to verify future headers
function createClient(
    clientState: bytes,
    consensusState: bytes,
): (id: bytes, err: error)

// once a client has been created, it can be referenced with the identifier and passed the header
// to keep the client up-to-date. In most cases, this will cause a new consensus state derived from the header
// to be stored in the client
function updateClient(
    clientId: bytes,
    header: bytes,
): error

// once a client has been created, relayers can submit misbehaviour that proves the counterparty chain violated the trust model.
// The light client must verify the misbehaviour using the trust model of the consensus mechanism
// and execute some custom logic such as freezing the client from accepting future updates and proof verification.
function submitMisbehaviour(
    clientId: bytes,
    misbehaviour: bytes,
): error
As relayers keep the client up-to-date and add ConsensusStates to the client, IBC CORE will use the exposed verification endpoints: VerifyMembership and VerifyNonMembership to verify incoming packet-flow messages coming from the counterparty.
// verifies a membership of a path and value in the counterparty chain identified by the provided clientId
// against a particular ConsensusState identified by the provided height
function verifyMembership(
    clientId: bytes,
    height: Number,
    proof: bytes,
    path: CommitmentPath,
    value: bytes
): error

// verifies the nonmembership of a path in the counterparty chain identified by the provided clientId
// against a particular ConsensusState identified by the provided height
function verifyNonMembership(
    cliendId: bytes,
    height: Number,
    proof: bytes,
    path: CommitmentPath
): error

Core IBC Functionality

IBC in its essence is the ability for applications on different blockchains with different consensus mechanisms to communicate with each other through client backed security. Thus, IBC needs the client described above and the IBC applications that define the packet data they wish to send and receive. In addition to these layers, IBC v1 introduced the connection and channel abstractions to connect these two fundamental layers. IBC v2 intends to compress only the necessary aspects of connection and channel layers into a single packet handler with no handshakes but before doing this it is critical to understand what service they currently provide. Properties of IBC v1 Connection:
  • Verifies the validity of the counterparty client
  • Establishes a unique identifier on each side for a shared abstract understanding (the connection)
  • Establishes an agreement on the IBC version and supported features
  • Allows multiple connections to be built against the same client pair
  • Establishes the delay period so this security parameter can be instantiated differently for different connections against the same client pairing.
  • Defines which channel orderings are supported
Properties of IBC v1 Channel:
  • Separates applications into dedicated 1-1 communication channels. This prevents applications from writing into each other’s channels.
  • Allows applications to come to agreement on the application parameters (version negotiation). Ensures that each side can understand the other’s communication and that they are running mutually compatible logic. This version negotiation is a multi-step process that allows the finalized version to differ substantially from the one initially proposed
  • Establishes the ordering of the channel
  • Establishes unique identifiers for the applications on either chain to use to reference each other when sending and receiving packets.
  • The application protocol can be continually upgraded over time by using the upgrade handshake which allows the same channel which may have accumulated state to use new mutually agreed upon application packet data format(s) and associated new logic.
  • Ensures exactly-once delivery of packet flow datagrams (Send, Receive, Acknowledge, Timeout)
  • Ensures valid packet flow (Send => Receive => Acknowledge) XOR (Send => Timeout)

Identifying Counterparties

In core IBC, the connection and channel handshakes serve to ensure the validity of counterparty clients, ensure the IBC and application versions are mutually compatible, as well as providing unique identifiers for each side to refer to the counterparty. Since we are removing handshakes in IBC V2, we must have a different way to provide the chain with knowledge of the counterparty. With a client, we can prove any key/value path on the counterparty. However, without knowing which identifier the counterparty uses when it sends messages to us; we cannot differentiate between messages sent from the counterparty to our chain vs messages sent from the counterparty with other chains. Most implementations will not be able to store the ICS-24 paths directly as a key in the global namespace; but will instead write to a reserved, prefixed keyspace so as not to conflict with other application state writes. Thus the counterparty information we must have includes both its identifier for our chain as well as the key prefix under which it will write the provable ICS-24 paths. Thus, IBC V2 will introduce a new message RegisterCounterparty that will associate the counterparty client of our chain with our client of the counterparty. Thus, if the RegisterCounterparty message is submitted to both sides correctly. Then both sides have mirrored <client,client> pairs that can be treated as identifiers for the sender and receiver chains the packet is associated with. Assuming they are correct, the client on each side is unique and provides an authenticated stream of packet data between the two chains. If the RegisterCounterparty message submits the wrong clientID, this can lead to invalid behaviour; but this is equivalent to a relayer submitting an invalid client in place of a correct client for the desired chain. In the simplest case, we can rely on out-of-band social consensus to only send on valid <client, client> pairs that represent a connection between the desired chains of the user; just as we rely on out-of-band social consensus that a given clientID and channel built on top of it is the valid, canonical identifier of our desired chain in IBC V1.
interface Counterparty {
    clientId: bytes
    counterpartyPrefix: []bytes
}
This Counterparty will be keyed on the client identifier existing on our chain. Thus, both sides get access to each other’s client identifier. This effectively creates a connection with unique identifiers on both sides that reference each other’s consensus. Thus, the resulting client, client pairing in IBC V2 replaces the separate connection layer that existed in IBC V1. The RegisterCounterparty method allows for authentication that implementations may verify before storing the provided counterparty identifier. The strongest authentication possible is to have a valid clientState and consensus state of our chain in the authentication along with a proof it was stored at the claimed counterparty identifier. This is equivalent to the validateSelfClient logic performed in the connection handshake. A simpler but weaker authentication would simply be to check that the RegisterCounterparty message is sent by the same relayer that initialized the client. This would make the client parameters completely initialized by the relayer. Thus, users must verify that the client is pointing to the correct chain and that the counterparty identifier is correct as well before using identifiers to send a packet. In practice, this is verified by social consensus.

IBC V2 Packet Processing

IBC V2 will simply provide packet delivery between two chains communicating and identifying each other by on-chain light clients as specified in ICS-02 with application packet data being routed to their specific IBC applications with packet-flow semantics as specified in ICS-04. The packet clientIDs as mentioned above will tell the IBC router which chain to send the packets to and which chain a received packet came from, while the portIDs in the payload specifies which application on the router the packet should be sent to. Thus, once two chains have set up clients for each other with specific Identifiers, they can send IBC packets like so.
interface Packet {
  sequence: uint64
  timeoutTimestamp: uint64
  sourceClientId: Identifier // identifier of the destination client on sender chain
  destClientId: Identifier // identifier of the sender client on the destination chain
  payload: []Payload
}
Since the packets are addressed directly with the underlying light clients, there are no more handshakes necessary. Instead the packet sender must be capable of providing the correct <client, client> pair. Sending a packet with the wrong source client is equivalent to sending a packet with the wrong source channel. Sending a packet on a client with the wrong provided counterparty is an error and will cause the packet to be rejected. If the counterparty is set incorrectly for the new client, this is a misconfiguration in the IBC V2 setup process. Unexpected behavior may occur in this case, though it is expected that users validate the counterparty configurations on both sides are correct before sending packets using the client identifiers. This validation may be done directly or through social consensus. If the client and counterparty identifiers are setup correctly, then the correctness and soundness properties of IBC holds. IBC packet flow is guaranteed to succeed. If the counterparty is misconfigured, then as we will see it will be impossible for the intended destination to correctly verify the packet thus, the packet will simply time out. The Payload contains all the application specific information. This includes the opaque application data that the sender application wishes to send to the receiving application; it also includes the Encoding and Version that should be used to decode and process the application data. Note that this is a departure from IBC V1 where this metadata about how to process the application data was negotiated in the channel handshake. Here, each packet carries the information about how its individual data should be processed. This allows the Version and Encoding to change from packet to packet; allowing applications to upgrade asynchronously and optimistically send new packet encodings and versions to their counterparties. If the counterparty application can support receiving the new payload, it will successfully be processed; otherwise the receive will simply error and the sending application reverts state upon receiving the ErrorAcknowledgement. This increases the possibility for errors to occur during an application’s packet processing but massively increases the flexibility of IBC applications to upgrade and evolve over time. Similarly, the portIDs on the sender and receiver application are no longer prenegotiated in the channel handshake and instead are in the payload. Thus in IBC v2; a sending application can route its packet to ANY OTHER application on the receiving application by simply specifying its portID in the payload as a receiver. It is incumbent on applications to restrict which counterparty applications it wishes to communicate with by validating the source and destination portIDs provided in the payload. Thus, the per-packet Payload replaces the separate channel layer that existed in IBC V1. For more details on the Payload structure, see ICS-04.

Registering IBC applications on the router

IBC CORE contains routers mapping reserved application portIDs to individual IBC applications as well as a mapping from clientIDs to individual IBC clients.
type IBCRouter struct {
    apps: portID -> IBCApp
    clients: clientId -> IBCClient
}

Packet Flow

For a detailed specification of the packet flow, please refer to ICS-04. The packet-flow messages defined by IBC are: SendPacket, ReceivePacket, AcknowledgePacket and TimeoutPacket. SendPacket will most often be triggered by user-action that wants to initiate a cross-chain action (e.g. token transfer) by sending a packet from an application on the sender chain to an application on the destination chain. Every other message is the result of counterparty action, thus they must be submitted by an off-chain relayer that can submit a proof of counterparty that authenticates the message is valid. For example, the RecvPacket message can only be submitted if the relayer can prove that the counterparty did send a packet to our chain by submitting a proof to our on-chain client. The source chain commits the packet under the ICS-04 standardized commitment path which is constructed with packet.sourceClientId and packet.sequence. Since the packet.sourceClientId is a unique reference to the destination chain on the source chain, a packet commitment stored on this path is guaranteed to be a packet the source chain intends to send to the destination chain. The destination chain can verify this path using its on-chain client identified by packet.destClientId Similarly, AcknowledgePacket and TimeoutPacket are messages that get sent back to the sending chain after the an attempted packet receipt. If the packet receipt is successful, an application-specific acknowledgement will be written to the ICS-04 standardized acknowledgement path under the packet.destClientId and packet.sequence. The sending chain can verify that the relayer-provided acknowledgment was committed to by the receiving chain by verifying this path using the on-chain client identified by packet.sourceClientId. Since the packet.destClientId is a unique reference to the sending chain on the destination chain and the sequence is unique in the stream of packets from source chain to destination; we can be guaranteed that the acknowledgement was written for the packet we previously sent with the provided sourceClientId and sequence. This acknowledgement is then given to the sending application to perform appropriate application logic for the given acknowledgement. The TimeoutPacket is called if the packet receipt is unsuccessful. All compliant implementations must write a sentinel non-empty value into the standardized ICS-04 receipt path if it successfully receives a packet. This receipt path is constructed using the packet.destClientId and packet.sequence. Thus, if the value does not exist after the packet timeout has been passed, we can be guaranteed that the packet has timed out. The sending chain verifies a relayer-provided NonMembership proof for the receipt path of the given packet, if it succeeds then the timeout is verified and the timeout logic for the sending application is executed. Note the nonmembership proof MUST be verified against a consensus state that is executed past the timeout timestamp of the packet, and packet receiving MUST fail on the destination after the timeout has elapsed. This ensures that a packet cannot be timed out on the source chain and received on the destination simultaneously. Thus, the packet handler implements the handlers for these messages by constructing the necessary path and value to authenticate the message as specified in ICS-04; it then routes the verification of the membership/nonmembership proof to the relevant ICS-02 client as specified in the packet. If the IBC TAO checks succeed and the client verification succeeds; then the packet message is authenticated and the application data in the payload can be processed by the application as trusted data. The packet sequence ensures that the stream of packets from a source chain to destination chain are all uniquely identified and prevents replay attacks. More detailed specification of the IBC TAO checks and packet handler behaviour can be found in ICS-04.

Correctness

Claim: If the clients are setup correctly, then a chain can always verify packet flow messages sent by a valid counterparty. If the clients are correct, then they can verify any key/value membership proof as well as a key non-membership proof. All packet flow message (SendPacket, RecvPacket, and TimeoutPacket) are sent with the full packet. The packet contains both sender and receiver identifiers. Thus on packet flow messages sent to the receiver (RecvPacket), we use the receiver identifier in the packet to retrieve our local client and the source identifier to determine which path the sender stored the packet under. We can thus use our retrieved client to verify a key/value membership proof to validate that the packet was sent by the counterparty. Similarly, for packet flow messages sent to the sender (AcknowledgePacket, TimeoutPacket); the packet is provided again. This time, we use the sender identifier to retrieve the local client and the destination identifier to determine the key path that the receiver must have written to when it received the packet. We can thus use our retrieved client to verify a key/value membership proof to validate that the packet was sent by the counterparty. In the case of timeout, if the packet receipt wasn’t written to the receipt path determined by the destination identifier this can be verified by our retrieved client using the key nonmembership proof.

Soundness

Claim: If the clients are setup correctly, then a chain cannot mistake a packet flow message intended for a different chain as a valid message from a valid counterparty. We must note that client identifiers are unique to each chain but are not globally unique. Let us first consider a user that correctly specifies the source and destination identifiers in the packet. We wish to ensure that well-formed packets (i.e. packets with correctly setup client ids) cannot have packet flow messages succeed on third-party chains. Ill-formed packets (i.e. packets with invalid client ids) may in some cases complete in invalid states; however we must ensure that any completed state from these packets cannot mix with the state of other valid packets. We are guaranteed that the source identifier is unique on the source chain, the destination identifier is unique on the destination chain. Additionally, the destination identifier points to a valid client of the source chain, and the source identifier points to a valid client of the destination chain. Suppose the RecvPacket is sent to a chain other than the one identified by the sourceClient on the source chain. In the packet flow messages sent to the receiver (RecvPacket), the packet send is verified using the client on the destination chain (retrieved using destination identifier) with the packet commitment path derived by the source identifier. This verification check can only pass if the chain identified by the destination client committed the packet we received under the source client identifier. This is only possible if the destination client is pointing to the original source chain, or if it is pointing to a different chain that committed the exact same packet. Pointing to the original source chain would mean we sent the packet to the correct . Since the sender only sends packets intended for the destination chain by setting to a unique source identifier, we can be sure the packet was indeed intended for us. Since our client on the receiver is also correctly pointing to the sender chain, we are verifying the proof against a specific consensus algorithm that we assume to be honest. If the packet is committed to the wrong key path, then we will not accept the packet. Similarly, if the packet is committed by the wrong chain then we will not be able to verify correctly.