diff --git a/static/js/modules/crypto/paillier.js b/static/js/modules/crypto/paillier.js index dca1d08..5b6c4fa 100644 --- a/static/js/modules/crypto/paillier.js +++ b/static/js/modules/crypto/paillier.js @@ -86,7 +86,7 @@ export class ReadOnlyCyphertext { this.cyphertext = (this.cyphertext * c.cyphertext) % this.pubKey.n ** 2n; } - prove(plainText, a) { + prove(tag, plainText, a) { return new ProofSessionVerifier(this, plainText, a); } } diff --git a/static/js/modules/interface/main.js b/static/js/modules/interface/main.js index e6c3891..9149355 100644 --- a/static/js/modules/interface/main.js +++ b/static/js/modules/interface/main.js @@ -157,6 +157,17 @@ document.addEventListener("ACT", async (ev) => { } }); +// todo has to filter by player +document.addEventListener("PROOF", async (ev) => { + const data = ev.detail; + if (data.stage === "CONJECTURE") { + // find the relevant entity + let region = Region.getRegion(data.region); + + region.prove(data.plainText, data.noise()); + } +}); + document.addEventListener("endTurn", () => { if (game.isPregame() && game.allReinforcementsPlaced()) { game.incrementState(); diff --git a/static/js/modules/interface/map.js b/static/js/modules/interface/map.js index 87d7ac7..606be8b 100644 --- a/static/js/modules/interface/map.js +++ b/static/js/modules/interface/map.js @@ -1,3 +1,5 @@ +import { Packet } from "./packet.js"; + const REGIONS = {}; class Continent { @@ -22,6 +24,71 @@ class Strength { this.assumedStrength = null; } + + prove(region) { + if (this.cipherText.readOnly) { + return; + } + + const controller = new AbortController(); + let proofSessionProver = this.cipherText.prove(); + + document.addEventListener( + "PROOF", + (ev) => { + const data = ev.detail; + + if (data.region === region && data.stage === "CHALLENGE") { + let z = proofSessionProver.prove(data.challenge); + + socket.emit("message", Packet.createProof(region, z)); + controller.abort(); + } + }, + { signal: controller.signal } + ); + + socket.emit( + "message", + Packet.createProofConjecture( + region, + this.cipherText.plainText, + proofSessionProver.a + ) + ); + } + + verify(region, plainText, a) { + if (!this.cipherText.readOnly) { + return; + } + + const controller = new AbortController(); + let proofSessionVerifier = this.cipherText.prove(plainText, a); + + document.addEventListener( + "PROOF", + (ev) => { + const data = ev.detail; + + if (data.region === region && data.stage === "PROOF") { + if (proofSessionVerifier.verify(data.z)) { + console.log("verified"); + this.assumedStrength = plainText; + controller.abort(); + } else { + console.warn("Failed to verify ciphertext!"); + } + } + }, + { signal: controller.signal } + ); + + socket.emit( + "message", + Packet.createProofChallenge(region, proofSessionVerifier.challenge) + ); + } } export class Region { @@ -72,13 +139,19 @@ export class Region { if (this.owner === null) { return ""; } else if (!this.strength.cipherText.readOnly) { - return this.strength.cipherText.plainText; + return this.strength.cipherText.plainText.toString(); } else if (this.strength.assumedStrength !== null) { - return `${this.strength.assumedStrength}`; + return this.strength.assumedStrength.toString(); } else { return "?"; } } + + prove() {} + + verify(plainText, a) { + this.strength.verify(this.name, plainText, a); + } } const EAST = new Continent("East"); diff --git a/static/js/modules/interface/packet.js b/static/js/modules/interface/packet.js index 53396aa..312769f 100644 --- a/static/js/modules/interface/packet.js +++ b/static/js/modules/interface/packet.js @@ -1,4 +1,4 @@ -import { ID, game } from "./main.js"; +import { ID } from "./main.js"; export class Packet { static _createBase(name) { @@ -85,10 +85,10 @@ export class Packet { }); } - static createReinforce(regionCyphertexts) { + static createReinforce(regionCipherTexts) { return this._sign({ ...this._createBase("ACT"), - regions: regionCyphertexts, + regions: regionCipherTexts, }); } @@ -116,4 +116,32 @@ export class Packet { action: "END", }); } + + static createProofConjecture(region, plainText, a) { + return this._sign({ + ...this._createBase("PROOF"), + stage: "CONJECTURE", + plainText: plainText, + a: a, + region: region, + }); + } + + static createProofChallenge(region, challenge) { + return this._sign({ + ...this._createBase("PROOF"), + stage: "CHALLENGE", + challenge: challenge, + region: region, + }); + } + + static createProof(region, z) { + return this._sign({ + ...this._createBase("PROOF"), + stage: "PROOF", + z: z, + region: region, + }); + } } diff --git a/static/js/modules/interface/player.js b/static/js/modules/interface/player.js index 0cabdcb..2edba23 100644 --- a/static/js/modules/interface/player.js +++ b/static/js/modules/interface/player.js @@ -15,7 +15,7 @@ const PHASE_FORTIFY = 3; let totalDice = 0; export class Player { - constructor(id, local, rsa_key, paillier_key) { + constructor(id, local, rsaKey, paillierKey) { // Game state this.totalStrength = 0; this.ready = false; @@ -23,8 +23,8 @@ export class Player { // Protocol state this.timeout = null; this.id = id; - this.rsaPubKey = RsaPubKey.fromJSON(rsa_key); - this.paillierPubKey = PaillierPubKey.fromJSON(paillier_key); + this.rsaPubKey = RsaPubKey.fromJSON(rsaKey); + this.paillierPubKey = PaillierPubKey.fromJSON(paillierKey); this.lastPacket = 0; // Data which is reset every turn @@ -88,7 +88,7 @@ export class Player { if (region.owner === null) { region.claim( this, - new ReadOnlyCyphertext(this.paillierPubKey, data.cipherText) + new ReadOnlyCyphertext(this.paillierPubKey, BigInt(data.cipherText)) ); this.totalStrength += 1; @@ -111,7 +111,12 @@ export class Player { let region = Region.getRegion(regionName); if (region.owner === this) { - region.reinforce(BigInt(data.regions[regionName])); + region.reinforce( + new ReadOnlyCyphertext( + this.paillierPubKey, + BigInt(data.regions[regionName]) + ) + ); } } diff --git a/whitepaper/Dissertation.pdf b/whitepaper/Dissertation.pdf index 91b5f8e..c82c6d8 100644 Binary files a/whitepaper/Dissertation.pdf and b/whitepaper/Dissertation.pdf differ diff --git a/whitepaper/Dissertation.tex b/whitepaper/Dissertation.tex index e8ae9ff..213659e 100644 --- a/whitepaper/Dissertation.tex +++ b/whitepaper/Dissertation.tex @@ -192,7 +192,7 @@ A similar issue appears in the proposed system: a cheating player could update t \subsubsection{Additive homomorphic cryptosystems} -Some cryptosystems admit an additive homomorphic property: that is, given the public key and two encrypted values $\sigma_1 = E(m_1), \sigma_2 = E(m_2)$, the value $\sigma_1 + \sigma_2 = E(m_1 + m_2)$ is the cyphertext of the underlying operation. +Some cryptosystems admit an additive homomorphic property: that is, given the public key and two encrypted values $\sigma_1 = E(m_1), \sigma_2 = E(m_2)$, the value $\sigma_1 + \sigma_2 = E(m_1 + m_2)$ is the ciphertext of the underlying operation. \cite{paillier1999public} defined a cryptosystem based on residuosity classes, which expresses this property. \cite{damgaard2010generalization} demonstrates an honest-verifier zero-knowledge proof for proving a given value is 0. Hence, clearly, proving a summation $a + b = v$ can be performed by proving $v - a - b = 0$ in an additive homomorphic cryptosystem. @@ -316,7 +316,7 @@ The selection of such $g$ is ideal, as the binomial expansion property helps to \subsection{Encryption} -The cyphertext is, in general, computed as $c = g^m r^n \mod n^2$ for $r < n$ some random secret value. To make this easier to compute, we compute the equivalent value $c = (r^n \mod n^2) \cdot (g^m \mod n^2) \mod n^2$. +The ciphertext is, in general, computed as $c = g^m r^n \mod n^2$ for $r < n$ some random secret value. To make this easier to compute, we compute the equivalent value $c = (r^n \mod n^2) \cdot (g^m \mod n^2) \mod n^2$. \subsection{Private key} @@ -326,13 +326,13 @@ We are also interested in the ability to compute $\mu = \lambda^{-1} \mod n$ as \subsection{Decryption} -Let $c$ be the cyphertext. The corresponding plaintext is computed as $m = L(c^\lambda \mod n^2) \cdot \mu \mod n$, where $L(x) = \frac{x - 1}{n}$. This is relatively simple to compute in JavaScript. +Let $c$ be the ciphertext. The corresponding plaintext is computed as $m = L(c^\lambda \mod n^2) \cdot \mu \mod n$, where $L(x) = \frac{x - 1}{n}$. This is relatively simple to compute in JavaScript. \subsection{Proof system} -The proof system is that of \cite{damgard2003}. The authors give a method to prove knowledge of the encrypted value. The importance of using a zero-knowledge method for this is that it verifies knowledge to a single party. This party should be an honest verifier: this is an assumption we have made of the context, but in general this is not true, and so this provides an attack surface for colluding parties. +The proof system is that of \cite{damgard2003}. The authors give a method to prove knowledge of an encrypted value. The importance of using a zero-knowledge method for this is that it verifies knowledge to a single party. This party should be an honest verifier: this is an assumption we have made of the context, but in general this is not true, and so this provides an attack surface for colluding parties. -The proof system presented is an interactive proof for a given cyphertext $c$ being an encryption of 0. +The proof system presented is an interactive proof for a given ciphertext $c$ being an encryption of 0. \begin{center} \begin{tikzpicture}[every node/.append style={very thick,rounded corners=0.1mm}] @@ -360,14 +360,13 @@ The proof system presented is an interactive proof for a given cyphertext $c$ be \end{tikzpicture} \end{center} -Then, a proof for the following homologous problem can be trivially constructed: given some cyphertext $c = g^mr^n \mod n^2$, prove that the text $cg^{-m} \mod n^2$ is an encryption of 0. +A proof for the following homologous problem can be trivially constructed: given some ciphertext $c = g^mr^n \mod n^2$, prove that the text $cg^{-m} \mod n^2$ is an encryption of 0. The text $cg^{-m}$ is constructed by the verifier. The prover then proceeds with the proof as normal, since $cg^{-m}$ is an encryption of 0 under the same noise as the encryption of $m$ given. % Furthermore, the above protocol can be made non-interactive using the Fiat-Shamir heuristic \citep{fiatshamir}. (this contradicts the lit review) \subsection{Implementation details} - \subsection{Application to domain} Players should prove a number of properties of their game state to each other to ensure fair play. These are as follows. \begin{enumerate} @@ -384,7 +383,7 @@ Players should prove a number of properties of their game state to each other to (4) and (5) can be generalised further as range proofs. -For (1), we propose the following communication sequence. The player submits pairs $(R, c_R)$ for each region they control, where $R$ is the region and $c_R$ is a cyphertext encoding the number of reinforcements to add to the region (which may be 0). Each player computes $c_{R_1} \cdot \ldots \cdot c_{R_n}$. +For (1), we propose the following communication sequence. The player submits pairs $(R, c_R)$ for each region they control, where $R$ is the region and $c_R$ is a ciphertext encoding the number of reinforcements to add to the region (which may be 0). Each player computes $c_{R_1} \cdot \ldots \cdot c_{R_n}$. \subsection{Shared random values} @@ -420,7 +419,7 @@ This is achieved through bit-commitment and properties of $\mathbb{Z}_n$. The pr Depending on how $N_A + N_B$ is then turned into a random value within a range, this system may be manipulated by an attacker who has some knowledge of how participants are generating their noise. As a basic example, suppose a random value within range is generated by taking $N_A + N_B \mod 3$, and participants are producing 2-bit noises. An attacker could submit a 3-bit noise with the most-significant bit set, in which case the odds of getting a 1 are significantly higher than the odds of a 0 or a 2. To avoid this problem, peers should agree beforehand on the number of bits to transmit, and truncate any values in the final stage that exceed this limit. -The encryption function used must also guarantee the integrity of decrypted cyphertexts to prevent a malicious party creating a cyphertext which decrypts to multiple valid values through using different keys. +The encryption function used must also guarantee the integrity of decrypted ciphertexts to prevent a malicious party creating a ciphertext which decrypts to multiple valid values through using different keys. \begin{proposition} The scheme shown is not manipulable by a single cheater. @@ -436,7 +435,7 @@ The encryption function used must also guarantee the integrity of decrypted cyph \subsection{Avoiding modular bias} -The typical way to avoid modular bias is by resampling. To avoid excessive communication, resampling can be performed within the bit sequence by partitioning into blocks of $n$ bits and taking blocks until one falls within range. This is appropriate in the presented use case as random values need only be up to 6, so the likelihood of consuming over 63 bits of noise when resampling for a value in the range 0 to 5 is $\left(\frac{1}{4}\right)^{21} \approx 2.3 \times 10^{-13}$. +The typical way to avoid modular bias is by resampling. To avoid excessive communication, resampling can be performed within the bit sequence by partitioning into blocks of $n$ bits and taking blocks until one falls within range. This is appropriate in the presented use case as random values need only be up to 6, so the probability of consuming over 63 bits of noise when resampling for a value in the range 0 to 5 is $\left(\frac{1}{4}\right)^{21} \approx 2.3 \times 10^{-13}$. \bibliography{Dissertation}