Number: HIP-0006
Title: Trustless Live Auctions for Handshake
Type: Informational
Status: Final
Authors: Matthew Zipkin (@pinheadmz)
Created: 2021-06-17
We propose an ascending-price auction system for HNS domain names without trust or
third-party custody. A name owner initiates an auction by locking their TLD with
a verifiable script. Bidders place bids by partially-signing TRANSFER
transactions,
and the name owner eventually picks the highest bid and atomically swaps the name
ownership for the payment. The protocol requires more interactivity and centralization
than the current Shakedex-style HIP-0001 auction format, but with some compelling trade-offs.
In addition, the limits on collaborative transaction construction (like atomic swaps)
mean that the buyer can not add a change output to the final transaction. Because there is
no change output, the name owner must pay the miner fee out of their sale price in the
final transaction.
This proposal is based on HIP-0001, and
in particular the Swap Script, Transaction Structure, and Swap Proof. Handshake
primitives like OP_TYPE
and SIGHASH_SINGLEREVERSE
are also required.
Recall the HIP-0001 Swap Script only allows two covenant actions be applied to the name:
-
The name owner can sign a TRANSFER with their private key
-
Anyone can FINALIZE without a signature
The auction technically begins when a name owner FINALIZEs a transfer of their name to a HIP-0001 locking script. The name owner must then publicly reveal the redeem script for the address that owns the name, so it can be verified. The locking script prevents the name owner from canceling an in-progress name ownership transfer. Since there is a small amount of interactivity required in the protocol, the name owner must also publish "where" the auction will take place. The auction "location" is just some internet endpoint where bidders can submit bids. It can be a private email address (in which case the name owner may choose to keep all bids confidential or truly blind), or it can be a public server that announces every new bid as they arrive.
A user that wants to bid on the name constructs a partially-signed transaction in the following format:
vin:
0: current name owner, encumbered by Swap Script
1: A UTXO controlled by the bidder of some value V
vout:
0: TRANSFER to the bidder's address
Input 1 is signed with SIGHASH_ANYONECANPAY | SIGHASH_SINGLEREVERSE
. This commits
the incoming payment to the name transfer. Note that Input 0 is not signed yet,
but it must be the current name-owning UTXO or the linked output 0 would be invalid.
Note that this transaction has no change output. Because the bidder is signing
with single-reverse, they can't specify a change output in addition to the name
transfer output. If they were to sign with SIGHASH_ALL
then the next step of
the protocol would not be possible. If they signed with SIGHASH_SINGLE
they could
commit to a change address, but not the name transfer.
Having no change output means the INPUT coin they are spending MUST BE THE VALUE OF THE BID THEY INTEND TO PLACE ON THE NAME. This is a big inconvenience that can be solved in at least these two ways:
-
The user prepares their wallet in advance and collects UTXOs with a range of values (1 HNS, 10 HNS, 100 HNS, 1000 HNS, etc).
-
When the user wants to bid, their wallet creates a "perfect" UTXO by spending to itself, creating a coin with the exact value desired. This output can be used (even before it is confirmed) in this protocol.
The name owner collects partially-signed bids and after some time, declares the highest value bid the winner. The name owner completes the auction by adding a payment output to the partially-signed transaction, signing and broadcasting:
vin:
0: current name owner, encumbered by Swap Script
1: A UTXO controlled by the bidder of some value V
vout:
0: TRANSFER to the bidder's address
1: payment to the name-owner
The name owner pays themselves from the fully funded transaction, and signs input 0
with SIGHASH_ALL
. This transaction is a valid atomic swap and can be broadcast to the network.
Because the locking script prevents all other name actions, the auction winner can
FINALIZE
this name transfer any time after the 288-block lockout without any other
obligation.
Note that since the bidder creates the INPUT and the name owner creates the OUTPUT, the name owner MUST also pay the miner fee for this transaction. They do so by reducing the value of output 1, effectively paying the miner fee out of the money they earned by the sale.
The partially-signed bid transactions are collected by the name owner and commit to spending a UTXO of some value V from the blockchain. In the simplest construction, nothing prevents the bidder from invalidating this off-chain transaction by double-spending the coin they used to bid. For this reason, the name owner must routinely check that all submitted bids are still valid. They can try finalizing the highest bid and if that fails, try to finalize the second-highest bid, etc.
More advanced constructions may be possible to more effectively prevent this, see the next section for one possible extension.
To ensure that bids are funded by reliable outputs, the bidder and name owner can engage in a time-lock multisig contract that will ensure the bids are valid until the end of the auction. Bidders will effectively be "locking up" their bids for the duration of the auction. After the auction expiry time, bidders can "redeem" their losing bids with (almost) no potential grief. This pattern should be very familiar to Handshake users!
OPEN: Name owners must include extra metadata in their auction announcement:
-
An indication that all bids must be submitted using this mode, or they will be ignored.
-
An auction expiration time, set by blockchain height:
expiration_height
-
A public key from the name owner's wallet that they can sign transactions with:
owner_pk
BID: When a user wants to submit a bid for this name, they follow these steps:
-
Derive a public key from their wallet that they can sign with:
bidder_pk
-
Combine their key and the auction announcement data into the script below:
bid_lock_script
-
Derive the corresponding address for that script and send their bid amount to that address:
bid_lock_tx
-
Construct a partially-signed TX
bid_submit_tx
as defined in Auction BID but with the following modifications:-
Input 1 of this transaction will spend the UTXO created by
bid_lock_tx
-
The redeem script in the witness of input 1 will be a partially-signed multisig spend defined below as
bid_submit_script
, with a single0x00
byte as a placeholder for the name owner's signature.
-
-
Bidder submits the following data to the name owner:
-
bid_lock_script
with all data inserted such that the name owner can verify it matches the address in the output ofbid_lock_tx
. -
The double-partially-signed
bid_submit_tx
(which will explicitly indicate the outpoint of the UTXO created bybid_lock_tx
)
-
FINALIZE: The name owner must finalize the atomic swap BEFORE expiration_height
:
-
Name owner chooses the highest bid and verifies that its
bid_lock_tx
is confirmed on chain, and that the output address is spendable bybid_lock_script
. They construct a final TX as defined in Auction FINALIZE:-
Name owner inserts their self-payment (minus miner fee) at output 1 like normal.
-
Name owner signs the name-owning UTXO into a TRANSFER at input 0 like normal.
-
Name owner must now also COMPLETE THE MULTISIG in input 1 to fund the transaction. This is accomplished by filling in
owner_sig
in the incompletebid_submit_script
that the bidder has included in the witness ofbid_submit_tx
.
-
REDEEM: If the bidder loses the auction or if for any reason the expiration_height
is
reached without their bid_lock_tx
output being spent into an atomic swap, they can
refund their bid value using bid_redeem_script
below. That refund transaction MUST
have a locktime set at expiration_height
and of course can not be submitted until that time.
The only potential grief attack is a race condition after expiration_height
has been reached
in which both spending paths are valid: the name owner can still complete the atomic swap, and the
bidder can now submit a refund. However, if the name owner has already completed the auction
and finalized a "winner" then only the refund path is valid.
bid_lock_script:
OP_IF
OP_2
<owner_pk>
<bidder_pk>
OP_2
OP_CHECKMULTISIG
OP_ELSE
<expiration_height>
OP_CHECKLOCKTIMEVERIFY
OP_DROP
<bidder_pk>
OP_CHECKSIG
OP_ENDIF
bid_submit_script:
OP_1
0x00 # placeholder for <owner_sig>
<bidder_sig>
bid_redeem_script:
OP_0
<bidder_sig>
One possible scenario with this protocol is that the name owner never finalizes any bids. They might have decided a "reserve price" was not met or just changed their mind about the sale. One technique that may be used to ensure bidders that a winner will be chosen by a certain time is to include a trusted moderator.
Auctions could be held on a platform like Shakedex or run by an agency with a good reputation like Namebase or Kyokan. This agent would publish a static public key for their platform, and name owners would have to incorporate that key along with a relative locktime into the HIP-0001 locking script like so:
OP_TYPE
0x09 // TRANSFER
OP_EQUAL
OP_IF
OP_IF
<name-owner's public key>
OP_ELSE
<timespan of auction>
OP_CSV
OP_DROP
<platform's public key>
OP_ENDIF
OP_CHECKSIG
OP_ELSE
OP_TYPE
0x0a // FINALIZE
OP_EQUAL
OP_ENDIF
What this does is add a rule to the locking script such that, after a certain amount of time (i.e. 5 days) the platform operator can finalize the auction with their own private key instead of waiting for the name owner to do so. Technically this means that if the name owner does not finalize the auction in time (or simply transfer the name back to themselves -- publicly ending the auction on the blockchain) the platform can steal the name OR the value of the highest bid. In some cases, this may be the actual terms-of-service. In more common cases we can assume a platform with interest in maintaining a good reputation would award the highest bidder with the name and pay the name owner from output 1 of the final transaction.
A commercial platform could also improve auction quality by regularly performing the offline bid-validity checks mentioned in the previous section. The platform would have to be trusted (via reputation) to perform this job correctly or some bids may be censored.
Because this auction protocol is interactive, bidders need a place to send their partially-signed transactions where they are accessible to the name owner to finalize. A name owner could therefore open an auction by adding TXT records to their zone announcing the endpoint to send bids, along with the redeem script to prove the name is locked by HIP-0001! Once this UPDATE has been sent, users just need to wait for the name owner to FINALIZE the transfer to the HIP-0001 address, and the auction is LIVE!