diff --git a/Makefile b/Makefile index de46d56..4342e07 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ LIBDIR := lib +MD_PREPROCESSOR := ./fix-sub.py include $(LIBDIR)/main.mk $(LIBDIR)/main.mk: diff --git a/draft-savage-ppm-3phm-mpc.md b/draft-savage-ppm-3phm-mpc.md index b90623f..b336911 100644 --- a/draft-savage-ppm-3phm-mpc.md +++ b/draft-savage-ppm-3phm-mpc.md @@ -174,9 +174,9 @@ method can be used, but the following process is typical: ~~~ pseudocode -x₁ = random() -x₂ = random() -x₃ = x - x₁ - x₂ +x_1 = random() +x_2 = random() +x_3 = x - x_1 - x_2 ~~~ Then, each party in the MPC receives a different set of two values. This @@ -434,7 +434,7 @@ Since the two verifiers possess all of this information distributed amongst them requires O(logN) communication, for proving expressions of the form: ~~~ pseudocode -sum(i=0..n-1, ui · vi) = t +sum(i=0..n, ui · vi) = t ~~~ In the setting where the Prover (P\=) and the left verifier @@ -468,59 +468,59 @@ P\-, the other being known to both P\= and P+. Rearranging terms: ~~~ pseudocode -x\- ∧ y+ ⊕ (x\- ∧ y\- ⊕ z\- ⊕ r\- ) ⊕ x\+ ∧ y\- ⊕ r+ = 0 +x- ∧ y+ ⊕ (x- ∧ y- ⊕ z- ⊕ r- ) ⊕ x+ ∧ y- ⊕ r+ = 0 ~~~ Define: ~~~ pseudocode -e\- = x\- ∧ y\- ⊕ z\- ⊕ r\- +e- = x- ∧ y- ⊕ z- ⊕ r- ~~~ Then: ~~~ pseudocode -(x\- ∧ y+ ⊕ e\- ) ⊕ (x\+ ∧ y\- ⊕ r+) = 0 +(x- ∧ y+ ⊕ e- ) ⊕ (x+ ∧ y- ⊕ r+) = 0 ~~~ -Using: x ⊕ y = x\*(1 - 2\*y) + y +Using: `x ⊕ y = x*(1 - 2*y) + y` ~~~ pseudocode -(x\-·y+·(1 - 2e\-) + e\-) ⊕ (x+·y\-·(1 - 2r+) + r+) = 0 +(x-·y+·(1 - 2e-) + e-) ⊕ (x+·y-·(1 - 2r+) + r+) = 0 ~~~ -Using: x ⊕ y = x + y - 2\*x\*y +Using: x ⊕ y = x + y - 2*x*y ~~~ pseudocode -(x\-·y+·(1 - 2e\-) + e\-) -\+ (x+·y\-·(1 - 2r+) + r+) -\- 2(x\-·y+·(1 - 2e\-) + e\-)(x+·y\-·(1 - 2r+) + r+) = 0 +(x-·y+·(1 - 2e-) + e-) ++ (x+·y-·(1 - 2r+) + r+) +- 2(x-·y+·(1 - 2e-) + e-)(x+·y-·(1 - 2r+) + r+) = 0 ~~~ Distributing Terms: ~~~ pseudocode -x\-·(1 - 2e\-)·y+ + e\- -\+ y\-·x+·(1 - 2r+) + r+ -\- 2x\-·y\-·(1 - 2e\-)·y+·x+·(1 - 2r+) - 2x\-·(1 - 2e\-)·y+·r+ - 2e\-·y\-·x+·(1 - 2r+) - 2e\-·r+ = 0 +x-·(1 - 2e-)·y+ + e- ++ y-·x+·(1 - 2r+) + r+ +- 2x-·y-·(1 - 2e-)·y+·x+·(1 - 2r+) - 2x-·(1 - 2e-)·y+·r+ - 2e-·y-·x+·(1 - 2r+) - 2e-·r+ = 0 ~~~ Rearranging terms, and subtracting 1/2 from both sides: ~~~ pseudocode -\- 2x\-·y\-·(1 - 2e\-)·y+·x+·(1 - 2r+) -\+ y\-·x+·(1 - 2r+) - 2e\-·y\-·x+·(1 - 2r+) -\+ x\-·(1 - 2e\-)·y+ - 2x\-·(1 - 2e\-)·y+·r+ -\+ e\- - 2e\-·r+ + r+ - ½ = - ½ +- 2x-·y-·(1 - 2e-)·y+·x+·(1 - 2r+) ++ y-·x+·(1 - 2r+) - 2e-·y-·x+·(1 - 2r+) ++ x-·(1 - 2e-)·y+ - 2x-·(1 - 2e-)·y+·r+ ++ e- - 2e-·r+ + r+ - ½ = - ½ ~~~ Factoring allows this to be written as an expression with four terms, each with a component taken from the left (which we will label g) and a component from the right (which we will label h): ~~~ pseudocode -\[-2x\-·y\-·(1 - 2e\-)\] · \[y+·x+·(1 - 2r+)\] -\+ \[y\-(1 - 2e\-)\] · \[x+·(1 - 2r+)\] -\+ \[x\-·(1 - 2e\-)\] · \[y+(1 - 2r+)\] -\+ \[-½(1 - 2e\-)\] · \[(1 - 2r+)\] = -½ +[-2x-·y-·(1 - 2e-)] · [y+·x+·(1 - 2r+)] ++ [y-(1 - 2e-)] · [x+·(1 - 2r+)] ++ [x-·(1 - 2e-)] · [y+(1 - 2r+)] ++ [-½(1 - 2e-)] · [(1 - 2r+)] = -½ ~~~ Renaming terms as new variables, the result is the dot product of two four dimensional vectors, g and h: @@ -535,7 +535,7 @@ Or alternatively: sum(i=1..4, gi · hi) = -½ ~~~ -Where P\= and P+ both compute `hi` as follows: +Where P= and P+ both compute `hi` as follows: ~~~ pseudocode h1 = y+·x+·(1 - 2·r+) @@ -544,34 +544,34 @@ h3 = y+·(1 - 2·r+) h4 = 1 − 2·r+ ~~~ -And P\= and P\- both compute gi as follows: +And P= and P- both compute gi as follows: ~~~ pseudocode -g1 = -2·x\-·y\-·(1 - 2·e\- ) -g2 = y\-·(1 - 2·e\- ) -g3 = x\-·(1 - 2·e\- ) -g4 = -½(1 - 2·e\-) +g1 = -2·x-·y-·(1 - 2·e- ) +g2 = y-·(1 - 2·e- ) +g3 = x-·(1 - 2·e- ) +g4 = -½(1 - 2·e-) ~~~ And where: ~~~ pseudocode -e\- = x\- ∧ y\- ⊕ z\- ⊕ r\- +e- = x- ∧ y- ⊕ z- ⊕ r- ~~~ In this field, the negative inverse of two (-½) is 1,152,921,504,606,846,975. ## Validating a batch of multiplications {#initial-uv} -Each multiplication therefore produces two vectors of length 4. To validate a batch of m multiplications, the Prover (P\=), uses this approach to produce two vectors of length 4m. +Each multiplication therefore produces two vectors of length 4. To validate a batch of m multiplications, the Prover (P=), uses this approach to produce two vectors of length 4m. -The Prover P\= and verifier P\- both compute the vector u +The Prover P= and verifier P- both compute the vector u ~~~ pseudocode u = (g1(1), g2(1), g3(1), g4(1), …, g1(m), g2(m), g3(m), g4(m)) ~~~ -The Prover P\= and verifier P+ both compute the vector v +The Prover P= and verifier P+ both compute the vector v ~~~ pseudocode v = (h1(1), h2(1), h3(1), h4(1), …, h1(m), h2(m), h3(m), h4(m)) @@ -617,14 +617,14 @@ At each iteration: minimal number required to uniquely define it. 2. These `2L - 1` points are split into two additive secret-shares - `G(x)\-` and `G(x)+` and sent to the verifiers - P\- and P+, respectively. These shares form the + `G(x)-` and `G(x)+` and sent to the verifiers + P- and P+, respectively. These shares form the distributed zero-knowledge proof. 3. The verifiers verify the proposition using their shares by computing - `b\- = t\- - sum(i=0..L-1, G(x)\-)` and + `b- = t- - sum(i=0..L-1, G(x)-)` and `b+ = t+ - sum(i=0..L-1, G(x)+)`. They - send each other the value they compute and confirm that `b\- + + send each other the value they compute and confirm that `b- + b+ = 0`. If this test fails, the entire protocol is aborted. 4. At this point, the prover could have produced values for `G(0..L-1)` that @@ -673,14 +673,14 @@ lookup tables if necessary. ### Producing Polynomials `p(x)` and `q(x)` -The prover (P\=) and the verifier P\-, chunk the vector +The prover (P=) and the verifier P-, chunk the vector `u` into `s` chunks of length `L`. ~~~ pseudocode -chunk 0: 0, u1, …, uL-1\> -chunk 1: L, uL+1, …, u2L-1\> +chunk 0: 0, u1, …, uL-1> +chunk 1: L, uL+1, …, u2L-1> … -chunk s-1: (s-1)L, u(s-1)L+1, …, usL-1\> +chunk s-1: (s-1)L, u(s-1)L+1, …, usL-1> ~~~ If the length of `u` is not divisible by `L`, then the final chunk will be @@ -692,21 +692,20 @@ fewer chunks. They will interpret each chunk (i) as L points lying on a polynomial, pi(x) of degree L - 1, corresponding to the x coordinates 0, 1, …, L-1, that is to say they will interpret them as pi(0), pi(1), …, pi(L-1). -The Prover (P\=) and verifier (P\-) can find the value of pi(x) for any other value of x by using Lagrange interpolation. +The Prover (P\=) and verifier (P-) can find the value of pi(x) for any other value of x by using Lagrange interpolation. The Prover will use Lagrange interpolation to compute the value of pi(L), pi(L+1), …, pi(2L-2). The same process is applied for the vector v. -The Prover (P\=) and the verifier P+, will chunk the vector v into s chunks of length L. - -chunk 1: 0, v1, …, vL-1\> - -chunk 2: L, vL+1, …, v2L-1\> +The Prover (P=) and the verifier P+, will chunk the vector v into s chunks of length L. +~~~ pseudocode +chunk 0: 0, v1, …, vL-1> +chunk 1: L, vL+1, …, v2L-1> … - -chunk s-1: (s-1)L, v(s-1)L+1, …, vsL-1\> +chunk s-1: (s-1)L, v(s-1)L+1, …, vsL-1> +~~~ As before, if the length of v is not a multiple of L, the final chunk will be padded with zeros. @@ -730,11 +729,11 @@ An equivalent method of proving u · v = t, is to show that sum(i=0..L-1, G(i)) ### Masking the zero-knowledge proof -The Prover (P\=), cannot simply send this zero-knowledge proof to the verifiers, as doing so would release private information. Instead, the prover can produce a two-part additive secret-sharing of these 2L - 1 points, sending one share to each verifier. +The Prover (P=), cannot simply send this zero-knowledge proof to the verifiers, as doing so would release private information. Instead, the prover can produce a two-part additive secret-sharing of these 2L - 1 points, sending one share to each verifier. -The Prover (P\=) and the right verifier (P+) will generate one share using their shared randomness. We will denote this share G(x)+. This requires no communication. +The Prover (P=) and the right verifier (P+) will generate one share using their shared randomness. We will denote this share G(x)+. This requires no communication. -The Prover (P\=) will compute the other share via subtraction, and will send it to the left verifier (P\-). Transmitting this share G(x)\-, will require sending 2L - 1 field values, which will require 8 bytes per field value as we are using Mersenne prime 261\-1 for our prime field. +The Prover (P=) will compute the other share via subtraction, and will send it to the left verifier (P-). Transmitting this share G(x)-, will require sending 2L - 1 field values, which will require 8 bytes per field value as we are using Mersenne prime 261-1 for our prime field. ### Checking that the proof says the right thing @@ -742,17 +741,17 @@ To check that: sum(i=0..L-1, G(i)) = t -The left verifier P\- will compute: +The left verifier P- will compute: -b\- = t\- - sum(i=0..L-1, G(i)\-) +b- = t- - sum(i=0..L-1, G(i)-) The right verifier P+ will compute: b+ = t+ - sum(i=0..L-1, G(i)+) -The two verifiers will reveal these values b\- and b+ to one another, so that each can reconstruct the full sum: +The two verifiers will reveal these values b- and b+ to one another, so that each can reconstruct the full sum: -b = b\- + b+ +b = b- + b+ They will confirm that b = 0. If it does not, the parties abort and destroy their shares. @@ -771,19 +770,19 @@ To minimize the rounds of communication, instead of having the verifiers select this random point, we utilize the Fiat-Shamir transformation to produce a constant-round proof system. -The Prover (P\=) will hash the zero-knowledge proof shares it has generated onto a field element as follows: +The Prover (P=) will hash the zero-knowledge proof shares it has generated onto a field element as follows: ~~~ pseudocode commitment = SHA256( concat( - SHA256(\[G(x)\]\-), - SHA256(\[G(x)\]+) + SHA256([G(x)]-), + SHA256([G(x)]+) ) ) -r = (bytes2int(commitment\[..16\]) % (prime - L)) + L +r = (bytes2int(commitment[..16]) % (prime - L)) + L ~~~ -This computation does not use the entire output of the hash function, just enough to ensure that the value of r has minimal bias. For SHA-256 and a prime field modulo 261\-1, the bias is in the order of 2\-67, which is negligible. +This computation does not use the entire output of the hash function, just enough to ensure that the value of r has minimal bias. For SHA-256 and a prime field modulo 261-1, the bias is in the order of 2-67, which is negligible. The verifiers generate the same point r independently. Each verifier only has access to one set of shares from G(x) so they each compute a hash of the shares they have. They then send that hash to each other, after which they can compute the full hash value. @@ -820,11 +819,11 @@ Note that this is a problem of exactly the same form as the original problem, except that the length of u’ and v’ is now a factor of L shorter than the original length of u and v. -The Prover (P\=) and verifier P\- use Lagrange +The Prover (P=) and verifier P- use Lagrange interpolation to compute the value of pi(r) for all (i) in the range 0..s and set this as the new vector u’. -Similarly, the Prover (P\=) and verifier P+ use Lagrange +Similarly, the Prover (P=) and verifier P+ use Lagrange interpolation to compute the value of qi(r) for all (i) in the range 0..s and set this as the new vector v’. diff --git a/fix-sub.py b/fix-sub.py new file mode 100755 index 0000000..e876d4e --- /dev/null +++ b/fix-sub.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 + +import fileinput +import re + +chars = "-+=()0123456789i" +subtr = "₋₊₌₍₎₀₁₂₃₄₅₆₇₈₉ᵢ" +suptr = "⁻⁺⁼⁽⁾⁰¹²³⁴⁵⁶⁷⁸⁹ⁱ" + +pseudocode = re.compile(r"^(~~~~*) *pseudocode$") +sub = re.compile(r"(?:([" + chars + r"]+)|(?<=\w)_([" + chars + r"]))") +sup = re.compile(r"(?:([" + chars + r"]+)|(?<=\w)\^([" + chars + r"]))") + +def tr(line, pattern, target): + result = "" + lastend = 0 + for m in pattern.finditer(line): + result += line[lastend:m.start()] + for c in (m[1] or m[2]): + i = chars.find(c) + result += target[i] + lastend = m.end() + result += line[lastend:] + return result + +end = None +code = False +for line in fileinput.input(): + if end is None: + m = pseudocode.match(line) + if m: + end = m[1] + else: + # TODO Look for `code` instead + pass + else: + if line.strip() != end: + line = tr(line, sub, subtr) + line = tr(line, sup, suptr) + else: + end = None + + print(line, end="")