Union IBC
Overview
IBC is a blockchain interoperability protocol for secure general message passing between blockchains. At Union, we use a specialized in-house version (more EVM friendly) that slightly deviates from the canonical ibc-go and ibc. This document is an attempt at specifying the implementation.
The semantic of the core protocol can be found in the ibc repository. Our implementation is deviating from the semantic in few places that we will describe. Most of the changes are specializations/optimizations targeting the EVM.
The protocol assumes the execution happens within a smart contract engine (contract addresses MUST be unique).
Protocol
ICS-002 Client
- verifyMembershipno longer takes a- delayPeriodTimeand- delayPeriodBlocks, the specific light clients SHOULD implement such verification if necessary.
- verifyNonMembership- delayPeriodTimehas been removed.
- delayPeriodBlockshas been removed.
 
- clientIdis a unique- uint32. The client type MUST be stored and indexable with the- clientId.
Additions
- registerClientMUST be implemented and called before being able to call- createClienton a light client. Only one client type MUST exist for a given light client:
registerClient ∷ Implementations → ClientType → Address → ImplementationsregisterClient impls clientType lightClient = do  assert (getImplementation impls clientType ≡ null)  setImplementation impls clientType lightClientICS-003 Connection
- connectionIdis no longer a string but a unique- uint32.
- ConnectionEnd- counterpartyPrefixhas been removed.
- versionhas been removed.
- delayPeriodTimehas been removed.
- delayPeriodBlockshas been removed.
 
type ClientId = Uint32type ConnectionId = Uint32
class ConnectionEnd where  state ∷ ConnectionState  counterpartyConnectionId ∷ ConnectionId  clientId ∷ ClientId  counterpartyClientId ∷ ClientIdICS-004 Channel and Packet
Channel
- channelIdis no longer a string but a unique- uint32.
- ChannelEnd:- connectionHopshas been renamed to- connectionIdand it’s type changed from- [connectionIdentifer]to- connectionId.
- upgradeSequencehas been removed.
 
type ChannelId = Uint32data ChannelOrder = Ordered | Unordereddata ChannelState = Init | TryOpen | Open | Closed
class ChannelEnd where    state ∷ ChannelState    ordering ∷ ChannelOrder    connectionId ∷ ConnectionId    counterpartyChannelId ∷ ChannelId    counterpartyPortId ∷ String    version ∷ StringPacket
- Packet- sourcePorthas been removed.
- destinationPorthas been removed.
- timeoutHeighttype has been changed from- (uint64, uint64)to- uint64.
 
class Packet where    sequence ∷ Uint64    sourceChannel ∷ ChannelId    destinationChannel ∷ ChannelId    payload ∷ Bytes    timeoutHeight ∷ Uint64    timeoutTimestamp ∷ Uint64ICS-024 Host
Path-space
Union approach to the path-space is deviating from the canonical implementation. Since we no longer use string for client/connection/channel identifiers, the commitment paths are binary.
type Prefix = Uint256
clientStatePrefix ∷ PrefixclientStatePrefix = 0x00
consensusStatePrefix ∷ PrefixconsensusStatePrefix = 0x01
connectionsPrefix ∷ PrefixconnectionsPrefix = 0x02
channelsPrefix ∷ PrefixchannelsPrefix = 0x03
packetsPrefix ∷ PrefixpacketsPrefix = 0x04
packetAcksPrefix ∷ PrefixpacketAcksPrefix = 0x05
nextSeqSendPrefix ∷ PrefixnextSeqSendPrefix = 0x06
nextSeqRecvPrefix ∷ PrefixnextSeqRecvPrefix = 0x07
nextSeqAckPrefix ∷ PrefixnextSeqAckPrefix = 0x08
commit ∷ AbiEncode a ⇒ a → Bytes32commit x = keccak256 (abiEncode x)
clientStateKey ∷ ClientId → Bytes32clientStateKey clientId = commit (clientStatePrefix, clientId)
consensusStateKey ∷ ClientId → Uint64 → Bytes32consensusStateKey clientId height = commit (consensusStatePrefix, clientId, height)
connectionKey ∷ ConnectionId → Bytes32connectionKey connectionId = commit (connectionsPrefix, connectionId)
channelKey ∷ ChannelId → Bytes32channelKey channelId = commit (channelsPrefix, channelId)
packetKey ∷ ChannelId → Bytes32 → Bytes32packetKey channelId packetHash = commit (packetsPrefix, channelId, packetHash)
packetReceiptKey ∷ ChannelId → Bytes32 → Bytes32packetReceiptKey channelId packetHash = commit (packetAcksPrefix, channelId, packetHash)
nextSequenceSendKey ∷ ChannelId → Bytes32nextSequenceSendKey channelId = commit (nextSeqSendPrefix, channelId)
nextSequenceRecvKey ∷ ChannelId → Bytes32nextSequenceRecvKey channelId = commit (nextSeqRecvPrefix, channelId)
nextSequenceAckKey ∷ ChannelId → Bytes32nextSequenceAckKey channelId = commit (nextSeqAckPrefix, channelId)Commitments
After all preconditions are met, the protocol commits a succinct digest of the structures we need to prove on the counterparty. This commitments are encoded differently than in the canonical implementation, instead of protobuf, we use the solidity contract ABI encoding encoding.
Let’s define the setCommitment and it’s associated commit functions to update a store.
setCommitment ∷ Store → Bytes32 → Bytes32 → Store
commit ∷ AbiEncode a ⇒ a → Bytes32commit x = keccak256 (abiEncode x)Client
commitClientState ∷ Store → ClientId → ClientState → StorecommitClientState store clientId clientState =  setCommitment (clientStateKey clientId) (commit clientState)
commitConsensusState ∷ Store → ClientId → ConsensusState → StorecommitConsensusState store clientId consensusState =  setCommitment (clientConsensusKey clientId) (commit consensusState)Connection
commitConnection ∷ Store → ConnectionId → ConnectionEnd → StorecommitConnection store connectionId connection =  setCommitment (connectionKey connectionId) (commit connection)Channel
commitChannel ∷ Store → ChannelId → ChannelEnd → StorecommitChannel store channelId channel =  setCommitment (channelKey channelId) (commit channel)Packet
commitmentMagic ∷ Bytes32commitmentMagic = 0x0100000000000000000000000000000000000000000000000000000000000000
commitPacket ∷ Store → ChannelId → Packet → StorecommitPacket store channelId packet =  setCommitment (packetKey channelId (commit packet)) commitmentMagic
mergeAck ∷ Bytes32 → Bytes32mergeAck ack =  commitmentMagic |    (ack & 0x00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
commitReceipt ∷ Store → ChannelId → Packet → StorecommitReceipt store channelId packet =  setCommitment (packetReceiptKey channelId (commit packet)) commitmentMagic
type Acknowledgement = Bytes
commitAck ∷ Store → ChannelId → Packet → Acknowledgement → StorecommitAck store channelId packet ack =  setCommitment (packetReceiptKey channelId (commit packet)) (mergeAck (keccak256 ack))Extensions
ICS-004 Packet
The packetKey and packetReceiptKey are special commitments that no longer takes a sequence but the whole packet hash. This allows us to extend the protocol with batching for sent packets and written acknowledgements.
commitmentMagic ∷ Bytes32commitmentMagic = 0x0100000000000000000000000000000000000000000000000000000000000000
setCommitment ∷ Store → Bytes32 → Bytes32 → StoregetCommitment ∷ Store → Bytes32 → Bytes32
mergeAck ∷ Bytes32 → Bytes32mergeAck ack =  commitmentMagic |    (ack & 0x00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
commitmentExist ∷ Store → Bytes32 → Packet → BoolcommitmentExist store expectedCommitment packet =  getCommitment store (commit packet) ≡ exceptedCommitment
batchSend ∷ Store → [Packet] → StorebatchSend store packets = do  assert (all (commitmentExist store commitmentMagic) packets)  setCommitment store (packetKey channelId (commit packets)) commitmentMagic
batchAcks ∷ Store → [Packet] → [Acknowledgement] → StorebatchAcks store packets acks = do  assert (    all      (\(ack, packet) -> commitmentExist store (mergeAck (keccak256 ack)) packet)      (zip acks packet)  )  setCommitment    store    (packetReceiptKey channelId (commit packets))    (mergeAck (commit acks))batchSend is used to commit a batch of previously sent packets. It allows the relayer to provide a single membership proof for the whole batch at destination (recv).
batchAcks is used to commit a batch of previously written acknowledgements. It allows the relayer to provide a single membership proof for the whole batch at destination (ack).
This functions can be used to trade execution gas on the source chain for the destination and vice versa. Committing a batch of sent packets will require an extra transaction on the source but will lower the execution gas on destination (single proof). Similarly, the batching of acknowledgements trade execution gas on the destination for the source. This further allow relayers and market makers to efficiently handle asset transfers based on gas price.