From 5f785119865714e4d289b2788760f2a84162923d Mon Sep 17 00:00:00 2001 From: Neil Date: Thu, 7 Nov 2024 16:06:01 +0700 Subject: [PATCH] feat(circle.py): accomplish --- src/circle.py | 183 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 171 insertions(+), 12 deletions(-) diff --git a/src/circle.py b/src/circle.py index 0d6e0c6..c0aa37d 100644 --- a/src/circle.py +++ b/src/circle.py @@ -5,7 +5,7 @@ sys.path.append("../src") from utils import log_2 -from merkle import MerkleTree +from merkle import MerkleTree, verify_decommitment from merlin.merlin_transcript import MerlinTranscript F31 = GF(31) @@ -270,7 +270,7 @@ def extrapolate(cls, evals, blowup_factor): class FRI: @classmethod - def fold_y(cls, evals, domain, r, debug=False): + def fold_y(cls, evals, domain, beta, debug=False): # first step (J mapping) # for f in natural order, we just divide f into 2 parts from the middle N = len(evals) @@ -283,30 +283,169 @@ def fold_y(cls, evals, domain, r, debug=False): for i, (_, y) in enumerate(domain[:N//2]): f0 = (left[i] + right[i]) / C31(2) f1 = (left[i] - right[i]) / (C31(2) * y) - evals[i] = f0 + r * f1 - if debug: print(f"f[{i}] = {evals[i]} = ({left[i]} + {right[i]})/2 + {r} * ({left[i]} - {right[i]})/(2 * {y})") + evals[i] = f0 + beta * f1 + if debug: print(f"f[{i}] = {evals[i]} = ({left[i]} + {right[i]})/2 + {beta} * ({left[i]} - {right[i]})/(2 * {y})") return evals + # Inputs: + # f is the polynomial to be folded + # D is the domain of f + # r is the random number for random linear combination + # Outputs: + # The first return value is the folded polynomial + # The second return value is the new domain @classmethod - def commit_phase(cls, input, transcript, debug=False): + def fold_x(cls, f, D, r, debug=False): + assert len(f) == len(D), "len(f) != len(D), {} != {}, f={}, D={}".format(len(f), len(D), f, D) + + # divide + N = len(f) + # left is the first half of f, of x from 1 to g^(N/2) + left = f[:N//2] + # right is the second half of f, of x from g^(N-1) to g^(N/2), which corresponds to minus x in left + right = f[:N//2-1:-1] + assert len(left) == len(right), "len(left) != len(right), {} != {}, left={}, right={}".format(len(left), len(right), left, right) + + for i, x in enumerate(D[:N//2]): + # f == f0 + x * f1 + f0 = (left[i] + right[i]) / C31(2) + f1 = (left[i] - right[i]) / (C31(2) * x) + # f[:N//2] stores the folded polynomial + if debug: print(f"f[{i}] = {f[i]} = ({left[i]} + {right[i]})/2 + {r} * ({left[i]} - {right[i]})/(2 * {x})") + f[i] = f0 + r * f1 + # if debug: print(f"{f[i]} = ({left[i]} + {right[i]})/2 + {r} * ({left[i]} - {right[i]})/(2 * {x})") + # reuse f[N//2:] to store new domain + f[N//2 + i] = 2 * x^2 - 1 + + # return the folded polynomial and the new domain + return f[:N//2], f[N//2:] + + @classmethod + def commit_phase(cls, input, domain, transcript, debug=False): assert isinstance(transcript, MerlinTranscript), "transcript should be a MerlinTranscript" + folded = input + + # fold + trees = [] + oracles = [] + for _ in range(log_2(len(input))): + # random number + transcript.append_message(b'tree', bytes(trees[-1].root, 'ascii')) + + beta = int.from_bytes(transcript.challenge_bytes(b'beta', int(4)), 'big') + folded, domain = cls.fold_x(folded, domain, beta, debug) + + if debug: print(f"f={folded}, D={domain}") + + # merkle tree + trees.append(MerkleTree(folded)) + oracles.append(folded[:]) + + final_poly = folded[0] + if debug: + for x in folded: + assert final_poly == x, "final_poly != x, {} != {}, final_poly={}, x={}".format(final_poly, x, final_poly, x) + transcript.append_message(b"final_poly", bytes(final_poly, 'ascii')) + + return { + "commits": [tree.root for tree in trees], + "trees": trees, + "oracles": oracles, + "final_poly": final_poly + } @classmethod - def prove(cls, input, transcript, open_input, debug=False): + def answer_query(cls, trees, oracles, index, degree, debug=False): + opening_proofs = [] + sibling_values = [] + for i, tree in enumerate(trees): + index_i = index >> i + index_i_sibling = 1 << degree - 1 - index_i + degree >>= 1 + + tree = trees[i] + oracle = oracles[i] + + assert isinstance(tree, MerkleTree), "tree should be a MerkleTree" + + opening_proofs.append((tree.get_authentication_path(index_i), tree.get_authentication_path(index_i_sibling))) + sibling_values.append(oracle[index_i_sibling]) + + if debug: + assert verify_decommitment(index_i, oracle[index_i], opening_proofs[0], tree.root), "verify_decommitment failed" + assert verify_decommitment(index_i_sibling, oracle[index_i_sibling], opening_proofs[1], tree.root), "verify_decommitment failed" + + return zip(opening_proofs, sibling_values) + + @classmethod + def prove(cls, input, domain, transcript, open_input, num_queries, debug=False): assert isinstance(transcript, MerlinTranscript), "transcript should be a MerlinTranscript" + degree = len(input) + # commit phase + commit_phase_result = cls.commit_phase(input, domain, transcript, debug) # query phase + sample = lambda: int.from_bytes(transcript.challenge_bytes(b"query", 4), "big") + queries = [sample() for _ in range(num_queries)] + + query_proofs = [] + for query in queries: + query_proofs.append({ + "input_proof": open_input(query), + "commit_phase_openings": cls.answer_query(commit_phase_result["trees"], commit_phase_result["oracles"], query >> (32 - log_2(degree)), degree, debug) + }) + + return { + "commit_phase_commits": commit_phase_result["commits"], + "query_proofs": query_proofs, + "final_poly": commit_phase_result["final_poly"] + } @classmethod - def verify_query(cls): - pass + def verify_query(cls, index, steps, reduced_opening, log_max_height): + folded_eval = reduced_opening + for log_folded_height, (beta, comm, opening) in zip(range(log_max_height)[::-1], steps): + index_sibling = 1 << log_folded_height - 1 - index + + opening_proofs = opening[0] + sibling_values = opening[1] + + assert verify_decommitment(index, folded_eval, opening_proofs[0], comm), "verify_decommitment failed" + assert verify_decommitment(index_sibling, sibling_values, opening_proofs[1], comm), "verify_decommitment failed" + + domain = CirclePCS.natural_domain_for_degree(1 << log_folded_height) + x = domain[index] + left = folded_eval if index < index_sibling else sibling_values + right = sibling_values if index < index_sibling else folded_eval + folded_eval = (left + right) / C31(2) + beta * ((left - right) / (C31(2) * x)) + + return folded_eval @classmethod - def verify(cls): - pass + def verify(cls, proof, transcript, open_input, debug=False): + assert isinstance(transcript, MerlinTranscript), "transcript should be a MerlinTranscript" + + betas = [] + for i in range(len(proof["trees"])): + transcript.append_message(b"tree", bytes(proof["commit_phase_commits"][i], 'ascii')) + beta = transcript.challenge_bytes(b"beta", 4) + beta = int.from_bytes(beta, "big") + betas.append(beta) + transcript.append_message(b"final_poly", bytes(proof["final_poly"], 'ascii')) + + folded_eval = 0 + for qp in proof["query_proofs"]: + index = transcript.challenge_bytes(b"query", 4) + index = int.from_bytes(index, "big") + ro = open_input(index, qp["input_proof"]) + + log_max_height = len(proof["commit_phase_commits"]) + folded_eval = cls.verify_query(index >> (32 - log_max_height), zip(betas, proof["commit_phase_commits"], qp["commit_phase_openings"]), ro, log_max_height) + + assert folded_eval == proof["final_poly"], "folded_eval != proof['final_poly'], {} != {}".format(folded_eval, proof["final_poly"]) class CirclePCS: G5 = [g**k for k in range(32)] @@ -337,7 +476,7 @@ def commit(cls, eval, domain, blowup_factor): return tree.root, tree @classmethod - def open(cls, evals, tree, zeta, log_blowup, transcript, debug=False): + def open(cls, evals, evals_commit, zeta, log_blowup, transcript, num_queries, debug=False): assert isinstance(transcript, MerlinTranscript), "transcript should be a MerlinTranscript" alpha = transcript.challenge_bytes(b"alpha", 4) alpha = int.from_bytes(alpha, "big") @@ -361,11 +500,31 @@ def open(cls, evals, tree, zeta, log_blowup, transcript, debug=False): # fold first layer # a little modified compared to code in p3 fri_input = FRI.fold_y(first_layer, domain, bivariate_beta, debug) + domain = sq(domain) + + def open_input(index): + index >>= 32 - log_2(len(evals)) + assert isinstance(evals_commit, MerkleTree), "evals_commit should be a MerkleTree" + input_opening = (evals_commit.get_authentication_path(index), evals[index]) + + if debug: + assert verify_decommitment(index, evals[index], input_opening, evals_commit.root), "verify_decommitment failed" + + first_layer_index = index + first_layer_index_sibling = first_layer_index ^ 1 + first_layer_proof = (first_layer_tree.get_authentication_path(first_layer_index), first_layer_tree.get_authentication_path(first_layer_index_sibling)) + + return { + "input_proof": input_opening, + "first_layer_proof": first_layer_proof, + "first_layer_sibling_value": first_layer[first_layer_index_sibling] + } - fri_proof = FRI.prove(fri_input) + fri_proof = FRI.prove(fri_input, domain, transcript, open_input, num_queries, debug) return { "first_layer_commitment": first_layer_tree.root, + "lambda": lambda_, "fri_proof": fri_proof }