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);
// 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;

View File

@ -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,
});
}

View File

@ -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;

View File

@ -6,7 +6,6 @@
<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://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/sha3.js') }}"></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,
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}}
}
}
@

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 \\
$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}