# janken vs yoshiking - zer0pts CTF 2021
###### tags: `zer0pts CTF 2021` `crypto`
## overview
You should beat yoshiking at the rock-paper-scissors over 100 times without defeat. He decides his hand and give us its commitment before you input your hand. The commitment is encrypted by ElGamal encryption scheme.
```python=
import random
import signal
from flag import flag
from Crypto.Util.number import getStrongPrime, inverse
HANDNAMES = {
1: "Rock",
2: "Scissors",
3: "Paper"
}
def commit(m, key):
(g, p), (x, _) = key
r = random.randint(2, p-1)
c1 = pow(g, r, p)
c2 = m * pow(g, r*x, p) % p
return (c1, c2)
def decrypt(c, key):
c1, c2 = c
_, (x, p)= key
m = c2 * inverse(pow(c1, x, p), p) % p
return m
def keygen(size):
p = getStrongPrime(size)
g = random.randint(2, p-1)
x = random.randint(2, p-1)
return (g, p), (x, p)
signal.alarm(3600)
key = keygen(1024)
(g, p), _ = key
print("[yoshiking]: Hello! Let's play Janken(RPS)")
print("[yoshiking]: Here is g: {}, and p: {}".format(g, p))
round = 0
wins = 0
while True:
round += 1
print("[system]: ROUND {}".format(round))
yoshiking_hand = random.randint(1, 3)
c = commit(yoshiking_hand, key)
print("[yoshiking]: my commitment is={}".format(c))
hand = input("[system]: your hand(1-3): ")
print("")
try:
hand = int(hand)
if not (1 <= hand <= 3):
raise ValueError()
except ValueError:
print("[yoshiking]: Ohhhhhhhhhhhhhhhh no! :(")
exit()
yoshiking_hand = decrypt(c, key)
print("[yoshiking]: My hand is ... {}".format(HANDNAMES[yoshiking_hand]))
print("[yoshiking]: Your hand is ... {}".format(HANDNAMES[hand]))
result = (yoshiking_hand - hand + 3) % 3
if result == 0:
print("[yoshiking]: Draw, draw, draw!!!")
elif result == 1:
print("[yoshiking]: Yo! You win!!! Ho!")
wins += 1
print("[system]: wins: {}".format(wins))
if wins >= 100:
break
elif result == 2:
print("[yoshiking]: Ahahahaha! I'm the winnnnnnner!!!!")
print("[yoshiking]: You, good loser!")
print("[system]: you can check that yoshiking doesn't cheat")
print("[system]: here's the private key: {}".format(key[1][0]))
exit()
print("[yoshiking]: Wow! You are the king of roshambo!")
print("[yoshiking]: suge- flag ageru")
print(flag)
```
## solution
It has better to focus on that the plaintext-space is quite small: 1 or 2 or 3. If we could see such like that the yoshiking's hand is even or not, or yoshiking's hand is quadratic residue or not.
The commitment is $(C_1, C_2) = (g^x, mg^{xr}) \mod p$. When we assume $g^{xr}$ is quadratic residue, $C_2$ is quadratic residue when $m$ is quadratic residue and $C_2$ is quadratic non-residue when $m$ is quadratic non-residue.
1 is always quadratic residue. Weather 2, 3 are quadratic residue or not is decided by $p$. For example, if we find a good $p$ such that $2, 3$ are quadratic non-residue, we can distinguish yoshiking's hand is 1 or otherwise by his commitment.
Thus, we can choose our hand adaptively in response to yoshiking's hand. Then we don't lose against yoshiking.
## exploit
```python=
from ptrlib import Socket
import random
import os
HOST = os.getenv("HOST", "localhost")
PORT = os.getenv("PORT", "9999")
def legendre_symbol(a, p):
ls = pow(a, (p - 1)//2, p)
if ls == p - 1:
return -1
return ls
while True:
sock = Socket(HOST, int(PORT))
g, p = sock.recvregex("g: ([0-9]+), and p: ([0-9]+)")
g, p = int(g), int(p)
# check parameter
try:
assert legendre_symbol(1, p) == 1
assert legendre_symbol(2, p) != 1
assert legendre_symbol(3, p) != 1
for _ in range(100):
m = random.randint(1, 3)
c = m * pow(g, random.randint(2, p-1), p) % p
assert legendre_symbol(m, p) == legendre_symbol(c, p)
except AssertionError:
sock.close()
continue
break
while True:
c1, c2 = eval(sock.recvlineafter("="))
qr = legendre_symbol(c2, p)
if qr == 1:
sock.sendlineafter("(1-3): ", "3")
else:
sock.sendlineafter("(1-3): ", "2")
sock.recvline()
sock.recvline()
sock.recvline()
result = sock.recvline().decode()
if "draw" in result:
pass
elif "ahaha" in result:
print("[-] something wrong")
exit()
elif "win" in result:
wins = int(sock.recvlineafter("wins: "))
print("wins: {}".format(wins))
if wins >= 100:
break
print(sock.recvuntil("}").decode())
```