diff --git a/static/js/dom.js b/static/js/dom.js new file mode 100644 index 0000000..05a3142 --- /dev/null +++ b/static/js/dom.js @@ -0,0 +1,16 @@ +export function updatePlayerDom() { + let list = document.querySelector("#playerList"); + list.replaceChildren(); + + let newDom = document.createElement("li"); + newDom.textContent = ID; + list.appendChild(newDom); + + for (let playerId of Object.keys(players)) { + let newDom = document.createElement("li"); + if (playerId !== ID) { + newDom.textContent = playerId; + list.appendChild(newDom); + } + } +} diff --git a/static/js/index.js b/static/js/index.js index 28873c8..cbacc64 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -1,61 +1,199 @@ +import { Player } from "./player.js"; + const ID = window.crypto.randomUUID(); // Timeout to consider a player disconnected const TIMEOUT = 30_000; let players = {}; +const WAITING = 0; +const PRE_GAME = 1; +const PLAYING = 2; +const POST_GAME = 3; + +let game_state = WAITING; + document.addEventListener("DOMContentLoaded", () => { let socket = io(); socket.on("connect", () => { console.log("Connected!"); - socket.emit("message", { type: "ANNOUNCE", author: ID, name: "" }); - players[ID] = { name: "", timeout: null }; + socket.emit("message", { + type: "ANNOUNCE", + id: window.crypto.randomUUID(), + author: ID, + name: "", + }); + // Create self + players[ID] = new Player(ID, name); }); socket.on("message", (data) => { // Ignore any messages that originate from us. - if (data.author !== ID) { - switch (data.type) { - case "ANNOUNCE": - playerConnected(socket, data); - break; + if (data.author === ID) { + return; + } - case "KEEPALIVE": - keepAlive(data); - break; - } + switch (data.type) { + case "ANNOUNCE": + playerConnected(socket, data); + break; + + case "KEEPALIVE": + keepAlive(data); + break; + + case "SYNC": + sync(data); + break; + + case "RANDOM": + cooperativeRandom(data); + break; } }); // Emit keepalive messages to inform other players we are still here window.setInterval(() => { - socket.emit("message", { type: "KEEPALIVE", author: ID }); + socket.emit("message", { + type: "KEEPALIVE", + id: window.crypto.randomUUID(), + author: ID, + }); }, TIMEOUT / 5); }); +/** + * Process player connect packets: these inform that a new player has joined. + * + * @param socket Socket instance used to announce ourselves to new player + * @param data Packet received + */ function playerConnected(socket, data) { + // Block players from joining mid-game + if (game_state !== WAITING) { + return; + } + // When a new player is seen, all announce to ensure they know all players. if (players[data.author] === undefined) { - players[data.author] = { name: data.name, timeout: window.setTimeout(() => { delete players[data.author]; updatePlayerDom(); }, TIMEOUT) }; - socket.emit("message", { type: "ANNOUNCE", author: ID, name: "" }); + players[data.author] = new Player(data.author, data.name); + socket.emit("message", { + type: "ANNOUNCE", + id: window.crypto.randomUUID(), + author: ID, + name: "", + }); + players[data.author].resetTimeout(); } else { } updatePlayerDom(); } +/** + * Process keep-alive packets: these are packets that check players are still online. + * + * @param data Packet received + */ function keepAlive(data) { - window.clearTimeout(players[data.author].timeout); - players[data.author].timeout = window.setTimeout(() => { delete players[data.author]; updatePlayerDom(); }, TIMEOUT); + players[data.author].resetTimeout(); } -function updatePlayerDom() { - let list = document.querySelector('#playerList'); - list.replaceChildren(); - for (let playerId of Object.keys(players)) { - let newDom = document.createElement('li'); - newDom.textContent = playerId; - list.appendChild(newDom); +/** + * Process sync packets: update player details like status and name. + * + * @param data Packet received + */ +function sync(data) { + players[data.author].name = data.name; + players[data.author].ready = data.ready; + + if (allPlayersReady()) { + game_state = PRE_GAME; + // Decide turn order. "Master" begins a cooperative rng process. + + // + } +} + +function allPlayersReady() { + for (let player of Object.values(players)) { + if (!player.ready) { + return false; + } + } + + return true; +} + +// Track ongoing random sessions. +const randomSessions = {}; + +/** + * Process cooperative random protocol. + * + * @param socket Socket instance for communication + * @param data Packet received + */ +function cooperativeRandom(socket, data) { + // Step 0: extract relevant information from data + let session = randomSessions[data.session]; + const stage = data.stage; + + if (session === undefined) { + // Step 1: generate and encrypt our random value. 4 bytes = 32 bit integer + const noise = CryptoJS.libs.WordArray.random(4); + const key = CryptoJS.libs.WordArray.random(256); + const cipherText = CryptoJS.AES.encrypt(noise, key); + + randomSessions[data.session] = { + range: data.range, + cipherTexts: {}, + cipherKeys: {}, + ourKey: key, + ourNoise: noise, + finalValue: -1, + }; + session = randomSessions[data.session]; + + // Step 2: send our random value and wait for all responses + socket.emit("message", { + type: "RANDOM", + author: ID, + stage: "CIPHERTEXT", + range: data.range, + cipherText: cipherText, + }); + } + + if (stage === "CIPHERTEXT") { + session.cipherTexts[data.author] = data.cipherText; + + if (Object.keys(session.cipherTexts).length === Object.keys(players).length) { + // Step 3: release our key once all players have sent a ciphertext + socket.emit("message", { + type: "RANDOM", + author: ID, + stage: "DECRYPT", + cipherKey: session.ourKey, + }); + } + } else if (stage === "DECRYPT") { + session.cipherKeys[data.author] = data.cipherKey; + + // Step 4: get final random value + if (Object.keys(session.cipherKeys).length === Object.keys(players).length) { + let total = 0; + + for (let participant of Object.keys(session.cipherKeys)) { + total += CryptoJS.AES.decrypt( + session.cipherTexts[participant], + session.cipherKeys[participant] + ); + } + + session.finalValue = total % session.range; + } } } diff --git a/static/js/player.js b/static/js/player.js new file mode 100644 index 0000000..14b804e --- /dev/null +++ b/static/js/player.js @@ -0,0 +1,21 @@ +import { updatePlayerDom } from "./dom.js"; + +export class Player { + constructor(id, name) { + this.name = name; + this.timeout = null; + this.id = id; + this.ready = false; + } + + resetTimeout() { + if (this.timeout !== null) { + window.clearTimeout(players[data.author].timeout); + } + + this.timeout = window.setTimeout(() => { + delete players[this.id]; + updatePlayerDom(); + }, TIMEOUT); + } +} diff --git a/templates/index.html b/templates/index.html index 0c49a63..0e81ac8 100644 --- a/templates/index.html +++ b/templates/index.html @@ -6,7 +6,8 @@ - + + diff --git a/whitepaper/demonstration/presentation.pdf b/whitepaper/demonstration/presentation.pdf new file mode 100644 index 0000000..5ab5437 Binary files /dev/null and b/whitepaper/demonstration/presentation.pdf differ diff --git a/whitepaper/demonstration/presentation.tex b/whitepaper/demonstration/presentation.tex new file mode 100644 index 0000000..ca488ce --- /dev/null +++ b/whitepaper/demonstration/presentation.tex @@ -0,0 +1,46 @@ +\documentclass{beamer} +\usetheme{default} + +\setbeamertemplate{frametitle}[default][center] + +\title{"Risk" in an untrusted setting} +\author{Jude Southworth} +\begin{document} +\begin{frame}[plain] + \maketitle +\end{frame} +\begin{frame}{Risk} + \begin{itemize} + \item \textit{Risk} is a popular strategy board game. + \item It is played on a single board, depicting a world map, partitioned into regions. + \item A player owns a region of the map by stationing troops within the region. + \item Players fight for regions by gambling some of their troops against the troops in the other player's region. + \end{itemize} +\end{frame} +\begin{frame}{Risk} + \begin{itemize} + \item \textit{Risk} has a variant called "fog of war". + \item In this variant, players cannot see the number of troops stationed within regions they don't control, or don't neighbour. + \item This variant is therefore only played online, in a \textbf{trusted setup}. + \end{itemize} +\end{frame} +\begin{frame}{Proposition} + \begin{itemize} + \item Play fog-of-war Risk in an untrusted setup. + \item In the untrusted setup, the same guarantees should be made as the trusted setup, but on a peer-to-peer network. + \end{itemize} +\end{frame} +\begin{frame}{Proposition} + \begin{itemize} + \item Zero-knowledge proofs. \begin{itemize} + \item + \end{itemize} + \item Asymmetric encryption. \begin{itemize} + \item + \end{itemize} + \item Hashing. \begin{itemize} + \item + \end{itemize} + \end{itemize} +\end{frame} +\end{document}