Message

The message is the core data structure used by the Hyperlane protocol. It is a packed data structure that contains all the information needed to route a message from one domain to another. The structure of the message is as follows:

#[derive(Serde, starknet::Store, Drop, Clone)]
pub struct Message {
    pub version: u8,
    pub nonce: u32,
    pub origin: u32,
    pub sender: u256,
    pub destination: u32,
    pub recipient: u256,
    pub body: Bytes,
}

There was an issue that the sender and the recipient were the ContractAddress type, which is felt252, so if the sender or the recipient is using the 32-bytes addressing mechanism, those will not be a valid address. See Finding ref for more details.

Mailbox

The mailbox is the entry point for developers to send and receive messages from.

mailbox contract

The mailbox contract handles message dispatching, processing, and security checks for cross-chain communication.

Key functions for mailbox are as follows:

  • dispatch — sends messages to the destination domain and recipient

  • process — receives and processes incoming messages

  • quote_dispatch — computes quote for dispatching a message to the destination domain and recipient

  • recipient_ism — determines the ISM for a recipient

The dispatch function is to send messages to the destination domain and recipient. The key steps of the function are as below:

  1. Build the message using the destination domain, sender, and provided details.

  2. Generate a unique message ID.

  3. Emit Dispatch and DispatchId events.

  4. Handle fee collection and distribution:

    • Calculate fees for required and default hooks.

    • Check if a sufficient fee is provided.

    • Verify the sender's balance and allowance.

    • Transfer fees to the respective hooks.

  5. Execute post-dispatch hooks.

The process function is to receive and process incoming messages from other sources. The key steps of the function are as below:

  1. Verify the message version and destination.

  2. Check if the message has already been delivered.

  3. Record the delivery in storage.

  4. Determine the recipient's ISM.

  5. Emit Process and ProcessId events.

  6. Verify the message using the recipient's ISM.

  7. Call the recipient's handle function with the message details.

mailboxclient contract

This contract serves as a proxy for the Mailbox client in the Hyperlane cross-chain communication system. It integrates MailboxclientComponent, OwnableComponent, and UpgradeableComponent to provide mailbox functionality with ownership management and upgrade capabilities.

The constructor function initializes the contract with mailbox address and owner, and the upgrade function allows the owner to upgrade the contract implementation.

mailboxclient_component contract

The MailboxclientComponent is a crucial part of the Hyperlane cross-chain messaging system, providing core functionality for managing mailbox interactions, hooks, and ISMs.

MailboxClient exposes functions that allow subclasses to easily send messages to the Mailbox via the mailbox storage variable and permission message delivery via the onlyMailbox modifier.

Key functions for mailboxclient_component are as follows:

  • set_hook — sets the custom hook address

  • set_interchain_security_module — sets the ISM address

  • _MailboxClient_initialize — initializes the mailbox client configuration

  • _dispatch — dispatches a message to a destination domain and recipient

  • quote_dispatch — computes quote for dispatching a message to the destination domain and recipient

  • initialize — initializes the mailbox client with a mailbox address

Hooks

Post-dispatch hooks allow developers to configure additional origin chain behavior with message content dispatched via the Mailbox. This allows developers to integrate third-party/native bridges, make additional chain commitments, or require custom fees all while maintaining a consistent single-call Mailbox interface.

protocol_fee contract

This contract implements a post-dispatch hook for collecting protocol fees in the Hyperlane cross-chain messaging system. It manages fee collection, beneficiary settings, and provides functionality for fee quotes and collection.

Key functions for protocol_fee are as follows:

  • post_dispatch — processes the protocol fee after message dispatch

  • quote_dispatch — provides a quote for the protocol fee

  • set_protocol_fee — sets the protocol fee and can be called by the owner

  • set_beneficiary — sets the beneficiary of collected fees and can be called by the owner

  • collect_protocol_fees — transfers collected fees to the beneficiary

merkle_tree_hook contract

This contract implements a post-dispatch hook for maintaining a Merkle tree of dispatched messages in the Hyperlane cross-chain messaging system. It provides functionality for inserting messages into the tree, calculating roots, and managing the tree structure.

Key functions for merkle_tree_hook are as follows:

  • post_dispatch — processes the message after dispatch by inserting it into the Merkle tree

  • quote_dispatch — provides a quote for the hook operation (returns 0 in this implementation)

  • _insert — inserts a new node into the Merkle tree

  • _root — calculates the current root of the Merkle tree

  • _branch_root — calculates the root given a leaf, branch, and index

The same message can be inserted into the Merkle tree hook multiple times, as long as it is the latest dispatched message. We discussed with the Pragma team and got the response that this is the same with the Solidity implementation too and it is acceptable behavior. Even if the message is inserted multiple times, it will only be delivered once. See Discussion point ref for more details.

Interchain Security modules

Hyperlane is secured by ISMs. ISMs are smart contracts that are responsible for verifying that interchain messages being delivered on the destination chain were actually sent on the origin chain.

Hyperlane developers can optionally override the Mailbox's default ISM by specifying an application-specific ISM, which they can configure, compose, and customize according to the needs of their application.

The primary function that ISMs must implement is verify(). The Mailbox will call IInterchainSecurityModule.verify() before delivering a message to its recipient. If verify() reverts or returns false, the message will not be delivered.

The verify() function takes two parameters:

  1. _metadata. This consists of arbitrary bytes provided by Relayer. Typically, these bytes are specific to the ISM. For example, for Multisig ISM, _metadata must include validator signatures.

  2. _message. This consists of the Hyperlane message being verified. ISMs can use this to inspect details about the message being verified. For example, Multisig ISM could change validator sets based on the origin chain of the message.

The following shows a simplified sequence diagram of an interchain message being verified and delivered on the destination chain.

ism_sequence_diagram

Multisig ISM

The Multisig ISM is one of the most commonly used ISM types. These ISMs verify that m-of-n validators have attested to the validity of a particular interchain message. It should be implemented to check if the metadata provided to verify satisfies a quorum of signatures from a set of configured validators.

message_id_multisig contract

This contract implements a multi-sig ISM for message verification in a cross-chain communication system. It manages a set of validators and a threshold for message verification.

The structure of the message_id_multisig metadata is as follows:

  • [ 0: 32] Origin Merkle tree address

  • [ 32: 64] Signed checkpoint root

  • [ 64: 68] Signed checkpoint index

  • [ 68:????] Validator signatures (length := threshold * 65)

In order to verify the message, the following checks must pass:

  1. The metadata must not be empty.

  2. The calculated digest must match the message and metadata.

  3. The number of valid signatures must meet or exceed the threshold.

  4. Each signature must be from a recognized validator in the list.

  5. The threshold must be greater than zero.

merkleroot_multisig contract

This contract implements a Merkle root--based multi-sig ISM for verifying cross-chain messages. It manages a set of validators and a threshold for message verification, incorporating Merkle proofs in the verification process.

The structure of the merkleroot_multisig metadata is as follows:

  • [ 0: 32] Origin Merkle tree address

  • [ 32: 36] Index of message ID in Merkle tree

  • [ 36: 68] Signed checkpoint message ID

  • [ 68:1092] Merkle proof

  • [1092:1096] Signed checkpoint index (computed from proof and index)

  • [1096:????] Validator signatures (length := threshold * 65)

In order to verify the message, the following checks must pass:

  1. The metadata must not be empty.

  2. The message index in the metadata must be valid (not greater than the signed index).

  3. The Merkle proof in the metadata must be valid for the given message.

  4. The calculated digest, including the Merkle root, must be correct.

  5. The number of valid signatures must meet or exceed the threshold.

  6. Each signature must be from a recognized validator in the list.

  7. The threshold must be greater than zero.

Routing ISM

Developers can use a Routing ISM to delegate message verification to a different ISM. This allows developers to change security models based on message content or application context.

This ISM simply switches security models depending on the origin chain of the message. A simple use case for this is to use different Multisig ISM validator sets for each chain.

Eventually, you could imagine a DomainRoutingIsm routing to different light-client--based ISMs, depending on the type of consensus protocol used on the origin chain.

Aggregation ISM

Developers can use an Aggregation ISM to combine security from multiple ISMs. Simply put, an Aggregation ISM requires that m-of-n ISMs verify a particular interchain message.

Developers can configure, for each origin chain, a set of n ISMs, and the number of ISMs needed to verify a message.

Aggregation ISMs can aggregate the security of any ISMs. For example, users can deploy a Multisig ISM with their own validator set and deploy an Aggregation ISM that aggregates that ISM with the Hyperlane default ISM.

The structure of the merkleroot_multisig metadata is as follows:

  • [????:????] Metadata start/end uint32 ranges, packed as uint64

  • [????:????] ISM metadata, packed encoding

There was an issue that the valid message could be unverifiable in the verify function. See Finding ref for more details.

In order to verify the message, the following checks must pass:

  1. The threshold must be set and not zero.

  2. The number of modules must be sufficient to potentially meet the threshold.

  3. Each module in the list must be checked if it has corresponding metadata.

  4. For modules with metadata, their individual verify function must return true.

  5. The number of successful verifications must reach the threshold.

  6. No module verification should fail during the process.

  7. The verification process must continue until the threshold is met or all modules are checked.

  8. After checking all modules, the number of successful verifications must equal the threshold.

  9. No assertions should fail or errors be thrown during the entire process.

noop ISM

The verify function always returns true so that all verifications will succeed.

fn verify(self: @ContractState, _metadata: Bytes, _message: Message) -> bool {
    true
}

Pausable ISM

The verify function returns true if it is not paused by the owner.

fn verify(self: @ContractState, _metadata: Bytes, _message: Message) -> bool {
    self.pausable.assert_not_paused();
    true
}

Validator Announcing contract

The Validators announce their signature storage location so that the relayer can fetch and verify their signatures.

The announce function in the contract handles a few steps to announce a validator-signature storage location. This was different from the original Solidity implementation, but it has been acknowledged. See Finding ref for more details.

  1. It converts input parameters and generates a replay_id using a Poseidon hash.

  2. It checks for replay protection to prevent duplicate announcements.

  3. It calculates the announcement digest and verifies the provided signature.

  4. It adds the validator to the list if new or updates existing validator information.

  5. It stores the new storage location for the validator and updates related counts.

  6. Reverts if the announcement already occurred or if the given signature is invalid.

Zellic © 2025Back to top ↑