From a6961e1900ab4573b926aa4aad0fdde6ad5d22b7 Mon Sep 17 00:00:00 2001 From: jude Date: Mon, 13 Mar 2023 14:52:14 +0000 Subject: [PATCH] ... --- static/js/modules/crypto/paillier.js | 26 ++++++++++----- static/js/modules/crypto/rsa.js | 4 +-- static/js/modules/interface/dom.js | 26 +++++++++++++-- static/js/modules/interface/game.js | 4 +-- static/js/modules/interface/main.js | 11 +++--- static/js/modules/interface/map.js | 48 ++++++++++++++++++++++++--- static/js/modules/interface/packet.js | 24 ++++++++++++-- static/js/modules/interface/player.js | 28 +++++++++++----- templates/index.html | 10 ++++++ 9 files changed, 146 insertions(+), 35 deletions(-) diff --git a/static/js/modules/crypto/paillier.js b/static/js/modules/crypto/paillier.js index 4d0e12e..41ab72d 100644 --- a/static/js/modules/crypto/paillier.js +++ b/static/js/modules/crypto/paillier.js @@ -1,11 +1,9 @@ import { random2048, generate_prime } from "./random_primes.js"; import { mod_exp } from "./math.js"; -let p, q, pubKey, privKey; - -class PubKey { - constructor(p, q) { - this.n = p * q; +export class PaillierPubKey { + constructor(n) { + this.n = n; // this.g = this.n + 1n; } @@ -23,9 +21,19 @@ class PubKey { // Compute g^m r^n from crt return (gm * mod_exp(r, this.n, this.n ** 2n)) % this.n ** 2n; } + + toJSON() { + return { + n: "0x" + this.n.toString(16), + }; + } + + static fromJSON(data) { + return new PaillierPubKey(BigInt(data.n)); + } } -class PrivKey { +class PaillierPrivKey { constructor(p, q) { this.n = p * q; this.lambda = (p - 1n) * (q - 1n); @@ -40,6 +48,8 @@ class PrivKey { } export function generate_keypair() { + let p, q, pubKey, privKey; + if (window.sessionStorage.getItem("p") === null) { p = generate_prime(); window.sessionStorage.setItem("p", p); @@ -54,8 +64,8 @@ export function generate_keypair() { q = BigInt(window.sessionStorage.getItem("q")); } - pubKey = new PubKey(p, q); - privKey = new PrivKey(p, q); + pubKey = new PaillierPubKey(p * q); + privKey = new PaillierPrivKey(p, q); return { pubKey, privKey }; } diff --git a/static/js/modules/crypto/rsa.js b/static/js/modules/crypto/rsa.js index 1094dae..f58bdf1 100644 --- a/static/js/modules/crypto/rsa.js +++ b/static/js/modules/crypto/rsa.js @@ -1,8 +1,6 @@ import { generate_prime } from "./random_primes.js"; import { mod_exp, mod_inv } from "./math.js"; -let p, q, pubKey, privKey; - export class RsaPubKey { constructor(n, e) { this.n = n; @@ -37,6 +35,8 @@ class RsaPrivKey { } export function generate_rsa_keypair() { + let p, q, pubKey, privKey; + if (window.sessionStorage.getItem("rsa_p") === null) { p = generate_prime(); window.sessionStorage.setItem("rsa_p", p); diff --git a/static/js/modules/interface/dom.js b/static/js/modules/interface/dom.js index 457c95e..74e6899 100644 --- a/static/js/modules/interface/dom.js +++ b/static/js/modules/interface/dom.js @@ -31,8 +31,21 @@ function updateMapDom() { document.querySelectorAll(".fortify, .attack").forEach((e) => { e.classList.add("hidden"); }); + + if (Region.allRegionsClaimed()) { + document.querySelectorAll(".claim").forEach((e) => { + e.classList.add("hidden"); + }); + document.querySelectorAll(".reinforce").forEach((e) => { + e.classList.remove("hidden"); + }); + } else { + document.querySelectorAll(".reinforce").forEach((e) => { + e.classList.add("hidden"); + }); + } } else if (game.isPlaying()) { - document.querySelectorAll(".node button").forEach((e) => { + document.querySelectorAll(".node button:not(.claim)").forEach((e) => { e.classList.remove("hidden"); }); @@ -45,7 +58,7 @@ function updateMapDom() { for (let region of Region.getAllRegions()) { const element = document.querySelector(`.node[data-name=${region.name}]`); - element.querySelector(".strength").textContent = region.strength || ""; + element.querySelector(".strength").textContent = region.displayStrength(); element.style.backgroundColor = region.owner === null ? "white" : region.owner.getColor(); } @@ -119,13 +132,20 @@ document.addEventListener("DOMContentLoaded", () => { socket.emit("message", Packet.createEndTurn()); }); - document.querySelectorAll(".reinforce").forEach((el) => + document.querySelectorAll(".claim").forEach((el) => el.addEventListener("click", (ev) => { let region = ev.target.closest(".node").dataset.name; socket.emit("message", Packet.createRegionClaim(region)); }) ); + document.querySelectorAll(".reinforce").forEach((el) => + el.addEventListener("click", (ev) => { + let region = ev.target.closest(".node").dataset.name; + socket.emit("message", Packet.createReinforce(region)); + }) + ); + document.querySelectorAll(".attack").forEach((el) => el.addEventListener("click", (ev) => { let modal = document.querySelector("#modal"); diff --git a/static/js/modules/interface/game.js b/static/js/modules/interface/game.js index 5791874..b4a9b91 100644 --- a/static/js/modules/interface/game.js +++ b/static/js/modules/interface/game.js @@ -40,11 +40,11 @@ export class Game { return Object.values(this.players).filter((p) => p.isPlaying)[0]; } - addPlayer(id, is_us, pubkey) { + addPlayer(id, is_us, rsa_key, paillier_key) { let is_new = this.players[id] === undefined; if (this.isWaiting()) { - this.players[id] = new Player(id, is_us, pubkey); + this.players[id] = new Player(id, is_us, rsa_key, paillier_key); if (is_us) { this.us = this.players[id]; } diff --git a/static/js/modules/interface/main.js b/static/js/modules/interface/main.js index 6369ae9..008342f 100644 --- a/static/js/modules/interface/main.js +++ b/static/js/modules/interface/main.js @@ -8,6 +8,7 @@ import "./dom.js"; export const ID = window.crypto.randomUUID(); export const game = new Game(); +window.game = game; export let socket; export let random; let barrier; @@ -27,13 +28,13 @@ document.addEventListener("DOMContentLoaded", () => { socket.on("connect", () => { window.console.log("Connected!"); window.console.log(`We are: ${ID}`); - socket.emit("message", Packet.createAnnounce(window.rsa)); - game.addPlayer(ID, true, window.rsa.pubKey); + socket.emit("message", Packet.createAnnounce(window.rsa, window.paillier)); + game.addPlayer(ID, true, window.rsa.pubKey, window.paillier.pubKey); }); socket.on("message", async (packet) => { let data = packet.payload; - window.console.log(data); + if (data.type !== "KEEPALIVE") window.console.log(data); const sender = game.players[data.author]; if (sender === undefined) { @@ -91,11 +92,11 @@ document.addEventListener("ANNOUNCE", (ev) => { const data = ev.detail; if (data.author === ID) return; - let is_new = game.addPlayer(data.author, false, data.rsaPubKey); + 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)); + socket.emit("message", Packet.createAnnounce(window.rsa, window.paillier)); } }); diff --git a/static/js/modules/interface/map.js b/static/js/modules/interface/map.js index dfafd01..f079b5d 100644 --- a/static/js/modules/interface/map.js +++ b/static/js/modules/interface/map.js @@ -14,11 +14,28 @@ class Continent { } } +class Strength { + constructor(cyphertext) { + this.cyphertext = cyphertext; + this.assumedStrength = null; + } + + update(amount) { + if (this.cyphertext === null) { + this.cyphertext = amount; + } else { + this.cyphertext *= amount; + } + + this.assumedStrength = null; + } +} + export class Region { constructor(name, continent) { this.name = name; this.owner = null; - this.strength = 0; + this.strength = new Strength(null); this.neighbours = new Set(); this.continent = continent; @@ -36,13 +53,17 @@ export class Region { ); } + // todo fix static reinforcementsRemaining() { if (allPlaced) { return 0; } else { let totalStrength = Object.values(REGIONS) .filter((region) => region.owner === game.us) - .reduce((counter, region) => counter + region.strength, 0); + .reduce( + (counter, region) => counter + region.strength.assumedStrength, + 0 + ); return _REINFORCEMENT_MULTIPLIER * (10 - game.playerCount()) - totalStrength; } @@ -53,7 +74,7 @@ export class Region { return true; } else { let totalStrength = Object.values(REGIONS).reduce( - (counter, region) => counter + region.strength, + (counter, region) => counter + region.strength.assumedStrength, 0 ); @@ -76,11 +97,28 @@ export class Region { claim(player) { this.owner = player; - this.strength = 1; + this.strength.update(player.paillierPubKey.encrypt(1n)); + this.strength.assumedStrength = 1; } reinforce(amount) { - this.strength += amount; + this.strength.update(amount); + } + + isClaimed() { + return this.strength.cyphertext !== null; + } + + displayStrength() { + if (this.owner === null) { + return ""; + } else if (this.owner === game.us) { + return window.paillier.privKey.decrypt(this.strength.cyphertext).toString(); + } else if (this.strength.assumedStrength !== null) { + return `${this.strength.assumedStrength}`; + } else { + return "?"; + } } } diff --git a/static/js/modules/interface/packet.js b/static/js/modules/interface/packet.js index 2e37621..f06886c 100644 --- a/static/js/modules/interface/packet.js +++ b/static/js/modules/interface/packet.js @@ -1,4 +1,4 @@ -import { ID } from "./main.js"; +import { ID, game } from "./main.js"; export class Packet { static _createBase(name) { @@ -30,10 +30,11 @@ export class Packet { }; } - static createAnnounce(rsaKeyPair) { + static createAnnounce(rsaKeyPair, paillierKeyPair) { return this._sign({ ...this._createBase("ANNOUNCE"), rsaPubKey: rsaKeyPair.pubKey.toJSON(), + paillierPubKey: paillierKeyPair.pubKey.toJSON(), }); } @@ -83,6 +84,25 @@ export class Packet { }); } + static createReinforce(region) { + // todo cache some of these, possibly by pregenerating cyphertexts in a web worker + let regions = {}; + for (let ourRegion of game.us.getRegions()) { + if (ourRegion.name !== region) { + regions[ourRegion.name] = + "0x" + game.us.paillierPubKey.encrypt(0n).toString(16); + } else { + regions[ourRegion.name] = + "0x" + game.us.paillierPubKey.encrypt(1n).toString(16); + } + } + + return this._sign({ + ...this._createBase("ACT"), + regions: regions, + }); + } + static createAction(action, startRegion, endRegion, amount) { return this._sign({ ...this._createBase("ACT"), diff --git a/static/js/modules/interface/player.js b/static/js/modules/interface/player.js index c19c233..6ce97c3 100644 --- a/static/js/modules/interface/player.js +++ b/static/js/modules/interface/player.js @@ -1,6 +1,7 @@ import { Packet } from "./packet.js"; import { socket, game, random } from "./main.js"; import { RsaPubKey } from "../crypto/rsa.js"; +import { PaillierPubKey } from "../crypto/paillier.js"; import { Region } from "./map.js"; import { showDefenseDom } from "./dom.js"; @@ -14,11 +15,12 @@ const PHASE_FORTIFY = 3; let totalDice = 0; export class Player { - constructor(id, local, pubkey) { + constructor(id, local, rsa_key, paillier_key) { this.timeout = null; this.id = id; this.ready = false; - this.rsaPubKey = RsaPubKey.fromJSON(pubkey); + this.rsaPubKey = RsaPubKey.fromJSON(rsa_key); + this.paillierPubKey = PaillierPubKey.fromJSON(paillier_key); this.lastPacket = 0; // Data which is reset every turn @@ -64,6 +66,13 @@ export class Player { this.color = `hsl(${randomColor} 57% 50%)`; } + /** + * Get all regions controlled by this player. + */ + getRegions() { + return Region.getAllRegions().filter((r) => r.owner === this); + } + /** * Claim a region of the map. * @@ -83,17 +92,20 @@ export class Player { /** * Reinforce a region of the map. * + * Todo. Check object before committing changes. + * * @param data Data received via socket. */ reinforce(data) { - let region = Region.getRegion(data.region); + for (let regionName of Object.keys(data.regions)) { + let region = Region.getRegion(regionName); - if (region.owner === this) { - region.reinforce(1); - return true; - } else { - return false; + if (region.owner === this) { + region.reinforce(BigInt(data.regions[regionName])); + } } + + return true; } /** diff --git a/templates/index.html b/templates/index.html index 80a6d9b..99d78c8 100644 --- a/templates/index.html +++ b/templates/index.html @@ -79,6 +79,7 @@
A
+ @@ -88,6 +89,7 @@
B
+ @@ -97,6 +99,7 @@
C
+ @@ -106,6 +109,7 @@
D
+ @@ -115,6 +119,7 @@
E
+ @@ -124,6 +129,7 @@
F
+ @@ -133,6 +139,7 @@
G
+ @@ -142,6 +149,7 @@
H
+ @@ -151,6 +159,7 @@
I
+ @@ -160,6 +169,7 @@
J
+