Fixed stuff

This commit is contained in:
jude 2023-04-13 13:32:29 +01:00
parent 1d9ab1d601
commit b7150d6547
5 changed files with 69 additions and 35 deletions

View File

@ -1,11 +1,14 @@
import { cryptoRandom, generate_prime } from "./random_primes.js"; import { cryptoRandom, generate_prime, KEY_SIZE } from "./random_primes.js";
import { gcd, mod_exp } from "./math.js"; import { gcd, mod_exp } from "./math.js";
const PAILLIER = 0;
const JURIK = 1;
class Ciphertext { class Ciphertext {
constructor(key, plainText, r) { constructor(key, plainText, r) {
if (r === undefined) { if (r === undefined) {
r = cryptoRandom(2048); // Use the optimised form using Jacobi classes
} r = cryptoRandom();
// Compute g^m by binomial theorem. // Compute g^m by binomial theorem.
let gm = (1n + key.n * plainText) % key.n2; let gm = (1n + key.n * plainText) % key.n2;
@ -18,7 +21,25 @@ class Ciphertext {
this.cipherText += key.n2; this.cipherText += key.n2;
} }
this.mode = JURIK;
this.r = mod_exp(key.h, r, key.n);
} else {
// Use the standard form
// Compute g^m by binomial theorem.
let gm = (1n + key.n * plainText) % key.n2;
// Compute g^m r^n.
this.cipherText = (gm * mod_exp(r, key.n, key.n2)) % key.n2;
// Force into range.
while (this.cipherText < 0n) {
this.cipherText += key.n2;
}
this.mode = PAILLIER;
this.r = r; this.r = r;
}
this.pubKey = key; this.pubKey = key;
this.plainText = plainText; this.plainText = plainText;
@ -46,9 +67,9 @@ class Ciphertext {
// Construct a non-interactive proof // Construct a non-interactive proof
proveNI() { proveNI() {
let rp = cryptoRandom(4096); let rp = cryptoRandom(KEY_SIZE * 2);
while (rp >= this.pubKey.n) { while (rp >= this.pubKey.n) {
rp = cryptoRandom(4096); rp = cryptoRandom(KEY_SIZE * 2);
} }
let a = mod_exp(rp, this.pubKey.n, this.pubKey.n2); let a = mod_exp(rp, this.pubKey.n, this.pubKey.n2);
@ -92,9 +113,9 @@ class ValueProofSessionProver {
constructor(cipherText) { constructor(cipherText) {
this.cipherText = cipherText; this.cipherText = cipherText;
this.rp = cryptoRandom(4096); this.rp = cryptoRandom(KEY_SIZE * 2);
while (this.rp >= this.cipherText.pubKey.n) { while (this.rp >= this.cipherText.pubKey.n) {
this.rp = cryptoRandom(4096); this.rp = cryptoRandom(KEY_SIZE * 2);
} }
} }
@ -168,7 +189,7 @@ class ValueProofSessionVerifier {
if (challenge === undefined) { if (challenge === undefined) {
// Shift the challenge down by 1 to ensure it is smaller than either prime factor. // Shift the challenge down by 1 to ensure it is smaller than either prime factor.
this.challenge = cryptoRandom(2048) << 1n; this.challenge = cryptoRandom(KEY_SIZE) << 1n;
} else { } else {
this.challenge = challenge; this.challenge = challenge;
} }
@ -205,10 +226,9 @@ export class PaillierPubKey {
this.n = n; this.n = n;
if (h === undefined) { if (h === undefined) {
let x = cryptoRandom(4096); let x = cryptoRandom(KEY_SIZE * 2);
while (x >= this.n) { while (x >= this.n) {
x = cryptoRandom(4096); x = cryptoRandom(KEY_SIZE * 2);
} }
this.h = ((-1n * x ** 2n) % this.n) + this.n; this.h = ((-1n * x ** 2n) % this.n) + this.n;

View File

@ -1,8 +1,10 @@
import { mod_exp } from "./math.js"; import { mod_exp } from "./math.js";
export const KEY_SIZE = 512;
export function cryptoRandom(bits) { export function cryptoRandom(bits) {
if (bits === undefined) { if (bits === undefined) {
bits = 2048; bits = KEY_SIZE;
} }
let length = bits / 64; let length = bits / 64;
@ -29,7 +31,7 @@ function generate_bigint() {
intRepr >>= 1n; intRepr >>= 1n;
// Add 2^2047 to force into range from below // Add 2^2047 to force into range from below
intRepr += 2n ** 2047n; intRepr += 2n ** BigInt(KEY_SIZE - 1);
return intRepr; return intRepr;
} }

View File

@ -1,4 +1,8 @@
// Fisher-Yates shuffle /**
* CSPRNG Fisher-Yates shuffle.
*
* Only works on lists up to 255 elements.
*/
function cryptoShuffle(l) { function cryptoShuffle(l) {
for (let i = l.length - 1; i > 0; i--) { for (let i = l.length - 1; i > 0; i--) {
let value = new Uint8Array([0]); let value = new Uint8Array([0]);

Binary file not shown.

View File

@ -261,13 +261,13 @@ The number of operations is dependent primarily on the size of the exponent. For
I chose to use primes of length 2048 bits. This is a typical prime size for public-key cryptography, as this generates a modulus $n = pq$ of length 4096 bits. I chose to use primes of length 2048 bits. This is a typical prime size for public-key cryptography, as this generates a modulus $n = pq$ of length 4096 bits.
Generating these primes is a basic application of the Rabin-Miller primality test \cite{RABIN1980128}. This produces probabilistic primes, however upon completing sufficiently many rounds of verification, the likelihood of these numbers actually not being prime is dwarfed by the likelihood of hardware failure. Generating these primes is a basic application of the Rabin-Miller primality test \cite{RABIN1980128}. This produces probabilistic primes, however upon completing sufficiently many rounds of verification, the likelihood of these numbers actually not being prime is dwarfed by the likelihood of some other failure, such as hardware failure.
\subsection{Public key} \subsection{Public key}
In the Paillier cryptosystem, the public key is a pair $(n, g)$ where $n = pq$ for primes $p, q$ satisfying $\gcd(pq, (p - 1)(q - 1)) = 1$ and $g \in \mathbb{Z}^*_{n^2}$. We restrict the range of plaintexts $m$ to $m < n$. In the Paillier cryptosystem, the public key is a pair $(n, g)$ where $n = pq$ for primes $p, q$ satisfying $\gcd(pq, (p - 1)(q - 1)) = 1$ and $g \in \mathbb{Z}^*_{n^2}$. We restrict the range of plaintexts $m$ to $m < n$.
The Paillier cryptosystem is otherwise generic over the choice of primes $p, q$. However, by choosing $p, q$ of equal length, the required property on $pq, (p - 1)(q - 1)$ coprime is guaranteed. The Paillier cryptosystem is otherwise generic over the choice of primes $p, q$. However, by choosing $p, q$ of equal length, the required property of $pq$ and $(p - 1)(q - 1)$ being coprime is guaranteed.
\begin{proposition} \begin{proposition}
For $p, q$ prime of equal length, $\gcd(pq, (p - 1)(q - 1)) = 1$. For $p, q$ prime of equal length, $\gcd(pq, (p - 1)(q - 1)) = 1$.
@ -291,7 +291,7 @@ The selection of such $g$ is ideal, as the binomial expansion property helps to
\subsection{Encryption} \subsection{Encryption}
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$. In the original Paillier scheme, ciphertexts are computed as $c = g^m r^n \mod n^2$ for $r < n$ some random secret value. To make this easier to compute, we can compute the equivalent value $c = (r^n \mod n^2) \cdot (g^m \mod n^2) \mod n^2$.
\subsection{Private key} \subsection{Private key}
@ -301,7 +301,7 @@ We are also interested in the ability to compute $\mu = \lambda^{-1} \mod n$ as
\subsection{Decryption} \subsection{Decryption}
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. 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 operation can be optimised by applying Chinese remainder theorem. However, in the application presented, decryption is not used and is only useful as a debugging measure. So this need not be optimised.
\subsection{Implementation details} \subsection{Implementation details}
@ -311,8 +311,9 @@ Paillier is implemented by four classes: \texttt{PubKey}, \texttt{PrivKey}, \tex
A large part of Risk involves random behaviour dictated by rolling some number of dice. To achieve this, some fair protocol must be used to generate random values consistently across each peer without any peer being able to manipulate the outcomes. A large part of Risk involves random behaviour dictated by rolling some number of dice. To achieve this, some fair protocol must be used to generate random values consistently across each peer without any peer being able to manipulate the outcomes.
This is achieved through bit-commitment and properties of $\mathbb{Z}_n$. The protocol for two peers is as follows, and generalises to $n$ peers trivially. This is achieved through bit-commitment and properties of $\mathbb{Z}_n$. The protocol for two peers is as follows, and generalises to $n$ peers.
\begin{protocol}[Shared random values]
\begin{center} \begin{center}
\begin{tikzpicture}[ \begin{tikzpicture}[
every node/.append style={very thick,rounded corners=0.1mm} every node/.append style={very thick,rounded corners=0.1mm}
@ -338,6 +339,9 @@ This is achieved through bit-commitment and properties of $\mathbb{Z}_n$. The pr
\draw [very thick] (B)-- (NoiseB)-- (CB)-- (6,-7); \draw [very thick] (B)-- (NoiseB)-- (CB)-- (6,-7);
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
\end{protocol}
To generalise this to $n$ peers, we ensure that each peer waits to receive all encrypted noises before transmitting their decryption key.
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 probability of the final result being a 1 are 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. Addition of noise will then operate modulo $2^\ell$, where $\ell$ is the agreed-upon number of bits. 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 probability of the final result being a 1 are 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. Addition of noise will then operate modulo $2^\ell$, where $\ell$ is the agreed-upon number of bits.
@ -372,11 +376,13 @@ Random values are used in two places. \begin{itemize}
\item Rolling dice. \item Rolling dice.
\end{itemize} \end{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. This has another benefit as the unique name can then be used with the Web Lock API to prevent race conditions that may occur due to this protocol running in a non-blocking manner.
\subsection{Proof system} \subsection{Proof system}
The first proof to discuss 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 first proof to discuss 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 ciphertext $c$ being an encryption of zero. The proof system presented is a Schnorr-style interactive proof for a given ciphertext $c$ being an encryption of zero.
\begin{center} \begin{center}
\begin{tikzpicture}[every node/.append style={very thick,rounded corners=0.1mm}] \begin{tikzpicture}[every node/.append style={very thick,rounded corners=0.1mm}]
@ -384,7 +390,7 @@ The proof system presented is an interactive proof for a given ciphertext $c$ be
\node[draw,rectangle] (P) at (0,0) {Prover}; \node[draw,rectangle] (P) at (0,0) {Prover};
\node[draw,rectangle] (V) at (6,0) {Verifier}; \node[draw,rectangle] (V) at (6,0) {Verifier};
\node[draw=blue!50,rectangle,thick,text width=5cm] (v) at (0,-1.5) {$r \in \mathbb{Z}_n^*$ with $c = r^n \mod n^2$}; \node[draw=blue!50,rectangle,thick,text width=5.05cm] (v) at (0,-1.5) {$r \in \mathbb{Z}_n^*$ with $c = r^n \mod n^2$};
\draw [->,very thick] (0,-3)--node [auto] {$c$}++(6,0); \draw [->,very thick] (0,-3)--node [auto] {$c$}++(6,0);
\node[draw=blue!50,rectangle,thick] (r) at (0,-4) {Choose random $r^* \in \mathbb{Z}_n^*$}; \node[draw=blue!50,rectangle,thick] (r) at (0,-4) {Choose random $r^* \in \mathbb{Z}_n^*$};
@ -562,15 +568,17 @@ There is little room for optimisation of the mathematics in Paillier encryption.
\textbf{Public parameter.} The choice of the public parameter $g$ can improve the time complexity by removing the need for some large modular exponentiation. Selection of $g = n + 1$ is good in this regard, as binomial theorem allows the modular exponentiation $g^m \mod n^2$ to be reduced to the computation $1 + nm \mod n^2$. \textbf{Public parameter.} The choice of the public parameter $g$ can improve the time complexity by removing the need for some large modular exponentiation. Selection of $g = n + 1$ is good in this regard, as binomial theorem allows the modular exponentiation $g^m \mod n^2$ to be reduced to the computation $1 + nm \mod n^2$.
\textbf{Smaller key size.} The complexity of Paillier encryption increases with key size. Using a smaller key could considerably reduce the time taken \cite{paillier1999public}.
\textbf{Caching.} As the main values being encrypted are 0 or 1, a peer could maintain a cache of encryptions of these values and transmit these instantly. Caching may be executed in a background "web worker". A consideration is whether a peer may be able to execute a timing-related attack by first exhausting a peer's cache of a known plaintext value, and then requesting an unknown value and using the time taken to determine if the value was sent from the exhausted cache or not. \textbf{Caching.} As the main values being encrypted are 0 or 1, a peer could maintain a cache of encryptions of these values and transmit these instantly. Caching may be executed in a background "web worker". A consideration is whether a peer may be able to execute a timing-related attack by first exhausting a peer's cache of a known plaintext value, and then requesting an unknown value and using the time taken to determine if the value was sent from the exhausted cache or not.
Taking this idea further, one may simply cache $r^n$ for a number of randomly generated $r$ (as this is the slowest part of encryption). This eliminates the timing attack concern, and grants full flexibility with the values being encrypted. Taking this idea further, one may simply cache $r^n$ for a number of randomly generated $r$ (as this is the slowest part of encryption). This eliminates the timing attack concern, and grants full flexibility with the values being encrypted.
\textbf{Alternative Paillier scheme.} \cite{Jurik2003ExtensionsTT} presents an optimised encryption scheme based on the subgroup of elements with Jacobi symbol $+1$. This forms a group as the Jacobi symbol is multiplicative, being a generalisation of the Legendre symbol. \textbf{Alternative Paillier scheme.} \cite{Jurik2003ExtensionsTT} presents an optimised encryption scheme based on the subgroup of elements with Jacobi symbol $+1$. This forms a group as the Jacobi symbol is multiplicative, being a generalisation of the Legendre symbol.
I used this scheme to reduce the time to encrypt to half. Greater optimisations are possible through pre-computation of fixed-base exponentials, but this takes a considerable amount of time, and I found it unfeasible within my implementation, since keypairs are only used for a single session. I used this scheme to reduce the time to encrypt to half. Greater optimisations are possible through pre-computation of fixed-base exponentials, but this takes a considerable amount of time, and I found it infeasible within my implementation, since keypairs are only used for a single session.
\textbf{Smaller key size.} The complexity of Paillier encryption increases with key size. Using a smaller key could considerably reduce the time taken \cite{paillier1999public}.
I tested this on top of the alternative Paillier scheme from above. This resulted in linear reductions in encryption time: encryption under a 1024-bit modulus took half the amount of time as under a 2048-bit modulus and so on.
\textbf{Vectorised plaintexts.} The maximum size of a plaintext is $|n|$: in our case, this is 4096 bits. By considering this as a vector of 128 32-bit values, peers could use a single ciphertext to represent their entire state. \cite{10.1145/2809695.2809723} uses this process to allow embedded devices to make use of the homomorphic properties of Paillier. \textbf{Vectorised plaintexts.} The maximum size of a plaintext is $|n|$: in our case, this is 4096 bits. By considering this as a vector of 128 32-bit values, peers could use a single ciphertext to represent their entire state. \cite{10.1145/2809695.2809723} uses this process to allow embedded devices to make use of the homomorphic properties of Paillier.