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="")