Smart Contracts

Token Registry - Proof-carrying Rollup

The point of RollupNC is to facilitate off-chain transfers of arbitrary ERC20 or the EVM gas token. On deployment, the gas token is available. Using the token registry, anyone can request a token be added to the L2 network and the permissioned sequencer can approve a token for deposit and transfer on the L2 network.

The Rollup contract is a point of contact that is only used for entry and exit. Depositing into the L2 contract requires no zero knowledge proof, however it does make use of a temporary merkle tree in the contract to batch in groups of 2^n deposits onto L2. The rollup operator confirms deposits are batched in using a zero knowledge proof. The same proof is used to commit L2 state transfers on-chain.

Once a depositor has made an L2 withdrawal transaction that has been committed to L1 in the state root, depositors can withdraw the exact amount of tokens in the L2 withdrawal transaction on L1.

Since EdDSA keys are much more efficient to use in zero knowledge than the ECDSA keys used on L1, this code base a mechanism by which pubkeys of the two cryptosystems can be associated with each-other in a verifiable manner.

Note: we describe this code base with the Poseidon hash function - the original version uses MiMC instead. The outcome is almost identical (Poseidon is actually inferior in this case) but do not be alarmed or confused by the switching of the two as the only consequence is efficiency.

These mechanics are explained in more detail in the following sections.

Deploying Poseidon Contracts

Using circomlibjs, you can generate a Poseidon hashing contract specific to the number of inputs you need. This is limited to 6 inputs, meaning you mush do multiple rounds of Poseidon hashing to hash larger quantities of inputs together.

Deployment (seen in BattleZips' RollupNC updated repo) is as follows, using hardhat-deploy:

const { ethers } = require('hardhat');

/**
 * Deploy All Contracts
 */
module.exports = async ({ run, ethers, network, deployments }) => {

    // deploy Poseidon hasher for 2 inputs
    const poseidonT3ABI = poseidonContract.generateABI(2);
    const poseidonT3Bytecode = poseidonContract.createCode(2);
    const poseidonT3Factory = new ethers.ContractFactory(poseidonT3ABI, poseidonT3Bytecode, operator);
    const poseidonT3 = await poseidonT3Factory.deploy();
    await poseidonT3.deployed();
    
    // deploy Poseidon hasher for 5 inputs
    const poseidonT6ABI = poseidonContract.generateABI(5);
    const poseidonT6Bytecode = poseidonContract.createCode(5);
    const poseidonT6Factory = new ethers.ContractFactory(poseidonT6ABI, poseidonT6Bytecode, operator);
    const poseidonT6 = await poseidonT6Factory.deploy();
    await poseidonT6.deployed();

    .
    .
    .
    
    // link Poseidon contracts in to RollupNC contract
    const { address: rollupAddress } = await deployments.deploy('RollupNC', {
        .
        .
        .
        libraries: {
            PoseidonT3: poseidonT3.address,
            PoseidonT6: poseidonT6.address
        }
    })
})

From here, you can easily perform Poseidon hashes in Solidity as shown below (and in the BattleZips RollupNC repo [library])

pragma solidity 0.8.15;
import "./libraries/Poseidon.sol";

unction deposit(
    uint256[2] memory pubkey,
    uint256 amount,
    uint256 tokenType
) public payable {
    .
    .
    .
    
    // generate a deposit leaf (account leaf)
    uint256 depositHash = PoseidonT6.poseidon(
        [pubkey[0], pubkey[1], amount, uint256(0), tokenType]
    );
    
    .
    .
    .
}

Last updated