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 { 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),
};
}
class PrivKey {
static fromJSON(data) {
return new PaillierPubKey(BigInt(data.n));
}
}
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 };
}

View File

@ -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);

View File

@ -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");

View File

@ -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];
}

View File

@ -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));
}
});

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 {
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 "?";
}
}
}

View File

@ -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"),

View File

@ -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,19 +92,22 @@ 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;
region.reinforce(BigInt(data.regions[regionName]));
}
}
return true;
}
/**
* Process a generic action packet representing a player's move.
*

View File

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