diff --git a/README.md b/README.md new file mode 100644 index 0000000..1e097b3 --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +# Riskless +### Undergraduate dissertation + +**Paper: https://judesouthworth.xyz/Dissertation.pdf** + +## Running the demo + +1. `pip install -r requirements.txt` +2. `python3 app.py` +3. Navigate browser to `http://localhost:5000` + +### Running the benchmarks + +1. Press F12 +2. Type the name of the benchmark to run: + - `RSABench` + - `PaillierBench` + - `ZeroProofBench` + - `ZeroProofVerifierBench` + - `ZeroProofSizeBench` + - `Protocol4Bench` + - `Protocol4VerifierBench` + - `Protocol4SizeBench` +3. Wait for results to output in milliseconds + +### Changing the key size + +The key size is determined by the variable `KEY_SIZE` in `random_primes.js`. This +variable dictates the size of _each_ prime used in the modulus, so the total key size +is twice this value. Recommend selecting one of 512, 1024, or 2048. In fact, I recommend +selecting 512 if you want the program to run in less than 3-5 business days. diff --git a/static/js/lz-string.js b/static/js/lz-string.js new file mode 100644 index 0000000..7bb03c2 --- /dev/null +++ b/static/js/lz-string.js @@ -0,0 +1,309 @@ +var LZString = (function () { + var r = String.fromCharCode, + o = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", + n = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$", + e = {}; + function t(r, o) { + if (!e[r]) { + e[r] = {}; + for (var n = 0; n < r.length; n++) e[r][r.charAt(n)] = n; + } + return e[r][o]; + } + var i = { + compressToBase64: function (r) { + if (null == r) return ""; + var n = i._compress(r, 6, function (r) { + return o.charAt(r); + }); + switch (n.length % 4) { + default: + case 0: + return n; + case 1: + return n + "==="; + case 2: + return n + "=="; + case 3: + return n + "="; + } + }, + decompressFromBase64: function (r) { + return null == r + ? "" + : "" == r + ? null + : i._decompress(r.length, 32, function (n) { + return t(o, r.charAt(n)); + }); + }, + compressToUTF16: function (o) { + return null == o + ? "" + : i._compress(o, 15, function (o) { + return r(o + 32); + }) + " "; + }, + decompressFromUTF16: function (r) { + return null == r + ? "" + : "" == r + ? null + : i._decompress(r.length, 16384, function (o) { + return r.charCodeAt(o) - 32; + }); + }, + compressToUint8Array: function (r) { + for ( + var o = i.compress(r), + n = new Uint8Array(2 * o.length), + e = 0, + t = o.length; + e < t; + e++ + ) { + var s = o.charCodeAt(e); + (n[2 * e] = s >>> 8), (n[2 * e + 1] = s % 256); + } + return n; + }, + decompressFromUint8Array: function (o) { + if (null == o) return i.decompress(o); + for (var n = new Array(o.length / 2), e = 0, t = n.length; e < t; e++) + n[e] = 256 * o[2 * e] + o[2 * e + 1]; + var s = []; + return ( + n.forEach(function (o) { + s.push(r(o)); + }), + i.decompress(s.join("")) + ); + }, + compressToEncodedURIComponent: function (r) { + return null == r + ? "" + : i._compress(r, 6, function (r) { + return n.charAt(r); + }); + }, + decompressFromEncodedURIComponent: function (r) { + return null == r + ? "" + : "" == r + ? null + : ((r = r.replace(/ /g, "+")), + i._decompress(r.length, 32, function (o) { + return t(n, r.charAt(o)); + })); + }, + compress: function (o) { + return i._compress(o, 16, function (o) { + return r(o); + }); + }, + _compress: function (r, o, n) { + if (null == r) return ""; + var e, + t, + i, + s = {}, + u = {}, + a = "", + p = "", + c = "", + l = 2, + f = 3, + h = 2, + d = [], + m = 0, + v = 0; + for (i = 0; i < r.length; i += 1) + if ( + ((a = r.charAt(i)), + Object.prototype.hasOwnProperty.call(s, a) || + ((s[a] = f++), (u[a] = !0)), + (p = c + a), + Object.prototype.hasOwnProperty.call(s, p)) + ) + c = p; + else { + if (Object.prototype.hasOwnProperty.call(u, c)) { + if (c.charCodeAt(0) < 256) { + for (e = 0; e < h; e++) + (m <<= 1), + v == o - 1 ? ((v = 0), d.push(n(m)), (m = 0)) : v++; + for (t = c.charCodeAt(0), e = 0; e < 8; e++) + (m = (m << 1) | (1 & t)), + v == o - 1 ? ((v = 0), d.push(n(m)), (m = 0)) : v++, + (t >>= 1); + } else { + for (t = 1, e = 0; e < h; e++) + (m = (m << 1) | t), + v == o - 1 ? ((v = 0), d.push(n(m)), (m = 0)) : v++, + (t = 0); + for (t = c.charCodeAt(0), e = 0; e < 16; e++) + (m = (m << 1) | (1 & t)), + v == o - 1 ? ((v = 0), d.push(n(m)), (m = 0)) : v++, + (t >>= 1); + } + 0 == --l && ((l = Math.pow(2, h)), h++), delete u[c]; + } else + for (t = s[c], e = 0; e < h; e++) + (m = (m << 1) | (1 & t)), + v == o - 1 ? ((v = 0), d.push(n(m)), (m = 0)) : v++, + (t >>= 1); + 0 == --l && ((l = Math.pow(2, h)), h++), + (s[p] = f++), + (c = String(a)); + } + if ("" !== c) { + if (Object.prototype.hasOwnProperty.call(u, c)) { + if (c.charCodeAt(0) < 256) { + for (e = 0; e < h; e++) + (m <<= 1), + v == o - 1 ? ((v = 0), d.push(n(m)), (m = 0)) : v++; + for (t = c.charCodeAt(0), e = 0; e < 8; e++) + (m = (m << 1) | (1 & t)), + v == o - 1 ? ((v = 0), d.push(n(m)), (m = 0)) : v++, + (t >>= 1); + } else { + for (t = 1, e = 0; e < h; e++) + (m = (m << 1) | t), + v == o - 1 ? ((v = 0), d.push(n(m)), (m = 0)) : v++, + (t = 0); + for (t = c.charCodeAt(0), e = 0; e < 16; e++) + (m = (m << 1) | (1 & t)), + v == o - 1 ? ((v = 0), d.push(n(m)), (m = 0)) : v++, + (t >>= 1); + } + 0 == --l && ((l = Math.pow(2, h)), h++), delete u[c]; + } else + for (t = s[c], e = 0; e < h; e++) + (m = (m << 1) | (1 & t)), + v == o - 1 ? ((v = 0), d.push(n(m)), (m = 0)) : v++, + (t >>= 1); + 0 == --l && ((l = Math.pow(2, h)), h++); + } + for (t = 2, e = 0; e < h; e++) + (m = (m << 1) | (1 & t)), + v == o - 1 ? ((v = 0), d.push(n(m)), (m = 0)) : v++, + (t >>= 1); + for (;;) { + if (((m <<= 1), v == o - 1)) { + d.push(n(m)); + break; + } + v++; + } + return d.join(""); + }, + decompress: function (r) { + return null == r + ? "" + : "" == r + ? null + : i._decompress(r.length, 32768, function (o) { + return r.charCodeAt(o); + }); + }, + _decompress: function (o, n, e) { + var t, + i, + s, + u, + a, + p, + c, + l = [], + f = 4, + h = 4, + d = 3, + m = "", + v = [], + g = { val: e(0), position: n, index: 1 }; + for (t = 0; t < 3; t += 1) l[t] = t; + for (s = 0, a = Math.pow(2, 2), p = 1; p != a; ) + (u = g.val & g.position), + (g.position >>= 1), + 0 == g.position && ((g.position = n), (g.val = e(g.index++))), + (s |= (u > 0 ? 1 : 0) * p), + (p <<= 1); + switch (s) { + case 0: + for (s = 0, a = Math.pow(2, 8), p = 1; p != a; ) + (u = g.val & g.position), + (g.position >>= 1), + 0 == g.position && ((g.position = n), (g.val = e(g.index++))), + (s |= (u > 0 ? 1 : 0) * p), + (p <<= 1); + c = r(s); + break; + case 1: + for (s = 0, a = Math.pow(2, 16), p = 1; p != a; ) + (u = g.val & g.position), + (g.position >>= 1), + 0 == g.position && ((g.position = n), (g.val = e(g.index++))), + (s |= (u > 0 ? 1 : 0) * p), + (p <<= 1); + c = r(s); + break; + case 2: + return ""; + } + for (l[3] = c, i = c, v.push(c); ; ) { + if (g.index > o) return ""; + for (s = 0, a = Math.pow(2, d), p = 1; p != a; ) + (u = g.val & g.position), + (g.position >>= 1), + 0 == g.position && ((g.position = n), (g.val = e(g.index++))), + (s |= (u > 0 ? 1 : 0) * p), + (p <<= 1); + switch ((c = s)) { + case 0: + for (s = 0, a = Math.pow(2, 8), p = 1; p != a; ) + (u = g.val & g.position), + (g.position >>= 1), + 0 == g.position && + ((g.position = n), (g.val = e(g.index++))), + (s |= (u > 0 ? 1 : 0) * p), + (p <<= 1); + (l[h++] = r(s)), (c = h - 1), f--; + break; + case 1: + for (s = 0, a = Math.pow(2, 16), p = 1; p != a; ) + (u = g.val & g.position), + (g.position >>= 1), + 0 == g.position && + ((g.position = n), (g.val = e(g.index++))), + (s |= (u > 0 ? 1 : 0) * p), + (p <<= 1); + (l[h++] = r(s)), (c = h - 1), f--; + break; + case 2: + return v.join(""); + } + if ((0 == f && ((f = Math.pow(2, d)), d++), l[c])) m = l[c]; + else { + if (c !== h) return null; + m = i + i.charAt(0); + } + v.push(m), + (l[h++] = i + m.charAt(0)), + (i = m), + 0 == --f && ((f = Math.pow(2, d)), d++); + } + }, + }; + return i; +})(); +"function" == typeof define && define.amd + ? define(function () { + return LZString; + }) + : "undefined" != typeof module && null != module + ? (module.exports = LZString) + : "undefined" != typeof angular && + null != angular && + angular.module("LZString", []).factory("LZString", function () { + return LZString; + }); diff --git a/static/js/modules/crypto/paillier.js b/static/js/modules/crypto/paillier.js index fead085..07aa1c8 100644 --- a/static/js/modules/crypto/paillier.js +++ b/static/js/modules/crypto/paillier.js @@ -4,6 +4,25 @@ import { gcd, mod_exp } from "./math.js"; const PAILLIER = 0; const JURIK = 1; +function RSTransform(g, a, p) { + let plainText = p.toString(16); + if (plainText.length % 2 !== 0) { + plainText = "0" + plainText; + } + + let aStr = a.toString(16); + if (aStr.length % 2 !== 0) { + aStr = "0" + aStr; + } + + let hasher = new jsSHA("SHAKE256", "HEX"); + hasher.update(g.toString(16)); + hasher.update(plainText); + hasher.update(aStr); + + return BigInt("0x" + hasher.getHash("HEX", { outputLen: 2048 })); +} + class Ciphertext { constructor(key, plainText, r, set) { if (set !== undefined) { @@ -93,28 +112,12 @@ class Ciphertext { } let a = mod_exp(rp, this.pubKey.n, this.pubKey.n2); - let hasher = new jsSHA("SHAKE256", "HEX"); - let plainText = this.plainText.toString(16); - if (plainText.length % 2 !== 0) { - plainText = "0" + plainText; - } - - let aStr = a.toString(16); - if (aStr.length % 2 !== 0) { - aStr = "0" + aStr; - } - - hasher.update(this.pubKey.g.toString(16)); - hasher.update(plainText); - hasher.update(aStr); - - let challenge = BigInt("0x" + hasher.getHash("HEX", { outputLen: 2048 })); + let challenge = RSTransform(this.pubKey.g, a, this.plainText); return { plainText: "0x" + this.plainText.toString(16), a: "0x" + a.toString(16), - challenge: "0x" + challenge.toString(16), proof: "0x" + ( @@ -195,11 +198,17 @@ export class ReadOnlyCiphertext { } verifyNI(statement) { + let challenge = RSTransform( + this.pubKey.g, + BigInt(statement.a), + BigInt(statement.plainText) + ); + let verifier = new ValueProofSessionVerifier( this, BigInt(statement.plainText), BigInt(statement.a), - BigInt(statement.challenge) + challenge ); if (verifier.verify(BigInt(statement.proof))) { diff --git a/static/js/modules/crypto/random_primes.js b/static/js/modules/crypto/random_primes.js index 93628b5..e970f94 100644 --- a/static/js/modules/crypto/random_primes.js +++ b/static/js/modules/crypto/random_primes.js @@ -1,6 +1,6 @@ import { mod_exp } from "./math.js"; -export const KEY_SIZE = 512; +export const KEY_SIZE = 2048; export function cryptoRandom(bits) { if (bits === undefined) { diff --git a/static/js/modules/interface/proofs.js b/static/js/modules/interface/proofs.js index 196810d..0667bfb 100644 --- a/static/js/modules/interface/proofs.js +++ b/static/js/modules/interface/proofs.js @@ -135,7 +135,6 @@ export function verifyRegions(obj, key) { let plaintext = c.verifyNI(verification.zeroProofs[regionName]); if (plaintext !== 0n) { - console.log(plaintext); return false; } } diff --git a/templates/index.html b/templates/index.html index f80a0bd..11e00b3 100644 --- a/templates/index.html +++ b/templates/index.html @@ -7,7 +7,8 @@ - + + @@ -188,6 +189,8 @@ function RSABench() { console.log("Warming up") + const ROUNDS = 250; + for (let i = 0n; i < 100n; i++) { window.rsa.pubKey.encrypt(i); } @@ -195,17 +198,19 @@ console.log("Benching") performance.mark("rsa-start") - for (let i = 0n; i < 250n; i++) { + for (let i = 0n; i < BigInt(ROUNDS); i++) { window.rsa.pubKey.encrypt(i); } performance.mark("rsa-end") - console.log(`Bench done. Duration: ${performance.measure("rsa-duration", "rsa-start", "rsa-end").duration}`) + console.log(`Bench done. Time per encrypt: ${performance.measure("rsa-duration", "rsa-start", "rsa-end").duration / ROUNDS}`) } function PaillierBench() { console.log("Warming up") + const ROUNDS = 250 + for (let i = 0n; i < 100n; i++) { window.paillier.pubKey.encrypt(i); } @@ -213,12 +218,162 @@ console.log("Benching") performance.mark("paillier-start") - for (let i = 0n; i < 250n; i++) { + for (let i = 0n; i < BigInt(ROUNDS); i++) { window.paillier.pubKey.encrypt(i); } performance.mark("paillier-end") - console.log(`Bench done. Duration: ${performance.measure("paillier-duration", "paillier-start", "paillier-end").duration}`) + console.log(`Bench done. Time per encrypt: ${performance.measure("paillier-duration", "paillier-start", "paillier-end").duration / ROUNDS}`) + } + + function ZeroProofBench() { + console.log("Warming up") + + const cipherText = paillier.pubKey.encrypt(0n) + const ROUNDS = 20; + + for (let i = 0; i < 5; i++) { + cipherText.proveNI(); + } + + console.log("Benching") + + performance.mark("paillier-start") + for (let i = 0; i < ROUNDS; i++) { + cipherText.proveNI() + } + performance.mark("paillier-end") + + console.log(`Bench done. Time per proof: ${performance.measure("paillier-duration", "paillier-start", "paillier-end").duration / ROUNDS}`) + } + + function ZeroProofVerifierBench() { + console.log("Warming up") + const ROUNDS = 20 + + const cipherText = paillier.pubKey.encrypt(1n) + const readOnly = cipherText.asReadOnlyCiphertext(); + const proof = cipherText.proveNI() + + for (let i = 0; i < 5; i++) { + readOnly.verifyNI(proof) + } + + console.log("Benching") + + performance.mark("paillier-start") + for (let i = 0; i < ROUNDS; i++) { + readOnly.verifyNI(proof) + } + performance.mark("paillier-end") + + console.log(`Bench done. Time per verification: ${performance.measure("paillier-duration", "paillier-start", "paillier-end").duration / ROUNDS}`) + } + + function Protocol4Bench() { + console.log("Warming up") + + const regions = { + A: paillier.pubKey.encrypt(0n), + B: paillier.pubKey.encrypt(1n), + C: paillier.pubKey.encrypt(0n), + D: paillier.pubKey.encrypt(0n), + E: paillier.pubKey.encrypt(0n) + } + const ROUNDS = 20; + + for (let i = 0; i < 5; i++) { + proveRegions(regions) + } + + console.log("Benching") + + performance.mark("paillier-start") + for (let i = 0; i < ROUNDS; i++) { + proveRegions(regions) + } + performance.mark("paillier-end") + + console.log(`Bench done. Time per proof: ${performance.measure("paillier-duration", "paillier-start", "paillier-end").duration / ROUNDS}`) + } + + function Protocol4VerifierBench() { + console.log("Warming up") + + const ROUNDS = 20; + const regions = { + A: paillier.pubKey.encrypt(0n), + B: paillier.pubKey.encrypt(1n), + C: paillier.pubKey.encrypt(0n), + D: paillier.pubKey.encrypt(0n), + E: paillier.pubKey.encrypt(0n) + } + let proof = proveRegions(regions) + + for (let i = 0; i < 5; i++) { + verifyRegions(proof, paillier.pubKey) + } + + console.log("Benching") + + performance.mark("paillier-start") + for (let i = 0; i < ROUNDS; i++) { + verifyRegions(proof, paillier.pubKey) + } + performance.mark("paillier-end") + + console.log(`Bench done. Time per verification: ${performance.measure("paillier-duration", "paillier-start", "paillier-end").duration / ROUNDS}`) + } + + // https://gist.github.com/kawanet/352a2ed1d1656816b2bc + function string_to_buffer(src) { + return (new Uint16Array([].map.call(src, function(c) { + return c.charCodeAt(0) + }))).buffer; + } + + function Protocol4Size() { + const regions = { + A: paillier.pubKey.encrypt(0n), + B: paillier.pubKey.encrypt(1n), + C: paillier.pubKey.encrypt(0n), + D: paillier.pubKey.encrypt(0n), + E: paillier.pubKey.encrypt(0n) + } + let ROUNDS = 10; + + let size = 0; + let compressedSize = 0; + + for (let x = 0; x < ROUNDS; x++) { + let s = JSON.stringify(proveRegions(regions)); + size += string_to_buffer(s).byteLength; + compressedSize += LZString.compressToUint8Array(s).length; + } + + return { + size: size / ROUNDS, + compressedSize: compressedSize / ROUNDS + }; + } + + function ZeroProofSize() { + const ROUNDS = 100; + const cipherText = paillier.pubKey.encrypt(0n) + + let size = 0; + let compressedSize = 0; + + for (let x = 0; x < ROUNDS; x++) { + let s = JSON.stringify(cipherText.proveNI()); + size += string_to_buffer(s).byteLength; + compressedSize += LZString.compressToUint8Array(s).length; + } + + return { + size: size / ROUNDS, + compressedSize: compressedSize / ROUNDS + }; } diff --git a/whitepaper/Dissertation.pdf b/whitepaper/Dissertation.pdf index 9de8332..8408d83 100644 Binary files a/whitepaper/Dissertation.pdf and b/whitepaper/Dissertation.pdf differ diff --git a/whitepaper/Dissertation.tex b/whitepaper/Dissertation.tex index a2b04f7..da84116 100644 --- a/whitepaper/Dissertation.tex +++ b/whitepaper/Dissertation.tex @@ -8,6 +8,11 @@ \usepackage{amsthm} \usepackage{tikz} \usepackage{minted} +\usepackage{multirow} +\usepackage{tabularx} +\usepackage{booktabs} +\usepackage{ragged2e} +\usepackage[alph]{parnotes} \DeclareMathOperator{\lcm}{lcm} \DeclareMathOperator{\id}{id} @@ -598,18 +603,52 @@ The other proofs do not translate so trivially to this structure however. In fac \subsection{Complexity results} -All measurements taken on Brave 1.50.114 (Chromium 112.0.5615.49) 64-bit, using a Ryzen 5 3600 CPU. +All measurements taken on Brave 1.50.114 (Chromium 112.0.5615.49) 64-bit, using a Ryzen 5 3600 CPU. Absolute timings are extremely dependent on the browser engine: for example Firefox 111.0.1 was typically 4 times slower than the results shown. -\begin{center} - \begin{tabular}{|c|c|c|c|} - \hline - Modulus size & Na\"ive encrypt & Jacobi encrypt & RSA encrypt \\\hline +\begin{table}[htp] + \fontsize{10pt}{10pt}\selectfont + \caption{Time to encrypt} + \begin{tabularx}{\textwidth}{c *3{>{\Centering}X}} + \toprule + Modulus size & Na\"ive encrypt & Jacobi encrypt & RSA encrypt \\ + \midrule $|n| = 1024$ & 6ms & 4ms & <1ms \\ $|n| = 2048$ & 34ms & 22ms & <1ms \\ $|n| = 4096$ & 189ms & 128ms & <1ms \\ - \hline - \end{tabular} -\end{center} + \bottomrule + \end{tabularx} +\end{table} + +\begin{table}[htp] + \fontsize{10pt}{10pt}\selectfont + \caption{Time to process proofs} + \begin{tabularx}{\textwidth}{c *4{>{\Centering}X}} + \toprule + \multirow{2}{*}{Modulus size} & \multicolumn{2}{c}{Proof-of-zero non-interactive} & \multicolumn{2}{c}{\hyperref[protocol1]{Protocol~\ref*{protocol1}} with $t = 24$} \tabularnewline \cmidrule(l){2-3}\cmidrule(l){4-5} + & Prover & Verifier & Prover & Verifier \\ + \midrule + $|n| = 1024$ & 10ms & 18ms & 1,740ms & 2,190ms \\ + $|n| = 2048$ & 44ms & 68ms & 8,170ms & 8,421ms \\ + $|n| = 4096$ & 225ms & 292ms & 41,500ms & 34,405ms \\ + \bottomrule + \end{tabularx} +\end{table} + +\begin{table}[htp] + \fontsize{10pt}{10pt}\selectfont + \caption{Byte size\parnote{1 UTF-16 character, as used by ECMAScript, is 2 or more bytes} of encoded proofs} + \begin{tabularx}{\textwidth}{c *4{>{\Centering}X}} + \toprule + \multirow{2}{*}{Modulus size} & \multicolumn{2}{c}{Proof-of-zero non-interactive} & \multicolumn{2}{c}{\hyperref[protocol1]{Protocol~\ref*{protocol1}} with $t = 24$} \tabularnewline \cmidrule(l){2-3}\cmidrule(l){4-5} + & JSON & with LZ-String & JSON & with LZ-String \\ + \midrule + $|n| = 1024$ & 1,617B & 576B (35.62\%) & 338,902B & 95,738B (28.25\%) \\ + $|n| = 2048$ & 3,153B & 1,050B (33.30\%) & 662,233B & 187,333B (28.29\%) \\ + $|n| = 4096$ & 6,226B & 1,999B (32.11\%) & 1,315,027B & 368,646B (28.03\%) \\ + \bottomrule + \end{tabularx} + \parnotes +\end{table} \subsection{Quantum resistance}