Cross-chain Asset Transfer Without a Bridge - Part One
Share on

To accomplish cross-chain asset transfer, most of the currently available solutions are based on a bridge, a separate, intermediate entity typically trusted with holding these assets during some period of the transaction. This trust assumption is undesirable, as it creates a large opportunity for attack. In this post, I will explain that, assuming the existence of a zkRollup, one can implement cross-chain asset transfer without the need for additional trust assumptions (such as a bridge).

Introduction

Suppose you hold 500,000 USDT as an L2 asset on Ethereum, and you want to transfer it to USDT on Avalanche. Currently, you would need to contract the services of a bridge, B, a separate entity that will "buy" your ETH-USDT and, shortly after, "sell" you AVE-USDT.

Observe that various assumptions are at work here:

  1. You need to trust B with your assets for some period of time. If the company is hacked or goes bankrupt, you may never see your assets again.
  2. This bridge needs to have 500,000 AVE-USDT as a reserve; if not, the transaction cannot proceed.

In this post, I will show a way to accomplish this asset transfer without the disadvantages mentioned here. More specifically, I will show that by judiciously combining the underlying primitives and using the standard security assumptions of zkRollups, we can implement cross-chain asset transfer.

To this end, we must first prepare the stage by summarizing Rollups in general and zkRollups in particular. Then, we will examine intra-layer and cross-layer asset transfer and discuss what a proof of transaction entails in each case. This sets the stage for the most important part: cross-chain asset transfer. We will first consider an L1-to-L1 scenario, followed by an L2-to-L2 scenario. As a new contribution, we will present the entangled rollup: one L2 shared by two L1s. This will be followed by a discussion on cross-chain communication methods and underlying assumptions.

A Quick Reminder of Rollups

In a rollup, transaction processing is taken off-chain and combined into batches to reduce cost and enhance speed. However, every transaction will be recorded on the main chain, along with the overall rollup ledger's state. There is always a delay between the core L2 and its being recorded on L1.

In the words of Vitalik Buterin:

“Rollups move computation (and state storage) off-chain, but keep some data per transaction on-chain. To improve efficiency, they use a whole host of fancy compression tricks to replace data with computation wherever possible. [...] There is a smart contract on-chain which maintains a state root: the Merkle root of the state of the rollup (meaning, the account balances, contract code, etc., that are "inside" the rollup). Anyone can publish a batch, a collection of transactions in a highly compressed form together with the previous state root and the new state root (the Merkle root after processing the transactions). The contract checks that the previous state root in the batch matches its current state root; if it does, it switches the state root to the new state root.”

For data, this means that all transaction data must stay on L1, but in compressed form to reduce gas costs. For processing, this means that computations are moved to L2 as much as possible, while only the results are written on L1.

In other words, a rollup emulates the ledger functionality on top of the original blockchain, which are therefore called Layer 2 and Layer 1, respectively.

But how is transaction validity assured in a rollup?

Just relying on the correctness of the smart contracts that implements the rollup is not enough. The definition of a rollup is that each state transition (i.e.\ transaction validity) is maintained, verified and endorsed by the main chain, which therefore needs an external (software independent) validation or verification mechanism, independent of the L2 implementation.

Two mechanisms are commonly used:

1 -- Fault proofs, as used by optimistic rollups

In an optimistic rollup, an entity (often called the Sequencer) publishes \( oldStateRoot_2, txBatch, newStateRoot \)(which relate to Layer 2) on \( layer_1 \). During a specified period of time (often seven days), this state^ change can be challenged by an outsider. If no such challenge occurs, the proposed state becomes permanent (hence "optimistic"). However, if a state change is challenged, the following process is triggered.

An arbitration contract on L1, assumed to exist, will decide whether the Sequencer or the Challenger is correct. This arbitration contract is activated by the Challenger.

The contract then declares who is right and who is wrong. As a result, the loser of the arbitration will have their bond (a significant deposit) slashed as a penalty.

Thus, in an optimistic rollup, parties have an economic (game-theoretic) incentive to behave honestly.

For a more detailed description about optimistic rollups, see:
ethereum.org/en/developers/docs/scaling/optimistic-rollups/#what-is-an-optimistic-rollup 

2 -- Validity proofs, used in zkrollups.

In a zkRollup, a different approach is taken. For every state transition \( oldStateRoot, txBatch \Longrightarrow newStateRoot \) a proof of knowledge is generated, which shows that \(\text{newStateRoot}_{2}\) is the correct result of executing the batch. Though sometimes known as zero-knowledge proofs, this terminology is misleading, strictly speaking. Privacy isn't an issue in a rollup, since the transactions are supposed to be public anyway. What is an issue, though, is the succinctness of such a proof, since on-chain verification is expensive and therefore should be done efficiently (often a SNARK).

To be more precise: Let \( STF \) denote the state transition function for the Layer 2 ledger. If the Sequencer is honest, we must have that $$ newState = STF(oldState, txBatch) $$

Remember that on a blockchain, transaction contents can take several forms:

  • Data, which can be used to publish a message on a blockchain;
  • Payment information, which can be used to transfer assets from one account to another;
  • An L2 smart contract address; in this case, STF will execute this smart contract within the L2 smart contract execution environment.

So how can L1 (and with it, everybody who's monitoring this main chain) be convinced that L2 indeed computed $newState_2$ correctly from $oldState_2$ and $txBatch$? By applying verifiable computing on STF This means that the execution trace of $STF(oldState_2,batch)$ is sent to a Validity Proof Generator, which produces a succinct non-interactive validity proof that Equation (1) holds.

Subsequently, the rollup contract on L1 has a method which, on input $oldState_2, batch, newState_2, validityProof$, verifies the validity proof and returns an ACCEPT/REJECT verdict. In case of an ACCEPT, $oldState_2, batch, newState_2$ are recorded as valid on L1, while in case of REJECT the batch and state change are simply discarded.

Intra-layer Asset Transfer and Asset Invariance

The raison d'être of blockchain is asset transfer, and extreme care is taken to preserve asset invariance during transactions: nothing is lost, nothing is gained. It’s important to note that the underlying security mechanism of asset invariance is subtly different for L1 and L2.

Layer 1

The correctness of asset transfer on L1 is guaranteed by the consensus mechanism adopted for L1 and is publicly verifiable because it is open-source software.

Layer 2.

L1 only guarantees the availability of the rollup data of L2, not its correctness. The correctness of asset transfer on L2 depends on the correct implementation of L2. The zkVM merely verifies that the rollup data published on L1 is consistent with the ledger program on L2, but if there is a mistake in this program, the zkVM will not catch it.

However, since the L2 source is often copied from L1, this risk is minimized.

Decentralized Sequencer

The decentralized Sequencer ensures that submitted transactions are batched (avoiding censorship) but does not ensure asset invariance.

Cross-layer Asset Transfers

Given an L1 with an L2 rollup, we can imagine asset transfers between them in two directions. We’ll use Ether as an example:

  • Deposit (or buy) L2 assets: Deposit ETH on L1 and obtain ETH₂, the "wrapped" token created for L2.
  • Withdraw (or sell) L2 assets: Redeem ETH₂ and convert them to ETH on L1.

These cross-layer asset transfers can be interpreted as a vertical bridge, also called a native bridge.

To assure asset invariance, these transactions adhere to one of the following principles, depending on the context:

  • Atomicity: Either the entire transaction succeeds, or the entire transaction is rolled back. The system is never left in an intermediate, inconsistent state. A smart contract typically guarantees this property.
  • First destroy (lock, burn) the asset, create a proof of this fact, then create (unlock, mint) the corresponding value elsewhere. In this case, if the asset is not created for any reason, some party incurs an economic loss and has an incentive to complain. Conversely, when assets are created without an equivalent value being destroyed, the benefiting party may choose to remain silent.

To accomplish this, we define:

  • X₁ = customer X's account on L1
  • X₂ = customer X's account on L2
  • RollupAcc₁ = rollup's asset transfer account in ETH on L1
  • RollupAcc₂ = rollup's asset transfer account in ETH₂ on L2

These last two accounts serve to lock and unlock transactions. The smart contracts that manage these accounts are called shadow contracts.

To avoid discussion about lock/unlock versus burn/mint, we assume that, on initialization, a large fixed quantity of ETH₂ is minted for (credited to) RollupAcc₂. These assets remain locked in this account unless unlocked by an equivalent lock transaction on L1 (Polygon does this too). Note that asset invariance should hold: RollupAcc₁ + RollupAcc₂ = InitialQuantity, always.

Asset Transfer from L1 to L2 — Deposit

When transferring assets from L1 to L2, there are two options: using atomicity or the first-destroy-then-create principle.

Deposit Option 1 — Atomic Transaction

The rollup smart contract on L1 performs the transaction on both L1 and the rollup, leaving L2 to clean up. Many rollups implement this as an emergency mechanism to avoid censorship.

Deposit 1: Transfer an amount of z ETH from L1 to ETH₂ on L2.

  • Smart contract on L1 (L1 performs both transactions):
    1. txId = TransferAsset(X₁, RollupAcc₁, z)
    2. UpdateRollupData(X₂, z)
    3. WAIT for settlement on L1
  • Sequencer (SEQ): Through some mechanism, the Sequencer receives txId.
  • Smart contract on L2: TransferAsset(RollupAcc₂, X₂, z) (L2 cleans up the mess)

This approach has some advantages: atomicity is guaranteed by L1, and transaction settlement time is short. However, it complicates sequencing and increases gas costs.

Deposit Option 2 — First Destroy, Then Create

The smart contract on L1 produces a proof of the transaction and sends it to the Sequencer to trigger the transaction on L2. The smart contract on L2 trusts L1, so the proof-of-transaction is not a ZK Proof, just a simple txId.

Deposit 2: Transfer z ETH from L1 to ETH₂ on L2.

  • Smart contract on L1:some text
    • txId = TransferAsset(X₁, RollupAcc₁, z) (lock asset on L1)
    • WAIT for settlement on L1
  • Sequencer (SEQ): Through some mechanism, SEQ receives txId.
  • Smart contract on L2:some text
    • TransferAsset(RollupAcc₂, X₂, z) (unlock asset on L2)
    • WAIT for settlement of the rollup data on L1

In step 3, there are several options: SEQ monitors L1, SEQ is alerted by the smart contract on L1, or SEQ is notified by Client X, etc.

Redeem — Asset Transfer from L2 to L1

The smart contract on L2 produces a proof of the transaction and sends it to the smart contract on L1 as a trigger for the transaction on L1. Since L1 does not trust L2, this proof-of-transaction must be a ZK Proof, which functions like a certified check. Asset invariance is guaranteed through the first-destroy-then-create paradigm.

Measures should be taken to ensure that a check can only be cashed once; maintaining a list of previously cashed checks is a good solution, combined with expiration dates to prevent the list from growing indefinitely.

Note that in this case, triggering the smart contract on L1 to perform the transaction on the rollup and letting L2 clean up the mess is not an option, as the rollup data is compressed and incomprehensible to the smart contract on L1.

Withdraw: Transfer assets from L2 to L1

  • Smart contract on L2: TransferAsset(X₂, RollupAcc₂, z)
  • WAIT for settlement of the rollup data on L1
  • SEQ/ZKProver: Generate a ZK Proof for the correctness of this transaction
  • SEQ: Send the ZK Proof to the shadow contract on L1
  • Smart contract on L1 — Unlock: TransferAsset(RollupAcc₁, X₁, z)
  • WAIT for settlement on L1

Observe that two steps require waiting for the transaction to settle.

This article was written by Jeroen van de Graaf, Senior Cryptographer at ZKM. Part Two of the series will examine the mechanism of the Entangled Rollup in more detail.

More articles
Getting to Know zkMIPS Proving Architecture
TL;DR: zkMIPS proves the correct execution of a MIPS program in five steps: it (1) divides the program in segments, (2) divides the instructions of each segment in four module tables, (3) proves instructions from each module table independently, (4) proves instructions from each segment is contained in one of its tables, and (5) recursively proves that the sequence of segment match the program execution. Step 3 is written as a STARK, step 4 is a logUp proof written as a STARK, and step 5 as a PLONK proof. All proof steps are implemented with the Plonky2 library. Optionally, one can generate a final Groth16 proof to verify the program execution on-chain.
The Quantum Entangled Network
ZKM is building the Quantum Network using Entangled Rollups to enable Universal Settlement.
Cross-chain Asset Transfer Without a Bridge - Part One

To accomplish cross-chain asset transfer, most of the currently available solutions are based on a bridge, a separate, intermediate entity typically trusted with holding these assets during some period of the transaction. This trust assumption is undesirable, as it creates a large opportunity for attack. In this post, I will explain that, assuming the existence of a zkRollup, one can implement cross-chain asset transfer without the need for additional trust assumptions (such as a bridge).

Introduction

Suppose you hold 500,000 USDT as an L2 asset on Ethereum, and you want to transfer it to USDT on Avalanche. Currently, you would need to contract the services of a bridge, B, a separate entity that will "buy" your ETH-USDT and, shortly after, "sell" you AVE-USDT.

Observe that various assumptions are at work here:

  1. You need to trust B with your assets for some period of time. If the company is hacked or goes bankrupt, you may never see your assets again.
  2. This bridge needs to have 500,000 AVE-USDT as a reserve; if not, the transaction cannot proceed.

In this post, I will show a way to accomplish this asset transfer without the disadvantages mentioned here. More specifically, I will show that by judiciously combining the underlying primitives and using the standard security assumptions of zkRollups, we can implement cross-chain asset transfer.

To this end, we must first prepare the stage by summarizing Rollups in general and zkRollups in particular. Then, we will examine intra-layer and cross-layer asset transfer and discuss what a proof of transaction entails in each case. This sets the stage for the most important part: cross-chain asset transfer. We will first consider an L1-to-L1 scenario, followed by an L2-to-L2 scenario. As a new contribution, we will present the entangled rollup: one L2 shared by two L1s. This will be followed by a discussion on cross-chain communication methods and underlying assumptions.

A Quick Reminder of Rollups

In a rollup, transaction processing is taken off-chain and combined into batches to reduce cost and enhance speed. However, every transaction will be recorded on the main chain, along with the overall rollup ledger's state. There is always a delay between the core L2 and its being recorded on L1.

In the words of Vitalik Buterin:

“Rollups move computation (and state storage) off-chain, but keep some data per transaction on-chain. To improve efficiency, they use a whole host of fancy compression tricks to replace data with computation wherever possible. [...] There is a smart contract on-chain which maintains a state root: the Merkle root of the state of the rollup (meaning, the account balances, contract code, etc., that are "inside" the rollup). Anyone can publish a batch, a collection of transactions in a highly compressed form together with the previous state root and the new state root (the Merkle root after processing the transactions). The contract checks that the previous state root in the batch matches its current state root; if it does, it switches the state root to the new state root.”

For data, this means that all transaction data must stay on L1, but in compressed form to reduce gas costs. For processing, this means that computations are moved to L2 as much as possible, while only the results are written on L1.

In other words, a rollup emulates the ledger functionality on top of the original blockchain, which are therefore called Layer 2 and Layer 1, respectively.

But how is transaction validity assured in a rollup?

Just relying on the correctness of the smart contracts that implements the rollup is not enough. The definition of a rollup is that each state transition (i.e.\ transaction validity) is maintained, verified and endorsed by the main chain, which therefore needs an external (software independent) validation or verification mechanism, independent of the L2 implementation.

Two mechanisms are commonly used:

1 -- Fault proofs, as used by optimistic rollups

In an optimistic rollup, an entity (often called the Sequencer) publishes \( oldStateRoot_2, txBatch, newStateRoot \)(which relate to Layer 2) on \( layer_1 \). During a specified period of time (often seven days), this state^ change can be challenged by an outsider. If no such challenge occurs, the proposed state becomes permanent (hence "optimistic"). However, if a state change is challenged, the following process is triggered.

An arbitration contract on L1, assumed to exist, will decide whether the Sequencer or the Challenger is correct. This arbitration contract is activated by the Challenger.

The contract then declares who is right and who is wrong. As a result, the loser of the arbitration will have their bond (a significant deposit) slashed as a penalty.

Thus, in an optimistic rollup, parties have an economic (game-theoretic) incentive to behave honestly.

For a more detailed description about optimistic rollups, see:
ethereum.org/en/developers/docs/scaling/optimistic-rollups/#what-is-an-optimistic-rollup 

2 -- Validity proofs, used in zkrollups.

In a zkRollup, a different approach is taken. For every state transition \( oldStateRoot, txBatch \Longrightarrow newStateRoot \) a proof of knowledge is generated, which shows that \(\text{newStateRoot}_{2}\) is the correct result of executing the batch. Though sometimes known as zero-knowledge proofs, this terminology is misleading, strictly speaking. Privacy isn't an issue in a rollup, since the transactions are supposed to be public anyway. What is an issue, though, is the succinctness of such a proof, since on-chain verification is expensive and therefore should be done efficiently (often a SNARK).

To be more precise: Let \( STF \) denote the state transition function for the Layer 2 ledger. If the Sequencer is honest, we must have that $$ newState = STF(oldState, txBatch) $$

Remember that on a blockchain, transaction contents can take several forms:

  • Data, which can be used to publish a message on a blockchain;
  • Payment information, which can be used to transfer assets from one account to another;
  • An L2 smart contract address; in this case, STF will execute this smart contract within the L2 smart contract execution environment.

So how can L1 (and with it, everybody who's monitoring this main chain) be convinced that L2 indeed computed $newState_2$ correctly from $oldState_2$ and $txBatch$? By applying verifiable computing on STF This means that the execution trace of $STF(oldState_2,batch)$ is sent to a Validity Proof Generator, which produces a succinct non-interactive validity proof that Equation (1) holds.

Subsequently, the rollup contract on L1 has a method which, on input $oldState_2, batch, newState_2, validityProof$, verifies the validity proof and returns an ACCEPT/REJECT verdict. In case of an ACCEPT, $oldState_2, batch, newState_2$ are recorded as valid on L1, while in case of REJECT the batch and state change are simply discarded.

Intra-layer Asset Transfer and Asset Invariance

The raison d'être of blockchain is asset transfer, and extreme care is taken to preserve asset invariance during transactions: nothing is lost, nothing is gained. It’s important to note that the underlying security mechanism of asset invariance is subtly different for L1 and L2.

Layer 1

The correctness of asset transfer on L1 is guaranteed by the consensus mechanism adopted for L1 and is publicly verifiable because it is open-source software.

Layer 2.

L1 only guarantees the availability of the rollup data of L2, not its correctness. The correctness of asset transfer on L2 depends on the correct implementation of L2. The zkVM merely verifies that the rollup data published on L1 is consistent with the ledger program on L2, but if there is a mistake in this program, the zkVM will not catch it.

However, since the L2 source is often copied from L1, this risk is minimized.

Decentralized Sequencer

The decentralized Sequencer ensures that submitted transactions are batched (avoiding censorship) but does not ensure asset invariance.

Cross-layer Asset Transfers

Given an L1 with an L2 rollup, we can imagine asset transfers between them in two directions. We’ll use Ether as an example:

  • Deposit (or buy) L2 assets: Deposit ETH on L1 and obtain ETH₂, the "wrapped" token created for L2.
  • Withdraw (or sell) L2 assets: Redeem ETH₂ and convert them to ETH on L1.

These cross-layer asset transfers can be interpreted as a vertical bridge, also called a native bridge.

To assure asset invariance, these transactions adhere to one of the following principles, depending on the context:

  • Atomicity: Either the entire transaction succeeds, or the entire transaction is rolled back. The system is never left in an intermediate, inconsistent state. A smart contract typically guarantees this property.
  • First destroy (lock, burn) the asset, create a proof of this fact, then create (unlock, mint) the corresponding value elsewhere. In this case, if the asset is not created for any reason, some party incurs an economic loss and has an incentive to complain. Conversely, when assets are created without an equivalent value being destroyed, the benefiting party may choose to remain silent.

To accomplish this, we define:

  • X₁ = customer X's account on L1
  • X₂ = customer X's account on L2
  • RollupAcc₁ = rollup's asset transfer account in ETH on L1
  • RollupAcc₂ = rollup's asset transfer account in ETH₂ on L2

These last two accounts serve to lock and unlock transactions. The smart contracts that manage these accounts are called shadow contracts.

To avoid discussion about lock/unlock versus burn/mint, we assume that, on initialization, a large fixed quantity of ETH₂ is minted for (credited to) RollupAcc₂. These assets remain locked in this account unless unlocked by an equivalent lock transaction on L1 (Polygon does this too). Note that asset invariance should hold: RollupAcc₁ + RollupAcc₂ = InitialQuantity, always.

Asset Transfer from L1 to L2 — Deposit

When transferring assets from L1 to L2, there are two options: using atomicity or the first-destroy-then-create principle.

Deposit Option 1 — Atomic Transaction

The rollup smart contract on L1 performs the transaction on both L1 and the rollup, leaving L2 to clean up. Many rollups implement this as an emergency mechanism to avoid censorship.

Deposit 1: Transfer an amount of z ETH from L1 to ETH₂ on L2.

  • Smart contract on L1 (L1 performs both transactions):
    1. txId = TransferAsset(X₁, RollupAcc₁, z)
    2. UpdateRollupData(X₂, z)
    3. WAIT for settlement on L1
  • Sequencer (SEQ): Through some mechanism, the Sequencer receives txId.
  • Smart contract on L2: TransferAsset(RollupAcc₂, X₂, z) (L2 cleans up the mess)

This approach has some advantages: atomicity is guaranteed by L1, and transaction settlement time is short. However, it complicates sequencing and increases gas costs.

Deposit Option 2 — First Destroy, Then Create

The smart contract on L1 produces a proof of the transaction and sends it to the Sequencer to trigger the transaction on L2. The smart contract on L2 trusts L1, so the proof-of-transaction is not a ZK Proof, just a simple txId.

Deposit 2: Transfer z ETH from L1 to ETH₂ on L2.

  • Smart contract on L1:some text
    • txId = TransferAsset(X₁, RollupAcc₁, z) (lock asset on L1)
    • WAIT for settlement on L1
  • Sequencer (SEQ): Through some mechanism, SEQ receives txId.
  • Smart contract on L2:some text
    • TransferAsset(RollupAcc₂, X₂, z) (unlock asset on L2)
    • WAIT for settlement of the rollup data on L1

In step 3, there are several options: SEQ monitors L1, SEQ is alerted by the smart contract on L1, or SEQ is notified by Client X, etc.

Redeem — Asset Transfer from L2 to L1

The smart contract on L2 produces a proof of the transaction and sends it to the smart contract on L1 as a trigger for the transaction on L1. Since L1 does not trust L2, this proof-of-transaction must be a ZK Proof, which functions like a certified check. Asset invariance is guaranteed through the first-destroy-then-create paradigm.

Measures should be taken to ensure that a check can only be cashed once; maintaining a list of previously cashed checks is a good solution, combined with expiration dates to prevent the list from growing indefinitely.

Note that in this case, triggering the smart contract on L1 to perform the transaction on the rollup and letting L2 clean up the mess is not an option, as the rollup data is compressed and incomprehensible to the smart contract on L1.

Withdraw: Transfer assets from L2 to L1

  • Smart contract on L2: TransferAsset(X₂, RollupAcc₂, z)
  • WAIT for settlement of the rollup data on L1
  • SEQ/ZKProver: Generate a ZK Proof for the correctness of this transaction
  • SEQ: Send the ZK Proof to the shadow contract on L1
  • Smart contract on L1 — Unlock: TransferAsset(RollupAcc₁, X₁, z)
  • WAIT for settlement on L1

Observe that two steps require waiting for the transaction to settle.

This article was written by Jeroen van de Graaf, Senior Cryptographer at ZKM. Part Two of the series will examine the mechanism of the Entangled Rollup in more detail.