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";
|
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");
|
||||||
|
@ -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];
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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];
|
||||||
|
@ -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.
@ -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}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user