def prove_low_degree(values, root_of_unity, maxdeg_plus_1, modulus, exclude_multiples_of=0): f = PrimeField(modulus) print('Proving %d values are degree <= %d' % (len(values), maxdeg_plus_1)) # If the degree we are checking for is less than or equal to 32, # use the polynomial directly as a proof if maxdeg_plus_1 <= 16: print('Produced FRI proof') return [[x.to_bytes(32, 'big') for x in values]] # Calculate the set of x coordinates xs = get_power_cycle(root_of_unity, modulus) assert len(values) == len(xs) # Put the values into a Merkle tree. This is the root that the # proof will be checked against m = merkelize(values) # Select a pseudo-random x coordinate special_x = int.from_bytes(m[1], 'big') % modulus # 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 each row, and not # directly from the polynomial, as this is more efficient 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 ys = get_pseudorandom_indices(m2[1], len(column), 40, exclude_multiples_of=exclude_multiples_of) # Compute the Merkle branches for the values in the polynomial and the column branches = [] for y in ys: branches.append( [mk_branch(m2, y)] + [mk_branch(m, y + (len(xs) // 4) * j) for j in range(4)]) # This component of the proof o = [m2[1], branches] # Recurse... return [o] + prove_low_degree(column, f.exp(root_of_unity, 4), maxdeg_plus_1 // 4, modulus, exclude_multiples_of=exclude_multiples_of)
def prove_low_degree(poly, root_of_unity, values, maxdeg_plus_1): print('Proving %d values are degree <= %d' % (len(values), maxdeg_plus_1)) # If the degree we are checking for is less than or equal to 32, # use the polynomial directly as a proof if maxdeg_plus_1 <= 32: print('Produced FRI proof') return [[x.to_bytes(32, 'big') for x in values]] # Calculate the set of x coordinates xs = get_power_cycle(root_of_unity) # Put the values into a Merkle tree. This is the root that the # proof will be checked against m = merkelize(values) # Select a pseudo-random x coordinate special_x = int.from_bytes(m[1], 'big') % modulus # Calculate the "column" (see https://vitalik.ca/general/2017/11/22/starks_part_2.html) # at that x coordinate # We calculate the column by Lagrange-interpolating the row, and not # directly, as this is more efficient column = [] for i in range(len(xs) // 4): x_poly = f.lagrange_interp( [values[i + len(values) * j // 4] for j in range(4)], [xs[i + len(xs) * j // 4] for j in range(4)]) column.append(f.eval_poly_at(x_poly, special_x)) m2 = merkelize(column) # Pseudo-randomly select y indices to sample ys = get_indices(m2[1], len(column), 40) # Compute the Merkle branches for the values in the polynomial and the column branches = [] for y in ys: branches.append( [mk_branch(m2, y)] + [mk_branch(m, y + (len(xs) // 4) * j) for j in range(4)]) # This component of the proof o = [m2[1], branches] # In the next iteration of the proof, we'll work with smaller roots of unity sub_xs = [xs[i] for i in range(0, len(xs), 4)] # Interpolate the polynomial for the column ypoly = fft(column[:len(sub_xs)], modulus, pow(root_of_unity, 4, modulus), inv=True) # Recurse... return [o] + prove_low_degree(ypoly, pow(root_of_unity, 4, modulus), column, maxdeg_plus_1 // 4)
def mk_mimc_proof(inp, logsteps, logprecision): start_time = time.time() assert logsteps < logprecision <= 32 steps = 2**logsteps precision = 2**logprecision # Root of unity such that x^precision=1 root = pow(7, (modulus-1)//precision, modulus) # Root of unity such that x^skips=1 skips = precision // steps subroot = pow(root, skips) # Powers of the root of unity, our computational trace will be # along the sequence of roots of unity xs = get_power_cycle(subroot) # Generate the computational trace constants = [] values = [inp] k = 1 for i in range(steps-1): values.append((values[-1]**3 + (k ^ 1)) % modulus) constants.append(k ^ 1) k = (k * 9) & ((1 << 256) - 1) constants.append(0) print('Done generating computational trace') # Interpolate the computational trace into a polynomial values_polynomial = fft(values, modulus, subroot, inv=True) constants_polynomial = fft(constants, modulus, subroot, inv=True) print('Converted computational steps and constants into a polynomial') # Create the composed polynomial such that # C(P(x), P(rx), K(x)) = P(rx) - P(x)**3 - K(x) term1 = multiply_base(values_polynomial, subroot) p_evaluations = fft(values_polynomial, modulus, root) term2 = fft([pow(x, 3, modulus) for x in p_evaluations], modulus, root, inv=True)[:len(values_polynomial) * 3 - 2] c_of_values = f.sub_polys(f.sub_polys(term1, term2), constants_polynomial) print('Computed C(P, K) polynomial') # Compute D(x) = C(P(x), P(rx), K(x)) / Z(x) # Z(x) = (x^steps - 1) / (x - x_atlast_step) d = divide_by_xnm1(f.mul_polys(c_of_values, [modulus-xs[steps-1], 1]), steps) # assert f.mul_polys(d, z) == c_of_values print('Computed D polynomial') # Evaluate D and K across the entire subgroup d_evaluations = fft(d, modulus, root) k_evaluations = fft(constants_polynomial, modulus, root) print('Evaluated P, D and K') # Compute their Merkle roots p_mtree = merkelize(p_evaluations) d_mtree = merkelize(d_evaluations) k_mtree = merkelize(k_evaluations) print('Computed hash root') # Based on the hashes of P and D, we select a random linear combination # of P * x^steps and D, and prove the low-degreeness of that, instead of proving # the low-degreeness of P and D separately k = int.from_bytes(blake(p_mtree[1] + d_mtree[1]), 'big') lincomb = f.add_polys(d, f.mul_by_const([0] * steps + values_polynomial, k)) l_evaluations = fft(lincomb, modulus, root) l_mtree = merkelize(l_evaluations) print('Computed random linear combination') # Do some spot checks of the Merkle tree at pseudo-random coordinates branches = [] samples = spot_check_security_factor // (logprecision - logsteps) positions = get_indices(l_mtree[1], precision - skips, samples) for pos in positions: branches.append(mk_branch(p_mtree, pos)) branches.append(mk_branch(p_mtree, pos + skips)) branches.append(mk_branch(d_mtree, pos)) branches.append(mk_branch(k_mtree, pos)) branches.append(mk_branch(l_mtree, pos)) print('Computed %d spot checks' % samples) # Return the Merkle roots of P and D, the spot check Merkle proofs, # and low-degree proofs of P and D o = [p_mtree[1], d_mtree[1], k_mtree[1], l_mtree[1], branches, prove_low_degree(lincomb, root, l_evaluations, steps * 2)] print("STARK computed in %.4f sec" % (time.time() - start_time)) return o
def mk_mimc_proof(inp, steps, round_constants): start_time = time.time() # Some constraints to make our job easier assert steps <= 2**32 // extension_factor assert is_a_power_of_2(steps) and is_a_power_of_2(len(round_constants)) assert len(round_constants) < steps precision = steps * extension_factor # Root of unity such that x^precision=1 G2 = f.exp(7, (modulus-1)//precision) # Root of unity such that x^steps=1 skips = precision // steps G1 = f.exp(G2, skips) # Powers of the higher-order root of unity xs = get_power_cycle(G2, modulus) last_step_position = xs[(steps-1)*extension_factor] # Generate the computational trace computational_trace = [inp] for i in range(steps-1): computational_trace.append( (computational_trace[-1]**3 + round_constants[i % len(round_constants)]) % modulus ) output = computational_trace[-1] print('Done generating computational trace') # Interpolate the computational trace into a polynomial P, with each step # along a successive power of G1 computational_trace_polynomial = fft(computational_trace, modulus, G1, inv=True) p_evaluations = fft(computational_trace_polynomial, modulus, G2) print('Converted computational steps into a polynomial and low-degree extended it') skips2 = steps // len(round_constants) constants_mini_polynomial = fft(round_constants, modulus, f.exp(G1, skips2), inv=True) constants_polynomial = [0 if i % skips2 else constants_mini_polynomial[i//skips2] for i in range(steps)] constants_mini_extension = fft(constants_mini_polynomial, modulus, f.exp(G2, skips2)) print('Converted round constants into a polynomial and low-degree extended it') # Create the composed polynomial such that # C(P(x), P(g1*x), K(x)) = P(g1*x) - P(x)**3 - K(x) c_of_p_evaluations = [(p_evaluations[(i+extension_factor)%precision] - f.exp(p_evaluations[i], 3) - constants_mini_extension[i % len(constants_mini_extension)]) % modulus for i in range(precision)] print('Computed C(P, K) polynomial') # Compute D(x) = C(P(x), P(g1*x), K(x)) / Z(x) # Z(x) = (x^steps - 1) / (x - x_atlast_step) z_num_evaluations = [xs[(i * steps) % precision] - 1 for i in range(precision)] z_num_inv = f.multi_inv(z_num_evaluations) z_den_evaluations = [xs[i] - last_step_position for i in range(precision)] d_evaluations = [cp * zd * zni % modulus for cp, zd, zni in zip(c_of_p_evaluations, z_den_evaluations, z_num_inv)] print('Computed D polynomial') # Compute interpolant of ((1, input), (x_atlast_step, output)) interpolant = f.lagrange_interp_2([1, last_step_position], [inp, output]) i_evaluations = [f.eval_poly_at(interpolant, x) for x in xs] zeropoly2 = f.mul_polys([-1, 1], [-last_step_position, 1]) inv_z2_evaluations = f.multi_inv([f.eval_poly_at(zeropoly2, x) for x in xs]) b_evaluations = [((p - i) * invq) % modulus for p, i, invq in zip(p_evaluations, i_evaluations, inv_z2_evaluations)] print('Computed B polynomial') # Compute their Merkle roots p_mtree = merkelize(p_evaluations) d_mtree = merkelize(d_evaluations) b_mtree = merkelize(b_evaluations) print('Computed hash root') # Based on the hashes of P, D and B, we select a random linear combination # of P * x^steps, P, B * x^steps, B and D, and prove the low-degreeness of that, # instead of proving the low-degreeness of P, B and D separately k1 = int.from_bytes(blake(p_mtree[1] + d_mtree[1] + b_mtree[1] + b'\x01'), 'big') k2 = int.from_bytes(blake(p_mtree[1] + d_mtree[1] + b_mtree[1] + b'\x02'), 'big') k3 = int.from_bytes(blake(p_mtree[1] + d_mtree[1] + b_mtree[1] + b'\x03'), 'big') k4 = int.from_bytes(blake(p_mtree[1] + d_mtree[1] + b_mtree[1] + b'\x04'), 'big') # Compute the linear combination. We don't even both calculating it in # coefficient form; we just compute the evaluations G2_to_the_steps = f.exp(G2, steps) powers = [1] for i in range(1, precision): powers.append(powers[-1] * G2_to_the_steps % modulus) l_evaluations = [(d_evaluations[i] + p_evaluations[i] * k1 + p_evaluations[i] * k2 * powers[i] + b_evaluations[i] * k3 + b_evaluations[i] * powers[i] * k4) % modulus for i in range(precision)] l_mtree = merkelize(l_evaluations) print('Computed random linear combination') # Do some spot checks of the Merkle tree at pseudo-random coordinates, excluding # multiples of `extension_factor` branches = [] samples = spot_check_security_factor positions = get_pseudorandom_indices(l_mtree[1], precision, samples, exclude_multiples_of=extension_factor) for pos in positions: branches.append(mk_branch(p_mtree, pos)) branches.append(mk_branch(p_mtree, (pos + skips) % precision)) branches.append(mk_branch(d_mtree, pos)) branches.append(mk_branch(b_mtree, pos)) branches.append(mk_branch(l_mtree, pos)) print('Computed %d spot checks' % samples) # Return the Merkle roots of P and D, the spot check Merkle proofs, # and low-degree proofs of P and D o = [p_mtree[1], d_mtree[1], b_mtree[1], l_mtree[1], branches, prove_low_degree(l_evaluations, G2, steps * 2, modulus, exclude_multiples_of=extension_factor)] print("STARK computed in %.4f sec" % (time.time() - start_time)) return o
def test_merkletree(): t = merkelize(range(128)) b = mk_branch(t, 59) assert verify_branch(t[1], 59, b) == 59 print('Merkle tree works')
def mk_mimc_proof(inp, logsteps, logprecision): start_time = time.time() assert logsteps < logprecision <= 32 steps = 2**logsteps precision = 2**logprecision # Root of unity such that x^precision=1 root = pow(7, (modulus - 1) // precision, modulus) # Root of unity such that x^skips=1 skips = precision // steps subroot = pow(root, skips) # Powers of the root of unity, our computational trace will be # along the sequence of roots of unity xs = get_power_cycle(subroot) # Generate the computational trace values = [inp] for i in range(steps - 1): values.append((values[-1]**3 + xs[i]) % modulus) print('Done generating computational trace') # Interpolate the computational trace into a polynomial # values_polynomial = f.lagrange_interp(values, [pow(subroot, i, modulus) for i in range(steps)]) values_polynomial = fft(values, modulus, subroot, inv=True) print('Computed polynomial') #for x, v in zip(xs, values): # assert f.eval_poly_at(values_polynomial, x) == v # Create the composed polynomial such that # C(P(x), P(rx)) = P(rx) - P(x)**3 - x term1 = multiply_base(values_polynomial, subroot) term2 = fft( [pow(x, 3, modulus) for x in fft(values_polynomial, modulus, root)], modulus, root, inv=True)[:len(values_polynomial) * 3 - 2] c_of_values = f.sub_polys(f.sub_polys(term1, term2), [0, 1]) print('Computed C(P) polynomial') # Compute D(x) = C(P(x)) / Z(x) # Z(x) = (x^steps - 1) / (x - x_atlast_step) d = divide_by_xnm1(f.mul_polys(c_of_values, [modulus - xs[steps - 1], 1]), steps) # assert f.mul_polys(d, z) == c_of_values print('Computed D polynomial') # Evaluate P and D across the entire subgroup p_evaluations = fft(values_polynomial, modulus, root) d_evaluations = fft(d, modulus, root) print('Evaluated P and D') # Compute their Merkle roots p_mtree = merkelize(p_evaluations) d_mtree = merkelize(d_evaluations) print('Computed hash root') # Do some spot checks of the Merkle tree at pseudo-random coordinates branches = [] samples = spot_check_security_factor // (logprecision - logsteps) positions = get_indices(blake(p_mtree[1] + d_mtree[1]), precision - skips, samples) for pos in positions: branches.append(mk_branch(p_mtree, pos)) branches.append(mk_branch(p_mtree, pos + skips)) branches.append(mk_branch(d_mtree, pos)) print('Computed %d spot checks' % samples) while len(d) < steps * 2: d += [0] # Return the Merkle roots of P and D, the spot check Merkle proofs, # and low-degree proofs of P and D o = [ p_mtree[1], d_mtree[1], branches, prove_low_degree(values_polynomial, root, p_evaluations, steps), prove_low_degree(d, root, d_evaluations, steps * 2) ] print("STARK computed in %.4f sec" % (time.time() - start_time)) return o
def test_merkletree(): t = merkelize([x.to_bytes(32, 'big') for x in range(128)]) b = mk_branch(t, 59) assert verify_branch(t[1], 59, b, output_as_int=True) == 59 print('Merkle tree works')