zerocompress

0.2.0-beta-2 • Public • Published

zerocompress CircleCI

Compress Ethereum calldata then decompress on chain. For use in L2 applications.

Strategy

Calldata in Ethereum is priced at 4 gas per zero byte, and 16 gas per non-zero byte. As a result data can only be efficiently compressed if either:

  1. Zeroes are compressed to non-zeros with more than 75% efficiency
  2. Zeroes are not replaced with non-zeroes during compression

Single bit

Data is compressed by looking at each byte and storing a single bit indicating whether the byte is zero or non-zero. If the byte is non-zero it is added to a uniques array of bytes.

Each sequence of 8 consecutive zero bytes is compressed to a single zero byte.

Opcodes

A zero value in the uniques array indicates a special operation. The byte following a zero byte in the uniques array specifies an opcode for inflation. Any number of following bytes may be used as an argument for the opcode.

  • [x] 0x00() - zero insertion, insert a fixed number of zeroes specified by a number at register 1 (this number may be 0) (use this to make the total data length shorter to avoid padding)
  • [x] 0x01-0xE0 - Fixed length 0 insertion
  • [x] 0xE1-0xF1 - Fixed length 0xFF insertion
  • [x] 0xF2-0xF6 (uint40 id) - address insertion
  • [x] 0xF7-0xFB (uint40 id) - bls pubkey insertion (uint[4])
  • [ ] tbd(uint8 length) - insert a repeat string of bytes specified at register 2
  • [x] 0xFC-0xFF - for external use

Registers are bytes at the end of the data.

Use

npm i zerocompress

Contract

Implement a contract inheriting from the Decompressor contract.

import "zerocompress/contracts/Decompressor.sol";

contract Example is Decompressor {
  function testFunc(uint a, uint b) public {
    require(a != b);
  }
}

JS

Encode the data for the target function call using Ethers or Web3. Then compress the data using compressSingle or compressDouble. These functions will return a function to call and data to pass.

const { ethers } = require('ethers')
const { ExampleABI } = require('Example.sol.json')
const { compress } = require('zerocompress')

// deploy your contract that inherits Decompressor
const example = new ethers.Contract(
  '0xabcabcabc0abcabcabc0abcabcabc0abcabcabc0',
  ExampleABI
)

// Encode the function call
const calldata = example.interface.encodeFunctionData('testFunc', [ 20, 40 ])
// Compress the data and get a function to call and data to pass
const [ func, data ] = compress(calldata)
// Call the function with the data
const tx = await example[func](data)
// Wait for the transaction to complete
await tx.wait()

compress API

compress(calldata, options) returns [func, data]

  • calldata: A bytes string optionally prefixed with 0x. Should be an 8 byte function selector followed by abi encoded arguments.
  • options: An optional object specifying address and bls public key replacements.
    • addressSubs: An optional object mapping addresses to integers. Example: { '0xabcabcabc0abcabcabc0abcabcabc0abcabcabc0': 10029 }
    • blsPubkeySubs: An array of arrays mapping bls public keys to integers. Each item should be an array of length 2 containing the bls key (as an array of hex strings) as the first element, and an integer as the second element. Example:
[
  [ // the first replacement element
    [
      '0x02387ea12d645f4a3af6f974de1732c5d2a4469ddd14b74068f3f9ab71a3adf2',
      '0x2fb47daa10b6066a152832ce5275297424ef3b778f9853510cea6a79ab07bf42',
      '0x26d1389521121b21d7f6ee981f35ff627944b3a6877ecdc194b7b9ed6c1ec112',
      '0x0f4c29d245098298838b2e66de53d1d10482bddada6b4fc0d4b5b814fa2d5b16'
    ],
    41294
  ],
  [ // the second replacement element
    [
      '0x02186699ca6e1174566611699dd295af60434d60b0478a621e933789cf6c9574',
      '0x2f43e276298f9ecfc4c3925155fe852b94cfd191410831b051663fabf99ec2ce',
      '0x11bf07ff3b44f6f4ec24effc81be892097e591af6591481c6d84a99d0ece2f17',
      '0x28a14cf5fb0e62f55cba5048c3c6cfe53ad8eda449e7528abf30484eb7efdc64'
    ],
    20939
  ]
]
  • func: (return value) A fully formed function selector to call on the target contract. Example: decompress(bytes32[2])
  • data: (return value) Data to pass to the returned function.

Address Registry

To use address substitution each address must be registered with the Decompress contract. Registration will return an integer that can be substituted in compressed data.

const { decompressAddress, AddressRegistryABI } = require('zerocompress')
{
  const registry = new ethers.Contract(decompressAddress, AddressRegistryABI)
  await registry.bindAddress(myAddress).then(t => t.wait())
  const id = await registry.idByAddress(myAddress)
  // id is the address that can be used for compression now
  // e.g. compress(calldata, { addressSubs: { [myAddress]: id }})
}

BLS Public Key Registry

To use BLS public key substitution each public key must be registered with the Decompress contract before use. Registration will return an integer that can be substituted in compressed data.

const { decompressAddress, BLSPubkeyRegistryABI } = require('zerocompress')
{
  const registry = new ethers.Contract(decompressAddress, BLSPubkeyRegistryABI)
  await registry.bindPubkey(myPubkey).then(t => t.wait())
  const id = await registry.idByPubkey(myPubkey)
  // id is the pubkey that can be used for compression now
  // e.g. compress(calldata, { blsPubkeySubs: [ [ myPubkey, id ] ]})
}

Addresses

The Decompress contract (implementing the AddressRegistry and BLSPubkeyRegistry) is available on the following networks:

Package Sidebar

Install

npm i zerocompress

Weekly Downloads

1

Version

0.2.0-beta-2

License

GPL-3.0-or-later

Unpacked Size

2.88 MB

Total Files

25

Last publish

Collaborators

  • jchancehud