Cooperative random

This commit is contained in:
jude 2023-01-29 16:47:37 +00:00
parent 8e22d37b64
commit 42e2514c82
6 changed files with 246 additions and 24 deletions

16
static/js/dom.js Normal file
View File

@ -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);
}
}
}

View File

@ -1,21 +1,39 @@
import { Player } from "./player.js";
const ID = window.crypto.randomUUID(); const ID = window.crypto.randomUUID();
// Timeout to consider a player disconnected // Timeout to consider a player disconnected
const TIMEOUT = 30_000; const TIMEOUT = 30_000;
let players = {}; let players = {};
const WAITING = 0;
const PRE_GAME = 1;
const PLAYING = 2;
const POST_GAME = 3;
let game_state = WAITING;
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
let socket = io(); let socket = io();
socket.on("connect", () => { socket.on("connect", () => {
console.log("Connected!"); console.log("Connected!");
socket.emit("message", { type: "ANNOUNCE", author: ID, name: "" }); socket.emit("message", {
players[ID] = { name: "", timeout: null }; type: "ANNOUNCE",
id: window.crypto.randomUUID(),
author: ID,
name: "",
});
// Create self
players[ID] = new Player(ID, name);
}); });
socket.on("message", (data) => { socket.on("message", (data) => {
// Ignore any messages that originate from us. // Ignore any messages that originate from us.
if (data.author !== ID) { if (data.author === ID) {
return;
}
switch (data.type) { switch (data.type) {
case "ANNOUNCE": case "ANNOUNCE":
playerConnected(socket, data); playerConnected(socket, data);
@ -24,38 +42,158 @@ document.addEventListener("DOMContentLoaded", () => {
case "KEEPALIVE": case "KEEPALIVE":
keepAlive(data); keepAlive(data);
break; break;
}
case "SYNC":
sync(data);
break;
case "RANDOM":
cooperativeRandom(data);
break;
} }
}); });
// Emit keepalive messages to inform other players we are still here // Emit keepalive messages to inform other players we are still here
window.setInterval(() => { window.setInterval(() => {
socket.emit("message", { type: "KEEPALIVE", author: ID }); socket.emit("message", {
type: "KEEPALIVE",
id: window.crypto.randomUUID(),
author: ID,
});
}, TIMEOUT / 5); }, 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) { 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. // When a new player is seen, all announce to ensure they know all players.
if (players[data.author] === undefined) { if (players[data.author] === undefined) {
players[data.author] = { name: data.name, timeout: window.setTimeout(() => { delete players[data.author]; updatePlayerDom(); }, TIMEOUT) }; players[data.author] = new Player(data.author, data.name);
socket.emit("message", { type: "ANNOUNCE", author: ID, name: "" }); socket.emit("message", {
type: "ANNOUNCE",
id: window.crypto.randomUUID(),
author: ID,
name: "",
});
players[data.author].resetTimeout();
} else { } else {
} }
updatePlayerDom(); updatePlayerDom();
} }
/**
* Process keep-alive packets: these are packets that check players are still online.
*
* @param data Packet received
*/
function keepAlive(data) { function keepAlive(data) {
window.clearTimeout(players[data.author].timeout); players[data.author].resetTimeout();
players[data.author].timeout = window.setTimeout(() => { delete players[data.author]; updatePlayerDom(); }, TIMEOUT);
} }
function updatePlayerDom() { /**
let list = document.querySelector('#playerList'); * Process sync packets: update player details like status and name.
list.replaceChildren(); *
for (let playerId of Object.keys(players)) { * @param data Packet received
let newDom = document.createElement('li'); */
newDom.textContent = playerId; function sync(data) {
list.appendChild(newDom); 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;
}
} }
} }

21
static/js/player.js Normal file
View File

@ -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);
}
}

View File

@ -6,7 +6,8 @@
<link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet"> <link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet">
<script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script> <script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>
<script src="{{ url_for('static', filename='js/index.js') }}"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
<script type="module" src="{{ url_for('static', filename='js/index.js') }}"></script>
</head> </head>
<body> <body>

Binary file not shown.

View File

@ -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}