---
title: "zkCheck: proving code is safe without showing the code"
version: "Version 1.0"
date: "September 2026"
---

## Abstract

Smart-contract audits have one price, paid in advance: publish the source. The audit then vouches for what everyone can now read. This trades the developer's privacy for the holder's confidence, and for a large class of code, trading bots, sold strategies, launches that want no front-running before the reveal, plain proprietary work, the price is too high, and the code goes out unchecked instead.

zkCheck separates the two. A **seal** is a record against the hash of a contract's runtime bytecode, holding three things: the answers to a fixed, public list of machine-decidable checks, with evidence; a signed description of what the code does, written by an auditor who read it and never quotes it; and a stake the auditor locked behind the seal, taken if a claim proves the checks were wrong. The checks are designed to run inside a zero-knowledge virtual machine, so that the proof that they ran on this exact code can be verified by anyone, including a contract, without seeing the code. The source is read by one auditor and deleted.

The result is a security check whose output is public and whose input is not: eight answers, an opinion, a signature and money, and nothing else. This paper describes the design, the exact line between private and public, the economics that make the signature mean something, the limits that a holder must read alongside the seal, and the road from today's signed checks to proven ones and an on-chain registry.

## 1. The problem

### 1.1 The audit bargain

An audit is a document that says a reader looked at the code and found, or did not find, certain things. It has value in proportion to the reader's competence and honesty, which the reader asserts, and the document's binding to the code, which is usually a commit hash in the report. It costs weeks and tens of thousands of dollars, and it assumes the code will be published, since the audit of unpublished code is a private matter between two parties that no third party can use.

For open protocols this is right. Publishing is the point, and the audit is a public good attached to a public artifact.

For everything else it is a poor fit. The developer of a market-making bot, a yield strategy, a game with hidden mechanics, a launch whose edge is that nobody has seen the contract, faces a choice: publish and lose the edge, or stay unpublished and unverifiable. Most choose the second. The holders of those tokens have nothing: not a report, not a hash, not a name.

### 1.2 The verification bargain

Explorers offer a cheaper version of the same trade. Verify the source against the bytecode, and the explorer shows a green tick and the code. Everyone can read it, and anyone can run whatever analysis they like. The tick says one thing: this source produces this bytecode. It does not say what the code does.

Automated scanners do say something. They run heuristics over the verified source and print risk flags. They inherit the same price (the source must be public) and add a new problem: the heuristics are the scanner's, changing without notice, unexplained, and unaccountable. A flag is a flag; nobody stands behind it.

### 1.3 What a holder actually needs

A holder about to buy needs the answers to a short list of questions, decided the same way for every token, with a reason attached to each answer, from someone who loses something if the answers are wrong. They do not need to read the code, and most cannot. They need the list to be public so they know what was checked, the answers to be about *this* bytecode and no other, and the someone to be identifiable.

None of that requires the source to be public. It requires the source to have been read, once, by a party the holder can hold to account.

## 2. The seal

### 2.1 Definition

A seal is a record keyed by `keccak256(runtimeBytecode)`. It contains:

- the **rule set** identifier (today: rule set one);
- for each check in the rule set, an **answer** in {pass, fail, not run} and one sentence of **evidence**;
- a **review**: text describing the contract, signed by the auditor, or the reason there is none;
- the **auditor**'s address and the amount **staked** behind this seal;
- a **signature** by the auditor over a canonical digest of the hash, the answers and the review;
- a **provenance** flag: whether the answers were *signed* by the auditor or *proven* by a zkVM receipt;
- the **commit** hash the source was read at, the **contract** name and **compiler** version;
- the deployment **address** it was matched against, if any.

The repository, the source, the artifacts and the submitter's identity are not in the seal.

### 2.2 Why bytecode

Keying on bytecode rather than on an address makes the seal a property of code, not of deployments. Every contract with that runtime, on this chain or another, wears the same seal. A single byte of difference is a different hash and no seal. This is what makes the mark meaningful: it cannot be inherited by a fork, and it cannot be lost by redeploying the same thing.

The cost is that the seal is blind to state. It cannot see the owner of one deployment, the constructor arguments, the liquidity, or the supply distribution. Section 7 lists these limits in full. They are the price of a seal that follows code, and the design prefers that price to a seal that follows addresses and can be gamed by deploying twice.

### 2.3 Runtime, metadata, immutables

The seal hashes the runtime bytecode as `eth_getCode` returns it: the full bytes, including the compiler's trailing CBOR metadata. That is the hash anyone will compute, and the seal must be found by it.

Matching the auditor's build to the chain is a different operation. Two honest builds of the same source can differ in the metadata block (it hashes source paths) and in immutable slots (the artifact has zeros, the chain has constructor values). The match sets both aside on both sides and requires the rest to be identical. Everything else that changes bytecode, the compiler version, the optimizer, `via_ir`, linked library addresses, must match exactly, because it changes what runs.

## 3. Rule set one

### 3.1 Design rules

A check is admitted to a rule set if and only if it is **decidable by a program, the same way every time, on the compiled artifact and the chain**, and produces **one sentence of evidence** naming what decided it. This excludes everything that requires judgement, and it excludes most of what an audit report contains. That is deliberate: a short list that is proven is worth more than a long list that is promised, and everything excluded has a place in the review.

When a check cannot decide, it fails and says why. The asymmetry is chosen: a false fail costs a developer a fix and a resubmission; a false pass costs holders money.

### 3.2 The checks

The checks read the compiler's abstract syntax tree, resolving every reference by declaration id so that names never collide, and build a call graph from every externally callable function. "Reachable" below means reachable through that graph from an `external` or `public` function of the sealed contract or its bases, constructor excluded.

**01 · No hidden mint.** A sink is a function that calls `_mint`, or that increases a state variable named like `totalSupply` (the standard `_update`, `_mint` and `_burn` are exempt as sinks, since `_update` is on every transfer path in OpenZeppelin's design). Fail if any sink is reachable. Owner-only does not matter: the check asks what the code can do, not who is allowed.

**02 · No owner drain.** In any reachable function outside the standard ERC-20 set, a call to `_transfer`, `_update`, `_burn` or `transferFrom` whose first argument is not the caller, without an allowance being spent in the same function, or any direct write to a balances mapping. Fail if found.

**03 · Tax under the cap.** Tax variables are unsigned state variables named with `tax` or `fee` (address-like names excluded). Setters are reachable functions that assign to them. Each setter must contain a comparison against a number literal; the largest such literal, divided by the largest denominator literal in the contract (100, 1 000, 10 000 or 100 000), must be at or under 0.10. No tax variable, or no setter, passes.

**04 · No blacklist.** A `mapping(address => bool)` read in a reverting position (inside `require`, or an `if` whose body reverts) anywhere in the transfer path, with a reachable setter. Name never enters the decision; a fee-exemption mapping read only to pick a branch passes.

**05 · No pause on transfer.** A `bool` read in a reverting position in the transfer path. The check determines which value blocks (by counting negations between the read and the guard) and passes only if every reachable assignment writes the literal that does not block. `tradingOpen = true` passes; `paused = v` fails.

**06 · Not upgradeable.** The runtime opcodes, walked instruction by instruction with `PUSH` data skipped and the metadata tail removed, contain no `DELEGATECALL`.

**07 · Sell path clears.** On a fork of the chain at the current block, the largest wallet holder (found from `Transfer` logs, contracts excluded) sends one thousandth of its balance to a fresh address, and the fresh address sends it back. Both must succeed. Not run when the contract is not deployed or not held. It is a transfer, not a pool swap, and the evidence says so.

**08 · No self-destruct.** Same scan as 06; no `SELFDESTRUCT`.

### 3.3 Evidence

Every answer carries a sentence. A fail names the function and the path: `Supply can grow after deployment: mint → _mint.` A pass names what was seen: `Minting happens in the constructor only.` The evidence is what lets a developer fix, a holder understand, and a claim (section 5) argue. A pass reveals nothing about the code that the fact of passing does not; a fail reveals a function name, which is the price of the fail being useful.

### 3.4 Versioning

A rule set is frozen at publication. Rule set two will add checks; it will not change the meaning of any check in rule set one. Every seal records its rule set, so an old seal's eight answers mean tomorrow what they mean today.

## 4. The proof

### 4.1 Signed and proven

There are two ways to believe an answer. **Signed**: the auditor ran the check and signed the result; the trust is in the key and the stake behind it. **Proven**: the check ran inside a zkVM that produced a receipt; the receipt is a mathematical statement that this program, with this hash, ran on an input with that hash and produced this output; the trust is in arithmetic.

Every seal says which it is. The two are never presented as the same thing, on the site, in the API or on a badge. A launchpad that wants to gate on proven seals can, and one that accepts signed ones knows what it accepts.

### 4.2 What the zkVM proves

The program is the checks themselves: parse the artifact, walk the tree, scan the opcodes, decide. The receipt commits to the program (so it cannot be replaced by one that always passes), the rule set (which is the program's identity), and the bytecode hash (so answers cannot be reused for other code). The receipt does not contain the input. The checks ran on something with this hash, and the something stays private.

### 4.3 What it does not, yet

The compile-and-match step, which ties the source to the chain, runs outside the proof today; moving the compiler inside a zkVM is feasible and expensive, and is on the roadmap after the checks themselves. Check 07 is a full EVM execution against live state and stays signed until a zkEVM makes it affordable. The review is a language model's output and cannot be proven in any meaningful sense; it is an opinion and is labelled one.

### 4.4 Why layer proof over stake

A stake is a promise with a price. A proof is a fact. A stake can be smaller than the damage; the vote that takes it can be lazy or captured. A proof cannot be wrong about what it proves. The design uses proof where the question is mechanical and stake where it is not, and it uses proof also because proof travels: a contract on another chain can verify a receipt; it can only ask whom to trust about a signature.

## 5. The review, the auditor, the stake, the claims

### 5.1 The review

The checks catch shapes. A reader catches intent, unusual mechanisms, and the thing nobody wrote a check for. The review is that reader: a language model reads the project's own source (dependencies, tests and scripts excluded) with the eight answers, and writes under 350 words describing what the contract does, who holds which powers, what a holder is exposed to, what looks off, ending with one sentence beginning "Bottom line". The auditor signs it.

The review never quotes code. The review is public and the source is not; a quoted function is a leaked function. The rule is absolute and the stake is its enforcement.

The review is an opinion. It is stored apart from the checks, hashed on its own line in the signed digest, and labelled everywhere. A seal without a review (source too large, model declined, no key) is a full seal with the reason in its place.

### 5.2 The auditor

An auditor is a key with tokens locked behind it. It signs seals; its address goes on each one with the amount bound to it. The stake is what turns a signature into a promise. An auditor cannot sign more seals than its stake covers, so reach is bounded by exposure; an auditor whose seals keep failing runs out of stake and stops being able to sign, without a committee.

The auditor sees everything: the source, the repository, the commit. That is the model. Privacy in a zk review is privacy from the public, not from the one who checks. The auditor keeps the repository name and commit so it can later prove what it read, deletes the tree as soon as the seal is signed, and publishes nothing that identifies the repository.

Any key that stakes can be an auditor. At launch there is one, run by zkCheck, because the press must exist before anyone else runs one. That is the weakest configuration the design allows, and the site says so. Two independent auditors sealing the same hash is a stronger seal than either, and the registry will show both.

### 5.3 Claims

A claim is a challenge: this sealed contract was exploited, through something a check covers, at the sealed commit, and here is the transaction. It is opened with a bond. It is decided by a vote of stakers excluding the auditor in question. The auditor can be compelled by the vote to disclose the source at the sealed commit to the voters; the alternative is losing by default.

If it passes: the auditor's stake behind that seal is taken, a share of the cover pool is added, the total goes to the holders the exploit hit in proportion to their loss, and the seal is marked failed, not deleted, so that a lookup finds a failed seal rather than nothing. If it fails: the bond goes to the cover pool.

A claim is about the checks. A sealed token that passed eight of eight and was exploited through something none of them cover is not a failed seal; it is a limit of the list, and the list is public. This line keeps auditors willing to sign, and it is why the list must grow.

## 6. The privacy line

The claim of the design is that a security check need not cost privacy. That claim is only as good as the line's precision.

**Stays private.** The source, read by one auditor from a read-only GitHub grant to one repository, held on the worker's disk during the review, deleted after, never returned by any endpoint, never quoted. The repository's name and history; the API never returns the name, and the commit hash identifies nothing without it. The submitter; there is no account, the GitHub identity dies with a one-hour cookie, and the fee-paying wallet, when the registry exists, can be fresh. The compiled artifacts, deleted with the tree.

**Becomes public.** The code hash, which anyone can compute from the chain. The deployment address, already on the chain. Eight answers with evidence; evidence names functions, which is less than code and is the cost of a fail being useful. The review, which describes and does not quote. Contract name and compiler version, both already in the bytecode's metadata. The commit hash. The auditor, the signature, the stake. The submitted name and symbol.

**The grant.** The GitHub App asks for `Contents: read-only` and the mandatory `Metadata: read-only`, nothing else. The developer installs it on chosen repositories and can revoke it at any time. The worker uses the resulting token for two calls: resolve the ref to a commit, download the tree at that commit as a tarball with submodules. The token is never written to disk.

## 7. Limits

A seal is a floor. This section is the ceiling, and it is part of the product: the site links it from every seal.

The checks do not look at reentrancy in arbitrary calls, arithmetic outside supply, oracle manipulation, front-running, flash-loan surfaces, signature replay, or access control on functions outside their scope. A contract can pass eight of eight and be exploitable in a way none of them cover.

The seal cannot see state: the owner of this deployment, the constructor arguments, the liquidity, the supply distribution, the current value of any variable. Two deployments of the same sealed code, one honest and one not, wear the same seal.

The seal cannot see intent. The review is one model's reading, once, of the project's own files, which excludes dependencies; it can miss things and be wrong, and it is signed so that being wrong costs and labelled so that nobody mistakes it for proof.

Check 07 is a transfer, not a swap. The vote can be lazy or captured. The stakes and the pool are the size they are. The seal's name and symbol are what the submitter typed.

The right reading of a seal is: these eight questions had these answers, this auditor said this and put this much behind it, and here is what none of that covers. That is more than a holder has today, and it is not everything.

## 8. Economics

### 8.1 The token's one job

The platform token exists to be the thing an auditor can lose. It is the denomination of stakes and of the cover pool. It has no other role in the mechanism, and the design resists giving it one.

### 8.2 The fee

A seal costs a fee, paid in the token, set in the registry at deployment and changeable only with a public delay. It is split:

| Share | To | For |
|---|---|---|
| 50% | The auditor | Running the press, reading the source, signing, bearing the stake's risk. |
| 25% | The cover pool | Paying claims beyond the auditor's stake. |
| 15% | Buyback and burn | Bought on the market, burned. |
| 10% | The platform | Operating the registry and the site. |

Nothing is minted. The token has a fixed supply, the fee is the only flow into the system, and the burn is the only flow out.

### 8.3 Why an auditor stakes

Because the fee pays for it. An auditor's expected income is the fee share times seals signed; its expected loss is the stake times the rate at which its seals fail claims. An honest auditor with a working press has a failure rate near zero and a positive expectation. A careless one does not, and the stake removes it. Multiple auditors compete on stake size (a bigger stake is a stronger seal, and a launchpad can prefer it) and on fee, which the registry lets each auditor set above the floor.

### 8.4 Launch

The token launches on Pons, on Robinhood Chain, at a fixed supply, with no allocation to the platform beyond what the fee split earns over time. The registry's treasury address is public. The first auditor's stake is posted from that treasury, publicly, so that the launch configuration is inspectable.

## 9. The road

Each step is a change to what a seal can say, and each is marked on the seal.

1. **Today.** Signed checks, one auditor, seals in the auditor's database, the site reading from it. Eight checks, all documented, deterministic, reproducible by anyone who runs the worker on the same input.
2. **Registry.** A contract on Robinhood Chain holding seals by code hash, auditors by address with stakes, fees with the split above, claims with bonds and votes. `sealOf(codehash)` as a view any contract can call. The worker writes to it; the site reads from it.
3. **Proven checks.** Checks 01 to 06 and 08 inside a general-purpose zkVM, the receipt on the seal, a verifier contract that requires it before the registry accepts `proven: zkvm`. The auditor still runs the press; the public no longer has to trust that it did.
4. **More auditors.** The worker as a Docker image anyone can run with their own key and stake; the registry showing every seal on a hash; badges preferring multiply-sealed code.
5. **Rule set two.** Added checks, published first, then in the registry, each with its documented decision procedure. Candidates: `Transfer(address(0), …)` emission paths as a stronger mint check; state-diff on a fork as a stronger drain check; pool-swap simulation for 07; time-locked switches.
6. **Proven match.** The compile-and-match step inside the zkVM, so the link from source to chain is a fact and not a signature.
7. **Local proving.** The developer runs the press locally and submits only the receipt; the auditor never sees the source. This is the end of the road and the beginning of a different product, and it is only possible once every step before it exists.

## 10. Conclusion

A holder who reads a seal today knows: these eight questions had these answers on this exact bytecode, here is why for each, here is what an auditor said after reading the code, here is who the auditor is and what they signed. Tomorrow they will know the answers were proven and the auditor's money is behind them. At no point will they, or anyone else, have seen the code.

That is a smaller promise than an audit makes and a larger one than an unaudited token offers, and it is the promise that fits the code that would otherwise go unchecked. The list of what a seal cannot see is published beside every seal, because a floor is only useful if everyone knows it is a floor.

---

*zkCheck runs on Robinhood Chain. The worker and the checks are published with the registry release; this document is at zkcheck.dev/whitepaper.md. Paintings on the site: The Met Open Access.*
