Home About John Resume/CV References Writing Research

Ethereum Near Bridging

Overview

This document reviews the Ethereum 2.0 specifications including Light Client specifications. It does a detailed review of the NEAR Rainbow Bridge implementation and also includes references to Harmony’s design to support Mountain Merkle Ranges.

Key differences in supporting Ethereum 2.0 (Proof of Stake) vs Proof of Work involves removing the ETHHASH logic and SPV client and potentially replacing with MMR trees per epoch and checkpoints similar to Harmony Light Client on Ethereum.

Table of Contents

Ethereum 2.0 Specifications

Ethereum 2.0 Light Client Support

How light client implementation and verification of ETH and ETH2 can be done via smart contracts in other protocols.

For this we review three Key items

  1. Light Client Specifications including Extended light client protocol described by Altair Light Client – Sync Protocol and the The Portal Network Specification
  2. Near Rainbow Bridge Light Client Walkthrough include eth2near-block-relay-rs, nearbridge contracts and nearprover contracts
  3. Prysm light-client prototype

Note: Time on Ethereum 2.0 Proof of Stake is divided into slots and epochs. One slot is 12 seconds. One epoch is 6.4 minutes, consisting of 32 slots. One block can be created for each slot.

Light Client Specification

Altair Light Client – Sync Protocol

The Portal Network

Transaction Proofs

References

Near Rainbow Bridge Ethereum Light Client Walkthrough

The following is a walkthrough of how a transaction executed on Ethereum is propogated to NEAR’s eth2-client. See Cryptographic Primitives for more information on the cryptography used.

At a high level the ethereum light client contract

Ethereum to NEAR block propagation flow

Ethereum to NEAR block propagation components

Ethereum Light Client Finality Update Verify Components

finality-update-verify is called from fn verify_bls_signature_for_finality_update to verify signatures as part of light_client updates. It relies heavily on the lighthouse codebase for it’s consensus and cryptogrphic primitives. See Cryptographic Primitives for more information.

Cryptographic Primitives

Following are cryptographic primitives used in the eth2-client contract and finality-update-verify. Many are from the lighthouse codebase. Specifically consensus and crypto functions.

Some common primitives

Some Primitives from Lighthouse

Some Smart Contracts deployed on Ethereum

Some Primitives from NEAR Rainbow Bridge

Near Rainbow Bridge Near Light Client Walkthrough

The following is a walkthrough of how a transaction executed on NEAR is propogated to Ethereum’s nearbridge. See nearbridge Cryptographic Primitives for more information on the cryptography used.

NearOnEthClient Overview

The following is an excerpt from a blog by near on eth-near-rainbow-bridge

NearOnEthClient is an implementation of the NEAR light client in Solidity as an Ethereum contract. Unlike EthOnNearClient it does not need to verify every single NEAR header and can skip most of them as long as it verifies at least one header per NEAR epoch, which is about 43k blocks and lasts about half a day. As a result, NearOnEthClient can memorize hashes of all submitted NEAR headers in history, so if you are making a transfer from NEAR to Ethereum and it gets interrupted you don’t need to worry and you can resume it any time, even months later. Another useful property of the NEAR light client is that every NEAR header contains a root of the merkle tree computed from all headers before it. As a result, if you have one NEAR header you can efficiently verify any event that happened in any header before it.

Another useful property of the NEAR light client is that it only accepts final blocks, and final blocks cannot leave the canonical chain in NEAR. This means that NearOnEthClient does not need to worry about forks.

However, unfortunately, NEAR uses Ed25519 to sign messages of the validators who approve the blocks, and this signature is not available as an EVM precompile. It makes verification of all signatures of a single NEAR header prohibitively expensive. So technically, we cannot verify one NEAR header within one contract call to NearOnEthClient. Therefore we adopt the optimistic approach where NearOnEthClient verifies everything in the NEAR header except the signatures. Then anyone can challenge a signature in a submitted header within a 4-hour challenge window. The challenge requires verification of a single Ed25519 signature which would cost about 500k Ethereum gas (expensive, but possible). The user submitting the NEAR header would have to post a bond in Ethereum tokens, and a successful challenge would burn half of the bond and return the other half to the challenger. The bond should be large enough to pay for the gas even if the gas price increases exponentially during the 4 hours. For instance, a 20 ETH bond would cover gas price hikes up to 20000 Gwei. This optimistic approach requires having a watchdog service that monitors submitted NEAR headers and challenges any headers with invalid signatures. For added security, independent users can run several watchdog services.

Once EIP665 is accepted, Ethereum will have the Ed25519 signature available as an EVM precompile. This will make watchdog services and the 4-hour challenge window unnecessary.

At its bare minimum, Rainbow Bridge consists of EthOnNearClient and NearOnEthClient contracts, and three services: Eth2NearRelay, Near2EthRelay, and the Watchdog. We might argue that this already constitutes a bridge since we have established a cryptographic link between two blockchains, but practically speaking it requires a large portion of additional code to make application developers even consider using the Rainbow Bridge for their applications.

The following information on sending assets from NEAR back to Ethereum is an excerpt from https://near.org/bridge/.

Sending assets from NEAR back to Ethereum currently takes a maximum of sixteen hours (due to Ethereum finality times) and costs around $60 (due to ETH gas costs and at current ETH price). These costs and speeds will improve in the near future.

NEAR to Ethereum block propagation costing

The following links provide the production Ethereum addresses and blockexplorer views for NearBridge.sol and the ERC20 Locker

At time of writing (Oct 26th, 2022).

NEAR to Ethereum block propagation flow

NEAR Light Client Documentation gives an overview of how light clients work. At a high level the light client needs to fetch at least one block per epoch i.e. every 42,200 blocks or approxmiately 12 hours. Also Having the LightClientBlockView for block B is sufficient to be able to verify any statement about state or outcomes in any block in the ancestry of B (including B itself).

The current scripts and codebase indicates that a block would be fetched every 30 seconds with a max delay of 10 seconds. It feels that this would be expensive to update Ethereum so frequently. NEAR’s bridge documentation states Sending assets from NEAR back to Ethereum currently takes a maximum of sixteen hours (due to Ethereum finality times). This seems to align with sending light client updates once per NEAR epoch. The block fetch period is configurable in the relayer.

The RPC returns the LightClientBlock for the block as far into the future from the last known hash as possible for the light client to still accept it. Specifically, it either returns the last final block of the next epoch, or the last final known block. If there’s no newer final block than the one the light client knows about, the RPC returns an empty result.

A standalone light client would bootstrap by requesting next blocks until it receives an empty result, and then periodically request the next light client block.

A smart contract-based light client that enables a bridge to NEAR on a different blockchain naturally cannot request blocks itself. Instead external oracles query the next light client block from one of the full nodes, and submit it to the light client smart contract. The smart contract-based light client performs the same checks described above, so the oracle doesn’t need to be trusted.

Block Submitters stake ETH to be allowed to submit blocks which get’s slashed if the watchdog identifies blocks with invalid signatures.

Note: Have not identified how the block submitters are rewarded for submitting blocks. Currently have only identified them locking ETH to be able to submit blocks and being slashed if they submit blocks with invalid signatures.

NEAR to Ethereum watchdog

The watchdog runs every 10 seconds and validates blocks on NearBridge.sol challenging blocks with incorrect signatures. Note: It uses heep-prometheus for monitoring and storing block and producer information using gauges and counters.

NEAR to Ethereum block propagation components

NEAR Rainbow Bridge Utils

rainbow-bridge-utils provides a set of utilities for the near rainbow bridge written in javascript.

nearbridge Cryptographic Primitives

Token Transfer Process Flow

The NEAR Rainbow Bridge uses ERC-20 connectors which are developed in rainbow-token-connector and rainbow-bridge-client. Also see eth2near-fun-transfer.md.

Following is an overview of timing and anticipated costs

Note: This uses Ethreum ERC20 and NEAR NEP-141 initally developed for NEP-21

Generic ERC-20/NEP-141 connector for Rainbow Bridge

Specification

Ethereum’s side

contract ERC20Locker {
  constructor(bytes memory nearTokenFactory, INearProver prover) public;
  function lockToken(IERC20 token, uint256 amount, string memory accountId) public;
  function unlockToken(bytes memory proofData, uint64 proofBlockHeader) public;
}

NEAR’s side

struct BridgeTokenFactory {
    /// The account of the prover that we can use to prove
    pub prover_account: AccountId,
    /// Address of the Ethereum locker contract.
    pub locker_address: [u8; 20],
    /// Hashes of the events that were already used.
    pub used_events: UnorderedSet<Vec<u8>>,
    /// Mapping from Ethereum tokens to NEAR tokens.
    pub tokens: UnorderedMap<EvmAddress, AccountId>;
}

impl BridgeTokenFactory {
    /// Initializes the contract.
    /// `prover_account`: NEAR account of the Near Prover contract;
    /// `locker_address`: Ethereum address of the locker contract, in hex.
    #[init]
    pub fn new(prover_account: AccountId, locker_address: String) -> Self;

    /// Relays the lock event from Ethereum.
    /// Uses prover to validate that proof is correct and relies on a canonical Ethereum chain.
    /// Send `mint` action to the token that is specified in the proof.
    #[payable]
    pub fn deposit(&mut self, proof: Proof);
  
    /// A callback from BridgeToken contract deployed under this factory.
    /// Is called after tokens are burned there to create an receipt result `(amount, token_address, recipient_address)` for Ethereum to unlock the token.
    pub fn finish_withdraw(token_account: AccountId, amount: Balance, recipient: EvmAddress);
    
    /// Transfers given NEP-21 token from `predecessor_id` to factory to lock.
    /// On success, leaves a receipt result `(amount, token_address, recipient_address)`.
    #[payable]
    pub fn lock(&mut self, token: AccountId, amount: Balance, recipient: String);

    /// Relays the unlock event from Ethereum.
    /// Uses prover to validate that proof is correct and relies on a canonical Ethereum chain.
    /// Uses NEP-21 `transfer` action to move funds to `recipient` account.
    #[payable]
    pub fn unlock(&mut self, proof: Proof);

    /// Deploys BridgeToken contract for the given EVM address in hex code.
    /// The name of new NEP21 compatible contract will be <hex(evm_address)>.<current_id>.
    /// Expects ~35N attached to cover storage for BridgeToken.
    #[payable]
    pub fn deploy_bridge_token(address: String);

    /// Checks if Bridge Token has been successfully deployed with `deploy_bridge_token`.
    /// On success, returns the name of NEP21 contract associated with given address (<hex(evm_address)>.<current_id>).
    /// Otherwise, returns "token do not exists" error.
    pub fn get_bridge_token_account_id(&self, address: String) -> AccountId;
}

struct BridgeToken {
   controller: AccountId,
   token: Token, // uses https://github.com/ilblackdragon/balancer-near/tree/master/near-lib-rs
}

impl BridgeToken {
    /// Setup the Token contract with given factory/controller.
    pub fn new(controller: AccountId) -> Self;

    /// Mint tokens to given user. Only can be called by the controller.
    pub fn mint(&mut self, account_id: AccountId, amount: Balance);

    /// Withdraw tokens from this contract.
    /// Burns sender's tokens and calls controller to create event for relaying.
    pub fn withdraw(&mut self, amount: U128, recipient: String) -> Promise;
}

impl FungibleToken for BridgeToken {
   // see example https://github.com/ilblackdragon/balancer-near/blob/master/balancer-pool/src/lib.rs#L329
}

Setup new ERC-20 on NEAR

To setup token contract on NEAR side, anyone can call <bridge_token_factory>.deploy_bridge_token(<erc20>) where <erc20> is the address of the token. With this call must attach the amount of $NEAR to cover storage for (at least 30 $NEAR currently).

This will create <<hex(erc20)>.<bridge_token_factory>> NEP141-compatible contract.

Usage flow Ethereum -> NEAR

  1. User sends <erc20>.approve(<erc20locker>, <amount>) Ethereum transaction.
  2. User sends <erc20locker>.lock(<erc20>, <amount>, <destination>) Ethereum transaction. This transaction will create Locked event.
  3. Relayers will be sending Ethereum blocks to the EthClient on NEAR side.
  4. After sufficient number of confirmations on top of the mined Ethereum block that contain the lock transaction, user or relayer can call BridgeTokenFactory.deposit(proof). Proof is the extracted information from the event on Ethereum side.
  5. BridgeTokenFactory.deposit function will call EthProver and verify that proof is correct and relies on a block with sufficient number of confirmations.
  6. EthProver will return callback to BridgeTokenFactory confirming that proof is correct.
  7. BridgeTokenFactory will call <<hex(erc20)>.<bridge_token_factory>>.mint(<near_account_id>, <amount>).
  8. User can use <<hex(erc20)>.<bridge_token_factory>> token in other applications now on NEAR.

Usage flow NEAR -> Ethereum

  1. token-locker locks NEP141 tokens on NEAR side.

To deposit funds into the locker, call ft_transfer_call where msg contains Ethereum address the funds should arrive to. This will emit <token: String, amount: u128, recipient address: EthAddress> (which arrives to deposit on Ethereum side).

Accepts Unlock(token: String, sender_id: EthAddress, amount: u256, recipient: String) event from Ethereum side with a proof, verifies its correctness. If recipient contains ‘:’ will split it into <recipient, msg> and do ft_transfer_call(recipient, amount, None, msg). Otherwise will ft_transfer to recipient.

To get metadata of token to Ethereum, need to call log_metadata, which will create a result <token: String, name: String, symbol: String, decimals: u8, blockHeight: u64>.

  1. erc20-bridge-token - BridgeTokenFactory and BridgeToken Ethereum contracts.

BridgeTokenFactory creates new BridgeToken that correspond to specific token account id on NEAR side.

BridgeTokenFactory receives deposit with proof from NEAR, verify them and mint appropriate amounts on recipient addresses.

Calling withdraw will burn tokens of this user and will generate event <token: String, sender_id: EthAddress, amount: u256, recipient: String> that can be relayed to token-factory.

Caveats

Generally, this connector allows any account to call ft_transfer_call opening for potential malicious tokens to be bridged to Ethereum. The expectation here is that on Ethereum side, the token lists will handle this, as it’s the same attack model as malicious tokens on Uniswap and other DEXs.

Using Ethereum BridgeTokenFactory contract can always resolve Ethereum address of a contract back to NEAR one to check that it is indeed bridging token from NEAR and is created by this factory.

Testing

Testing Ethereum side

cd erc20-connector
yarn
yarn run test

Testing NEAR side

make res/bridge_token_factory.wasm
cargo test --all

Token Transfer Components

Note: This uses Ethreum ERC20 and NEAR NEP-141 initally developed for NEP-21

References

Prysm Light Client

References

Harmony Merkle Mount Range

Near Rainbow Bridge Review

The NEAR Rainbow bridge is in this github repository and is supported by Aurora-labs.

It recently provided support for ETH 2.0 in this Pull Request (762).

It interacts lighthouse for Ethereum 2.0 Consensus and tree_hash functions as well as bls signatures.

High Level their architecture is similar to the Horizon Bridge but with some key differences, including but not limited to

NEAR Rainbow Bridge: Component Overview

The following smart contracts are deployed on NEAR and work in conjunction with eth2near bridging functionality to propogate blocks from Ethereum to NEAR.

Note here we will focus on the eth2-client for ETH 2.0 Proof of Stake Bridging however if interested in however there is also an eth-client which was used for ETH 1.0 Proof of Work Integration using rust-ethhash.

The following smart contracts are deployed on Ethereum and used for propogating blocks from NEAR to Ethereum.