...
This commit is contained in:
parent
4a55f5c11f
commit
a6961e1900
@ -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 };
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
|
@ -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];
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -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 "?";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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"),
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user