solidity → daml · a confidential auction on canton

the bid no one else can see.

The same sealed-bid auction, built twice. On the EVM you fake privacy with commit, reveal, deposits, and forfeiture. On Canton, a bid is simply shared with no one else, so all of that machinery disappears.

4 → 2

steps to run it, after one-time setup

1 tx

token-standard dvp settlement

17 + 9

foundry + daml tests, green

0

lines of privacy plumbing in daml

built on the latest stack: daml 3.4 on canton · canton network token standard (cip-0056) · solidity 0.8.35

Watch the privacy hold

Place a sealed bid. See exactly what each party is, and isn't, allowed to read.

Four parties, four ledgers. The auctioneer co-signs every bid, so it sees all of them. Each bidder signs only their own. Flip to EVM and every redaction bar drops at once: the difference between a private ledger and a public one, in a click. Then settle: the winner's funds pay the seller and losers are refunded atomically, and even then you only ever see your own money move.

Live ledger · 0 sealed bids

Each Bid is shared only with the auctioneer and its bidder. Watch a bid appear in full for them, and as a redacted bar for everyone else.


AAliceCC
BBobCC
CCarolCC
A

Alice

Bidder

sees 0/0

  • no bids yet
B

Bob

Bidder

sees 0/0

  • no bids yet
C

Carol

Bidder

sees 0/0

  • no bids yet
AU

Auctioneer

Auctioneer

sees 0/0

  • no bids yet

Redaction bars are a teaching aid. On a live Canton deployment, a sealed bid never reaches a non-stakeholder's participant node at all. They wouldn't see even a placeholder.

But can the auctioneer cheat?

If only the auctioneer sees the bids, what stops it crowning the wrong winner?

Privacy puts every bid in the auctioneer's hands, which raises the obvious objection. The answer is on the ledger: settlement has to prove it counted every bid, so the winner is the true high bid, not just a trusted one.

✓ enforced

It can't drop a higher bid

Every sealed bid bumps a registry-signed counter the moment it is placed. To settle, the auctioneer must hand back exactly that many bids, so the set is provably all of them, never a chosen few.

↩ reclaimable

Worst case is a stall, not a stolen win

A misbehaving auctioneer can refuse to settle, but it can never crown a loser. Once the deadline passes, every bidder reclaims their own locked funds without it.

the tradeoff

Trust sits on the neutral registry

The count is vouched for by the token issuer, not the auctioneer, because a fully private ledger cannot prove completeness on its own. The EVM side turns the same dial the other way: it reveals every bid so anyone can check.

the same privacy, in code

on the evm, privacy is machinery. on canton, it's the type.

The Solidity contract spends commit, reveal, deposits, and a timeline to fake a secret. The Daml template just names who is allowed to see the bid, and the rest disappears.

solidity / src / SealedBidAuction.sol
SealedBidAuction.sol ×
// commit phase: hide the bid behind a hash + a deposit
function commit(bytes32 blindedBid) external payable onlyBefore(biddingEnd) {
    if (blindedBid == bytes32(0)) revert EmptyCommitment();
    if (bids[msg.sender].blindedBid != bytes32(0)) revert AlreadyCommitted();
    bids[msg.sender] = Bid({blindedBid: blindedBid, deposit: msg.value, revealed: false});
}

// reveal phase: open it later, and now it is public forever
function reveal(uint256 value, bytes32 secret) external onlyAfter(biddingEnd) onlyBefore(revealEnd) {
    Bid storage bid = bids[msg.sender];
    if (bid.blindedBid == bytes32(0)) revert NothingCommitted();
    bool valid = bid.blindedBid == _commitmentOf(value, secret)
        && bid.deposit >= value && value > 0;
    if (valid && value > highestBid) {
        highestBid = value; highestBidder = msg.sender;
    }
    // ... refunds, pendingReturns, withdraw, forfeiture
}
Solidity 0.8.35UTF-8 · LF · Ln 1, Col 1
daml / ConfidentialAuction.daml
ConfidentialAuction.daml ×
-- a single sealed bid, co-signed by the auctioneer and the
-- bidder and nobody else. it carries the bidder's locked
-- funds as a token-standard Allocation.
template Bid
  with
    auctioneer : Party
    auctionId : Text
    item : Text
    bidder : Party
    beneficiary : Party
    amount : Decimal
    allocation : ContractId Allocation
  where
    signatory auctioneer, bidder
    ensure amount > 0.0
    -- the signatory set is the entire privacy model
Daml 3.4UTF-8 · LF · Ln 1, Col 1

The translation

Everything you'd reach for in Solidity, and what it becomes.

Solidity / EVMDaml / Canton
mapping(address => Bid)one Bid contract per bidder
AlreadyCommitted guardsingle-use BidRight
msg.senderchoice controller
require(msg.sender == owner)controller / signatory
deposit in ether (msg.value)CIP-0056 Holding
mutate storagearchive + re-create
keccak256 commit + revealnothing
forfeiture (reveal-or-lose)nothing
pendingReturns / withdrawone token-standard DvP
event / emitstakeholders see the tx

✕ deleted

The reveal phase

No hash to commit to, so no second window to reveal it. A bid is born private.

✕ deleted

Forfeiture

Losing bidders aren't punished. Their locked funds are returned atomically once the auctioneer settles, so there's no 'reveal or lose your deposit' threat.

✕ deleted

The pull-payment dance

Settlement is one atomic transaction. The winner pays the seller and losers are refunded together, so there's no escrow-then-withdraw step and no reentrancy surface.

the lifecycle

Two timed windows and a forfeiture rule. Or two transactions.

Privacy is the headline, but the cost shows up in the lifecycle. The EVM auction runs across two timed phases and a string of transactions, and punishes anyone who forgets to reveal. On Canton the same auction is a bid and an atomic settlement.

Solidity / EVM

public
  1. deploy the contractsetup

    the commit and reveal deadlines are fixed up front

  2. commit windowtimed

    each bidder sends a transaction carrying a hashed bid and a deposit

  3. reveal windowforfeit risk

    each bidder sends a reveal transaction; miss it and the deposit is forfeited

  4. auctionEnd()tx

    a transaction tallies the revealed bids and picks the winner

  5. withdraw()tx

    every loser pulls their own refund in a separate transaction

two timed windows, up to 2N + 2 transactions

deposit forfeited if a bidder never reveals

Daml / Canton

private
  1. create the auctionsetup

    the auctioneer invites bidders; a single settle deadline

  2. place bid1 tx

    seals the bid and locks the funds as a token-standard holding, in one transaction

  3. settleatomic

    atomic delivery-versus-payment once the auctioneer settles: the winner pays the seller and losers are refunded, recorded together

two steps, one atomic settlement

no reveal window, no forfeiture, no pull-payment

run it yourself

From reading about it to running it, in one command.

Everything on this page is reproducible from the repo. No Docker: a real Canton node boots in-process, the token-standard packages are vendored, and the scripts find your toolchain for you.

bash
$ git clone github.com/Giri-Aayush/solidity-to-daml-confidential-auction
$ cd solidity-to-daml-confidential-auction
$ make test # 17 solidity + 9 daml tests, green
$ make canton # the auction on a real canton node
$ make web # this site, locally

make test

Runs both suites, the same thing CI runs on every push. The token-standard packages are vendored, so it builds offline.

make canton

Boots a real Canton node in-process (no Docker), deploys the auction, and runs the privacy + DvP proof on the live ledger, then cleans up.

make web

Serves this explainer locally, so you can read the code and poke the demo side by side.