Last active
August 10, 2020 12:49
-
-
Save scalahub/2687001f9c5b877a0e6d4321723781aa to your computer and use it in GitHub Desktop.
Rock-paper-scissors in Ergo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** RPS game: | |
Alice creates a RPS game of "playAmount" ergs by creating a Half-game UTXO called the "halfGameOutput" output below. | |
Another player (Bob) then sends a transaction spending Alice's UTXO and creating two outputs called "fullGameOutputs" ("Full game" UTXOs). | |
After Alice opens her commitment (see below), the fullGameOutputs can be spent by the winner. | |
The transactions encode the following protocol. | |
protocol: | |
Step 1: Alice commits to secret number a in {0, 1, 2} as follows: | |
Generate random s and compute h = Hash(s||a) | |
h is the commitment to a | |
Alice also selects the "play amount", the amount each player must spend to participate. | |
She generates a halfGameOutput encoding h and some spending condition given below by halfGameScript | |
Step 2: Bob chooses random number b from {0, 1, 2} (public) and creates a new tx spending Alice's UTXO along with | |
some others and creating two outputs that has the spending conditions given by fullGameScript. | |
(one of the conditions being that the amount of that output is >= twice the play amount.) | |
Step 3: Alice reveals (s, a) to open her commitment and winner is decided as per RPS rules. | |
If Alice fails to open her commitment before some deadline then Bob automatically wins. | |
For simplicity, we will use following bytes to designate choices | |
0x00 = 0 = rock | |
0x01 = 1 = paper | |
0x02 = 2 = scissors | |
*/ | |
// Details here: https://github.com/ScorexFoundation/sigmastate-interpreter/blob/develop/sigmastate/src/test/scala/sigmastate/utxo/examples/RPSGameExampleSpecification.scala | |
val fullGameScript = compile(fullGameEnv, | |
"""{ | |
| val s = getVar[Coll[Byte]](0).get // Alice's secret byte string s | |
| val a = getVar[Byte](1).get // Alice's secret choice a (represented as a byte) | |
| val b = SELF.R4[Byte].get // Bob's public choice b (represented as a byte) | |
| val bob = SELF.R5[SigmaProp].get | |
| val bobDeadline = SELF.R6[Int].get // after this height, Bob gets to spend unconditionally | |
| val drawPubKey = SELF.R7[SigmaProp].get | |
| | |
| (bob && HEIGHT > bobDeadline) || { | |
| val valid_a = (a == 0 || a == 1 || a == 2) && blake2b256(s ++ Coll(a)) == k | |
| valid_a && { | |
| val a_minus_b = a - b | |
| if (a_minus_b == 0) drawPubKey else { | |
| if ((a_minus_b) == 1 || (a_minus_b) == -2) alice else bob | |
| } | |
| } | |
| } | |
|}""".stripMargin | |
).asSigmaProp | |
val halfGameEnv = Map( | |
ScriptNameProp -> "halfGameScript", | |
"alice" -> alicePubKey, | |
"fullGameScriptHash" -> Blake2b256(fullGameScript.treeWithSegregation.bytes) | |
) | |
// Note that below script allows Alice to spend the half-game output anytime before Bob spends it. | |
// We could also consider a more restricted version of the game where Alice is unable to spend the half-game output | |
// before some minimum height. | |
val halfGameScript = compile(halfGameEnv, | |
"""{ | |
| OUTPUTS.forall{(out:Box) => | |
| val b = out.R4[Byte].get | |
| val bobDeadline = out.R6[Int].get | |
| val validBobInput = b == 0 || b == 1 || b == 2 | |
| // Bob needs to ensure that out.R5 contains bobPubKey | |
| bobDeadline >= HEIGHT+30 && | |
| out.value >= SELF.value && | |
| validBobInput && | |
| blake2b256(out.propositionBytes) == fullGameScriptHash | |
| } && OUTPUTS.size == 2 && | |
| OUTPUTS(0).R7[SigmaProp].get == alice // Alice does not care for Bob's draw case | |
| // Bob needs to ensure that OUTPUTS(1).R7 contains his public key | |
|} | |
""".stripMargin).asBoolValue.toSigmaProp |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment