Fixed most other stuff that I broke
This commit is contained in:
parent
96fe20503a
commit
c0f2c4bbef
@ -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");
|
||||
|
@ -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];
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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];
|
||||
|
@ -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.
@ -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}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user