# Hidden in Plain Sight: A Sneaky Solidity Implementation of a Sealed-Bid Auction

> Editor’s note: This piece is part of our ongoing series on all things auctions for web3. Part 1 was an overview of auction designs, and technical challenges (and opportunities) specific to mechanism design in a permissionless blockchains context. Part 2 was a piece on clearing the market and avoidin...

- URL: https://a16zcrypto.com/posts/article/hidden-in-plain-sight-a-sneaky-solidity-implementation-of-a-sealed-bid-auction
- Authors: [Michael Zhu](https://a16zcrypto.com/team/michael-zhu)
- Published: 2022-11-16
- Updated: 2023-10-09
- Focus areas: code & engineering
- Tags: auction design, open source, smart contracts, mechanism design, Solidity

---

*Editor’s note: This piece is part of our ongoing series on all things auctions for web3.* [*Part 1*](https://web3-with-a16z.simplecast.com/episodes/auction-design-mechanisms-incentives-choices-tradeoffs) *was an overview of auction designs, and technical challenges (and opportunities) specific to mechanism design in a permissionless blockchains context.* [*Part 2*](https://a16zcrypto.com/nft-sales-market-clearing-gas-wars-auction-mechanism-design-for-builders/) *was a piece on clearing the market and avoiding gas wars.* [*Part 3*](https://a16zcrypto.com/how-auction-theory-informs-implementations/) *shares an overview of canonical auction types, a look into how theory translates into practice, and our first implementation of a novel, sealed-bid Vickrey auction.* 

On-chain auctions are one of the most interesting (and ubiquitous) design spaces in web3 — from NFT sales to collateral auctions — giving rise to a new landscape of implementations and research. While auction mechanism design has been around for centuries, and has evolved in recent decades with the advent of the web and ecommerce, we’re only now applying these approaches to smart contracts.

We’re also starting to see more auction designs that are native to blockchains, including our [open source](https://github.com/a16z/auction-zoo) Solidity implementation of a Vickrey auction, and several interesting developments from the community (including [suggestions](https://twitter.com/cygaar_dev/status/1579270471664553985)for efficiency improvements, new [theoretical results](https://arxiv.org/abs/2209.11293), and two [hackathon-winning](https://ethglobal.com/showcase/anonymous-vickrey-auctions-on-chain-igh5e) [implementations](https://ethglobal.com/showcase/peekaboo-wwnv1) of sealed-bid auctions). In our first design, we made a tradeoff between privacy and capital efficiency: We used overcollateralization (bidders lock up more collateral than is required by their bid) in order to enforce payment from the winning bidder, without revealing the precise bid values via the collateral amount. By locking up more capital you get more privacy at a potentially greater opportunity cost. But what if we could have bid privacy *without* overcollateralization?

This post introduces a new auction design that we call “SneakyAuction,” which combines the `CREATE2` opcode and state proofs to guarantee bid privacy without requiring bidders to lock up more collateral than is required. We begin by breaking down how it works, and then compare it to our previous implementation (OverCollateralizedAuction) in terms of gas cost, user experience, and privacy. We’ve also added the implementation to our [Auction Zoo](https://github.com/a16z/auction-zoo) repository on GitHub so that you can fork it, build on it, and follow along as we dive into more mechanics; in meantime, more on how it works and compares to our past design below.

## How it works: Committing to bids using CREATE2

There are two requirements we need to create an “eventually-public” sealed-bid auction on-chain. First, bids need to be private for the duration of the bidding period, and then revealed when it ends; commit-reveal schemes (where users publish hash-committed values, and then reveal their inputs later) can replicate this mechanism on-chain. The second requirement is collateralization: bids must be backed by collateral to ensure the winner has enough funds to fulfill their commitment.

In our overcollateralized Vickrey implementation, prospective buyers place bids by calling the [`commitBid`](https://github.com/a16z/auction-zoo/blob/main/src/sealed-bid/over-collateralized-auction/OverCollateralizedAuction.sol#L182) function, supplying a hash commitment and the collateral to be escrowed. This approach satisfies the requirements, but has some drawbacks. Even though the bid itself is hidden by the hash, the `commitBid` transaction openly and immediately signals the intent of the user: “I would like to bid on this auction, and here is the collateral for my bid.” Without overcollateralization, the visibility (and linkability) of both *intent* and *collateral* would reveal bid values. But if we can obfuscate the intent of a transaction, we might be able to achieve bid privacy without relying on overcollateralization.

The [`CREATE2`](https://docs.openzeppelin.com/cli/2.8/deploying-with-create2) opcode, introduced in [EIP-1014](https://eips.ethereum.org/EIPS/eip-1014) and included in the Constantinople hard fork, gives us a way to do just that. The `CREATE` and `CREATE2` opcodes are both used to deploy smart contracts, but they differ in how the deployment addresses are computed. The `CREATE` deployment address is computed as a hash of the deployer’s address and nonce; the `CREATE2` deployment address, on the other hand, is computed as a hash of the contract’s bytecode and constructor parameters, an arbitrary salt, and the deployer’s address ([details](https://ethereum.stackexchange.com/a/761)).

`CREATE2` is often used in the factory pattern to deploy contracts to predictable addresses — for example, the [UniswapV3PoolDeployer](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3PoolDeployer.sol) contract uses `CREATE2` to deploy each pool contract to an address which is a function of the token pair and fee tier. `CREATE2` can also be used to (re)deploy upgradable smart contracts, most notably in the [metamorphic](https://a16zcrypto.com/metamorphic-smart-contract-detector-tool) contract pattern.

More importantly for us, the `CREATE2` deployment address can function as a hash commitment to any behavior defined by the input bytecode and parameters. If the constructor parameters encode a bid, the `CREATE2` address can serve as a bid commitment.

![](https://dwt2zme5yrom6.cloudfront.net/uploads/2022/11/carbon-9.png)

*Computing the address of a vault in Solidity*

Moreover, the contract itself can serve as a vault — a bidder can send ETH to the `CREATE2` vault address *before the contract is deployed* to collateralize and commit to their bid in one simple transfer! Since the bidder does not have the private key for the vault address, the collateral is locked until the bid is revealed, at which point the SneakyAuction contract deploys and unlocks the vault.

![sneakyvault Solidity auction contract](https://dwt2zme5yrom6.cloudfront.net/uploads/2022/11/carbon-10.png)

*The SneakyVault contract. Checks whether its bid has won, and sends its ETH to the seller or bidder accordingly. All in the constructor!*

This approach makes the transaction indistinguishable from a transfer to an externally owned address (EOA). The bid transaction is hidden in plain sight, among other transfers on the blockchain. One important caveat, however: this seemingly tidy solution also makes it difficult to determine *when* the collateral was locked. It’s essential for auction security that the vault was funded before any bids are revealed. Otherwise, an opportunistic buyer could wait until the very end of the reveal period, at which point most bids have already been revealed, to decide whether or not to collateralize their vault. We need to ensure that vaults are collateralized *during the bidding period,* not during the reveal period, using another tool: state proofs.

## Retroactively verifying collateral using state proofs

One way to ensure that a vault was collateralized during the bidding period is by checking its balance at a past block. It’s relatively easy to do this off-chain by querying an archive node; but much more difficult to accomplish (trustlessly) on-chain. The EVM’s `BALANCE` opcode reads the current balance of an address, but no such opcode exists to retrieve a *past*balance. In fact, the only EVM opcode that provides any sort of historical state access is `BLOCKHASH`, which returns the hash of one of the last 256 blocks. Fortunately — with some off-chain help — blockhash works just well enough for our use case.

The blockhash is the hash of the block header, which includes (among other metadata) the **state root** of that block. The state root is the root node of a [Merkle-Patricia trie](https://ethereum.org/en/developers/docs/data-structures-and-encoding/patricia-merkle-trie/), where each leaf node corresponds to a particular address and includes the address’ *balance* at that block. We can’t directly access these leaf nodes on-chain, but we can verify that the contents of a leaf node are correct. In fact, the [`eth_getProof`](https://eips.ethereum.org/EIPS/eip-1186) RPC method supported by [Alchemy](https://docs.alchemy.com/reference/eth-getproof) (among other providers) returns the Merkle proofs needed to perform this verification (Leo Zhang provides an [in-depth explanation](https://medium.com/@chiqing/verify-ethereum-account-balance-with-state-proof-83b51ceb15cf) of how this works in the context of Ethereum light clients). This means that with a little bit of off-chain help (a single RPC call), bidders can prove to the SneakyAuction contract that their vault was collateralized during the bidding period.

![](https://dwt2zme5yrom6.cloudfront.net/uploads/2022/11/trie.png)

*The components of an EVM block header. Source:* [*https://ethereum.stackexchange.com/a/6414*](https://ethereum.stackexchange.com/a/6414)

In our implementation, the [first bid](https://github.com/a16z/auction-zoo/blob/main/src/sealed-bid/sneaky-auction/SneakyAuction.sol#L231-L258) revealed for an auction stores the blockhash of the previous block. This transaction effectively transitions the auction from the bidding phase to the reveal phase — all [subsequent bids](https://github.com/a16z/auction-zoo/blob/main/src/sealed-bid/sneaky-auction/SneakyAuction.sol#L259-L279) revealed must provide a Merkle proof that their vault was sufficiently collateralized before that block (i.e. before the first bid was revealed). Note that the *first* `revealBid` transaction would ideally be submitted via a private transaction pool (e.g. Flashbots); otherwise, a bidder watching the mempool (seeing the value of the revealed bid) could front-run the transaction and place a last-second bid.

### LibBalanceProof

To minimize costs for bidders, we wrote a gas-optimized [library](https://github.com/a16z/auction-zoo/blob/main/src/sealed-bid/sneaky-auction/LibBalanceProof.sol) to verify balance proofs on-chain that builds on [contracts](https://github.com/aragon/evm-storage-proofs)written by the Aragon team (who pioneered on-chain storage proofs in 2018), and Hamdi Allam’s contracts for on-chain [RLP decoding](https://github.com/hamdiallam/Solidity-RLP/). Our library uses a number of low-level tricks and optimizations that rely on the particular structure of the state trie, so it cannot be used for generic Merkle-Patricia trie proofs. In return, it allows the SneakyAuction contract to verify the past balance of a vault in less than 30,000 gas.

We also wrote a lightweight [JavaScript wrapper](https://github.com/a16z/auction-zoo/blob/main/getBalanceProof.js) for the `eth_getProof` RPC method. Given an address and block number, it returns the balance proof and RLP-serialized block header, which can be used to reveal a bid.

## How it compares

Let’s compare our new SneakyAuction approach against the OverCollateralizedAuction design we last released, along several key dimensions that technical designers or users care about: gas costs, user experience, and privacy.

### Gas costs

SneakyAuction’s `revealBid`, `endAuction`, and `withdrawCollateral` functions require deploying a `SneakyVault`, so they are more expensive than their OverCollateralizedAuction counterparts. `revealBid` is particularly expensive because it also verifies a balance proof, which costs about 25,000 gas.

|  | **OverCollateralizedAuction** | **SneakyAuction** |
| --- | --- | --- |
| **createAuction** | 132,625 | 112,296 |
| **commitBid** | 46,120 | 21,000 (cost of an ETH transfer) |
| **revealBid** | 33,728 | 135,741 |
| **endAuction** | 57,652 | 87,340 |
| **withdrawCollateral** | 30,426 | 65,825 |

*Approximate gas costs of different operations, based on Foundry unit tests*

### User experience

Although the two implementations follow a similar overall flow (bidding phase, reveal phase, auction ends), there are some differences in user experience. SneakyAuction has a few minor disadvantages:

-   The experience of sending ETH to an undeployed vault, though it could be abstracted away by the front-end, is potentially confusing for users who examine their bid transaction on a block explorer.
-   With OverCollateralizedAuction, it’s possible to end the auction early if all bids have been revealed. This is not possible in SneakyAuction because the contract has no way of knowing how many bids have been committed.
-   Bidders can update their bid and top up their collateral with OverCollateralizedAuction by calling `commitBid` again. In SneakyAuction, bidders can’t make updates once the bid’s vault has been collateralized.

### Privacy

The bid privacy of OverCollateralizedAuction relies on bidders choosing to lock up extra collateral (so onlookers know an upper bound of a bid but not the exact amount). SneakyAuction, on the other hand, derives privacy from on-chain activity that’s completely unrelated to the auction itself: ETH transfers that happen during the auction’s bidding period.

For simplicity, let’s assume that each bid is collateralized using a single ETH transfer. We observe that:

1.  The collateralization transaction should be the first time anyone has interacted with the vault address on-chain.
2.  We don’t expect any other transactions to touch the vault address for the rest of the bidding period.
3.  No transactions can originate from the vault address (because no one has the private key).

ETH transfers during the bidding period to otherwise “untouched” addresses are plausibly bids — in other words, they are the “noise” that hides bid transactions. To help quantify the privacy of SneakyAuction, we can look at the shape of this noise distribution.![](https://dwt2zme5yrom6.cloudfront.net/uploads/2022/11/eth_transfers-2.gif)

This histogram shows the year-to-date distribution of daily ETH transfers (on Ethereum mainnet) to untouched addresses, illustrating the noise distribution for a 24-hour bidding period. We can see that most transactions fall into the \[0.001, 1\] ETH range, implying that auctions with an expected bid value in that range would have the strongest privacy. On the other hand, the typical noise may not provide sufficient privacy for auctions where the expected bid is greater than 10 ETH — there are rarely more than 100 transfers in that range, so an auction attracting many bids would create a conspicuous spike in the distribution.

For another perspective on this data, these scatter plots depict the transfers on October 15, 2022, overlaid with bids from two hypothetical auctions:

![](https://dwt2zme5yrom6.cloudfront.net/uploads/2022/11/stripplot2.png)

*200 bids, normally distributed around 1 ETH*

![](https://dwt2zme5yrom6.cloudfront.net/uploads/2022/11/stripplot1.png)

*200 bids, normally distributed around 100 ETH*

Intuitively, it would be much easier for an observer to identify bids from the second auction. In practice, you could use a clustering algorithm such as the [expectation-maximization](https://en.wikipedia.org/wiki/Expectation%E2%80%93maximization_algorithm) (EM) algorithm to predict which transactions are bids.

However, there are a few other factors that can make SneakyAuction more private (and therefore more compelling) in practice:

1.  Longer bidding periods: Privacy scales with the length of the bidding period –– the longer the bidding period, the more transfers there are to hide bids.
2.  Concurrent auctions: Privacy scales with the number of concurrent auctions –– if two auctions are in their bidding phases at the same time, one auction’s bids serve as noise for the other.

SneakyAuction can also benefit from overcollateralization — since SneakyVault returns any excess ETH to the bidder, bidders can opt to overcollateralize for further privacy. So in a sense, SneakyAuction provides strictly stronger privacy than our previous implementation.

A simple corollary of SneakyAuction’s privacy mechanism is that it hides the *number* of bids during the bidding period. This is an advantage over OverCollateralizedAuction, which only hides the bid values — the number of bid commitments that have been made for a given auction is fully public (and may give away how competitive the auction is).

\*\*\*

While our first implementation of a sealed-bid auction translated real-world features into on-chain design decisions, our second design relies on a novel and practical mechanism to use the public nature of blockchains to its advantage: sealed bids “hide” among unrelated blockchain activity.

Though this new approach is a convenient way to achieve bid privacy without overcollateralization, it’s not necessarily suitable for all auctions (for instance auctions with many high-value bids). Privacy improves for auctions that expect smaller bids (and especially over a longer period of time).

\*\*\*  
*The views expressed here are those of the individual AH Capital Management, L.L.C. (“a16z”) personnel quoted and are not the views of a16z or its affiliates. Certain information contained in here has been obtained from third-party sources, including from portfolio companies of funds managed by a16z. While taken from sources believed to be reliable, a16z has not independently verified such information and makes no representations about the current or enduring accuracy of the information or its appropriateness for a given situation. In addition, this content may include third-party advertisements; a16z has not reviewed such advertisements and does not endorse any advertising content contained therein.*

*This content is provided for informational purposes only, and should not be relied upon as legal, business, investment, or tax advice. You should consult your own advisers as to those matters. References to any securities or digital assets are for illustrative purposes only, and do not constitute an investment recommendation or offer to provide investment advisory services. Furthermore, this content is not directed at nor intended for use by any investors or prospective investors, and may not under any circumstances be relied upon when making a decision to invest in any fund managed by a16z. (An offering to invest in an a16z fund will be made only by the private placement memorandum, subscription agreement, and other relevant documentation of any such fund and should be read in their entirety.) Any investments or portfolio companies mentioned, referred to, or described are not representative of all investments in vehicles managed by a16z, and there can be no assurance that the investments will be profitable or that other investments made in the future will have similar characteristics or results. A list of investments made by funds managed by Andreessen Horowitz (excluding investments for which the issuer has not provided permission for a16z to disclose publicly as well as unannounced investments in publicly traded digital assets) is available at [https://a16z.com/investments/](https://a16z.com/investments/).*

*Charts and graphs provided within are for informational purposes solely and should not be relied upon when making any investment decision. Past performance is not indicative of future results. The content speaks only as of the date indicated. Any projections, estimates, forecasts, targets, prospects, and/or opinions expressed in these materials are subject to change without notice and may differ or be contrary to opinions expressed by others. Please see [https://a16z.com/disclosures](https://a16z.com/disclosures) for additional important information*

---

Source: [Hidden in Plain Sight: A Sneaky Solidity Implementation of a Sealed-Bid Auction](https://a16zcrypto.com/posts/article/hidden-in-plain-sight-a-sneaky-solidity-implementation-of-a-sealed-bid-auction) — a16z crypto. Full archive: https://a16zcrypto.com/posts
