-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
61 additions
and
25 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,54 +1,90 @@ | ||
# Multi-hop cross-chain bridging | ||
|
||
This document briefly describes how multi-hop cross-chain bridging is done in Taiko. | ||
This document explains how multi-hop cross-chain bridging works in Taiko. | ||
|
||
## L1<->L2 data synchronization | ||
We'll use this diagram to illustrate a blockchain's state. The large triangle represents the world state, while the smaller triangle represents the storage tree of a special contract named the "Signal Service," deployed on both L1 and L2. | ||
|
||
## L1<->L2 data synchornization | ||
We will use the following diagram to represent a blockchain's state. The bigger triangle is the world state, the smaller triangle is the storage tree of a special contract called the *Signal Service*, which must be deployed on both L1 and L2. | ||
![State Diagram](./multihop/state.png) | ||
|
||
<img src="./multihop/state.png" height="280" style="padding:40px"> | ||
|
||
When a signal is sent by the Signal Service, a unique slot in the Signal Service's storage will be written with value `1`, as shown by the code below. | ||
When a signal is sent by the Signal Service, a unique slot in its storage is updated with a value of `1`, as shown in the Solidity code below: | ||
|
||
```solidity | ||
function _sendSignal(address sender, bytes32 signal) | ||
internal returns (bytes32 slot) | ||
{ | ||
function _sendSignal(address sender, bytes32 signal) internal returns (bytes32 slot) { | ||
if (signal == 0) revert SS_INVALID_SIGNAL(); | ||
slot = getSignalSlot(uint64(block.chainid), sender, signal); | ||
assembly { | ||
sstore(slot, 1) | ||
} | ||
} | ||
function getSignalSlot( | ||
uint64 chainId, | ||
address app, | ||
bytes32 signal | ||
) | ||
public | ||
pure | ||
returns (bytes32) | ||
{ | ||
function getSignalSlot(uint64 chainId, address app, bytes32 signal) public pure returns (bytes32) { | ||
return keccak256(abi.encodePacked("SIGNAL", chainId, app, signal)); | ||
} | ||
``` | ||
|
||
Merkle proofs can be used to verify that signals has been sent by specific senders if the signal service's state root is known on another chain. A full merkle proof consists of an *account proof* and a *storage proof*. Note that if the signal service's storage root (aka the *signal root*) is known on another chain, a storage proof is sufficient to verify the signal was indeed send on the source chain, a full merkle proof is not necessary. | ||
Merkle proofs can verify signals sent by specific senders when the signal service's state root is known on another chain. A full merkle proof comprises an *account proof* and a *storage proof*. However, if the signal service's storage root (or the *signal root*) is known on another chain, only a storage proof is necessary to verify the signal's source. | ||
|
||
![Merkle Proof](./multihop/merkle_proof.png) | ||
|
||
Taiko's core protocol code (TaikoL1.sol and TaikoL2.sol) automatically synchronizes or relays the state roots between L1 and L2. | ||
|
||
When chainA's state root is relayed to chainB, a special signal is sent in chainB's signal service. This signal is calculated incorporating chainA's block ID. These special signals are always sent by the target chain's signal service. | ||
|
||
![L1-L2 Sync](./multihop/l1_l2_sync.png) | ||
|
||
If you deploy more chains using Taiko protocol, you can create a chain of relayed state roots between them. | ||
|
||
![Three Chains](./multihop/three_chains.png) | ||
|
||
## Verifying bridged messages | ||
|
||
### One-hop bridging | ||
Consider the 1-hop example below. | ||
|
||
To verify that "some app" has sent a custom message, we verify if the corresponding signal (associated with the message sender, "some app") has been set by the signal service (0x1000A) on L1. After L1's state root is relayed to L2, we need the following info on L2 to verify the message on L1: | ||
|
||
1. Message's signal and its sender, to compute the storage slot now supposed to be 1. | ||
2. A full merkle proof generated by an L1 node for the above slot. | ||
3. L1 signal service's address associated with the merkle proof. | ||
4. L2 signal service's address to verify that L1's state root has been relayed to L2 already. | ||
|
||
![1-Hop Bridging](./multihop/bridge_1hop.png) | ||
|
||
<img src="./multihop/merkle_proof.png" height="280" style="padding:40px"> | ||
### Multi-hop bridging | ||
In the 2-hop example below, two merkle proofs are needed, and the signal service addresses for L1 and L2 need verification. L3's signal service address does not need verification as the bridging verification occurs in L3's signal service contract, with L3's signal service address being `address(this)`. | ||
|
||
![2-Hop Bridging](./multihop/bridge_2hop.png) | ||
|
||
## Caching | ||
|
||
Taiko's core protocol code (TaikoL1.sol and TaikoL2.sol) automatically cross-synchronize or relay the state roots between L1 and L2. | ||
Caching is optional and is activated per hop when the transaction intends to reuse some state root or signal root for future bridging verification. | ||
|
||
|
||
In the diagram below with 2 hops, L1's state root and L2's signal root can be cached to L3 if specified. | ||
|
||
![Cache Example 1](./multihop/cache_1.png) | ||
|
||
If both are cached, two more signals will be sent in L3's signal service. | ||
|
||
![Cache Example 1 Done](./multihop/cache_1_done.png) | ||
|
||
Depending on the type of data (state root or signal root), the signal is generated differently. | ||
|
||
```solidity | ||
function signalForChainData(uint64 chainId, bytes32 kind, bytes32 data) public pure returns (bytes32) { | ||
return keccak256(abi.encode(chainId, kind, data)); | ||
} | ||
``` | ||
|
||
When chainA's state root is relayed to chainB, a special signal is sent (written) in chainB's signal service. The signal is calculated such that chainA's block ID must be hashed in. Note that these special signals are always sent by the target chain's signal service, as shown in the diagram below. | ||
Once cached on L3, one full merkle proof is sufficient to verify everything that happened on L1 before or when L1's state root becomes 0x1111. This allows skipping the middle-hop. | ||
|
||
<img src="./multihop/l1_l2_sync.png" height="400" style="padding:40px"> | ||
![Cache Use 1](./multihop/cache_1_use_1.png) | ||
|
||
If you deploy more chains using Taiko protocol, you can have a chain of relayed state roots between these chains. | ||
If L1's state root is not cached on L3 but only L2's signal root is, then one full merkle proof for L1 and a storage proof for L2 are used to verify a bridged message. | ||
|
||
![Cache Use 2](./multihop/cache_1_use_2.png) | ||
|
||
<img src="./multihop/three_chains.png" style="padding-top:40px"> | ||
Note that the last hop (L2)'s state root has already been auto-relayed to L3, so it cannot be recached. Therefore, only the last hop's signal root can be cached. | ||
|
||
For all other non-last hops, if a full proof is used, the state root can be cached; if a storage proof is used, the signal root can be cached. But by default, caching is all disabled. |