diff --git a/README.md b/README.md index 5e6736bd3..90586fd06 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ All standards at or past the "Draft" stage are listed here in order of their ICS | --------------------------------------------------------------- | -------------------------- | ----- | --------------- | ------------- | | [6](spec/client/ics-006-solo-machine-client/README.md) | Solo Machine Client | Candidate | [ibc-go](https://github.com/cosmos/ibc-go/tree/main/modules/light-clients/06-solomachine) | Protocol team | | [7](spec/client/ics-007-tendermint-client/README.md) | Tendermint Client | Candidate | [ibc-go](https://github.com/cosmos/ibc-go/tree/main/modules/light-clients/07-tendermint), [ibc-rs](https://github.com/cosmos/ibc-rs/tree/main/ibc-clients/ics07-tendermint) | Protocol team | -| [8](spec/client/ics-008-wasm-client/README.md) | Wasm Client | Candidate | [ibc-go](https://github.com/cosmos/ibc-go/tree/main/modules/light-clients/08-wasm), [ibc-rs](https://github.com/cosmos/ibc-rs/tree/main/ibc-clients/ics08-wasm) | Protocol team / [Composable Finance](https://www.composable.finance) | +| [8](spec/client/ics-008-wasm-client/README.md) | Wasm Client | Candidate | [ibc-go](https://github.com/cosmos/ibc-go/tree/main/modules/light-clients/08-wasm), [ibc-rs](https://github.com/cosmos/ibc-rs/tree/main/ibc-clients/ics08-wasm) | Protocol team / [Composable Finance](https://www.github.com/ComposableFi) | | [9](spec/client/ics-009-loopback-cilent/README.md) | Loopback Client | Draft | [ibc-go](https://github.com/cosmos/ibc-go/tree/main/modules/light-clients/09-localhost) | Protocol team | | [10](spec/client/ics-010-grandpa-client/README.md) | GRANDPA Client | Draft | | [Octopus Network](https://oct.network) | @@ -63,7 +63,7 @@ All standards at or past the "Draft" stage are listed here in order of their ICS | [20](spec/app/ics-020-fungible-token-transfer/v1/README.md) | v1 | Fungible Token Transfer | Candidate | [ibc-go](https://github.com/cosmos/ibc-go/tree/main/modules/apps/transfer), [ibc-rs](https://github.com/cosmos/ibc-rs/tree/main/ibc-apps/ics20-transfer) | Protocol team | | [27](spec/app/ics-027-interchain-accounts/README.md) | v1 | Interchain Accounts | Candidate | [ibc-go](https://github.com/cosmos/ibc-go/tree/main/modules/apps/27-interchain-accounts) | Protocol team | | [28](spec/app/ics-028-cross-chain-validation/README.md) | v1 | Cross-Chain Validation | Candidate | [interchain-security](https://github.com/cosmos/interchain-security/tree/main) | Cosmos Hub team | -| [29](spec/app/ics-029-fee-payment) | v1 | General Relayer Incentivization Mechanism | Candidate | [ibc-go](https://github.com/cosmos/ibc-go/tree/main/modules/apps/29-fee) | Protocol team | +| [29](spec/app/ics-029-fee-payment) | v1 | General Relayer Incentivization Mechanism | DEPRECATED | | Protocol team | | [30](spec/app/ics-030-middleware) | v1 | IBC Application Middleware | N/A | N/A | Protocol team | | [31](spec/app/ics-031-crosschain-queries) | v1 | Cross-Chain Queries | Draft | N/A | Protocol team | | [32](https://github.com/strangelove-ventures/async-icq) | v1 | Interchain Queries | Candidate | [async-icq](https://github.com/strangelove-ventures/async-icq) | [Strangelove Ventures](https://strange.love) | diff --git a/spec/IBC_V2/README.md b/spec/IBC_V2/README.md new file mode 100644 index 000000000..dc49965cb --- /dev/null +++ b/spec/IBC_V2/README.md @@ -0,0 +1,202 @@ +--- +title: IBC v2 +stage: EXPERIMENTAL +version compatibility: ibc-go v10.0.0 +author: Aditya Sripal +created: 2024-08-15 +--- + +## IBC v2 + +### 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](../core/ics-024-host-requirements/README.md), 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. + +```typescript +// 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 `ConsensusState`s to the client, **IBC CORE** will use the exposed verification endpoints: `VerifyMembership` and `VerifyNonMembership` to verify incoming packet-flow messages coming from the counterparty. + +```typescript +// 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 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 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. + +```typescript +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](core/ics-002-client-semantics/README.md) with application packet data being routed to their specific IBC applications with packet-flow semantics as specified in [ICS-04](core/ics-004-packet-semantics/PACKET_HANDLER.md). 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. + +```typescript +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 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](core/ics-004-packet-semantics/PACKET.md). + +### 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. + +```typescript +type IBCRouter struct { + apps: portID -> IBCApp + clients: clientId -> IBCClient +} +``` + +### Packet Flow + +For a detailed specification of the packet flow, please refer to [ICS-04](core/ics-004-packet-semantics/PACKET_HANDLER.md). + +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](core/ics-004-packet-semantics/PACKET.md); it then routes the verification of the membership/nonmembership proof to the relevant [ICS-02](core/ics-002-client-semantics/README.md) 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](core/ics-004-packet-semantics/PACKET_HANDLER.md). + +### 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. diff --git a/spec/IBC_V2/core/ics-002-client-semantics/README.md b/spec/IBC_V2/core/ics-002-client-semantics/README.md new file mode 100644 index 000000000..aaefd6344 --- /dev/null +++ b/spec/IBC_V2/core/ics-002-client-semantics/README.md @@ -0,0 +1,512 @@ +--- +ics: 2 +title: Client Semantics +stage: draft +category: IBC/TAO +kind: interface +requires: 24 +required-by: 4 +version compatibility: ibc-go v10.0.0 +author: Juwoon Yun , Christopher Goes , Aditya Sripal +created: 2019-02-25 +modified: 2024-08-22 +--- + +## 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`. + +```typescript +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 `ConsensusState`s. + +- `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 `Height`s. + +- `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 `ConsensusState`s can be generated. + Verification against the client after it is frozen will also fail. + +```typescript +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 `ConsensusState`s. +These state verification functions enable higher-level protocol abstractions to +verify sub-components of the state of the remote state machines. + +`ValidityPredicate`s MUST reflect the behaviour of the remote state machine and its `Consensus`, i.e., +`ValidityPredicate`s 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. + +```typescript +type Height +``` + +```typescript +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 `ConensusState`s 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. + +```typescript +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`. + +```typescript +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. + +```typescript +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. + +```typescript +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). + +```typescript +type latestClientHeight = ( + clientState: ClientState) + => Height +``` + +Client types MUST define a method on the client state to fetch the timestamp at a given height + +```typescript +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. +`ClientMessage`s 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. + +```typescript +type ClientMessage = bytes +``` + +#### `CommitmentProof` + +`CommitmentProof` is an opaque data structure defined by the client type. + +```typescript +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](../ics-004-packet-semantics/PACKET.md)). + +```typescript +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](../ics-024-host-requirements/README.md#path-space)). + +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. + +```typescript +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](https://github.com/cosmos/ics23) `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 `Identifier`s if required. +The validation function `validateClientIdentifier` MAY be provided. + +```typescript +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 `ConsensusState`s 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: + +```typescript +type verifyMembership = (ClientState, Height, CommitmentProof, Path, Value) => boolean +``` + +```typescript +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`: 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: . + +## 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: + +August 22, 2024 - [Changes for IBC/TAO V2](https://github.com/cosmos/ibc/pull/1147) + +## Copyright + +All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0). diff --git a/spec/IBC_V2/core/ics-004-packet-semantics/PACKET.md b/spec/IBC_V2/core/ics-004-packet-semantics/PACKET.md new file mode 100644 index 000000000..6a0a2f3e6 --- /dev/null +++ b/spec/IBC_V2/core/ics-004-packet-semantics/PACKET.md @@ -0,0 +1,170 @@ +# Packet Structure and Provable Commitment Specification + +## Packet V2 Structure + +The IBC packet sends application data from a source chain to a destination chain with a timeout that specifies when the packet is no longer valid. The packet will be committed to by the source chain as specified in the ICS-24 specification. The receiver chain will then verify the packet commitment under the ICS-24 specified packet commitment path. If the proof succeeds, the IBC handler sends the application data(s) to the relevant application(s). + +```typescript +interface Packet { + // identifier for the destination-chain client existing on source chain + sourceClientId: bytes, + // identifier for the source-chain client existing on destination chain + destClientId: bytes, + // the sequence uniquely identifies this packet + // in the stream of packets from source to dest chain + sequence: uint64, + // the timeout is the timestamp in seconds on the destination chain + // at which point the packet is no longer valid. + // It cannot be received on the destination chain and can + // be timed out on the source chain + timeout: uint64, + // the data includes the messages that are intended + // to be sent to application(s) on the destination chain + // from application(s) on the source chain + // IBC core handlers will route the payload to the desired + // application using the port identifiers but the rest of the + // payload will be processed by the application + data: [Payload] +} + +interface Payload { + // sourcePort identifies the sending application on the source chain + sourcePort: bytes, + // destPort identifies the receiving application on the dest chain + destPort: bytes, + // version identifies the version that sending application + // expects destination chain to use in processing the message + // if dest chain does not support the version, the payload must + // be rejected with an error acknowledgement + version: string, + // encoding allows the sending application to specify which + // encoding was used to encode the app data + // the receiving applicaton will decode the appData into + // the strucure expected given the version provided + // if the encoding is not supported, receiving application + // must be rejected with an error acknowledgement. + // the encoding string MUST be in MIME format + encoding: string, + // appData is the opaque content sent from the source application + // to the dest application. It will be decoded and interpreted + // as specified by the version and encoding fields + appData: bytes, +} +``` + +The source and destination client identifiers at the top-level of the packet identify the chains communicating. The `sourceClientId` identifier **must** be unique on the source chain and is a pointer to the destination chain client on the source chain. The `destClientId` identifier **must** be a unique identifier on the destination chain and is a pointer to the source chain client on the destination chain. The sequence is a monotonically incrementing nonce to uniquely identify packets sent between the source and destination chain. + +The timeout is the UNIX timestamp in seconds that must be passed on the **destination** chain before the packet is invalid and no longer capable of being received. Note that the timeout timestamp is assessed against the destination chain's clock which may drift relative to the clocks of the sender chain or a third party observer. If a packet is received on the destination chain after the timeout timestamp has passed relative to the destination chain's clock; the packet must be rejected so that it can be safely timed out and reverted by the sender chain. + +In version 2 of the IBC specification, implementations **MAY** support multiple application data within the same packet. This can be represented by a list of payloads. Implementations may choose to only support a single payload per packet, in which case they can just reject incoming packets sent with multiple payloads. + +Each payload will include its own `Encoding` and `AppVersion` that will be sent to the application to instruct it how to decode and interpret the opaque application data. The application must be able to support the provided `Encoding` and `AppVersion` in order to process the `AppData`. If the receiving application does not support the encoding or app version, then the application **must** return an error to IBC core. If the receiving application does support the provided encoding and app version, then the application must decode the application as specified by the `Encoding` string and then process the application as expected by the counterparty given the agreed-upon app version. Since the `Encoding` and `AppVersion` are now in each packet they can be changed on a per-packet basis and an application can simultaneously support many encodings and app versions from a counterparty. This is in stark contrast to IBC version 1 where the channel prenegotiated the channel version (which implicitly negotiates the encoding as well); so that changing the app version after channel opening is very difficult. + +All implementations must commit the packet in the standardized IBC commitment format to satisfy the protocol. In order to do this we must first commit the packet data and timeout. The timeout is encoded in LittleEndian format. The packet data which is a list of payloads is committed to by hashing each individual field of the payload and successively concatenating them together. This ensures a standard unambigious commitment for a given packet. Thus a given packet will always create the exact same commitment by all compliant implementations and two different packets will never create the same commitment by a compliant implementation. This commitment value is then stored under the standardized provable packet commitment key as defined below: + +```typescript +func packetCommitmentPath(packet: Packet): bytes { + return packet.sourceClientId + byte(0x01) + bigEndian(packet.sequence) +} +``` + +```typescript +// commitPayload hashes all the fields of the packet data to create a standard size +// preimage before committing it in the packet. +func commitPayload(payload: Payload): bytes { + buffer = sha256.Hash(payload.sourcePort) + buffer = append(sha256.Hash(payload.destPort)) + buffer = append(sha256.Hash(payload.version)) + buffer = append(sha256.Hash(payload.encoding)) + buffer = append(sha256.Hash(payload.appData)) + return sha256.Hash(buffer) +} + +// commitV2Packet commits to all fields in the packet +// by hashing each individual field and then hashing these fields together +// Note: SourceClient and the sequence are omitted since they will be included in the key +// Every other field of the packet is committed to in the packet which will be stored in the +// packet commitment value +// The final preimage will be prepended by the byte 0x02 before hashing in order to clearly define the protocol version +// and allow for future upgradability +func commitV2Packet(packet: Packet) { + timeoutBytes = LittleEndian(packet.timeout) + var appBytes: bytes + for p in packet.payload { + appBytes = append(appBytes, commitPayload(p)) + } + buffer = sha256.Hash(packet.destClient) + buffer = append(buffer, sha256.hash(timeoutBytes)) + buffer = append(buffer, sha256.hash(appBytes)) + buffer = append([]byte{0x02}, buffer) + return sha256.Hash(buffer) +} +``` + +## Acknowledgement V2 + +The acknowledgement in the version 2 specification is also modified to support multiple payloads in the packet that will each go to separate applications that can write their own acknowledgements. Each acknowledgment will be contained within the final packet acknowledgment in the same order that they were received in the original packet. Thus if a packet contains payloads for modules `A` and `B` in that order; the receiver will write an acknowledgment with the app acknowledgements `A` and `B` in the same order. + +The acknowledgement which is itself a list of app acknowledgement bytes must be committed to by hashing each individual acknowledgement and concatenating them together and hashing the result. This ensures that all compliant implementations reach the same acknowledgment commitment and that two different acknowledgements never create the same commitment. + +An application may not need to return an acknowledgment. In this case, it may return a sentinel acknowledgement value `SENTINEL_ACKNOWLEDGMENT` which will be the single byte in the byte array: `bytes(0x01)`. In this case, the IBC `acknowledgePacket` handler will still do the core IBC acknowledgment logic but it will not call the application's acknowledgePacket callback. + +```typescript +interface Acknowledgement { + // Each app in the payload will have an acknowledgment in this list in the same order + // that they were received in the payload + // If an app does not need to send an acknowledgement, there must be a SENTINEL_ACKNOWLEDGEMENT + // in its place + // The app acknowledgement must be encoded in the same manner specified in the payload it received + // and must be created and processed in the manner expected by the version specified in the payload. + appAcknowledgement: [bytes] +} +``` + +All acknowledgements must be committed to and stored under the standardized acknowledgment path. Note that since each acknowledgement is associated with a given received packet, the acnowledgement path is constructed using the packet `destClientId` and its `sequence` to generate a unique key for the acknowledgement. + +```typescript +func acknowledgementPath(packet: Packet) { + return packet.destClientId + byte(0x02) + bigEndian(packet.Sequence) +} +``` + +```typescript +// commitV2Acknowledgement hashes each app acknowledgment and hashes them together +// the final preimage will be prepended with the byte 0x02 before hashing in order to clearly define the protocol version +// and allow for future upgradability +func commitV2Acknowledgment(ack: Acknowledgement) { + var buffer: bytes + for appAck in ack.appAcknowledgement { + buffer = append(buffer, sha256.Hash(appAck)) + } + buffer = append([]byte{0x02}, buffer) + return sha256.Hash(buffer) +} +``` + +## Packet Receipt V2 + +A packet receipt will only tell the sending chain that the counterparty has successfully received the packet. Thus we just need a provable boolean flag uniquely associated with the sent packet. Thus, the receiver chain stores the packet receipt keyed on the destination identifier and the sequence to uniquely identify the packet. + +For chains that support nonexistence proofs of their own state, they can simply write a `SENTINEL_RECEIPT_VALUE` under the receipt path. This `SENTINEL_RECEIPT_PATH` can be any non-nil value so it is recommended to write a single byte. The receipt path is standardized as below. Similar to the acknowledgement, each receipt is associated with a given received packet the receipt path is constructed using the packet `destClientId` and its `sequence` to generate a unique key for the receipt. + +```typescript +func receiptPath(packet: Packet) { + return packet.destClientId + byte(0x03) + bigEndian(packet.Sequence) +} +``` + +## Provable Path-space + +IBC/TAO implementations MUST implement the following paths for the `provableStore` in the exact format specified. This is because counterparty IBC/TAO implementations will construct the paths according to this specification and send it to the light client to verify the IBC specified value stored under the IBC specified path. The `provableStore` is specified in [ICS24 Host Requirements](../ics-024-host-requirements/README.md) + +Future paths may be used in future versions of the protocol, so the entire key-space in the provable store MUST be reserved for the IBC handler. + +| Value | Path format | +| -------------------------- | ---------------------------------------------- | +| Packet Commitment | {sourceClientId}0x1{bigEndianUint64Sequence} | +| Packet Receipt | {destClientId}0x2{bigEndianUint64Sequence} | +| Acknowledgement Commitment | {destClientId}0x3{bigEndianUint64Sequence} | + +Note that the IBC protocol ensures that the packet `(sourceClientId, sequence)` tuple uniquely identifies a packet on the sending chain, and the `(destClientId, sequence)` tuple uniquely identifies a packet on the receiving chain. This property along with the byte separator between the client identifier and sequence in the standardized paths ensures that commitments, receipts, and acknowledgements are each written to different paths for the same packet. Thus, so long as the host requirements specified in ICS24 are respected; a provable key written to state by the IBC handler for a given packet will never be overwritten with a different value. This ensures secure and correct communication between chains in the IBC ecosystem. diff --git a/spec/IBC_V2/core/ics-004-packet-semantics/PACKET_HANDLER.md b/spec/IBC_V2/core/ics-004-packet-semantics/PACKET_HANDLER.md new file mode 100644 index 000000000..b1d71e77c --- /dev/null +++ b/spec/IBC_V2/core/ics-004-packet-semantics/PACKET_HANDLER.md @@ -0,0 +1,201 @@ +# IBC Packet Handler + +The packet handler specification defines the semantics and behavior that implementations must enforce in order to support IBC v2 protocol. + +## Packet Structure + +A `Packet` in the interblockchain communication protocol is the primary interface by which applications will send data to counterparty applications on other chains. It is defined as follows: + +```typescript +interface Packet { + sourceClientId: bytes // identifier of the client on the sending chain + destClientId: bytes // identifier of the client on the receiving chain + sequence: uint64 // unique number identifying this packet in the stream of packets from sourceClientId to destClientId + timeoutTimestamp: uint64, // indicates the timeout as a UNIX timestamp in seconds. If the timeout timestamp is reached on destination chain, it is no longer receivable + data: Payload[] // a list of payloads intended for applications on the receiving chain +} +``` + +```typescript +interface Payload { + sourcePort: bytes, // identifier of the sending application on the sending chain + destPort: bytes, // identifier of the receiving application on the receiving chain + version: string, // payload version only interpretable by sending/receiving applications + encoding: string, // payload encoding only interpretable by sending/receiving applications + value: bytes // application-specific data that can be parsed by receiving application given the version and encoding +} +``` + +The packet is never directly serialised and sent to counterparty chains. Instead a standardized non-malleable committment to the packet data is stored under the standardized unique key for the packet as defined in ICS-24. Thus, implementations MAY make individual choices on the exact packet structure and serialization scheme they use internally so long as they respect the standardized commitment defined by the IBC protocol when writing to the provable store. + +Packet Invariants: + +- None of the packet fields are allowed to be empty +- For every payload included, none of the payload fields are allowed to be empty + +## Receipt + +A `Receipt` is a sentinel byte that is stored under the standardized provable ReceiptPath of a given packet by the receiving chain when it successfully receives the packet. This prevents replay attacks and also the possibility of timing out a packet on the sender chain when the packet has already been received. The specific value of the receipt does not matter so long as its not empty. + +## Acknowledgement Structure + +An `Acknowledgement` is the interface that will be used by receiving applications to return application specific information back to the sender. If every application successfully received its payload, then each receiving application will return their custom acknowledgement bytes which will be appended to the acknowledgement array. If **any** application returns an error, then the acknowledgement will have a single element with a sentinel error acknowledgement. + +```typescript +const ErrorAcknowledgement = sha256("UNIVERSAL_ERROR_ACKNOWLEDGEMENT") + +interface Acknowledgement { + appAcknowledgement bytes[] // array of an array of bytes. Each element of the array contains an acknowledgement from a specific application +} +``` + +Acknowledgement Invariants: + +- If the acknowledgement interface includes an error acknowledgement then there must be only a single element in the array with the error acknowledgement +- There CANNOT be multiple app acknowledgements where an element is the error acknowledgement +- If there are multiple app acknowledgements, the length of the app acknowledgements is the same length as the payloads in the associated packet and each acknowledgement is associated with the payload in the same position in the payload array. + +## SendPacket + +SendPacket is called by users to execute an inter-blockchain flow. The user submits a message with a payload(s) for each IBC application they wish to interact with. The SendPacket handler must call the sendPacket logic of each sending application as identified by the sourcePort of the payload. If none of the sending applications error, then the sendPacket handler must construct the packet with the user-provided sourceClient, payloads, and timeout and the destinationClient it retrieves from the counterparty storage given the sourceClient and a generated sequence that is unique for the sourceClientId. It will commit the packet with the ICS24 commitment function under the ICS24 path. The sending chain MAY store the ICS24 path under a custom prefix in the provable store. In this case, the counterparty must have knowledge of the custom prefix as provided by the relayer on setup. The sending chain SHOULD check the provided timestamp against an authenticated time oracle (local BFT time or destination client latest timestamp) and preemptively reject a user-provided packet with a timestamp that has already passed. + +The user may be an off-chain process or an on-chain actor. In either case, the user is not trusted by the IBC protocol. The IBC application is responsible for properly authenticating that the user is allowed to send the requested app data using the IBC application's port as specified in the source port of the payload. The IBC application is also responsible for executing any app-specific logic that must run before the IBC packet can be sent (e.g. escrowing user's tokens before sending a fungible token transfer packet). + +SendPacket Inputs: + +`payloads: Payload[]`: List of payloads that are to be sent from source applications on sending chain to corresponding destination applications on the receiving chain. Implementations MAY choose to only support a single payload per packet. +`sourceClientId: bytes`: Identifier of the receiver chain client that exists on the sending chain. +`timeoutTimestamp: uint64`: The timeout in UNIX seconds after which the packet is no longer receivable on the receiving chain. NOTE: This timestamp is evaluated against the **receiving chain** clock as there may be drift between the sending chain and receiving chain clocks + +SendPacket Preconditions: + +- A valid client exists on the sending chain with the `sourceClientId` +- There exists a mapping on the sending chain from `sourceClientId` to `Counterparty` + +SendPacket Postconditions: + +- The sending application(s) as identified by the source port(s) in the payload(s) have all executed their sendPacket logic successfully +- The following packet gets committed and stored under the packet commitment path as specified by ICS24: + +```typescript +interface Packet { + sourceClientId: sourceClientId, + destClientId: getCounterparty(sourceClientId).ClientId, // destClientId should be filled in with the registered counterparty id for provided sourceClientId + sequence: generateUniqueSequence(sourceClientId), + timeoutTimestamp: timeoutTimestamp + data: payloads +} +``` + +- Since the packet is committed to with a hash in-state, implementations must provide the packet fields for relayers to reconstruct. This can be emitted in an event system or stored in state as the full packet under an auxilliary key if the implementing platform does not have an event system. + +SendPacket Errorconditions: + +- Any of the sending applications returns an error during its sendPacket logic execution +- The sending client is invalid (expired or frozen) + +SendPacket Invariants: + +- The sourceClientId MUST exist on the sending chain +- The destClientId MUST be the registered counterparty of the sourceClientId on the sending chain +- The sending chain MUST NOT have sent a previous packet with the same `sourceClientId` and `sequence` + +## RecvPacket + +RecvPacket is called by relayers once a packet has been committed on the sender chain in order to process the packet on the receiving chain. Since the relayer is not trusted, the relayer must provide a proof that the sender chain had indeed committed the provided packet which will be verified against the `destClient` on the receiving chain. + +If the proof succeeds, and the packet passes replay and timeout checks; then each payload is sent to the receiving application as part of the receiving application callback. + +RecvPacket Inputs: +`packet: Packet`: The packet sent from the sending chain to our chain +`proof: bytes`: An opaque proof that will be sent to the destination client. The destination client is responsible for interpreting the bytes as a proof and verifying the packet commitment key/value provided by the packet handler against the provided proof. +`proofHeight: Number`: This is the height of the counterparty chain from which the proof was generated. A corresponding consensus state for this height must exist on the destination client for the proof to verify correctly. + +RecvPacket Preconditions: + +- A valid client exists on the receiving chain with `destClientId` +- There exists a mapping from `destClientId` to `Counterparty` + +RecvPacket Postconditions: + +- A packet receipt is stored under the specified ICS24 with the `destClientId` and `sequence` +- All receiving application(s) as identified by the destPort(s) in the payload(s) have executed their recvPacket logic + +RecvPacket Errorconditions: + +- `Counterparty.ClientId` != `packet.sourceClientId` ensures that packet was sent by expected counterparty +- `packet.TimeoutTimestamp` >= `chain.BlockTime()` ensures we cannot receive successfully if packet can be timed out on sending chain +- Packet receipt does not already exist in state for the `destClientId` and `sequence`. This prevents replay attacks +- Membership proof does not successfully verify + +## WriteAcknowledgement + +WriteAcknowledgement Inputs: + +`destClientId: bytes`: Identifier of the sender chain client that exist on the receiving chain +`sequence: uint64`: Unique sequence identifying the packet from sending chain to receiving chain +`ack: Acknowledgement`: Acknowledgement collected by receiving chain from all receiving applications after they have returned their individual acknowledgement. If any individual application errors, the entire acknowledgement MUST have a single element with just the SENTINEL ERROR ACKNOWLEDGEMENT. If all applications successfully received, then every application must have its own acknowledgement set in the `Acknowledgement` in the same order that they existed in the payload of the sending packet. + +WriteAcknowledgement Preconditions: + +- A packet receipt is stored under the specified ICS24 with the `destClientId` and `sequence` +- An acknowledgement for the `destClientId` and `sequence` has not already been written under the ICS24 path + +WriteAcknowledgement Postconditions: + +- The acknowledgement is committed and written to the acknowledgement path as specified in ICS24 +- Since the acknowledgement is being hashed, the full acknowledgement fields should be made available for relayers to reconstruct. This can be emitted in an event system or stored in state as the full packet under an auxilliary key if the implementing platform does not have an event system. +- Implementors SHOULD also emit the full packet again in `WriteAcknowledgement` since the sender chain is only expected to store the packet commitment and not the full packet; relayers are expected to pass the packet back to the sender chain to process the acknowledgement. Thus, in order to support stateless relayers it is helpful to re-emit the packet fields on `WriteAcknowledgement` so the relayer can reconstruct the packet. +- If the acknowledgement is successful, then all receiving applications must have executed their recvPacket logic and written state +- If the acknowledgement is unsuccessful (ie ERROR ACK), any state changes made by the receiving applications MUST all be reverted. This ensure atomic execution of the multi-payload packet. + +## AcknowledgePacket + +AcknowledgePacket Inputs: + +`packet: Packet`: The packet that was originally sent by our chain +`acknowledgement: Acknowledgement`: The acknowledgement written by the receiving chain for the packet +`proof: bytes`: An opaque proof that will be sent to the source client. The source client is responsible for interpreting the proof and verifying it against the acknowledgement key/value provided by the packet handler. +`proofHeight: Number`: This is the height of the counterparty chain from which the proof was generated. A corresponding consensus state for this height must exist on the source client for the proof to verify correctly. + +AcknowledgePacket Preconditions: + +- A valid client exists on the sending chain with the `sourceClientId` +- There exists a mapping on the sending chain from `sourceClientId` to `Counterparty` +- A packet commitment has been stored under the ICS24 packet path with `sourceClientId` and `sequence` + +AcknowledgePacket Postconditions: + +- All sending applications execute the ackPacket logic with the payload and the individual acknowledgement for that payload or the universal `ErrorAcknowledgement`. +- Stored commitment for the packet is deleted + +AcknowledgePacket Errorconditions: + +- `packet.destClient` != `counterparty.ClientId`. This should never happen if the second error condition is not true, since we constructed the packet correctly earlier +- The packet provided by the relayer does not commit to the stored commitment we have stored for the `sourceClientId` and `sequence` +- Membership proof of the acknowledgement commitment on the receiving chain as standardized by ICS24 does not verify + +## TimeoutPacket + +TimeoutPacket Inputs: + +`packet: Packet`: The packet that was originally sent by our chain +`proof: bytes`: An opaque non-existence proof that will be sent to the source client. The source client is responsible for interpreting the proof and verifying it against the receipt key provided by the packet handler. +`proofHeight: Number`: This is the height of the counterparty chain from which the proof was generated. A corresponding consensus state for this height must exist on the source client for the proof to verify correctly. + +TimeoutPacket Preconditions: + +- A valid client exists on the sending chain with the `sourceClientId` +- There exists a mapping on the sending chain from `sourceClientId` to `Counterparty` +- A packet commitment has been stored under the ICS24 packet path with `sourceClientId` and `sequence` + +TimeoutPacket Postconditions: + +- All sending applications execute the timeoutPacket logic with the payload. +- Stored commitment for the packet is deleted + +TimeoutPacket Errorconditions: + +- `packet.destClient` != `counterparty.ClientId`. This should never happen if the second error condition is not true, since we constructed the packet correctly earlier +- The packet provided by the relayer does not commit to the stored commitment we have stored for the `sourceClientId` and `sequence` +- Non-Membership proof of the packet receipt on the receiving chain as standardized by ICS24 does not verify diff --git a/spec/IBC_V2/core/ics-005-port-allocation/README.md b/spec/IBC_V2/core/ics-005-port-allocation/README.md new file mode 100644 index 000000000..928528ae2 --- /dev/null +++ b/spec/IBC_V2/core/ics-005-port-allocation/README.md @@ -0,0 +1,56 @@ +--- +ics: 5 +title: Port Allocation +stage: Draft +required-by: 4 +category: IBC/TAO +kind: interface +version compatibility: ibc-go v10.0.0 +author: Aditya Sripal +created: 2024-05-17 +--- + +## Synopsis + +This standard specifies the port allocation system by which modules can bind to uniquely named ports allocated by the IBC handler. +The port identifiers in the packet defines which application to route the packet callback to. The source portID is an identifier of the application sending the packet, thus it will also receive the `AcknowledgePacket` and `TimeoutPacket` callback. The destination portID is the identifier of the application receiving the packet and will receive the `ReceivePacket` callback. + +Modules may register multiple ports on a state machine and send from any of their registered ports to any arbitrary port on a remote state machine. Each port on a state machine must be mapped to a specific IBC module as defined by [ICS-26](../ics-026-application-callbacks/README.md). Thus the IBC application to portID mapping is one-to-many. + +NOTE: IBC v1 included a channel along with a channel handshake that explicitly associated a unique channel between two portIDs on counterparty chains. Thus, the portIDs on both sides were tightly coupled such that no other application other than the ones bound by the portIDs were allowed to send packets on the dedicated channel. IBC v2 removed the concept of a channel and all packet flow is between chains rather than being isolated module-module communication. Thus, an application on a sending chain is allowed to send a packet to ANY other application on a destination chain by identifying the application with the portIDs in the packet. Thus, it is now the responsibility of applications to restrict which applications are allowed to send packets to them by checking the portID in the callback and rejecting any packet that comes from an unauthorized application. + +### Motivation + +The interblockchain communication protocol is designed to facilitate module-to-module communication, where modules are independent, possibly mutually distrusted, self-contained +elements of code executing on sovereign ledgers. + +## Technical Specification + +### Registering a port + +The IBC handler MUST provide a way for applications to register their callbacks on a portID. + +```typescript +function registerPort(portId: Identifier, cbs: ICS26App) => void +``` + +RegisterPort Preconditions: + +- There is no other application that is registered on the port router for the given `portId`. + +RegisterPort Postconditions: + +- The ICS26 application is registered on the provided `portId`. +- Any incoming packet flow message addressed to the `portId` is routed to the ICS26 application. Any outgoing packet flow message addressed by the `portId` MUST come from the ICS26 application + +### Authenticating and Routing Packet Flow Messages + +Once an application is registered with a port, it is the port router's responsibility to properly route packet flow messages to the appropriate application identified by the portId in the payload. Similarly when the application sends packet flow messages to the port router, the router MUST ensure that the application is authenticated to send the packet flow message by checking if the payload portIDs are registered to the application. + +For packet flow messages on the packet sending chain (e.g. `SendPacket`, `AcknowledgePacket`, `TimeoutPacket`); the port router MUST do this authentication and routing using the packet payload's `sourcePortId`. + +For packet flow messages on the packet receiving chain (e.g. `RecvPacket` and optionally the asynchronous `WriteAcknowledgement`); the port router MUST do this authentication and routing using the packet payload's `destPortId`. + +[ICS-4](../ics-004-packet-semantics/PACKET_HANDLER.md) defines the packet flow messages and the expected behavior of their respected handlers. When the packet flow message arrives from the core ICS-4 handler to the application (e.g. `RecvPacket`, `AcknowledgePacket`, `TimeoutPacket`); then the portRouter acts as a router routing the message from the core handler to the ICS26 application. When the packet flow message arrives from the application to the core ICS-4 handler (e.g. `SendPacket`, or the optional `WriteAcknowledgement`); then the portRouter acts as an authenticator by checking that the calling application is registered as the owner of port they wish to send the message on before sending the message to the ICS-4 handler. + +NOTE: It is possible for implementations to change the order of execution flow so long as they still respect all the expected semantics and behavior defined in ICS-4. In this case, the port router's role as router or authenticator will change accordingly. diff --git a/spec/IBC_V2/core/ics-024-host-requirements/README.md b/spec/IBC_V2/core/ics-024-host-requirements/README.md new file mode 100644 index 000000000..32a913cc6 --- /dev/null +++ b/spec/IBC_V2/core/ics-024-host-requirements/README.md @@ -0,0 +1,144 @@ +--- +ics: 24 +title: Host State Machine Requirements +stage: draft +category: IBC/TAO +kind: interface +required-by: 4 +version compatibility: ibc-go v10.0.0 +author: Aditya Sripal +created: 2024-08-21 +modified: 2024-08-21 +--- + +## Synopsis + +This specification defines the minimal set of properties which must be fulfilled by a state machine hosting an implementation of the interblockchain communication protocol. IBC relies on a key-value provable store for cross-chain communication. In version 2 of the specification, the expected key-value storage will only be for the keys that are relevant for packet processing. + +### Motivation + +IBC is designed to be a common standard which will be hosted by a variety of blockchains & state machines and must clearly define the requirements of the host. + +### Definitions + +### Desired Properties + +IBC should require as simple an interface from the underlying state machine as possible to maximise the ease of correct implementation. + +## Technical Specification + +### Module system + +The host state machine must support a module system, whereby self-contained, potentially mutually distrusted packages of code can safely execute on the same ledger, control how and when they allow other modules to communicate with them, and be identified and manipulated by a "master module" or execution environment. + +The IBC core handlers as defined in ICS-4 must have + +### Paths, identifiers, separators + +An `Identifier` is a bytestring used as a key for an object stored in state, such as a packet commitment, acknowledgement, or receipt. + +Identifiers MUST be non-empty (of positive integer length). + +Identifiers MUST consist of characters in one of the following categories only: + +- Alphanumeric +- `.`, `_`, `+`, `-`, `#` +- `[`, `]`, `<`, `>` + +A `Path` is a bytestring used as the key for an object stored in state. Paths MUST contain only identifiers, constant bytestrings, and the separator `"/"`. + +Identifiers are not intended to be valuable resources — to prevent name squatting, minimum length requirements or pseudorandom generation MAY be implemented, but particular restrictions are not imposed by this specification. + +The separator `"/"` is used to separate and concatenate two identifiers or an identifier and a constant bytestring. Identifiers MUST NOT contain the `"/"` character, which prevents ambiguity. + +By default, identifiers have the following minimum and maximum lengths in characters: + +| Port identifier | Client identifier | +| --------------- | ----------------- | +| 2 - 128 | 2 - 64 | + +### Key/value Store + +The host state machine MUST provide a key/value store interface +with three functions that behave in the standard way: + +```typescript +type get = (path: Path) => Value | void +``` + +```typescript +type set = (path: Path, value: Value) => void +``` + +```typescript +type queryProof = (path: Path) => (CommitmentProof, Value) +``` + +`queryProof` will return a `Membership` proof if there exists a value for that path in the key/value store and a `NonMembership` proof if there is no value stored for the path. + +The host state machine SHOULD provide an interface for deleting +a Path from the key/value store as well though it is not required: + +```typescript +type delete = (path: Path) => void +``` + +`Path` is as defined above. `Value` is an arbitrary bytestring encoding of a particular data structure. The specific Path and Values required to be written to the provable store are defined in [ICS-4](../ics-004-packet-semantics/PACKET.md). + +These functions MUST be permissioned to the IBC packet handler module (the implementation of which is described in [ICS-4](../ics-004-packet-semantics/PACKET_HANDLER.md)) only, so only the IBC handler module can `set` or `delete` the paths that can be read by `get`. + +In most cases, this will be implemented as a sub-store (prefixed key-space) of a larger key/value store used by the entire state machine. This is why ICS-2 defines a `counterpartyCommitmentPrefix` that is associated with the client. The IBC handler will prefix the `counterpartyCommitmentPrefix` to the ICS-4 standardized path before proof verification against a `ConsensusState` in the client. + +### Provable Path-space + +IBC/TAO implementations MUST implement the following paths for the `provableStore` in the exact format specified. This is because counterparty IBC/TAO implementations will construct the paths according to this specification and send it to the light client to verify the IBC specified value stored under the IBC specified path. + +Future paths may be used in future versions of the protocol, so the entire key-space in the provable store MUST be reserved for the IBC handler. + +| Value | Path format | +| -------------------------- | -------------------------------------------- | +| Packet Commitment | {sourceClientId}0x1{bigEndianUint64Sequence} | +| Packet Receipt | {destClientId}0x2{bigEndianUint64Sequence} | +| Acknowledgement Commitment | {destClientId}0x3{bigEndianUint64Sequence} | + +IBC V2 only proves commitments related to packet handling, thus the commitments and how to construct them are specifed in [ICS-4](../ics-004-packet-semantics/PACKET.md). + +As mentioned above, the provable path space controlled by the IBC handler may be prefixed in a global provable key/value store. In this case, the prefix must be appended by the IBC handler before the proof is verified. + +The provable store MUST be capable of providing `MembershipProof` for a key/value pair that exists in the store. It MUST also be capable of providing a `NonMembership` proof for a key that does not exist in the store. + +In the case, the state machine does not support `NonMembership` proofs; a client may get around this restriction by associating a `SENTINEL_ABSENCE_VALUE` with meaning the key does not exist and treating a `MembershipProof` with a `SENTINEL_ABSENCE_VALUE` as a `NonMembershipProof`. In this case, the state machine is responsible for ensuring that there is a way to write a `SENTINEL_ABSENCE_VALUE` to the keys that IBC needs to prove nonmembership for and it MUST ensure that an actor cannot set the `SENTINEL_ABSENCE_VALUE` directly for a key accidentally. These requirements and how to implement them are outside the scope of this specification and remain the responsibility of the bespoke IBC implementation. + +### Finality + +The state machine MUST make updates sequentially so that all state updates happen in order and can be associated with a unique `Height` in that order. Each state update at a height `h` MUST be eventually **finalized** at a finite timestamp `t` such that the order of state updates from the initial state up to `h` will never change after time `t`. + +IBC handlers will only accept packet-flow messages from state updates which are already deemed to be finalized. In cases where the finality property is probabilistically guaranteed, this probabilitic guarantee must be handled within the ICS-2 client in order to provide a final view of the remote state machine for the ICS-4 packet handler. + +### Time + +As the state updates are applied to the state machine over time, the state update algorithm MUST itself have secure access to the current timestamp at which the state update is being applied. This is needed for IBC handlers to process timeouts correctly. + +If the state machine update mechanism does not itself provide a timestamp to the state machine handler, then there must be a time oracle updates as part of the state machine update itself. In this case, the security model of IBC will also include the security model of the time oracle. + +This timestamp for a state update MUST be monotonically increasing and it MUST be the greater than or equal to the timestamp that the counterparty client will return for the `ConsensusState` associated with that state update. + +## Backwards Compatibility + +Not applicable. + +## Forwards Compatibility + +Key/value store functionality and consensus state type are unlikely to change during operation of a single host state machine. + +`submitDatagram` can change over time as relayers should be able to update their processes. + +## Example Implementations + +## History + +Aug 21, 2024 - [Initial draft](https://github.com/cosmos/ibc/pull/1144) + +## Copyright + +All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0). diff --git a/spec/IBC_V2/core/ics-026-application-callbacks/README.md b/spec/IBC_V2/core/ics-026-application-callbacks/README.md new file mode 100644 index 000000000..04a607c96 --- /dev/null +++ b/spec/IBC_V2/core/ics-026-application-callbacks/README.md @@ -0,0 +1,180 @@ +--- +ics: 26 +title: IBC Application Callbacks +stage: Draft +category: IBC/TAO +kind: instantiation +version compatibility: ibc-go v10.0.0 +author: Aditya Sripal +created: 2025-03-19 +--- + +## Synopsis + +IBC enables module to module communication across remote state machines by providing a secure packet flow authenticated by the ICS-4 packet handler. The IBC core protocol is responsible for TAO (transport, authentication, ordering) of packets between two chains. These packets contain payload(s) that carry the application-specific information that is being communicated between two ICS26 applications. The data in the payload is itself opaque to the IBC core protocol, IBC core only verifies that it was correctly sent by the sender and then provides that data to the receiver for application-specific interpretation and processing. + +This specification standardizes the interface between ICS-4 (core IBC/TAO) and an IBC application (i.e. ICS26 app) for all the packet flow messages. + +The default IBC handler uses a receiver call pattern, where modules must individually call the IBC handler in order to send packets. In turn, the IBC handler verifies incoming packet flow messages like `ReceivePacket`, `AcknowledgePacket` and `TimeoutPacket` and calls into the appropriate ICS26 application as described in [ICS5 Port Allocation](../ics-005-port-allocation/README.md). + +## Technical Specification + +### Payload Structure + +The payload structure is reproduced from [ICS-4](../ics-004-packet-semantics/PACKET.md) since all of the following application functions are operating on the payloads that are being sent in the packets. + +```typescript +interface Payload { + sourcePort: bytes, // identifier of the sending application on the sending chain + destPort: bytes, // identifier of the receiving application on the receiving chain + version: string, // payload version only interpretable by sending/receiving applications + encoding: string, // payload encoding only interpretable by sending/receiving applications + value: bytes // application-specific data that can be parsed by receiving application given the version and encoding +} +``` + +### Core Handler Interface Exposed to ICS26 Applications + +The IBC core handler MUST expose the following function signature to the ICS26 applications registered on the port router, so that the application can send packets. + +#### SendPacket + +SendPacket Inputs: + +`payloads: Payload`: This is the payload that the application wishes to send to an application on the receiver chain. +`sourceClientId: bytes`: Identifier of the receiver chain client that exists on the sending chain. +`timeoutTimestamp: uint64`: The timeout in UNIX seconds after which the packet is no longer receivable on the receiving chain. NOTE: This timestamp is evaluated against the **receiving chain** clock as there may be drift between the sending chain and receiving chain clocks + +SendPacket Preconditions: + +- The application is registered on the port router with `payload.SourcePortId` +- The application MUST have successfully conducted any application specific logic necessary for sending the given payload. +- The sending client exists for `sourceClientId` + +SendPacket Postconditions: + +- The following packet gets committed and stored under the packet commitment path as specified by ICS24: + +```typescript +interface Packet { + sourceClientId: sourceClientId, + destClientId: getCounterparty(sourceClientId).ClientId, // destClientId should be filled in with the registered counterparty id for provided sourceClientId + sequence: generateUniqueSequence(sourceClientId), + timeoutTimestamp: msg.timeoutTimestamp + data: msg.Payloads +} +``` + +- The sequence is returned to the ICS26 application + +SendPacket ErrorConditions: + +- The sending client is invalid (expired or frozen) +- The provided `timeoutTimstamp` has already elapsed +- The sending application is not allowed to send the provided payload to the requested receiving application as identified by `payload.DestPort` + +NOTE: IBC v2 allows multiple payloads coming from multiple applications to be sent in the same packet. If an implementation chooses to support this feature, they may either provide an entrypoint in the core handler to send multiple packets, which must then call each individual application `OnSendPacket` callback to validate their individual payload and do application-specific sending logic; or they may queue the payloads coming from each application until the packet is ready to be committed. + +#### WriteAcknowledgement + +The IBC core handler MAY expose the following function signature to the ICS26 applications registed on the port router, so that the application can write acknowledgements asynchronously. + +This is only necessary if the implementation supports processing packets asynchronously. In this case, an application may process the packet asynchronously from when the IBC core handler receives the packet. Thus, the acknowledgement cannot be returned as part of the `OnRecvPacket` callback and must be submitted to the core IBC handler by the ICS26 application at a later time. Thus, we must introduce a new endpoint on the IBC handler for the ICS26 application to call when it is done processing a receive packet and wants to write the acknowledgement. + +WriteAcknowledgement Inputs: + +`destClientId: bytes`: Identifier of the sender chain client that exist on the receiving chain (i.e. executing chain) +`sequence: uint64`: Unique sequence identifying the packet from sending chain to receiving chain +`ack: bytes`: Acknowledgement from the receiving application for the payload it was sent by the application. If the receive was unsuccessful, the `ack` must be the `SENTINEL_ERROR_ACKNOWLEDGEMENT`, otherwise it may be some application-specific data. + +WriteAcknowledgement Preconditions: + +- A packet receipt is stored under the specified ICS24 with the `destClientId` and `sequence` +- An acknowledgement for the `destClientId` and `sequence` has not already been written under the ICS24 path + +WriteAcknowledgement Postconditions: + +- The acknowledgement is committed and written to the acknowledgement path as specified in ICS24 +- If the acknowledgement is successful, then all receiving applications must have executed their recvPacket logic and written state +- If the acknowledgement is unsuccessful (ie ERROR ACK), any state changes made by the receiving applications MUST all be reverted. This ensure atomic execution of the multi-payload packet. + +NOTE: In the case that the packet contained multiple payloads, the IBC core handler MUST wait for all applications to return their individual acknowledgements for the packet before commiting the acknowledgment. If ANY application returns the error acknowledgement, then the acknowledgement for the entire packet only contains the `ERROR_SENTINEL_ACKNOWLEDGEMENT`. Otherwise, the acknowledgment is a list containing each applications individual acknowledgment in the same order that their associated payload existed in the packet. + +### ICS26 Interface Exposed to Core Handler + +Modules must expose the following function signatures to the routing module, which are called upon the receipt of various datagrams: + +#### OnRecvPacket + +OnRecvPacket Inputs: + +`sourceClientId: bytes`: This is the identifier of the client on the sending chain. NOTE: This is an identifier on the counterparty chain provided as information for the application, but it should not be treated as a unique identifier on the receiving chain. +`destClientId: bytes`: This is the identifier of the receiving chain (i.e. executing chain) +`sequence: uint64`: This is the unique sequence for the packet in the stream of packets from sending chain to destination chain. The tuple `(destClientId, sequence)` uniquely identifies the packet on this chain. +`payload: Payload`. This is the payload that an application registered by `payload.SourcePort` on the sending chain sends to the executing application + +OnRecvPacket Preconditions: + +- The application is registered on the port router with `payload.DestPort` +- The destination client exists for `destClientId` +- All IBC/TAO verification checks have already been authenticated by IBC core handler. Thus, when the application receives a packet; it can be guaranteed of its authenticity and need only perform the relevant application logic for the given payload. + +OnRecvPacket Postconditions: + +- The application has executed all app-specific logic for the given payload and made the appropriate state changes +- The application returns an app acknowledgment `ack: bytes` to the core IBC handler to be written as an acknowledgement of the payload in this packet. + +OnRecvPacket ErrorConditions: + +- The sending application as identified by `payload.SourcePortId` is not allowed to send a payload to the receiving application +- The requested version as identified by `payload.Version` is unsupported +- The requested encoding as identified by `payload.Encoding` is unsupported +- An error occured while processing the `payload.Value` after decoding with `payload.Encoding` and processing the payload in the manner expected by `payload.Version`. + +IMPORTANT: If the `OnRecvPacket` callback errors for any reason, the state changes made during the callback MUST be reverted and the IBC core handler MUST write the `SENTINEL_ERROR_ACKNOWLEDGEMENT` for this packet even if other payloads in the packet are received successfully. + +#### OnAcknowledgePacket + +OnAcknowledgePacket Inputs: + +`sourceClientId: bytes`: This is the identifier of the client on the sending chain (i.e. executing chain). +`destClientId: bytes`: This is the identifier of the receiving chain. NOTE: This is an identifier on the counterparty chain provided as information for the application, but it should not be treated as a unique identifier on the receiving chain. +`sequence: uint64`: This is the unique sequence for the packet in the stream of packets from sending chain to destination chain. The tuple `(sourceClientId, sequence)` uniquely identifies the packet on this chain. +`acknowledgement: bytes`: This is the acknowledgement that the receiving application sent for the payload that we previously sent. It may be a successful acknowledgement with app-specific information or it may be the `SENTINEL_ERROR_ACKNOWLEDGEMENT` in which case we should handle any app-specific logic needed for a packet that failed to be sent. +`payload: Payload`: This is the original payload that we previously sent + +OnAcknowledgementPreconditions: + +- This application had previously sent the provided payload in a packet with the provided `sourceClientId` and `sequence`. +- All IBC/TAO verification checks have already been authenticated by IBC core handler. Thus, when the application receives an acknowledgement; it can be guaranteed of its authenticity and need only perform the relevant application logic for the given acknowledgement and payload. + +OnAcknowledgement Postconditions: + +- The application has executed all app-specific logic for the given payload and acknowledgment and made the appropriate state changes +- If the acknowledgement was the `SENTINEL_ERROR_ACKNOWLEDGEMENT`, this will usually involve reverting whatever application state changes were made during `SendPacket` (e.g. unescrowing tokens for transfer) + +OnAcknowledgement Errorconditions: + +- Application specific errors may occur while processing the acknowledgement. The packet lifecycle is already complete. Implementations MAY choose to allow retries or not. + +#### OnTimeoutPacket + +OnTimeoutPacket Inputs: + +`sourceClientId: bytes`: This is the identifier of the client on the sending chain (i.e. executing chain). +`destClientId: bytes`: This is the identifier of the receiving chain. NOTE: This is an identifier on the counterparty chain provided as information for the application, but it should not be treated as a unique identifier on the receiving chain. +`sequence: uint64`: This is the unique sequence for the packet in the stream of packets from sending chain to destination chain. The tuple `(sourceClientId, sequence)` uniquely identifies the packet on this chain. +`payload: Payload`: This is the original payload that we previously sent + +OnTimeoutPacket Preconditions: + +- This application had previously sent the provided payload in a packet with the provided `sourceClientId` and `sequence`. +- All IBC/TAO verification checks have already been authenticated by IBC core handler. Thus, when the application receives an timeout; it can be guaranteed of its authenticity and need only perform the relevant application timeout logic for the given payload. + +OnTimeoutPacket Postconditions: + +- The application has executed all app-specific logic for the given payload and made the appropriate state changes. This will usually involve reverting whatever application state changes were made during `SendPacket` (e.g. unescrowing tokens for transfer) + +OnTimeoutPacket Errorconditions: + +- Application specific errors may occur while processing the timeout. The packet lifecycle is already complete. Implementations MAY choose to allow retries or not. diff --git a/spec/eureka/README.md b/spec/eureka/README.md deleted file mode 100644 index 23287f291..000000000 --- a/spec/eureka/README.md +++ /dev/null @@ -1,392 +0,0 @@ ---- -ics: TBD -title: IBC v2 -stage: EXPERIMENTAL -category: IBC/TAO -kind: interface -version compatibility: ibc-go v10.0.0 -author: Aditya Sripal -created: 2024-08-15 ---- - -## IBC v2 - -### Context - -Note: This specification is in an experimental phase. The final specification of the v2 of the IBC protocol is being specified in the v2 folder of the TAO protocol. This document will serve as a placeholder for people to view and comment on as a more formal specification is being discussed and implemented. - -The implementation of the entire IBC protocol as it currently stands is a large undertaking. While there exists ready-made implementations like ibc-go this is only deployable on the Cosmos-SDK. Similarly, there exists ibc-rs which is a library for chains to integrate. However, this requires the chain to be implemented in Rust, there still exists some non-trivial work to integrate the ibc-rs library into the target state machine, and certain limitations either in the state machine or in ibc-rs may prevent using the library for the target chain. - -Writing an implementation from scratch is a problem many ecosystems face as a major barrier for IBC adoption. - -The goal of this document is to serve as the simplest IBC specification that will allow new ecosystems to implement a protocol that can communicate with fully implemented IBC chains using the same security assumptions. It will also explain the motivations of the original design choices of the IBC protocol and how the new ibc architecture rethinks these design choices while still retaining the desired properties of IBC. - -The IBC v2 protocol must have the same security properties as IBC, and must be completely compatible with IBC applications. It may not have the full flexibility offered by standard IBC. - -### Desired Properties - -- Light-client backed security -- Unique identifiers for each channel end -- Authenticated application channel (must verify that the counterparty is running the correct client and app parameters) -- Applications must be mutually compatible with standard IBC applications. -- Must be capable of being implemented in smart contract environments with resource constraints and high gas costs. - -### Specification - -### Light Clients - -The light client module can be implemented exactly as-is with regards to its functionality. It **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 in case the trust model of the client is violated by the counterparty consensus mechanism (e.g. committing to different headers for the same height). - -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. - -```typescript -// 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, -): (Identifier, 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: Identifier, - 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: Identifier, - misbehaviour: bytes, -): error -``` - -// TODO: Keep very limited buffer of consensus states -// Keep ability to migrate client (without necessarily consensus governance) - -### Core IBC Functionality - -IBC in its essence is the ability for applications on different blockchains with different security models to communicate with each other through light-client backed security. Thus, IBC needs the light client described above and the IBC applications that define the packet data they wish to send and receive. In addition to these layers, core IBC introduces 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 to a new router layer but before doing this it is critical to understand what service they currently provide. - -Properties of 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 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 lite, 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 lite will introduce a new message `ProvideCounterparty` that will associate the counterparty client of our chain with our client of the counterparty. Thus, if the `ProvideCounterparty` message is submitted to both sides correctly. Then both sides have mirrored pairs that can be treated as channel identifiers. 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 `ProvideCounterparty` 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 pairs that represent a connection between the desired chains of the user; just as we currently 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. - -```typescript -interface Counterparty { - channelId: Identifier - keyPrefix: CommitmentPrefix -} - -function ProvideCounterparty( - clientIdentifier: Identifier, // this will be our own client identifier representing our channel to desired chain - counterpartyClientIdentifier: Identifier, // this is the counterparty's identifier of our chain - counterpartyKeyPrefix: CommitmentPrefix, - authentication: data, // implementation-specific authentication data -) { - assert(verify(authentication)) - - counterparty = Counterparty{ - clientId: counterpartyClientIdentifier, - keyPrefix: counterpartyKeyPrefix - } - - privateStore.set(counterpartyPath(channelIdentifier), counterparty) -} - -// getCounterparty retrieves the stored counterparty identifier -// given the clientIdentifier on our chain once it is provided -function getCounterparty(clientIdentifier: Identifier): Counterparty { - return privateStore.get(counterpartyPath(clientIdentifier)) -} -``` - -The `ProvideCounterparty` method allows for authentication data 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 `ProvideCounterparty` 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 the lite channel identified by the provided client-client pair. - -### IBC Lite - -IBC lite 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 remaining as they were defined in ICS-04. The channelID derived from the 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 portID 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. - -```typescript -interface Packet { - sequence: uint64 - timeoutHeight: Height - timeoutTimestamp: uint64 - sourcePort: Identifier // identifier of the application on sender - sourceChannel: Identifier // identifier of the client of destination on sender chain - destPort: Identifier // identifier of the application on destination - destChannel: Identifier // identifier of the client of sender on the destination chain -} -``` - -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 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 channel with the wrong provided counterparty is a new source of errors, however this is added to the burden of out-of-band 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 a user sends a packet with the wrong destination channel, 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. - -### Registering IBC applications on the router - -The IBC router contains a mapping from a reserved application port and the supported versions of that application as well as a mapping from channelIdentifiers to channels. - -```typescript -type IBCRouter struct { - versions: portID -> [Version] - callbacks: portID -> [Callback] - clients: clientId -> Client - ports: portID -> counterpartyPortID -} -``` - -### Packet Flow through the Router - -```typescript -function sendPacket( - sourcePort: Identifier, - sourceChannel: Identifier, - timeoutHeight: Height, - timeoutTimestamp: uint64, - packetData: []byte -): uint64 { - // in this specification, the source channel is the clientId - client = router.clients[packet.sourceChannel] - assert(client !== null) - - // disallow packets with a zero timeoutHeight and timeoutTimestamp - assert(timeoutHeight !== 0 || timeoutTimestamp !== 0) - - // check that the timeout height hasn't already passed in the local client tracking the receiving chain - latestClientHeight = client.latestClientHeight() - assert(timeoutHeight === 0 || latestClientHeight < timeoutHeight) - - // if the sequence doesn't already exist, this call initializes the sequence to 0 - sequence = channelStore.get(nextSequenceSendPath(sourcePort, sourceChannel)) - - // store commitment to the packet data & packet timeout - channelStore.set( - packetCommitmentPath(sourcePort, sourceChannel, sequence), - hash(hash(data), timeoutHeight, timeoutTimestamp) - ) - - // increment the sequence. Thus there are monotonically increasing sequences for packet flow - // from sourcePort, sourceChannel pair - channelStore.set(nextSequenceSendPath(sourcePort, sourceChannel), sequence+1) - - // log that a packet can be safely sent - emitLogEntry("sendPacket", { - sequence: sequence, - data: data, - timeoutHeight: timeoutHeight, - timeoutTimestamp: timeoutTimestamp - }) - -} - -function recvPacket( - packet: OpaquePacket, - proof: CommitmentProof, - proofHeight: Height, - relayer: string): Packet { - // in this specification, the destination channel is the clientId - client = router.clients[packet.destChannel] - assert(client !== null) - - // assert source channel is destChannel's counterparty channel identifier - counterparty = getCounterparty(packet.destChannel) - assert(packet.sourceChannel == counterparty.channelId) - - // assert source port is destPort's counterparty port identifier - assert(packet.sourcePort == ports[packet.destPort]) - - packetPath = packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence) - merklePath = applyPrefix(counterparty.keyPrefix, packetPath) - // DISCUSSION NEEDED: Should we have an in-protocol notion of Prefixing the path - // or should we make this a concern of the client's VerifyMembership - // proofPath = applyPrefix(client.counterpartyChannelStoreIdentifier, packetPath) - assert(client.verifyMembership( - proofHeight, - 0, 0, // zeroed out delay period - proof, - merklePath, - hash(packet.data, packet.timeoutHeight, packet.timeoutTimestamp) - )) - - assert(packet.timeoutHeight === 0 || getConsensusHeight() < packet.timeoutHeight) - assert(packet.timeoutTimestamp === 0 || currentTimestamp() < packet.timeoutTimestamp) - - - // we must set the receipt so it can be verified on the other side - // this receipt does not contain any data, since the packet has not yet been processed - // it's the sentinel success receipt: []byte{0x01} - packetReceipt = channelStore.get(packetReceiptPath(packet.destPort, packet.destChannel, packet.sequence)) - assert(packetReceipt === null) - - channelStore.set( - packetReceiptPath(packet.destPort, packet.destChannel, packet.sequence), - SUCCESSFUL_RECEIPT - ) - - // log that a packet has been received - emitLogEntry("recvPacket", { - data: packet.data - timeoutHeight: packet.timeoutHeight, - timeoutTimestamp: packet.timeoutTimestamp, - sequence: packet.sequence, - sourcePort: packet.sourcePort, - sourceChannel: packet.sourceChannel, - destPort: packet.destPort, - destChannel: packet.destChannel, - order: channel.order, - connection: channel.connectionHops[0] - }) - - cbs = router.callbacks[packet.destPort] - // IMPORTANT: if the ack is error, then the callback reverts its internal state changes, but the entire tx continues - ack = cbs.OnRecvPacket(packet, relayer) - - if ack != nil { - channelStore.set(packetAcknowledgementPath(packet.destPort, packet.destChannel, packet.sequence), ack) - } -} - -function acknowledgePacket( - packet: OpaquePacket, - acknowledgement: bytes, - proof: CommitmentProof, - proofHeight: Height, - relayer: string -) { - // in this specification, the source channel is the clientId - client = router.clients[packet.sourceChannel] - assert(client !== null) - - // assert dest channel is sourceChannel's counterparty channel identifier - counterparty = getCounterparty(packet.destChannel) - assert(packet.sourceChannel == counterparty.channelId) - - // assert dest port is sourcePort's counterparty port identifier - assert(packet.destPort == ports[packet.sourcePort]) - - // verify we sent the packet and haven't cleared it out yet - assert(provableStore.get(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence)) - === hash(hash(packet.data), packet.timeoutHeight, packet.timeoutTimestamp)) - - ackPath = packetAcknowledgementPath(packet.destPort, packet.destChannel) - merklePath = applyPrefix(counterparty.keyPrefix, ackPath) - assert(client.verifyMembership( - proofHeight, - 0, 0, - proof, - merklePath, - hash(acknowledgement) - )) - - cbs = router.callbacks[packet.sourcePort] - cbs.OnAcknowledgePacket(packet, acknowledgement, relayer) - - channelStore.delete(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence)) -} - -function timeoutPacket( - packet: OpaquePacket, - proof: CommitmentProof, - proofHeight: Height, - relayer: string -) { - // in this specification, the source channel is the clientId - client = router.clients[packet.sourceChannel] - assert(client !== null) - - // assert dest channel is sourceChannel's counterparty channel identifier - counterparty = getCounterparty(packet.destChannel) - assert(packet.sourceChannel == counterparty.channelId) - - // assert dest port is sourcePort's counterparty port identifier - assert(packet.destPort == ports[packet.sourcePort]) - - // verify we sent the packet and haven't cleared it out yet - assert(provableStore.get(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence)) - === hash(hash(packet.data), packet.timeoutHeight, packet.timeoutTimestamp)) - - // get the timestamp from the final consensus state in the channel path - var proofTimestamp - proofTimestamp = client.getTimestampAtHeight(proofHeight) - assert(err != nil) - - // check that timeout height or timeout timestamp has passed on the other end - assert( - (packet.timeoutHeight > 0 && proofHeight >= packet.timeoutHeight) || - (packet.timeoutTimestamp > 0 && proofTimestamp >= packet.timeoutTimestamp)) - - receiptPath = packetReceiptPath(packet.destPort, packet.destChannel, packet.sequence) - merklePath = applyPrefix(counterparty.keyPrefix, receiptPath) - assert(client.verifyNonMembership( - proofHeight - 0, 0, - proof, - merklePath - )) - - cbs = router.callbacks[packet.sourcePort] - cbs.OnTimeoutPacket(packet, relayer) - - channelStore.delete(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence)) -} -``` - -### 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 - -// To do after prototyping and going through some of these examples before writing it down - -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 channel 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.