From 407a8a9aad4c03b52382f6d5163aab858ee01443 Mon Sep 17 00:00:00 2001 From: Bharath Ramsundar Date: Sun, 17 Nov 2019 19:34:01 -0800 Subject: [PATCH 1/4] local --- starks/fri.py | 48 ++++++++++++++++++++++++++------------------ starks/poly_utils.py | 6 +++--- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/starks/fri.py b/starks/fri.py index 0a0396e..6a8a318 100644 --- a/starks/fri.py +++ b/starks/fri.py @@ -1,3 +1,4 @@ +import math from typing import List from starks.merkle_tree import merkelize from starks.merkle_tree import mk_branch @@ -10,42 +11,51 @@ from starks.numbertype import Field from starks.numbertype import Poly from starks.fft import NonBinaryFFT +from starks.reedsolomon import AffineSpace -class FRI(object): - """Abstract superclass for FRI implementations.""" +# TODO: This is a placeholder +def select_random(field: Field) -> FieldElement: + return field(0) - def __init__(self): - raise NotImplementedError - - def generate_proximity_proof(self, - f: Poly, - S, - security_factor:int = 40) -> List[bytes]: - raise NotImplementedError +def select_subspace(space: AffineSpace) -> AffineSpace: + field = space.field + basis = space.basis + sub_basis = basis[0:2] + return AffineSpace(field, sub_basis) - def verify_proximity_proof(self, - proof: List[bytes], - merkle_root: bytes, - S, - security_factor:int = 40) -> bool: - raise NotImplementedError +def eval_on_subspace(q: Poly, space: AffineSpace) -> AffineSpace: + # TODO: This is broken! + return space class AffineSubspaceFRI(object): """Implements Fast Reed Solomon IOPP for affine spaces.""" - def __init__(self, field, affine_space): + def __init__(self, field, affine_space, rho): self.field = field self.affine_space = affine_space + self.rho = rho def generate_proximity_proof(self, f: Poly, - S, security_factor:int = 40) -> List[bytes]: + R = -math.log(self.rho, 2) + eta = 2 # Perhaps an option to set in the constructor + k_0 = math.log(len(self.affine_space), 2) + r = int(math.floor((k_0 - R)/eta)) + # q_0 = Zero_(L_0)? + L_i = self.affine_space # L_0 is the affine space + L_i0 = select_subspace(L_i) + for i in range(r): + x_i = select_random(self.field) + # Convention: L^i_0 is the space generated by the first eta basis + q_i = construct_affine_vanishing_polynomial(L_i0) + L_i_plus_1 = eval_on_subspace(q_i, L_i) + # vectors of L^i raise NotImplementedError def verify_proximity_proof(self, proof: List[bytes], merkle_root: bytes, - S, + S: AffineSpace, security_factor:int = 40) -> bool: raise NotImplementedError diff --git a/starks/poly_utils.py b/starks/poly_utils.py index 911f090..97330ac 100644 --- a/starks/poly_utils.py +++ b/starks/poly_utils.py @@ -59,11 +59,11 @@ def draw_random_interpolant(degree, xs, ys): # TODO(rbharath): Need to implement this correctly return 0 -def construct_affine_vanishing_polynomial(field: Field, aff: AffineSpace) -> Poly: +def construct_affine_vanishing_polynomial(aff: AffineSpace) -> Poly: """Constructs a polynomial which vanishes over a given affine space.""" # TODO(rbharath): Need to implement this correctly. - aff_elts = [field(elt) for elt in aff] - return zpoly(field, aff_elts) + aff_elts = [aff.field(elt) for elt in aff] + return zpoly(aff.field, aff_elts) def is_irreducible(polynomial: Poly, p: int) -> bool: """is_irreducible: Polynomial, int -> bool From adf8c31c7bcecf52e2acc367b6d81724e8212fd8 Mon Sep 17 00:00:00 2001 From: Bharath Ramsundar Date: Sun, 17 Nov 2019 19:42:03 -0800 Subject: [PATCH 2/4] Add failing affine fri test case --- starks/stark.py | 451 ++++++++++++++++++++-------------------- starks/test/test_fri.py | 49 +++-- 2 files changed, 251 insertions(+), 249 deletions(-) diff --git a/starks/stark.py b/starks/stark.py index 9b54365..6629220 100644 --- a/starks/stark.py +++ b/starks/stark.py @@ -10,7 +10,6 @@ from starks.polynomial import polynomials_over from starks.poly_utils import lagrange_interp_2 from starks.fft import NonBinaryFFT -from starks.fri import FRI from starks.utils import generate_Xi_s from starks.utils import get_power_cycle from starks.utils import is_a_power_of_2 @@ -176,228 +175,228 @@ def compute_pseudorandom_linear_combination(entropy: bytes, trace_polys: List[Po print('Computed random linear combination') return l_joint_poly -class STARK(object): - """Generates and verifies STARKs - - TODO(rbharath): This should perhaps be split into STARKProver and - STARKVerifier for sanitation in a future PR. - """ - def __init__(self, field, steps: int, - extension_factor: int, width: int, step_polys: List[Poly], spot_check_security_factor: int =80): - """ - TODO(rbharath): I believe what this class is doing is - constructing a smooth multiplicative group. Alternatively, - this could be an affine space. - - Parameters - ---------- - field: Field - The Field in which computation is permored - steps: int - The number of steps in AIR - extension_factor: Int - A power of two which is the degree to which the trace is expanded - when constructing polynomials. For example, a trace of length 512 - with an extension_factor of 8 would construct polynomial evaluations - on a 4096 elements. - """ - self.field = field - self.width = width - self.steps = steps - self.step_polys = step_polys - self.extension_factor = extension_factor - self.precision = steps * extension_factor - self.spot_check_security_factor = spot_check_security_factor - - if self.field.p != 2: - modulus = self.field.p - # TODO(rbharath): Perhaps these should be roots of the primitive - # polynomials in the full-fledged starks. - # Root of unity such that x^precision=1 - self.G2 = field(7)**((modulus - 1) // self.precision) - - # Root of unity such that x^steps=1 - self.G1 = self.G2**extension_factor - - ## Powers of the higher-order root of unity - self.xs = get_power_cycle(self.G2, self.field) - self.last_step_position = self.xs[(steps - 1) * extension_factor] - self.fft_solver = NonBinaryFFT(self.field, self.G2) - else: - # We are in the case of a binary field - self.fft_solver = BinaryFFT(self.field) - - def get_degree(self): - return max([poly.degree() for poly in self.step_polys]) - - def mk_proof(self, witness: List[List[FieldElement]], boundary: List[Tuple]): - """Generate a STARK for a MIMC calculation""" - start_time = time.time() - - # list of length |width| - trace_polys = construct_trace_polynomials(witness, self.field, self.G1) - constraint_polys = construct_constraint_polynomials(self.step_polys, - trace_polys, self.field, self.G1, self.width) - remainder_polys = construct_remainder_polynomials(constraint_polys, self.field, - self.steps, self.last_step_position) - boundary_polys = construct_boundary_polynomials( - trace_polys, witness, boundary, self.field, - self.last_step_position, self.width) - - polys = trace_polys + remainder_polys + boundary_polys - # Compute their Merkle root - # TODO(rbharath): The merkelization is computed on the - # affine subspace of the RS[F, L, pho] I believe. - # Alternatively on a smooth multiplicative group, which is - # what's happening now. - poly_evals = [] - for poly in polys: - poly_eval = self.fft_solver.fft(poly) - poly_evals.append(poly_eval) - mtree = merkelize_polynomial_evaluations(self.width, poly_evals) - - l_poly = compute_pseudorandom_linear_combination( - mtree[1], trace_polys, remainder_polys, boundary_polys, self.field, - self.G2, self.precision, self.steps, self.width) - l_evaluations = self.fft_solver.fft(l_poly) - l_mtree = merkelize(l_evaluations) - - branches = self.compute_merkle_spot_checks(mtree, l_mtree) - - fri = FRI(self.field) - # Return the Merkle roots of P and D, the spot check Merkle - # proofs, and low-degree proofs of P and D - o = [ - mtree[1], l_mtree[1], branches, - fri.generate_proximity_proof( - l_poly, - self.G2, - self.steps*self.get_degree(), - exclude_multiples_of=self.extension_factor) - ] - print("STARK computed in %.4f sec" % (time.time() - start_time)) - return o - - def verify_proof(self, proof: List[bytes], witness, boundary): - """Verifies a STARK - - Parameters - ---------- - comp: AIR - An Algebraic Intermediate Representation - TODO(rbharath): This function should not see comp! This wouldn't be present - in the real protocol. - params: StarkParams - TODO(rbharath): Is this needed? - """ - start_time = time.time() - m_root, l_root, branches, fri_proof = proof - - # Verifies the low-degree proofs - fri = FRI(self.field) - assert fri.verify_proximity_proof( - fri_proof, - l_root, - self.G2, - # TODO(rbharath): Degree must be in parameters - self.steps * self.get_degree(), - exclude_multiples_of=self.extension_factor) - - ## Performs the spot checks - samples = self.spot_check_security_factor - positions = get_pseudorandom_indices( - l_root, self.precision, samples, - exclude_multiples_of=self.extension_factor) - ks = get_pseudorandom_ks(m_root, 4) - for i, pos in enumerate(positions): - self.verify_proof_at_position(witness, boundary, ks, proof, i, pos) - - print('Verified %d consistency checks' % self.spot_check_security_factor) - print('Verified STARK in %.4f sec' % (time.time() - start_time)) - return True - - def verify_proof_at_position(self, witness, boundary, ks, proof, i, pos): - """Verifies merkle proof at given position in extended trace""" - field = self.field - width = self.width - polysOver = polynomials_over(field).factory - k1, k2, k3, k4 = ks - m_root, l_root, branches, fri_proof = proof - x = self.G2**pos - x_to_the_steps = x**self.steps - # Recall m is the merkle tree of the raw polynomials, and l - # is the merkle tree of the pseudorandom combination - # polynomial. Leaf node from m[pos] - mbranch1 = verify_branch(m_root, pos, branches[i * 3]) - unpacked_leaf1 = unpack_merkle_leaf(mbranch1, width, 3) - # Leaf node from m[pos + extension_factor] - mbranch2 = verify_branch( - m_root, - (pos + self.extension_factor) % self.precision, - branches[i * 3 + 1]) - unpacked_leaf2 = unpack_merkle_leaf(mbranch2, width, 3) - # Leaf node from l[pos] - l_of_x = verify_branch(l_root, pos, branches[i * 3 + 2], - output_as_int=True) - - # This undoes the packing that's done in merkelize_polynomials - p_of_x = [field(p_of_x_dim) for p_of_x_dim in unpacked_leaf1[:width]] - p_of_g1x = [field(p_of_g1x_dim) for p_of_g1x_dim in unpacked_leaf2[:width]] - d_of_x = [field(d_of_x_dim) for d_of_x_dim in unpacked_leaf1[width:2*width]] - b_of_x = [field(b_of_x_dim) for b_of_x_dim in unpacked_leaf1[2*width:]] - - zvalue = (x**self.steps - 1)/(x - self.last_step_position) - k_of_xs = [] - - # Check transition constraints C(P(x)) = Z(x) * D(x) - f_of_p_of_x = [self.step_polys[i](p_of_x) for i in range(width)] - for dim in range(width): - p_of_g1x_dim = p_of_g1x[dim] - p_of_x_dim = p_of_x[dim] - d_of_x_dim = d_of_x[dim] - f_of_p_of_x_dim = f_of_p_of_x[dim] - assert (p_of_g1x_dim - f_of_p_of_x_dim - zvalue * d_of_x_dim) == 0 - - # Check boundary constraints B(x) * Q(x) + I(x) = P(x) - # TODO(rbharath): How do I promote a single-dim poly into a multidimensional poly? - zeropoly2 = polysOver([-1, 1])*polysOver([-self.last_step_position, 1]) - for dim in range(width): - #interpolant_dim = lagrange_interp_2(modulus, [1, self.last_step_position], [comp.inp[dim], comp.output[dim]]) - # TODO(rbharath): Add output_dim extraction - constraint = boundary[dim] - (_, _, input_value) = constraint - # TODO(rbharath): Explicitly passing the witness here isn't optimal. Should the verifier have to use the witness? - output_dim = witness[dim][-1] - interpolant = lagrange_interp_2(field, [1, self.last_step_position], [input_value, output_dim]) - assert (p_of_x[dim] - b_of_x[dim] * zeropoly2(x) - interpolant(x)) == 0 - - # TODO(rbharath): I'm commenting this out for now, but I think commenting - # out this check breaks security guarantees!! To fix this, we need a way - # of getting the dimensionwise l's, which might necessitate passing more - # merkle branches into the original proof. Will refactor in a subsequent - # PR. - # Check correctness of the linear combination - #assert (l_of_x - d_of_x - k1 * p_of_x - k2 * p_of_x * x_to_the_steps - - # k3 * b_of_x - k4 * b_of_x * x_to_the_steps) % modulus == 0 - - - # TODO(rbharath): This method is poorly structured since it - # computes spot checks for both the mtree and the ltree - # simultaneously. This makes refactoring challenging. Break - # up and separate in future PR. - # TODO(rbharath): Was the goal of this to reduce the number - # of merkle branches needed? Is this implemented correctly? - def compute_merkle_spot_checks(self, mtree, l_mtree, samples=80): - """Computes pseudorandom spot checks of Merkle tree.""" - # Do some spot checks of the Merkle tree at pseudo-random - # coordinates, excluding multiples of `extension_factor` - branches = [] - positions = get_pseudorandom_indices( - l_mtree[1], self.precision, samples, exclude_multiples_of=self.extension_factor) - for pos in positions: - branches.append(mk_branch(mtree, pos)) - branches.append(mk_branch(mtree, (pos + self.extension_factor) % self.precision)) - branches.append(mk_branch(l_mtree, pos)) - print('Computed %d spot checks' % samples) - return branches - +#class STARK(object): +# """Generates and verifies STARKs +# +# TODO(rbharath): This should perhaps be split into STARKProver and +# STARKVerifier for sanitation in a future PR. +# """ +# def __init__(self, field, steps: int, +# extension_factor: int, width: int, step_polys: List[Poly], spot_check_security_factor: int =80): +# """ +# TODO(rbharath): I believe what this class is doing is +# constructing a smooth multiplicative group. Alternatively, +# this could be an affine space. +# +# Parameters +# ---------- +# field: Field +# The Field in which computation is permored +# steps: int +# The number of steps in AIR +# extension_factor: Int +# A power of two which is the degree to which the trace is expanded +# when constructing polynomials. For example, a trace of length 512 +# with an extension_factor of 8 would construct polynomial evaluations +# on a 4096 elements. +# """ +# self.field = field +# self.width = width +# self.steps = steps +# self.step_polys = step_polys +# self.extension_factor = extension_factor +# self.precision = steps * extension_factor +# self.spot_check_security_factor = spot_check_security_factor +# +# if self.field.p != 2: +# modulus = self.field.p +# # TODO(rbharath): Perhaps these should be roots of the primitive +# # polynomials in the full-fledged starks. +# # Root of unity such that x^precision=1 +# self.G2 = field(7)**((modulus - 1) // self.precision) +# +# # Root of unity such that x^steps=1 +# self.G1 = self.G2**extension_factor +# +# ## Powers of the higher-order root of unity +# self.xs = get_power_cycle(self.G2, self.field) +# self.last_step_position = self.xs[(steps - 1) * extension_factor] +# self.fft_solver = NonBinaryFFT(self.field, self.G2) +# else: +# # We are in the case of a binary field +# self.fft_solver = BinaryFFT(self.field) +# +# def get_degree(self): +# return max([poly.degree() for poly in self.step_polys]) +# +# def mk_proof(self, witness: List[List[FieldElement]], boundary: List[Tuple]): +# """Generate a STARK for a MIMC calculation""" +# start_time = time.time() +# +# # list of length |width| +# trace_polys = construct_trace_polynomials(witness, self.field, self.G1) +# constraint_polys = construct_constraint_polynomials(self.step_polys, +# trace_polys, self.field, self.G1, self.width) +# remainder_polys = construct_remainder_polynomials(constraint_polys, self.field, +# self.steps, self.last_step_position) +# boundary_polys = construct_boundary_polynomials( +# trace_polys, witness, boundary, self.field, +# self.last_step_position, self.width) +# +# polys = trace_polys + remainder_polys + boundary_polys +# # Compute their Merkle root +# # TODO(rbharath): The merkelization is computed on the +# # affine subspace of the RS[F, L, pho] I believe. +# # Alternatively on a smooth multiplicative group, which is +# # what's happening now. +# poly_evals = [] +# for poly in polys: +# poly_eval = self.fft_solver.fft(poly) +# poly_evals.append(poly_eval) +# mtree = merkelize_polynomial_evaluations(self.width, poly_evals) +# +# l_poly = compute_pseudorandom_linear_combination( +# mtree[1], trace_polys, remainder_polys, boundary_polys, self.field, +# self.G2, self.precision, self.steps, self.width) +# l_evaluations = self.fft_solver.fft(l_poly) +# l_mtree = merkelize(l_evaluations) +# +# branches = self.compute_merkle_spot_checks(mtree, l_mtree) +# +# fri = FRI(self.field) +# # Return the Merkle roots of P and D, the spot check Merkle +# # proofs, and low-degree proofs of P and D +# o = [ +# mtree[1], l_mtree[1], branches, +# fri.generate_proximity_proof( +# l_poly, +# self.G2, +# self.steps*self.get_degree(), +# exclude_multiples_of=self.extension_factor) +# ] +# print("STARK computed in %.4f sec" % (time.time() - start_time)) +# return o +# +# def verify_proof(self, proof: List[bytes], witness, boundary): +# """Verifies a STARK +# +# Parameters +# ---------- +# comp: AIR +# An Algebraic Intermediate Representation +# TODO(rbharath): This function should not see comp! This wouldn't be present +# in the real protocol. +# params: StarkParams +# TODO(rbharath): Is this needed? +# """ +# start_time = time.time() +# m_root, l_root, branches, fri_proof = proof +# +# # Verifies the low-degree proofs +# fri = FRI(self.field) +# assert fri.verify_proximity_proof( +# fri_proof, +# l_root, +# self.G2, +# # TODO(rbharath): Degree must be in parameters +# self.steps * self.get_degree(), +# exclude_multiples_of=self.extension_factor) +# +# ## Performs the spot checks +# samples = self.spot_check_security_factor +# positions = get_pseudorandom_indices( +# l_root, self.precision, samples, +# exclude_multiples_of=self.extension_factor) +# ks = get_pseudorandom_ks(m_root, 4) +# for i, pos in enumerate(positions): +# self.verify_proof_at_position(witness, boundary, ks, proof, i, pos) +# +# print('Verified %d consistency checks' % self.spot_check_security_factor) +# print('Verified STARK in %.4f sec' % (time.time() - start_time)) +# return True +# +# def verify_proof_at_position(self, witness, boundary, ks, proof, i, pos): +# """Verifies merkle proof at given position in extended trace""" +# field = self.field +# width = self.width +# polysOver = polynomials_over(field).factory +# k1, k2, k3, k4 = ks +# m_root, l_root, branches, fri_proof = proof +# x = self.G2**pos +# x_to_the_steps = x**self.steps +# # Recall m is the merkle tree of the raw polynomials, and l +# # is the merkle tree of the pseudorandom combination +# # polynomial. Leaf node from m[pos] +# mbranch1 = verify_branch(m_root, pos, branches[i * 3]) +# unpacked_leaf1 = unpack_merkle_leaf(mbranch1, width, 3) +# # Leaf node from m[pos + extension_factor] +# mbranch2 = verify_branch( +# m_root, +# (pos + self.extension_factor) % self.precision, +# branches[i * 3 + 1]) +# unpacked_leaf2 = unpack_merkle_leaf(mbranch2, width, 3) +# # Leaf node from l[pos] +# l_of_x = verify_branch(l_root, pos, branches[i * 3 + 2], +# output_as_int=True) +# +# # This undoes the packing that's done in merkelize_polynomials +# p_of_x = [field(p_of_x_dim) for p_of_x_dim in unpacked_leaf1[:width]] +# p_of_g1x = [field(p_of_g1x_dim) for p_of_g1x_dim in unpacked_leaf2[:width]] +# d_of_x = [field(d_of_x_dim) for d_of_x_dim in unpacked_leaf1[width:2*width]] +# b_of_x = [field(b_of_x_dim) for b_of_x_dim in unpacked_leaf1[2*width:]] +# +# zvalue = (x**self.steps - 1)/(x - self.last_step_position) +# k_of_xs = [] +# +# # Check transition constraints C(P(x)) = Z(x) * D(x) +# f_of_p_of_x = [self.step_polys[i](p_of_x) for i in range(width)] +# for dim in range(width): +# p_of_g1x_dim = p_of_g1x[dim] +# p_of_x_dim = p_of_x[dim] +# d_of_x_dim = d_of_x[dim] +# f_of_p_of_x_dim = f_of_p_of_x[dim] +# assert (p_of_g1x_dim - f_of_p_of_x_dim - zvalue * d_of_x_dim) == 0 +# +# # Check boundary constraints B(x) * Q(x) + I(x) = P(x) +# # TODO(rbharath): How do I promote a single-dim poly into a multidimensional poly? +# zeropoly2 = polysOver([-1, 1])*polysOver([-self.last_step_position, 1]) +# for dim in range(width): +# #interpolant_dim = lagrange_interp_2(modulus, [1, self.last_step_position], [comp.inp[dim], comp.output[dim]]) +# # TODO(rbharath): Add output_dim extraction +# constraint = boundary[dim] +# (_, _, input_value) = constraint +# # TODO(rbharath): Explicitly passing the witness here isn't optimal. Should the verifier have to use the witness? +# output_dim = witness[dim][-1] +# interpolant = lagrange_interp_2(field, [1, self.last_step_position], [input_value, output_dim]) +# assert (p_of_x[dim] - b_of_x[dim] * zeropoly2(x) - interpolant(x)) == 0 +# +# # TODO(rbharath): I'm commenting this out for now, but I think commenting +# # out this check breaks security guarantees!! To fix this, we need a way +# # of getting the dimensionwise l's, which might necessitate passing more +# # merkle branches into the original proof. Will refactor in a subsequent +# # PR. +# # Check correctness of the linear combination +# #assert (l_of_x - d_of_x - k1 * p_of_x - k2 * p_of_x * x_to_the_steps - +# # k3 * b_of_x - k4 * b_of_x * x_to_the_steps) % modulus == 0 +# +# +# # TODO(rbharath): This method is poorly structured since it +# # computes spot checks for both the mtree and the ltree +# # simultaneously. This makes refactoring challenging. Break +# # up and separate in future PR. +# # TODO(rbharath): Was the goal of this to reduce the number +# # of merkle branches needed? Is this implemented correctly? +# def compute_merkle_spot_checks(self, mtree, l_mtree, samples=80): +# """Computes pseudorandom spot checks of Merkle tree.""" +# # Do some spot checks of the Merkle tree at pseudo-random +# # coordinates, excluding multiples of `extension_factor` +# branches = [] +# positions = get_pseudorandom_indices( +# l_mtree[1], self.precision, samples, exclude_multiples_of=self.extension_factor) +# for pos in positions: +# branches.append(mk_branch(mtree, pos)) +# branches.append(mk_branch(mtree, (pos + self.extension_factor) % self.precision)) +# branches.append(mk_branch(l_mtree, pos)) +# print('Computed %d spot checks' % samples) +# return branches +# diff --git a/starks/test/test_fri.py b/starks/test/test_fri.py index 4a89864..23161a7 100644 --- a/starks/test/test_fri.py +++ b/starks/test/test_fri.py @@ -11,13 +11,13 @@ from starks.poly_utils import multivariates_over from starks.air import AIR from starks.stark import get_power_cycle -from starks.stark import STARK from starks.stark import construct_trace_polynomials from starks.stark import construct_constraint_polynomials from starks.stark import construct_remainder_polynomials from starks.stark import construct_boundary_polynomials from starks.stark import compute_pseudorandom_linear_combination from starks.finitefield import FiniteField +from starks.reedsolomon import AffineSpace class TestFRI(unittest.TestCase): @@ -45,28 +45,31 @@ def test_basic_prove(self): # The proof is a list of length one, whose first entry is just the evaluations converted to bytes assert len(proof[0]) == 8 -# def test_binary_fri_proof(self): -# """Test proof on low degree implementation""" -# # This finite field is of size 2^17 -# p = 2 -# m = 17 -# Zp = IntegersModP(p) -# polysOver = polynomials_over(Zp) -# field = FiniteField(p, m) -# #field = FiniteField(p, m) -# #x^17 + x^3 + 1 is primitive -# coefficients = [Zp(0)] * 18 -# coefficients[0] = Zp(1) -# coefficients[3] = Zp(1) -# coefficients[17] = Zp(1) -# poly = polysOver(coefficients) -# field = FiniteField(p, m, polynomialModulus=poly) -# polysOver = polynomials_over(field).factory -# -# # This is a low degree polynomial so we hit the special -# # case of the handler. -# S = None # TODO: What is the space? -# fri = AffineSubspaceFRI(field) + def test_binary_fri_proof(self): + """Test proof on low degree implementation""" + # This finite field is of size 2^17 + p = 2 + m = 17 + Zp = IntegersModP(p) + polysOver = polynomials_over(Zp) + field = FiniteField(p, m) + #field = FiniteField(p, m) + #x^17 + x^3 + 1 is primitive + coefficients = [Zp(0)] * 18 + coefficients[0] = Zp(1) + coefficients[3] = Zp(1) + coefficients[17] = Zp(1) + poly = polysOver(coefficients) + field = FiniteField(p, m, polynomialModulus=poly) + polysOver = polynomials_over(field).factory + + S = AffineSpace(field, [field(1)])# TODO: What is the space? + rho = 0.1 + fri = AffineSubspaceFRI(field, S, rho) + + fri_poly = polysOver([Zp(0), Zp(1)]) + fri.generate_proximity_proof(fri_poly) + # proof = fri.generate_proximity_proof(poly, S) # # The proof is a list of length one, whose first entry is just the evaluations converted to bytes # assert len(proof[0]) == 8 From f534874d554ac5726898b12c02034a4b50f95d23 Mon Sep 17 00:00:00 2001 From: Bharath Ramsundar Date: Mon, 18 Nov 2019 18:24:51 -0800 Subject: [PATCH 3/4] partial implementation of FRI commit --- starks/fri.py | 414 +++++++++++++++++++++------------------- starks/test/test_fri.py | 214 +++++++++++---------- 2 files changed, 325 insertions(+), 303 deletions(-) diff --git a/starks/fri.py b/starks/fri.py index 6a8a318..196f88f 100644 --- a/starks/fri.py +++ b/starks/fri.py @@ -7,6 +7,7 @@ from starks.utils import get_pseudorandom_indices from starks.poly_utils import lagrange_interp from starks.poly_utils import multi_interp_4 +from starks.poly_utils import construct_affine_vanishing_polynomial from starks.numbertype import FieldElement from starks.numbertype import Field from starks.numbertype import Poly @@ -24,8 +25,9 @@ def select_subspace(space: AffineSpace) -> AffineSpace: return AffineSpace(field, sub_basis) def eval_on_subspace(q: Poly, space: AffineSpace) -> AffineSpace: - # TODO: This is broken! - return space + # TODO(rbharath): Does this work as advertised? + eval_elts = set([q(elt) for elt in space]) + return eval_elts class AffineSubspaceFRI(object): """Implements Fast Reed Solomon IOPP for affine spaces.""" @@ -34,9 +36,7 @@ def __init__(self, field, affine_space, rho): self.affine_space = affine_space self.rho = rho - def generate_proximity_proof(self, - f: Poly, - security_factor:int = 40) -> List[bytes]: + def commit(self, f, security_factor:int = 40) -> List[bytes]: R = -math.log(self.rho, 2) eta = 2 # Perhaps an option to set in the constructor k_0 = math.log(len(self.affine_space), 2) @@ -44,13 +44,31 @@ def generate_proximity_proof(self, # q_0 = Zero_(L_0)? L_i = self.affine_space # L_0 is the affine space L_i0 = select_subspace(L_i) + f_i = f for i in range(r): + # TODO: This should be picked using a hash of some common data to apply the fiat-shamir heuristic x_i = select_random(self.field) # Convention: L^i_0 is the space generated by the first eta basis q_i = construct_affine_vanishing_polynomial(L_i0) L_i_plus_1 = eval_on_subspace(q_i, L_i) - # vectors of L^i - raise NotImplementedError + if i < r-1: + # define empty function + f_i_plus_1 = {} + for x in L_i: + coset = [x + l_i for l_i in L_i] + coset_eval = [f_i(x_c) for x_c in coset] + interp = lagrange_interp(self.field, coset_eval) + f_i_plus_1 = interp(x_i) + # Does this lexical capture work? + f_i = lambda z: f_i_plus_1[z] + L_i = L_i_plus_1 + else: + f_r = f_i + L_r = L_i + vals = [f_r(val) for val in self.affine_space] + interp = lagrange_interp(self.field, vals) + d = rho * len(L_r) - 1 + return interp.coefficients[:d+1] def verify_proximity_proof(self, proof: List[bytes], @@ -59,194 +77,194 @@ def verify_proximity_proof(self, security_factor:int = 40) -> bool: raise NotImplementedError -class SmoothSubgroupFRI(object): - """Implements Fast Reed Solomon Interactive Oracle Protocol - - This class implements the FRI for a smooth multiplicative - subgroup of a finite field. Recall that such a subgroup is - of size 2^n and is a subgroup of the cyclic group of nonzero - elements of the finite field. - - # TODO(rbharath): Swap this to work with a FourierPolynomial instead of a regular polynomial. Will be more efficient. - """ - def __init__(self, field): - self.field = field - - def generate_proximity_proof(self, - f: Poly, - root_of_unity: FieldElement, - maxdeg_plus_1: int, - exclude_multiples_of:int = 0, - fri_spot_check_security_factor:int = 40) -> List[bytes]: - """ - Generate an FRI proof that the polynomial that has the - specified values at successive powers of the specified root - of unity has a degree lower than maxdeg_plus_1 - - We use maxdeg+1 instead of maxdeg because it's more - mathematically convenient in this case. - - Note that if values is a n-degree polynomial, root_of_unity - should be a n-th root of unity. - """ - print("Generating proof. Degree %d" % maxdeg_plus_1) - fft_solver = NonBinaryFFT(self.field, root_of_unity) - values = fft_solver.fft(f) - # If the degree we are checking for is less than or equal - # to 32, use the polynomial directly as a proof - # TODO(rbharath): Why does this make sense? - if maxdeg_plus_1 <= 16: - print('Produced FRI proof') - return [[x.to_bytes() for x in values]] - - # Calculate the set of x coordinates - xs = get_power_cycle(root_of_unity, self.field) - - assert len(values) == len(xs) - - # Put the values into a Merkle tree. This is the root that - # the proof will be checked against. Note this is a list of - # length 2*len(values) storing the complete merkle tree. - m = merkelize(values) - - # Select a pseudo-random x coordinate - # This is the merkle-root of the polynomial. - #special_x = int.from_bytes(m[1], 'big') % modulus - special_x = self.field(m[1]) - - # 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 = multi_interp_4(self.field, - [[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 = [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), fri_spot_check_security_factor, 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)]) - - o = [m2[1], branches] - - # Recurse... - # Swapping the root of unity to get new polynomial - fft_solver = NonBinaryFFT(self.field, root_of_unity**4) - column_poly = fft_solver.inv_fft(column) - return [o] + self.generate_proximity_proof( - column_poly, - root_of_unity**4, - maxdeg_plus_1 // 4, - exclude_multiples_of=exclude_multiples_of) - - def verify_proximity_proof(self, - proof: List[bytes], - merkle_root: bytes, - root_of_unity: FieldElement, - maxdeg_plus_1: int, - exclude_multiples_of:int = 0, - fri_spot_check_security_factor:int = 40) -> bool: - """Verifies proximity of this function to this RS code.""" - # Calculate which root of unity we're working with - testval = root_of_unity - # roudeg is the power of the root of unity - roudeg = 1 - while testval != 1: - roudeg *= 2 - testval = (testval * testval) - - # Powers of the given root of unity 1, p, p**2, p**3 such that p**4 = 1 - quartic_roots_of_unity = [ - 1, - root_of_unity**(roudeg // 4), - root_of_unity**(roudeg // 2), - root_of_unity**(roudeg * 3 // 4) - ] - - # Verify the recursive components of the proof - for prf in proof[:-1]: - root2, branches = prf - print('Verifying degree <= %d' % maxdeg_plus_1) - - # Calculate the pseudo-random x coordinate - special_x = self.field(merkle_root) - - # Calculate the pseudo-randomly sampled y indices - ys = get_pseudorandom_indices( - root2, roudeg // 4, fri_spot_check_security_factor, exclude_multiples_of=exclude_multiples_of) - - # 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 = root_of_unity**y - xcoords.append( - [(quartic_roots_of_unity[j] * x1) for j in range(4)]) - - # The values from the original polynomial - row = [ - verify_branch( - merkle_root, y + (roudeg // 4) * j, prf, output_as_int=True) - for j, prf in zip(range(4), branches[i][1:]) - ] - rows.append(row) - - columnvals.append( - verify_branch(root2, y, branches[i][0], output_as_int=True)) - - # 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 = multi_interp_4(self.field, xcoords, rows) - - for p, c in zip(polys, columnvals): - assert p(special_x) == c - - # Update constants to check the next proof - merkle_root = root2 - root_of_unity = root_of_unity**4 - maxdeg_plus_1 //= 4 - roudeg //= 4 - - # Verify the direct components of the proof - data = [int.from_bytes(x, 'big') for x in proof[-1]] - print('Verifying degree <= %d' % maxdeg_plus_1) - assert maxdeg_plus_1 <= 16 - - # Check the Merkle root matches up - mtree = merkelize(data) - assert mtree[1] == merkle_root - - # Check the degree of the data - #powers = get_power_cycle(root_of_unity, modulus) - powers = get_power_cycle(root_of_unity, self.field) - if exclude_multiples_of: - pts = [x for x in range(len(data)) if x % exclude_multiples_of] - else: - pts = range(len(data)) - - poly = lagrange_interp(self.field, - [powers[x] for x in pts[:maxdeg_plus_1]], - [data[x] for x in pts[:maxdeg_plus_1]]) - for x in pts[maxdeg_plus_1:]: - assert poly(powers[x]) == data[x] - - print('FRI proof verified') - return True +#class SmoothSubgroupFRI(object): +# """Implements Fast Reed Solomon Interactive Oracle Protocol +# +# This class implements the FRI for a smooth multiplicative +# subgroup of a finite field. Recall that such a subgroup is +# of size 2^n and is a subgroup of the cyclic group of nonzero +# elements of the finite field. +# +# # TODO(rbharath): Swap this to work with a FourierPolynomial instead of a regular polynomial. Will be more efficient. +# """ +# def __init__(self, field): +# self.field = field +# +# def generate_proximity_proof(self, +# f: Poly, +# root_of_unity: FieldElement, +# maxdeg_plus_1: int, +# exclude_multiples_of:int = 0, +# fri_spot_check_security_factor:int = 40) -> List[bytes]: +# """ +# Generate an FRI proof that the polynomial that has the +# specified values at successive powers of the specified root +# of unity has a degree lower than maxdeg_plus_1 +# +# We use maxdeg+1 instead of maxdeg because it's more +# mathematically convenient in this case. +# +# Note that if values is a n-degree polynomial, root_of_unity +# should be a n-th root of unity. +# """ +# print("Generating proof. Degree %d" % maxdeg_plus_1) +# fft_solver = NonBinaryFFT(self.field, root_of_unity) +# values = fft_solver.fft(f) +# # If the degree we are checking for is less than or equal +# # to 32, use the polynomial directly as a proof +# # TODO(rbharath): Why does this make sense? +# if maxdeg_plus_1 <= 16: +# print('Produced FRI proof') +# return [[x.to_bytes() for x in values]] +# +# # Calculate the set of x coordinates +# xs = get_power_cycle(root_of_unity, self.field) +# +# assert len(values) == len(xs) +# +# # Put the values into a Merkle tree. This is the root that +# # the proof will be checked against. Note this is a list of +# # length 2*len(values) storing the complete merkle tree. +# m = merkelize(values) +# +# # Select a pseudo-random x coordinate +# # This is the merkle-root of the polynomial. +# #special_x = int.from_bytes(m[1], 'big') % modulus +# special_x = self.field(m[1]) +# +# # 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 = multi_interp_4(self.field, +# [[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 = [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), fri_spot_check_security_factor, 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)]) +# +# o = [m2[1], branches] +# +# # Recurse... +# # Swapping the root of unity to get new polynomial +# fft_solver = NonBinaryFFT(self.field, root_of_unity**4) +# column_poly = fft_solver.inv_fft(column) +# return [o] + self.generate_proximity_proof( +# column_poly, +# root_of_unity**4, +# maxdeg_plus_1 // 4, +# exclude_multiples_of=exclude_multiples_of) +# +# def verify_proximity_proof(self, +# proof: List[bytes], +# merkle_root: bytes, +# root_of_unity: FieldElement, +# maxdeg_plus_1: int, +# exclude_multiples_of:int = 0, +# fri_spot_check_security_factor:int = 40) -> bool: +# """Verifies proximity of this function to this RS code.""" +# # Calculate which root of unity we're working with +# testval = root_of_unity +# # roudeg is the power of the root of unity +# roudeg = 1 +# while testval != 1: +# roudeg *= 2 +# testval = (testval * testval) +# +# # Powers of the given root of unity 1, p, p**2, p**3 such that p**4 = 1 +# quartic_roots_of_unity = [ +# 1, +# root_of_unity**(roudeg // 4), +# root_of_unity**(roudeg // 2), +# root_of_unity**(roudeg * 3 // 4) +# ] +# +# # Verify the recursive components of the proof +# for prf in proof[:-1]: +# root2, branches = prf +# print('Verifying degree <= %d' % maxdeg_plus_1) +# +# # Calculate the pseudo-random x coordinate +# special_x = self.field(merkle_root) +# +# # Calculate the pseudo-randomly sampled y indices +# ys = get_pseudorandom_indices( +# root2, roudeg // 4, fri_spot_check_security_factor, exclude_multiples_of=exclude_multiples_of) +# +# # 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 = root_of_unity**y +# xcoords.append( +# [(quartic_roots_of_unity[j] * x1) for j in range(4)]) +# +# # The values from the original polynomial +# row = [ +# verify_branch( +# merkle_root, y + (roudeg // 4) * j, prf, output_as_int=True) +# for j, prf in zip(range(4), branches[i][1:]) +# ] +# rows.append(row) +# +# columnvals.append( +# verify_branch(root2, y, branches[i][0], output_as_int=True)) +# +# # 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 = multi_interp_4(self.field, xcoords, rows) +# +# for p, c in zip(polys, columnvals): +# assert p(special_x) == c +# +# # Update constants to check the next proof +# merkle_root = root2 +# root_of_unity = root_of_unity**4 +# maxdeg_plus_1 //= 4 +# roudeg //= 4 +# +# # Verify the direct components of the proof +# data = [int.from_bytes(x, 'big') for x in proof[-1]] +# print('Verifying degree <= %d' % maxdeg_plus_1) +# assert maxdeg_plus_1 <= 16 +# +# # Check the Merkle root matches up +# mtree = merkelize(data) +# assert mtree[1] == merkle_root +# +# # Check the degree of the data +# #powers = get_power_cycle(root_of_unity, modulus) +# powers = get_power_cycle(root_of_unity, self.field) +# if exclude_multiples_of: +# pts = [x for x in range(len(data)) if x % exclude_multiples_of] +# else: +# pts = range(len(data)) +# +# poly = lagrange_interp(self.field, +# [powers[x] for x in pts[:maxdeg_plus_1]], +# [data[x] for x in pts[:maxdeg_plus_1]]) +# for x in pts[maxdeg_plus_1:]: +# assert poly(powers[x]) == data[x] +# +# print('FRI proof verified') +# return True diff --git a/starks/test/test_fri.py b/starks/test/test_fri.py index 23161a7..8019986 100644 --- a/starks/test/test_fri.py +++ b/starks/test/test_fri.py @@ -1,6 +1,6 @@ import unittest from starks.fri import AffineSubspaceFRI -from starks.fri import SmoothSubgroupFRI +#from starks.fri import SmoothSubgroupFRI from starks.merkle_tree import merkelize from starks.merkle_tree import merkelize_polynomial_evaluations from starks.compression import bin_length @@ -25,25 +25,25 @@ class TestFRI(unittest.TestCase): Basic tests for FRI implementation. """ - def test_basic_prove(self): - """Test proof on low degree implementation""" - degree = 4 - modulus = 2**256 - 2**32 * 351 + 1 - field = IntegersModP(modulus) - polysOver = polynomials_over(field).factory - # 1 + x + 3x^2 + 4 x^3 mod 31 - poly = polysOver([val for val in range(degree)]) + #def test_basic_prove(self): + # """Test proof on low degree implementation""" + # degree = 4 + # modulus = 2**256 - 2**32 * 351 + 1 + # field = IntegersModP(modulus) + # polysOver = polynomials_over(field).factory + # # 1 + x + 3x^2 + 4 x^3 mod 31 + # poly = polysOver([val for val in range(degree)]) - # A root of unity is a number such that z^n = 1 - # This provides us a 6-th root of unity (z^6 = 1) - root_of_unity = field(7)**((modulus-1)//8) + # # A root of unity is a number such that z^n = 1 + # # This provides us a 6-th root of unity (z^6 = 1) + # root_of_unity = field(7)**((modulus-1)//8) - # This is a low degree polynomial so we hit the special - # case of the handler. - fri = SmoothSubgroupFRI(field) - proof = fri.generate_proximity_proof(poly, root_of_unity, degree) - # The proof is a list of length one, whose first entry is just the evaluations converted to bytes - assert len(proof[0]) == 8 + # # This is a low degree polynomial so we hit the special + # # case of the handler. + # fri = SmoothSubgroupFRI(field) + # proof = fri.generate_proximity_proof(poly, root_of_unity, degree) + # # The proof is a list of length one, whose first entry is just the evaluations converted to bytes + # assert len(proof[0]) == 8 def test_binary_fri_proof(self): """Test proof on low degree implementation""" @@ -52,15 +52,16 @@ def test_binary_fri_proof(self): m = 17 Zp = IntegersModP(p) polysOver = polynomials_over(Zp) - field = FiniteField(p, m) #field = FiniteField(p, m) - #x^17 + x^3 + 1 is primitive - coefficients = [Zp(0)] * 18 - coefficients[0] = Zp(1) - coefficients[3] = Zp(1) - coefficients[17] = Zp(1) - poly = polysOver(coefficients) - field = FiniteField(p, m, polynomialModulus=poly) + ##field = FiniteField(p, m) + ##x^17 + x^3 + 1 is primitive + #coefficients = [Zp(0)] * 18 + #coefficients[0] = Zp(1) + #coefficients[3] = Zp(1) + #coefficients[17] = Zp(1) + #poly = polysOver(coefficients) + #field = FiniteField(p, m, polynomialModulus=poly) + field = Zp polysOver = polynomials_over(field).factory S = AffineSpace(field, [field(1)])# TODO: What is the space? @@ -68,90 +69,93 @@ def test_binary_fri_proof(self): fri = AffineSubspaceFRI(field, S, rho) fri_poly = polysOver([Zp(0), Zp(1)]) - fri.generate_proximity_proof(fri_poly) + vals = fri.commit(fri_poly) + print("vals") + print(vals) + assert 0 == 1 # proof = fri.generate_proximity_proof(poly, S) # # The proof is a list of length one, whose first entry is just the evaluations converted to bytes # assert len(proof[0]) == 8 - def test_high_degree_prove(self): - """Tests proof generation on high degree polynomials""" - steps = 512 - modulus = 2**256 - 2**32 * 351 + 1 - field = IntegersModP(modulus) - polysOver = polynomials_over(field).factory - # Some round constants borrowed from MiMC - poly = polysOver([field((i**7) ^ 42) for i in range(steps)]) - # Root of unity such that x^steps=1 - root_of_unity = field(7)**((modulus-1)//steps) - # We're trying to prove this is a (steps-1)-degree - # polnomial - # degree = (steps-1) + 1 = steps - fri = SmoothSubgroupFRI(field) - degree = steps - proof = fri.generate_proximity_proof(poly, root_of_unity, degree) - # The proof recurses by dividing maxdeg_plus_1 by 4 - # So 512, 128, 32, 8. (The base case passes over to - # special handler for degree 16 or less so these are all - # recursions). - assert len(proof) == 4 - for i, rec_proof in enumerate(proof): - if i < 3: - # Each subproof is [merkle_root, branches] for all but - # base case. - assert len(rec_proof) == 2 - assert len(rec_proof[1]) == 40 - else: - # Here we trigger the base case. - assert len(rec_proof) == 8 - - def test_verify_low_degree_proof(self): - """Verify a low degree proof""" - dims = 1 - modulus = 31 - steps = 512 - degree = steps - modulus = 2**256 - 2**32 * 351 + 1 - field = IntegersModP(modulus) - polysOver = polynomials_over(field).factory - poly = polysOver([field((i**7) ^ 42) for i in range(steps)]) - # Root of unity such that x^steps=1 - root_of_unity = field(7)**((modulus - 1) // steps) - fri = SmoothSubgroupFRI(field) - proof = fri.generate_proximity_proof(poly, root_of_unity, degree) - - # TODO(rbharath): Should this be a method? - fft_solver = NonBinaryFFT(field, root_of_unity) - evaluations = fft_solver.fft(poly) - e_mtree = merkelize(evaluations) - mroot = e_mtree[1] - verification = fri.verify_proximity_proof(proof, mroot, root_of_unity, degree) - assert verification - - def test_fri(self): - """Pure FRI tests""" - #degree = 4096 - degree = 256 - modulus = 2**256 - 2**32 * 351 + 1 - field= IntegersModP(modulus) - polysOver = polynomials_over(field).factory - poly = polysOver([field(val) for val in range(degree)]) - root_of_unity = field(7)**((modulus - 1) // (degree*4)) - #evaluations = fft(poly, modulus, root_of_unity) - #evaluations = [val[0] for val in evaluations] - #proof = prove_low_degree(evaluations, root_of_unity, 4096, modulus) - fri = SmoothSubgroupFRI(field) - proof = fri.generate_proximity_proof(poly, root_of_unity, degree) - print("Approx proof length: %d" % bin_length(compress_fri(proof))) - #assert verify_low_degree_proof( - # merkelize(evaluations)[1], root_of_unity, proof, 4096, modulus) - - fft_solver = NonBinaryFFT(field, root_of_unity) - evaluations = fft_solver.fft(poly) - e_mtree = merkelize(evaluations) - mroot = e_mtree[1] - verification = fri.verify_proximity_proof(proof, mroot, root_of_unity, degree) - assert verification + #def test_high_degree_prove(self): + # """Tests proof generation on high degree polynomials""" + # steps = 512 + # modulus = 2**256 - 2**32 * 351 + 1 + # field = IntegersModP(modulus) + # polysOver = polynomials_over(field).factory + # # Some round constants borrowed from MiMC + # poly = polysOver([field((i**7) ^ 42) for i in range(steps)]) + # # Root of unity such that x^steps=1 + # root_of_unity = field(7)**((modulus-1)//steps) + # # We're trying to prove this is a (steps-1)-degree + # # polnomial + # # degree = (steps-1) + 1 = steps + # fri = SmoothSubgroupFRI(field) + # degree = steps + # proof = fri.generate_proximity_proof(poly, root_of_unity, degree) + # # The proof recurses by dividing maxdeg_plus_1 by 4 + # # So 512, 128, 32, 8. (The base case passes over to + # # special handler for degree 16 or less so these are all + # # recursions). + # assert len(proof) == 4 + # for i, rec_proof in enumerate(proof): + # if i < 3: + # # Each subproof is [merkle_root, branches] for all but + # # base case. + # assert len(rec_proof) == 2 + # assert len(rec_proof[1]) == 40 + # else: + # # Here we trigger the base case. + # assert len(rec_proof) == 8 + + #def test_verify_low_degree_proof(self): + # """Verify a low degree proof""" + # dims = 1 + # modulus = 31 + # steps = 512 + # degree = steps + # modulus = 2**256 - 2**32 * 351 + 1 + # field = IntegersModP(modulus) + # polysOver = polynomials_over(field).factory + # poly = polysOver([field((i**7) ^ 42) for i in range(steps)]) + # # Root of unity such that x^steps=1 + # root_of_unity = field(7)**((modulus - 1) // steps) + # fri = SmoothSubgroupFRI(field) + # proof = fri.generate_proximity_proof(poly, root_of_unity, degree) + + # # TODO(rbharath): Should this be a method? + # fft_solver = NonBinaryFFT(field, root_of_unity) + # evaluations = fft_solver.fft(poly) + # e_mtree = merkelize(evaluations) + # mroot = e_mtree[1] + # verification = fri.verify_proximity_proof(proof, mroot, root_of_unity, degree) + # assert verification + + #def test_fri(self): + # """Pure FRI tests""" + # #degree = 4096 + # degree = 256 + # modulus = 2**256 - 2**32 * 351 + 1 + # field= IntegersModP(modulus) + # polysOver = polynomials_over(field).factory + # poly = polysOver([field(val) for val in range(degree)]) + # root_of_unity = field(7)**((modulus - 1) // (degree*4)) + # #evaluations = fft(poly, modulus, root_of_unity) + # #evaluations = [val[0] for val in evaluations] + # #proof = prove_low_degree(evaluations, root_of_unity, 4096, modulus) + # fri = SmoothSubgroupFRI(field) + # proof = fri.generate_proximity_proof(poly, root_of_unity, degree) + # print("Approx proof length: %d" % bin_length(compress_fri(proof))) + # #assert verify_low_degree_proof( + # # merkelize(evaluations)[1], root_of_unity, proof, 4096, modulus) + + # fft_solver = NonBinaryFFT(field, root_of_unity) + # evaluations = fft_solver.fft(poly) + # e_mtree = merkelize(evaluations) + # mroot = e_mtree[1] + # verification = fri.verify_proximity_proof(proof, mroot, root_of_unity, degree) + # assert verification # TODO(rbharath): Make a good test for failure of high degree polynomials #fakedata = [ From 172f72112922581db23c5d9840398a6ecebb8bb0 Mon Sep 17 00:00:00 2001 From: Fattaneh Bayatbabolghani Date: Wed, 4 Dec 2019 13:07:13 -0800 Subject: [PATCH 4/4] Update fri.py updated commit and added query! --- starks/fri.py | 64 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 57 insertions(+), 7 deletions(-) diff --git a/starks/fri.py b/starks/fri.py index 196f88f..b906485 100644 --- a/starks/fri.py +++ b/starks/fri.py @@ -1,4 +1,7 @@ import math +import hashlib +from random import seed +from random import randint from typing import List from starks.merkle_tree import merkelize from starks.merkle_tree import mk_branch @@ -13,15 +16,30 @@ from starks.numbertype import Poly from starks.fft import NonBinaryFFT from starks.reedsolomon import AffineSpace +from starks.polynomial import polynomials_over # TODO: This is a placeholder def select_random(field: Field) -> FieldElement: - return field(0) + result = hashlib.sha384(x.encode()) + num = HextoBin(str(result)) + polysOver = polynomials_over(IntegersModP(field.p)) + return field(polysOver(num)) -def select_subspace(space: AffineSpace) -> AffineSpace: +def HextoBin(Hexnum): + num = int(Hexnum, 16) + bStr = '' + while num > 0: + bStr = str(num % 2) + bStr + num = num >> 1 + l_num = [] + for i in range(len(bStr)): + l_num.append(bStr[i]) + return l_num + +def select_subspace(space: AffineSpace, eta) -> AffineSpace: field = space.field basis = space.basis - sub_basis = basis[0:2] + sub_basis = basis[0:eta] return AffineSpace(field, sub_basis) def eval_on_subspace(q: Poly, space: AffineSpace) -> AffineSpace: @@ -37,17 +55,20 @@ def __init__(self, field, affine_space, rho): self.rho = rho def commit(self, f, security_factor:int = 40) -> List[bytes]: + # rate parameter R = -math.log(self.rho, 2) + # localization parameter eta = 2 # Perhaps an option to set in the constructor k_0 = math.log(len(self.affine_space), 2) r = int(math.floor((k_0 - R)/eta)) - # q_0 = Zero_(L_0)? L_i = self.affine_space # L_0 is the affine space - L_i0 = select_subspace(L_i) + L_i0 = select_subspace(L_i, eta) # its dimention should be eta f_i = f + for i in range(r): - # TODO: This should be picked using a hash of some common data to apply the fiat-shamir heuristic - x_i = select_random(self.field) + # This should be picked using a hash of some common data to apply the fiat-shamir heuristic + x = str(R)+str(eta)+str(r)+str(k_0)+str(L_i)+str(L_i0) + x_i = select_random(self.field, x) # Convention: L^i_0 is the space generated by the first eta basis q_i = construct_affine_vanishing_polynomial(L_i0) L_i_plus_1 = eval_on_subspace(q_i, L_i) @@ -70,6 +91,35 @@ def commit(self, f, security_factor:int = 40) -> List[bytes]: d = rho * len(L_r) - 1 return interp.coefficients[:d+1] + def query(self, f, l, L0, x, Pr): + seed(1) + # rate parameter + R = -math.log(self.rho, 2) + # localization parameter + eta = 2 # Perhaps an option to set in the constructor + k_0 = math.log(len(self.affine_space), 2) + r = int(math.floor((k_0 - R)/eta)) + L = self.affine_space + for j in range(l): + random_n = randint(0, len(L)) + s_i_plus = [] + s_i_plus.append(L[random_n]) + S_i = [] + for i in range(r): + q_i = construct_affine_vanishing_polynomial(L0[i]) + s_i_plus.append(q_i(s_i_plus[-1])) + S_i.append([s_i_plus[i] + l_i for l_i in L0[i]]) + + interp = [] + for i in range(r): + coset_eval = [f(x_c) for x_c in S_i] + interp.append(lagrange_interp(self.field, coset_eval)) + + for i in range(r): + if f[i](s_i_plus[i+1]) is not interp[i](x[i]): + return False + return True + def verify_proximity_proof(self, proof: List[bytes], merkle_root: bytes,