removed cryptojs from requirements

This commit is contained in:
jude 2023-05-02 14:19:15 +01:00
parent 6ffdadb0b5
commit b59ced8fa6
7 changed files with 72 additions and 53 deletions

View File

@ -49,7 +49,10 @@ document.addEventListener("DOMContentLoaded", () => {
let sig = BigInt(packet.sig); let sig = BigInt(packet.sig);
// decrypt and compare signature // decrypt and compare signature
let dehash = sender.rsaPubKey.encrypt(sig); 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) { if (dehash !== hash) {
window.console.error(`Signature invalid! Ignoring packet ${data.id}.`); window.console.error(`Signature invalid! Ignoring packet ${data.id}.`);
return; return;

View File

@ -12,16 +12,12 @@ export class Packet {
static _sign(data) { static _sign(data) {
// compute hash of data // compute hash of data
let hash = CryptoJS.SHA3(JSON.stringify(data)); let hasher = new jsSHA("SHA3-256", "TEXT");
let res = 0n; hasher.update(JSON.stringify(data));
for (let word32 of hash.words) { let hash = BigInt("0x" + hasher.getHash("HEX"));
// remove any sign
let bi = BigInt.asUintN(32, BigInt(word32));
res = (res << 32n) | bi;
}
// sign hash // sign hash
let sig = window.rsa.privKey.decrypt(res); let sig = window.rsa.privKey.decrypt(hash);
// return new packet // return new packet
return { return {
@ -54,22 +50,23 @@ export class Packet {
}); });
} }
static createRandomCyphertext(sessionId, range, cipherText) { static createRandomHMAC(sessionId, range, hmac, key) {
return this._sign({ return this._sign({
...this._createBase("RANDOM"), ...this._createBase("RANDOM"),
session: sessionId, session: sessionId,
range: range, range: range,
stage: "CIPHERTEXT", stage: "COMMIT",
cipherText: cipherText, hmac: hmac,
key: key,
}); });
} }
static createRandomKey(sessionId, key) { static createRandomNoise(sessionId, noise) {
return this._sign({ return this._sign({
...this._createBase("RANDOM"), ...this._createBase("RANDOM"),
session: sessionId, session: sessionId,
stage: "DECRYPT", stage: "REVEAL",
cipherKey: key, noise: noise,
}); });
} }

View File

@ -1,19 +1,24 @@
import { socket, game } from "./main.js"; import { socket, game } from "./main.js";
import { Packet } from "./packet.js"; import { Packet } from "./packet.js";
import { cryptoRandom } from "../crypto/random_primes.js";
class RandomSession { class RandomSession {
constructor(range) { constructor(range) {
this.range = range; this.range = range;
this.cipherTexts = {}; this.hmacs = {};
this.cipherKeys = {}; this.keys = {};
this.ourKey = CryptoJS.lib.WordArray.random(32).toString(); this.noises = {};
this.ourNoise = CryptoJS.lib.WordArray.random(8); this.ourKey = cryptoRandom(1024).toString(16);
this.ourNoise = cryptoRandom(64);
this.finalValue = null; this.finalValue = null;
this.resolvers = []; this.resolvers = [];
} }
cipherText() { hmac() {
return CryptoJS.AES.encrypt(this.ourNoise, this.ourKey).toString(); 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( socket.emit(
"message", "message",
Packet.createRandomCyphertext(sessionId, n, session.cipherText()) Packet.createRandomHMAC(sessionId, n, session.hmac(), session.ourKey)
); );
return session; return session;
@ -78,41 +83,49 @@ export class Random {
session = this.initialiseSession(data.range, data.session); session = this.initialiseSession(data.range, data.session);
} }
if (stage === "CIPHERTEXT") { if (stage === "COMMIT") {
session.cipherTexts[data.author] = data.cipherText; session.hmacs[data.author] = data.hmac;
session.keys[data.author] = data.key;
if ( if (
Object.keys(session.cipherTexts).length === Object.keys(session.hmacs).length ===
Object.keys(game.players).length - 1 Object.keys(game.players).length - 1
) { ) {
// Step 3: release our key once all players have sent a ciphertext // Step 3: release our key once all players have sent a ciphertext
socket.emit( socket.emit(
"message", "message",
Packet.createRandomKey(data.session, session.ourKey) Packet.createRandomNoise(
data.session,
"0x" + session.ourNoise.toString(16)
)
); );
} }
} else if (stage === "DECRYPT") { } else if (stage === "REVEAL") {
session.cipherKeys[data.author] = data.cipherKey; // 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 // Step 4: get final random value
if ( if (
Object.keys(session.cipherKeys).length === Object.keys(session.noises).length ===
Object.keys(game.players).length - 1 Object.keys(game.players).length - 1
) { ) {
// Lock out wait calls as they may resolve to never-ending promises. // Lock out wait calls as they may resolve to never-ending promises.
await navigator.locks.request(`random-${data.session}`, () => { 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)) { for (let noise of Object.values(session.noises)) {
let decrypted = CryptoJS.AES.decrypt( total += noise;
session.cipherTexts[participant],
session.cipherKeys[participant]
).toString();
total += BigInt("0x" + decrypted);
} }
// 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 blockSize = BigInt(Math.ceil(Math.log2(session.range)));
let blockMask = 2n ** blockSize - 1n; let blockMask = 2n ** blockSize - 1n;

View File

@ -6,7 +6,6 @@
<link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet"> <link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet">
<script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script> <script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
<script src="{{ url_for('static', filename='js/lz-string.min.js') }}"></script> <script src="{{ url_for('static', filename='js/lz-string.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/sha3.js') }}"></script> <script src="{{ url_for('static', filename='js/sha3.js') }}"></script>
<script src="{{ url_for('static', filename='js/modules/interface/main.js') }}" type="module"></script> <script src="{{ url_for('static', filename='js/modules/interface/main.js') }}" type="module"></script>

View File

@ -286,7 +286,7 @@ howpublished = {\url{https://zcash.readthedocs.io/en/latest/rtd_pages/basics.htm
} }
@misc{jssha, @misc{jssha,
author = {Caligatio}, author = {Brian Turek},
title = {{jsSHA: A JavaScript/TypeScript implementation of the complete Secure Hash Standard (SHA) family}}, title = {{jsSHA: A JavaScript/TypeScript implementation of the complete Secure Hash Standard (SHA) family}},
year = {2022}, year = {2022},
publisher = {GitHub}, publisher = {GitHub},
@ -522,3 +522,5 @@ author = {"Nir"},
title = {Frequently Asked Questions | Soulseek}, title = {Frequently Asked Questions | Soulseek},
howpublished = {\url{http://www.soulseekqt.net/news/faq-page#t10n606}} howpublished = {\url{http://www.soulseekqt.net/news/faq-page#t10n606}}
} }
@

Binary file not shown.

View File

@ -83,6 +83,7 @@ We present a modern implementation of the Paillier cryptosystem for the browser,
$\lambda(k)$ & Carmichael's totient function \\ $\lambda(k)$ & Carmichael's totient function \\
$H(\dots)$ & Ideal cryptographic hash function \\ $H(\dots)$ & Ideal cryptographic hash function \\
$\in_R$ & Selection at random \\ $\in_R$ & Selection at random \\
$A \mathbin\Vert B$ & Concatenation of $A$ and $B$ \\
\bottomrule \bottomrule
\end{tabularx} \end{tabularx}
\end{table} \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] (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$}; \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,-3)--node [auto] {$H(k_A \mathbin\Vert N_A), k_A$}++(6,0);
\draw [<-,very thick] (0,-4)--node [auto] {$E_{k_B}(N_B)$}++(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,-5)--node [auto] {$N_A$}++(6,0);
\draw [<-,very thick] (0,-6)--node [auto] {$k_B$}++(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] (ChA) at (0,-7) {Check $H(k_B \mathbin\Vert N_B)$};
\node[draw=blue!50,rectangle,thick] (CB) at (6,-7) {Compute $N_A + 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] (A)-- (NoiseA)-- (ChA)-- (CA)-- (0,-8);
\draw [very thick] (B)-- (NoiseB)-- (CB)-- (6,-7); \draw [very thick] (B)-- (NoiseB)-- (ChB)-- (CB)-- (6,-8);
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
\end{protocol} \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$. 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} \begin{proposition}
With the above considerations, the scheme shown is not manipulable by a single cheater. 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} \begin{proof}
Suppose $P_1, \dots, P_{n-1}$ are honest participants, and $P_n$ is a cheater with a desired outcome. 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. 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} \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. 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. 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} \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} Players should prove a number of properties of their game state to each other to ensure fair play. These are as follows. \begin{enumerate}