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

View File

@ -32,6 +32,10 @@ export class Game {
document.dispatchEvent(event);
}
playerCount() {
return Object.values(this.players).length;
}
currentPlayer() {
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 { Packet } from "./packet.js";
import { Game } from "./game.js";
import { Region } from "./map.js";
import "./dom.js";
export const ID = window.crypto.randomUUID();
export const game = new Game();
export let socket;
let random;
export let random;
let barrier;
window.paillier = generate_keypair();
window.rsa = generate_rsa_keypair();
@ -42,8 +43,8 @@ document.addEventListener("DOMContentLoaded", () => {
} else {
let sig = BigInt(packet.sig);
// decrypt and compare signature
let dehash = sender.rsaPubKey.encrypt(sig).toString(16);
let hash = CryptoJS.SHA3(JSON.stringify(data)).toString();
let dehash = sender.rsaPubKey.encrypt(sig);
let hash = BigInt("0x" + CryptoJS.SHA3(JSON.stringify(data)).toString());
if (dehash !== hash) {
window.console.error(`Signature invalid! Ignoring packet ${data.id}.`);
return;
@ -138,6 +139,9 @@ document.addEventListener("ACT", async (ev) => {
} else {
if (await game.currentPlayer().act(data)) {
game.currentPlayer().endTurn();
} else {
const event = new CustomEvent("turnProgress");
document.dispatchEvent(event);
}
}
}
@ -156,5 +160,8 @@ document.addEventListener("gameStateUpdate", async () => {
firstPlayer.isPlaying = true;
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;
// In standard Risk, this is 5
const _REINFORCEMENT_MULTIPLIER = 1;
export const REGIONS = {};
const REGIONS = {};
class Continent {
constructor(name) {
@ -39,11 +41,10 @@ export class Region {
return 0;
} else {
let totalStrength = Object.values(REGIONS)
.filter((region) => region.owner === us)
.filter((region) => region.owner === game.us)
.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,
0
);
let numPlayers = Object.values(players).length;
allPlaced =
totalStrength >=
numPlayers * _REINFORCEMENT_MULTIPLIER * (10 - numPlayers);
game.playerCount() *
_REINFORCEMENT_MULTIPLIER *
(10 - game.playerCount());
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() {
return this._sign(this._createBase("BARRIER"));
}
static createRegionClaim(region) {
return {
return this._sign({
...this._createBase("ACT"),
region: region,
};
});
}
static createAction(action, startRegion, endRegion, amount) {

View File

@ -1,6 +1,8 @@
import { Packet } from "./packet.js";
import { socket } from "./main.js";
import { socket, game, random } from "./main.js";
import { RsaPubKey } from "../crypto/rsa.js";
import { Region } from "./map.js";
import { showDefenseDom } from "./dom.js";
// Timeout to consider a player disconnected
const TIMEOUT = 30_000;
@ -100,9 +102,6 @@ export class Player {
* @returns {boolean} Whether this player's turn has ended or not.
*/
async act(data) {
console.log(`player: ${this.id}`);
console.log(data);
if (this.turnPhase === PHASE_REINFORCE) {
if (data.region !== undefined) {
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 (defender.owner === us) {
if (defender.owner === game.us) {
showDefenseDom(defender.name);
}
@ -223,8 +222,6 @@ export class Player {
// Reset the promises in case they attack again.
defender.owner.defenderPromise = null;
defender.owner.defenderAmount = null;
updateDom();
}
async setDefense(amount) {
@ -284,8 +281,9 @@ export class Player {
return Math.min(
3,
Math.floor(
Object.values(REGIONS).filter((region) => region.owner === this).length /
3
Object.values(Region.getAllRegions()).filter(
(region) => region.owner === this
).length / 3
)
);
}
@ -307,10 +305,14 @@ export class Player {
endTurn() {
this.isPlaying = false;
this.nextPlayer().startTurn();
const event = new CustomEvent("playerChange");
document.dispatchEvent(event);
}
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);
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 {
constructor(range) {
@ -56,14 +57,10 @@ export class Random {
this.sessions[sessionId] = session;
socket.emit("message", {
type: "RANDOM",
author: ID,
session: sessionId,
range: n,
stage: "CIPHERTEXT",
cipherText: session.cipherText(),
});
socket.emit(
"message",
Packet.createRandomCyphertext(sessionId, n, session.cipherText())
);
return session;
}
@ -90,13 +87,10 @@ export class Random {
Object.keys(game.players).length - 1
) {
// Step 3: release our key once all players have sent a ciphertext
socket.emit("message", {
type: "RANDOM",
author: ID,
session: data.session,
stage: "DECRYPT",
cipherKey: session.ourKey,
});
socket.emit(
"message",
Packet.createRandomKey(data.session, session.ourKey)
);
}
} else if (stage === "DECRYPT") {
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}
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.
RSA keys are accepted by peers on a first-seen basis.
\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.
@ -362,7 +366,7 @@ Then, a proof for the following homologous problem can be trivially constructed:
\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 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 moved when fortifying.
\end{itemize}
\end{enumerate}
Of these, the bottom two are, more specifically, range proofs. %todo is this grammar right?
The top three can be addressed with the protocol we have provided however.
(4) and (5) can be generalised further as range proofs.
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}