Fixed most other stuff that I broke

This commit is contained in:
jude 2023-03-05 17:19:37 +00:00
parent 96fe20503a
commit c0f2c4bbef
9 changed files with 88 additions and 50 deletions

View File

@ -3,12 +3,12 @@ import { Region } from "./map.js";
import { Packet } from "./packet.js"; import { Packet } from "./packet.js";
export function unlockMapDom() { export function unlockMapDom() {
Object.values(REGIONS).forEach((region) => { Object.values(Region.getAllRegions()).forEach((region) => {
if (!allRegionsClaimed() && region.owner === null) { if (!Region.allRegionsClaimed() && region.owner === null) {
document document
.querySelector(`.node[data-name=${region.name}] .actions`) .querySelector(`.node[data-name=${region.name}] .actions`)
.classList.remove("hidden"); .classList.remove("hidden");
} else if (region.owner === us) { } else if (region.owner === game.us) {
document document
.querySelector(`.node[data-name=${region.name}] .actions`) .querySelector(`.node[data-name=${region.name}] .actions`)
.classList.remove("hidden"); .classList.remove("hidden");
@ -36,7 +36,7 @@ function updateMapDom() {
e.classList.remove("hidden"); e.classList.remove("hidden");
}); });
if (us.isPlaying) { if (game.us.isPlaying) {
document.querySelector("#end-turn").classList.remove("hidden"); document.querySelector("#end-turn").classList.remove("hidden");
} else { } else {
document.querySelector("#end-turn").classList.add("hidden"); document.querySelector("#end-turn").classList.add("hidden");
@ -49,6 +49,8 @@ function updateMapDom() {
element.style.backgroundColor = element.style.backgroundColor =
region.owner === null ? "white" : region.owner.getColor(); region.owner === null ? "white" : region.owner.getColor();
} }
showRemainingReinforcements();
} }
document.addEventListener("gameStateUpdate", () => { document.addEventListener("gameStateUpdate", () => {
@ -57,6 +59,8 @@ document.addEventListener("gameStateUpdate", () => {
} }
}); });
document.addEventListener("gameStateUpdate", updateMapDom); document.addEventListener("gameStateUpdate", updateMapDom);
document.addEventListener("playerChange", updateMapDom);
document.addEventListener("turnProgress", updateMapDom);
function updatePlayerDom() { function updatePlayerDom() {
let list = document.querySelector("#playerList"); let list = document.querySelector("#playerList");
@ -99,7 +103,7 @@ function updatePlayerDom() {
document.addEventListener("addPlayer", updatePlayerDom); document.addEventListener("addPlayer", updatePlayerDom);
document.addEventListener("removePlayer", updatePlayerDom); document.addEventListener("removePlayer", updatePlayerDom);
document.addEventListener("updatePlayer", updatePlayerDom); document.addEventListener("updatePlayer", updatePlayerDom);
document.addEventListener("gameStateUpdate", updatePlayerDom); document.addEventListener("playerChange", updatePlayerDom);
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
document.querySelector("#ready-button").addEventListener("click", async (ev) => { document.querySelector("#ready-button").addEventListener("click", async (ev) => {
@ -207,17 +211,18 @@ function showRemainingReinforcements() {
if (game.isPregame()) { if (game.isPregame()) {
document.querySelector( document.querySelector(
"#remaining-reinforcements" "#remaining-reinforcements"
).innerHTML = `<span>Remaining placements: ${reinforcementsRemaining()}</span>`; ).innerHTML = `<span>Remaining placements: ${Region.reinforcementsRemaining()}</span>`;
} else if (game.isPlaying()) { } else if (game.isPlaying()) {
document.querySelector( document.querySelector(
"#remaining-reinforcements" "#remaining-reinforcements"
).innerHTML = `<span>Remaining placements: ${ ).innerHTML = `<span>Remaining placements: ${
currentPlayer().reinforcementsAvailable - currentPlayer().reinforcementsPlaced game.currentPlayer().reinforcementsAvailable -
game.currentPlayer().reinforcementsPlaced
}</span>`; }</span>`;
} }
} }
function showDefenseDom(region) { export function showDefenseDom(region) {
const modal = document.querySelector("#defense-modal"); const modal = document.querySelector("#defense-modal");
modal.querySelector("span").textContent = region; modal.querySelector("span").textContent = region;
modal.classList.remove("hidden"); modal.classList.remove("hidden");

View File

@ -32,6 +32,10 @@ export class Game {
document.dispatchEvent(event); document.dispatchEvent(event);
} }
playerCount() {
return Object.values(this.players).length;
}
currentPlayer() { currentPlayer() {
return Object.values(this.players).filter((p) => p.isPlaying)[0]; return Object.values(this.players).filter((p) => p.isPlaying)[0];
} }

View File

@ -3,12 +3,13 @@ import { Random } from "./random.js";
import { Barrier } from "./barrier.js"; import { Barrier } from "./barrier.js";
import { Packet } from "./packet.js"; import { Packet } from "./packet.js";
import { Game } from "./game.js"; import { Game } from "./game.js";
import { Region } from "./map.js";
import "./dom.js"; import "./dom.js";
export const ID = window.crypto.randomUUID(); export const ID = window.crypto.randomUUID();
export const game = new Game(); export const game = new Game();
export let socket; export let socket;
let random; export let random;
let barrier; let barrier;
window.paillier = generate_keypair(); window.paillier = generate_keypair();
window.rsa = generate_rsa_keypair(); window.rsa = generate_rsa_keypair();
@ -42,8 +43,8 @@ document.addEventListener("DOMContentLoaded", () => {
} else { } else {
let sig = BigInt(packet.sig); let sig = BigInt(packet.sig);
// decrypt and compare signature // decrypt and compare signature
let dehash = sender.rsaPubKey.encrypt(sig).toString(16); let dehash = sender.rsaPubKey.encrypt(sig);
let hash = CryptoJS.SHA3(JSON.stringify(data)).toString(); let hash = BigInt("0x" + CryptoJS.SHA3(JSON.stringify(data)).toString());
if (dehash !== hash) { if (dehash !== hash) {
window.console.error(`Signature invalid! Ignoring packet ${data.id}.`); window.console.error(`Signature invalid! Ignoring packet ${data.id}.`);
return; return;
@ -138,6 +139,9 @@ document.addEventListener("ACT", async (ev) => {
} else { } else {
if (await game.currentPlayer().act(data)) { if (await game.currentPlayer().act(data)) {
game.currentPlayer().endTurn(); game.currentPlayer().endTurn();
} else {
const event = new CustomEvent("turnProgress");
document.dispatchEvent(event);
} }
} }
} }
@ -156,5 +160,8 @@ document.addEventListener("gameStateUpdate", async () => {
firstPlayer.isPlaying = true; firstPlayer.isPlaying = true;
await barrier.wait(); await barrier.wait();
const event = new CustomEvent("playerChange");
document.dispatchEvent(event);
} }
}); });

View File

@ -1,9 +1,11 @@
import { game } from "./main.js";
let allPlaced = false; let allPlaced = false;
// In standard Risk, this is 5 // In standard Risk, this is 5
const _REINFORCEMENT_MULTIPLIER = 1; const _REINFORCEMENT_MULTIPLIER = 1;
export const REGIONS = {}; const REGIONS = {};
class Continent { class Continent {
constructor(name) { constructor(name) {
@ -39,11 +41,10 @@ export class Region {
return 0; return 0;
} else { } else {
let totalStrength = Object.values(REGIONS) let totalStrength = Object.values(REGIONS)
.filter((region) => region.owner === us) .filter((region) => region.owner === game.us)
.reduce((counter, region) => counter + region.strength, 0); .reduce((counter, region) => counter + region.strength, 0);
let numPlayers = Object.values(players).length;
return _REINFORCEMENT_MULTIPLIER * (10 - numPlayers) - totalStrength; return _REINFORCEMENT_MULTIPLIER * (10 - game.playerCount()) - totalStrength;
} }
} }
@ -55,11 +56,12 @@ export class Region {
(counter, region) => counter + region.strength, (counter, region) => counter + region.strength,
0 0
); );
let numPlayers = Object.values(players).length;
allPlaced = allPlaced =
totalStrength >= totalStrength >=
numPlayers * _REINFORCEMENT_MULTIPLIER * (10 - numPlayers); game.playerCount() *
_REINFORCEMENT_MULTIPLIER *
(10 - game.playerCount());
return allPlaced; return allPlaced;
} }
} }

View File

@ -52,15 +52,34 @@ export class Packet {
}); });
} }
static createRandomCyphertext(sessionId, range, cipherText) {
return this._sign({
...this._createBase("RANDOM"),
session: sessionId,
range: range,
stage: "CIPHERTEXT",
cipherText: cipherText,
});
}
static createRandomKey(sessionId, key) {
return this._sign({
...this._createBase("RANDOM"),
session: sessionId,
stage: "DECRYPT",
cipherKey: key,
});
}
static createBarrierSignal() { static createBarrierSignal() {
return this._sign(this._createBase("BARRIER")); return this._sign(this._createBase("BARRIER"));
} }
static createRegionClaim(region) { static createRegionClaim(region) {
return { return this._sign({
...this._createBase("ACT"), ...this._createBase("ACT"),
region: region, region: region,
}; });
} }
static createAction(action, startRegion, endRegion, amount) { static createAction(action, startRegion, endRegion, amount) {

View File

@ -1,6 +1,8 @@
import { Packet } from "./packet.js"; import { Packet } from "./packet.js";
import { socket } from "./main.js"; import { socket, game, random } from "./main.js";
import { RsaPubKey } from "../crypto/rsa.js"; import { RsaPubKey } from "../crypto/rsa.js";
import { Region } from "./map.js";
import { showDefenseDom } from "./dom.js";
// Timeout to consider a player disconnected // Timeout to consider a player disconnected
const TIMEOUT = 30_000; const TIMEOUT = 30_000;
@ -100,9 +102,6 @@ export class Player {
* @returns {boolean} Whether this player's turn has ended or not. * @returns {boolean} Whether this player's turn has ended or not.
*/ */
async act(data) { async act(data) {
console.log(`player: ${this.id}`);
console.log(data);
if (this.turnPhase === PHASE_REINFORCE) { if (this.turnPhase === PHASE_REINFORCE) {
if (data.region !== undefined) { if (data.region !== undefined) {
if (this.reinforce(data)) { if (this.reinforce(data)) {
@ -158,7 +157,7 @@ export class Player {
} }
// If we're the defender, we need to send a packet to state our defense. // If we're the defender, we need to send a packet to state our defense.
if (defender.owner === us) { if (defender.owner === game.us) {
showDefenseDom(defender.name); showDefenseDom(defender.name);
} }
@ -223,8 +222,6 @@ export class Player {
// Reset the promises in case they attack again. // Reset the promises in case they attack again.
defender.owner.defenderPromise = null; defender.owner.defenderPromise = null;
defender.owner.defenderAmount = null; defender.owner.defenderAmount = null;
updateDom();
} }
async setDefense(amount) { async setDefense(amount) {
@ -284,8 +281,9 @@ export class Player {
return Math.min( return Math.min(
3, 3,
Math.floor( Math.floor(
Object.values(REGIONS).filter((region) => region.owner === this).length / Object.values(Region.getAllRegions()).filter(
3 (region) => region.owner === this
).length / 3
) )
); );
} }
@ -307,10 +305,14 @@ export class Player {
endTurn() { endTurn() {
this.isPlaying = false; this.isPlaying = false;
this.nextPlayer().startTurn(); this.nextPlayer().startTurn();
const event = new CustomEvent("playerChange");
document.dispatchEvent(event);
} }
nextPlayer() { nextPlayer() {
let sorted = Object.values(players).sort((a, b) => (a.id < b.id ? -1 : 1)); // todo move this to :class:Game
let sorted = Object.values(game.players).sort((a, b) => (a.id < b.id ? -1 : 1));
let ourIndex = sorted.findIndex((player) => player.id === this.id); let ourIndex = sorted.findIndex((player) => player.id === this.id);
return sorted[(ourIndex + 1) % sorted.length]; return sorted[(ourIndex + 1) % sorted.length];

View File

@ -1,4 +1,5 @@
import { socket, ID, game } from "./main.js"; import { socket, game } from "./main.js";
import { Packet } from "./packet.js";
class RandomSession { class RandomSession {
constructor(range) { constructor(range) {
@ -56,14 +57,10 @@ export class Random {
this.sessions[sessionId] = session; this.sessions[sessionId] = session;
socket.emit("message", { socket.emit(
type: "RANDOM", "message",
author: ID, Packet.createRandomCyphertext(sessionId, n, session.cipherText())
session: sessionId, );
range: n,
stage: "CIPHERTEXT",
cipherText: session.cipherText(),
});
return session; return session;
} }
@ -90,13 +87,10 @@ export class Random {
Object.keys(game.players).length - 1 Object.keys(game.players).length - 1
) { ) {
// Step 3: release our key once all players have sent a ciphertext // Step 3: release our key once all players have sent a ciphertext
socket.emit("message", { socket.emit(
type: "RANDOM", "message",
author: ID, Packet.createRandomKey(data.session, session.ourKey)
session: data.session, );
stage: "DECRYPT",
cipherKey: session.ourKey,
});
} }
} else if (stage === "DECRYPT") { } else if (stage === "DECRYPT") {
session.cipherKeys[data.author] = data.cipherKey; session.cipherKeys[data.author] = data.cipherKey;

Binary file not shown.

View File

@ -259,10 +259,14 @@ In particular, the final point allows for the use of purely JSON messages, which
\subsection{Message structure} \subsection{Message structure}
Messages are given a fixed structure to make processing simpler. Each JSON message holds an \texttt{author} field, being the sender's ID, and an \texttt{action}, which at a high level dictates how each client should process the message. Messages are given a fixed structure to make processing simpler. Each JSON message holds an \texttt{author} field, being the sender's ID; a message ID to prevent replay attacks and associate related messages; and an \texttt{action}, which at a high level dictates how each client should process the message.
The action more specifically is one of \texttt{ANNOUNCE}, \texttt{DISCONNECT}, \texttt{KEEPALIVE}, \texttt{RANDOM}, and \texttt{ACT}. The first three of these are used for managing the network by ensuring peers are aware of each other and know the state of the network. \texttt{RANDOM} is designated to be used by the shared-random-value subprotocol defined later. \texttt{ACT} is used by players to submit actions for their turn during gameplay.
Each message is also signed to verify the author. This is a standard application of RSA. A hash of the message is taken, then encrypted with the private key. This can be verified with the public key. Each message is also signed to verify the author. This is a standard application of RSA. A hash of the message is taken, then encrypted with the private key. This can be verified with the public key.
RSA keys are accepted by peers on a first-seen basis.
\subsection{Paillier} \subsection{Paillier}
Paillier requires the calculation of two large primes for the generation of public and private key pairs. ECMAScript typically stores integers as floating point numbers, giving precision up to $2^{53}$. This is clearly inappropriate for the generation of sufficiently large primes. Paillier requires the calculation of two large primes for the generation of public and private key pairs. ECMAScript typically stores integers as floating point numbers, giving precision up to $2^{53}$. This is clearly inappropriate for the generation of sufficiently large primes.
@ -362,7 +366,7 @@ Then, a proof for the following homologous problem can be trivially constructed:
\subsection{Application to domain} \subsection{Application to domain}
Players should prove a number of properties of their game state to each other to ensure fair play. These are as follows. \begin{itemize} Players should prove a number of properties of their game state to each other to ensure fair play. These are as follows. \begin{enumerate}
\item The number of reinforcements placed during the first stage of a turn. \item The number of reinforcements placed during the first stage of a turn.
\item The number of units on a region neighbouring another player. \item The number of units on a region neighbouring another player.
@ -372,10 +376,11 @@ Players should prove a number of properties of their game state to each other to
\item The number of units available for an attack/defence. \item The number of units available for an attack/defence.
\item The number of units moved when fortifying. \item The number of units moved when fortifying.
\end{itemize} \end{enumerate}
Of these, the bottom two are, more specifically, range proofs. %todo is this grammar right? (4) and (5) can be generalised further as range proofs.
The top three can be addressed with the protocol we have provided however.
For (1), we propose the following communication sequence. The player submits pairs $(R, c_R)$ for each region they control, where $R$ is the region and $c_R$ is a cyphertext encoding the number of reinforcements to add to the region (which may be 0). Each player computes $c_{R_1} \cdot \ldots \cdot c_{R_n}$.
\bibliography{Dissertation} \bibliography{Dissertation}