{"id":26825124,"url":"https://github.com/chancehudson/zerocompress","last_synced_at":"2025-10-08T19:38:40.727Z","repository":{"id":43157765,"uuid":"458974002","full_name":"chancehudson/zerocompress","owner":"chancehudson","description":null,"archived":false,"fork":false,"pushed_at":"2022-04-14T06:16:32.000Z","size":1267,"stargazers_count":4,"open_issues_count":3,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-28T14:05:55.559Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/chancehudson.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-02-14T01:04:08.000Z","updated_at":"2022-04-20T08:50:07.000Z","dependencies_parsed_at":"2022-09-22T07:11:51.996Z","dependency_job_id":null,"html_url":"https://github.com/chancehudson/zerocompress","commit_stats":null,"previous_names":["chancehudson/zerocompress"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/chancehudson/zerocompress","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chancehudson%2Fzerocompress","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chancehudson%2Fzerocompress/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chancehudson%2Fzerocompress/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chancehudson%2Fzerocompress/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chancehudson","download_url":"https://codeload.github.com/chancehudson/zerocompress/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chancehudson%2Fzerocompress/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279000703,"owners_count":26082805,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-08T02:00:06.501Z","response_time":56,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-03-30T10:19:18.347Z","updated_at":"2025-10-08T19:38:40.696Z","avatar_url":"https://github.com/chancehudson.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# zerocompress [![CircleCI](https://img.shields.io/circleci/build/github/vimwitch/zerocompress)](https://app.circleci.com/pipelines/github/vimwitch/zerocompress?branch=main\u0026filter=all)\n\nCompress Ethereum calldata then decompress on chain. For use in L2 applications.\n\n## Strategy\n\nCalldata 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:\n\n1. Zeroes are compressed to non-zeros with more than 75% efficiency\n2. Zeroes are not replaced with non-zeroes during compression\n\n### Zerocompress\n\nThere are 3 main structures in zerocompressed data:\n\n1. A config section specifying default values and lengths of the following sections\n2. A bits section storing a 1 for a non-zero byte and 0 for a zero byte\n3. A section of non-zero bytes\n\nThis algorithm stores a 0 bit for each zero byte and a 1 bit for each non-zero byte in section 2. The non-zero byte is added to section 3 to be inserted during inflation. Inflation is done by iterating over each bit in section 2 and inserting either a zero byte or pulling and inserting the next non-zero byte from section 3.\n\nUsing this strategy there should never be a 1 bit pointing to a 0 byte. This fact is used to implement opcodes for decompression, which can be seen below.\n\n### Opcodes\n\nA zero value in section 3 indicates a special operation. The byte following a zero byte specifies the opcode for inflation. Any number of trailing bytes may be used as argument(s) for the opcode.\n\n- [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)\n- [x] `0x01-0xE0` - Fixed length 0 insertion\n- [x] `0xE1-0xF1` - Fixed length `0xFF` insertion\n- [x] `0xF2-0xF6 (uint40 id)` - address insertion\n- [x] `0xF7-0xFB (uint40 id)` - bls pubkey insertion (uint[4])\n- [ ] `tbd(uint8 length)` - insert a repeat string of bytes specified at register 2\n- [x] `0xFC-0xFF` - for external use\n\nRegisters are bytes at the end of the data.\n\n## Use\n\n`npm i zerocompress`\n\n### Contract\n\nImplement a contract inheriting from the `Decompressor` contract.\n\n```js\nimport \"zerocompress/contracts/Decompressor.sol\";\n\ncontract Example is Decompressor {\n  function testFunc(uint a, uint b) public {\n    require(a != b);\n  }\n}\n```\n\n### JS\n\nEncode 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.\n\n```js\nconst { ethers } = require('ethers')\nconst { ExampleABI } = require('Example.sol.json')\nconst { compress } = require('zerocompress')\n\n// deploy your contract that inherits Decompressor\nconst example = new ethers.Contract(\n  '0xabcabcabc0abcabcabc0abcabcabc0abcabcabc0',\n  ExampleABI\n)\n\n// Encode the function call\nconst calldata = example.interface.encodeFunctionData('testFunc', [ 20, 40 ])\n// Compress the data and get a function to call and data to pass\nconst [ func, data ] = compress(calldata)\n// Call the function with the data\nconst tx = await example[func](data)\n// Wait for the transaction to complete\nawait tx.wait()\n```\n\n### `compress` API\n\n`compress(calldata, options) returns [func, data]`\n\n- `calldata`: A bytes string optionally prefixed with `0x`. Should be an 8 byte function selector followed by abi encoded arguments.\n- `options`: An optional object specifying address and bls public key replacements.\n  - `addressSubs`: An optional object mapping addresses to integers. Example: `{ '0xabcabcabc0abcabcabc0abcabcabc0abcabcabc0': 10029 }`\n  - `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:\n\n```\n[\n  [ // the first replacement element\n    [\n      '0x02387ea12d645f4a3af6f974de1732c5d2a4469ddd14b74068f3f9ab71a3adf2',\n      '0x2fb47daa10b6066a152832ce5275297424ef3b778f9853510cea6a79ab07bf42',\n      '0x26d1389521121b21d7f6ee981f35ff627944b3a6877ecdc194b7b9ed6c1ec112',\n      '0x0f4c29d245098298838b2e66de53d1d10482bddada6b4fc0d4b5b814fa2d5b16'\n    ],\n    41294\n  ],\n  [ // the second replacement element\n    [\n      '0x02186699ca6e1174566611699dd295af60434d60b0478a621e933789cf6c9574',\n      '0x2f43e276298f9ecfc4c3925155fe852b94cfd191410831b051663fabf99ec2ce',\n      '0x11bf07ff3b44f6f4ec24effc81be892097e591af6591481c6d84a99d0ece2f17',\n      '0x28a14cf5fb0e62f55cba5048c3c6cfe53ad8eda449e7528abf30484eb7efdc64'\n    ],\n    20939\n  ]\n]\n```\n  - `func`: (return value) A fully formed function selector to call on the target contract. Example: `decompress(bytes32[2])`\n  - `data`: (return value) Data to pass to the returned function.\n\n### Address Registry\n\nTo use address substitution each address must be registered with the `Decompress` contract. Registration will return an integer that can be substituted in compressed data.\n\n```\nconst { decompressAddress, AddressRegistryABI } = require('zerocompress')\n{\n  const registry = new ethers.Contract(decompressAddress, AddressRegistryABI)\n  await registry.bindAddress(myAddress).then(t =\u003e t.wait())\n  const id = await registry.idByAddress(myAddress)\n  // id is the address that can be used for compression now\n  // e.g. compress(calldata, { addressSubs: { [myAddress]: id }})\n}\n```\n\n### BLS Public Key Registry\n\nTo 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.\n\n```\nconst { decompressAddress, BLSPubkeyRegistryABI } = require('zerocompress')\n{\n  const registry = new ethers.Contract(decompressAddress, BLSPubkeyRegistryABI)\n  await registry.bindPubkey(myPubkey).then(t =\u003e t.wait())\n  const id = await registry.idByPubkey(myPubkey)\n  // id is the pubkey that can be used for compression now\n  // e.g. compress(calldata, { blsPubkeySubs: [ [ myPubkey, id ] ]})\n}\n```\n\n### Addresses\n\nThe `Decompress` contract (implementing the `AddressRegistry` and `BLSPubkeyRegistry`) is available on the following networks:\n\n- Optimism Kovan: [0x75b6fA14947E6B524ECEf46a6A4A3c1D0E0b62dF](https://kovan-optimistic.etherscan.io/address/0x75b6fA14947E6B524ECEf46a6A4A3c1D0E0b62dF)\n- Arbitrum Rinkeby: [0x75b6fA14947E6B524ECEf46a6A4A3c1D0E0b62dF](https://testnet.arbiscan.io/address/0x75b6fA14947E6B524ECEf46a6A4A3c1D0E0b62dF)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchancehudson%2Fzerocompress","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchancehudson%2Fzerocompress","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchancehudson%2Fzerocompress/lists"}