Cloak contracts: security review
This is a shortened, redacted version of a real Proovant report. One finding is shown in full. The others are listed with their severity and outcome, and their detail is blacked out. Everything shown here is already public in Cloak's own review log or comes from our own test runs.
1. Summary
Twelve findings: nine in the contracts planned for mainnet and three in testnet-only code. Each was reproduced in a Foundry test before it was reported. None lets anyone take bidder deposits or private balances. The most serious allow a bidder or creator to gain an unfair result in a drop or launch, or to block a feature for others.
| ID | Finding | Severity | Outcome |
|---|---|---|---|
| CL-01 | A bidder with several sealed bids could steer the NFT drop tie lottery by choosing which bids to open | Medium | Fixed |
| CL-02 | In an undersubscribed launch, a dust bid at the minimum price sets the price for everyone | Medium | By design |
| CL-03 | One well-funded actor can fill a launch's deposit cap | Medium | Accepted |
| CL-04 | A creator can inflate their own drop with bids they keep sealed | Medium-Low | Mitigated |
| CL-05 | Anyone could open a per-token vault for a Uniswap launch before it was admitted to the shared vault, locking it out | Low-Medium | Fixed |
| CL-06 | One very large batch order made the whole round refund, less the vault fee | Low-Medium | Fixed |
| CL-07 | A whitelist spot sale's naming deadline could be one second after bidding closed | Low-Medium | Fixed |
| CL-08 | The private router refused native currency paid out by a Uniswap v4 market | Low | Fixed |
| CL-09 | A note owner could withdraw as a relayer fee to themselves and skip the withdrawal fee | Low | Fixed |
| A-1 | A large donation pushes a launch onto a different market path (testnet-only code) | Low | Documented |
| A-2 | A collection returning a malformed ownership value could block escrow claims (testnet-only code) | Low-Medium | Fixed |
| A-3 | The escrow drop's draw had the same weakness as CL-01 (testnet-only code) | With CL-01 | Fixed |
Severity is our rating. Outcomes are as published by the Cloak team: 8 fixed, 1 mitigated, 3 left as they were (by design, accepted, or documented).
2. Scope and method
- In scope: the Solidity contracts in the repository's two contract folders at the reviewed commit.
- Not covered: the zero-knowledge circuit, the SDK, the keeper and relayer, and the website.
- Method: scope mapping and threat model, parallel specialist analysis, an independent stage that tries to disprove every lead, a Foundry proof of concept for each survivor, and a human review of every finding against the team's latest commits and documentation.
- Testing: local chain. One test, for A-1, uses a testnet fork and needs network access.
3. Finding CL-01, in full
A bidder with several sealed bids could steer the tie lottery by choosing which bids to open
What happens. When a drop is oversubscribed at the clearing price, a lottery decides which of the tied bids win. The lottery seed was derived only from the salts of the bids that were opened. A bidder who holds several sealed bids therefore controls part of the seed: by choosing which of their own bids to open, they choose between many possible draws.
Who can do it. Any bidder with more than one sealed bid. It needs no special role. The cost is the penalty on each bid left sealed.
Exploit scenario. The bidder places several small sealed bids. After bidding closes they compute, off-chain, the lottery result for every combination of their bids, pick the combination that wins the most, and open only those.
Impact, in numbers from the test. A flat-price drop of 10 NFTs, 30 honest single-NFT bids, and an attacker with 8 sealed single-NFT bids. A fair share for the attacker is about 2.1 NFTs. The best of the 256 combinations wins 7, for a net profit of 13.9 units of the native currency in this setup after penalties.
Proof of concept. Copy the test file into contracts/test/ and run it from contracts/. Nothing in the source is modified.
$ forge test --match-path test/CloakPoc_Drop_CL01_CL03_CL04.t.sol -vv [PASS] test_poc_tieGrind() (gas: 809676152) Logs: reveal-all NFTs won (one sample draw): 6 fair expectation x1000 (8 tickets of 38, 10 NFTs): 2105 best mask: 223 best NFTs won: 7 best net profit (wei): 13900000000000000000 Suite result: ok. 3 passed; 0 failed; 0 skipped
Test assumptions. Local chain. A flat-price drop with one price tick. No mocks are needed for this test. The other two tests in the file belong to CL-03 and CL-04.
Recommendation. Add entropy to the draw that no bidder knows while bids can still be opened, for example the hash of a block after settlement.
Fix review. The team changed the draw to also use the hash of the block after settlement. We reran the attack against the fixed code over 60 fresh block hashes. The best-looking combination won 95 NFTs in total, against 128 for opening every bid, where a fair share is about 126. Choosing which bids to open no longer helps.
4. Findings CL-02 to CL-09 and A-1 to A-3
Redacted in this sample
In the full report each finding has the same sections as CL-01: what happens, who can do it, the scenario, the impact in numbers, the test and its command, the assumptions, and a recommendation.
5. PoC package
The report ships with a package of drop-in test files, a short README with one command per finding, and the logs of our own runs. Where a test replaces something with a stand-in, the README says so. In this review the ZK verifier was replaced by the team's own mock verifier in the tests that touch it.
6. Limits of this review
- The review is AI-assisted. It does not guarantee that other vulnerabilities do not exist and it is not a formal audit.
- Tests ran on a local chain, apart from one testnet fork test. Nothing was sent to a live chain.
- The circuit and the off-chain components were not reviewed.
- Findings on testnet-only code are listed separately, because they do not affect the contracts planned for mainnet.