Mental poker for turn-based
strategy games – securing
fairness without a trusted third
party
Stian Aleksander Ellingsen
15-12-2016
Master’s Thesis
Master of Science in Applied Computer Science
30 ECTS
Department of Computer Science and Media Technology
Norwegian University of Science and Technology,
Supervisor: Mariusz Nowostawski
Abstract
Strategy video games typically provide players with only partial information
about the game world, hiding everything outside the range of vision of each
player’s units. A third party is typically trusted with running the game and limiting the information given to players. A player colluding with the third party
could gain a huge unfair advantage by accessing more information than allowed
by the rules, and such cheating is generally undetectable. Between players who
don’t trust a common third party, a different solution is needed.
The same problem arises for card games when implemented over a computer
network in a provably fair way without a trusted third party. In card games,
this problem is known as mental poker, and a solution to this problem was first
suggested in 1981. While turn-based strategy games and card games have some
important elements in common, the methods provided in mental poker solutions
do not translate directly to turn-based strategy games.
Some work has been done on hiding player units in strategy games without a
trusted third party, but not completely without unwanted information exposure.
There is also previous work on securely generating maps for strategy games without a third party, but not in a way that hides unexplored terrain.
This project applies the problem of mental poker to provide a protocol for
playing a turn-based strategy game over a network, with computational requirements that can be met by a normal computer. This includes securely generating
random maps, hiding unexplored terrain and units with negligible information
exposure during gameplay, while dissuading attempts at cheating or disruption.
Mental poker for turn-based strategy games – securing fairness without a trusted third party
Contents
Contents . . . . . . . . . . . . . . . . . . . . . . . . .
1 Introduction . . . . . . . . . . . . . . . . . . . . .
1.1 Problem description . . . . . . . . . . . . . .
1.2 Threat model and principles . . . . . . . . . .
2 Related work . . . . . . . . . . . . . . . . . . . . .
2.1 Fair play . . . . . . . . . . . . . . . . . . . . .
2.2 Obtaining randomness . . . . . . . . . . . . .
2.3 Map generation . . . . . . . . . . . . . . . . .
2.4 Consequences of the game outcome . . . . . .
3 Protocol . . . . . . . . . . . . . . . . . . . . . . . .
3.1 Game initialisation . . . . . . . . . . . . . . .
3.1.1 Game parametres . . . . . . . . . . . .
3.1.2 Private keys . . . . . . . . . . . . . . .
3.1.3 Contract . . . . . . . . . . . . . . . . .
3.1.4 Modulus . . . . . . . . . . . . . . . . .
3.1.5 The game state . . . . . . . . . . . . .
3.1.6 Initialisation of the game state . . . .
3.2 Privately retrieving randomness . . . . . . . .
3.3 Updating and retrieving encrypted game state
3.4 Verification . . . . . . . . . . . . . . . . . . .
3.5 Ending the game . . . . . . . . . . . . . . . .
3.6 Cipher . . . . . . . . . . . . . . . . . . . . . .
4 Map generation . . . . . . . . . . . . . . . . . . .
4.1 Algorithms . . . . . . . . . . . . . . . . . . .
5 Security parametres . . . . . . . . . . . . . . . . .
6 Performance analysis . . . . . . . . . . . . . . . .
6.1 Blockchain scripts . . . . . . . . . . . . . . . .
7 Enforcing fair play . . . . . . . . . . . . . . . . .
8 Conclusion and future work . . . . . . . . . . . .
A Blockchain details . . . . . . . . . . . . . . . . . .
A.1 Features . . . . . . . . . . . . . . . . . . . . .
A.2 Objects . . . . . . . . . . . . . . . . . . . . .
i
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
i
1
1
2
3
3
4
5
5
7
7
7
8
8
9
10
10
10
11
11
11
12
14
15
19
20
21
23
25
27
27
27
Mental poker for turn-based strategy games – securing fairness without a trusted third party
A.2.1 State . . . . . . .
A.3 Branches . . . . . . . . .
A.3.1 REVEALp . . . . .
A.3.2 GENERIC . . . . .
A.3.3 TIMEOUTp . . . .
A.3.4 CHEATi,j,p,q . . . .
A.4 Programs . . . . . . . .
A.4.1 progMain . . . .
A.4.2 progRevealp,q . .
A.4.3 progWithStatei,p,q
A.5 Transactions . . . . . . .
A.5.1 txFunding . . . .
A.5.2 txResignp,q . . .
A.5.3 txRevealp,q . . .
A.5.4 txWithStatei,p,q .
A.5.5 txDefaultp,q . . .
B Source code . . . . . . . . .
B.1 Blockchain.hs . . . . . .
B.2 Compile.hs . . . . . . .
B.3 Game.hs . . . . . . . . .
Glossary . . . . . . . . . . . . .
Bibliography . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
ii
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
27
28
28
28
28
28
29
29
29
29
29
29
30
30
30
30
32
32
42
54
59
62
Mental poker for turn-based strategy games – securing fairness without a trusted third party
Chapter 1
Introduction
The project deals with the problem of playing a turn-based strategy (TBS) game
between two or more players over a network, in such a way that each player is
able to ensure that the game is fair, without players having to trust each other
or a common third party. This problem is greatly complicated when each player
should be restricted to seeing only the parts of the game world that their own
units can see, a typical feature in strategy games.
1.1
Problem description
In strategy video games, players typically control a set of units placed in a game
world shared with opponents. Depending on their type, units can be commanded
to explore the world, to collect resources found in the world, or to combat against
opponent units. Two main sub-genres are real-time strategy (RTS) games and
TBS games, which mainly differ in the way timekeeping works. In RTS , time is
continuous, while in TBS, players’ actions are grouped in turns.
An important feature in many strategy video games is the effect referred to as
fog of war: A player can only see the part of the game world that is within their
unit’s line of sight [1]. However, the whole game state is usually available to the
computer which is running the game logic, and may easily be accessed by the
owner, which could be one (or all) of the players, or a third party which could
be colluding with one of the players. This type of cheating may give a player a
huge advantage over honest players and can’t be detected directly, if at all.
Mental poker, introduced by Shamir, Rivest and Adleman [20], involves playing a game of cards at a distance, using only messages between the players. To
ensure a fair method of playing mental poker, important properties of a physical
equivalent of the game are preserved, such as ensuring a fair deal, being able
to hide one’s own hand from other players, and making sure that two players
can’t have the same card. This is made possible with the use of cryptography. For
example, securely shuffling the deck can be done by having each player encrypt
1
Mental poker for turn-based strategy games – securing fairness without a trusted third party
each card in the deck, shuffle the deck and then pass it to the next player.
While mental poker is mostly concerned with card games [21], the same problem can be applied to other types of games as well. The element of incomplete
information is similar to fog of war in strategy games, and TBS games in particular are played in turns like card games.
Applying the problem of mental poker to TBS games prompts the need for
a protocol for playing a TBS game over a network, fairly, and with negligible
information exposure, enforcing fog of war on units as well as the rest of the
game world. This project seeks to provide a practical solution to this, including
methods to sufficiently dissuade attempts at cheating or disruption.
1.2
Threat model and principles
Two players want to play a TBS game against each other over the Internet. They
do not trust each other to play fairly, nor do they have a trusted third party (TTP)
in common to run the game and decide on the outcome. Each player is assumed
to play the game on a computer system that they fully control and trust.
For the purpose of this project, cheating involves retrieving information that
should not be accessible to the player or modifying the game state in a way
that is not allowed by the game rules. Other actions that may be considered
cheating, such as disrupting the network connection of the other player or using
additional computer software to aid in or automate selection of game actions,
are not considered in this project.
2
Mental poker for turn-based strategy games – securing fairness without a trusted third party
Chapter 2
Related work
2.1
Fair play
This thesis applies the problem of mental poker to turn-based strategy (TBS)
games. There has been a lot of research on mental poker, but this has mostly
focused on card games [21]. It is not obvious how to apply methods of shuffling
and dealing cards to a TBS game. Nonetheless, the cryptographic primitives used
for these games could be useful.
The mental poker protocol given by Shamir, Rivest and Adleman [20] relies on
a commutative encryption scheme, meaning that the order of encryption doesn’t
matter when a message is doubly encrypted with different keys – a description
of such a scheme is given by Pohlig and Hellman [18]. In a game between Alice
and Bob, Bob starts by encrypting and shuffling the cards, sending the deck to
Alice. To obtain her hand, Alice takes a random set of cards, encrypts them and
gets Bob to decrypt them. Alice then decrypts these. This way, Alice can’t control
which cards she obtains, and Bob can’t know Alice’s hand.
Goldreich, Micali and Wigderson [13] give a general solution for playing any
mental game as long as the majority of the participants are honest. This is done
by building circuits of logic gates to implement the whole game. The purpose of
their paper is to prove that such a model exists, and efficiency is not a concern.
However, for something as complex as a full TBS game, performance will quickly
become an issue.
Chambers et al. [9] give a method for detecting when a player has bypassed
the fog of war to reveal opponents’ units and positions. The method works by
exchanging bitmaps of the visible areas between players, and only sending information about the units that should be seen by each opponent. During the game,
players commit to all their moves, and like in the protocol of Shamir, Rivest and
Adleman [20], reveal their secrets and their commited moves at the end of the
game for verification. While aimed at real-time strategy (RTS) games, the method
can be trivially adopted to a TBS game. However, it does not hide player locations
3
Mental poker for turn-based strategy games – securing fairness without a trusted third party
completely, as each player’s visible area is exposed to the opponents.
Bursztein et al. [7] provide a better solution with respect to information exposure by using oblivious set intersection, which limits the exposure of a player’s
visible area to the parts that overlap with each opponent’s units. A potentially
useful optimisation is their notion of what they call "hypergrids" to reduce the
number of cells that a visibility map must contain. While their method does not
expose the visibility areas directly, it still leaks information about their sizes, as
well as the number of units and which opponents can see each unit. To limit this
information exposure, they propose using a chaff in the form of random, meaningless data and queries, but this still reveals an upper bound on the visible area
and number of units, and there is no way to have a false negative on whether a
unit is visible to an opponent.
The methods presented in the aforementioned two papers are only aimed at
hiding units. They are not able to hide unexplored terrain.
Rice [19] proposes a method for fairly generating a map suitable for a TBS
game in a peer-to-peer fashion. This method consists of deterministically generating the whole map from a securely generated seed using procedural generation
algorithms before gameplay begins. The paper mentions the concept of using
fog of war to hide parts of the map from each player and generating the map
as it is explored, but the proposed method does not cover this due to concerns
about information exposure between players. However, cryptographic methods
like those used in mental poker may possibly be used together with the concepts
and adapted versions of the algorithms in this paper for map generation that
implements fog of war.
2.2 Obtaining randomness
In the peer-to-peer map generation protocol by Rice [19], players derive a seed
value from a simple XOR between random values generated by each player. Fairness is secured by having each player commit to their random value by a signed
hash, so that no player can influence the final seed to their advantage by adjusting their own value. This method works well when all players should have
shared knowledge about the generated map, but won’t work if only some of the
players should have access to some seed.
Diffie and Hellman [10] give a technique for deriving a shared secret between
two parties, from random numbers that are sent in the clear. Their system, known
as the Diffie–Hellman key exchange, is based on modular exponentiation and
relies on the commutative property of combining two exponents, i.e., (xa )b =
(xb )a .
Pohlig and Hellman [18] describe a cryptosystem based on exponentiation
modulo a prime, where the exponent and its inverse are secret numbers used to
encrypt and decrypt messages. This is known as the Pohlig–Hellman exponentiation cipher. The Diffie–Hellman key exchange may be considered a special case
4
Mental poker for turn-based strategy games – securing fairness without a trusted third party
of this cryptosystem, where the key is derived by repeatedly encrypting a public
number with each party’s secret exponent.
Shamir, Rivest and Adleman [20] show how cards can be dealt in a fair way in
a game of mental poker by using a cryptosystem with certain properties. In particular, one of the requirements is the aforementioned commutativity of encryption.
In their card dealing protocol, randomness is introduced by having the players
shuffle and randomly select encrypted cards without being able to distinguish
them. They give the Pohlig–Hellman cipher as an example of a cryptosystem that
has the required properties. While the methods of shuffling are not directly applicable to procedural generation, it is an interesting demonstration of how a
commutative cipher may be used in a game with incomplete information.
2.3 Map generation
In the procedural map generation algorithm by Rice [19], the map is built up
by several layers, each of which determines some feature of the map. While
the resource layer works on each map cell independently and can thus easily
be adopted to a game with incomplete information, other layers rely on noise
functions that may be problematic.
The terrain height layer uses the Diamond-Square algorithm, which when
given the variables necessary to calculate the value at some point, reveals the
exact values at the edges of each grid cell surrounding that point at all the subdivision levels.
The land/water layer requires a pass through the whole terrain layer to find
the average height. This is not possible when only part of the map is accessible,
so a different way of finding an appropriate land/water level is required.
The trees/deserts layer uses Simplex noise, in which each point gets its value
from the three surrounding grid points. This algorithm is well suited for local
map generation with minimal exposure of the surrounding area.
A potential problem with many noise algorithms is the use of inexact operations, and the use of floating-point operations in general. Such operations could
behave differently across platforms, causing players to generate slightly different
maps from the same seed. Rice [19] addresses this issue by having the players
compare hashes of their generated maps. However, such comparison is not possible when players can only access parts of the map and should have no knowledge
of which parts other players have access to.
2.4
Consequences of the game outcome
While active cheating can easily be detected at the end of a game by using a
commitment scheme and revealing the secret data at the end of the game as
suggested in several papers [20, 9, 7], detection in itself is not necessarily enough
to prevent cheating or other unwanted behaviour such as aborting the game.
Chambers et al. [9] and Bursztein et al. [7] suggest the use of a central server
5
Mental poker for turn-based strategy games – securing fairness without a trusted third party
which can verify the game and has the power to penalise cheaters somehow. This
requires all players to trust a common third party to make fair decisions against
them during the game. As with the ability to check that a game was played fairly,
this project will attempt to get rid of the need for a single trusted third party
(TTP) to decide the consequences of the game.
Interfacing with a cryptocurrency such as Bitcoin could allow for a game to
require players to make a deposit at the beginning of the game. At the end of the
game, those who played by the rules would be refunded and receive a share of
the deposits of any non-compliant players.
Central to Bitcoin and its security is a public ledger of transactions (TXs),
individually validated by every node on the network. This ledger is backed by
a blockchain maintained by the network, where the difficulty of reverting the
chain grows exponentially with the number of blocks to revert. [3]
Implementing a protocol for playing a game entirely in Bitcoin is made possible by scripted transactions, and has been done by Andrychowicz et al. [3] and
Bentov and Kumaresan [4] in the form of lotteries. However, as these games encode the entire set of rules and inputs in TXs on the blockchain, scalability quickly
becomes a problem. Every single node on the Bitcoin network must simulate the
game with all its rules and the inputs of players, in the form of transaction scripts,
to verify the transactions. The language used in the transaction scripts is limited,
and inefficient workarounds often have to be used [8]. A much more complex
program like a TBS game would be impractical to simulate completely in the
form of Bitcoin TXs, if at all possible.
Butterin et al. [8] address the limitations of using Bitcoin to implement transactions with arbitrary rules. Their platform, Ethereum, uses a blockchain like
Bitcoin, but with more complete scripting abilities. However, scalability is still an
issue, as all game rules and inputs must still be encoded in the block chain and
verified by every node in the network.
A possible way of interfacing with Bitcoin with minimal trust would be to have
a set of oracles each evaluate the outcome of a game and sign transactions based
on this evaluation. This method would use a multi-signature scheme requiring a
majority of the oracles to sign each transaction. Implementations of such oracles
include Orisi and First Temple [16, 5]. This method does require trust in the
majority of the oracles, but it is an improvement over a single TTP.
6
Mental poker for turn-based strategy games – securing fairness without a trusted third party
Chapter 3
Protocol
The following proposed protocol requires a cipher with the following properties:
1.
2.
3.
4.
Ek (x) is the ciphertext of a message x under key k.
Dk (Ek (x)) = x for all messages x and keys k.
Ek (Ej (x)) = Ej (Ek (x)) for all messages x and keys j and k.
Given any number of messages xi and their corresponding ciphertexts
Ek (H(xi )) and a message y, it is impossible to derive Ek (H(y)), for all k.
5. Given any messages x and y, it is impossible to find keys j and k such that
Ej (x) = Ek (y).
6. Given messages x and y, it is impossible to distinguish Ek (x) and Ek (y).
7. Given message x, it is impossible to correlate Ek (x) and Ej (x) as long as k
and j are not related.
Properties 1 to 5 are identical to those required by Shamir, Rivest and Adleman [20], except that their property 4 is replaced here by a stronger property.
Properties 6 and 7 are necessary to ensure that a player can obtain randomness
privately, i.e. without any other party learning anything about what the randomness is for.
In addition, the solution uses some function H(x) to convert x into something
that can be encrypted by the cipher.
A cipher with the above properties is given in section 3.6.
3.1
Game initialisation
3.1.1
Game parametres
Before starting, the players agree on the specific game rules and key sizes.
The values agreed upon include
l, the security level in bits, e.g. 64,
µ, the size of the modulus in bits, e.g. 768,
7
Mental poker for turn-based strategy games – securing fairness without a trusted third party
the amount of funds that each player should lock into the contract.
m should have a value closely matching the expected requirement for a security level of s bits, as a value that is too low will decrease the overall security
level, while a higher value will decrease performance without increasing the security level. Key sizes are discussed in chapter 5.
3.1.2
Private keys
The players generate the private keys that they will use for the cipher described
in section 3.6:
Alice selects a cryptographically secure random integer kA from the interval
[1, 22s − 1],
Bob selects a cryptographically secure random integer kB from the interval
[1, 22s − 1].
These keys will be revealed at the end of the game, either voluntarily or as
required by the contract.
3.1.3
Contract
The players now generate and sign the initial transactions (TXs) that make up
the contract described in appendix A.
For signing TXs that are part of the contract, the players use a different set of
keys than the ones used in the cipher:
Alice generates a private key dA and reveals the corresponding public key
QA for use with the contract.
Bob generates a private key dB and reveals the corresponding public key
QB for use with the contract.
These keys do not necessarily have to be unique to a game session, but generating new keypairs whenever possible improves privacy.
To be able to know the address that the first TX, txFunding, should pay to,
the players need each other’s blockchain public keys and the hashes of the two
merklised abstract syntax tree (MAST) subscripts that provide kA and kB .
Alice reveals PA = Hash256(xkkA ), where x is the byte length of kA when
serialised,
Bob reveals PB = Hash256(ykkB ), where y is the byte length of kB when
serialised.
From this information, the players are each able to construct the MAST and
the corresponding output of txFunding.
Each player also needs to provide the inputs they want txFunding to redeem,
as well as a change output if necessary. Each player
provides their inputs and change outputs,
verifies that the other player covers a fair amount of the contract output
8
Mental poker for turn-based strategy games – securing fairness without a trusted third party
plus any TX fee deemed necessary,
constructs an unsigned version of txFunding by arranging the inputs and
outputs in a particular deterministic way.
The players should typically provide equal amounts of funding, but a player
that increases the TX size significantly by e.g. adding many inputs may have to
cover a larger part of the TX fee.
Before signing txFunding, each player needs to make sure that they can terminate the contract without losing their funds.
Alice signs txResignB,A , txRevealB,A and txDefaultB,A ,
Bob signs txResignA,B , txRevealA,B and txDefaultA,B .
These signatures use the hash type SINGLE, which means the other player can
add other inputs and outputs to the TX, as long as the already signed input–
output pair remains unchanged.
Once the players have received the correctly generated and signed TXs, they
can finally sign the txFunding. Each player
3.1.4
provides the signatures and other witness data necessary for txFunding to
redeem their inputs,
broadcasts the fully signed TX to the network if necessary.
Modulus
For reasons described in chapter 5, the cipher given in section 3.6 needs each
game session to generate a new modulus that cannot be guessed in advance by
either player. This modulus is generated as follows:
1. Alice calculates hA = H(kA k“MODULUS”) and reveals cA = H(hA ). Bob
calculates hB = H(kB k“MODULUS”) and reveals cB = H(hB ).
2. Alice reveals hA . Bob reveals hB .
3. Alice verifies that cB = H(hB ). Bob verifies that cA = H(hA ).
4. The modulus p is calculated as the lowest value found to be a safe prime
such that p 2µ − (hA XOR hB ).
Finding a large safe prime may take several seconds of CPU time, as the
average difference between adjacent safe primes in a given range increases
quadratically with the distance between primes in the same range. The workload can be divided among the players, e.g. by having Alice search assuming that
p mod 24 = 11 and Bob search assuming that p mod 24 = 23.1 If Bob finds a
valid safe prime, is not strictly necessary for Alice to verify that this is the lowest
safe prime Bob could find, as long as she verifies the result to be a safe prime
and has not found a lower safe prime in her part of the search space.
1
Given a safe prime p > 7, p mod 12 = 11, therefore p mod 24 2 11, 23.
9
Mental poker for turn-based strategy games – securing fairness without a trusted third party
3.1.5
The game state
All progress in the game happens through passing modifications of the game
state object back and forth between the players. Along with every state update,
the player responsible for the new state generates and signs a txWithState TX
which the other player can use to redeem the full amount of funds locked in the
contract if the update is later found to have been against the game rules. To be
able to provide compact proofs in case of cheating, the game state is stored in a
Merkle tree.
Depending on the amount of computation that may fit in a TX, the steps involving private computation according to game rules may result in a series of
intermediate game state objects, each of which has a signed TX associated with
it.
3.1.6
Initialisation of the game state
The initial game state is fully known by both players. The first two updates consist of initialising the private state of each player. The private state of a player
is used to hold the data that should be available only to that particular player,
along with computations involving such data.
1. Alice takes the initial state s0 and derives s1 by performing a state update
as follows:
1. Set the StateMessage field to InitPrivate.
2. In the StatePrivateA subtree, set the PrivateExponent field to kA .
Alice generates and signs txWithState1,A,B and sends it to Bob along with
state1 .
2. Bob takes s1 and derives s2 by performing a state update as follows:
1. In the StatePrivateB subtree, set the PrivateExponent field to kB .
Bob generates and signs txWithState2,B,A and sends it to Alice along with
state2 .
3.2
Privately retrieving randomness
If Alice needs to derive deterministic randomness rx from some identifier x, she
performs the following:
1.
2.
3.
4.
Let f = H(“BLINDING FACTOR”kkA kH(x)).
Let c = EfkA (H(x)).
Query Bob for the value of c = EkB (c).
Calculate the result as rx = H(Df (c )).
0
0
Bob does not learn the value of x or rx from the query, since the data Bob
operates on has been encrypted with f which is used only for this single query.
Properties 6 and 7 ensures that Bob is unable to distinguish this data from any
other encrypted data. Due to property 4, Alice only learns rx from the query and
10
Mental poker for turn-based strategy games – securing fairness without a trusted third party
can’t use it to learn rx , for any x where x =
6
x.
0
0
0
3.3 Updating and retrieving encrypted game state
The encrypted game state EGSi is a mapping used to store dynamic parts of the
game state si which either player should be able to access privately (i.e. without
the other player’s knowledge) when allowed by the game rules. This includes
buildings, units and removal of depleted resources.
If, in state si , Alice needs to update the state to si such that some EGSi (x)
decrypts to the value y, she performs the following:
0
1.
2.
3.
4.
Let f = H(“BLINDING FACTOR”kkA ki).
Let c = EfkA (AONT(y)).
Query Bob for the value of c = EkB (c).
Update the encrypted game state such that
0
EGS i0 (x)
= Df (c ).
0
Alice reveals that she is writing some value to x, but Bob has no way to know
the unencrypted value from this process alone. To also hide what Alice is updating unless Bob already has access to the thing being updated, such as a particular
location, x may itself be derived from private randomness retrieval as described
in section 3.2.
Retrieval and decryption of a particular value reveals neither the identity nor
the value to the other player. If, in state si , Alice instead needs to retrieve the
decrypted value of EGSi (x), she performs the following:
1.
2.
3.
4.
3.4
Let f = H(“BLINDING FACTOR”kkA ki).
Let c = DfkA (EGSi (x)).
Query Bob for the value of c = DkB (c ).
Read the decrypted value as y = AONT(Ef (c )).
0
0
0
Verification
A player could easily try to cheat by retrieving randomness data that the player
shouldn’t have access to. Such cheating can only be detected once the game
has ended and the players reveal their private keys. However, other types of
cheating may be detected during gameplay. In such cases, the player detecting
the cheating may decide to immediately force the contract to close as described
in section 3.5, as they are then guaranteed to be able to redeem the funds of the
cheating player and probably have no interest in playing further.
3.5 Ending the game
The game normally ends when dictated by the game rules, such as when a player
has reached a winning condition or a player chooses to resign. Once Alice and
Bob agree that the game has ended, they reveal kA and kB , respectively. After
obtaining each other’s private keys, they examine each other’s actions to verify
that they were all according to the rules. If so, they agree to redeem the funds
11
Mental poker for turn-based strategy games – securing fairness without a trusted third party
locked in the contract using progMain(GENERIC), splitting the funds between
each other.
However, either player can also forcibly close the contract at any time, e.g.
because the other player is not responsive. This is done by publishing a txResign
TX , as described in the following example:
If Alice resigns and reveals kA by publishing txResignA,B , Bob must respond by
publishing either txRevealB,A or, if Alice cheated in some state si , txWithStatei,A,B
within some previously agreed upon number of confirmations. Otherwise Alice gets to spend Bob’s funds by redeeming the output of txResignA,B using
progRevealA,B (TIMEOUTA ). Then, similarly, if Bob has responded by publishing
txRevealB,A , revealing his private key kB , Alice must respond by publishing either
txDefaultA,B or, for some state sj in which Bob cheated, txWithStatej,B,A within
the same agreed upon number of confirmations. Otherwise Bob can spend the
funds using progRevealB,A (TIMEOUTB )
If no player has cheated, they will end up with txDefaultA,B , which was previously signed by Bob as described in section 3.1.3. This TX pays half of the contract
funds to outputs previously chosen by Bob, with the rest going to outputs that
Alice may choose.
If either player decides to publish a txWithState TX, they then need to provide another TX containing the evidence of cheating. If e.g. Bob publishes
txWithStatei,A,B , he must then create some TX that redeems the output of
txWithStatei,A,B using progWithStatei,A,B (CHEATi,j,A,B ), for some j, within the
agreed upon number of confirmations. Otherwise Alice can spend the funds using progWithStatei,A,B (TIMEOUTA ).
3.6 Cipher
While the Pohlig–Hellman cipher has most of the required properties, in particular property 3, it does not fully meet the requirements for properties 6 and 7,
because the ciphertext Ek (x) = xk mod p reveals whether the plaintext x is a
quadratic residue modulo p [6].
Properties 6 and 7 depend on the decisional Diffie–Hellman (DDH) assumption. In the case of exponentiation modulo a safe prime p as recommended by
Pohlig and Hellman [18], the assumption is in general only believed to hold if
the messages are limited to the group of quadratic residues modulo p [6].
When working modulo a safe prime, turning quadratic nonresidues into
quadratic residues can be done simply by negation2 . The DDH assumption can
therefore be maintained by negating any nonresidue before encrypting. The
same can be achieved by using even exponents as keys3 . If all original messages
2
For any modulus p mod 4 = 3, the negative of a quadratic nonresidue is a quadratic residue
[14]. p mod 4 = 3 for all safe primes p 6= 5, since p = 2q + 1 where q is odd.
k
3
For any even exponent k, xk is congruent to the square of x 2 modulo m. xk is therefore a
quadratic residue modulo m.
12
Mental poker for turn-based strategy games – securing fairness without a trusted third party
, then property 2 can still be maintained.
are kept within 1 < x p−1
2
Using conditional negation modulo p, it is possible to define a variant of the
Pohlig–Hellman cipher that meet the requirements for properties 6 and 7 [11]:
Modulus: p = 2q + 1, where p and q are large primes.
Key: 1 < k < q − 1.
Message: 1 < m q.
Encryption: Ek (m) = min(mk mod p, p − mk mod p).
Decryption: Dk (c) = Ex (C), where kx mod q = 1.
The modulus is a safe prime, as recommended by Pohlig and Hellman [18].
However, there is no requirement that the key be an odd number, as would be
required in the original cipher. An even exponent projects the number onto the
group of quadratic residues, and has no multiplicative inverse modulo p−1 = 2q.
A multiplicative inverse modulo q is used instead, which may negate the result
with respect to the original. Since the modified cipher conditionally negates results such that Ek (m) q, a negation in the exponentiation step has no effect.
Since Ek (m) behaves exactly like the Pohlig–Hellman cipher when mk mod p q
and simply negates otherwise (and (−m)k = mk ), properties 2 and 3 remain
valid.
For property 4 to hold, an appropriate function is needed for H(m). If, for
example, H(m) = m, then due to the multiplicative property of the cipher, given
x, y, Ek (H(x)) and Ek (H(y)), one would also know Ek (H(x y)). Any secure hash
function will do, but a suggestion is H(m) = SHA-256(m) + 2256 . This function
works for all m as long as p > 2258 and is simple to evaluate in a Bitcoin TX.
13
Mental poker for turn-based strategy games – securing fairness without a trusted third party
Chapter 4
Map generation
While any procedural generation algorithm could be adapted to work with the
method in section 3.2, the degree to which generation can be kept local varies.
For example, an algorithm that determines sea level by taking the average of all
points on a height map would require the whole height map to be generated,
providing no locality at all. At the other extreme would be an algorithm where
all sample points are independent of each other. A typical game map consists
of features of different sizes – e.g., different biomes at a very large scale, and
certain resources like individual materials and food at a very small scale. Some
features, such as the height of the terrain, vary both at large and small scales.
Such features may be combined by several noise functions operating at different
scales, each generated independently with optimal locality.
When used for the purpose of generating maps deterministically and with fog
of war, a good noise function
keeps information exposure as local as possible when sampling,
has a consistent probability distribution from cell to cell,
does not require more queries than real-time performance allows,
is simple to implement using exact operations so that inconsistencies do
not occur between players.
Figure 1a shows a texture generated by the midpoint displacement algorithm
as used by Rice [19] (there referred to as diamond-square). As can be seen in
figure 1b, this algorithm is unsuitable for keeping map information local. Ideally
this figure would show a single dark spot concentrated around one point. However, in the midpoint displacement algorithm, sampling a single point anywhere
on the map requires the values at the corners of the map, which result in full
knowledge of the values along the edges of the map. At each subdivision level,
the midpoint in the square surrounding the sampling point creates a cross filling
the square, along which map values are known exactly. In fact, there is no point
of the map where knowledge is zero, since the value at the midpoint of the map
14
Mental poker for turn-based strategy games – securing fairness without a trusted third party
Figure 1: Midpoint displacement texture
(a) Fully generated texture
(b) Uncertainty after sampling one point.
Black corresponds to full knowledge,
white corresponds to no knowledge (in relation to the map’s maximum variance)
must be known and affects every part of the map.
Extending the midpoint displacement algorithm to a diamond-square algorithm would add some uncertainty along the distant edges, but the corners at
each subdivision level would remain.
Approximations of fractional Brownian motion (fBm) can be achieved by using other noise functions as well. This is generally done by combining noise
functions at different scales and different amplitudes, such that the amplitude
is proportional to the scale, thus approximating the frequency spectrum of fBm
[12]. Figure 2 shows a texture generated in this way, using convolution as described further in section 4.1.
The non-local nature of fBm means that it is not possible to achieve true
fBm without revealing some non-local information. However, by combining noise
functions at different scales, each layer of noise can have its locality optimised.
Figure 2b shows knowledge of the map after sampling a single point. Compared
to the results from the midpoint displacement algorithm in figure 1b, this layerbased algorithm is able to keep knowledge of the map much more local.
4.1
Algorithms
At the lowest noise scale, each cell simply gets its own value independent of the
other cells. At higher scales, however, values change more slowly from cell to
cell. This can be achieved in two ways: either by interpolating between points
in a larger grid, or by averaging across multiple nearby points in the underlying
grid to get the value for each cell. Combining the two methods is also possible.
The interpolation algorithm consists of making a grid and assigning each grid
15
Mental poker for turn-based strategy games – securing fairness without a trusted third party
Figure 2: Approximation of fBm by convolution
(a) Fully generated texture
(b) Uncertainty after sampling one point
point a random value. The grid size is some integral multiple of the size of the
map cells. Map cells that lie between grid points get their values by interpolating
between the surrounding grid points. In a map of hexagonal cells, each cell may
be surrounded by up to three grid points that make a triangle. By converting
the coordinates of the cell to triangular coordinates within the surrounding grid
points, the value from each grid point can be weighted to determine the interpolated value. This conversion is simple, since the hexagons can be represented
using axial coordinates that translate directly to triangular coordinates.
Interpolation can be a problem in some cases, because it results in a varying
probability distribution. In the simple case of linear interpolation between two
independent variables each with the same uniform distribution, the midpoint
has a triangular probability distribution, with only half the variance compared to
the two variables. This may create visible artefacts or otherwise complicate the
process of converting these values to map data.
Averaging across multiple cells to blur the noise texture can be done in a convolution process. Figure 3 shows some masks that can be used to filter hexagonal
cells. Figure 4 shows the same noise texture filtered with each of these masks
(adjusted to maintain equal variance). Some artefacts revealing the edges of the
masks can be seen, particularly with the largest masks. This effect can be reduced
by softening the edges of the kernels. In figure 5, the masks have been modified
such that each of the outermost cells only contributes half as much to the final
value as each inner cell. The same noise texture filtered with the modified masks
can be seen in figure 6.
The convolution algorithm is quite simple: Each cell is assigned a random
value. A suitable convolution mask, such as one shown in figure 5, is chosen.
Then, regular convolution is performed. For each cell to be sampled, the mask is
centred on the cell, and each cell value of the mask is multiplied with the value
16
Mental poker for turn-based strategy games – securing fairness without a trusted third party
Figure 3: Convolution kernels
(a) Single cell
(b) 3 cells wide
(c) 5 cells wide
(d) 9 cells wide
Figure 4: Convolution filtering
(a) No filter
(b) 3-cell filter
(c) 5-cell filter
(d) 9-cell filter
Figure 5: Modified convolution kernels
(a) 3 cells wide
(b) 5 cells wide
(c) 9 cells wide
Figure 6: Filtering with modified kernels
(a) No filter
(b) 3-cell filter
(c) 5-cell filter
17
(d) 9-cell filter
Mental poker for turn-based strategy games – securing fairness without a trusted third party
of the underlying cell. These products are then averaged to get the sample value.
An additional step is to adjust for the loss of variance when averaging: Assuming
that the average is zero, the value is multiplied by
Pn
wi
,
Pi=1
n
2
i=1 wi
p
where w1 . . . wn are the values of the convolution mask. This multiplication uses
a square root, which is a potentially inexact operation. If the masks are constant,
this can be solved by having the players agree on this value before starting the
game. Otherwise, an integer square root may have to be used.
At very large scales, convolution may become a performance issue. Sampling
a texture for the first time takes as many queries as there are cells in the convolution mask, i.e. O(N2 ), where N is the width of the mask. Further samples will
generally be taken next to existing samples, thus requiring only O(N) queries.
Performance is therefore mostly an issue in the beginning of the game, although
O(N) queries per move could still be too demanding.
Reducing the size of the required mask while keeping noise at the same scale
requires scaling up the underlying grid, introducing the need for interpolation.
The aforementioned effect on the probability distribution is reduced, however,
as the convolution filtering causes neighbouring cells to be highly correlated.
Convolution and interpolation may be combined by making a grid like in
the interpolation algorithm, performing convolution across this grid (by treating each grid point as a cell), and then interpolating from the filtered grid.
18
Mental poker for turn-based strategy games – securing fairness without a trusted third party
Chapter 5
Security parametres
When deciding on the security parametres of a cryptosystem, the security level is
often set with the goal of ensuring protection against the strongest possible adversaries for the next several years to decades. The cryptosystem in this project
only protects the secrecy of game data for the duration of a game, which is typically on the order of hours. In the current design, breach of this cryptosystem
only results in one party gaining read access to game state that should have been
hidden. A player that manages to break the cryptosystem cannot take advantage
of this to e.g. steal the funds that the other player has deposited in the contract,
as the contract is protected by a separate, more secure cryptosystem. The combination of a very limited timeframe and low impact in case of a breach means
that a relatively low security level may be acceptable.
In their system designed for real-time strategy (RTS) games, Bursztein et al.
[7] use a 127-bit elliptic curve for securing a game lasting about an hour. They
estimate an attack to take about 6400 core-years1 . In a modular exponentiation
cipher, a 768-bit modulus would give comparable security with an estimated
36,500 core-years to crack [2]. Such an attack would break the security of that
particular modulus: after the first step, any key using the same modulus may be
derived in only 2 core-days [2]. This problem can be addressed by ensuring that
each game session uses a unique modulus, preferably such that no player can
guess the modulus ahead of the game.
Smaller exponents result in faster exponentiation [2]. In order to maintain
the security of the cipher, the key should have a size of at least twice the security
level provided by the modulus [17]. For a 768-bit modulus, this corresponds
roughly to a 128-bit key2 .
1
3200 machine-years on a machine with 2 physical cores.
This comes from comparing the 768-bit modulus to the 127-bit elliptic curve (q
p
which has a security level of approximately 64 bits (cracking takes O( q) steps) [7].
2
19
2127 ),
Mental poker for turn-based strategy games – securing fairness without a trusted third party
Chapter 6
Performance analysis
The following calculations concern the algorithms for approximating fractional
Brownian motion (fBm) on a 2D map of hexagonal cells. N is the number of
levels above single-cell, and the scale doubles at each level. Calculations of the
upper bounds for a single sample are given in table 1.
With pure convolution and masks likePthose in figure 5, the number of cells
in a mask with width 1 + 2n cells is 1 + ni=1 6i = 3n(n + 1) + 1. At level l > 0,
P
i
i
N
N
n = 2l−1 . To sample a single point, 1 + N−1
i=0 3(4 + 2 ) + 1 = N + 3(2 − 1) + 4
queries
made. To sample a point next to a previously sampled point,
P must be i−1
1
+
2(2
) = N + 2N+1 − 1 new queries must be made.
1+ N
i=1
Using interpolation without convolution, up to 3 queries are needed at each
level above 0. Sampling a single point takes between N + 1 and 3N + 1 queries,
while sampling a point next to a previously sampled point takes between 1 and
N + 1 new queries.
Combining convolution and interpolation, limiting the mask to level M, is
equivalent to M-level pure convolution plus 1 to 3 level-M convolution samplings at each of the N − M interpolation layers. For a single sample, the interpolation layers need an additional (N − M)(3(4M + 2M ) + 1) to (N − M)(3(4M +
2M ) + 1 + 2M + 4(2M ) − 2) samples1 , while sampling a point next to a previously
sampled point takes an additional 0 to (N − M)(M + 2M+1 − 1) queries for the
interpolation layers.
Taking into account the performance of encryption, convolution is feasible up
to level 5. Level 6 may require M = 4, level 7 M = 3.
1
The upper bound is a pessimistic approximation based on one first sample and two neighbouring samples, which doesn’t take into account the overlapping queries of the neighbouring
samples.
20
Mental poker for turn-based strategy games – securing fairness without a trusted third party
Table 1: Number of queries for a single sample
N
convolution
M=4
M=3
M=2
M=1
interpolation
6.1
1
2
8 27
- 27
8 35
4
7
3
4
5
6
7
8
88 305 1122 4291 16772 66309
- 305 1192 2079
2966
3853
88 341
594
847
1100
1353
106 185
264
343
422
501
62
89
116
143
170
197
10
13
16
19
22
25
Blockchain scripts
For the blockchain to be able to determine the outcome of a game, a player needs
to be able produce a transaction that contains the information necessary to show
that the other player has cheated. For a complex game, the whole set of rules
and game state will typically be too large to fit in a single transaction. However,
only the parts of the rules and game state relevant to a single action need to be
evaluated on the blockchain. By using a merklised abstract syntax tree (MAST),
a transaction output can be made valid for a large set of shorter scripts, each
covering their part of the game rules, and only the relevant script needs to be
specified in the redeeming transaction. BIP 114 [15] is a proposal to add MAST to
Bitcoin. Similarly, the full game state can be kept in a Merkle tree and only have
the evaluated parts revealed to a script. This technique can already be used with
the operations available to Bitcoin scripts today.
Arithmetic in Bitcoin scripts is currently limited to addition and subtraction
on operands limited to 32 bits (including a sign bit). In particular, multiplication and remainder operations are not available. A single script is also limited
to 201 operations. While it is theoretically possible to implement a contract under these limits, the number of scripts for intermediate states that would have
to be produced and signed for each encryption operation would quickly become
a bottleneck for game performance. The current reference implementation for
BIP 114 [15] does enable more arithmetic operations, including multiplication
and remainder, and extends the range of operands to 56 bits. These extensions
reduce the complexity of implementing modular exponentiation, although a single modular exponentiation with a modulus large enough for security would still
not fit in a transaction (TX).
A single modular exponentiation should ideally be able to fit in a TX along
with two signature verifications and some simpler arithmetic and logic. The
blockchain design described in the appendix includes an additional operation
to perform modular arithmetic. However, as this operation is computationally
expensive compared to any other currently available operation, its implementation may require putting additional limits on a TX using this operation.
21
Mental poker for turn-based strategy games – securing fairness without a trusted third party
The current design described in appendix A, if used together with the current MAST proposal specified in BIP 114 [15], has a performance issue that may
limit how fast a player can generate txWithState TXs. A TX of this kind must be
generated for every state update for the other player to accept it. The output
program of this TX, progWithState, may have a large number of CHEAT branches,
each of which must contain the new state and must be individually hashed and
then combined in order to obtain the program hash to use as the output in the
txWithState TX. A small change to the contract fixes this issue for intermediate
states, i.e. those states which are updated by the same player who made them:
a single txWithState may include the root of a Merkle tree containing several
intermediate states rather than a single state. That way multiple state updates
may be provided for each MAST of a txWithState.
22
Mental poker for turn-based strategy games – securing fairness without a trusted third party
Chapter 7
Enforcing fair play
With the peer-to-peer protocol alone, players have a way to provide evidence
to each other at the end of the game showing that they have played by the
rules. However, since the players cannot necessarily rely on a common trusted
third party (TTP) to rule against a cheating player, there may be little incentive a
player to play fairly and provide evidence of fair play.
By integrating a blockchain-enforceable contract with the protocol, the contract can take funds from the players at the beginning of the game, to be returned
to the players once they have verified each other’s actions. If e.g., at the end of
the game, Bob refuses to provide his key kB , Alice can invoke the contract to
make Bob either reveal the key or lose his part of the funds. If the key then reveals that Bob cheated, Alice can use this evidence with the contract in order to
redeem Bob’s funds.
Appendix A describes a contract designed to run on a version of the Bitcoin
blockchain with some extensions, one of which is currently specified in BIP 114
[15].
An important thing to note is that, while the game rules enforced by the contract may be Turing complete, it is not necessary for the scripting system to
be Turing complete. Bitcoin’s scripting system is not Turing complete; Turing
completeness makes the execution of a program difficult to reason about and is
therefore avoided where program execution should be kept simple. The scripting
system in Bitcoin is part of the consensus layer, meaning that every node in the
network must agree exactly with the other nodes in how a script executes.
The current contract does not attempt to enforce progress of the game. Enforcing progress mainly becomes a problem if the game involves a prize to be
paid to the winner. If Bob thinks that Alice is going to win the game, he may decide to halt the game by not providing updates to Alice. At that point it becomes
a waiting game to see who first resigns in order to get their locked funds back.
Instead, either player has the opportunity to resign at any time by broadcast-
23
Mental poker for turn-based strategy games – securing fairness without a trusted third party
ing their version of a particular transaction (TX), txResign, which reveals their
private key.
Given a blockchain with more advanced features, the contract could try to
enforce progress by forcing players to communicate over the blockchain in case
they are unable to communicate directly. For a complex game such as a turnbased strategy (TBS) game, this would likely end up being both slow and expensive, however. In the simplest variant of such a contract, the non-responding
player could wait almost the whole duration of some timeout before providing
their update. Such timeouts need to be long, on the order of hours or days, e.g. in
case there are capacity issues on the blockchain or in case many blocks happen
to appear in quick succession. An improvement over this design could apply increasing costs depending on how many blocks an update is provided. A stalling
player not willing to pay more than the other player would then be likely to
respond within the next block. Further, the contract could make use of replaceby-fee (RBF) signalling to let the players pass updates back and forth in the form
of multiple unconfirmed TXs over the network as if in a game of hot potato, with
the player getting a TX of theirs onto the blockchain having to pay less than the
other player. However, even with this improvement the process would likely end
up being too slow and expensive, as each TX needs a higher fee than the TX it
replaces and there’s a limit to how many replacements can happen in a row.
24
Mental poker for turn-based strategy games – securing fairness without a trusted third party
Chapter 8
Conclusion and future work
The work on this project has resulted in the design of a protocol for playing
a turn-based strategy (TBS) game between two players over a network, including map generation under fog of war, in a way that dissuades cheating and lets
the players know whether the game was played fairly. This protocol consists of
several modular components, including privately retrieving deterministic randomness and using a blockchain contract to make cheating prohibitively costly.
One of the obvious next steps to make from this work is to implement a working game that fully implements the designed protocol. A large part of such a
project would be to make all the contract scripts necessary to evaluate game
state updates. Perhaps the project should begin with developing better tools for
generating such scripts.
The modular nature of the protocol should make it easy to adapt parts of
the protocol to other applications. Adaptations of the protocol as a whole may
be suitable for certain other types of games. Real-time strategy (RTS) games in
particular are very much like TBS games other than how time is handled. Other
games of interest may be games in the style of Minecraft, where players can
explore a 3D world, digging below the surface and collecting resources.
The current protocol is designed for two players for simplicity. Extending the
protocol to three or more players is a potential task for future work. Such an
extension opens up for some interesting possibilities. One is the anonymisation
of in-game actions, so that a player cannot tell which of the other players is
providing a certain state update. Another is continuing the game with one player
less if a player drops out without revealing their private key. A potential challenge
in enabling such a feature would be map re-generation. One issue that comes up
when there are more than two players is that some players could collude in ways
that are out of the control of the game rules.
Enforcing game progress has been deemed infeasible as part of this project,
but simpler games that may use a similar protocol could potentially implement
25
Mental poker for turn-based strategy games – securing fairness without a trusted third party
the such enforcement via the blockchain.
26
Mental poker for turn-based strategy games – securing fairness without a trusted third party
Appendix A
Blockchain details
The following shows an example of a blockchain design and necessary transactions (TXs) for a 2-player game. It is not meant to provide a complete specification.
A.1
Features
For the blockchain to be able to securely determine the outcome of a game,
it needs to have all operations necessary to evaluate the game rules. However,
Turing completeness is not necessary in the scripting system.
Big integer arithmetic, up to e.g. 1024 bits
Basic modular arithmetic
Modular exponentiation
Merklised abstract syntax tree (MAST), e.g. as specified in
BIP
114 [15]
Arithmetic operands are currently limited to 32-bit signed integers in Bitcoin.
Some operations are disabled, in particular multiplication and remainder. Native
support of big integer arithmetic is not strictly necessary, but the scripts necessary
to prove certain cheating get more complicated and require more intermediate
states without this support.
A.2
Objects
Objects are structured in the form of multi-level Merkle trees. That way, TXs only
include the parts of the objects that are relevant to validation.
A.2.1
State
The state object contains the whole game state and history.
Given a state s in a game between two players a and b,
π(s) is the state that directly precedes s,
δ(s) is the update that α(s) performed on π(s) to produce s,
27
Mental poker for turn-based strategy games – securing fairness without a trusted third party
εp (s), for each player p 2 {A, B}, is the secret exponent of p,
Every state object contains the secret exponents of each player for easy access
by the CHEAT branches described below. This may seem like a problem as each
player has a copy of the state object but is not supposed to know the secret
exponent of the other player until the game has finished. However, as the object
is constructed as a Merkle tree, only the hashes of the exponents are shared
during the game.
A.3
Branches
A program consists of one or more branches, one of which is selected for execution by the redeeming TX.
Each point under each branch signifies a subscript. The subscripts are individually hashed when constructing the MAST, so one subscript can go into multiple
branches without having to process large amounts of data for each branch.
A.3.1
REVEAL p
cRevealPushScript Pp A B
See Game.hs for the definition of cRevealPushScript. Using Compile.hs, this
code generates the following script:
push QA ; OpCheckSigVerify; push QB ; OpCheckSigVerify; push Pp ;
OpSize; OpDup; OpToAltStack; Op1; Op16; OpWithin; OpVerify OpFromAltStack; OpSwapCat; OpHash256; OpEqualVerify; Op1; OpGreaterThan;
OpVerify
A.3.2
A.3.3
GENERIC
push QA ; OpCheckSigVerify; push QB ; OpCheckSigVerify
TIMEOUT p
push x; OpCheckSequenceVerify; push Qp ; OpCheckSigVerify,
where x is the TX input sequence number that encodes a relative locktime of
a certain number of blocks.
A.3.4
CHEAT i,j,p,q
push kp
push si
push Qq ; OpCheckSigVerify; cheatedj
cheatedj here expands to one of several strings of operations, essentially one
for each rule in the game. See cCheat in Game.hs which implements some of
these branches.
The push kp subscript is initially only known by player p. The hash of this
subscript is the commitment that p must reveal in the input TX. Since player q
knows the hash and that this hash must correspond to kp , they know the script
28
Mental poker for turn-based strategy games – securing fairness without a trusted third party
will work as intended.
A.4 Programs
Each output of a TX defines a program that must execute successfully in a TX
trying to redeem that particular output. A program is structured as a MAST, so
that only the branch selected for execution needs to be provided.
A.4.1
A.4.2
A.4.3
progMain
{REVEALA , REVEALB , GENERIC}
progRevealp,q
{REVEALq , GENERIC, TIMEOUTp }
progWithStatei,p,q
{CHEATi,j,p,q | j 2 {0..n}} [ {TIMEOUTp }
A.5 Transactions
As in Bitcoin, a TX consists of one or more inputs and one or more outputs. Each
input contains a script and a reference to the output of an earlier TX for this TX
to redeem. The script consists of the branch to be evaluated in a MAST program
whose root hash is specified by the output, along with data to be passed to this
program, such as the variables in the contract described below. A TX is only valid
if all of the following are true:
The root hash of the MAST in each input script matches that specified by
the output it refers to.
Every input script evaluates to true.
The sum of output values is equal to the sum of input values minus the TX
fee.
A.5.1 txFunding
The game starts with a funding TX that establishes the contract that can be used
to enforce all the rules of the game. Typically each player provides half of the
funds available to the contract and receives their part back once the game has
finished. If a player p is found to have cheated or has dropped out without properly ending the game, p loses the funds to the other player q.
Besides change outputs, the TX will typically include one output:
progMain,
which receives the sum of funds that should be locked by the contract.
While the funding TX determines all rules and further possible TXs, the TX will
usually be quite small. The defining part of the TX is the output described above,
which only contains the 32-byte root hash of the combination of possible scripts.
29
Mental poker for turn-based strategy games – securing fairness without a trusted third party
A.5.2
txResignp,q
1. Signatures
σp , with hash type ALL
σq , with hash type SINGLE
2. In: txFunding
Stack: σA , σB , kp
progMain(REVEALp )
3. Out: progRevealp,q
A.5.3
txRevealp,q
1. Signatures
σp , with hash type ALL
σq , with hash type SINGLE
2. In: txResignq,p
Stack: σA , σB , kp
progRevealq,p (REVEALp )
3. Out: progRevealp,q
A.5.4
txWithStatei,p,q
Produced and signed by p and given to q for every state si generated by p.
To be signed and broadcast by q once q knows kp (revealed in input TX) if q
finds a way to spend it using CHEAT. q would never sign and broadcast this
TX unless p has cheated, otherwise q could not spend and p could spend
everything via TIMEOUTp .
Problem: CHEAT creates a large hash tree where every branch must be
recreated for every state.
1. Signatures
σp , with hash type SINGLE + ANYONECANPAY
σq , with hash type ALL
2. In: {txResignp,q , txRevealp,q }
Stack: σA , σB
progReveal(GENERIC)
3. Out: progWithStatei,p,q
A.5.5
txDefaultp,q
1. Signatures
σp , with hash type ALL
σq , with hash type SINGLE
30
Mental poker for turn-based strategy games – securing fairness without a trusted third party
2. In: txRevealq,p
Stack: σA , σB
progReveal(GENERIC)
3. Out: 50% of funds minus fee: spendable by Qq
31
Mental poker for turn-based strategy games – securing fairness without a trusted third party
Appendix B
Source code
B.1
Blockchain.hs
{-# LANGUAGE DeriveGeneric, FlexibleInstances, FunctionalDependencies,
GeneralizedNewtypeDeriving, MultiParamTypeClasses, TemplateHaskell
#-}
module Blockchain where
import Control.Applicative
(Applicative, pure, (<*>))
import Control.Lens
(ASetter, Getting, abbreviatedFields, assign, makeFields, makeLenses,
makeLensesWith, over, use, view, (%=), (%~), (.=), (^.))
import Control.Monad
(ap, forM, forM_, guard, mapM_, replicateM, void, when)
import Crypto.Error
(CryptoFailable (CryptoPassed))
import Crypto.Hash
(Digest, HashAlgorithm, RIPEMD160 (RIPEMD160), SHA256 (SHA256),
digestFromByteString, hash, hashDigestSize, hashWith)
import Crypto.Number.ModArithmetic (expFast)
import Crypto.PubKey.Ed25519
(PublicKey, SecretKey, Signature, publicKey, signature, verify)
import Data.Bits
(complement, shiftL, shiftR, xor, (.&.), (.|.))
import Data.ByteArray
(ByteArrayAccess, Bytes, convert, pack, unpack)
import Data.ByteString
(ByteString)
import Data.Function
(on)
import Data.Functor
(Functor, fmap, (<$>))
import Data.Maybe
(isJust)
import Data.Monoid
(Monoid (mappend, mempty), (<>))
import Data.Serialize
(Get, Putter, Serialize, encode, get, getByteString, getWord16le,
getWord32le, getWord8, put, putByteString, putWord16le, putWord32le,
putWord8, remaining, runGet, runGetState, runPut)
import Data.Word
(Word16, Word32, Word64, Word8)
import GHC.Generics
(Generic)
import
import
import
import
qualified
qualified
qualified
qualified
Control.Monad.State
Data.ByteString
Data.ByteString.Base16
Data.Map
as
as
as
as
MS
BS
X
Map
data StackItem = MkStackItem [Word8]
32
Mental poker for turn-based strategy games – securing fairness without a trusted third party
deriving (Generic, Eq)
instance Serialize StackItem
data Op = OpP OpPush
| OpD ByteString
| OpC OpCode
instance Show Op where
show o = case o of
OpP p -> show p
OpC c -> show c
showList [o] s
= show o ++ s
showList (o:os) s = show o ++ " " ++ showList os s
showList [] s
= s
instance Serialize Op where
put (OpP p) = case p of
OpX 0
-> putWord8 0
OpX x
| x >= -1 && x <= 16 -> putWord8 $ 80 + fromIntegral x
OpPushData n d | n > 0 && n < 76 -> putWord8 n >> putByteString d
OpPushData1 n d -> putWord8 76 >> putWord8 n >> putByteString d
OpPushData2 n d -> putWord8 77 >> putWord16le n >> putByteString d
OpPushData4 n d -> putWord8 78 >> putWord32le n >> putByteString d
put (OpC c) = putWord8 . fromIntegral $ 97 + fromEnum c
get = getWord8 >>= go
where go b | b < 97 = OpP <$> case b of
0 -> return $ OpX 0
_ | b < 76 -> OpPushData b <$> getByteString (fromIntegral b)
76 -> getWord8 >>=
\n -> OpPushData n <$> getByteString (fromIntegral n)
77 -> getWord16le >>=
\n -> OpPushData2 n <$> getByteString (fromIntegral n)
78 -> getWord32le >>=
\n -> OpPushData4 n <$> getByteString (fromIntegral n)
80 -> undefined
_ -> return . OpX $ fromIntegral b - 80
| otherwise = return . OpC $ toEnum (fromIntegral b - 97)
data OpPush = OpX Int
| OpPushData Word8 ByteString
| OpPushData1 Word8 ByteString
| OpPushData2 Word16 ByteString
| OpPushData4 Word32 ByteString
deriving Eq
instance Show OpPush where
show (OpX (-1)) = "Op1Negate"
show (OpX n) = "Op" ++ show n
show p = case p of
OpPushData n d -> f "" n d
OpPushData1 n d -> f "Data1
OpPushData2 n d -> f "Data2
OpPushData4 n d -> f "Data4
where f s n d = "OpPush" ++
data OpCode =
|
|
|
|
|
|
"
"
"
s
n d
n d
n d
++ show n ++ " " ++ show (X.encode d)
OpVerify
OpToAltStack
OpFromAltStack
OpDepth
OpDrop
OpDup
OpNip
33
Mental poker for turn-based strategy games – securing fairness without a trusted third party
| OpOver
| OpPick
| OpRoll
| OpRot
| OpSwap
| OpTuck
| Op2Drop
| Op2Dup
| Op3Dup
| Op2Over
| Op2Rot
| Op2Swap
| OpCat
| OpSubstr
| OpLeft
| OpRight
| OpSize
| OpSwapCat
| OpInvert
| OpAnd
| OpOr
| OpXor
| OpEqual
| OpEqualVerify
| Op1Add
| Op1Sub
| Op2Mul
| Op2Div
| OpNegate
| OpAbs
| OpNot
| Op0NotEqual
| OpAdd
| OpSub
| OpMul
| OpDiv
| OpMod
| OpPowMod
| OpLShift
| OpRShift
| OpBoolAnd
| OpBoolOr
| OpNumEqual
| OpNumEqualVerify
| OpNumNotEqual
| OpLessThan
| OpGreaterThan
| OpLessThanOrEqual
| OpGreaterThanOrEqual
| OpMin
| OpMax
| OpWithin
| OpHash160
| OpHash256
| OpCodeSeparator
| OpCheckSig
| OpCheckSigVerify
| OpCheckMultiSig
| OpCheckMultiSigVerify
| OpCheckLockTimeVerify
| OpCheckSequenceVerify
deriving (Enum, Eq, Generic, Show)
34
Mental poker for turn-based strategy games – securing fairness without a trusted third party
data OutputHashtype = SighashAll | SighashNone | SighashSingle
deriving Generic
data Hashtype = MkHashtype OutputHashtype Bool
deriving Generic
data TxSig = MkTxSig Signature Hashtype
type Outpoint = (Hash256, Word32)
data Input = MkInput Outpoint Word32
data Output = MkOutput { _value
:: Word64,
_program :: Hash256 }
deriving Generic
data Transaction = MkTransaction { _inputs
_outputs
_nSequence
_witness
::
::
::
::
[Input],
[Output],
Word32,
Witness }
data Witness = MkWitness [WitnessField]
data WitnessField = MkWitnessField { wfStack
:: [StackItem],
wfSubscripts :: [ByteString],
wfPath
:: [Hash256] }
newtype Hash160 = MkHash160 (Digest RIPEMD160)
deriving (Eq, Ord, Show, ByteArrayAccess)
newtype Hash256 = MkHash256 (Digest SHA256)
deriving (Eq, Ord, Show, ByteArrayAccess)
type UTXOSet = Map.Map Outpoint Output
data MAST = MLeaf [ByteString]
| MTree MAST MAST
data ScriptState = MkScriptState { ssStack
ssAltstack
ssInputValue
ssInputIx
ssHashCode
ssCode
ssTx
::
::
::
::
::
::
::
[StackItem],
[StackItem],
Word64,
Int,
ByteString,
ByteString,
Transaction }
data Exec a = MkExec (ScriptState -> Maybe (a, ScriptState))
| ExecInvalid
makeLenses ’’Output
makeLenses ’’Transaction
makeLensesWith abbreviatedFields ’’WitnessField
makeLensesWith abbreviatedFields ’’ScriptState
class FromStackVal a where
fromStackValue :: StackItem -> a
class ToStackVal a where
toStackValue :: a -> StackItem
35
Mental poker for turn-based strategy games – securing fairness without a trusted third party
instance Serialize OutputHashtype
instance Serialize Hashtype
instance Serialize Output
instance Serialize Hash160 where
put = putByteString . convert
get = MkHash160 <$> getDigest RIPEMD160
instance Serialize Hash256 where
put = putByteString . convert
get = MkHash256 <$> getDigest SHA256
instance Monad Exec where
ExecInvalid >>= _ = ExecInvalid
MkExec f
>>= g = MkExec (\s -> do
(r, s’) <- f s
runExec (g r) s’)
return a = MkExec $ \s -> Just (a, s)
instance Applicative Exec where
pure = return
(<*>) = ap
instance Functor Exec where
fmap f xs = xs >>= return . f
instance MS.MonadState ScriptState Exec where
state f = MkExec $ Just . f
instance FromStackVal Integer where
fromStackValue (MkStackItem x) = fromStackInt x
instance ToStackVal Integer where
toStackValue = MkStackItem . stackInt
instance FromStackVal Bool where
fromStackValue = ((0 :: Integer) /=) . fromStackValue
instance ToStackVal Bool where
toStackValue = toStackValue . fromEnum
instance ToStackVal Int where
toStackValue = toStackValue . toInteger
instance FromStackVal Bytes where
fromStackValue (MkStackItem x) = pack x
instance FromStackVal ByteString where
fromStackValue (MkStackItem x) = pack x
instance ToStackVal ByteString where
toStackValue = MkStackItem . unpack
instance ToStackVal PublicKey where
toStackValue = (toStackValue :: ByteString -> StackItem) . convert
decodeScript :: Get [Op]
decodeScript = remaining >>= \n ->
case n of
0 -> return []
_ -> get >>= \o -> (o :) <$> decodeScript
36
Mental poker for turn-based strategy games – securing fairness without a trusted third party
getDigest :: HashAlgorithm a => a -> Get (Digest a)
getDigest a = do
Just d <- digestFromByteString <$> getByteString (hashDigestSize a)
return d
txValidate :: UTXOSet -> Transaction -> Bool
txValidate s t@(MkTransaction is os nl (MkWitness w)) = isJust $ do
guard $ length is == length w
utxos <- forM is $ \(MkInput op _) -> Map.lookup op s
guard $ ((>=) ‘on‘ (sum . (toInteger <$> view value <$>))) utxos os
forM_ (zip3 [0..] utxos w) $
\(i, MkOutput iv ip, MkWitnessField stack subscripts path) -> do
let sh = (hash256 . encode) <$> subscripts
ln = hash256 $ encode sh
mr = merkleRootFromBranch ln path
guard $ mr == ip
r <- evalScript stack iv i (BS.concat subscripts) t
guard $ null r
merkleRootFromBranch :: Hash256 -> [Hash256] -> Hash256
merkleRootFromBranch leaf [] = leaf
merkleRootFromBranch leaf (x:xs) = merkleRootFromBranch (hash256 $ encode ns) xs
where ns | leaf < x = (leaf, x)
| otherwise = (x, leaf)
putByteArray :: ByteArrayAccess a => Putter a
putByteArray = (put :: Putter ByteString) . convert
stackInt :: Integer -> [Word8]
stackInt n | n < 0
= let x : xs = stackInt (-n)
in 128 + x : xs
| n == 0
= []
| n < 128
= [fromInteger n]
| otherwise = fromInteger n : stackInt (n ‘div‘ 256)
fromStackInt :: [Word8] -> Integer
fromStackInt []
= 0
fromStackInt xs@(x:_) | x < 128
= go xs
| otherwise = -go xs
where go []
= 0
go (n:ns) = toInteger n + 256 * go ns
hash160 :: ByteArrayAccess ba => ba -> Hash160
hash160 = MkHash160 . hash . hashWith SHA256
hash256 :: ByteArrayAccess ba => ba -> Hash256
hash256 = MkHash256 . hash . hashWith SHA256
evalScript :: [StackItem] -> Word64 -> Int -> ByteString -> Transaction
-> Maybe [StackItem]
evalScript s iv i c tx =
let state = MkScriptState s [] iv i c c tx
in evalExec (go >> use stack) state
where go = exNextOp >>= \o -> case o of
Just (OpP p) -> exOpPush p >> go
Just (OpC c) -> eval c >> go
Nothing
-> return ()
runExec :: Exec a -> ScriptState -> Maybe (a, ScriptState)
runExec ExecInvalid _ = Nothing
runExec (MkExec f) s = f s
37
Mental poker for turn-based strategy games – securing fairness without a trusted third party
evalExec :: Exec a -> ScriptState -> Maybe a
evalExec e = fmap fst . runExec e
exDecodeScript :: Get a -> Exec a
exDecodeScript g = do
c <- use code
(r, c’) <- case runGetState g c 0 of
Left _ -> ExecInvalid
Right x -> return x
code .= c’
return r
exNextOp
exNextOp
case n
0 ->
_ ->
:: Exec (Maybe Op)
= exDecodeScript $ remaining >>= \n ->
of
return Nothing
get
exPeek :: Getting [r] ScriptState [r] -> Exec r
exPeek g = do
s <- use g
when (null s)
ExecInvalid
return $ head s
exPop :: Exec StackItem
exPop = exPop’ stack
exPop’ :: Getting [r] ScriptState [r] -> Exec r
exPop’ g = do
x <- exPeek g
stack %= tail
return x
exPush :: StackItem -> Exec ()
exPush = exPush’ stack
exPush’ :: MS.MonadState s m => ASetter s s [a] [a] -> a -> m ()
exPush’ g x = g %= (x :)
exEnsure :: Int -> Exec ()
exEnsure n = do
s <- use stack
when (length s < n)
ExecInvalid
exPopVal :: FromStackVal a => Exec a
exPopVal = fromStackValue <$> exPop
exPopKey :: Exec PublicKey
exPopKey = do
b <- exPopVal
case publicKey (b :: Bytes) of
CryptoPassed k -> return k
_
-> ExecInvalid
exPopSig :: Exec TxSig
exPopSig = do
b <- exPopVal
let rg = runGet $ do
bs <- signature <$> getByteString 32
38
Mental poker for turn-based strategy games – securing fairness without a trusted third party
ht <- get
return (bs, ht)
case rg b of
Right (CryptoPassed s, ht) -> return $ MkTxSig s ht
Left _
-> ExecInvalid
exPopSize :: Exec Int
exPopSize = do
n <- exPopVal
when (n < 0 || n >= 2^31)
ExecInvalid
return $ fromInteger n
exZipWith :: (Word8 -> Word8 -> Word8) -> Exec ()
exZipWith f = do
MkStackItem a <- exPop
MkStackItem b <- exPop
when (length a /= length b)
ExecInvalid
exPush . MkStackItem $ zipWith f a b
exPushVal :: ToStackVal a => a -> Exec ()
exPushVal = exPush . toStackValue
exModifyVal :: (FromStackVal a, ToStackVal b) => (a -> b) -> Exec ()
exModifyVal f = exPopVal >>= exPushVal . f
exModifyInt :: ToStackVal a => (Integer -> a) -> Exec ()
exModifyInt = exModifyVal
exBinOp :: (FromStackVal a, FromStackVal b, ToStackVal c) => (a -> b -> c)
-> Exec ()
exBinOp f = do
b <- exPopVal
a <- exPopVal
exPushVal $ f a b
exBinOpInt :: ToStackVal a => (Integer -> Integer -> a) -> Exec ()
exBinOpInt = exBinOp
exOpPush :: OpPush -> Exec ()
exOpPush (OpX x) = exPushVal x
exOpPush p = case p of
OpPushData _ d -> exPushVal
OpPushData1 _ d -> exPushVal
OpPushData2 _ d -> exPushVal
OpPushData4 _ d -> exPushVal
d
d
d
d
eval :: OpCode -> Exec ()
eval OpVerify = do
MkStackItem x <- exPop
when (fromStackInt x == 0)
ExecInvalid
eval OpToAltStack = exPop >>= exPush’ altstack
eval OpFromAltStack = exPop’ altstack >>= exPush
eval OpDepth = use stack >>= exPushVal . length
eval OpDrop = void exPop
eval OpDup = exPeek stack >>= exPush
eval OpNip = exEnsure 2 >> stack %= \s -> head s : drop 2 s
eval OpOver = exEnsure 2 >> stack %= \s -> s !! 1 : s
eval OpPick = do
n <- exPopSize
39
Mental poker for turn-based strategy games – securing fairness without a trusted third party
exEnsure $ n + 1
stack %= \s -> s !! n : s
eval OpRoll = do
n <- exPopSize
exEnsure $ n + 1
stack %= \s -> s !! n : take n s ++ drop (n + 1) s
eval OpRot = exEnsure 3 >> stack %= \s -> s !! 2 : take 2 s ++ drop 3 s
eval OpSwap = exEnsure 2 >> stack %= \s -> s !! 1 : head s : drop 2 s
eval OpTuck = exEnsure 2 >> stack %= \s -> take 2 s ++ [head s] ++ drop 2 s
eval Op2Drop = eval OpDrop >> eval OpDrop
eval Op2Dup = exEnsure 2 >> stack %= \s -> take 2 s ++ s
eval Op3Dup = exEnsure 3 >> stack %= \s -> take 3 s ++ s
eval Op2Over = exEnsure 4 >> stack %= \s -> take 2 (drop 2 s) ++ s
eval Op2Rot = exEnsure 6 >> stack %= \s -> take 2 (drop 4 s) ++ take 4 s ++
drop 6 s
eval Op2Swap = exEnsure 4 >> stack %= \s -> take 2 (drop 2 s) ++ take 2 s ++
drop 4 s
eval OpCat = do
MkStackItem b <- exPop
MkStackItem a <- exPop
exPush . MkStackItem $ a ++ b
eval OpSubstr = do
s <- exPopSize
b <- exPopSize
MkStackItem x <- exPop
when (b + s > length x)
ExecInvalid
exPush . MkStackItem . take s $ drop b x
eval OpLeft = do
s <- exPopSize
MkStackItem x <- exPop
when (s > length x)
ExecInvalid
exPush . MkStackItem $ take s x
eval OpRight = do
s <- exPopSize
MkStackItem x <- exPop
let n = s - length x
when (n < 0)
ExecInvalid
exPush . MkStackItem $ drop n x
eval OpSize = do
MkStackItem x <- exPeek stack
exPushVal $ length x
eval OpSwapCat = eval OpSwap >> eval OpCat
eval OpInvert = do
MkStackItem x <- exPop
exPush . MkStackItem $ complement <$> x
eval OpAnd = exZipWith (.&.)
eval OpOr = exZipWith (.|.)
eval OpXor = exZipWith xor
eval OpEqual = do
a <- exPop
b <- exPop
exPushVal $ a == b
eval OpEqualVerify = eval OpEqual >> eval OpVerify
eval Op1Add = exModifyInt (1+)
eval Op1Sub = exModifyInt (‘subtract‘ 1)
eval Op2Mul = exModifyInt (2*)
eval Op2Div = exModifyInt (‘div‘ 2)
eval OpNegate = exModifyInt negate
eval OpAbs = exModifyInt abs
40
Mental poker for turn-based strategy games – securing fairness without a trusted third party
eval OpNot = exModifyInt (0==)
eval Op0NotEqual = exModifyInt (0/=)
eval OpAdd = exBinOpInt (+)
eval OpSub = exBinOpInt (-)
eval OpMul = exBinOpInt (*)
eval OpDiv = do
x <- exPopVal
when (x == 0)
ExecInvalid
exModifyInt (‘div‘ x)
eval OpMod = do
x <- exPopVal
when (x == 0)
ExecInvalid
exModifyInt (‘rem‘ x)
eval OpPowMod = do
m <- exPopVal
e <- exPopVal
b <- exPopVal
when (min e b < 0 || max e b >= m)
ExecInvalid
exPushVal $ expFast b e m
eval OpLShift = exPopSize >>= \x -> exModifyInt (‘shiftL‘ x)
eval OpRShift = exPopSize >>= \x -> exModifyInt (‘shiftR‘ x)
eval OpBoolAnd = exBinOp (&&)
eval OpBoolOr = exBinOp (||)
eval OpNumEqual = exBinOpInt (==)
eval OpNumEqualVerify = eval OpNumEqual >> eval OpVerify
eval OpNumNotEqual = exBinOpInt (/=)
eval OpLessThan = exBinOpInt (<)
eval OpGreaterThan = exBinOpInt (>)
eval OpLessThanOrEqual = exBinOpInt (<=)
eval OpGreaterThanOrEqual = exBinOpInt (>=)
eval OpMin = exBinOpInt min
eval OpMax = exBinOpInt max
eval OpWithin = do
h <- exPopVal
l <- exPopVal
exModifyInt (\x -> l < x && x < h)
eval OpHash160 = do
MkStackItem x <- exPop
exPush . MkStackItem . unpack $ hash160 (pack x :: Bytes)
eval OpHash256 = do
MkStackItem x <- exPop
exPush . MkStackItem . unpack $ hash256 (pack x :: Bytes)
eval OpCodeSeparator = use code >>= assign hashCode
eval OpCheckSig = do
pk <- exPopKey
MkTxSig s ht <- exPopSig
iv <- use inputValue
i <- use inputIx
hc <- use hashCode
t <- use tx
let h = txHash ht iv i hc t
exPushVal $ verify pk h s
eval OpCheckSigVerify = eval OpCheckSig >> eval OpVerify
eval OpCheckMultiSig = do
nk <- exPopSize
ks <- replicateM nk exPopKey
ns <- exPopSize
ss <- replicateM ns exPopSig
iv <- use inputValue
41
Mental poker for turn-based strategy games – securing fairness without a trusted third party
i <- use inputIx
hc <- use hashCode
t <- use tx
exPushVal $ go iv i hc t ks ss
where
go iv i o t (k:ks’) ss@(MkTxSig s ht:ss’)
| verify k (txHash ht iv i o t) s = go iv i o t ks’ ss’
| otherwise
= go iv i o t ks’ ss
go _ _ _ _ _ [] = True
go _ _ _ _ _ _ = False
eval OpCheckMultiSigVerify = eval OpCheckSig >> eval OpVerify
txHash :: Hashtype -> Word64 -> Int -> ByteString -> Transaction -> Hash256
txHash ht@(MkHashtype oh acp) iv i hc (MkTransaction is os nl w) =
hash256 . runPut $ do
put $ case acp of
False -> hash256 . encode $ (\(MkInput op _) -> op) <$> is
True -> null256
put $ case (acp, oh) of
(False, SighashAll) -> hash256 $ encode $ (\(MkInput _ ns) -> ns) <$> is
_
-> null256
put $ let MkInput op _ = is !! i in op
put hc
put iv
put $ case oh of
SighashAll
-> hash256 $ encode os
SighashSingle | i < length os -> hash256 . encode $ os !! i
_
-> null256
put nl
put ht
null256 = MkHash256 d
where Just d = digestFromByteString (pack $ replicate 32 0 :: Bytes)
B.2
Compile.hs
{-# LANGUAGE FlexibleContexts, ImpredicativeTypes, LiberalTypeSynonyms,
OverloadedStrings, RankNTypes, TemplateHaskell #-}
module Compile where
import Blockchain hiding (value)
import
import
import
import
import
import
Control.Monad
Data.Either
Data.Function
Data.List
Data.Monoid
Data.Word
import Control.DeepSeq
(force)
import Control.Lens
(Getter, Lens’, Setter’, anyOf, at, contains, each, filtered,
makeLenses, over, setting, to, use, uses, view, (%=), (+=), (-=), (.=),
(.~), (<%=), (<+=), (<-=), (<<+=), (<>=), (?=), (^.))
import Control.Monad.Except (ExceptT (ExceptT), runExceptT, throwError)
import Control.Monad.List
(ListT, runListT)
import Control.Monad.Random (Rand, StdGen, getSplit, newStdGen, runRand)
import Control.Monad.State
(StateT (StateT), runStateT)
import Control.Monad.Trans
(lift)
import Data.ByteString
(ByteString)
import Data.IntMap.Strict
(IntMap)
import Data.IntSet
(IntSet)
42
Mental poker for turn-based strategy games – securing fairness without a trusted third party
import Data.Map.Strict
(Map)
import Data.Serialize
(Put, put, runGet, runPut)
import System.Random.Shuffle (shuffleM)
--import qualified Debug.Trace
import
import
import
import
import
import
qualified
qualified
qualified
qualified
qualified
qualified
Control.Monad.State
Data.Array
Data.ByteString
Data.IntMap.Strict
Data.IntSet
Data.Map.Strict
as
as
as
as
as
as
MS
A
BS
IntMap
IntSet
Map
data Subscript = ScriptCode ByteString
| ScriptHash Hash256
deriving Eq
instance Show Subscript
show (ScriptCode c) =
show (let Right x =
show (ScriptHash h) =
where
"Subscript: " ++
runGet decodeScript c in x)
"Hash: " ++ show h
data CompilerState = MkCompilerState { _refData
_nRefs
_depth
_altDepth
_output
_opCount
_marks
_branch
_nextWit
deriving (Eq, Show)
data RefData = MkRefData { _stackLocations
_altLocations
_value
_refCount
_pushLimit
deriving (Eq, Show)
::
::
::
::
::
::
::
::
::
::
::
::
::
::
IntMap RefData,
Int,
Int,
Int,
[Subscript],
Int,
[String],
[Int],
Ref }
IntSet,
IntSet,
Maybe ByteString,
Int,
Int }
data Ref = MkRef Int
deriving (Eq,Show)
type Compile a =
StateT CompilerState (ExceptT String (ListT (Rand StdGen))) a
makeLenses ’’CompilerState
makeLenses ’’RefData
traceM :: Monad f => String -> f ()
traceM =
const $ return ()
-- Debug.Trace.traceM
traceShowM :: (Show a, Monad f) => a -> f ()
traceShowM =
const $ return ()
-- Debug.Trace.traceShowM
43
Mental poker for turn-based strategy games – securing fairness without a trusted third party
emptyCompilerState :: CompilerState
emptyCompilerState = MkCompilerState
(IntMap.singleton 0 (emptyRefData & stackLocations .~
IntSet.singleton (-1))) 1 0 0 [] 0 [] [] (MkRef 0)
emptyRefData :: RefData
emptyRefData = MkRefData IntSet.empty IntSet.empty Nothing 0 0
mergeCompiles :: CompilerState -> Map [Int] CompilerState
-> Map [Int] CompilerState
mergeCompiles s = force $
Map.insertWith (\a b -> minimumBy (compare ‘on‘ view badness) [a, b])
(s ^. branch) s
runCompiles ::
->
runCompiles n0
where go 0 _
go n m
Int -> Compile a -> StdGen -> CompilerState
[Map [Int] CompilerState]
c g0 s = nub $ go n0 Map.empty g0
_ = []
g = let (es, g’) = runCompile c g s
m’ = foldr mergeCompiles m (snd <$> rights es)
in m’ : go (n - 1) m’ g’
testCompile :: Int -> Compile a -> IO [Map [Int] CompilerState]
testCompile n m = do
g <- newStdGen
return $ runCompiles n m g emptyCompilerState
runCompile :: Compile a -> StdGen -> CompilerState
-> ([Either String (a, CompilerState)], StdGen)
runCompile m g =
flip runRand g . runListT . runExceptT . runStateT m
badness :: Getter CompilerState Int
badness = to $ do
c <- view opCount
s <- view scriptLength
return $ c + s -- 50 * c + s
scriptLength :: Getter CompilerState Int
scriptLength = to $ do
os <- view output
return . sum $ (\s -> 32 + case s of
ScriptCode d -> BS.length d
ScriptHash _ -> 32) <$> os
refStacks :: Compile ([Int], [Int])
refStacks = do
rds <- uses refData IntMap.toList
d <- use depth
ad <- use altDepth
nw <- uses marks length
let
ms = [(d - i - 1, k)
| (k, rd) <- rds, i <- IntSet.elems $ rd ^. stackLocations]
as = [(ad - i - 1, k)
| (k, rd) <- rds, i <- IntSet.elems $ rd ^. altLocations]
return (f (1 + nw + d) ms, f ad as)
where f n = A.elems . A.accumArray (flip const) (-1) (0, n - 1)
witnessRef :: String -> Compile Ref
44
Mental poker for turn-based strategy games – securing fairness without a trusted third party
witnessRef l = do
r@(MkRef i) <- use nextWit
p <- getRefData r
when (IntSet.null $ p ^. stackLocations) $
throwError $ "Witness ref " ++ show i ++ " not in stack"
ms <- marks <%= (l :)
n <- absRef $ -length ms - 1
nextWit .= n
return r
mkRef :: Compile Ref
mkRef = do
nr <- nRefs <<+= 1
refData . at nr ?= emptyRefData
return $ MkRef nr
findRef :: Eq b => Lens’ RefData b -> b -> Compile Ref
findRef l v = do
rd <- use refData
case find ((v ==) . view l . snd) (IntMap.toList rd) of
Just (k, _) -> return $ MkRef k
_ -> do
r@(MkRef nr) <- mkRef
refData . at nr %= fmap (l .~ v)
return r
absRef :: Int -> Compile Ref
absRef n = findRef (stackLocations . contains n) True
stackRef :: Int -> Compile Ref
stackRef n = do
d <- uses depth pred
absRef $ d - n
literal :: ToStackVal a => a -> Compile Ref
literal v = do
let MkStackItem x = toStackValue v
r <- findRef value (Just $ BS.pack x)
refDataOf r . pushLimit . filtered (>= 0) %= succ
return r
refOnStack :: RefData -> Bool
refOnStack r = not . IntSet.null $ r ^. stackLocations
refOnAlt :: RefData -> Bool
refOnAlt r = not . IntSet.null $ r ^. altLocations
refCanPush :: RefData -> Bool
refCanPush r = r ^. pushLimit /= 0 && r ^. value /= Nothing
refCanUse :: RefData -> Bool
refCanUse r = refOnStack r || refOnAlt r || refCanPush r
refStackCount :: RefData -> Int
refStackCount r =
IntSet.size (r ^. stackLocations) + IntSet.size (r ^. altLocations)
refCanPop :: Int -> RefData -> Bool
refCanPop n r
| pl < 0
= True
| otherwise = sc + pl >= n + r ^. refCount
where pl | refCanPush r = r ^. pushLimit
45
Mental poker for turn-based strategy games – securing fairness without a trusted third party
| otherwise
= 0
sc = refStackCount r
refPos :: Ref -> Compile Int
refPos r = do
rd <- getRefData r
unless (refOnStack rd) $
throwError $ show r ++ " not in stack"
d <- uses depth pred
return $ d - IntSet.findMax (rd ^. stackLocations)
refPopAlt :: Ref -> Compile ()
refPopAlt r = do
rd <- getRefData r
unless (refOnAlt rd) $
throwError $ show r ++ " not in altstack"
d <- use altDepth
replicateM_ (d - IntSet.findMax (rd ^. altLocations)) $ op OpFromAltStack
refPush :: Ref -> Compile ()
refPush r = do
rd <- getRefData r
unless (refCanPush rd) $ throwError $ show r ++ " has no value to push"
d <- use depth
refDataOf r . stackLocations . contains d .= True
refDataOf r . pushLimit %= pred
let Just x = rd ^. value
void $ push x
refDataOf :: Ref -> Setter’ CompilerState RefData
refDataOf (MkRef i) = setting $ over (refData . at i) . fmap
refPick :: Ref -> Compile ()
refPick r = refPos r >>= opPick
refRoll :: Ref -> Compile ()
refRoll r = refPos r >>= opRoll
getRefData :: Ref -> Compile RefData
getRefData r@(MkRef i) =
use (refData . at i) >>= maybe (throwError $ show r ++ " not found") return
fetch :: Ref -> Compile ()
fetch r = do
rd <- getRefData r
let s | refCanPop 1 rd =
| otherwise
=
p | refCanPush rd =
| otherwise
=
a | refOnAlt rd
=
| otherwise
=
l | refCanPop 1 rd =
| otherwise
=
variants $ concat [s, p,
[refRoll r]
[refPick r]
[refPush r >> l]
[]
[refPopAlt r >> l]
[]
return ()
void $ op OpDup >> op OpToAltStack
a]
moveToStack :: Ref -> Compile ()
moveToStack r = do
rd <- getRefData r
case () of
_ | refOnStack rd -> return ()
| refCanPush rd -> refPush r
| refOnAlt rd -> refPopAlt r
46
Mental poker for turn-based strategy games – securing fairness without a trusted third party
| otherwise -> throwError $ show r ++ " value not found"
fetchUnord :: [Ref] -> Compile ()
fetchUnord rs = variants [fetch’ rs’ | rs’ <- permutations rs]
fetch’ :: [Ref] -> Compile ()
fetch’ rs = do
traceM $ "fetch’ " ++ show rs
forM_ rs $ \r -> refDataOf r . refCount -= 1
rds <- mapM getRefData rs
let nrds = filter
(\(_, rd) -> not $ (refCanPush rd && refCanPop 1 rd) || refOnStack rd) $
zip rs rds
variants [forM_ ps (moveToStack . fst) >> go
| trds <- nrds : subsequences nrds,
ps <- permutations trds]
where go = do
l <- uses depth pred
rds <- mapM getRefData rs
traceM "go"
traceM $ "rds = " ++ show rds
let rdos = takeWhile refOnStack rds
rps = reverse $
((l -) . IntSet.findMax . view stackLocations) <$> rdos
nmrps = takeWhile (not . (‘isPrefixOf‘ [0..])) . tails $ rps
mr = length rps - length nmrps
nr = length rs - mr
cvs = do
rp <- nmrps
let x = map fst . filter (not . refCanPop 1 . snd) . zip rp $
reverse rdos
xt <- subsequences x
xtp <- permutations xt
xbp <- permutations (x \\ xt)
(d, p, c) <- stackMap
guard $ (xtp ++ rp ++ xbp) ‘isPrefixOf‘ (p ++ [d ..])
return . void $ do
traceM "cv"
op c
forM_ xtp $ \_ -> op OpToAltStack
go
dv = do
traceM "dv"
traceM $ "rps = " ++ show rps
let x = map snd . filter (not . refCanPop 1 . fst) . take mr $
zip rds rs
traceM $ "x = " ++ show x
oc <- use opCount
traceM $ "opCount = " ++ show oc
forM x $ \r -> refPick r >> op OpToAltStack
when (nr > 0) $ do
traceM $ "fetch " ++ show (rs !! mr)
fetch $ rs !! mr
go
if nr == 0 && all (refCanPop 1) rds
then return ()
else do
traceM $ "variants (dv : cvs)"
variants (dv : cvs)
stackMap :: [(Int, [Int], OpCode)]
stackMap = [(1, [], OpDrop),
47
Mental poker for turn-based strategy games – securing fairness without a trusted third party
(0,
(2,
(0,
(3,
(2,
(2,
(2,
(0,
(0,
(0,
(6,
(4,
(1,
[0], OpDup),
[0], OpNip),
[1], OpOver),
[2, 0, 1], OpRot),
[1, 0], OpSwap),
[0, 1, 0], OpTuck),
[], Op2Drop),
[0, 1], Op2Dup),
[0, 1, 2], Op3Dup),
[2, 3], Op2Over),
[4, 5, 0, 1, 2, 3], Op2Rot),
[2, 3, 0, 1], Op2Swap),
[], OpToAltStack)]
refCat :: Ref -> Ref -> Compile Ref
refCat a b = variants [fetch’ [a, b] >> head <$> op OpCat,
fetch’ [b, a] >> head <$> op OpSwapCat]
refDataNull :: RefData -> Bool
refDataNull rd =
IntSet.null (rd ^. stackLocations) &&
IntSet.null (rd ^. altLocations) &&
rd ^. value == Nothing
variants :: [Compile a] -> Compile a
variants ms = do
ms’ <- lift . lift . lift $ shuffleM ms
go ms’
where go [] = traceM "All variants failed" >> throwError "All variants failed"
go (n:ns) = do
g <- lift . lift . lift $ getSplit
s <- MS.get
let (es, _) = runCompile n g s
case rights es of
[] -> go ns
rs -> msum [MS.put s’ >> return a | (a, s’) <- rs]
branches :: [Compile a] -> Compile a
branches cs =
StateT $ \s -> ExceptT $ msum [runExceptT $ runStateT (branch <>= [i] >> m) s
| (i, m) <- zip [0..] cs]
opPick :: Int -> Compile ()
opPick 0 = void $ op OpDup
opPick 1 = void $ op OpOver
opPick n = void $ do
push n >> op OpPick
stackDupMovement $ \i -> case i of
0 -> []
_ | i == n + 1 -> [0, n + 1]
_ | otherwise -> [i]
opRoll :: Int -> Compile ()
opRoll 0 = return ()
opRoll 1 = void $ op OpSwap
opRoll 2 = void $ op OpRot
opRoll n = do
freezeRefs $ push n >> op OpRoll
stackMovement $ \i -> case () of
_ | i == n
-> 0
| i < n
-> i + 1
| otherwise -> i
48
Mental poker for turn-based strategy games – securing fairness without a trusted third party
return ()
freezeRefs :: Compile a -> Compile a
freezeRefs c = do
rd <- use refData
d <- use depth
ad <- use altDepth
r <- c
refData .= rd
depth .= d
altDepth .= ad
return r
stackMovement :: (Int -> Int) -> Compile ()
stackMovement f = do
d <- uses depth pred
refData . each . stackLocations %= IntSet.map (\i -> d - f (d - i))
stackDupMovement :: (Int -> [Int]) -> Compile ()
stackDupMovement f = do
d <- uses depth pred
refData . each . stackLocations %=
IntSet.fromList . concat . map (map (d -) . f . (d -)) . IntSet.toList
op :: OpCode -> Compile [Ref]
op c = do
traceM $ "op " ++ show c
writeToScript . put $ OpC c
trackOp c
writeToScript :: Put -> Compile ()
writeToScript x = let b = runPut x
in output %= \p -> case p of
[]
-> [ScriptCode b]
(ScriptCode c : z) -> ScriptCode (c <> b) : z
(ScriptHash _ : _) -> (ScriptCode b) : p
trackOp :: OpCode -> Compile [Ref]
trackOp c = do
q >> o >> pops ni >> pushes no >> p
oc <- opCount <+= 1
when (oc > 201) $ throwError "Max op count reached"
mapM stackRef [0 .. no - 1]
where o = case c of
OpFromAltStack -> do
d <- altDepth <-= 1
m <- use depth
refData . each %= \x -> if x ^. altLocations .
then x & (altLocations
& (stackLocations
else x
OpToAltStack -> do
d <- altDepth <<+= 1
m <- uses depth pred
refData . each %= \x -> if x ^. stackLocations
then x & (altLocations
else x
OpDup -> stackDupMovement $ \i -> case i of
0 -> [-1, 0]
_ -> [i]
OpOver -> stackDupMovement $ \i -> case i of
1 -> [-1, 1]
49
contains d
. contains d) .~ False
. contains m) .~ True
. contains m
. contains d) .~ True
Mental poker for turn-based strategy games – securing fairness without a trusted third party
_ -> [i]
OpTuck -> stackDupMovement $ \i -> case i of
0 -> [-1, 1]
1 -> [0]
_ -> [i]
Op2Dup -> stackDupMovement $ \i -> case i ‘div‘ 2 of
0 -> [i - 2, i]
_ -> [i]
Op3Dup -> stackDupMovement $ \i -> case i ‘div‘ 3 of
0 -> [i - 3, i]
_ -> [i]
Op2Over -> stackDupMovement $ \i -> case i ‘div‘ 2 of
1 -> [i - 4, i]
_ -> [i]
OpNip -> trackOp OpSwap >> pops 1
OpPick -> stackFilter (0<)
OpRoll -> do
r <- stackRef 0
rd <- getRefData r
j <- case rd ^. value of
Nothing -> traceM "Unknown argument for OpRoll" >>
throwError "Unknown argument for OpRoll"
Just v ->
return . fromInteger . fromStackValue . MkStackItem $
BS.unpack v
stackMovement $ \i -> case () of
_ | i == j + 1 -> 0
| i <= j
-> i + 1
| otherwise -> i
OpRot -> stackMovement $ \i -> case i of
0 -> 1
1 -> 2
2 -> 0
_ -> i
OpSwap -> stackMovement $ \i -> case i of
0 -> 1
1 -> 0
_ -> i
Op2Rot -> stackMovement $ \i -> case () of
_ | i < 4 -> i + 2
| i < 6 -> i - 4
| otherwise -> i
Op2Swap -> stackMovement $ \i -> case () of
_ | i < 2 -> i + 2
| i < 4 -> i - 2
| otherwise -> i
OpCheckMultiSig -> stackClear
OpCheckMultiSigVerify -> stackClear
_ -> return ()
(ni, no) = opInOuts c
q = do
rds <- use refData
when (anyOf each (\rd -> rd ^. refCount > 0 && not (refCanUse rd))
rds) $ do
traceM "Protected ref disappeared before op"
throwError "Protected ref disappeared before op"
p = do
rds <- use refData
when (anyOf each (\rd -> rd ^. refCount > 0 && not (refCanUse rd))
rds) $ do
traceM "Protected ref disappeared"
throwError "Protected ref disappeared"
50
Mental poker for turn-based strategy games – securing fairness without a trusted third party
opInOuts :: OpCode -> (Int, Int)
opInOuts c = case c of
OpVerify
-> (1, 0)
OpToAltStack
-> (1, 0)
OpFromAltStack
-> (0, 1)
OpDepth
-> (0, 1)
OpDrop
-> (1, 0)
OpDup
-> (0, 1)
OpOver
-> (0, 1)
OpRoll
-> (1, 0)
OpTuck
-> (0, 1)
Op2Drop
-> (2, 0)
Op2Dup
-> (0, 2)
Op3Dup
-> (0, 3)
Op2Over
-> (0, 2)
OpCat
-> (2, 1)
OpSubstr
-> (3, 1)
OpLeft
-> (2, 1)
OpSize
-> (0, 1)
OpSwapCat
-> (2, 1)
OpInvert
-> (1, 1)
OpAnd
-> (2, 1)
OpOr
-> (2, 1)
OpXor
-> (2, 1)
OpEqual
-> (2, 1)
OpEqualVerify
-> (2, 0)
Op1Add
-> (1, 1)
Op1Sub
-> (1, 1)
Op2Mul
-> (1, 1)
Op2Div
-> (1, 1)
OpNegate
-> (1, 1)
OpAbs
-> (1, 1)
OpNot
-> (1, 1)
Op0NotEqual
-> (1, 1)
OpAdd
-> (2, 1)
OpSub
-> (2, 1)
OpMul
-> (2, 1)
OpDiv
-> (2, 1)
OpMod
-> (2, 1)
OpPowMod
-> (3, 1)
OpLShift
-> (2, 1)
OpRShift
-> (2, 1)
OpBoolAnd
-> (2, 1)
OpBoolOr
-> (2, 1)
OpNumEqual
-> (2, 1)
OpNumEqualVerify
-> (2, 0)
OpNumNotEqual
-> (2, 1)
OpLessThan
-> (2, 1)
OpLessThanOrEqual
-> (2, 1)
OpGreaterThan
-> (2, 1)
OpGreaterThanOrEqual -> (2, 1)
OpMin
-> (2, 1)
OpMax
-> (2, 1)
OpWithin
-> (3, 1)
OpHash160
-> (1, 1)
OpHash256
-> (1, 1)
OpCheckSig
-> (2, 1)
OpCheckSigVerify
-> (2, 0)
OpCheckMultiSig
-> (0, 1)
OpCheckLockTimeVerify -> (1, 0)
OpCheckSequenceVerify -> (1, 0)
51
Mental poker for turn-based strategy games – securing fairness without a trusted third party
_
-> (0, 0)
pops :: Int -> Compile ()
pops n = do
when (n > 0) $
stackFilter (n<=)
depth -= n
pushes :: Int -> Compile ()
pushes n = depth += n
stackFilter
stackFilter
d <- uses
refData .
:: (Int -> Bool) -> Compile ()
f = do
depth pred
each . stackLocations %= IntSet.filter (\i -> f (d - i))
stackClear :: Compile ()
stackClear = refData . each . stackLocations .= IntSet.empty
scriptHash :: Hash256 -> Compile ()
scriptHash h = output %= (ScriptHash h :) >> stackClear
opPush :: OpPush -> Compile Ref
opPush o = do
writeToScript $ put . OpP $ o
pushes 1
stackRef 0
:: [Word8] -> OpPush
[] = OpX 0
[81] = OpX (-1)
[v] | v > 0 && v < 17 = OpX $ fromIntegral v
x | n < 76 = OpPushData (fromIntegral n) d
| n < 256 = OpPushData1 (fromIntegral n) d
| n < 65536 = OpPushData2 (fromIntegral n) d
| otherwise = OpPushData4 (fromIntegral n) d
where n = length x
d = BS.pack x
mkPushOp
mkPushOp
mkPushOp
mkPushOp
mkPushOp
push :: ToStackVal a => a -> Compile Ref
push a = do
let MkStackItem x = toStackValue a
r <- opPush $ mkPushOp x
refDataOf r . value ?= BS.pack x
return r
opf :: OpCode -> [Ref] -> Compile [Ref]
opf o rs = fetch’ rs >> op o
opfu :: OpCode -> [Ref] -> Compile [Ref]
opfu o rs = fetchUnord rs >> op o
opPowMod :: Ref -> Ref -> Ref -> Compile Ref
opPowMod b e m = traceM "opPowMod" >> head <$> opf OpPowMod [b, e, m]
opSub :: Ref -> Ref -> Compile Ref
opSub m s = traceM "opSub" >> head <$> opf OpSub [m, s]
opMul :: Ref -> Ref -> Compile Ref
opMul a b = traceM "opMul" >> head <$> opfu OpMul [a, b]
opMod :: Ref -> Ref -> Compile Ref
52
Mental poker for turn-based strategy games – securing fairness without a trusted third party
opMod n m = traceM "opMod" >> head <$> opf OpMod [n, m]
opMin :: Ref -> Ref -> Compile Ref
opMin a b = traceM "opMin" >> head <$> opfu OpMin [a, b]
opNumNotEq :: Ref -> Ref -> Compile Ref
opNumNotEq a b = head <$> opfu OpNumNotEqual [a, b]
opVerify :: Ref -> Compile ()
opVerify x = void $ opf OpVerify [x]
opEqVerify :: Ref -> Ref -> Compile ()
opEqVerify a b = void $ opfu OpEqualVerify [a, b]
opCheckSigVerify :: Ref -> Ref -> Compile ()
opCheckSigVerify s k = void $ opf OpCheckSigVerify [s, k]
opHash256 :: Ref -> Compile Ref
opHash256 m = head <$> opf OpHash256 [m]
opNot :: Ref -> Compile Ref
opNot x = head <$> opf OpNot [x]
opEq :: Ref -> Ref -> Compile Ref
opEq a b = head <$> opfu OpEqual [a, b]
opSize :: Ref -> Compile Ref
opSize s = head <$> opf OpSize [s]
opWithin :: Ref -> Ref -> Ref -> Compile Ref
opWithin x l h = head <$> opf OpWithin [x, l, h]
opGt :: Ref -> Ref -> Compile Ref
opGt a b = head <$> variants [opf OpGreaterThan [a, b],
opf OpLessThan [b, a]]
opDrop
opDrop
opDrop
opDrop
opDrop
:: Int -> Compile ()
0 = return ()
1 = void $ op OpDrop
n | n > 1 = op Op2Drop >> opDrop (n - 2)
_ = throwError "Negative opDrop"
cleanStack :: Compile a -> Compile a
cleanStack c = do
d <- use depth
m <- uses marks length
r <- c
d’ <- use depth
m’ <- uses marks length
opDrop $ d’ + m’ - d - m
return r
unordered :: [Compile ()] -> Compile ()
unordered = variants . map sequence_ . permutations
ref’ :: Int -> Compile Ref -> Compile Ref
ref’ n c = do
r <- c
refDataOf r . refCount += n
return r
incRef :: Ref -> Compile ()
53
Mental poker for turn-based strategy games – securing fairness without a trusted third party
incRef r = refDataOf r . refCount %= (\n -> 1 + max 1 n)
B.3
Game.hs
{-# LANGUAGE OverloadedStrings #-}
module Game where
import Blockchain
import Compile
import
import
import
import
import
Control.Arrow
Control.Monad
Data.Bits
Data.List
Data.Word
import
import
import
import
import
import
Crypto.Error
Crypto.PubKey.Ed25519
Data.Bits.Extras
Data.ByteString
Data.Ord
Data.Serialize
(CryptoFailable (CryptoPassed))
(PublicKey, secretKey, toPublic)
(msb, w32)
(ByteString)
(comparing)
(encode)
import qualified Data.ByteString as BS
data TreeFieldType = SubTree
| VarField
| Field Int
data StateField = StatePrev
| StateMessage
| StatePrivateA
| StatePrivateB
deriving (Enum, Show)
data PrivateField = PrivateExponent
deriving Enum
data MessageType = MsgInitPrivate
| MsgQEncrypt
| MsgQDecrypt
| MsgAnswer
deriving Enum
class Enum a => TreeField a where
treeFieldType :: a -> TreeFieldType
instance TreeField StateField where
treeFieldType f = case f of
StatePrev
-> SubTree
StateMessage -> VarField
StatePrivateA -> SubTree
StatePrivateB -> SubTree
instance TreeField PrivateField where
treeFieldType f = case f of
PrivateExponent -> VarField
fieldSize :: TreeFieldType -> Int
54
Mental poker for turn-based strategy games – securing fairness without a trusted third party
fieldSize t
SubTree
VarField
Field s
= case t of
-> 32
-> 32
-> s
treeFieldTypes :: TreeField a => a -> [TreeFieldType]
treeFieldTypes a = treeFieldType <$> [toEnum 0 ‘asTypeOf‘ a ..]
treeFieldCounts :: [TreeFieldType] -> [Int]
treeFieldCounts ts0 = go 32 0 0 ts0
where go _ c _ [] = [c]
go g c n (t:ts) | n == 0
= go s (c + 1) s ts
| (n + s) <= l = go g’ (c + 1) (n + s) ts
| otherwise
= c : go 32 1 s ts
where s = fieldSize t
g’ = min g s
l = 32 + g’
treePathFromIndex :: Int -> Int -> (Int, Word32)
treePathFromIndex i n
| ln < 1 = (0, 0)
| msb (w32 i) == ln = (\(d, p) -> (d + 1, p ‘setBit‘ d)) $
treePathFromIndex (i ‘clearBit‘ ln) (n ‘clearBit‘ ln)
| otherwise = (lp, fromIntegral i)
where lp = 1 + msb (w32 $ n - 1)
ln = msb $ w32 n
treeFieldPos :: TreeField a => a -> ((Int, Word32), Int, Int)
treeFieldPos f = (treePathFromIndex pf (length fcs), i, c)
where fcs = treeFieldCounts $ treeFieldTypes f
fi = fromEnum f
(pf, cc) = foldr (\n (p, s) -> if s > fi
then (p, s)
else (p + 1, s + n)) (-1, 0) fcs
c = fcs !! pf
i = fi - cc + c
cTreeFieldsToRoot :: TreeField a => String -> [(a, Ref)] -> Compile Ref
cTreeFieldsToRoot m fs = do
[r] <- cTreeFieldUpdatesToRoots (m, m) (second (\a -> (a, a)) <$> fs)
return r
cTreeFieldUpdatesToRoots :: TreeField a => (String, String)
-> [(a, (Ref, Ref))] -> Compile [Ref]
-- TODO: The generated code is suboptimal when the new tree contains a
-- reference to the old tree. Should be possible to include the
-- constructed hash of the old tree when building the new tree and not
-- require it to be provided explicitly.
cTreeFieldUpdatesToRoots _ [] = return [] --e
cTreeFieldUpdatesToRoots (ma, mb) fs = go (first treeFieldPos <$> fs)
where go [(((-1,_),_,_),(la,lb))] = return $
if la == lb
then [la]
else [la, lb]
go (n@((p,_,c),_) : pics) =
let (dops, ndops) = partition dop pics
(ddops, nddops) = partition dp dops
in case ddops of
[] -> do
let ilas = (\((_,i’,_),(l’,_)) -> (i’, l’)) <$> (n : dops)
ilbs = (\((_,i’,_),(_,l’)) -> (i’, l’)) <$> (n : dops)
mp = "@" ++ show (p :: (Int, Word32))
55
Mental poker for turn-based strategy games – securing fairness without a trusted third party
na <- cAssemble (ma ++ mp) (sortBy (comparing fst) ilas) c
la’ <- opHash256 na
lb’ <- if ilbs == ilas
then return la’
else do
nb <- cAssemble (mb ++ mp) (sortBy (comparing fst) ilbs) c
opHash256 nb
go ((ppic, (la’, lb’)) : ndops)
_ -> go (ddops ++ n : nddops ++ ndops)
where
dop ((p’, _, _),_) = let ld = fst p’ - fst p
in ld >= 0 && snd p == snd p’ ‘shiftR‘ ld
dp ((p’, _, _),_) = p’ /= p
ppic = ((fst p - 1, snd p ‘shiftR‘ 1),
fromIntegral $ snd p .&. 1, 2)
cAssemble :: String -> [(Int, Ref)] -> Int -> Compile Ref
cAssemble m ((i0,l0):ps0) c
| i0 > 0 = do
e <- witnessRef $ mr 0 i0
l’ <- refCat e l0
go (i0 + 1) l’ ps0
| otherwise = go 1 l0 ps0
where go b l [] | b < c = do
e <- witnessRef $ mr b c
refCat l e
| otherwise = return l
go b l ((i,k):ps)
| i > b = do
e <- witnessRef $ mr b i
l’ <- refCat l e
go i l’ ((i,k):ps)
| otherwise = do
l’ <- refCat l k
go (i + 1) l’ ps
mr i j = m ++ ":" ++ show i ++
if j /= i + 1 then ".." ++ show (j - 1) else ""
cAgree :: PublicKey -> PublicKey -> Compile ()
-- Verify that signature s matches key a and that signature t matches
-- key b.
cAgree a b = void $ do
t <- witnessRef "t"
fetch’ [t]
push b
op OpCheckSigVerify
s <- witnessRef "s"
fetch’ [s]
push a
op OpCheckSigVerify
cCheckPushScript :: Ref -> Ref -> Compile ()
-- Verify that h is the hash of the subscript that uses
-- OpPush1..OpPush16 to push e onto the stack.
cCheckPushScript h e = void $ do
s <- ref’ 2 $ opSize e
lMin <- literal (1 :: Int)
lMax <- literal (16 :: Int)
opWithin s lMin lMax >>= opVerify
--lpc <- literal . BS.take 1 . encode $ OpP (OpPushData1 32 BS.empty)
--lps <- refCat lpc s
lpe <- refCat s e
56
Mental poker for turn-based strategy games – securing fairness without a trusted third party
opHash256 lpe >>= opEqVerify h
cRevealPushScript :: Hash256 -> PublicKey -> PublicKey -> Compile ()
cRevealPushScript h a b =
unordered [do lh <- literal $ encode h
e <- ref’ 2 $ witnessRef "e"
unordered [cCheckPushScript e lh,
literal (1 :: Int) >>= opGt e >>= opVerify],
cAgree a b]
cPushHash :: Hash256 -> Compile Ref
cPushHash h = do
freezeRefs $ scriptHash h
pushes 1
stackRef 0
cBadEncryption :: Integer -> Ref -> Ref -> Compile ()
cBadEncryption m s e = do
lm <- literal m
fetch lm
b <- ref’ 2 $ witnessRef "base"
r <- ref’ 2 $ witnessRef "result"
unordered [cVerifyCryptoMismatch b e lm r,
cVerifyCryptoMessages MsgQEncrypt b r s]
cBadDecryption :: Integer -> Ref -> Ref -> Compile ()
cBadDecryption m s e = do
lm <- literal m
fetch lm
b <- ref’ 2 $ witnessRef "base"
r <- ref’ 2 $ witnessRef "result"
unordered [cVerifyCryptoMismatch r e lm b,
cVerifyCryptoMessages MsgQDecrypt r b s]
cVerifyCryptoMessages :: MessageType -> Ref -> Ref -> Ref -> Compile ()
cVerifyCryptoMessages qt b r s = do
lqt <- literal $ fromEnum qt
bm <- refCat lqt b
p <- cTreeFieldsToRoot "prev" [(StateMessage, bm)]
lat <- literal $ fromEnum MsgAnswer
rm <- refCat lat r
s’ <- cTreeFieldsToRoot "s" [(StateMessage, rm), (StatePrev, p)]
opEqVerify s s’
cVerifyCryptoMismatch :: Ref -> Ref -> Ref -> Ref -> Compile ()
cVerifyCryptoMismatch b e m r = do
incRef m
r’ <- ref’ 2 $ opPowMod b e m
opSub m r’ >>= opMin r’ >>= opNumNotEq r >>= opVerify
cEvalInitPrivate :: StateField -> Ref -> Ref -> Compile Ref
cEvalInitPrivate f e p = do
z <- literal (0 :: Int)
he <- opHash256 e
[ppt,spt] <- cTreeFieldUpdatesToRoots ("prev." ++ show f, "s." ++ show f)
[(PrivateExponent, (z, he))]
hpm <- witnessRef $ "H(prev." ++ show StateMessage ++ ")"
pp <- witnessRef $ "prev." ++ show StatePrev
sm <- literal $ fromEnum MsgInitPrivate + 256 * fromEnum f
hsm <- opHash256 sm
[p’,s] <- cTreeFieldUpdatesToRoots ("prev", "s")
[(f, (ppt, spt)), (StateMessage, (hpm, hsm)), (StatePrev, (pp, p))]
57
Mental poker for turn-based strategy games – securing fairness without a trusted third party
opEqVerify p p’
return s
cBadEval :: Integer -> StateField -> Ref -> Ref -> Compile ()
cBadEval m f s e = do
p <- witnessRef "prev"
s’ <- branches [cEvalInitPrivate f e p]
opEq s s’ >>= opNot >>= opVerify
cCheat :: Integer -> Hash256 -> Hash256 -> PublicKey -> Compile ()
cCheat m he s q = cleanStack . void $ do
e <- cPushHash he
unordered [do sig <- witnessRef "signature"
rq <- push q
opCheckSigVerify sig rq,
do ls <- push $ encode s -- literal $ encode s
branches [cBadEncryption m ls e,
cBadDecryption m ls e,
cBadEval m StatePrivateA ls e]]
cTest :: Compile ()
cTest = cCheat modulus h0 h1 pubAlice
pubAlice :: PublicKey
pubAlice =
let CryptoPassed secret = secretKey $ hash256 ("Alice secret" :: ByteString)
in toPublic secret
pubBob :: PublicKey
pubBob =
let CryptoPassed secret = secretKey $ hash256 ("Bob secret" :: ByteString)
in toPublic secret
modulus :: Integer
-- From https://www.rfc-editor.org/rfc/rfc2412.txt
modulus = read $ concat ["0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1",
"29024E088A67CC74020BBEA63B139B22514A08798E3404DD",
"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245",
"E485B576625E7EC6F44C42E9A63A3620FFFFFFFFFFFFFFFF"]
h0 :: Hash256
h0 = hash256 ("h0" :: ByteString)
h1 :: Hash256
h1 = hash256 ("h1" :: ByteString)
58
Mental poker for turn-based strategy games – securing fairness without a trusted third party
Glossary
Bitcoin improvement proposal (BIP)
Numbered design document proposing changes to or documenting specific
parts of Bitcoin.
Blockchain
A transaction (TX) ledger shared and validated by nodes in a network,
where TXs are continuously collected into blocks which are deliberately
difficult to create, each new block being based on the latest block in the
longest valid chain, making old blocks more and more difficult to revert as
new blocks are added.
Contract
Agreement between parties consisting of
enforced by the blockchain.
TX s
that are linked together and
Decisional Diffie–Hellman (DDH)
In a cyclic group G of order q, the assumption that given ga and gb for
uniformly and independently chosen a, b 2 Zq , the value gab is indistinguishable from a random element in G.
Fee
Part of a TX’s total inputs going to the miner that manages to include the
TX in a block.
Fractional Brownian motion (fBm)
A type of noise that may be approximated by successive octaves of noise
with exponentially decaying amplitude.
Hash
Fixed-size data derived from arbitrary data via a deterministic function
such that different input leads to completely different output with high
probability.
59
Mental poker for turn-based strategy games – securing fairness without a trusted third party
Merkle tree
A tree in which every non-leaf node is a hash of its child nodes, which
means that a leaf node can be demonstrated to be part of a given tree only
knowing the sibling nodes along the branch up to the root node.
Merklised abstract syntax tree (MAST)
A Merkle tree whose nodes encode individual branches of a script, so the
redeeming TX only has to include the evaluated branch of a potentially
much larger script.
Quadratic residue
a is a quadratic residue modulo m if there exists an integer x such that
x2 mod m = a mod m [14].
Real-time strategy (RTS)
Like a turn-based strategy (TBS) game, with the main difference that time
is continuous and players control their units in real time.
Safe prime
A prime of the form 2q + 1, where q is also prime.
Security level
Estimated amount of resources required to break a cryptosystem, e.g., a
security level of 80 bits means that approximately 280 operations are required.
Seed
Number used to initialise a pseudorandom number generator.
Transaction (TX)
Transfer of e.g. Bitcoin value that may be broadcast to the network and
which miners may include into the blockchain.
Trusted third party (TTP)
Entity which facilitates interactions between parties who trust the third
party.
Turing completeness
The ability of a system to simulate a universal Turing machine or any
other Turing complete system, which includes all commonly used generalpurpose programming languages.
60
Mental poker for turn-based strategy games – securing fairness without a trusted third party
Turn-based strategy (TBS)
A genre of video games played in turns, where each player typically controls their own set of units and must use the units to e.g. explore the game
world, collect resources, build infrastructure and combat against opponent
units.
61
Mental poker for turn-based strategy games – securing fairness without a trusted third party
Bibliography
[1] Ernest Adams. Fundamentals of game design. Pearson Education, 2013,
p. 510.
[2] David Adrian et al. Imperfect Forward Secrecy: How Diffie-Hellman Fails
in Practice. 2015. URL: https : / / weakdh . org / imperfect - forward secrecy.pdf (visited on 28/05/2015).
[3] Marcin Andrychowicz et al. “Secure multiparty computations on Bitcoin”.
In: Security and Privacy (SP), 2014 IEEE Symposium on. IEEE. 2014,
pp. 443–458.
[4] Iddo Bentov and Ranjit Kumaresan. “How to use Bitcoin to design fair
protocols”. In: Advances in Cryptology–CRYPTO 2014. Springer, 2014,
pp. 421–439.
[5] Bitcoin Wiki. Contracts. 2015.
URL : https://en.bitcoin.it/w/index.
php?title=Contracts&oldid=53752.
[6] Dan Boneh. “The decision Diffie-Hellman problem”. In: Algorithmic number theory. Springer, 1998, pp. 48–63.
[7] Elie Bursztein et al. “OpenConflict: Preventing real time map hacks in
online games”. In: Security and Privacy (SP), 2011 IEEE Symposium on.
IEEE. 2011, pp. 506–520.
[8] Vitalik Butterin et al. A next-generation smart contract and decentralized
application platform. 2014.
[9] Chris Chambers et al. “Mitigating information exposure to cheaters in realtime strategy games”. In: Proceedings of the international workshop on Network and operating systems support for digital audio and video. ACM. 2005,
pp. 7–12.
[10] Whitfield Diffie and Martin E Hellman. “New directions in cryptography”.
In: Information Theory, IEEE Transactions on 22.6 (1976), pp. 644–654.
62
Mental poker for turn-based strategy games – securing fairness without a trusted third party
[11] fgrieu. A: Cryptographic system with double keys with reversible order.
2014. URL: http : / / crypto . stackexchange . com / a / 15293 (visited
on 12/12/2016).
[12] Alain Fournier, Don Fussell and Loren Carpenter. “Computer rendering of
stochastic models”. In: Communications of the ACM 25.6 (1982), pp. 371–
384.
[13] Oded Goldreich, Silvio Micali and Avi Wigderson. “How to play any mental game”. In: Proceedings of the nineteenth annual ACM symposium on
Theory of computing. ACM. 1987, pp. 218–229.
[14] Kenneth Ireland and Michael Ira Rosen. A classical introduction to modern
number theory. Quadratic reciprocity. Springer Science & Business Media,
1990. Chap. 5.
[15] Johnson Lau. Merkelized Abstract Syntax Tree. BIP 114. Bitcoin Project,
2016. URL: https : / / github . com / bitcoin / bips / blob / 9da1f65e39
0ca18c1d63abea8c4a112a6c95049d/bip- 0114.mediawiki (visited on
18/11/2016).
[16] Orisi. Orisi White Paper. 2014.
wiki/Orisi-White-Paper.
URL : https://github.com/orisi/wiki/
[17] Hilarie Orman. The OAKLEY key determination protocol. RFC 2412. RFC
Editor, Nov. 1998. URL: http://www.rfc- editor.org/rfc/rfc2412.
txt (visited on 29/05/2015).
[18] Stephen C Pohlig and Martin E Hellman. “An improved algorithm for
computing logarithms over GF(p) and its cryptographic significance (Corresp.)” In: Information Theory, IEEE Transactions on 24.1 (1978), pp. 106–
110.
[19] Stephen L Rice. Secure Map Generation for Multiplayer, Turn-Based Strategy
Games. University of Denver. 2014.
[20] Adi Shamir, Ronald L Rivest and Leonard M Adleman. Mental poker.
Springer, 1981. URL: http : / / people . csail . mit . edu / ~rivest /
ShamirRivestAdleman-MentalPoker.pdf.
[21] Heiko Stamer. Bibliography on Mental Poker. 2007. URL: http://archive
.cone.informatik.uni-freiburg.de/teaching/teamprojekt/dogw10/literature/MentalPoker-survey.pdf.
63
© Copyright 2026 Paperzz