import { generate_keypair, generate_rsa_keypair } from "../crypto/main.js"; 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"; import "./proofs.js"; export const ID = window.crypto.randomUUID(); export const game = new Game(); window.game = game; export let socket; export let random; let barrier; window.paillier = generate_keypair(); window.rsa = generate_rsa_keypair(); // Not totally reliable but better than nothing. window.addEventListener("beforeunload", () => { socket.emit("message", Packet.createDisconnect()); }); document.addEventListener("DOMContentLoaded", () => { socket = io(); random = new Random(); barrier = new Barrier(); socket.on("connect", () => { window.console.log("Connected!"); window.console.log(`We are: ${ID}`); socket.emit("message", Packet.createAnnounce(window.rsa, window.paillier)); game.addPlayer(ID, true, window.rsa.pubKey, window.paillier.pubKey); }); socket.on("message", async (packet) => { // window.console.log(`Received size: ${JSON.stringify(packet).length}`); let data = packet.payload; if (data.type !== "KEEPALIVE") window.console.log(data); 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 let dehash = sender.rsaPubKey.encrypt(sig); let hasher = new jsSHA("SHA3-256", "TEXT"); hasher.update(JSON.stringify(data)); let hash = BigInt("0x" + hasher.getHash("HEX")); if (dehash !== hash) { window.console.error(`Signature invalid! Ignoring packet ${data.id}.`); return; } // 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; } 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 */ document.addEventListener("ANNOUNCE", (ev) => { const data = ev.detail; if (data.author === ID) return; let is_new = game.addPlayer(data.author, false, data.rsaPubKey, data.paillierPubKey); // When a new player is seen, all announce to ensure they know all players. if (is_new) { socket.emit("message", Packet.createAnnounce(window.rsa, window.paillier)); } }); document.addEventListener("DISCONNECT", (ev) => { const data = ev.detail; game.removePlayer(data.author); }); /** * Process keep-alive packets: these are packets that check players are still online. * * @param data Packet received */ document.addEventListener("KEEPALIVE", (ev) => { const data = ev.detail; game.keepAlive(data.author); }); document.addEventListener("ACT", async (ev) => { const data = ev.detail; if (game.isWaiting()) { game.setReady(data.author, data.ready); } else { // Throw out our own packets if (!game.isPlaying() && data.author === game.us.id) { return; } if (data.author !== game.currentPlayer().id) { if (data.action === "DEFENSE") { await game.players[data.author].setDefense(data.amount); } return; } if (game.isPregame()) { if (!Region.allRegionsClaimed()) { // Claim a region in the pregame. if (game.currentPlayer().claim(data)) { // Increment to next player. game.currentPlayer().endTurn(); } } else if (!game.allReinforcementsPlaced()) { if (game.currentPlayer().reinforce(data)) { game.currentPlayer().endTurn(); } } if (game.allReinforcementsPlaced()) { game.incrementState(); } } else { if (await game.currentPlayer().act(data)) { game.currentPlayer().endTurn(); } else { const event = new CustomEvent("turnProgress"); document.dispatchEvent(event); } } } }); document.addEventListener("RESOLVE", async (ev) => { const data = ev.detail; if (game.contendedRegion !== null) { await game.contendedRegion.handleResolve(data); } }); // todo has to filter by player document.addEventListener("PROOF", async (ev) => { const data = ev.detail; 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(); } } if (data.stage === "CONJECTURE") { let region = Region.getRegion(data.region); region.verify(BigInt(data.plainText), BigInt(data.a)); } }); document.addEventListener("endTurn", () => { if (game.isPregame() && game.allReinforcementsPlaced()) { game.incrementState(); } }); document.addEventListener("gameStateUpdate", async () => { if (game.isPregame()) { let firstPlayerIndex = await random.get( Object.keys(game.players).length, "first-player" ); let firstPlayer = Object.values(game.players).sort((a, b) => a.id < b.id ? -1 : 1 )[firstPlayerIndex]; firstPlayer.isPlaying = true; await barrier.wait(); const event = new CustomEvent("playerChange"); document.dispatchEvent(event); } });