1. Introduction
Public blockchains are renowned for their transparency, which is great for auditability but challenging for privacy-sensitive applications. Commercial secrets, medical records, or sealed bidding processes require data confidentiality.
Midnight addresses this challenge by enabling developers to build smart contracts using its domain-specific language, Compact. Midnight allows you to manage private states and verify transactions seamlessly using Zero-Knowledge Proofs (ZKPs).
In this tutorial, we will explore how to build a Sealed-Bid Auction smart contract on Midnight. We will look at how bidders can submit secret bids without revealing their bid amounts, while the contract securely verifies and updates the highest bidder.
2. Core Concept: Public vs. Private State Separation
The core architecture of Midnight revolves around separating public and private states:
- Public State: Data visible to everyone on the blockchain (e.g., auction status, winning address).
- Private State: Sensitive data held privately by the user (e.g., exact bid amounts).
In our Sealed-Bid Auction scenario, bidders keep their bid amounts strictly in the Private State. The contract logic executes inside a Zero-Knowledge circuit, verifying that a new bid exceeds the current highest bid without ever exposing the actual numerical value to the public ledger.
3. Smart Contract Implementation (Compact)
Let's break down the logic using Midnight's Compact smart contract syntax.
Step 1: Defining Contract States
First, we define what information is public and how private bids are structured.
// Auction.compact
// Private data structure - known only to the individual bidder
struct Bid {
bidder: Address;
amount: u64; // Secret bid amount
}
contract SealedBidAuction {
// PUBLIC STATE (Visible to everyone)
state {
auction_ended: bool;
highest_bid: u64; // Verified current highest bid indicator
winner: Optional<Address>; // Address of the leading bidder
}
// PRIVATE STATE
// Managed locally by the user's wallet and evaluated inside ZK circuits.
}
Code Explanation:
highest_bid: Represents the verified benchmark for bids. The public ledger tracks state updates without leaking the historical sequence of lower private bids.
Step 2: Submitting a Private Bid
When a user submits a bid, their wallet generates a ZK proof asserting that their private bid meets the contract's conditions.
// Bid Submission Transition
// `amount` is provided as a PRIVATE input inside the ZK circuit.
transition submit_bid(amount: u64) {
// 1. Check: Is the auction still active?
assert !prev.auction_ended;
// 2. Check: Is the private bid greater than the current highest bid?
// This comparison happens entirely inside the ZK circuit.
assert amount > prev.highest_bid;
// 3. Action: Update the public state
// The winning address updates publicly, while the logic guarantees validity.
next.highest_bid = amount;
next.winner = Some(caller);
}
Code Explanation:
transition submit_bid(amount: u64): The amount parameter remains off-chain in terms of raw visibility. The user's wallet constructs a Zero-Knowledge proof demonstrating that amount > prev.highest_bid.
assert amount > prev.highest_bid: If this condition fails locally, the wallet cannot construct a valid ZK proof, preventing invalid transactions from ever reaching the network.
4. Key Takeaways for Developers
Midnight strikes a balance between public auditability and data privacy. Through this Sealed-Bid Auction example, we can observe three key mechanics:
Data Confidentiality: Sensitive values (amount) remain private to the user.
On-Chain Verification: State transitions are verified cryptographically via ZK proofs.
Public Consistency: Shared state (winner, auction_ended) updates reliably without compromising underlying secrets.
This model opens doors for private voting, selective KYC disclosures (e.g., proving age over 18 without revealing date of birth), and confidential DeFi protocols. By abstracting ZK math into the readable Compact language, Midnight makes building privacy-first dApps accessible to every developer.
Top comments (0)