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 in accept_tx_draft1's output is the same as B_commit of invitation_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 by RIPEMD160(SHA256(x)) to make transactions smaller