This commit is contained in:
jude 2023-03-13 14:52:14 +00:00
parent 4a55f5c11f
commit a6961e1900
9 changed files with 146 additions and 35 deletions

View File

@ -1,11 +1,9 @@
import { random2048, generate_prime } from "./random_primes.js"; import { random2048, generate_prime } from "./random_primes.js";
import { mod_exp } from "./math.js"; import { mod_exp } from "./math.js";
let p, q, pubKey, privKey; export class PaillierPubKey {
constructor(n) {
class PubKey { this.n = n;
constructor(p, q) {
this.n = p * q;
// this.g = this.n + 1n; // this.g = this.n + 1n;
} }
@ -23,9 +21,19 @@ class PubKey {
// Compute g^m r^n from crt // Compute g^m r^n from crt
return (gm * mod_exp(r, this.n, this.n ** 2n)) % this.n ** 2n; 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) { constructor(p, q) {
this.n = p * q; this.n = p * q;
this.lambda = (p - 1n) * (q - 1n); this.lambda = (p - 1n) * (q - 1n);
@ -40,6 +48,8 @@ class PrivKey {
} }
export function generate_keypair() { export function generate_keypair() {
let p, q, pubKey, privKey;
if (window.sessionStorage.getItem("p") === null) { if (window.sessionStorage.getItem("p") === null) {
p = generate_prime(); p = generate_prime();
window.sessionStorage.setItem("p", p); window.sessionStorage.setItem("p", p);
@ -54,8 +64,8 @@ export function generate_keypair() {
q = BigInt(window.sessionStorage.getItem("q")); q = BigInt(window.sessionStorage.getItem("q"));
} }
pubKey = new PubKey(p, q); pubKey = new PaillierPubKey(p * q);
privKey = new PrivKey(p, q); privKey = new PaillierPrivKey(p, q);
return { pubKey, privKey }; return { pubKey, privKey };
} }

View File

@ -1,8 +1,6 @@
import { generate_prime } from "./random_primes.js"; import { generate_prime } from "./random_primes.js";
import { mod_exp, mod_inv } from "./math.js"; import { mod_exp, mod_inv } from "./math.js";
let p, q, pubKey, privKey;
export class RsaPubKey { export class RsaPubKey {
constructor(n, e) { constructor(n, e) {
this.n = n; this.n = n;
@ -37,6 +35,8 @@ class RsaPrivKey {
} }
export function generate_rsa_keypair() { export function generate_rsa_keypair() {
let p, q, pubKey, privKey;
if (window.sessionStorage.getItem("rsa_p") === null) { if (window.sessionStorage.getItem("rsa_p") === null) {
p = generate_prime(); p = generate_prime();
window.sessionStorage.setItem("rsa_p", p); window.sessionStorage.setItem("rsa_p", p);

View File

@ -31,8 +31,21 @@ function updateMapDom() {
document.querySelectorAll(".fortify, .attack").forEach((e) => { document.querySelectorAll(".fortify, .attack").forEach((e) => {
e.classList.add("hidden"); 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()) { } else if (game.isPlaying()) {
document.querySelectorAll(".node button").forEach((e) => { document.querySelectorAll(".node button:not(.claim)").forEach((e) => {
e.classList.remove("hidden"); e.classList.remove("hidden");
}); });
@ -45,7 +58,7 @@ function updateMapDom() {
for (let region of Region.getAllRegions()) { for (let region of Region.getAllRegions()) {
const element = document.querySelector(`.node[data-name=${region.name}]`); const element = document.querySelector(`.node[data-name=${region.name}]`);
element.querySelector(".strength").textContent = region.strength || ""; element.querySelector(".strength").textContent = region.displayStrength();
element.style.backgroundColor = element.style.backgroundColor =
region.owner === null ? "white" : region.owner.getColor(); region.owner === null ? "white" : region.owner.getColor();
} }
@ -119,13 +132,20 @@ document.addEventListener("DOMContentLoaded", () => {
socket.emit("message", Packet.createEndTurn()); socket.emit("message", Packet.createEndTurn());
}); });
document.querySelectorAll(".reinforce").forEach((el) => document.querySelectorAll(".claim").forEach((el) =>
el.addEventListener("click", (ev) => { el.addEventListener("click", (ev) => {
let region = ev.target.closest(".node").dataset.name; let region = ev.target.closest(".node").dataset.name;
socket.emit("message", Packet.createRegionClaim(region)); 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) => document.querySelectorAll(".attack").forEach((el) =>
el.addEventListener("click", (ev) => { el.addEventListener("click", (ev) => {
let modal = document.querySelector("#modal"); let modal = document.querySelector("#modal");

View File

@ -40,11 +40,11 @@ export class Game {
return Object.values(this.players).filter((p) => p.isPlaying)[0]; 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; let is_new = this.players[id] === undefined;
if (this.isWaiting()) { 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) { if (is_us) {
this.us = this.players[id]; this.us = this.players[id];
} }

View File

@ -8,6 +8,7 @@ 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();
window.game = game;
export let socket; export let socket;
export let random; export let random;
let barrier; let barrier;
@ -27,13 +28,13 @@ document.addEventListener("DOMContentLoaded", () => {
socket.on("connect", () => { socket.on("connect", () => {
window.console.log("Connected!"); window.console.log("Connected!");
window.console.log(`We are: ${ID}`); window.console.log(`We are: ${ID}`);
socket.emit("message", Packet.createAnnounce(window.rsa)); socket.emit("message", Packet.createAnnounce(window.rsa, window.paillier));
game.addPlayer(ID, true, window.rsa.pubKey); game.addPlayer(ID, true, window.rsa.pubKey, window.paillier.pubKey);
}); });
socket.on("message", async (packet) => { socket.on("message", async (packet) => {
let data = packet.payload; let data = packet.payload;
window.console.log(data); if (data.type !== "KEEPALIVE") window.console.log(data);
const sender = game.players[data.author]; const sender = game.players[data.author];
if (sender === undefined) { if (sender === undefined) {
@ -91,11 +92,11 @@ document.addEventListener("ANNOUNCE", (ev) => {
const data = ev.detail; const data = ev.detail;
if (data.author === ID) return; 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. // When a new player is seen, all announce to ensure they know all players.
if (is_new) { if (is_new) {
socket.emit("message", Packet.createAnnounce(window.rsa)); socket.emit("message", Packet.createAnnounce(window.rsa, window.paillier));
} }
}); });

View File

@ -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 { export class Region {
constructor(name, continent) { constructor(name, continent) {
this.name = name; this.name = name;
this.owner = null; this.owner = null;
this.strength = 0; this.strength = new Strength(null);
this.neighbours = new Set(); this.neighbours = new Set();
this.continent = continent; this.continent = continent;
@ -36,13 +53,17 @@ export class Region {
); );
} }
// todo fix
static reinforcementsRemaining() { static reinforcementsRemaining() {
if (allPlaced) { if (allPlaced) {
return 0; return 0;
} else { } else {
let totalStrength = Object.values(REGIONS) let totalStrength = Object.values(REGIONS)
.filter((region) => region.owner === game.us) .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; return _REINFORCEMENT_MULTIPLIER * (10 - game.playerCount()) - totalStrength;
} }
@ -53,7 +74,7 @@ export class Region {
return true; return true;
} else { } else {
let totalStrength = Object.values(REGIONS).reduce( let totalStrength = Object.values(REGIONS).reduce(
(counter, region) => counter + region.strength, (counter, region) => counter + region.strength.assumedStrength,
0 0
); );
@ -76,11 +97,28 @@ export class Region {
claim(player) { claim(player) {
this.owner = player; this.owner = player;
this.strength = 1; this.strength.update(player.paillierPubKey.encrypt(1n));
this.strength.assumedStrength = 1;
} }
reinforce(amount) { 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 "?";
}
} }
} }

View File

@ -1,4 +1,4 @@
import { ID } from "./main.js"; import { ID, game } from "./main.js";
export class Packet { export class Packet {
static _createBase(name) { static _createBase(name) {
@ -30,10 +30,11 @@ export class Packet {
}; };
} }
static createAnnounce(rsaKeyPair) { static createAnnounce(rsaKeyPair, paillierKeyPair) {
return this._sign({ return this._sign({
...this._createBase("ANNOUNCE"), ...this._createBase("ANNOUNCE"),
rsaPubKey: rsaKeyPair.pubKey.toJSON(), 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) { static createAction(action, startRegion, endRegion, amount) {
return this._sign({ return this._sign({
...this._createBase("ACT"), ...this._createBase("ACT"),

View File

@ -1,6 +1,7 @@
import { Packet } from "./packet.js"; import { Packet } from "./packet.js";
import { socket, game, random } from "./main.js"; import { socket, game, random } from "./main.js";
import { RsaPubKey } from "../crypto/rsa.js"; import { RsaPubKey } from "../crypto/rsa.js";
import { PaillierPubKey } from "../crypto/paillier.js";
import { Region } from "./map.js"; import { Region } from "./map.js";
import { showDefenseDom } from "./dom.js"; import { showDefenseDom } from "./dom.js";
@ -14,11 +15,12 @@ const PHASE_FORTIFY = 3;
let totalDice = 0; let totalDice = 0;
export class Player { export class Player {
constructor(id, local, pubkey) { constructor(id, local, rsa_key, paillier_key) {
this.timeout = null; this.timeout = null;
this.id = id; this.id = id;
this.ready = false; this.ready = false;
this.rsaPubKey = RsaPubKey.fromJSON(pubkey); this.rsaPubKey = RsaPubKey.fromJSON(rsa_key);
this.paillierPubKey = PaillierPubKey.fromJSON(paillier_key);
this.lastPacket = 0; this.lastPacket = 0;
// Data which is reset every turn // Data which is reset every turn
@ -64,6 +66,13 @@ export class Player {
this.color = `hsl(${randomColor} 57% 50%)`; 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. * Claim a region of the map.
* *
@ -83,19 +92,22 @@ export class Player {
/** /**
* Reinforce a region of the map. * Reinforce a region of the map.
* *
* Todo. Check object before committing changes.
*
* @param data Data received via socket. * @param data Data received via socket.
*/ */
reinforce(data) { 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) { if (region.owner === this) {
region.reinforce(1); region.reinforce(BigInt(data.regions[regionName]));
return true;
} else {
return false;
} }
} }
return true;
}
/** /**
* Process a generic action packet representing a player's move. * Process a generic action packet representing a player's move.
* *

View File

@ -79,6 +79,7 @@
<div class="strength"></div> <div class="strength"></div>
<div class="label">A</div> <div class="label">A</div>
<div class="actions"> <div class="actions">
<button class="claim">Claim</button>
<button class="reinforce">Reinf.</button> <button class="reinforce">Reinf.</button>
<button class="attack">Attack</button> <button class="attack">Attack</button>
<button class="fortify">Fortify</button> <button class="fortify">Fortify</button>
@ -88,6 +89,7 @@
<div class="strength"></div> <div class="strength"></div>
<div class="label">B</div> <div class="label">B</div>
<div class="actions"> <div class="actions">
<button class="claim">Claim</button>
<button class="reinforce">Reinf.</button> <button class="reinforce">Reinf.</button>
<button class="attack">Attack</button> <button class="attack">Attack</button>
<button class="fortify">Fortify</button> <button class="fortify">Fortify</button>
@ -97,6 +99,7 @@
<div class="strength"></div> <div class="strength"></div>
<div class="label">C</div> <div class="label">C</div>
<div class="actions"> <div class="actions">
<button class="claim">Claim</button>
<button class="reinforce">Reinf.</button> <button class="reinforce">Reinf.</button>
<button class="attack">Attack</button> <button class="attack">Attack</button>
<button class="fortify">Fortify</button> <button class="fortify">Fortify</button>
@ -106,6 +109,7 @@
<div class="strength"></div> <div class="strength"></div>
<div class="label">D</div> <div class="label">D</div>
<div class="actions"> <div class="actions">
<button class="claim">Claim</button>
<button class="reinforce">Reinf.</button> <button class="reinforce">Reinf.</button>
<button class="attack">Attack</button> <button class="attack">Attack</button>
<button class="fortify">Fortify</button> <button class="fortify">Fortify</button>
@ -115,6 +119,7 @@
<div class="strength"></div> <div class="strength"></div>
<div class="label">E</div> <div class="label">E</div>
<div class="actions"> <div class="actions">
<button class="claim">Claim</button>
<button class="reinforce">Reinf.</button> <button class="reinforce">Reinf.</button>
<button class="attack">Attack</button> <button class="attack">Attack</button>
<button class="fortify">Fortify</button> <button class="fortify">Fortify</button>
@ -124,6 +129,7 @@
<div class="strength"></div> <div class="strength"></div>
<div class="label">F</div> <div class="label">F</div>
<div class="actions"> <div class="actions">
<button class="claim">Claim</button>
<button class="reinforce">Reinf.</button> <button class="reinforce">Reinf.</button>
<button class="attack">Attack</button> <button class="attack">Attack</button>
<button class="fortify">Fortify</button> <button class="fortify">Fortify</button>
@ -133,6 +139,7 @@
<div class="strength"></div> <div class="strength"></div>
<div class="label">G</div> <div class="label">G</div>
<div class="actions"> <div class="actions">
<button class="claim">Claim</button>
<button class="reinforce">Reinf.</button> <button class="reinforce">Reinf.</button>
<button class="attack">Attack</button> <button class="attack">Attack</button>
<button class="fortify">Fortify</button> <button class="fortify">Fortify</button>
@ -142,6 +149,7 @@
<div class="strength"></div> <div class="strength"></div>
<div class="label">H</div> <div class="label">H</div>
<div class="actions"> <div class="actions">
<button class="claim">Claim</button>
<button class="reinforce">Reinf.</button> <button class="reinforce">Reinf.</button>
<button class="attack">Attack</button> <button class="attack">Attack</button>
<button class="fortify">Fortify</button> <button class="fortify">Fortify</button>
@ -151,6 +159,7 @@
<div class="strength"></div> <div class="strength"></div>
<div class="label">I</div> <div class="label">I</div>
<div class="actions"> <div class="actions">
<button class="claim">Claim</button>
<button class="reinforce">Reinf.</button> <button class="reinforce">Reinf.</button>
<button class="attack">Attack</button> <button class="attack">Attack</button>
<button class="fortify">Fortify</button> <button class="fortify">Fortify</button>
@ -160,6 +169,7 @@
<div class="strength"></div> <div class="strength"></div>
<div class="label">J</div> <div class="label">J</div>
<div class="actions"> <div class="actions">
<button class="claim">Claim</button>
<button class="reinforce">Reinf.</button> <button class="reinforce">Reinf.</button>
<button class="attack">Attack</button> <button class="attack">Attack</button>
<button class="fortify">Fortify</button> <button class="fortify">Fortify</button>