diff --git a/mimc_stark/compression.py b/mimc_stark/compression.py index 838a04f..9b31b30 100644 --- a/mimc_stark/compression.py +++ b/mimc_stark/compression.py @@ -1,10 +1,12 @@ def compress_fri(prf): o = [] + oindex = {} def add_obj(x): - if x in o: - o.append(o.index(x).to_bytes(2, 'big')) + if x in oindex: + o.append(oindex[x].to_bytes(2, 'big')) else: o.append(x) + oindex[x] = len(o)-1 for root, yproofs in prf[:-1]: # print('Adding proof item, pos %d' % len(o)) @@ -54,11 +56,13 @@ def decompress_fri(proof): def compress_branches(branches): o = [] + oindex = {} def add_obj(x): - if x in o: - o.append(o.index(x).to_bytes(2, 'big')) + if x in oindex: + o.append(oindex[x].to_bytes(2, 'big')) else: o.append(x) + oindex[x] = len(o)-1 for branch in branches: for p in branch: diff --git a/mimc_stark/fft.py b/mimc_stark/fft.py index e2500c4..0aaba1c 100644 --- a/mimc_stark/fft.py +++ b/mimc_stark/fft.py @@ -1,13 +1,15 @@ def _simple_ft(vals, modulus, roots_of_unity): L = len(roots_of_unity) - o = [0 for _ in range(L)] + o = [] for i in range(L): + last = 0 for j in range(L): - o[i] += vals[j] * roots_of_unity[(i*j)%L] - return [x % modulus for x in o] + last += vals[j] * roots_of_unity[(i*j)%L] + o.append(last % modulus) + return o def _fft(vals, modulus, roots_of_unity): - if len(vals) == 1: + if len(vals) <= 1: return vals # return _simple_ft(vals, modulus, roots_of_unity) L = _fft(vals[::2], modulus, roots_of_unity[::2]) diff --git a/mimc_stark/fri.py b/mimc_stark/fri.py index 4cf5062..77df674 100644 --- a/mimc_stark/fri.py +++ b/mimc_stark/fri.py @@ -1,6 +1,5 @@ from merkle_tree import merkelize, mk_branch, verify_branch from utils import get_power_cycle, get_pseudorandom_indices -from fft import fft from poly_utils import PrimeField # Generate an FRI proof that the polynomial that has the specified @@ -33,15 +32,14 @@ def prove_low_degree(values, root_of_unity, maxdeg_plus_1, modulus, exclude_mult # Calculate the "column" at that x coordinate # (see https://vitalik.ca/general/2017/11/22/starks_part_2.html) - # We calculate the column by Lagrange-interpolating the row, and not + # We calculate the column by Lagrange-interpolating each row, and not # directly from the polynomial, as this is more efficient - column = [] - for i in range(len(xs)//4): - x_poly = f.lagrange_interp_4( - [xs[i+len(xs)*j//4] for j in range(4)], - [values[i+len(values)*j//4] for j in range(4)], - ) - column.append(f.eval_poly_at(x_poly, special_x)) + quarter_len = len(xs)//4 + x_polys = f.multi_interp_4( + [[xs[i+quarter_len*j] for j in range(4)] for i in range(quarter_len)], + [[values[i+quarter_len*j] for j in range(4)] for i in range(quarter_len)] + ) + column = [f.eval_quartic(p, special_x) for p in x_polys] m2 = merkelize(column) # Pseudo-randomly select y indices to sample @@ -56,11 +54,6 @@ def prove_low_degree(values, root_of_unity, maxdeg_plus_1, modulus, exclude_mult # This component of the proof o = [m2[1], branches] - # Interpolate the polynomial for the column - # sub_xs = [xs[i] for i in range(0, len(xs), 4)] - # ypoly = fft(column[:len(sub_xs)], modulus, - # f.exp(root_of_unity, 4), inv=True) - # Recurse... return [o] + prove_low_degree(column, f.exp(root_of_unity, 4), maxdeg_plus_1 // 4, modulus, exclude_multiples_of=exclude_multiples_of) @@ -94,24 +87,30 @@ def verify_low_degree_proof(merkle_root, root_of_unity, proof, maxdeg_plus_1, mo ys = get_pseudorandom_indices(root2, roudeg // 4, 40, exclude_multiples_of=exclude_multiples_of) - # Verify for each selected y coordinate that the four points from the - # polynomial and the one point from the column that are on that y - # coordinate are on the same deg < 4 polynomial + # For each y coordinate, get the x coordinates on the row, the values on + # the row, and the value at that y from the column + xcoords = [] + rows = [] + columnvals = [] for i, y in enumerate(ys): # The x coordinates from the polynomial x1 = f.exp(root_of_unity, y) - xcoords = [(quartic_roots_of_unity[j] * x1) % modulus for j in range(4)] + xcoords.append([(quartic_roots_of_unity[j] * x1) % modulus for j in range(4)]) - # The values from the polynomial + # The values from the original polynomial row = [verify_branch(merkle_root, y + (roudeg // 4) * j, prf) for j, prf in zip(range(4), branches[i][1:])] + rows.append(row) - # Verify proof and recover the column value - values = [verify_branch(root2, y, branches[i][0])] + row + columnvals.append(verify_branch(root2, y, branches[i][0])) - # Lagrange interpolate and check deg is < 4 - p = f.lagrange_interp_4(xcoords, row) - assert f.eval_poly_at(p, special_x) == verify_branch(root2, y, branches[i][0]) + # Verify for each selected y coordinate that the four points from the + # polynomial and the one point from the column that are on that y + # coordinate are on the same deg < 4 polynomial + polys = f.multi_interp_4(xcoords, rows) + + for p, c in zip(polys, columnvals): + assert f.eval_quartic(p, special_x) == c # Update constants to check the next proof merkle_root = root2 diff --git a/mimc_stark/poly_utils.py b/mimc_stark/poly_utils.py index 61cd853..ac1dffb 100644 --- a/mimc_stark/poly_utils.py +++ b/mimc_stark/poly_utils.py @@ -126,6 +126,12 @@ class PrimeField(): b[j] += nums[i][j] * yslice return [x % self.modulus for x in b] + # Optimized poly evaluation for degree 4 + def eval_quartic(self, p, x): + xsq = x * x % self.modulus + xcb = xsq * x + return (p[0] + p[1] * x + p[2] * xsq + p[3] * xcb) % self.modulus + # Optimized version of the above restricted to deg-4 polynomials def lagrange_interp_4(self, xs, ys): x01, x02, x03, x12, x13, x23 = \ @@ -159,3 +165,33 @@ class PrimeField(): inv_y0 = ys[0] * invall * e1 inv_y1 = ys[1] * invall * e0 return [(eq0[i] * inv_y0 + eq1[i] * inv_y1) % m for i in range(2)] + + # Optimized version of the above restricted to deg-4 polynomials + def multi_interp_4(self, xsets, ysets): + data = [] + invtargets = [] + for xs, ys in zip(xsets, ysets): + x01, x02, x03, x12, x13, x23 = \ + xs[0] * xs[1], xs[0] * xs[2], xs[0] * xs[3], xs[1] * xs[2], xs[1] * xs[3], xs[2] * xs[3] + m = self.modulus + eq0 = [-x12 * xs[3] % m, (x12 + x13 + x23), -xs[1]-xs[2]-xs[3], 1] + eq1 = [-x02 * xs[3] % m, (x02 + x03 + x23), -xs[0]-xs[2]-xs[3], 1] + eq2 = [-x01 * xs[3] % m, (x01 + x03 + x13), -xs[0]-xs[1]-xs[3], 1] + eq3 = [-x01 * xs[2] % m, (x01 + x02 + x12), -xs[0]-xs[1]-xs[2], 1] + e0 = self.eval_quartic(eq0, xs[0]) + e1 = self.eval_quartic(eq1, xs[1]) + e2 = self.eval_quartic(eq2, xs[2]) + e3 = self.eval_quartic(eq3, xs[3]) + data.append([ys, eq0, eq1, eq2, eq3]) + invtargets.extend([e0, e1, e2, e3]) + invalls = self.multi_inv(invtargets) + o = [] + for (i, (ys, eq0, eq1, eq2, eq3)) in enumerate(data): + invallz = invalls[i*4:i*4+4] + inv_y0 = ys[0] * invallz[0] % m + inv_y1 = ys[1] * invallz[1] % m + inv_y2 = ys[2] * invallz[2] % m + inv_y3 = ys[3] * invallz[3] % m + o.append([(eq0[i] * inv_y0 + eq1[i] * inv_y1 + eq2[i] * inv_y2 + eq3[i] * inv_y3) % m for i in range(4)]) + # assert o == [self.lagrange_interp_4(xs, ys) for xs, ys in zip(xsets, ysets)] + return o diff --git a/randao_analysis/low_influence/2of3.py b/randao_analysis/low_influence/2of3.py new file mode 100644 index 0000000..e35507e --- /dev/null +++ b/randao_analysis/low_influence/2of3.py @@ -0,0 +1,61 @@ +# Implements the "iterated 2-of-3 majority" low-influence function +# from https://arxiv.org/pdf/1406.5694.pdf and outputs the probability +# that any specific user will be able to influence the result + +import random + +def mkbits(depth): + return random.randrange(2**(3**depth)) + +def winner(val, depth): + if depth == 0: + return val & 1 + subwinners = [ + winner(val, depth-1), + winner(val >> (3**(depth-1)), depth-1), + winner(val >> (3**(depth-1) * 2), depth-1) + ] + return 1 if sum(subwinners) >= 2 else 0 + +def is_marginal(val, depth): + if depth == 0: + return True + s1, s2, s3 = val, val >> (3**(depth-1)), val >> (3**(depth-1) * 2) + w1, w2, w3 = ( + winner(s1, depth-1), + winner(s2, depth-1), + winner(s3, depth-1) + ) + if w1 == w2 and w2 == w3: + return False + + dominants = (s2, s3) if (w1 != w2 and w1 != w3) else \ + (s1, s3) if (w2 != w1 and w2 != w3) else \ + (s1, s2) if (w3 != w1 and w3 != w2) else \ + False + + return is_marginal(dominants[0], depth-1) or \ + is_marginal(dominants[1], depth-1) + +def is_marginal2(val, depth): + w = winner(val, depth) + for x in range(3**depth): + val2 = val ^ (1 << x) + w2 = winner(val2, depth) + if w2 != w: + return True + return False + +def influence(val, depth): + tot = 0 + w = winner(val, depth) + for i in range(3**depth): + val2 = val ^ (1 << i) + w2 = winner(val2, depth) + if w != w2: + tot += 1 + return tot / (3**depth) + +bitz = [mkbits(3) for i in range(1000)] + +print(sum([influence(b, 3) for b in bitz]) / 1000) diff --git a/randao_analysis/low_influence/tribes.py b/randao_analysis/low_influence/tribes.py new file mode 100644 index 0000000..b5a11ae --- /dev/null +++ b/randao_analysis/low_influence/tribes.py @@ -0,0 +1,46 @@ +# Implements a modified version of the TRIBES low-influence function +# mentioned in https://arxiv.org/pdf/1406.5694.pdf and outputs the +# probability that any specific user will be able to influence the result + +import random, math + +def mkbits(n): + return random.randrange(2**n) + +def tribes_log(n): + w = 1 + while w * 2**w * 693 < n * 1000: + w += 1 + return w + +def tribes(val, n): + split = tribes_log(n) + o = [] + full_subset = (1 << split) - 1 + for i in range(n): + vall = val ^ ((2*i+3)**n % 2**n) + t = 0 + for _ in range(n // split): + if vall & full_subset == full_subset: + t = 1 + break + vall >>= split + o.append(t) + if len(o) % 2 == 0 and o[-2] == 0 and o[-1] == 1: + return 0 + if len(o) % 2 == 0 and o[-2] == 1 and o[-1] == 0: + return 1 + return o[-1] + +def influence(val, n): + tot = 0 + w = tribes(val, n) + for i in range(n): + val2 = val ^ (1 << i) + w2 = tribes(val2, n) + if w != w2: + tot += 1 + return tot / n + +print(sum([influence(mkbits(50), 50) for i in range(1000)]) / 1000) +# print(sum([tribes(mkbits(50), 50) for i in range(1000)]) / 1000)