diff --git a/static/js/modules/crypto/paillier.js b/static/js/modules/crypto/paillier.js index 52aafc4..1a9376d 100644 --- a/static/js/modules/crypto/paillier.js +++ b/static/js/modules/crypto/paillier.js @@ -1,7 +1,7 @@ import { cryptoRandom, generate_prime } from "./random_primes.js"; import { mod_exp } from "./math.js"; -class Cyphertext { +class Ciphertext { constructor(key, plainText, r) { if (r === undefined) { r = cryptoRandom(4096); @@ -46,15 +46,15 @@ class Cyphertext { } prove() { - return new ProofSessionProver(this); + return new ZeroProofSessionProver(this); } asReadOnlyCyphertext() { - return new ReadOnlyCyphertext(this.pubKey, this.cyphertext); + return new ReadOnlyCiphertext(this.pubKey, this.cyphertext); } } -class ProofSessionProver { +class ZeroProofSessionProver { constructor(cipherText) { this.cipherText = cipherText; @@ -87,9 +87,9 @@ class ProofSessionProver { } } -window.Cyphertext = Cyphertext; +window.Cyphertext = Ciphertext; -export class ReadOnlyCyphertext { +export class ReadOnlyCiphertext { constructor(key, cyphertext) { this.cyphertext = cyphertext; this.pubKey = key; @@ -107,15 +107,15 @@ export class ReadOnlyCyphertext { } prove(plainText, a) { - return new ProofSessionVerifier(this, plainText, a); + return new ZeroProofSessionVerifier(this, plainText, a); } clone() { - return new ReadOnlyCyphertext(this.pubKey, this.cyphertext); + return new ReadOnlyCiphertext(this.pubKey, this.cyphertext); } } -class ProofSessionVerifier { +class ZeroProofSessionVerifier { constructor(cipherText, plainText, a) { // Clone, otherwise the update below will mutate the original value this.cipherText = cipherText.clone(); @@ -147,7 +147,7 @@ class ProofSessionVerifier { } } -window.ReadOnlyCyphertext = ReadOnlyCyphertext; +window.ReadOnlyCyphertext = ReadOnlyCiphertext; export class PaillierPubKey { constructor(n) { @@ -157,7 +157,7 @@ export class PaillierPubKey { } encrypt(m, r) { - return new Cyphertext(this, m, r); + return new Ciphertext(this, m, r); } toJSON() { diff --git a/static/js/modules/interface/dom.js b/static/js/modules/interface/dom.js index 33384b7..df2342f 100644 --- a/static/js/modules/interface/dom.js +++ b/static/js/modules/interface/dom.js @@ -148,6 +148,14 @@ document.addEventListener("DOMContentLoaded", () => { el.addEventListener("click", (ev) => { let region = ev.target.closest(".node").dataset.name; game.us.sendReinforce(region); + + if (game.isPregame()) { + game.us.endTurn(); + + if (game.allReinforcementsPlaced()) { + game.incrementState(); + } + } }) ); diff --git a/static/js/modules/interface/main.js b/static/js/modules/interface/main.js index 96aa15c..97052c9 100644 --- a/static/js/modules/interface/main.js +++ b/static/js/modules/interface/main.js @@ -125,7 +125,7 @@ document.addEventListener("ACT", async (ev) => { game.setReady(data.author, data.ready); } else { // Throw out our own packets - if (data.author === game.us) { + if (data.author === game.us.id) { return; } @@ -149,6 +149,10 @@ document.addEventListener("ACT", async (ev) => { game.currentPlayer().endTurn(); } } + + if (game.allReinforcementsPlaced()) { + game.incrementState(); + } } else { if (await game.currentPlayer().act(data)) { game.currentPlayer().endTurn(); @@ -163,8 +167,16 @@ document.addEventListener("ACT", async (ev) => { // 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") { - // find the relevant entity let region = Region.getRegion(data.region); region.verify(BigInt(data.plainText), BigInt(data.a)); diff --git a/static/js/modules/interface/map.js b/static/js/modules/interface/map.js index a94a2cf..69e8b4b 100644 --- a/static/js/modules/interface/map.js +++ b/static/js/modules/interface/map.js @@ -157,6 +157,10 @@ export class Region { this.strength.prove(this.name); } + requestProof() { + socket.emit("message", Packet.createProofRequest(this.name)); + } + verify(plainText, a) { this.strength.verify(this.name, plainText, a); } diff --git a/static/js/modules/interface/packet.js b/static/js/modules/interface/packet.js index 312769f..fbc9cee 100644 --- a/static/js/modules/interface/packet.js +++ b/static/js/modules/interface/packet.js @@ -117,6 +117,14 @@ export class Packet { }); } + static createProofRequest(region) { + return this._sign({ + ...this._createBase("PROOF"), + stage: "REQUEST", + region: region, + }); + } + static createProofConjecture(region, plainText, a) { return this._sign({ ...this._createBase("PROOF"), diff --git a/static/js/modules/interface/player.js b/static/js/modules/interface/player.js index e86ed22..65d2e23 100644 --- a/static/js/modules/interface/player.js +++ b/static/js/modules/interface/player.js @@ -1,7 +1,7 @@ import { Packet } from "./packet.js"; import { socket, game, random } from "./main.js"; import { RsaPubKey } from "../crypto/rsa.js"; -import { PaillierPubKey, ReadOnlyCyphertext } from "../crypto/paillier.js"; +import { PaillierPubKey, ReadOnlyCiphertext } from "../crypto/paillier.js"; import { Region } from "./map.js"; import { showDefenseDom } from "./dom.js"; @@ -88,7 +88,7 @@ export class Player { if (region.owner === null) { region.claim( this, - new ReadOnlyCyphertext(this.paillierPubKey, BigInt(data.cipherText)) + new ReadOnlyCiphertext(this.paillierPubKey, BigInt(data.cipherText)) ); this.totalStrength += 1; @@ -112,7 +112,7 @@ export class Player { if (region.owner === this) { region.reinforce( - new ReadOnlyCyphertext( + new ReadOnlyCiphertext( this.paillierPubKey, BigInt(data.regions[regionName]) ) @@ -122,6 +122,13 @@ export class Player { this.totalStrength += 1; + // request proofs + for (let region of this.getRegions()) { + if ([...region.neighbours.values()].find((r) => r.owner === game.us)) { + region.requestProof(); + } + } + return true; } @@ -154,15 +161,13 @@ export class Player { this.totalStrength += 1; - // send proofs - for (let region of this.getRegions()) { - // eh - if ([...region.neighbours.values()].find((r) => r.owner !== this)) { - region.prove(); + if (game.isPlaying()) { + this.reinforcementsPlaced += 1; + + if (this.reinforcementsPlaced === this.reinforcementsAvailable) { + this.turnPhase = PHASE_ATTACK; } } - - this.endTurn(); } /** @@ -173,7 +178,7 @@ export class Player { */ async act(data) { if (this.turnPhase === PHASE_REINFORCE) { - if (data.region !== undefined) { + if (data.regions !== undefined) { if (this.reinforce(data)) { this.reinforcementsPlaced += 1; } diff --git a/whitepaper/Dissertation.pdf b/whitepaper/Dissertation.pdf index 4b70538..aeab9e0 100644 Binary files a/whitepaper/Dissertation.pdf and b/whitepaper/Dissertation.pdf differ diff --git a/whitepaper/Dissertation.tex b/whitepaper/Dissertation.tex index f1e0a64..ff30086 100644 --- a/whitepaper/Dissertation.tex +++ b/whitepaper/Dissertation.tex @@ -510,7 +510,7 @@ Various parts of the implementation use the random oracle model: in particular, Paillier ciphertexts are constant size, each $\sim$1.0kB in size (as they are taken modulo $n^2$, where $n$ is the product of two 2048 bit primes). This is small enough for the memory and network limitations of today. -The proof of zero uses two Paillier ciphertexts, a challenge of size 2048 bits, and a proof statement of size 4096 bits. In total, this is $\sim$2.8kB. These are constant size, and since they run in a single round, take constant time. +The proof of zero uses two Paillier ciphertexts, a challenge of size 2048 bits, and a proof statement of size 4096 bits. In total, this is a constant size of $\sim$2.8kB. On the other hand, \hyperref[protocol1]{Protocol~\ref*{protocol1}} requires multiple rounds. Assume that we use 42 rounds: this provides an acceptable level of soundness, with a cheat probability of $\left(\frac{1}{2}\right)^{-42} \approx 2.3 \times 10^{-13}$. Additionally, assume that there are 10 regions to verify. Each round then requires ten Paillier ciphertexts alongside ten proofs of zero. This results in a proof size of $\sim$1.7MB. Whilst this is still within current memory limitations, the network cost is extreme; and this value may exceed what can be reasonably operated on within a processor's cache. @@ -520,6 +520,8 @@ This is all in an ideal situation without compression or signatures: in the impl The size of the proof of zero communication is, in total, $3290 + 1744 + 2243$ characters, i.e $\sim$7.3kB. This is about 2-3 times larger than the ideal size. A solution to this is to use a more compact format, for example msgpack \cite{msgpack} (which also has native support for binary literals). +This only considers the network footprint. The other consideration is the memory footprint. The proof of zero requires auxiliary memory beyond the new values communicated. In particular, it must clone the ciphertext being proven, in order to prevent mutating the original ciphertext when multiplying by $g^{-m}$. + \subsubsection{Time complexity} It is remarked that Paillier encryption performs considerably slower than RSA on all key sizes. \cite{paillier1999public} provides a table of theoretic results, suggesting that Paillier encryption can be over 1,000 times slower than RSA for the same key size.