Betting on the blockchain
The blockchain is a great tool that enables people that do not trust each other to transact. In blog post I'm exploring a way for two parties to place a bet without having any trust between them. Also, the none of the two parties should be able to enforce custodial control of the other party's money.
The game consists of four phases, the setup phase, the invitation phase, the accept phase, and the redeem phase. During each phase, it is ensured that honest players get all of their expected reward and dishonest players get nothing in some finite and pre-defined amount of time.
Setup phase
First, Alice and Bob choose a sufficiently large secret random value, A_random
, B_random
. Next, they compute the SHA256 of their random values, A_commit
, B_commit
. Alice sends A_commit
to Bob and Bob sends B_commit
to Alice.
During this phase, no transactions have been made and any party can leave without causing any damage to the other.
Invitation phase
To start the game, Alice broadcasts invitation_tx
that spends A_bet
bitcoins from her wallet. The output of invitation_tx
is a script that takes B_random
as input and then verifies a multisig between Alice and Bob. The script also includes a safety net. If the output is not spent before time t1
, then it is made possible for Alice to take back her money.
If they cannot reach an agreement or if Bob disappears from the game, all Alice has to do is to wait until time t1
, after which she can take her A_bet
bitcoins back.
invitation_tx =
inputs:
0:
from: alice_wallet
amount: A_bet
sigScript: aliceSig
outputs:
0:
amount: A_bet
pubScript: (B_random, A_sig, B_sig) ->
if sha256(B_random) == B_commit
checkmultisig(A_pub, A_sig, B_pub, B_sig)
else
checklocktimeverify(t1)
checksig(A_pub, A_sig)
Accept phase
After invitation_tx
has been mined and before time t1
has come, Bob has the opportunity to participate in the bet. To do so, he creates a transaction accept_tx_draft1
which spends A_bet
bitcoins from invitation_tx
and also spends B_bet
bitcoins from his wallet. The output is a script that takes A_random
and B_random
as inputs and calculates the winner's public key. The script also includes a safety net for Bob. If Alice fails to reveal her A_random
before time t2
, Bob will be able to spend A_bet + B_bet
bitcoins without Alice's participation.
accept_tx_draft1 =
inputs:
0:
from: invitation_tx
amount: A_bet
sigScript: [ EMPTY ]
1:
from: bob_wallet
amount: B_bet
sigScript: bobSig
outputs:
0:
amount: A_bet + B_bet
pubScript: (A_random, B_random, sig) ->
if sha256(A_random) == A_commit and sha256(B_random) == B_commit
# calculate winner based on A_random, B_random
checksig(winner_pub, sig)
else
checklocktimeverify(t2)
checksig(bob_pub, sig)
Bob sends accept_tx_draft1
to Alice, requesting her to sign her part of the multisig protecting invitation_tx
's output. Notice that the sigScript is [ EMPTY ], and doesn't not yet contain B_random
, so Alice doesn't get to know whether she has won or not.
Before Alice signs accept_tx_draft1
, she has to check that:
B_bet
is the amount they have agreed on- The output implements the game they agreed to play (e.g coinflip)
B_commit
inaccept_tx_draft1
's output is the same asB_commit
ofinvitation_tx
's output
If all these conditions are met, she signs the transaction and sends accept_tx_draft2
back to Bob.
accept_tx_draft2 =
inputs:
1:
from: invitation_tx
amount: A_bet
sigScript: [ A_sig ]
2:
from: bob_wallet
amount: B_bet
sigScript: bobSig
outputs:
1:
amount: A_bet + B_bet
pubScript: (A_random, B_random, sig) ->
if sha256(A_random) == A_commit and sha256(B_random) == B_commit
# calculate winner based on A_random, B_random
checksig(winner_pub, sig)
else
checklocktimeverify(t2)
checksig(bob_pub, sig)
Bob can now construct the final accept_tx
by signing his part of the invitation_tx
multisig and also adding his B_random
as input.
Before signing and broadcasting the transaction he should verify that the A_sig
sent by Alice is valid.
accept_tx =
inputs:
1:
from: invitation_tx
amount: A_bet
sigScript: [ B_random, A_sig, B_sig ]
2:
from: bob_wallet
amount: B_bet
sigScript: bobSig
outputs:
1:
amount: A_bet + B_bet
pubScript: (A_random, B_random, sig) ->
if sha256(A_random) == A_commit and sha256(B_random) == B_commit
# calculate winner based on A_random, B_random
checksig(winner_pub, sig)
else
checklocktimeverify(t2)
checksig(bob_pub, sig)
At this point, Bob goes on and broadcasts accept_tx
to the bitcoin network. By doing so, two things happen. First, Alice learns his B_random
value. Secondly, all the outputs of invitation_tx
are now spent.
When Alice learns B_random
she can figure out if she has lost the bet. However, there is nothing she can do to stop the transaction because time t1
hasn't come yet. Also, even when time t1
comes, it will be too late since invitation_tx
will be already spent.
Redeem phase
If Alice wins, she has until time t2
to redeem her prize, and she has all the information she needs to do so. She knows A_random
from the setup phase, and she knows B_random
by looking at the mined accept_tx
which as broadcasted during the accept phase.
If Alice loses, she can reveal her A_random
to Bob so that he can redeem his price. If Alice fails to cooperate or leaves the game, Bob will eventually get all the money after time t2
has come.
Optimisations
- We can skip directly to
accept_tx_draft2
if Alice pre-signs with a SIGHASH_ANYONECANPAY sigtype. - All
SHA256(x)
operations can be replaced byRIPEMD160(SHA256(x))
to make transactions smaller