Create Sender and Recipient smart contracts adhering to the structure shown below
Set mailbox and CCIP read ISM to the ISM address in references in INCO recipient during deployment
Now deploy sender contract to any chain of choice and configure the mailbox to the mailbox of that chain and the recipient to the contract you just deployed on INCO
Now send the necessary Ciphertext to the read server using the URL and request format below
Next send the same calldata to the function which commits Ciphertext hash to inco.
Now the relayer will query the ciphertext corresponding to the hash during relay time and finally process both ciphertext and hash by calling the handlewithCiphertext function on the recipient.
now the hash and corresponding Ciphertext is available in the CCIP read server and can be retrieved by relayer.
Formatting Ciphertext before uploading to the Server.
The fhevmjs library returns the ciphertext in the form of uint8 array. However we will be converting it to bytes and uploading it to the server.
use the following format technique to convert the ciphertext to bytes and remove additional padding provided by ethers.
const Ciphertext=fhevmInstance.alice.encrypt8(1);
const bytes_ciphertext=AbiCoder.defaultAbiCoder().encode(["bytes"], [Ciphertext]);
let padded_ciphertext:string="0x" + bytes_ciphertext.slice(130, 33146);
//commit to CCIP-server
//Dispatch transaction to origin chain
This formatting is important and allows correct dispatch and verification of ciphertext.
Smart Contract Integration
We will have to configure our smart contract logic on both origin chain and Inco to make sure this setup works. The guide and template code for integration is shown below
Origin Chain Contract
The integration to smart contracts on origin chain will be same as dispatch function used in Hyperlane. The origin chain can hold the hash of the ciphertext. while the metadata is made available on Inco for the corresponding hash.
We can also retrieve the hash of the ciphertext from the function for verification purpose.
You can refer the following template code.
Sender.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;
import {IMailbox} from "@hyperlane-xyz/core/contracts/interfaces/IMailbox.sol";
import {IPostDispatchHook} from "@hyperlane-xyz/core/contracts/interfaces/hooks/IPostDispatchHook.sol";
import {IInterchainSecurityModule} from "@hyperlane-xyz/core/contracts/interfaces/IInterchainSecurityModule.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
contract CipherTextCommitment is OwnableUpgradeable{
IPostDispatchHook public hook;
address mailbox;
address recipient;
mapping (bytes32 =>bytes32) hash;
uint32 DomainID=9090;
function setHook(address _hook) public onlyOwner {
hook = IPostDispatchHook(_hook);
}
event hashedCiphertext(bytes32 _hash);
constructor(address _mailbox,address _recipient) {
mailbox = _mailbox;
recipient=_recipient;
}
modifier onlyMailbox() {
require(
msg.sender == mailbox,
"Only mailbox can call this function !!!"
);
_;
}
//template code
function CommitCiphertextHash(bytes calldata Ciphertext) payable external returns (bytes32) {
//necessary snippet
bytes32 _hash = keccak256(Ciphertext);
hash[_hash] = _hash;
//necessary snippets
uint256 quote = IMailbox(mailbox).quoteDispatch(DomainID, addressToBytes32(recipient), abi.encode(_hash));
IMailbox(mailbox).dispatch{value: quote}(DomainID, addressToBytes32(recipient), abi.encode(_hash));
emit hashedCiphertext(_hash);
return _hash;
}
function addressToBytes32(address _addr) internal pure returns (bytes32) {
return bytes32(uint256(uint160(_addr)));
}
}
Additional metadata can be sent alongside hash, but hash must be the first variable while encoding according to ABI followed by arbitrary data of your choice.
Inco gentry smart contract
The smart contract on Inco has slight modifications needed to process our message
We will have to define the ISM to be CCIP-read ISM by specifying the address of ISM in the reference section by calling the setInterchainSecurityModule in the contract.
Additional handler function called handleWithCiphertext is called by ISM to pass the Ciphertext
Use the following template code
Recipient.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;
import {IMailbox} from "@hyperlane-xyz/core/contracts/interfaces/IMailbox.sol";
import {IPostDispatchHook} from "@hyperlane-xyz/core/contracts/interfaces/hooks/IPostDispatchHook.sol";
import {IInterchainSecurityModule} from "@hyperlane-xyz/core/contracts/interfaces/IInterchainSecurityModule.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
contract CipherTextProcessor is OwnableUpgradeable{
IInterchainSecurityModule public interchainSecurityModule;
function setInterchainSecurityModule(address _module) public {
interchainSecurityModule = IInterchainSecurityModule(_module);
}
event handled (bytes32 hash);
address mailbox; // address of mailbox contract
address public ISM;
constructor(address _mailbox, address _interchainSecurityModule) {
mailbox = _mailbox;
ISM=_interchainSecurityModule;
//Sets CCIP read ISM as ISM to be used
setInterchainSecurityModule(_interchainSecurityModule);
}
// Modifier so that only mailbox can call particular functions
modifier onlyMailbox() {
require(
msg.sender == mailbox,
"Only mailbox can call this function !!!"
);
_;
}
modifier onlyISM() {
require(
msg.sender == ISM,
"Only mailbox can call this function !!!"
);
_;
}
//no-op function but called by mailbox pos verification so keep
function handle( uint32 _origin,
bytes32 _sender,
bytes memory _body)external onlyMailbox {
(bytes32 committedHash)= abi.decode(_body, (bytes32));
emit handled(committedHash);
}
//*************************************************************************
// Necessary function to be implemented with no changes as it is called by ISM with metadata
function handleWithCiphertext( uint32 _origin,
bytes32 _sender,
bytes memory _message)external onlyISM {
(bytes memory message,bytes memory ciphertext)=abi.decode(_message,(bytes , bytes));
(bytes32 committedHash)= abi.decode(message, (bytes32));
//rest of function logic
}
//**************************************************************************
}
This will allow the ISM to write the ciphertext and hash on your recipient.
In case the application requires using both Ciphertext services and general message passing, refer the Routing ISM section.