2023-03-04 10:50:49 +00:00
|
|
|
import { generate_keypair, generate_rsa_keypair } from "../crypto/main.js";
|
2023-03-03 17:34:15 +00:00
|
|
|
import { Random } from "./random.js";
|
|
|
|
import { Barrier } from "./barrier.js";
|
|
|
|
import { Packet } from "./packet.js";
|
2023-03-04 00:25:54 +00:00
|
|
|
import { Game } from "./game.js";
|
2023-03-05 17:19:37 +00:00
|
|
|
import { Region } from "./map.js";
|
2023-03-04 00:25:54 +00:00
|
|
|
import "./dom.js";
|
2023-04-10 18:05:10 +00:00
|
|
|
import "./proofs.js";
|
2023-03-03 17:34:15 +00:00
|
|
|
|
|
|
|
export const ID = window.crypto.randomUUID();
|
|
|
|
export const game = new Game();
|
2023-03-13 14:52:14 +00:00
|
|
|
window.game = game;
|
2023-03-03 17:34:15 +00:00
|
|
|
export let socket;
|
2023-03-05 17:19:37 +00:00
|
|
|
export let random;
|
2023-03-03 17:34:15 +00:00
|
|
|
let barrier;
|
2023-03-04 10:50:49 +00:00
|
|
|
window.paillier = generate_keypair();
|
|
|
|
window.rsa = generate_rsa_keypair();
|
2023-03-03 17:34:15 +00:00
|
|
|
|
|
|
|
// Not totally reliable but better than nothing.
|
|
|
|
window.addEventListener("beforeunload", () => {
|
|
|
|
socket.emit("message", Packet.createDisconnect());
|
|
|
|
});
|
|
|
|
|
|
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
|
|
socket = io();
|
2023-04-06 19:42:24 +00:00
|
|
|
|
2023-03-03 17:34:15 +00:00
|
|
|
random = new Random();
|
|
|
|
barrier = new Barrier();
|
|
|
|
|
|
|
|
socket.on("connect", () => {
|
|
|
|
window.console.log("Connected!");
|
2023-03-04 00:25:54 +00:00
|
|
|
window.console.log(`We are: ${ID}`);
|
2023-03-13 14:52:14 +00:00
|
|
|
socket.emit("message", Packet.createAnnounce(window.rsa, window.paillier));
|
|
|
|
game.addPlayer(ID, true, window.rsa.pubKey, window.paillier.pubKey);
|
2023-03-03 17:34:15 +00:00
|
|
|
});
|
|
|
|
|
2023-03-04 14:19:26 +00:00
|
|
|
socket.on("message", async (packet) => {
|
2023-04-09 17:36:58 +00:00
|
|
|
// window.console.log(`Received size: ${JSON.stringify(packet).length}`);
|
2023-04-06 19:42:24 +00:00
|
|
|
|
2023-03-04 14:19:26 +00:00
|
|
|
let data = packet.payload;
|
2023-03-13 14:52:14 +00:00
|
|
|
if (data.type !== "KEEPALIVE") window.console.log(data);
|
2023-03-04 00:25:54 +00:00
|
|
|
|
2023-03-04 14:19:26 +00:00
|
|
|
const sender = game.players[data.author];
|
|
|
|
if (sender === undefined) {
|
|
|
|
window.console.warn(
|
|
|
|
`Not able to verify signature on packet ${data.id} as the sender is unknown.`
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
let sig = BigInt(packet.sig);
|
|
|
|
// decrypt and compare signature
|
2023-03-05 17:19:37 +00:00
|
|
|
let dehash = sender.rsaPubKey.encrypt(sig);
|
2023-05-02 13:19:15 +00:00
|
|
|
|
|
|
|
let hasher = new jsSHA("SHA3-256", "TEXT");
|
|
|
|
hasher.update(JSON.stringify(data));
|
|
|
|
let hash = BigInt("0x" + hasher.getHash("HEX"));
|
2023-03-04 14:19:26 +00:00
|
|
|
if (dehash !== hash) {
|
|
|
|
window.console.error(`Signature invalid! Ignoring packet ${data.id}.`);
|
|
|
|
return;
|
|
|
|
}
|
2023-03-07 15:43:47 +00:00
|
|
|
|
|
|
|
// check if the packet is replayed.
|
|
|
|
if (data.timestamp <= sender.lastPacket) {
|
|
|
|
window.console.error(`Replay detected! Ignoring packet ${data.id}.`);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
sender.lastPacket = data.timestamp;
|
2023-03-04 14:19:26 +00:00
|
|
|
}
|
2023-03-03 17:34:15 +00:00
|
|
|
|
|
|
|
switch (data.type) {
|
|
|
|
case "RANDOM":
|
|
|
|
if (data.author === ID) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
await random.processCooperativeRandom(data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "BARRIER":
|
|
|
|
if (data.author === ID) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
barrier.resolve(data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
const event = new CustomEvent(data.type, { detail: data });
|
|
|
|
document.dispatchEvent(event);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Process player connect packets: these inform that a new player has joined.
|
|
|
|
*
|
|
|
|
* @param data Packet received
|
|
|
|
*/
|
2023-03-04 00:25:54 +00:00
|
|
|
document.addEventListener("ANNOUNCE", (ev) => {
|
|
|
|
const data = ev.detail;
|
2023-03-03 17:34:15 +00:00
|
|
|
if (data.author === ID) return;
|
|
|
|
|
2023-03-13 14:52:14 +00:00
|
|
|
let is_new = game.addPlayer(data.author, false, data.rsaPubKey, data.paillierPubKey);
|
2023-03-03 17:34:15 +00:00
|
|
|
|
|
|
|
// When a new player is seen, all announce to ensure they know all players.
|
|
|
|
if (is_new) {
|
2023-03-13 14:52:14 +00:00
|
|
|
socket.emit("message", Packet.createAnnounce(window.rsa, window.paillier));
|
2023-03-03 17:34:15 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-03-04 00:25:54 +00:00
|
|
|
document.addEventListener("DISCONNECT", (ev) => {
|
|
|
|
const data = ev.detail;
|
2023-03-03 17:34:15 +00:00
|
|
|
game.removePlayer(data.author);
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Process keep-alive packets: these are packets that check players are still online.
|
|
|
|
*
|
|
|
|
* @param data Packet received
|
|
|
|
*/
|
2023-03-04 00:25:54 +00:00
|
|
|
document.addEventListener("KEEPALIVE", (ev) => {
|
|
|
|
const data = ev.detail;
|
2023-03-03 17:34:15 +00:00
|
|
|
game.keepAlive(data.author);
|
|
|
|
});
|
|
|
|
|
2023-03-04 00:25:54 +00:00
|
|
|
document.addEventListener("ACT", async (ev) => {
|
|
|
|
const data = ev.detail;
|
2023-03-03 17:34:15 +00:00
|
|
|
|
|
|
|
if (game.isWaiting()) {
|
|
|
|
game.setReady(data.author, data.ready);
|
2023-03-04 00:25:54 +00:00
|
|
|
} else {
|
2023-03-17 10:42:11 +00:00
|
|
|
// Throw out our own packets
|
2023-04-21 08:50:20 +00:00
|
|
|
if (!game.isPlaying() && data.author === game.us.id) {
|
2023-03-17 10:42:11 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-03-04 00:25:54 +00:00
|
|
|
if (data.author !== game.currentPlayer().id) {
|
|
|
|
if (data.action === "DEFENSE") {
|
|
|
|
await game.players[data.author].setDefense(data.amount);
|
2023-03-03 17:34:15 +00:00
|
|
|
}
|
|
|
|
|
2023-03-04 00:25:54 +00:00
|
|
|
return;
|
2023-03-03 17:34:15 +00:00
|
|
|
}
|
2023-03-04 00:25:54 +00:00
|
|
|
|
|
|
|
if (game.isPregame()) {
|
|
|
|
if (!Region.allRegionsClaimed()) {
|
|
|
|
// Claim a region in the pregame.
|
|
|
|
if (game.currentPlayer().claim(data)) {
|
|
|
|
// Increment to next player.
|
|
|
|
game.currentPlayer().endTurn();
|
|
|
|
}
|
2023-03-17 10:42:11 +00:00
|
|
|
} else if (!game.allReinforcementsPlaced()) {
|
2023-03-04 00:25:54 +00:00
|
|
|
if (game.currentPlayer().reinforce(data)) {
|
|
|
|
game.currentPlayer().endTurn();
|
|
|
|
}
|
|
|
|
}
|
2023-04-10 10:19:11 +00:00
|
|
|
|
|
|
|
if (game.allReinforcementsPlaced()) {
|
|
|
|
game.incrementState();
|
|
|
|
}
|
2023-03-04 00:25:54 +00:00
|
|
|
} else {
|
|
|
|
if (await game.currentPlayer().act(data)) {
|
|
|
|
game.currentPlayer().endTurn();
|
2023-03-05 17:19:37 +00:00
|
|
|
} else {
|
|
|
|
const event = new CustomEvent("turnProgress");
|
|
|
|
document.dispatchEvent(event);
|
2023-03-04 00:25:54 +00:00
|
|
|
}
|
2023-03-03 17:34:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-05-01 13:42:17 +00:00
|
|
|
document.addEventListener("RESOLVE", async (ev) => {
|
2023-04-30 17:42:52 +00:00
|
|
|
const data = ev.detail;
|
2023-05-01 13:42:17 +00:00
|
|
|
if (game.contendedRegion !== null) {
|
|
|
|
await game.contendedRegion.handleResolve(data);
|
|
|
|
}
|
2023-04-30 17:42:52 +00:00
|
|
|
});
|
|
|
|
|
2023-03-24 12:41:54 +00:00
|
|
|
// todo has to filter by player
|
|
|
|
document.addEventListener("PROOF", async (ev) => {
|
|
|
|
const data = ev.detail;
|
2023-04-10 10:19:11 +00:00
|
|
|
if (data.stage === "REQUEST") {
|
|
|
|
let region = Region.getRegion(data.region);
|
|
|
|
|
|
|
|
// todo check if this is a valid request e.g actually has neighbour
|
|
|
|
if (region.owner === game.us) {
|
|
|
|
region.prove();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-24 12:41:54 +00:00
|
|
|
if (data.stage === "CONJECTURE") {
|
|
|
|
let region = Region.getRegion(data.region);
|
|
|
|
|
2023-03-24 16:53:02 +00:00
|
|
|
region.verify(BigInt(data.plainText), BigInt(data.a));
|
2023-03-24 12:41:54 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-03-18 15:41:37 +00:00
|
|
|
document.addEventListener("endTurn", () => {
|
|
|
|
if (game.isPregame() && game.allReinforcementsPlaced()) {
|
|
|
|
game.incrementState();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-03-04 00:25:54 +00:00
|
|
|
document.addEventListener("gameStateUpdate", async () => {
|
|
|
|
if (game.isPregame()) {
|
|
|
|
let firstPlayerIndex = await random.get(
|
|
|
|
Object.keys(game.players).length,
|
|
|
|
"first-player"
|
|
|
|
);
|
2023-03-03 17:34:15 +00:00
|
|
|
|
2023-03-04 00:25:54 +00:00
|
|
|
let firstPlayer = Object.values(game.players).sort((a, b) =>
|
|
|
|
a.id < b.id ? -1 : 1
|
|
|
|
)[firstPlayerIndex];
|
2023-03-03 17:34:15 +00:00
|
|
|
|
2023-03-04 00:25:54 +00:00
|
|
|
firstPlayer.isPlaying = true;
|
|
|
|
await barrier.wait();
|
2023-03-05 17:19:37 +00:00
|
|
|
|
|
|
|
const event = new CustomEvent("playerChange");
|
|
|
|
document.dispatchEvent(event);
|
2023-03-04 00:25:54 +00:00
|
|
|
}
|
|
|
|
});
|