Cooperative random
This commit is contained in:
parent
8e22d37b64
commit
42e2514c82
16
static/js/dom.js
Normal file
16
static/js/dom.js
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,61 +1,199 @@
|
|||||||
|
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) {
|
||||||
switch (data.type) {
|
return;
|
||||||
case "ANNOUNCE":
|
}
|
||||||
playerConnected(socket, data);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "KEEPALIVE":
|
switch (data.type) {
|
||||||
keepAlive(data);
|
case "ANNOUNCE":
|
||||||
break;
|
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
|
// 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
21
static/js/player.js
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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>
|
||||||
|
|
||||||
|
BIN
whitepaper/demonstration/presentation.pdf
Normal file
BIN
whitepaper/demonstration/presentation.pdf
Normal file
Binary file not shown.
46
whitepaper/demonstration/presentation.tex
Normal file
46
whitepaper/demonstration/presentation.tex
Normal 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}
|
Loading…
Reference in New Issue
Block a user