1 of 84

Bitvm workshop

Let’s build an app together

2 of 84

Follow along

This presentation

tinyurl.com/bitvm-workshop

The github: click here�Tx broadcaster: click here�Mutinynet faucet: click here

3 of 84

Lower your expectations

This is just a sneak preview

Bit tac toe isn’t really ready

The code is messy and long

Will be better (I hope) at Advancing Bitcoin

4 of 84

Things you’ll learn

  1. Build tic tac toe�
  2. Evaluate rules�
  3. Transfer state�
  4. Guard tapleaves�
  5. Prove fraud

5 of 84

Things you won’t learn

  • Use existing codebases (they aren’t designed for two player games)�
  • Call any bitvm libraries (none are ready)�
  • Emulate a cpu (no need)

6 of 84

Wish list

I wish a bitvm library existed

I wish it automatically made “User” objects to store every user’s data

I wish it had methods for creating a series of transactions which reveal state and prove fraud

7 of 84

Let’s begin

<!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, user-scalable=no">

</head>

<body>

<h1>Welcome to bit tac toe</h1>

<p><button onclick="init()">Play</button></p>

<div class="tictacbox">

</div>

</body>

</html>

8 of 84

Add the gameboard

<div class="gameboard">

<div class="col1">

<div class="square square1"></div>

<div class="square square4"></div>

<div class="square square7"></div>

</div>

<div class="col2">

<div class="square square2"></div>

<div class="square square5"></div>

<div class="square square8"></div>

</div>

<div class="col3">

<div class="square square3"></div>

<div class="square square6"></div>

<div class="square square9"></div>

</div>

</div>

9 of 84

Make it prettier

...

<meta name="viewport" content="width=device-width, user-scalable=no">

<style>

html,input{line-height:1.25}*{box-sizing:border-box;font-size:1.15rem;font-family:Arial,sans-serif}html{max-width:800px;padding:0;margin:auto}body{margin:3rem 1rem}h1{font-size:2rem}h2{font-size:1.5rem}input{width:100%;height:1.8rem;font-size:1.15rem;border:1px solid grey}.tictacbox{display:flex;width:100%;justify-content:center}.gameboard{display:flex;width:15rem}.square{height:5rem;width:5rem;cursor:pointer;display:flex;justify-content:center;align-items:center}.col2 div{border-right:1px solid #000;border-left:1px solid #000}.square4,.square5,.square6{border-top:1px solid #000;border-bottom:1px solid #000}

</style>

10 of 84

Add dependencies

...

<meta name="viewport" content="width=device-width, user-scalable=no">

<script src="https://unpkg.com/@cmdcode/tapscript@1.4.0"></script>

<script src="https://bundle.run/noble-secp256k1@1.2.14"></script>

<script src="https://unpkg.com/@dashincubator/ripemd160/ripemd160.js"></script>

<script src="https://supertestnet.github.io/bitvm-workshop/combinations.js"></script>

<script>

var $ = document.querySelector.bind( document );

var $$ = document.querySelectorAll.bind( document );

</script>

11 of 84

Combinatorics:

4 choose 2

A B C D

[ A , B ] [ A , C ]

[ A , D ] [ B , C ]

[ B , D ] [ C , D ]

12 of 84

More dependencies

...� var hexToBytes = hex => Uint8Array.from( hex.match( /.{1,2}/g ).map( byte => parseInt( byte, 16 ) ) );

var bytesToHex = bytes => bytes.reduce( ( str, byte ) => str + byte.toString( 16 ).padStart( 2, "0" ), "" );

var rmd160 = s => {

if ( typeof s == "string" ) s = new TextEncoder().encode( s );

var hash = RIPEMD160.create();

hash.update( new Uint8Array( s ) );

return bytesToHex( hash.digest() );

}

var waitASec=num=>new Promise(res=>setTimeout(res,num*1000));

var waitForMove = async () => {

if ( !num ) await waitASec( 1 );

return num || waitForMove();

}

13 of 84

Make board work

<script>

var type = 1;var num;var game_on;

$$( '.square' ).forEach( item => {item.onclick = () => {

if ( !game_on ) return;

var conf = true;

if ( item.innerHTML ) conf = confirm( `This is an illegal move! Your opponent will be able to take your money. Click ok to do it anyway` );

if ( !conf ) return;

var symbol = `<span style="font-size: 200%;">&times;</span>`;

if ( !type ) symbol = `&#9711`;

item.innerHTML = symbol;

type = !type;

var a = item.classList[ 1 ];

num = Number( a.substring( a.length - 1, a.length ) );

}});

</script>

(Put this near the bottom)

14 of 84

Global variables

<script>

var contract = [];

var hashes = [];

var fraud_occurred = false;

var victory_achieved = false;

var alice = {

privkey: "ab".repeat( 32 ),

scripts: [],

trees: [],

preimages: [],

hashes: [],

accept_preimages: [],

accept_hashes: [],

bobs_moves: [],

bobs_squares: [],

bobs_acks: [],

}

...

15 of 84

Global variables

...

alice[ "pubkey" ] = nobleSecp256k1.getPublicKey( alice[ "privkey" ], true ).substring( 2 );

var funding_address = tapscript.Address.p2tr.fromPubKey( alice[ "pubkey" ], 'testnet' );

var alices_preimage;

var alices_hash;

var alices_move;

...

16 of 84

Global variables

var bob = {

privkey: "ba".repeat( 32 ),

scripts: [],

trees: [],

preimages: [],

hashes: [],

accept_preimages: [],

accept_hashes: [],

alices_moves: [],

alices_squares: [],

alices_acks: [],

}

bob[ "pubkey" ] = nobleSecp256k1.getPublicKey( bob[ "privkey" ], true ).substring( 2 );

var bobs_preimage;

var bobs_hash;

var bobs_move;

</script>

17 of 84

Alice’s preimages

<script>

var i; for ( i=0; i<9*5; i++ ) {

var preimage = nobleSecp256k1.utils.randomPrivateKey();

preimage = Array.from( preimage );

preimage.splice( 0, 12 );

preimage = new Uint8Array( preimage );

var hash = rmd160( preimage );

preimage = bytesToHex( preimage );

alice[ "preimages" ].push( preimage );

alice[ "hashes" ].push( hash );

}

</script>

18 of 84

Why 9*5?

19 of 84

Why 9*5?

20 of 84

Why 9*5?

21 of 84

Why 9*5?

22 of 84

Why 9*5?

23 of 84

Why 9*5?

24 of 84

Why 9*5?

25 of 84

Why 9*5?

26 of 84

Why 9*5?

27 of 84

Bob’s preimages

<script>

var i; for ( i=0; i<9*4; i++ ) {

var preimage = nobleSecp256k1.utils.randomPrivateKey();

preimage = Array.from( preimage );

preimage.splice( 0, 12 );

preimage = new Uint8Array( preimage );

var hash = rmd160( preimage );

preimage = bytesToHex( preimage );

bob[ "preimages" ].push( preimage );

bob[ "hashes" ].push( hash );

}

</script>

28 of 84

Alice’s acks

<script>

var i; for ( i=0; i<9*4; i++ ) {

var preimage = nobleSecp256k1.utils.randomPrivateKey();

preimage = Array.from( preimage );

preimage.splice( 0, 12 );

preimage = new Uint8Array( preimage );

var hash = rmd160( preimage );

preimage = bytesToHex( preimage );

alice[ "accept_preimages" ].push( preimage );

alice[ "accept_hashes" ].push( hash );

}

</script>

29 of 84

Bob’s acks

<script>

var i; for ( i=0; i<9*5; i++ ) {

var preimage = nobleSecp256k1.utils.randomPrivateKey();

preimage = Array.from( preimage );

preimage.splice( 0, 12 );

preimage = new Uint8Array( preimage );

var hash = rmd160( preimage );

preimage = bytesToHex( preimage );

bob[ "accept_preimages" ].push( preimage );

bob[ "accept_hashes" ].push( hash );

}

</script>

30 of 84

Console gameboard

<script>

sessionStorage[ "cell_0" ] = `___|`;

sessionStorage[ "cell_1" ] = `___|`;

sessionStorage[ "cell_2" ] = `___`;

sessionStorage[ "cell_3" ] = `___|`;

sessionStorage[ "cell_4" ] = `___|`;

sessionStorage[ "cell_5" ] = `___`;

sessionStorage[ "cell_6" ] = `___|`;

sessionStorage[ "cell_7" ] = `___|`;

sessionStorage[ "cell_8" ] = `___`;

console.log( sessionStorage[ "cell_0" ] + sessionStorage[ "cell_1" ] + sessionStorage[ "cell_2" ] );

console.log( sessionStorage[ "cell_3" ] + sessionStorage[ "cell_4" ] + sessionStorage[ "cell_5" ] );

console.log( sessionStorage[ "cell_6" ] + sessionStorage[ "cell_7" ] + sessionStorage[ "cell_8" ] );

</script>

31 of 84

How taproot works

<script>

var test_scripts = [

[ "OP_RIPEMD160", "2c6eee09b66fb156dfd1954561eef4045c9510b4", "OP_EQUALVERIFY", alice[ "pubkey" ], "OP_CHECKSIG" ],

[ 3, "OP_CHECKSEQUENCEVERIFY", "OP_DROP", bob[ "pubkey" ], "OP_CHECKSIG" ] ];

var test_pubkey = "ab".repeat( 32 );

var test_tree = test_scripts.map( s => tapscript.Tap.encodeScript( s ) );

var [ tpubkey ] = tapscript.Tap.getPubKey( test_pubkey, { tree: test_tree });

var test_address = tapscript.Address.p2tr.fromPubKey( tpubkey, 'testnet' );

var test_txid = prompt( `Please send money to this test address and enter your transaction's txid\n\n${test_address}` );

var test_vout = Number( prompt( `And vout` ) );

var test_amt = Number( prompt( `And amount` ) );

32 of 84

How taproot works

...� var txdata = tapscript.Tx.create({

vin: [{

txid: test_txid,

vout: test_vout,

prevout: {

value: test_amt,

scriptPubKey: tapscript.Address.toScriptPubKey( test_address )

},

}],

vout: [{

value: test_amt - 500,

scriptPubKey: tapscript.Address.toScriptPubKey( "tb1qd28npep0s8frcm3y7dxqajkcy2m40eysplyr9v" )

}]

});

33 of 84

How taproot works

...� var target = tapscript.Tap.encodeScript( test_scripts[ 0 ] );

var sig = tapscript.Signer.taproot.sign( alice[ "privkey" ], txdata, 0, { extension: target });

var [ _, cblock ] = tapscript.Tap.getPubKey( test_pubkey, { tree: test_tree, target });

var preimage = "05";

txdata.vin[0].witness = [sig,preimage,test_scripts[0],cblock]

var txhex = tapscript.Tx.encode( txdata ).hex;

console.log( txhex );

</script>

34 of 84

I am about to show you how we encode a move…but first:

State transfer and tapleaf guards

We can’t let this happen

To prevent it, we require a player’s “win move” to show two “acks” from their opponent, and we need the opponent to “ack” before they can move

35 of 84

Alice’s first move

<script>

var alicesTurn = turn => {

var original_turn = turn;

turn = turn * 9;

// The first two scripts are simple -- one is the 'happy� // path' whereby Alice and Bob cooperate, the other lets

// Bob takes Alice's money if Alice stops playing

var alices_turn_scripts = [

[ 0, alice[ "pubkey" ], "OP_CHECKSIGADD", bob[ "pubkey" ], "OP_CHECKSIGADD", 2, "OP_EQUAL" ],

[ 10, "OP_CHECKSEQUENCEVERIFY", bob[ "pubkey" ], "OP_CHECKSIG" ],

];

36 of 84

Alice’s first move

// this third script is more complex -- it forces Alice� // to reveal one of 9 preimages to represent marking an

// X in one of 9 squares of a tic tac toe board

var i; for ( i=turn; i<turn + 9; i++ ) {

if ( turn === 0 ) {

var initial_tapleaf_preimage = "";

var initial_tapleaf_guard = "9c1185a5c5e9fc54612808977ee8f548b2258d31";

var tapleaf_guard = initial_tapleaf_guard;

var initial_accept_preimage = "";

var initial_accept_guard = "9c1185a5c5e9fc54612808977ee8f548b2258d31";

var accept_guard = initial_accept_guard;

} else {

var tapleaf_guard = bob[ "hashes" ][i-9];

var accept_guard = alice[ "accept_hashes" ][i-9];

}

37 of 84

Alice’s first move

var alices_turn_third_script = [

"OP_RIPEMD160",

tapleaf_guard,

"OP_EQUALVERIFY",

"OP_RIPEMD160",

accept_guard,

"OP_EQUALVERIFY",

"OP_RIPEMD160",

"OP_DUP",

alice[ "hashes" ][ turn ],

"OP_EQUAL",

"OP_SWAP",

];

38 of 84

Alice’s first move

var j; for ( j=turn + 1; j<turn + 9; j++ ) {

alices_turn_third_script.push( ...[

"OP_DUP",

alice[ "hashes" ][ j ],

"OP_EQUAL",

"OP_ROT",

"OP_ADD",

"OP_SWAP",

]);

}

alices_turn_third_script.push( ...[

"OP_DROP",

"OP_VERIFY",

0, alice[ "pubkey" ], "OP_CHECKSIGADD", bob[ "pubkey" ], "OP_CHECKSIGADD", 2, "OP_EQUAL",

]);

alices_turn_scripts.push( alices_turn_third_script );

}

39 of 84

Yay Alice can move!

Her tree has 11 leaves:

  • the happy path
  • Abandonment
  • 9 “pick a square” paths

�But on each turn she also has to be able to prove fraud or victory

40 of 84

Alice proves fraud

var i; for ( i=1; i<5; i++ ) {

var combinations = [];

var hashes = [ ...bob[ "hashes" ] ];

hashes = hashes.splice( ( 9 * i ) - 9, 9 );

for ( var comb of G.combination( hashes, 2 ) ) combinations.push( Array.from( comb ) );

combinations.forEach( combination => {

alices_turn_scripts.push([

"OP_RIPEMD160",

"OP_SWAP",

"OP_RIPEMD160",

combination[ 0 ], "OP_EQUALVERIFY",

combination[ 1 ], "OP_EQUALVERIFY",

alice[ "pubkey" ],

"OP_CHECKSIG"

]);

});

}

This is for a double-move fraud

41 of 84

Alice proves fraud

var i; for ( i=0; i<9; i++ ) {

var combinations = [];var hashes = [];

hashes.push( bob[ "hashes" ][ i ], bob[ "hashes" ][ i + 9 ], bob[ "hashes" ][ i + 18 ], bob[ "hashes" ][ i + 27 ] );

for ( var comb of G.combination( hashes, 2 ) ) combinations.push( Array.from( comb ) );

combinations.forEach( combination => {

alices_turn_scripts.push([

"OP_RIPEMD160",

"OP_SWAP",

"OP_RIPEMD160",

combination[ 0 ], "OP_EQUALVERIFY",

combination[ 1 ], "OP_EQUALVERIFY",

alice[ "pubkey" ],

"OP_CHECKSIG"

]);

});

}

This is for skip-a-turn fraud

42 of 84

Alice proves fraud

var accept_hashes = [ ...bob[ "accept_hashes" ] ];

var reveal_hashes = [ ...bob[ "hashes" ] ];

accept_hashes.forEach( ( item, index ) => {

var i; for ( i=0; i<4; i++ ) {

if ( i==0 ) var hashnum = index % 9;

if ( i==1 ) var hashnum = ( index % 9 ) + 9;

if ( i==2 ) var hashnum = ( index % 9 ) + 18;

if ( i==3 ) var hashnum = ( index % 9 ) + 27;

alices_turn_scripts.push([

"OP_RIPEMD160",

"OP_SWAP",

"OP_RIPEMD160",

item, "OP_EQUALVERIFY",

reveal_hashes[ hashnum ], "OP_EQUALVERIFY",

alice[ "pubkey" ], "OP_CHECKSIG"

]);

}

});

This is for overwrite fraud

43 of 84

Alice claims victory

var combinations = [];

var i; for ( i=0; i<9; i++ ) {

/*row1*/ if ( i == 0 ) var hashes = [0,9,18,27,1,10,19,28];

if ( i == 1 ) var hashes = [0,9,18,27,2,11,20,29];

if ( i == 2 ) var hashes = [1,10,19,28,2,11,20,29];

/*row2*/ if ( i == 3 ) var hashes = [3,12,21,30,4,13,22,31];

if ( i == 4 ) var hashes = [3,12,21,30,5,14,23,32];

if ( i == 5 ) var hashes = [4,13,22,31,5,14,23,32];

/*row3*/ if ( i == 6 ) var hashes = [6,15,24,33,7,16,25,34];

if ( i == 7 ) var hashes = [6,15,24,33,8,17,26,35];

if ( i == 8 ) var hashes = [7,16,25,34,8,17,26,35];

for ( var comb of G.combination( hashes, 2 ) ) {var comb2 = JSON.parse( JSON.stringify( comb ) ); comb2.sort((a,b)=>a-b).reverse(); if ( ( comb2[ 0 ] - comb2[ 1 ] ) % 9 && comb2[ 0 ] - comb2[ 1 ] >= 3 ) combinations.push( Array.from( comb ) );}

}

44 of 84

Why 9 ways to win?

45 of 84

Alice claims victory

combinations.forEach( ( item, index ) => {

combinations.splice( index * 5, 1, ...JSON.parse( JSON.stringify( [combinations[ index * 5 ]].flatMap(i => Array(5).fill(i))) ) );

});

46 of 84

😅😅😅😅😅😅😅😅😅

combinations.forEach( ( item, index ) => {

if ( item.length > 2 ) return;

if ( index < 180 ) {if ( item[ 0 ] % 3 == 0 && item[ 1 ] % 3 == 1 || item[ 1 ] % 3 == 0 && item[ 0 ] % 3 == 1 ) {if ( index % 5 == 0 ) item.push( 2 );if ( index % 5 == 1 ) item.push( 11 );if ( index % 5 == 2 ) item.push( 20 );if ( index % 5 == 3 ) item.push( 29 );if ( index % 5 == 4 ) item.push( 38 );}

if ( item[ 0 ] % 3 == 0 && item[ 1 ] % 3 == 2 || item[ 1 ] % 3 == 0 && item[ 0 ] % 3 == 2 ) {if ( index % 5 == 0 ) item.push( 1 );if ( index % 5 == 1 ) item.push( 10 );if ( index % 5 == 2 ) item.push( 19 );if ( index % 5 == 3 ) item.push( 28 );if ( index % 5 == 4 ) item.push( 37 );}

if ( item[ 0 ] % 3 == 1 && item[ 1 ] % 3 == 2 || item[ 1 ] % 3 == 1 && item[ 0 ] % 3 == 2 ) {if ( index % 5 == 0 ) item.push( 0 );if ( index % 5 == 1 ) item.push( 9 );if ( index % 5 == 2 ) item.push( 18 );if ( index % 5 == 3 ) item.push( 27 );if ( index % 5 == 4 ) item.push( 36 );}

}

47 of 84

😅😅😅😅😅😅😅😅😅

if ( index >= 180 && index < 360 ) {

if ( item[ 0 ] % 3 == 0 && item[ 1 ] % 3 == 1 || item[ 1 ] % 3 == 0 && item[ 0 ] % 3 == 1 ) {if ( index % 5 == 0 ) item.push( 5 );if ( index % 5 == 1 ) item.push( 14 );if ( index % 5 == 2 ) item.push( 23 );if ( index % 5 == 3 ) item.push( 32 );if ( index % 5 == 4 ) item.push( 41 );}

if ( item[ 0 ] % 3 == 0 && item[ 1 ] % 3 == 2 || item[ 1 ] % 3 == 0 && item[ 0 ] % 3 == 2 ) {if ( index % 5 == 0 ) item.push( 4 );if ( index % 5 == 1 ) item.push( 13 );if ( index % 5 == 2 ) item.push( 22 );if ( index % 5 == 3 ) item.push( 31 );if ( index % 5 == 4 ) item.push( 40 );}

if ( item[ 0 ] % 3 == 1 && item[ 1 ] % 3 == 2 || item[ 1 ] % 3 == 1 && item[ 0 ] % 3 == 2 ) {if ( index % 5 == 0 ) item.push( 3 );if ( index % 5 == 1 ) item.push( 12 );if ( index % 5 == 2 ) item.push( 21 );if ( index % 5 == 3 ) item.push( 30 );if ( index % 5 == 4 ) item.push( 39 );}

}

48 of 84

😅😅😅😅😅😅😅😅😅

if ( index >= 360 && index < 540 ) {if ( item[ 0 ] % 3 == 0 && item[ 1 ] % 3 == 1 || item[ 1 ] % 3 == 0 && item[ 0 ] % 3 == 1 ) {if ( index % 5 == 0 ) item.push( 8 );if ( index % 5 == 1 ) item.push( 17 );if ( index % 5 == 2 ) item.push( 26 );if ( index % 5 == 3 ) item.push( 35 );if ( index % 5 == 4 ) item.push( 44 );}

if ( item[ 0 ] % 3 == 0 && item[ 1 ] % 3 == 2 || item[ 1 ] % 3 == 0 && item[ 0 ] % 3 == 2 ) {if ( index % 5 == 0 ) item.push( 7 );if ( index % 5 == 1 ) item.push( 16 );if ( index % 5 == 2 ) item.push( 25 );if ( index % 5 == 3 ) item.push( 34 );if ( index % 5 == 4 ) item.push( 43 );}

if ( item[ 0 ] % 3 == 1 && item[ 1 ] % 3 == 2 || item[ 1 ] % 3 == 1 && item[ 0 ] % 3 == 2 ) {if ( index % 5 == 0 ) item.push( 6 );if ( index % 5 == 1 ) item.push( 15 );if ( index % 5 == 2 ) item.push( 24 );if ( index % 5 == 3 ) item.push( 33 );if ( index % 5 == 4 ) item.push( 42 );}

}

});

49 of 84

Alice claims victory

combinations.forEach( combination => {

alices_turn_scripts.push([

"OP_RIPEMD160", "OP_SWAP", "OP_RIPEMD160",

2, "OP_ROLL",

"OP_RIPEMD160",

"OP_SWAP",

2, "OP_ROLL",

alice[ "hashes" ][ combination[ 2 ] ],

"OP_EQUALVERIFY",

bob[ "accept_hashes" ][ combination[ 1 ] ],

"OP_EQUALVERIFY",

bob[ "accept_hashes" ][ combination[ 0 ] ],

"OP_EQUALVERIFY",

//TODO: turn this into a multisig

alice[ "pubkey" ], "OP_CHECKSIG"

]);

});

50 of 84

End of Alice’s turn

alice[ "trees" ].push( alices_turn_scripts.map( s => tapscript.Tap.encodeScript( s ) ) );

alice[ "scripts" ].push( alices_turn_scripts );

var pubkey = "ab".repeat( 32 );

var [ tpubkey ] = tapscript.Tap.getPubKey( pubkey, { tree: alice[ "trees" ][ original_turn ] });

var alices_turn_address = tapscript.Address.p2tr.fromPubKey( tpubkey, 'testnet' );

return alices_turn_address;

}

</script>

var alices_first_turn_address = alicesTurn( 0 );

console.log( alices_first_turn_address );

51 of 84

Bob’s turn

<script>

var bobsTurn = turn => {

var original_turn = turn;

turn = turn * 9;

var bobs_turn_scripts = [

[ 0, alice[ "pubkey" ], "OP_CHECKSIGADD", bob[ "pubkey" ], "OP_CHECKSIGADD", 2, "OP_EQUAL" ],

[ 10, "OP_CHECKSEQUENCEVERIFY", alice[ "pubkey" ], "OP_CHECKSIG" ],

];

var i; for ( i=turn; i<turn + 9; i++ ) {

var tapleaf_guard = alice[ "hashes" ][ i ];

var accept_guard = bob[ "accept_hashes" ][ i ];

var bobs_turn_third_script = [

"OP_RIPEMD160",

tapleaf_guard,

"OP_EQUALVERIFY",

"OP_RIPEMD160",

accept_guard,

"OP_EQUALVERIFY",

"OP_RIPEMD160",

"OP_DUP",

bob[ "hashes" ][ turn ],

"OP_EQUAL",

"OP_SWAP",

];

var j; for ( j=turn + 1; j<turn + 9; j++ ) {

bobs_turn_third_script.push( ...[

"OP_DUP",

bob[ "hashes"][ j ],

"OP_EQUAL",

"OP_ROT",

"OP_ADD",

"OP_SWAP",

]);

}

bobs_turn_third_script.push( ...[

"OP_DROP",

"OP_VERIFY",

0,

alice[ "pubkey" ],

"OP_CHECKSIGADD",

bob[ "pubkey" ],

"OP_CHECKSIGADD",

2,

"OP_EQUAL",

]);

bobs_turn_scripts.push( bobs_turn_third_script );

}

bob[ "scripts" ].push( bobs_turn_scripts );

//allow Bob to prove fraud if Alice reveals two preimages for any of her turns

var i; for ( i=1; i<6; i++ ) {

var combinations = [];

var hashes = [ ...alice[ "hashes" ] ];

hashes = hashes.splice( ( 9 * i ) - 9, 9 );

for ( var comb of G.combination( hashes, 2 ) ) combinations.push( Array.from( comb ) );

combinations.forEach( combination => {

bobs_turn_scripts.push([

"OP_RIPEMD160",

"OP_SWAP",

"OP_RIPEMD160",

combination[ 0 ],

"OP_EQUALVERIFY",

combination[ 1 ],

"OP_EQUALVERIFY",

bob[ "pubkey" ],

"OP_CHECKSIG"

]);

});

}

var i; for ( i=0; i<9; i++ ) {

var combinations = [];

var hashes = [];

hashes.push( alice[ "hashes" ][ i ], alice[ "hashes" ][ i + 9 ], alice[ "hashes" ][ i + 18 ], alice[ "hashes" ][ i + 27 ], alice[ "hashes" ][ i + 36 ] );

for ( var comb of G.combination( hashes, 2 ) ) combinations.push( Array.from( comb ) );

combinations.forEach( combination => {

bobs_turn_scripts.push([

"OP_RIPEMD160",

"OP_SWAP",

"OP_RIPEMD160",

combination[ 0 ],

"OP_EQUALVERIFY",

combination[ 1 ],

"OP_EQUALVERIFY",

bob[ "pubkey" ],

"OP_CHECKSIG"

]);

});

}

var accept_hashes = [ ...alice[ "accept_hashes" ] ];

var reveal_hashes = [ ...alice[ "hashes" ] ];

accept_hashes.forEach( ( item, index ) => {

bobs_turn_scripts.push([

"OP_RIPEMD160",

"OP_SWAP",

"OP_RIPEMD160",

item,

"OP_EQUALVERIFY",

reveal_hashes[ index % 9 ],

"OP_EQUALVERIFY",

bob[ "pubkey" ],

"OP_CHECKSIG"

]);

bobs_turn_scripts.push([

"OP_RIPEMD160",

"OP_SWAP",

"OP_RIPEMD160",

item,

"OP_EQUALVERIFY",

reveal_hashes[ ( index % 9 ) + 9 ],

"OP_EQUALVERIFY",

bob[ "pubkey" ],

"OP_CHECKSIG"

]);

bobs_turn_scripts.push([

"OP_RIPEMD160",

"OP_SWAP",

"OP_RIPEMD160",

item,

"OP_EQUALVERIFY",

reveal_hashes[ ( index % 9 ) + 9 + 9 ],

"OP_EQUALVERIFY",

bob[ "pubkey" ],

"OP_CHECKSIG"

]);

bobs_turn_scripts.push([

"OP_RIPEMD160",

"OP_SWAP",

"OP_RIPEMD160",

item,

"OP_EQUALVERIFY",

reveal_hashes[ ( index % 9 ) + 9 + 9 + 9 ],

"OP_EQUALVERIFY",

bob[ "pubkey" ],

"OP_CHECKSIG"

]);

bobs_turn_scripts.push([

"OP_RIPEMD160",

"OP_SWAP",

"OP_RIPEMD160",

item,

"OP_EQUALVERIFY",

reveal_hashes[ ( index % 9 ) + 9 + 9 + 9 + 9 ],

"OP_EQUALVERIFY",

bob[ "pubkey" ],

"OP_CHECKSIG"

]);

});

var combinations = [];

var i; for ( i=0; i<9; i++ ) {

//row 1

if ( i == 0 ) var hashes = [0, 9, 18, 27, 1, 10, 19, 28];

if ( i == 1 ) var hashes = [0, 9, 18, 27, 2, 11, 20, 29];

if ( i == 2 ) var hashes = [1, 10, 19, 28, 2, 11, 20, 29];

//row 2

if ( i == 3 ) var hashes = [3, 12, 21, 30, 4, 13, 22, 31];

if ( i == 4 ) var hashes = [3, 12, 21, 30, 5, 14, 23, 32];

if ( i == 5 ) var hashes = [4, 13, 22, 31, 5, 14, 23, 32];

//row 3

if ( i == 6 ) var hashes = [6, 15, 24, 33, 7, 16, 25, 34];

if ( i == 7 ) var hashes = [6, 15, 24, 33, 8, 17, 26, 35];

if ( i == 8 ) var hashes = [7, 16, 25, 34, 8, 17, 26, 35];

for ( var comb of G.combination( hashes, 2 ) ) {var comb2 = JSON.parse( JSON.stringify( comb ) ); comb2.sort((a,b)=>a-b).reverse(); if ( ( comb2[ 0 ] - comb2[ 1 ] ) % 9 && comb2[ 0 ] - comb2[ 1 ] >= 3 ) combinations.push( Array.from( comb ) );}

}

combinations.forEach( ( item, index ) => {

combinations.splice( index * 4, 1, ...JSON.parse( JSON.stringify( [combinations[ index * 4 ]].flatMap(i => Array(4).fill(i))) ) );

});

combinations.forEach( ( item, index ) => {

if ( item.length > 2 ) return;

//row 1

//Alice had 180 here because there were originally 108 combinations and I multiplied them by 5 for each of her 5 turns. Then I divided the result, 540, by 3 to work on rows 1, 2, and 3. But Bob only has 4 turns so he only gets 108*4 = 432 combinations, meaning 180 for him should be 432/3 = 144

if ( index < 144 ) {

if ( item[ 0 ] % 3 == 0 && item[ 1 ] % 3 == 1 || item[ 1 ] % 3 == 0 && item[ 0 ] % 3 == 1 ) {

if ( index % 4 == 0 ) item.push( 2 );

if ( index % 4 == 1 ) item.push( 11 );

if ( index % 4 == 2 ) item.push( 20 );

if ( index % 4 == 3 ) item.push( 29 );

}

if ( item[ 0 ] % 3 == 0 && item[ 1 ] % 3 == 2 || item[ 1 ] % 3 == 0 && item[ 0 ] % 3 == 2 ) {

if ( index % 4 == 0 ) item.push( 1 );

if ( index % 4 == 1 ) item.push( 10 );

if ( index % 4 == 2 ) item.push( 19 );

if ( index % 4 == 3 ) item.push( 28 );

}

if ( item[ 0 ] % 3 == 1 && item[ 1 ] % 3 == 2 || item[ 1 ] % 3 == 1 && item[ 0 ] % 3 == 2 ) {

if ( index % 4 == 0 ) item.push( 0 );

if ( index % 4 == 1 ) item.push( 9 );

if ( index % 4 == 2 ) item.push( 18 );

if ( index % 4 == 3 ) item.push( 27 );

}

}

//row 2

if ( index >= 144 && index < 288 ) {

if ( item[ 0 ] % 3 == 0 && item[ 1 ] % 3 == 1 || item[ 1 ] % 3 == 0 && item[ 0 ] % 3 == 1 ) {

if ( index % 4 == 0 ) item.push( 5 );

if ( index % 4 == 1 ) item.push( 14 );

if ( index % 4 == 2 ) item.push( 23 );

if ( index % 4 == 3 ) item.push( 32 );

}

if ( item[ 0 ] % 3 == 0 && item[ 1 ] % 3 == 2 || item[ 1 ] % 3 == 0 && item[ 0 ] % 3 == 2 ) {

if ( index % 4 == 0 ) item.push( 4 );

if ( index % 4 == 1 ) item.push( 13 );

if ( index % 4 == 2 ) item.push( 22 );

if ( index % 4 == 3 ) item.push( 31 );

}

if ( item[ 0 ] % 3 == 1 && item[ 1 ] % 3 == 2 || item[ 1 ] % 3 == 1 && item[ 0 ] % 3 == 2 ) {

if ( index % 4 == 0 ) item.push( 3 );

if ( index % 4 == 1 ) item.push( 12 );

if ( index % 4 == 2 ) item.push( 21 );

if ( index % 4 == 3 ) item.push( 30 );

}

}

//row 3

if ( index >= 288 && index < 432 ) {

if ( item[ 0 ] % 3 == 0 && item[ 1 ] % 3 == 1 || item[ 1 ] % 3 == 0 && item[ 0 ] % 3 == 1 ) {

if ( index % 4 == 0 ) item.push( 8 );

if ( index % 4 == 1 ) item.push( 17 );

if ( index % 4 == 2 ) item.push( 26 );

if ( index % 4 == 3 ) item.push( 35 );

}

if ( item[ 0 ] % 3 == 0 && item[ 1 ] % 3 == 2 || item[ 1 ] % 3 == 0 && item[ 0 ] % 3 == 2 ) {

if ( index % 4 == 0 ) item.push( 7 );

if ( index % 4 == 1 ) item.push( 16 );

if ( index % 4 == 2 ) item.push( 25 );

if ( index % 4 == 3 ) item.push( 34 );

}

if ( item[ 0 ] % 3 == 1 && item[ 1 ] % 3 == 2 || item[ 1 ] % 3 == 1 && item[ 0 ] % 3 == 2 ) {

if ( index % 4 == 0 ) item.push( 6 );

if ( index % 4 == 1 ) item.push( 15 );

if ( index % 4 == 2 ) item.push( 24 );

if ( index % 4 == 3 ) item.push( 33 );

}

}

});

combinations.forEach( combination => {

bobs_turn_scripts.push([

"OP_RIPEMD160",

"OP_SWAP",

"OP_RIPEMD160",

2,

"OP_ROLL",

"OP_RIPEMD160",

"OP_SWAP",

2,

"OP_ROLL",

bob[ "hashes" ][ combination[ 2 ] ],

"OP_EQUALVERIFY",

alice[ "accept_hashes" ][ combination[ 1 ] ],

"OP_EQUALVERIFY",

alice[ "accept_hashes" ][ combination[ 0 ] ],

"OP_EQUALVERIFY",

//todo: ensure this is a multisig path that

//sends the money to an address where Bob

//has an opportunity to prove alice cheated

bob[ "pubkey" ],

"OP_CHECKSIG"

]);

});

bob[ "trees" ].push( bobs_turn_scripts.map( s => tapscript.Tap.encodeScript( s ) ) );

var pubkey = "ab".repeat( 32 );

var [ tpubkey ] = tapscript.Tap.getPubKey( pubkey, { tree: bob[ "trees" ][ original_turn ] });

var bobs_turn_address = tapscript.Address.p2tr.fromPubKey( tpubkey, 'testnet' );

return bobs_turn_address;

}

</script>

52 of 84

More fraud proof

<script>

var proveDoubleTurn = ( p1, p2, opponent, self ) => {

var h1 = rmd160( hexToBytes( p1 ) );

var h2 = rmd160( hexToBytes( p2 ) );

var combinations = [];

var i; for ( i=1; i<5; i++ ) {

var hashes = [ ...opponent[ "hashes" ] ];

hashes = hashes.splice( ( 9 * i ) - 9, 9 );

for ( var comb of G.combination( hashes, 2 ) ) combinations.push( Array.from( comb ) );

}

var possibles = [];

combinations.forEach( item => {

if ( item.includes( h1 ) ) possibles.push( item );

});

var actual;

53 of 84

More fraud proof

possibles.forEach( item => {

if ( item.includes( h2 ) ) actual = item;

});

if ( !actual ) return;

fraud_occurred = true;

var script = [

"OP_RIPEMD160",

"OP_SWAP",

"OP_RIPEMD160",

actual[ 0 ], "OP_EQUALVERIFY",

actual[ 1 ], "OP_EQUALVERIFY",

self[ "pubkey" ], "OP_CHECKSIG"

];

var target = tapscript.Tap.encodeScript( script );

return [ target, script ];

}

</script>

54 of 84

More fraud proof

var proveOverwriteSelf = ( p1, p2, opponent, self ) => {

var h1 = rmd160( hexToBytes( p1 ) );

var h2 = rmd160( hexToBytes( p2 ) );

var actual;

var i; for ( i=0; i<9; i++ ) {

var combinations = [];var hashes = [];

hashes.push( opponent[ "hashes" ][ i ], opponent[ "hashes" ][ i + 9 ], opponent[ "hashes" ][ i + 18 ], opponent[ "hashes" ][ i + 27 ] );

for ( var comb of G.combination( hashes, 2 ) ) combinations.push( Array.from( comb ) );

var possibles = [];

combinations.forEach( item => {if ( item.includes( h1 ) ) possibles.push( item );});

possibles.forEach( item => {if ( item.includes( h2 ) ) actual = item;});

}

if ( !actual ) return;fraud_occurred = true;

55 of 84

More fraud proof

var script = [

"OP_RIPEMD160",

"OP_SWAP",

"OP_RIPEMD160",

actual[ 0 ],

"OP_EQUALVERIFY",

actual[ 1 ],

"OP_EQUALVERIFY",

self[ "pubkey" ],

"OP_CHECKSIG"

];

var target = tapscript.Tap.encodeScript( script );

return [ target, script ];

}

56 of 84

More fraud proof

<script>

var proveOverwriteOpponent = ( p1, p2, opponent, self ) => {

var h1 = rmd160( hexToBytes( p1 ) );

var h2 = rmd160( hexToBytes( p2 ) );

var actual;

var accept_hashes = [ ...opponent[ "accept_hashes" ] ];

var reveal_hashes = [ ...opponent[ "hashes" ] ];

57 of 84

More fraud proof

accept_hashes.forEach( ( item, index ) => {

if ( ( item == h1 || item == h2 ) && ( reveal_hashes[ index % 9 ] == h1 || reveal_hashes[ index % 9 ] == h2 ) ) actual = [ item, reveal_hashes[index%9] ];

if ( ( item == h1 || item == h2 ) && ( reveal_hashes[ ( index % 9 ) + 9 ] == h1 || reveal_hashes[ ( index % 9 ) + 9 ] == h2 ) ) actual = [ item, reveal_hashes[(index%9) + 9 ] ];

if ( ( item == h1 || item == h2 ) && ( reveal_hashes[ ( index % 9 ) + 18 ] == h1 || reveal_hashes[ ( index % 9 ) + 9 + 9 ] == h2 ) ) actual = [ item, reveal_hashes[(index%9) + 18 ] ];

if ( ( item == h1 || item == h2 ) && ( reveal_hashes[ ( index % 9 ) + 27 ] == h1 || reveal_hashes[ ( index % 9 ) + 9 + 9 + 9 ] == h2 ) ) actual = [ item, reveal_hashes[(index%9) + 27 ] ];

});

if ( !actual ) return;� fraud_occurred = true;

58 of 84

More fraud proof

var script = [

"OP_RIPEMD160",

"OP_SWAP",

"OP_RIPEMD160",

actual[ 0 ],

"OP_EQUALVERIFY",

actual[ 1 ],

"OP_EQUALVERIFY",

self[ "pubkey" ],

"OP_CHECKSIG"

];

var target = tapscript.Tap.encodeScript( script );

return [ target, script ];

}

</script>

59 of 84

More victory proof

<script>

var proveVictory = ( p1, p2, p3, opponent, self ) => {

var h1 = rmd160( hexToBytes( p1 ) );

var h2 = rmd160( hexToBytes( p2 ) );

var h3 = rmd160( hexToBytes( p3 ) );

var actual;

var combinations = [];

var i; for ( i=0; i<9; i++ ) {

//row 1

if ( i == 0 ) var hashes = [0, 9, 18, 27, 1, 10, 19, 28];

if ( i == 1 ) var hashes = [0, 9, 18, 27, 2, 11, 20, 29];

if ( i == 2 ) var hashes = [1, 10, 19, 28, 2, 11, 20, 29];

//row 2

if ( i == 3 ) var hashes = [3, 12, 21, 30, 4, 13, 22, 31];

if ( i == 4 ) var hashes = [3, 12, 21, 30, 5, 14, 23, 32];

if ( i == 5 ) var hashes = [4, 13, 22, 31, 5, 14, 23, 32];

//row 3

if ( i == 6 ) var hashes = [6, 15, 24, 33, 7, 16, 25, 34];

if ( i == 7 ) var hashes = [6, 15, 24, 33, 8, 17, 26, 35];

if ( i == 8 ) var hashes = [7, 16, 25, 34, 8, 17, 26, 35];

for ( var comb of G.combination( hashes, 2 ) ) {var comb2 = JSON.parse( JSON.stringify( comb ) ); comb2.sort((a,b)=>a-b).reverse(); if ( ( comb2[ 0 ] - comb2[ 1 ] ) % 9 && comb2[ 0 ] - comb2[ 1 ] >= 3 ) combinations.push( Array.from( comb ) );}

}

combinations.forEach( ( item, index ) => {

combinations.splice( index * 5, 1, ...JSON.parse( JSON.stringify( [combinations[ index * 5 ]].flatMap(i => Array(5).fill(i))) ) );

});

combinations.forEach( ( item, index ) => {

if ( item.length > 2 ) return;

//row 1

if ( index < 180 ) {

if ( item[ 0 ] % 3 == 0 && item[ 1 ] % 3 == 1 || item[ 1 ] % 3 == 0 && item[ 0 ] % 3 == 1 ) {

if ( index % 5 == 0 ) item.push( 2 );

if ( index % 5 == 1 ) item.push( 11 );

if ( index % 5 == 2 ) item.push( 20 );

if ( index % 5 == 3 ) item.push( 29 );

if ( index % 5 == 4 ) item.push( 38 );

}

if ( item[ 0 ] % 3 == 0 && item[ 1 ] % 3 == 2 || item[ 1 ] % 3 == 0 && item[ 0 ] % 3 == 2 ) {

if ( index % 5 == 0 ) item.push( 1 );

if ( index % 5 == 1 ) item.push( 10 );

if ( index % 5 == 2 ) item.push( 19 );

if ( index % 5 == 3 ) item.push( 28 );

if ( index % 5 == 4 ) item.push( 37 );

}

if ( item[ 0 ] % 3 == 1 && item[ 1 ] % 3 == 2 || item[ 1 ] % 3 == 1 && item[ 0 ] % 3 == 2 ) {

if ( index % 5 == 0 ) item.push( 0 );

if ( index % 5 == 1 ) item.push( 9 );

if ( index % 5 == 2 ) item.push( 18 );

if ( index % 5 == 3 ) item.push( 27 );

if ( index % 5 == 4 ) item.push( 36 );

}

}

//row 2

if ( index >= 180 && index < 360 ) {

if ( item[ 0 ] % 3 == 0 && item[ 1 ] % 3 == 1 || item[ 1 ] % 3 == 0 && item[ 0 ] % 3 == 1 ) {

if ( index % 5 == 0 ) item.push( 5 );

if ( index % 5 == 1 ) item.push( 14 );

if ( index % 5 == 2 ) item.push( 23 );

if ( index % 5 == 3 ) item.push( 32 );

if ( index % 5 == 4 ) item.push( 41 );

}

if ( item[ 0 ] % 3 == 0 && item[ 1 ] % 3 == 2 || item[ 1 ] % 3 == 0 && item[ 0 ] % 3 == 2 ) {

if ( index % 5 == 0 ) item.push( 4 );

if ( index % 5 == 1 ) item.push( 13 );

if ( index % 5 == 2 ) item.push( 22 );

if ( index % 5 == 3 ) item.push( 31 );

if ( index % 5 == 4 ) item.push( 40 );

}

if ( item[ 0 ] % 3 == 1 && item[ 1 ] % 3 == 2 || item[ 1 ] % 3 == 1 && item[ 0 ] % 3 == 2 ) {

if ( index % 5 == 0 ) item.push( 3 );

if ( index % 5 == 1 ) item.push( 12 );

if ( index % 5 == 2 ) item.push( 21 );

if ( index % 5 == 3 ) item.push( 30 );

if ( index % 5 == 4 ) item.push( 39 );

}

}

//row 3

if ( index >= 360 && index < 540 ) {

if ( item[ 0 ] % 3 == 0 && item[ 1 ] % 3 == 1 || item[ 1 ] % 3 == 0 && item[ 0 ] % 3 == 1 ) {

if ( index % 5 == 0 ) item.push( 8 );

if ( index % 5 == 1 ) item.push( 17 );

if ( index % 5 == 2 ) item.push( 26 );

if ( index % 5 == 3 ) item.push( 35 );

if ( index % 5 == 4 ) item.push( 44 );

}

if ( item[ 0 ] % 3 == 0 && item[ 1 ] % 3 == 2 || item[ 1 ] % 3 == 0 && item[ 0 ] % 3 == 2 ) {

if ( index % 5 == 0 ) item.push( 7 );

if ( index % 5 == 1 ) item.push( 16 );

if ( index % 5 == 2 ) item.push( 25 );

if ( index % 5 == 3 ) item.push( 34 );

if ( index % 5 == 4 ) item.push( 43 );

}

if ( item[ 0 ] % 3 == 1 && item[ 1 ] % 3 == 2 || item[ 1 ] % 3 == 1 && item[ 0 ] % 3 == 2 ) {

if ( index % 5 == 0 ) item.push( 6 );

if ( index % 5 == 1 ) item.push( 15 );

if ( index % 5 == 2 ) item.push( 24 );

if ( index % 5 == 3 ) item.push( 33 );

if ( index % 5 == 4 ) item.push( 42 );

}

}

});

combinations.forEach( ( item, index ) => {

combinations[ index ][ 0 ] = opponent[ "accept_hashes" ][ item[ 0 ] ];

combinations[ index ][ 1 ] = opponent[ "accept_hashes" ][ item[ 1 ] ];

combinations[ index ][ 2 ] = self[ "hashes" ][ item[ 2 ] ];

});

var possibles = [];

combinations.forEach( item => {

if ( item.includes( h1 ) && item.includes( h2 ) ) possibles.push( item );

});

possibles.forEach( item => {

if ( item.includes( h3 ) ) actual = item;

});

if ( !actual ) return;

actual.reverse();

victory_achieved = true;

var script = [

"OP_RIPEMD160",

"OP_SWAP",

"OP_RIPEMD160",

2,

"OP_ROLL",

"OP_RIPEMD160",

"OP_SWAP",

2,

"OP_ROLL",

actual[ 0 ],

"OP_EQUALVERIFY",

actual[ 1 ],

"OP_EQUALVERIFY",

actual[ 2 ],

"OP_EQUALVERIFY",

//todo: ensure this is a multisig path that

//sends the money to an address where Bob

//has an opportunity to prove alice cheated

self[ "pubkey" ],

"OP_CHECKSIG"

];

var target = tapscript.Tap.encodeScript( script );

return [ target, script ];

}

</script>

60 of 84

Check for fraud 1

<script>

var aliceChecksFraud = ( step, turn ) => {

var bobs_preimage_2;

// bobs_preimage_2 = prompt( `Enter bob's second preimage` );

if ( !bobs_preimage_2 ) bobs_preimage_2 = "ab";

var is_double_turn = proveDoubleTurn( alice[ "bobs_moves" ][ turn - 1 ], bobs_preimage_2, bob, alice );

if ( is_double_turn ) {

61 of 84

Check for fraud 1

var txdata = tapscript.Tx.create({

vin: [{

txid: contract[ step ][ "txid" ],

vout: contract[ step ][ "vout" ],

prevout: {

value: contract[ step ][ "amt" ],

scriptPubKey: tapscript.Address.toScriptPubKey( contract[ step ][ "funding_address" ] )

},

}],

vout: [{

value: contract[ step ][ "amt" ] - 500,

scriptPubKey: tapscript.Address.toScriptPubKey( "tb1qd28npep0s8frcm3y7dxqajkcy2m40eysplyr9v" )

}]

});

62 of 84

Check for fraud 1

var target = is_double_turn[ 0 ];

var script = is_double_turn[ 1 ];

var sig = tapscript.Signer.taproot.sign( alice[ "privkey" ], txdata, 0, { extension: target });

var [ tpubkey, cblock ] = tapscript.Tap.getPubKey( "ab".repeat( 32 ), { tree: alice[ "trees" ][ turn ], target });

var bobs_preimage_1 = alice[ "bobs_moves" ][turn-1];

txdata.vin[ 0 ].witness = [ sig, bobs_preimage_1, bobs_preimage_2, script, cblock ];

var txhex = tapscript.Tx.encode( txdata ).hex;

console.log( "broadcast this to take bob's money:", txhex );

txdata.vin[ 0 ].witness = [ sig, bobs_preimage_2, bobs_preimage_1, script, cblock ];

var txhex = tapscript.Tx.encode( txdata ).hex;

console.log( "or this:", txhex );

}

63 of 84

Check for fraud 2

var hash_of_bobs_preimage = rmd160( hexToBytes( alice[ "bobs_moves" ][ turn - 1 ] ) );

var index_of_bobs_preimage = bob[ "hashes" ].indexOf( hash_of_bobs_preimage );

var square_bobs_preimage_belongs_to = index_of_bobs_preimage % 9;

if ( !alice[ "bobs_squares" ][ square_bobs_preimage_belongs_to ] ) {

alice[ "bobs_squares" ][ square_bobs_preimage_belongs_to ] = alice["bobs_moves"][turn-1];

} else {

var bobs_preimage_1 = alice["bobs_moves"][turn-1];

var bobs_preimage_2 = alice["bobs_squares"][ square_bobs_preimage_belongs_to ];

var overwrites_self = proveOverwriteSelf( bobs_preimage_1, bobs_preimage_2, bob, alice );

if ( overwrites_self ) {

64 of 84

Check for fraud 2

var txdata = tapscript.Tx.create({

vin: [{

txid: contract[ step ][ "txid" ],

vout: contract[ step ][ "vout" ],

prevout: {

value: contract[ step ][ "amt" ],

scriptPubKey: tapscript.Address.toScriptPubKey( contract[ step ][ "funding_address" ] )

},

}],

vout: [{

value: contract[ step ][ "amt" ] - 500,

scriptPubKey: tapscript.Address.toScriptPubKey( "tb1qd28npep0s8frcm3y7dxqajkcy2m40eysplyr9v" )

}]

});

65 of 84

Check for fraud 2

var target = overwrites_self[ 0 ];

var script = overwrites_self[ 1 ];

var sig = tapscript.Signer.taproot.sign( alice[ "privkey" ], txdata, 0, { extension: target });

var [ tpubkey, cblock ] = tapscript.Tap.getPubKey( "ab".repeat( 32 ), { tree: alice[ "trees" ][ turn ], target });

var bobs_preimage_1 = alice["bobs_moves"][turn-1]

txdata.vin[ 0 ].witness = [ sig, bobs_preimage_1, bobs_preimage_2, script, cblock ];

var txhex = tapscript.Tx.encode( txdata ).hex;

console.log( "broadcast this to take bob's money:", txhex );txdata.vin[ 0 ].witness = [ sig, bobs_preimage_2, bobs_preimage_1, script, cblock ];

var txhex = tapscript.Tx.encode( txdata ).hex;

console.log( "or this:", txhex );

}

}

66 of 84

Check for fraud 3

var hash_of_bobs_preimage = rmd160( hexToBytes( alice[ "bobs_moves" ][ turn - 1 ] ) );

var index_of_bobs_preimage = bob[ "hashes" ].indexOf( hash_of_bobs_preimage );

var square_bobs_preimage_belongs_to = index_of_bobs_preimage % 9;

if ( alice[ "bobs_acks" ][ square_bobs_preimage_belongs_to ] ) {

var bobs_preimage_1 = alice[ "bobs_moves" ][turn-1];

var bobs_preimage_2 = alice[ "bobs_acks" ][ square_bobs_preimage_belongs_to ];

var overwrites_opponent = proveOverwriteOpponent( bobs_preimage_1, bobs_preimage_2, bob, alice );

if ( overwrites_opponent ) {

67 of 84

Check for fraud 3

var txdata = tapscript.Tx.create({

vin: [{

txid: contract[ step ][ "txid" ],

vout: contract[ step ][ "vout" ],

prevout: {

value: contract[ step ][ "amt" ],

scriptPubKey: tapscript.Address.toScriptPubKey( contract[ step ][ "funding_address" ] )

},

}],

vout: [{

value: contract[ step ][ "amt" ] - 500,

scriptPubKey: tapscript.Address.toScriptPubKey( "tb1qd28npep0s8frcm3y7dxqajkcy2m40eysplyr9v" )

}]

});

68 of 84

Check for fraud 3

var target = overwrites_opponent[ 0 ];

var script = overwrites_opponent[ 1 ];

var sig = tapscript.Signer.taproot.sign( alice[ "privkey" ], txdata, 0, { extension: target });

var [ tpubkey, cblock ] = tapscript.Tap.getPubKey( "ab".repeat( 32 ), { tree: alice[ "trees" ][ turn ], target });var bobs_preimage_1 = alice["bobs_moves"][turn-1];txdata.vin[ 0 ].witness = [ sig, bobs_preimage_1, bobs_preimage_2, script, cblock ];var txhex = tapscript.Tx.encode( txdata ).hex;console.log( "broadcast this to take bob's money:", txhex );txdata.vin[ 0 ].witness = [ sig, bobs_preimage_2, bobs_preimage_1, script, cblock ];var txhex = tapscript.Tx.encode( txdata ).hex;console.log( "or this:", txhex );

}

}

}

</script>

69 of 84

Bob checks fraud

<script>

var bobChecksFraud = ( step, turn ) => {

//fraud case 1

// Suppose Alice reveals alice[ "preimages" ][ 0 ]. Bob should check if a move is already saved for alice_1. If not, he should save preimage 0 for alice_1. Otherwise he should combine it with the existing alice_1 preimage, find the appropriate tapleaf that lets him take Alice's money (knowing it will never be among tapleaves 0---2 but will be one of the 36 tapleaves among tapleaves 3---38), and do so.

var alices_preimage_2;

// alices_preimage_2 = prompt( `Enter alice's second preimage` );

if ( !alices_preimage_2 ) alices_preimage_2 = "ab";

var is_double_turn = proveDoubleTurn( bob[ "alices_moves" ][ turn ], alices_preimage_2, alice, bob );

if ( is_double_turn ) {

var txdata = tapscript.Tx.create({

vin: [{

txid: contract[ step ][ "txid" ],

vout: contract[ step ][ "vout" ],

prevout: {

value: contract[ step ][ "amt" ],

scriptPubKey: tapscript.Address.toScriptPubKey( contract[ step ][ "funding_address" ] )

},

}],

vout: [{

value: contract[ step ][ "amt" ] - 500,

scriptPubKey: tapscript.Address.toScriptPubKey( "tb1qd28npep0s8frcm3y7dxqajkcy2m40eysplyr9v" )

}]

});

var target = is_double_turn[ 0 ];

var script = is_double_turn[ 1 ];

var sig = tapscript.Signer.taproot.sign( bob[ "privkey" ], txdata, 0, { extension: target });

var pubkey = "ab".repeat( 32 );

var [ tpubkey, cblock ] = tapscript.Tap.getPubKey( pubkey, { tree: bob[ "trees" ][ turn ], target });

var alices_preimage_1 = bob[ "alices_moves" ][ turn ];

txdata.vin[ 0 ].witness = [ sig, alices_preimage_1, alices_preimage_2, script, cblock ];

var txhex = tapscript.Tx.encode( txdata ).hex;

console.log( "broadcast this to take alice's money:", txhex );

txdata.vin[ 0 ].witness = [ sig, alices_preimage_2, alices_preimage_1, script, cblock ];

var txhex = tapscript.Tx.encode( txdata ).hex;

console.log( "or this:", txhex );

}

//fraud case 2

//Bob should also check which square alice's preimage belongs to and check if that square was already marked by Alice (i.e. if she already revealed preimage[ ( whatever % 9 )+9 ], preimage[ ( whatever % 9 )+9+9 ], or preimage[ ( whatever % 9 )+9+9 ]). If not, he should save Alice's preimage for Alice's square_1. Otherwise he should combine it with the existing square_1 preimage, find the appropriate tapleaf that lets him take Alice's money (knowing it will never be among tapleaves 0---38 but will be one of the 36 tapleaves among tapleaves 39---74), and do so.

var hash_of_alices_preimage = rmd160( hexToBytes( bob[ "alices_moves" ][ turn ] ) );

var index_of_alices_preimage = alice[ "hashes" ].indexOf( hash_of_alices_preimage );

var square_alices_preimage_belongs_to = index_of_alices_preimage % 9;

if ( !bob[ "alices_squares" ][ square_alices_preimage_belongs_to ] ) {

bob[ "alices_squares" ][ square_alices_preimage_belongs_to ] = bob[ "alices_moves" ][ turn ];

} else {

var alices_preimage_1 = bob[ "alices_moves" ][ turn ];

var alices_preimage_2 = bob[ "alices_squares" ][ square_alices_preimage_belongs_to ];

var overwrites_self = proveOverwriteSelf( alices_preimage_1, alices_preimage_2, alice, bob );

if ( overwrites_self ) {

var txdata = tapscript.Tx.create({

vin: [{

txid: contract[ step ][ "txid" ],

vout: contract[ step ][ "vout" ],

prevout: {

value: contract[ step ][ "amt" ],

scriptPubKey: tapscript.Address.toScriptPubKey( contract[ step ][ "funding_address" ] )

},

}],

vout: [{

value: contract[ step ][ "amt" ] - 500,

scriptPubKey: tapscript.Address.toScriptPubKey( "tb1qd28npep0s8frcm3y7dxqajkcy2m40eysplyr9v" )

}]

});

var target = overwrites_self[ 0 ];

var script = overwrites_self[ 1 ];

var sig = tapscript.Signer.taproot.sign( bob[ "privkey" ], txdata, 0, { extension: target });

var pubkey = "ab".repeat( 32 );

var [ tpubkey, cblock ] = tapscript.Tap.getPubKey( pubkey, { tree: bob[ "trees" ][ turn ], target });

var alices_preimage_1 = bob[ "alices_moves" ][ turn ];

txdata.vin[ 0 ].witness = [ sig, alices_preimage_1, alices_preimage_2, script, cblock ];

var txhex = tapscript.Tx.encode( txdata ).hex;

console.log( "broadcast this to take alice's money:", txhex );

txdata.vin[ 0 ].witness = [ sig, alices_preimage_2, alices_preimage_1, script, cblock ];

var txhex = tapscript.Tx.encode( txdata ).hex;

console.log( "or this:", txhex );

}

}

//fraud case 3

//Also, whenever Alice reveals an accept_preimage, Bob should take its number mod 9 and mark that accept_X as taken, where X is a number 1 through 9 corresponding to the number of the accept_preimage Alice revealed. That way, when Alice reveals alice[ "preimages" ][ 0 ] or any other reveal_preimage, Bob can take its number mod 9 and check if she already marked that spot in a previous move. If not, well and good. Otherwise, he should combine the two preimages, find the appropriate tapleaf that lets him take Alice's money (knowing it will never be among tapleaves 0---74 but will be one of the 36 tapleaves among tapleaves 75---110), and do so.

var hash_of_alices_preimage = rmd160( hexToBytes( bob[ "alices_moves" ][ turn ] ) );

var index_of_alices_preimage = alice[ "hashes" ].indexOf( hash_of_alices_preimage );

var square_alices_preimage_belongs_to = index_of_alices_preimage % 9;

if ( bob[ "alices_acks" ][ square_alices_preimage_belongs_to ] ) {

var alices_preimage_1 = bob[ "alices_moves" ][ turn ];

var alices_preimage_2 = bob[ "alices_acks" ][ square_alices_preimage_belongs_to ];

var overwrites_opponent = proveOverwriteOpponent( alices_preimage_1, alices_preimage_2, alice, bob );

if ( overwrites_opponent ) {

var txdata = tapscript.Tx.create({

vin: [{

txid: contract[ step ][ "txid" ],

vout: contract[ step ][ "vout" ],

prevout: {

value: contract[ step ][ "amt" ],

scriptPubKey: tapscript.Address.toScriptPubKey( contract[ step ][ "funding_address" ] )

},

}],

vout: [{

value: contract[ step ][ "amt" ] - 500,

scriptPubKey: tapscript.Address.toScriptPubKey( "tb1qd28npep0s8frcm3y7dxqajkcy2m40eysplyr9v" )

}]

});

var target = overwrites_opponent[ 0 ];

var script = overwrites_opponent[ 1 ];

var sig = tapscript.Signer.taproot.sign( bob[ "privkey" ], txdata, 0, { extension: target });

var pubkey = "ab".repeat( 32 );

var [ tpubkey, cblock ] = tapscript.Tap.getPubKey( pubkey, { tree: bob[ "trees" ][ turn ], target });

var alices_preimage_1 = bob[ "alices_moves" ][ turn ];

txdata.vin[ 0 ].witness = [ sig, alices_preimage_1, alices_preimage_2, script, cblock ];

var txhex = tapscript.Tx.encode( txdata ).hex;

console.log( "broadcast this to take alice's money:", txhex );

txdata.vin[ 0 ].witness = [ sig, alices_preimage_2, alices_preimage_1, script, cblock ];

var txhex = tapscript.Tx.encode( txdata ).hex;

console.log( "or this:", txhex );

}

}

}

</script>

70 of 84

Check for victory

<script>

var aliceChecksVictory = ( step, turn, alices_move_preimage ) => {

var victory;

//check rows

var i; for ( i=0; i<3; i++ ) {

if ( i==0 ) var row_nums = [0,1,2];

if ( i==1 ) var row_nums = [3,4,5];

if ( i==2 ) var row_nums = [6,7,8];

for ( var comb of G.combination( row_nums, 2 ) ) {

if ( victory ) continue;

var squares = Array.from( comb );

var bobs_ack_1 = alice[ "bobs_acks" ][ squares[ 0 ] ];

var bobs_ack_2 = alice[ "bobs_acks" ][ squares[ 1 ] ];

if ( !bobs_ack_1 || !bobs_ack_2 ) continue;

victory = proveVictory( bobs_ack_1, bobs_ack_2, alices_move_preimage, bob, alice );

}

}

if ( victory ) {

var txdata = tapscript.Tx.create({

vin: [{

txid: contract[ step ][ "txid" ],

vout: contract[ step ][ "vout" ],

prevout: {

value: contract[ step ][ "amt" ],

scriptPubKey: tapscript.Address.toScriptPubKey( contract[ step ][ "funding_address" ] )

},

}],

vout: [{

value: contract[ step ][ "amt" ] - 500,

scriptPubKey: tapscript.Address.toScriptPubKey( "tb1qd28npep0s8frcm3y7dxqajkcy2m40eysplyr9v" )

}]

});

var target = victory[ 0 ];

var script = victory[ 1 ];

var sig = tapscript.Signer.taproot.sign( alice[ "privkey" ], txdata, 0, { extension: target });

var pubkey = "ab".repeat( 32 );

var [ tpubkey, cblock ] = tapscript.Tap.getPubKey( pubkey, { tree: alice[ "trees" ][ turn ], target });

txdata.vin[ 0 ].witness = [ sig, bobs_ack_1, bobs_ack_2, alices_move_preimage, script, cblock ];

var txhex = tapscript.Tx.encode( txdata ).hex;

console.log( "broadcast this to take your winnings:", txhex );

txdata.vin[ 0 ].witness = [ sig, bobs_ack_2, bobs_ack_1, alices_move_preimage, script, cblock ];

var txhex = tapscript.Tx.encode( txdata ).hex;

console.log( "or this:", txhex );

}

}

var bobChecksVictory = ( step, turn, bobs_move_preimage ) => {

var victory;

//check rows

var i; for ( i=0; i<3; i++ ) {

if ( i==0 ) var row_nums = [0,1,2];

if ( i==1 ) var row_nums = [3,4,5];

if ( i==2 ) var row_nums = [6,7,8];

for ( var comb of G.combination( row_nums, 2 ) ) {

if ( victory ) continue;

var squares = Array.from( comb );

var alices_ack_1 = bob[ "alices_acks" ][ squares[ 0 ] ];

var alices_ack_2 = bob[ "alices_acks" ][ squares[ 1 ] ];

if ( !alices_ack_1 || !alices_ack_2 ) continue;

victory = proveVictory( alices_ack_1, alices_ack_2, bobs_move_preimage, alice, bob );

}

}

if ( victory ) {

var txdata = tapscript.Tx.create({

vin: [{

txid: contract[ step ][ "txid" ],

vout: contract[ step ][ "vout" ],

prevout: {

value: contract[ step ][ "amt" ],

scriptPubKey: tapscript.Address.toScriptPubKey( contract[ step ][ "funding_address" ] )

},

}],

vout: [{

value: contract[ step ][ "amt" ] - 500,

scriptPubKey: tapscript.Address.toScriptPubKey( "tb1qd28npep0s8frcm3y7dxqajkcy2m40eysplyr9v" )

}]

});

var target = victory[ 0 ];

var script = victory[ 1 ];

var sig = tapscript.Signer.taproot.sign( bob[ "privkey" ], txdata, 0, { extension: target });

var pubkey = "ab".repeat( 32 );

var [ tpubkey, cblock ] = tapscript.Tap.getPubKey( pubkey, { tree: bob[ "trees" ][ turn ], target });

txdata.vin[ 0 ].witness = [ sig, alices_ack_1, alices_ack_2, bobs_move_preimage, script, cblock ];

var txhex = tapscript.Tx.encode( txdata ).hex;

console.log( "broadcast this to take your winnings:", txhex );

txdata.vin[ 0 ].witness = [ sig, alices_ack_2, alices_ack_1, bobs_move_preimage, script, cblock ];

var txhex = tapscript.Tx.encode( txdata ).hex;

console.log( "or this:", txhex );

}

}

</script>

71 of 84

We’re almost there!

Most of the logic is written now�

We still need to do this:�

  • logic for taking a turn
  • logic waiting your turn
  • code to display moves
  • call all these methods

72 of 84

Take your turn

<script>

var takeAlicesTurn = async ( step, turn, bobs_turn_address, bobs_preimage, bobs_move ) => {

var txdata = tapscript.Tx.create({

vin: [{

txid: contract[ step ][ "txid" ],

vout: contract[ step ][ "vout" ],

prevout: {

value: contract[ step ][ "amt" ],

scriptPubKey: tapscript.Address.toScriptPubKey( contract[ step ][ "funding_address" ] )

},

}],

vout: [{

value: contract[ step ][ "amt" ] - 500,

scriptPubKey: tapscript.Address.toScriptPubKey( bobs_turn_address )

}]

});

alert( `Please place an X` );

game_on = true;

var move = await waitForMove();

num = undefined;

game_on = undefined;

73 of 84

Take your turn

move = Number( move ) - 1;

var move_preimage = alice[ "preimages" ][ Number( move ) + Number( turn * 9 ) ];

aliceChecksVictory( step, turn, move_preimage );

if ( victory_achieved ) return alert( `You won! Check your console to collect your winnings!` );

if ( turn != 0 ) var accept_preimage = alice[ "accept_preimages" ][ bobs_move + Number( ( turn - 1 ) * 9 ) ];

else accept_preimage = "";

if ( turn == 0 ) var bobs_preimage = "";

if ( turn == 0 ) var bobs_move = move;

var target = tapscript.Tap.encodeScript( alice[ "scripts" ][ turn ][ bobs_move + 2 ] );

var pubkey = "ab".repeat( 32 );

var [ tpubkey, cblock ] = tapscript.Tap.getPubKey( pubkey, { tree: alice[ "trees" ][ turn ], target });

74 of 84

Take Alice’s turn

var sig_1 = tapscript.Signer.taproot.sign( alice[ "privkey" ], txdata, 0, { extension: target });

var sig_2 = tapscript.Signer.taproot.sign( bob[ "privkey" ], txdata, 0, { extension: target });

txdata.vin[ 0 ].witness = [ sig_2, sig_1, move_preimage, accept_preimage, bobs_preimage, alice[ "scripts" ][ turn ][ bobs_move + 2 ], cblock ];

var ftxhex = tapscript.Tx.encode( txdata ).hex;

var vtxid = tapscript.Tx.util.getTxid( txdata );

console.log( "Next step, broadcast this txhex to make it Bob's turn:", ftxhex );

console.log( `Then, reveal this move_preimage to bob: ${move_preimage}` );

if ( turn != 0 ) console.log( "And this accept_preimage:", accept_preimage );

contract.push({ txid: vtxid, vout: 0, amt: contract[ step ][ "amt" ] - 500, funding_address: bobs_turn_address, funding_tx: ftxhex });

}

75 of 84

Take Bob’s turn

var takeBobsTurn = async ( step, turn, alices_turn_address, alices_preimage, alices_move ) => {

var txdata = tapscript.Tx.create({

vin: [{

txid: contract[ step ][ "txid" ],

vout: contract[ step ][ "vout" ],

prevout: {

value: contract[ step ][ "amt" ],

scriptPubKey: tapscript.Address.toScriptPubKey( contract[ step ][ "funding_address" ] )

},

}],

vout: [{

value: contract[ step ][ "amt" ] - 500,

scriptPubKey: tapscript.Address.toScriptPubKey( alices_turn_address )

}]

});

alert( `Please place an O` );

game_on = true;

var move = await waitForMove();

num = undefined;

game_on = undefined;

move = Number( move ) - 1;

var move_preimage = bob[ "preimages" ][ Number( move ) + Number( turn * 9 ) ];

bobChecksVictory( step, turn, move_preimage );

if ( victory_achieved ) return alert( `You won! Check your console to collect your winnings!` );

var accept_preimage = bob[ "accept_preimages" ][ alices_move + Number( turn * 9 ) ];

var target = tapscript.Tap.encodeScript( bob[ "scripts" ][ turn ][ alices_move + 2 ] );

var pubkey = "ab".repeat( 32 );

var [ tpubkey, cblock ] = tapscript.Tap.getPubKey( pubkey, { tree: bob[ "trees" ][ turn ], target });

var sig_1 = tapscript.Signer.taproot.sign( alice[ "privkey" ], txdata, 0, { extension: target });

var sig_2 = tapscript.Signer.taproot.sign( bob[ "privkey" ], txdata, 0, { extension: target });

txdata.vin[ 0 ].witness = [ sig_2, sig_1, move_preimage, accept_preimage, alices_preimage, bob[ "scripts" ][ turn ][ alices_move + 2 ], cblock ];

var ftxhex = tapscript.Tx.encode( txdata ).hex;

var vtxid = tapscript.Tx.util.getTxid( txdata );

console.log( "Next step, broadcast this txhex to make it Alice's second turn:", ftxhex );

console.log( "Then, reveal this move_preimage to alice:", move_preimage );

console.log( "And this accept_preimage:", accept_preimage );

contract.push({ txid: vtxid, vout: 0, amt: contract[ step ][ "amt" ] - 500, funding_address: alices_turn_address, funding_tx: ftxhex });

}

</script>

76 of 84

Alice waits her turn

<script>

var waitForBobsAcceptance = async ( num1, num2, prev_accept_was_invalid ) => {

//get bob's acceptance

if ( prev_accept_was_invalid ) var msg = `The previous acceptance preimage was invalid. Enter bob's accept_preimage`;

else var msg = `Enter bob's accept_preimage`;

bobs_accept_preimage = prompt( msg );

//check if the accept preimage is valid

bobs_accept_hash = rmd160( hexToBytes( bobs_accept_preimage ) );

hashes = [ ...bob[ "accept_hashes" ] ];

hashes = hashes.splice( num1, num2 );

var index = hashes.indexOf( bobs_accept_hash );

if ( index < 0 ) return waitForBobsAcceptance( num1, num2, true );

return alice[ "bobs_acks" ][ index ] = bobs_accept_preimage;

}

var waitForBobsMove = async ( num1, num2, prev_move_was_invalid ) => {

//get bob's move

if ( prev_move_was_invalid ) var msg = `The previous move was invalid. Enter bob's move_preimage`;

else var msg = `Enter bob's move_preimage`;

bobs_preimage = prompt( msg );

//check if the move is valid

bobs_hash = rmd160( hexToBytes( bobs_preimage ) );

hashes = [ ...bob[ "hashes" ] ];

hashes = hashes.splice( num1, num2 );

if ( hashes.includes( bobs_hash ) ) return alice[ "bobs_moves" ].push( bobs_preimage );

waitForBobsMove( num1, num2, true );

}

77 of 84

Bob waits his turn

var waitForAlicesAcceptance = async ( num1, num2, prev_accept_was_invalid ) => {

//get alice's acceptance

if ( prev_accept_was_invalid ) var msg = `The previous acceptance preimage was invalid. Enter alice's accept_preimage`;

else var msg = `Enter alice's accept_preimage`;

alices_accept_preimage = prompt( msg );

//check if the accept preimage is valid

alices_accept_hash = rmd160( hexToBytes( alices_accept_preimage ) );

hashes = [ ...alice[ "accept_hashes" ] ];

hashes = hashes.splice( num1, num2 );

var index = hashes.indexOf( alices_accept_hash );

if ( index < 0 ) return waitForAlicesAcceptance( num1, num2, true );

return bob[ "alices_acks" ][ index ] = alices_accept_preimage;

}

var waitForAlice = async ( num1, num2, prev_move_was_invalid ) => {

//get alice's move

if ( prev_move_was_invalid ) var msg = `The previous move was invalid. Enter alice's move_preimage`;

else var msg = `Enter alice's move_preimage`;

alices_preimage = prompt( msg );

//check if the move is valid

alices_hash = rmd160( hexToBytes( alices_preimage ) );

hashes = [ ...alice[ "hashes" ] ];

hashes = hashes.splice( num1, num2 );

if ( hashes.includes( alices_hash ) ) return bob[ "alices_moves" ].push( alices_preimage );

waitForAlice( num1, num2, true );

}

</script>

78 of 84

Show the moves

<script>

var showAlicesMove = () => {

alices_move = hashes.indexOf( alices_hash );

sessionStorage[ `cell_${alices_move}` ] = "_X_|";

if ( alices_move % 3 == 2 ) sessionStorage[ `cell_${alices_move}` ] = "_X_";

var cell_0 = sessionStorage[ "cell_0" ];

var cell_1 = sessionStorage[ "cell_1" ];

var cell_2 = sessionStorage[ "cell_2" ];

var cell_3 = sessionStorage[ "cell_3" ];

var cell_4 = sessionStorage[ "cell_4" ];

var cell_5 = sessionStorage[ "cell_5" ];

var cell_6 = sessionStorage[ "cell_6" ];

var cell_7 = sessionStorage[ "cell_7" ];

var cell_8 = sessionStorage[ "cell_8" ];

console.log( cell_0 + cell_1 + cell_2 );

console.log( cell_3 + cell_4 + cell_5 );

console.log( cell_6 + cell_7 + cell_8 );

}

var showBobsMove = () => {

bobs_move = hashes.indexOf( bobs_hash );

sessionStorage[ `cell_${bobs_move}` ] = "_O_|";

if ( bobs_move % 3 == 2 ) sessionStorage[ `cell_${bobs_move}` ] = "_O_";

var cell_0 = sessionStorage[ "cell_0" ];

var cell_1 = sessionStorage[ "cell_1" ];

var cell_2 = sessionStorage[ "cell_2" ];

var cell_3 = sessionStorage[ "cell_3" ];

var cell_4 = sessionStorage[ "cell_4" ];

var cell_5 = sessionStorage[ "cell_5" ];

var cell_6 = sessionStorage[ "cell_6" ];

var cell_7 = sessionStorage[ "cell_7" ];

var cell_8 = sessionStorage[ "cell_8" ];

console.log( cell_0 + cell_1 + cell_2 );

console.log( cell_3 + cell_4 + cell_5 );

console.log( cell_6 + cell_7 + cell_8 );

}

</script>

79 of 84

Make Play work

<script>

var init = async () => {

var alices_first_turn_address = alicesTurn( 0 );

var bobs_first_turn_address = bobsTurn( 0 );

var alices_second_turn_address = alicesTurn( 1 );

var bobs_second_turn_address = bobsTurn( 1 );

var alices_third_turn_address = alicesTurn( 2 );

var bobs_third_turn_address = bobsTurn( 2 );

var alices_fourth_turn_address = alicesTurn( 3 );

var bobs_fourth_turn_address = bobsTurn( 3 );

var alices_fifth_turn_address = alicesTurn( 4 );

console.log( "Step 1, send money to this funding address:", funding_address );

var funding_txid = prompt( `please send money to this address: ${funding_address} and enter the txid` );

var funding_vout = prompt( `and the vout` );

funding_vout = Number( funding_vout );

var funding_amt = prompt( `and the amount` );

funding_amt = Number( funding_amt );

80 of 84

Make Play work

var txdata = tapscript.Tx.create({

vin: [{

txid: funding_txid,

vout: funding_vout,

prevout: {

value: funding_amt,

scriptPubKey: tapscript.Address.toScriptPubKey( funding_address )

},

}],

vout: [{

value: funding_amt - 500,

scriptPubKey: tapscript.Address.toScriptPubKey( alices_first_turn_address )

}]

});

81 of 84

Make Play work

var sig = tapscript.Signer.taproot.sign( alice[ "privkey" ], txdata, 0 );

txdata.vin[ 0 ].witness = [ sig ];

var ftxhex = tapscript.Tx.encode( txdata ).hex;

var vtxid = tapscript.Tx.util.getTxid( txdata );

console.log( "Step 2, broadcast this txhex to start Alice's turn:", ftxhex );

var utxo = [ funding_txid, funding_vout, funding_amt ];

contract.push({ txid: vtxid, vout: 0, amt: funding_amt - 500, funding_address: alices_first_turn_address, funding_tx: ftxhex });

} //close this for now but we'll keep adding to it

</script>

82 of 84

Make Play work

//alice takes her first turn

await takeAlicesTurn( 0, 0, bobs_first_turn_address );

if ( victory_achieved ) return;

//bob waits for alice's info

waitForAlice( 0, 9 );

showAlicesMove();

//bob checks for fraud

bobChecksFraud( 1, 0 );

if ( fraud_occurred ) return alert( `Oh no, your opponent committed fraud! Check your console to penalize them and take their money` );

//bob takes his first turn

await takeBobsTurn( 1, 0, alices_second_turn_address, alices_preimage, alices_move );

if ( victory_achieved ) return;

83 of 84

Make Play work

//alice waits for bob's info

waitForBobsAcceptance( 0, 9 );

waitForBobsMove( 0, 9 );

showBobsMove();

//alice checks for fraud

aliceChecksFraud( 2, 1 );

if ( fraud_occurred ) return alert( `Oh no, your opponent committed fraud! Check your console to penalize them and take their money` );

//alice takes her second turn

await takeAlicesTurn( 2, 1, bobs_second_turn_address, bobs_preimage, bobs_move );

if ( victory_achieved ) return;

//bob waits for alice's info

waitForAlicesAcceptance( 0, 9 );

waitForAlice( 9, 18 );

showAlicesMove();

//bob checks for fraud

bobChecksFraud( 3, 1 );

if ( fraud_occurred ) return alert( `Oh no, your opponent committed fraud! Check your console to penalize them and take their money` );

//bob takes his second turn

await takeBobsTurn( 3, 1, alices_third_turn_address, alices_preimage, alices_move );

if ( victory_achieved ) return;

//alice waits for bob's info

waitForBobsAcceptance( 9, 18 );

waitForBobsMove( 9, 18 );

showBobsMove();

//alice checks for fraud

aliceChecksFraud( 4, 2 );

if ( fraud_occurred ) return alert( `Oh no, your opponent committed fraud! Check your console to penalize them and take their money` );

//alice takes her third turn

await takeAlicesTurn( 4, 2, bobs_third_turn_address, bobs_preimage, bobs_move );

if ( victory_achieved ) return;

//bob waits for alice's info

waitForAlicesAcceptance( 9, 18 );

waitForAlice( 18, 27 );

showAlicesMove();

//bob checks for fraud

bobChecksFraud( 5, 2 );

if ( fraud_occurred ) return alert( `Oh no, your opponent committed fraud! Check your console to penalize them and take their money` );

//bob takes his third turn

await takeBobsTurn( 5, 2, alices_fourth_turn_address, alices_preimage, alices_move );

if ( victory_achieved ) return;

//alice waits for bob's info

waitForBobsAcceptance( 18, 27 );

waitForBobsMove( 18, 27 );

showBobsMove();

//alice checks for fraud

aliceChecksFraud( 6, 3 );

if ( fraud_occurred ) return alert( `Oh no, your opponent committed fraud! Check your console to penalize them and take their money` );

//alice takes her fourth turn

await takeAlicesTurn( 6, 3, bobs_fourth_turn_address, bobs_preimage, bobs_move );

if ( victory_achieved ) return;

//bob waits for alice's info

waitForAlicesAcceptance( 18, 27 );

waitForAlice( 27, 36 );

showAlicesMove();

//bob checks for fraud

bobChecksFraud( 7, 3 );

if ( fraud_occurred ) return alert( `Oh no, your opponent committed fraud! Check your console to penalize them and take their money` );

//bob takes his last turn

await takeBobsTurn( 7, 3, alices_fifth_turn_address, alices_preimage, alices_move );

if ( victory_achieved ) return;

//alice waits for bob's info

waitForBobsAcceptance( 27, 36 );

waitForBobsMove( 27, 36 );

showBobsMove();

//alice checks for fraud

aliceChecksFraud( 8, 4 );

if ( fraud_occurred ) return alert( `Oh no, your opponent committed fraud! Check your console to penalize them and take their money` );

//alice takes her last turn

await takeAlicesTurn( 8, 4, "tb1qd28npep0s8frcm3y7dxqajkcy2m40eysplyr9v", bobs_preimage, bobs_move );

if ( victory_achieved ) return;

84 of 84

Make Play work

Object.keys( sessionStorage ).forEach( item => { if ( sessionStorage[ item ].includes( "___|" ) ) sessionStorage[ item ] = "_X_|";if ( sessionStorage[ item ].includes( "___" ) ) sessionStorage[ item ] = "_X_"; });

var cell_0 = sessionStorage[ "cell_0" ];

var cell_1 = sessionStorage[ "cell_1" ];

var cell_2 = sessionStorage[ "cell_2" ];

var cell_3 = sessionStorage[ "cell_3" ];

var cell_4 = sessionStorage[ "cell_4" ];

var cell_5 = sessionStorage[ "cell_5" ];

var cell_6 = sessionStorage[ "cell_6" ];

var cell_7 = sessionStorage[ "cell_7" ];

var cell_8 = sessionStorage[ "cell_8" ];

console.log( cell_0 + cell_1 + cell_2 );

console.log( cell_3 + cell_4 + cell_5 );

console.log( cell_6 + cell_7 + cell_8 );