diff --git a/static/js/modules/interface/main.js b/static/js/modules/interface/main.js
index f4b3f9b..53e2d3c 100644
--- a/static/js/modules/interface/main.js
+++ b/static/js/modules/interface/main.js
@@ -49,7 +49,10 @@ document.addEventListener("DOMContentLoaded", () => {
let sig = BigInt(packet.sig);
// decrypt and compare signature
let dehash = sender.rsaPubKey.encrypt(sig);
- let hash = BigInt("0x" + CryptoJS.SHA3(JSON.stringify(data)).toString());
+
+ let hasher = new jsSHA("SHA3-256", "TEXT");
+ hasher.update(JSON.stringify(data));
+ let hash = BigInt("0x" + hasher.getHash("HEX"));
if (dehash !== hash) {
window.console.error(`Signature invalid! Ignoring packet ${data.id}.`);
return;
diff --git a/static/js/modules/interface/packet.js b/static/js/modules/interface/packet.js
index 2f238cb..213b893 100644
--- a/static/js/modules/interface/packet.js
+++ b/static/js/modules/interface/packet.js
@@ -12,16 +12,12 @@ export class Packet {
static _sign(data) {
// compute hash of data
- let hash = CryptoJS.SHA3(JSON.stringify(data));
- let res = 0n;
- for (let word32 of hash.words) {
- // remove any sign
- let bi = BigInt.asUintN(32, BigInt(word32));
- res = (res << 32n) | bi;
- }
+ let hasher = new jsSHA("SHA3-256", "TEXT");
+ hasher.update(JSON.stringify(data));
+ let hash = BigInt("0x" + hasher.getHash("HEX"));
// sign hash
- let sig = window.rsa.privKey.decrypt(res);
+ let sig = window.rsa.privKey.decrypt(hash);
// return new packet
return {
@@ -54,22 +50,23 @@ export class Packet {
});
}
- static createRandomCyphertext(sessionId, range, cipherText) {
+ static createRandomHMAC(sessionId, range, hmac, key) {
return this._sign({
...this._createBase("RANDOM"),
session: sessionId,
range: range,
- stage: "CIPHERTEXT",
- cipherText: cipherText,
+ stage: "COMMIT",
+ hmac: hmac,
+ key: key,
});
}
- static createRandomKey(sessionId, key) {
+ static createRandomNoise(sessionId, noise) {
return this._sign({
...this._createBase("RANDOM"),
session: sessionId,
- stage: "DECRYPT",
- cipherKey: key,
+ stage: "REVEAL",
+ noise: noise,
});
}
diff --git a/static/js/modules/interface/random.js b/static/js/modules/interface/random.js
index 2ed81e1..2a688b0 100644
--- a/static/js/modules/interface/random.js
+++ b/static/js/modules/interface/random.js
@@ -1,19 +1,24 @@
import { socket, game } from "./main.js";
import { Packet } from "./packet.js";
+import { cryptoRandom } from "../crypto/random_primes.js";
class RandomSession {
constructor(range) {
this.range = range;
- this.cipherTexts = {};
- this.cipherKeys = {};
- this.ourKey = CryptoJS.lib.WordArray.random(32).toString();
- this.ourNoise = CryptoJS.lib.WordArray.random(8);
+ this.hmacs = {};
+ this.keys = {};
+ this.noises = {};
+ this.ourKey = cryptoRandom(1024).toString(16);
+ this.ourNoise = cryptoRandom(64);
this.finalValue = null;
this.resolvers = [];
}
- cipherText() {
- return CryptoJS.AES.encrypt(this.ourNoise, this.ourKey).toString();
+ hmac() {
+ let hasher = new jsSHA("SHA3-256", "HEX");
+ hasher.update(this.ourKey);
+ hasher.update(this.ourNoise.toString(16));
+ return hasher.getHash("HEX");
}
}
@@ -58,7 +63,7 @@ export class Random {
socket.emit(
"message",
- Packet.createRandomCyphertext(sessionId, n, session.cipherText())
+ Packet.createRandomHMAC(sessionId, n, session.hmac(), session.ourKey)
);
return session;
@@ -78,41 +83,49 @@ export class Random {
session = this.initialiseSession(data.range, data.session);
}
- if (stage === "CIPHERTEXT") {
- session.cipherTexts[data.author] = data.cipherText;
+ if (stage === "COMMIT") {
+ session.hmacs[data.author] = data.hmac;
+ session.keys[data.author] = data.key;
if (
- Object.keys(session.cipherTexts).length ===
+ Object.keys(session.hmacs).length ===
Object.keys(game.players).length - 1
) {
// Step 3: release our key once all players have sent a ciphertext
socket.emit(
"message",
- Packet.createRandomKey(data.session, session.ourKey)
+ Packet.createRandomNoise(
+ data.session,
+ "0x" + session.ourNoise.toString(16)
+ )
);
}
- } else if (stage === "DECRYPT") {
- session.cipherKeys[data.author] = data.cipherKey;
+ } else if (stage === "REVEAL") {
+ // Check HMAC
+ let noise = BigInt(data.noise) % 2n ** 64n;
+ let hasher = new jsSHA("SHA3-256", "HEX");
+ hasher.update(session.keys[data.author]);
+ hasher.update(noise.toString(16));
+ let hash = hasher.getHash("HEX");
+
+ if (hash === session.hmacs[data.author]) {
+ session.noises[data.author] = noise;
+ }
// Step 4: get final random value
if (
- Object.keys(session.cipherKeys).length ===
+ Object.keys(session.noises).length ===
Object.keys(game.players).length - 1
) {
// Lock out wait calls as they may resolve to never-ending promises.
await navigator.locks.request(`random-${data.session}`, () => {
- let total = BigInt("0x" + session.ourNoise.toString());
+ let total = session.ourNoise;
- for (let participant of Object.keys(session.cipherKeys)) {
- let decrypted = CryptoJS.AES.decrypt(
- session.cipherTexts[participant],
- session.cipherKeys[participant]
- ).toString();
-
- total += BigInt("0x" + decrypted);
+ for (let noise of Object.values(session.noises)) {
+ total += noise;
}
- // Find first good block of bits to avoid modular bias
+ // Find first good block of bits
let blockSize = BigInt(Math.ceil(Math.log2(session.range)));
let blockMask = 2n ** blockSize - 1n;
diff --git a/templates/index.html b/templates/index.html
index b054521..b22aba7 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -6,7 +6,6 @@
-
diff --git a/whitepaper/Dissertation.bib b/whitepaper/Dissertation.bib
index ce52d8b..565c12c 100644
--- a/whitepaper/Dissertation.bib
+++ b/whitepaper/Dissertation.bib
@@ -286,7 +286,7 @@ howpublished = {\url{https://zcash.readthedocs.io/en/latest/rtd_pages/basics.htm
}
@misc{jssha,
- author = {Caligatio},
+ author = {Brian Turek},
title = {{jsSHA: A JavaScript/TypeScript implementation of the complete Secure Hash Standard (SHA) family}},
year = {2022},
publisher = {GitHub},
@@ -521,4 +521,6 @@ howpublished = {\url{https://www.science.org/content/article/why-criminals-cant-
author = {"Nir"},
title = {Frequently Asked Questions | Soulseek},
howpublished = {\url{http://www.soulseekqt.net/news/faq-page#t10n606}}
-}
\ No newline at end of file
+}
+
+@
\ No newline at end of file
diff --git a/whitepaper/Dissertation.pdf b/whitepaper/Dissertation.pdf
index 6719236..5d3e7ee 100644
Binary files a/whitepaper/Dissertation.pdf and b/whitepaper/Dissertation.pdf differ
diff --git a/whitepaper/Dissertation.tex b/whitepaper/Dissertation.tex
index 49381a3..27970f2 100644
--- a/whitepaper/Dissertation.tex
+++ b/whitepaper/Dissertation.tex
@@ -83,6 +83,7 @@ We present a modern implementation of the Paillier cryptosystem for the browser,
$\lambda(k)$ & Carmichael's totient function \\
$H(\dots)$ & Ideal cryptographic hash function \\
$\in_R$ & Selection at random \\
+ $A \mathbin\Vert B$ & Concatenation of $A$ and $B$ \\
\bottomrule
\end{tabularx}
\end{table}
@@ -598,17 +599,19 @@ This is achieved with a standard application of bit-commitment, and properties o
\node[draw=blue!50,rectangle,thick,text width=4cm] (NoiseA) at (0,-1.5) {Generate random noise $N_A$, random key $k_A$};
\node[draw=blue!50,rectangle,thick,text width=4cm] (NoiseB) at (6,-1.5) {Generate random noise $N_B$, random key $k_B$};
- \draw [->,very thick] (0,-3)--node [auto] {$E_{k_A}(N_A)$}++(6,0);
- \draw [<-,very thick] (0,-4)--node [auto] {$E_{k_B}(N_B)$}++(6,0);
+ \draw [->,very thick] (0,-3)--node [auto] {$H(k_A \mathbin\Vert N_A), k_A$}++(6,0);
+ \draw [<-,very thick] (0,-4)--node [auto] {$H(k_B \mathbin\Vert N_B), k_B$}++(6,0);
- \draw [->,very thick] (0,-5)--node [auto] {$k_A$}++(6,0);
- \draw [<-,very thick] (0,-6)--node [auto] {$k_B$}++(6,0);
+ \draw [->,very thick] (0,-5)--node [auto] {$N_A$}++(6,0);
+ \draw [<-,very thick] (0,-6)--node [auto] {$N_B$}++(6,0);
- \node[draw=blue!50,rectangle,thick] (CA) at (0,-7) {Compute $N_A + N_B$};
- \node[draw=blue!50,rectangle,thick] (CB) at (6,-7) {Compute $N_A + N_B$};
+ \node[draw=blue!50,rectangle,thick] (ChA) at (0,-7) {Check $H(k_B \mathbin\Vert N_B)$};
+ \node[draw=blue!50,rectangle,thick] (ChB) at (6,-7) {Check $H(k_A \mathbin\Vert N_A)$};
+ \node[draw=blue!50,rectangle,thick] (CA) at (0,-8) {Compute $N_A + N_B$};
+ \node[draw=blue!50,rectangle,thick] (CB) at (6,-8) {Compute $N_A + N_B$};
- \draw [very thick] (A)-- (NoiseA)-- (CA)-- (0,-7);
- \draw [very thick] (B)-- (NoiseB)-- (CB)-- (6,-7);
+ \draw [very thick] (A)-- (NoiseA)-- (ChA)-- (CA)-- (0,-8);
+ \draw [very thick] (B)-- (NoiseB)-- (ChB)-- (CB)-- (6,-8);
\end{tikzpicture}
\end{center}
\end{protocol}
@@ -617,7 +620,7 @@ To generalise this to $n$ peers, we ensure that each peer waits to receive all e
Depending on how $N_A + N_B$ is then moved into the required range, this system may be manipulated by an attacker who has some knowledge of how participants are generating their noise. As an 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 probability of the final result being a 1 is significantly higher than the probability of a 0 or a 2. This is a typical example of modular bias. To avoid this problem, peers should agree beforehand on the number of bits to transmit, and compute the final value as $N_A \oplus N_B$.
-The encryption function used must also guarantee the integrity of decrypted ciphertexts. Otherwise, a malicious party could create a ciphertext which decrypts to multiple valid values by using different keys.
+The hash function used must also be resistant to length-extension attacks for the presented protocol. In general, a hash-based message authentication code can be used.
\begin{proposition}
With the above considerations, the scheme shown is not manipulable by a single cheater.
@@ -626,13 +629,13 @@ The encryption function used must also guarantee the integrity of decrypted ciph
\begin{proof}
Suppose $P_1, \dots, P_{n-1}$ are honest participants, and $P_n$ is a cheater with a desired outcome.
- In step 1, each participant $P_i$ commits $E_{k_i}(N_i)$. The cheater $P_n$ commits a constructed noise $E_{k_n}(N_n)$.
+ In step 1, each participant $P_i$ commits $H(k_i \mathbin\Vert N_i)$. The cheater $P_n$ commits $H(k_n \mathbin\Vert N_n)$.
- The encryption function $E_k$ holds the confidentiality property: that is, without $k$, $P_i$ cannot retrieve $m$ given $E_k(m)$. So $P_n$'s choice of $N_n$ cannot be directed by other commitments.
+ The hash function $H$ holds the preimage resistance property: that is, $P_i$ cannot find $m$ given $H(m)$. So $P_n$'s choice of $N_n$ cannot be directed by other commitments.
The final value is dictated by the sum of all decrypted values. $P_n$ is therefore left in a position of choosing $N_n$ to control the outcome of $a + N_n$, where $a$ is selected uniformly at random from the abelian group $\mathbb{Z}_{2^\ell}$ for $\ell$ the agreed upon bit length.
- As every element of this group is of order $2^\ell$, the distribution of $a + N_n$ is identical regardless of the choice of $N_n$. So $P_n$ maintains no control over the outcome of $a + N_n$.
+ As every element of this group is of order $2^\ell$, the distribution of $a + N_n$ is identical regardless of the choice of $N_n$. As $P_n$ cannot reasonably find a collision for $H(k_n \mathbin\Vert N_n)$, $P_n$ must reveal $N_n$. So $P_n$ maintains no control over the outcome of $a + N_n$.
\end{proof}
This extends inductively to support $n-1$ cheating participants, even if colluding. Finally, we must consider how to reduce random noise to useful values.
@@ -652,6 +655,8 @@ Random values are used in two places. \begin{itemize}
As this protocol must run many times during a game, we consider each operation of the protocol as a "session", each of which has a unique name that is derived from the context. A benefit of this is that the unique name can be used with the Web Locks API to prevent race conditions that may occur due to this protocol running asynchronously.
+To achieve bit-commitment, we use SHA-3 \cite{FIPS202}, as implemented by jsSHA \cite{jssha}. SHA-3 is resistant to length-extension attacks, and is considered secure, so it is reasonable to assume that a malicious player will not be able to find a collision.
+
\section{Proof system}
Players should prove a number of properties of their game state to each other to ensure fair play. These are as follows. \begin{enumerate}