Sealed reserve
Most auction protocols treat the reserve price as public: the seller announces it before bidding opens, and everyone knows the floor. SealedIP does something different. The seller's reserve is encrypted into a CDR vault alongside the bids, and it is revealed and verified only at settlement — in the same transaction that decrypts the bids.
This makes SealedIP two-sided sealed: bidders don't know the reserve, and the seller doesn't know the bids. Neither side sees the other's number until settlement.
Why seal the reserve?
A publicly-known reserve creates strategic distortions:
-
Bidders cluster at the floor. Knowing the minimum, rational bidders shade their bids toward the reserve rather than their true valuation. Auctions with a visible floor often receive bids that hover just above it regardless of actual demand.
-
The seller leaks information. Publishing a reserve reveals the seller's bottom line. A low reserve signals willingness to accept a low price. A high reserve can deter bidders from participating at all.
-
Coordination risk increases. When bidders know the floor, they can coordinate implicitly around it — all staying low, knowing the others will too.
A sealed reserve removes all of this. Bidders must bid based on their own valuation of the IP. The seller commits to a floor without revealing it. The result is a true two-sided private-information game.
How the seller seals a reserve
The reserve uses the exact same cryptographic format as a bid. There is no separate mechanism.
The payload format is the 149-byte BidPayload:
| address signer (20 bytes) | uint256 amount (32) | bytes32 nonce (32) | signature (65) |
For a bid, signer is the bidder. For the reserve, signer is the seller.
Both use the same digest and signing scheme, so the same BidPayload.recoverSigner
Solidity function verifies both.
Constraints on submitEncryptedReserve:
- Caller must be the auction's seller (
NotSellerreverts otherwise). - Auction must be in
Openstate before the deadline (BiddingClosedreverts otherwise). - One write per auction (
ReserveAlreadyWrittenreverts on a second attempt).
Floor default: 0
If the seller does not call submitEncryptedReserve, the auction record has
reserveHasCiphertext = false. At settlement, the contract detects this and
sets the floor to 0. Every bid becomes a valid winner candidate; the highest
bid wins regardless of amount.
This means listing without sealing a reserve is valid. It's effectively a no-reserve auction in which the winner is whoever bids the most.
Reveal and verification at settle
The reserve is never stored in plaintext on-chain. It is only ever accessible as a ciphertext in CDR, reconstructed under the same two-gate condition as the bids.
At settlement, the orchestrator passes a ReserveReveal struct alongside the
bid reveals:
struct ReserveReveal {
uint256 amount;
bytes32 nonce;
bytes signature; // 65 bytes: r || s || v
}
The contract checks reserveHasCiphertext. If true, it calls
BidPayload.recoverSigner on the ReserveReveal. The recovered address must
equal auction.seller. If it does not, the contract reverts with
InvalidReserveReveal and the entire settlement fails. No bid is evaluated,
no license mints, no transfers occur.
If the signature is valid, reserveReveal.amount becomes the floor. Bids
below the floor are dropped and refunded. The highest bid at or above the
floor wins.
What the reserve does and doesn't reveal to the orchestrator
The orchestrator sees the reserve plaintext when it reads the CDR vault to
prepare the settle() call. This is a necessary property of the protocol:
someone must submit the reveal to the contract.
What the orchestrator cannot do:
- Forge a different reserve (the seller's signature would not match).
- Submit a false reserve that reverts settlement (the signature check would fail with
InvalidReserveReveal). - Use the reserve to select a different winner (the contract's on-chain logic picks the winner; the orchestrator only supplies data).
The trustlessness guarantee is that the orchestrator can only censor (refuse to
call settle()). It cannot manipulate the outcome.
How validators treat the reserve vault
The reserve vault (reserveUuid) is registered on AuctionRevealCondition
in the same call that creates the auction. It is bound to the same two-gate
condition as the bid vaults:
block.timestamp >= deadlineSealedAuction.isTriggered(auctionId) == true
CDR validators process the reserve vault at the same time as the bid vaults. All vaults open at once. Neither the reserve nor any bid can be decrypted before both gates pass.
The information asymmetry that remains
Even with a sealed reserve, two pieces of public information give bidders some signal:
- That a reserve was sealed:
reserveHasCiphertextis a public boolean. Bidders know whether the seller committed to a floor, but not what it is. - Deposit amounts: each bidder's deposit is visible on-chain. A bidder who deposits exactly their true valuation exposes an upper bound.
These are inherent properties of the protocol, documented in Known limitations.
Summary
| Bid | Reserve | |
|---|---|---|
| Who seals it | Bidder | Seller |
| When | Before deadline, Open state | Before deadline, Open state |
| CDR vault allocated | At allocateBidSlot | At createAuction |
| Ciphertext written | submitEncryptedBid | submitEncryptedReserve |
| Signer in payload | Bidder address | Seller address |
| Default if not sealed | Slot skipped, deposit refunded | Floor = 0 |
| Verified at settle | BidPayload.recoverSigner == bidder | BidPayload.recoverSigner == seller |
| On-chain error if invalid | Bid dropped silently | InvalidReserveReveal (settle reverts) |
See Settlement flow for the full sequenced breakdown, or Lifecycle for the step-by-step walkthrough.