removed cryptojs from requirements
This commit is contained in:
parent
6ffdadb0b5
commit
b59ced8fa6
@ -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;
|
||||||
|
@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
@ -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},
|
||||||
@ -521,4 +521,6 @@ howpublished = {\url{https://www.science.org/content/article/why-criminals-cant-
|
|||||||
author = {"Nir"},
|
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.
@ -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}
|
||||||
|
Loading…
Reference in New Issue
Block a user