diff --git a/README.md b/README.md index e244bb9..2873c94 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,12 @@ The parts of PlonK that are responsible for ensuring strong privacy are left out A lookup argument allows us to prove that a certain element can be found in a public lookup table. [PlonKup](https://eprint.iacr.org/2022/086.pdf) introduces lookup arguments to PlonK. Try to understand the construction in the paper and implement it here. ## Getting started -To see the proof system in action, run `python3 test.py` from the root of the repository. This will take you through the workflow of setup, proof generation, and verification for several example programs. + +To get started, you'll need to have a Python version >= 3.8 and [`poetry`](https://python-poetry.org) installed: `curl -sSL https://install.python-poetry.org | python3 -`. + +Then, run `poetry install` in the root of the repository. This will install all the dependencies in a virtualenv. + +Then, to see the proof system in action, run `poetry run python test.py` from the root of the repository. This will take you through the workflow of setup, proof generation, and verification for several example programs. ### Compiler #### Program We specify our program logic in a high-level language involving constraints and variable assignments. Here is a program that lets you prove that you know two small numbers that multiply to a given number (in our example we'll use 91) without revealing what those numbers are: @@ -68,11 +73,11 @@ class GateWires: ``` Examples of valid program constraints, and corresponding assembly: -| program constraint | assembly | -|--------------------------|-------------------------------------------------| -|a === 9 | ([None, None, 'a'], {'': 9}) | -|b <== a * c | (['a', 'c', 'b'], {'a*c': 1}) | -|d <== a * c - 45 * a + 987| (['a', 'c', 'd'], {'a*c': 1, 'a': -45, '': 987})| +| program constraint | assembly | +| -------------------------- | ------------------------------------------------ | +| a === 9 | ([None, None, 'a'], {'': 9}) | +| b <== a * c | (['a', 'c', 'b'], {'a*c': 1}) | +| d <== a * c - 45 * a + 987 | (['a', 'c', 'd'], {'a*c': 1, 'a': -45, '': 987}) | ### Setup @@ -113,23 +118,23 @@ class Proof: The proof consists of: -| proof element | remark | -|----------------------------------|----------------------------------------------------------------------------------------------| -|$[a(x)]_1$ | commitment to left wire polynomial | -|$[b(x)]_1$ | commitment to right wire polynomial | -|$[c(x)]_1$ | commitment to output wire polynomial | -|$[z(x)]_1$ | commitment to permutation polynomial | -|$[t_{lo}(x)]_1$ | commitment to $t_{lo}(X)$, the low chunk of the quotient polynomial $t(X)$ | -|$[t_{mid}(x)]_1$ | commitment to $t_{mid}(X)$, the middle chunk of the quotient polynomial $t(X)$ | -|$[t_{hi}(x)]_1$ | commitment to $t_{hi}(X)$, the high chunk of the quotient polynomial $t(X)$ | -|$\overline{a}$ | opening of $a(X)$ at evaluation challenge $\zeta$ | -|$\overline{b}$ | opening of $b(X)$ at evaluation challenge $\zeta$ | -|$\overline{c}$ | opening of $c(X)$ at evaluation challenge $\zeta$ | -|$\overline{\mathsf{s}}_{\sigma1}$ |opening of the first permutation polynomial $S_{\sigma1}(X)$ at evaluation challenge $\zeta$ | -|$\overline{\mathsf{s}}_{\sigma2}$ |opening of the second permutation polynomial $S_{\sigma2}(X)$ at evaluation challenge $\zeta$ | -|$\overline{z}_\omega$ | opening of shifted permutation polynomial $z(X)$ at shifted challenge $\zeta\omega$ | -|$[W_\zeta(X)]_1$ | commitment to the opening proof polynomial | -|$[W_{\zeta\omega}(X)]_1$ | commitment to the opening proof polynomial | +| proof element | remark | +| --------------------------------- | --------------------------------------------------------------------------------------------- | +| $[a(x)]_1$ | commitment to left wire polynomial | +| $[b(x)]_1$ | commitment to right wire polynomial | +| $[c(x)]_1$ | commitment to output wire polynomial | +| $[z(x)]_1$ | commitment to permutation polynomial | +| $[t_{lo}(x)]_1$ | commitment to $t_{lo}(X)$, the low chunk of the quotient polynomial $t(X)$ | +| $[t_{mid}(x)]_1$ | commitment to $t_{mid}(X)$, the middle chunk of the quotient polynomial $t(X)$ | +| $[t_{hi}(x)]_1$ | commitment to $t_{hi}(X)$, the high chunk of the quotient polynomial $t(X)$ | +| $\overline{a}$ | opening of $a(X)$ at evaluation challenge $\zeta$ | +| $\overline{b}$ | opening of $b(X)$ at evaluation challenge $\zeta$ | +| $\overline{c}$ | opening of $c(X)$ at evaluation challenge $\zeta$ | +| $\overline{\mathsf{s}}_{\sigma1}$ | opening of the first permutation polynomial $S_{\sigma1}(X)$ at evaluation challenge $\zeta$ | +| $\overline{\mathsf{s}}_{\sigma2}$ | opening of the second permutation polynomial $S_{\sigma2}(X)$ at evaluation challenge $\zeta$ | +| $\overline{z}_\omega$ | opening of shifted permutation polynomial $z(X)$ at shifted challenge $\zeta\omega$ | +| $[W_\zeta(X)]_1$ | commitment to the opening proof polynomial | +| $[W_{\zeta\omega}(X)]_1$ | commitment to the opening proof polynomial | ### Verifier @@ -145,15 +150,15 @@ class VerificationKey: The `VerificationKey` contains: -| verification key element | remark | -|----------------------------------|-------------------------------------------------------------------| -| $[q_M(x)]_1$ |commitment to multiplication selector polynomial | -| $[q_L(x)]_1$ |commitment to left selector polynomial | -| $[q_R(x)]_1$ |commitment to right selector polynomial | -| $[q_O(x)]_1$ |commitment to output selector polynomial | -| $[q_C(x)]_1$ |commitment to constants selector polynomial | -| $[S_{\sigma1}(x)]_1$ |commitment to the first permutation polynomial $S_{\sigma1}(X)$ | -| $[S_{\sigma2}(x)]_1$ |commitment to the second permutation polynomial $S_{\sigma2}(X)$ | -| $[S_{\sigma3}(x)]_1$ |commitment to the third permutation polynomial $S_{\sigma3}(X)$ | -| $[x]_2 = xH$ |(from the $\mathsf{srs}$) | -| $\omega$ |an $n$th root of unity, where $n$ is the program's group order. | +| verification key element | remark | +| ------------------------ | ---------------------------------------------------------------- | +| $[q_M(x)]_1$ | commitment to multiplication selector polynomial | +| $[q_L(x)]_1$ | commitment to left selector polynomial | +| $[q_R(x)]_1$ | commitment to right selector polynomial | +| $[q_O(x)]_1$ | commitment to output selector polynomial | +| $[q_C(x)]_1$ | commitment to constants selector polynomial | +| $[S_{\sigma1}(x)]_1$ | commitment to the first permutation polynomial $S_{\sigma1}(X)$ | +| $[S_{\sigma2}(x)]_1$ | commitment to the second permutation polynomial $S_{\sigma2}(X)$ | +| $[S_{\sigma3}(x)]_1$ | commitment to the third permutation polynomial $S_{\sigma3}(X)$ | +| $[x]_2 = xH$ | (from the $\mathsf{srs}$) | +| $\omega$ | an $n$th root of unity, where $n$ is the program's group order. | diff --git a/compiler/assembly.py b/compiler/assembly.py index 5e3c790..1c29052 100644 --- a/compiler/assembly.py +++ b/compiler/assembly.py @@ -3,9 +3,11 @@ from typing import Optional from dataclasses import dataclass + @dataclass class GateWires: """Variable names for Left, Right, and Output wires.""" + L: Optional[str] R: Optional[str] O: Optional[str] @@ -13,18 +15,22 @@ class GateWires: def as_list(self) -> list[Optional[str]]: return [self.L, self.R, self.O] + @dataclass class Gate: """Gate polynomial""" + L: f_inner = f_inner(0) R: f_inner = f_inner(0) M: f_inner = f_inner(0) O: f_inner = f_inner(0) C: f_inner = f_inner(0) + @dataclass class AssemblyEqn: """Assembly equation mapping wires to coefficients.""" + wires: GateWires coeffs: dict[Optional[str], int] @@ -37,31 +43,28 @@ def R(self) -> f_inner: return f_inner(0) def C(self) -> f_inner: - return f_inner(-self.coeffs.get('', 0)) + return f_inner(-self.coeffs.get("", 0)) def O(self) -> f_inner: - return f_inner(self.coeffs.get('$output_coeff', 1)) + return f_inner(self.coeffs.get("$output_coeff", 1)) def M(self) -> f_inner: if None not in self.wires.as_list(): - return f_inner(-self.coeffs.get(get_product_key(self.wires.L, self.wires.R), 0)) + return f_inner( + -self.coeffs.get(get_product_key(self.wires.L, self.wires.R), 0) + ) return f_inner(0) def gate(self) -> Gate: - return Gate( - self.L(), - self.R(), - self.M(), - self.O(), - self.C() - ) + return Gate(self.L(), self.R(), self.M(), self.O(), self.C()) + # Converts a arithmetic expression containing numbers, variables and {+, -, *} # into a mapping of term to coefficient # # For example: # ['a', '+', 'b', '*', 'c', '*', '5'] becomes {'a': 1, 'b*c': 5} -# +# # Note that this is a recursive algo, so the input can be a mix of tokens and # mapping expressions # @@ -69,38 +72,34 @@ def evaluate(exprs: list[str], first_is_negative=False) -> dict[Optional[str], i # Splits by + and - first, then *, to follow order of operations # The first_is_negative flag helps us correctly interpret expressions # like 6000 - 700 - 80 + 9 (that's 5229) - if '+' in exprs: - L = evaluate(exprs[:exprs.index('+')], first_is_negative) - R = evaluate(exprs[exprs.index('+')+1:], False) - return { - x: L.get(x, 0) + R.get(x, 0) for x in set(L.keys()).union(R.keys()) - } - elif '-' in exprs: - L = evaluate(exprs[:exprs.index('-')], first_is_negative) - R = evaluate(exprs[exprs.index('-')+1:], True) - return { - x: L.get(x, 0) + R.get(x, 0) for x in set(L.keys()).union(R.keys()) - } - elif '*' in exprs: - L = evaluate(exprs[:exprs.index('*')], first_is_negative) - R = evaluate(exprs[exprs.index('*')+1:], first_is_negative) + if "+" in exprs: + L = evaluate(exprs[: exprs.index("+")], first_is_negative) + R = evaluate(exprs[exprs.index("+") + 1 :], False) + return {x: L.get(x, 0) + R.get(x, 0) for x in set(L.keys()).union(R.keys())} + elif "-" in exprs: + L = evaluate(exprs[: exprs.index("-")], first_is_negative) + R = evaluate(exprs[exprs.index("-") + 1 :], True) + return {x: L.get(x, 0) + R.get(x, 0) for x in set(L.keys()).union(R.keys())} + elif "*" in exprs: + L = evaluate(exprs[: exprs.index("*")], first_is_negative) + R = evaluate(exprs[exprs.index("*") + 1 :], first_is_negative) o = {} for k1 in L.keys(): for k2 in R.keys(): o[get_product_key(k1, k2)] = L[k1] * R[k2] return o elif len(exprs) > 1: - raise Exception("No ops, expected sub-expr to be a unit: {}" - .format(exprs[1])) - elif exprs[0][0] == '-': + raise Exception("No ops, expected sub-expr to be a unit: {}".format(exprs[1])) + elif exprs[0][0] == "-": return evaluate([exprs[0][1:]], not first_is_negative) elif exprs[0].isnumeric(): - return {'': int(exprs[0]) * (-1 if first_is_negative else 1)} + return {"": int(exprs[0]) * (-1 if first_is_negative else 1)} elif is_valid_variable_name(exprs[0]): return {exprs[0]: -1 if first_is_negative else 1} else: raise Exception("ok wtf is {}".format(exprs[0])) + # Converts an equation to a mapping of term to coefficient, and verifies that # the operations in the equation are valid. # @@ -108,7 +107,7 @@ def evaluate(exprs: list[str], first_is_negative=False) -> dict[Optional[str], i # variable # # Think of the list of (variable triples, coeffs) pairs as this language's -# version of "assembly" +# version of "assembly" # # Example valid equations, and output: # a === 9 ([None, None, 'a'], {'': 9}) @@ -121,27 +120,27 @@ def evaluate(exprs: list[str], first_is_negative=False) -> dict[Optional[str], i # e <== a + b * c * d # Multiplicative degree > 2 # def eq_to_assembly(eq: str) -> AssemblyEqn: - tokens = eq.rstrip('\n').split(' ') - if tokens[1] in ('<==', '==='): + tokens = eq.rstrip("\n").split(" ") + if tokens[1] in ("<==", "==="): # First token is the output variable out = tokens[0] # Convert the expression to coefficient map form coeffs = evaluate(tokens[2:]) # Handle the "-x === a * b" case - if out[0] == '-': + if out[0] == "-": out = out[1:] - coeffs['$output_coeff'] = -1 + coeffs["$output_coeff"] = -1 # Check out variable name validity if not is_valid_variable_name(out): raise Exception("Invalid out variable name: {}".format(out)) # Gather list of variables used in the expression variables = [] for t in tokens[2:]: - var = t.lstrip('-') + var = t.lstrip("-") if is_valid_variable_name(var) and var not in variables: variables.append(var) - # Construct the list of allowed coefficients - allowed_coeffs = variables + ['', '$output_coeff'] + # Construct the list of allowed coefficients + allowed_coeffs = variables + ["", "$output_coeff"] if len(variables) == 0: pass elif len(variables) == 1: @@ -157,14 +156,11 @@ def eq_to_assembly(eq: str) -> AssemblyEqn: raise Exception("Disallowed multiplication: {}".format(key)) # Return output wires = variables + [None] * (2 - len(variables)) + [out] - return AssemblyEqn( - GateWires(wires[0], wires[1], wires[2]), - coeffs - ) - elif tokens[1] == 'public': + return AssemblyEqn(GateWires(wires[0], wires[1], wires[2]), coeffs) + elif tokens[1] == "public": return AssemblyEqn( GateWires(tokens[0], None, None), - {tokens[0]: -1, '$output_coeff': 0, '$public': True} + {tokens[0]: -1, "$output_coeff": 0, "$public": True}, ) else: raise Exception("Unsupported op: {}".format(tokens[1])) diff --git a/compiler/program.py b/compiler/program.py index b742968..7e90ecb 100644 --- a/compiler/program.py +++ b/compiler/program.py @@ -5,6 +5,7 @@ from .utils import * from typing import Optional, Set + class Program: constraints: list[AssemblyEqn] group_order: int @@ -18,7 +19,7 @@ def __init__(self, constraints: list[str], group_order: int): @classmethod def from_str(cls, constraints: str, group_order: int): - lines = [line.strip() for line in constraints.split('\n')] + lines = [line.strip() for line in constraints.split("\n")] return cls(lines, group_order) def coeffs(self) -> list[dict[Optional[str], int]]: @@ -39,11 +40,11 @@ def make_s_polynomials(self) -> dict[Column, list[Optional[f_inner]]]: # Mark unused cells for row in range(len(self.constraints), self.group_order): - for column in (Column.variants()): + for column in Column.variants(): variable_uses[None].add(Cell(column, row)) # For each list of positions, rotate by one. - # + # # For example, if some variable is used in positions # (LEFT, 4), (LEFT, 7) and (OUTPUT, 2), then we store: # @@ -51,7 +52,7 @@ def make_s_polynomials(self) -> dict[Column, list[Optional[f_inner]]]: # at S[OUTPUT][2] the field element representing (LEFT, 7) # at S[LEFT][4] the field element representing (OUTPUT, 2) - S = { + S: dict[Column, list[Optional[f_inner]]] = { Column.LEFT: [None] * self.group_order, Column.RIGHT: [None] * self.group_order, Column.OUTPUT: [None] * self.group_order, @@ -60,7 +61,7 @@ def make_s_polynomials(self) -> dict[Column, list[Optional[f_inner]]]: for _, uses in variable_uses.items(): sorted_uses = sorted(uses) for i, cell in enumerate(sorted_uses): - next_i = (i+1) % len(sorted_uses) + next_i = (i + 1) % len(sorted_uses) next_column = sorted_uses[next_i].column next_row = sorted_uses[next_i].row S[next_column][next_row] = cell.label(self.group_order) @@ -73,20 +74,24 @@ def get_public_assignments(self) -> list[Optional[str]]: o = [] no_more_allowed = False for coeff in coeffs: - if coeff.get('$public', False) is True: + if coeff.get("$public", False) is True: if no_more_allowed: raise Exception("Public var declarations must be at the top") - var_name = [x for x in list(coeff.keys()) if '$' not in x][0] - if coeff != {'$public': True, '$output_coeff': 0, var_name: -1}: - raise Exception("Malformatted coeffs: {}",format(coeffs)) + var_name = [x for x in list(coeff.keys()) if "$" not in str(x)][0] + if coeff != {"$public": True, "$output_coeff": 0, var_name: -1}: + raise Exception("Malformatted coeffs: {}", format(coeffs)) o.append(var_name) else: no_more_allowed = True return o # Generate the gate polynomials: L, R, M, O, C, - # each a list of length `group_order` - def make_gate_polynomials(self) -> tuple[list[f_inner], list[f_inner], list[f_inner], list[f_inner], list[f_inner]]: + # each a list of length `group_order` + def make_gate_polynomials( + self, + ) -> tuple[ + list[f_inner], list[f_inner], list[f_inner], list[f_inner], list[f_inner] + ]: L = [f_inner(0) for _ in range(self.group_order)] R = [f_inner(0) for _ in range(self.group_order)] M = [f_inner(0) for _ in range(self.group_order)] @@ -105,8 +110,10 @@ def make_gate_polynomials(self) -> tuple[list[f_inner], list[f_inner], list[f_in # assignments, starting from the given assignments. Eg. if # `starting_assignments` contains {'a': 3, 'b': 5}, and the first line # says `c <== a * b`, then it fills in `c: 15`. - def fill_variable_assignments(self, starting_assignments: dict[Optional[str], int]) -> dict[Optional[str], int]: - out = {k: f_inner(v) for k,v in starting_assignments.items()} + def fill_variable_assignments( + self, starting_assignments: dict[Optional[str], int] + ) -> dict[Optional[str], int]: + out = {k: f_inner(v) for k, v in starting_assignments.items()} out[None] = f_inner(0) for constraint in self.constraints: wires = constraint.wires @@ -114,21 +121,24 @@ def fill_variable_assignments(self, starting_assignments: dict[Optional[str], in in_L = wires.L in_R = wires.R output = wires.O - out_coeff = coeffs.get('$output_coeff', 1) + out_coeff = coeffs.get("$output_coeff", 1) product_key = get_product_key(in_L, in_R) if output is not None and out_coeff in (-1, 1): - new_value = f_inner( - coeffs.get('', 0) + - out[in_L] * coeffs.get(in_L, 0) + - out[in_R] * coeffs.get(in_R, 0) * (1 if in_R != in_L else 0) + - out[in_L] * out[in_R] * coeffs.get(product_key, 0) - ) * out_coeff # should be / but equivalent for (1, -1) + new_value = ( + f_inner( + coeffs.get("", 0) + + out[in_L] * coeffs.get(in_L, 0) + + out[in_R] * coeffs.get(in_R, 0) * (1 if in_R != in_L else 0) + + out[in_L] * out[in_R] * coeffs.get(product_key, 0) + ) + * out_coeff + ) # should be / but equivalent for (1, -1) if output in out: if out[output] != new_value: - raise Exception("Failed assertion: {} = {}" - .format(out[output], new_value)) + raise Exception( + "Failed assertion: {} = {}".format(out[output], new_value) + ) else: out[output] = new_value # print('filled in:', output, out[output]) - return {k: v.n for k,v in out.items()} - + return {k: v.n for k, v in out.items()} diff --git a/compiler/utils.py b/compiler/utils.py index c7f5e56..6e56780 100644 --- a/compiler/utils.py +++ b/compiler/utils.py @@ -2,6 +2,7 @@ from enum import Enum from dataclasses import dataclass + class Column(Enum): LEFT = 1 RIGHT = 2 @@ -15,6 +16,8 @@ def __lt__(self, other): @staticmethod def variants(): return [Column.LEFT, Column.RIGHT, Column.OUTPUT] + + @dataclass class Cell: column: Column @@ -43,13 +46,15 @@ def label(self, group_order: int) -> f_inner: assert self.row < group_order return get_roots_of_unity(group_order)[self.row] * self.column.value + # Gets the key to use in the coeffs dictionary for the term for key1*key2, # where key1 and key2 can be constant(''), a variable, or product keys # Note that degrees higher than 2 are disallowed in the compiler, but we # still allow them in the parser in case we find a way to compile them later def get_product_key(key1, key2): - members = sorted((key1 or '').split('*') + (key2 or '').split('*')) - return '*'.join([x for x in members if x]) + members = sorted((key1 or "").split("*") + (key2 or "").split("*")) + return "*".join([x for x in members if x]) + def is_valid_variable_name(name: str) -> bool: - return len(name) > 0 and name.isalnum() and name[0] not in '0123456789' + return len(name) > 0 and name.isalnum() and name[0] not in "0123456789" diff --git a/curve.py b/curve.py index 9f3ed92..ab6a05e 100644 --- a/curve.py +++ b/curve.py @@ -7,12 +7,13 @@ primitive_root = 5 + class Scalar(Field): field_modulus = b.curve_order # Gets the first root of unity of a given group order @classmethod - def root_of_unity(cls, group_order:int): + def root_of_unity(cls, group_order: int): return Scalar(5) ** ((cls.field_modulus - 1) // group_order) # Gets the full list of roots of unity of a given group order @@ -23,13 +24,16 @@ def roots_of_unity(cls, group_order: int): o.append(o[-1] * o[1]) return o -Base = NewType('Base', b.FQ) + +Base = NewType("Base", b.FQ) + def ec_mul(pt, coeff): - if hasattr(coeff, 'n'): + if hasattr(coeff, "n"): coeff = coeff.n return b.multiply(pt, coeff % b.curve_order) + # Elliptic curve linear combination. A truly optimized implementation # would replace this with a fast lin-comb algo, see https://ethresear.ch/t/7238 def ec_lincomb(pairs): @@ -37,7 +41,7 @@ def ec_lincomb(pairs): [pt for (pt, _) in pairs], [int(n) % b.curve_order for (_, n) in pairs], b.add, - b.Z1 + b.Z1, ) # Equivalent to: # o = b.Z1 diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..b1a9166 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,443 @@ +# This file is automatically @generated by Poetry and should not be changed by hand. + +[[package]] +name = "black" +version = "22.12.0" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"}, + {file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"}, + {file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"}, + {file = "black-22.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4"}, + {file = "black-22.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2"}, + {file = "black-22.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350"}, + {file = "black-22.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d"}, + {file = "black-22.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc"}, + {file = "black-22.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320"}, + {file = "black-22.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148"}, + {file = "black-22.12.0-py3-none-any.whl", hash = "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf"}, + {file = "black-22.12.0.tar.gz", hash = "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} +typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "cached-property" +version = "1.5.2" +description = "A decorator for caching properties in classes." +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"}, + {file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"}, +] + +[[package]] +name = "click" +version = "8.1.3" +description = "Composable command line interface toolkit" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "cytoolz" +version = "0.12.1" +description = "Cython implementation of Toolz: High performance functional utilities" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "cytoolz-0.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c59bb4ca88e1c69931468bf21f91c8f64d8bf1999eb163b7a2df336f60c304a"}, + {file = "cytoolz-0.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4d700e011156ff112966c6d77faaae125fcaf538f4cec2b9ce8957de82858f0f"}, + {file = "cytoolz-0.12.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23c3f57c48eb939d2986eba4aeaeedf930ebf94d58c91a42d4e0fc45ed5427dc"}, + {file = "cytoolz-0.12.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:25ff13c468c06da9ef26651dc389e7e8bb7af548f8c1dfb96305f57f18d398a8"}, + {file = "cytoolz-0.12.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a734511144309ea6e105406633affb74e303a3df07d8a3954f9b01946e27ecb1"}, + {file = "cytoolz-0.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48bc2f30d1b2646d675bb8e7778ab59379bf9edc59fe06fb0e7f85ba1271bf44"}, + {file = "cytoolz-0.12.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30936ae8fa68b6a1ac8ad6c4bacb5a8a00d51bc6c89f9614a1557b0105d09f8a"}, + {file = "cytoolz-0.12.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:efd1b2da3ee577fcfa723a214f73186aef9674dd5b28242d90443c7a82722b0f"}, + {file = "cytoolz-0.12.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6805b007af3557ee6c20dab491b6e55a8177f5b6845d9e6c653374d540366ba7"}, + {file = "cytoolz-0.12.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a6e63fc67b23830947b51e0a488992e3c904fce825ead565f3904dcf621d05f7"}, + {file = "cytoolz-0.12.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:9e324a94856d88ecf10f34c102d0ded67d7c3cf644153d77e34a29720ce6aa47"}, + {file = "cytoolz-0.12.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:02975e2b1e61e47e9afa311f4c1783d155136fad37c54a1cebfe991c5a0798a1"}, + {file = "cytoolz-0.12.1-cp310-cp310-win32.whl", hash = "sha256:b6569f6038133909cd658dbdcc6fc955f791dc47a7f5b55d2066f742253dcbfe"}, + {file = "cytoolz-0.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:1be368623e46ad3c1ce807e7a436acb119c26001507b31f92ceb21b86e08c386"}, + {file = "cytoolz-0.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:849f461bffa1e7700ccfcb5186df29cd4cdcc9efdb7199cb8b5681dc37045d72"}, + {file = "cytoolz-0.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4284120c978fb7039901bf6e66832cb3e82ac1b2a107512e735bdb04fd5533ed"}, + {file = "cytoolz-0.12.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ec296f01c29c809698eaf677211b6255691295c2b35caab2131e1e7eaadfbac"}, + {file = "cytoolz-0.12.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:37c53f456a1c84566a7d911eec57c4c6280b915ab0600e7671582793cc2769fe"}, + {file = "cytoolz-0.12.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1b6761791973b1e839b8309d5853b40eeb413368e31beaf5f2b6ed44c6fc7cf0"}, + {file = "cytoolz-0.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff478682e8ee6dbaa37201bb71bf4a6eee744006ab000e8f5cea05066fc7c845"}, + {file = "cytoolz-0.12.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:867bebe6be30ee36a836f9b835790762a74f46be8cc339ea57b68dcecdbc1133"}, + {file = "cytoolz-0.12.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7e903df991f0957e2b271a37bb25d28e0d260c52825ae67507d15ca55a935961"}, + {file = "cytoolz-0.12.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e797c4afb1b7962d3205b1959e1051f7e6bfbba29da44042a9efc2391f1feb38"}, + {file = "cytoolz-0.12.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b8eceaa12b7f152b046b67cb053ec2b5b00f73593983de69bc5e63a8aca4a7a8"}, + {file = "cytoolz-0.12.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:b575393dd431b8e211de35bd593d831dac870172b16e2b7934f3566b8fc89377"}, + {file = "cytoolz-0.12.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3032c0ba42dee5836d6b57a72a569b65df2c29e8ed266cb900d569003cf933a9"}, + {file = "cytoolz-0.12.1-cp311-cp311-win32.whl", hash = "sha256:c576bd63495150385b8d05eaae775387f378be2fd9805d3ffb4d17c87271fbad"}, + {file = "cytoolz-0.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:421b224dc4157a0d66625acb5798cf50858cfa06a5232d39a8bd6cf1fa88aca3"}, + {file = "cytoolz-0.12.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:be5a454a95797343d0fb1ed02caecae73a023b1393c112951c84f17ec9f4076c"}, + {file = "cytoolz-0.12.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:061387aa39b9c1576c25d0c59142513c09e77a2a07bd5d6211a43c7a758b6f45"}, + {file = "cytoolz-0.12.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:14f4dbc3f0ec8f6fc68865489af21dcf042ff007d2737c27bfd73296f15db544"}, + {file = "cytoolz-0.12.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a816bff6bf424753e1ac2441902ceaf37ae6718b745a53f6aa1a60c617fb4f5f"}, + {file = "cytoolz-0.12.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:633f19d1990b1cf9c67dce9c28bf8b5a18e42785d15548607a100e1236384d5d"}, + {file = "cytoolz-0.12.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fa7009c843667868aa8bdb3d68e5ef3d6356dd418b17ed5ca4e1340e82483a5"}, + {file = "cytoolz-0.12.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:1c29dd04e282ddfd45b457e3551075beec9128aa9271245e58ce924bf6e055f8"}, + {file = "cytoolz-0.12.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:cd35c0be4c46274129dd1678bb911dd4e93d23968b26f4e39cd55bc7cb3b1bac"}, + {file = "cytoolz-0.12.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:5158ae6d8dd112d003f677039a3613ca7d2592bfe35d7accf23684edb961fc26"}, + {file = "cytoolz-0.12.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:7eb9e6fa8a82c3d2f519f7d3942898a97792e3895569e9501b9431048289b82f"}, + {file = "cytoolz-0.12.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:ac6784cc43aec51a86cf9058a2a343084f8cf46a9281bea5762bfa608127c53b"}, + {file = "cytoolz-0.12.1-cp36-cp36m-win32.whl", hash = "sha256:794cce219bbcb2f36ca220f27d5afd64eaa854e04901bd6f240be156a578b607"}, + {file = "cytoolz-0.12.1-cp36-cp36m-win_amd64.whl", hash = "sha256:695dd8231e4f1bfb9a2363775a6e4e56ad9d2058058f817203a49614f4bfe33b"}, + {file = "cytoolz-0.12.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1bd8017ef0da935a20106272c5f5ff6b1114add1ccb09cfed1ff7ec5cc01c6d"}, + {file = "cytoolz-0.12.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56e1ebf6eb4438b8c45cbe7e7b22fc65df0c9efa97a70d3bf2f51e08b19756a5"}, + {file = "cytoolz-0.12.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:816c2038008ebf50d81171ddfae377f1af9e71d504ec609469dcb0906bfcf2ae"}, + {file = "cytoolz-0.12.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bebe58f7a160db7838eb70990c704db4bdc2d58bd364290fd69be0587be8bac"}, + {file = "cytoolz-0.12.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a72440305f634604827f96810e4469877b89f5c060d6852267650a49b0e3768c"}, + {file = "cytoolz-0.12.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b46ebc463bb45f278a2b94e630061c26e10077cb68d4c93583d8f4199699a5ef"}, + {file = "cytoolz-0.12.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:e75e287787e6adafed9d8c3d3e7647c0b5eb460221f9f92d7dfe48b45ba77c0d"}, + {file = "cytoolz-0.12.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:03ab22c9aeb1535f8647d23b6520b0c3d41aaa18d04ef42b352dde1931f2e2b1"}, + {file = "cytoolz-0.12.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b2ac288f27a2689d9e39f4cf4df5437a8eb038eaae515169586c77f9f8fb343a"}, + {file = "cytoolz-0.12.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:97a24c0d0806fcf9a6e75fc18aeb95adc37eb0baf6451f10a2de23ffd815329d"}, + {file = "cytoolz-0.12.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:42c9e5cd2a48a257b1f2402334b48122501f249b8dcf77082f569f2680f185eb"}, + {file = "cytoolz-0.12.1-cp37-cp37m-win32.whl", hash = "sha256:35fae4eaa0eaf9072a5fe2d244a79e65baae4e5ddbe9cc629c5037af800213a2"}, + {file = "cytoolz-0.12.1-cp37-cp37m-win_amd64.whl", hash = "sha256:5af43ca7026ead3dd08b261e4f7163cd2cf3ceaa74fa5a81f7b7ea5d445e41d6"}, + {file = "cytoolz-0.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fcc378fa97f02fbcef090b3611305425d72bd1c0afdd13ef4a82dc67d40638b6"}, + {file = "cytoolz-0.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc3645cf6b9246cb8e179db2803e4f0d148211d2a2cf22d5c9b5219111cd91a0"}, + {file = "cytoolz-0.12.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b245b824f4705aef0e4a03fafef3ad6cb59ef43cc564cdbf683ee28dfc11ad5"}, + {file = "cytoolz-0.12.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c1964dcb5f250fd13fac210944b20810d61ef4094a17fbbe502ab7a7eaeeace7"}, + {file = "cytoolz-0.12.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f7194a22a4a24f3561cb6ad1cca9c9b2f2cf34cc8d4bce6d6a24c80960323fa8"}, + {file = "cytoolz-0.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1c5434db53f3a94a37ad8aedb231901e001995d899af6ed1165f3d27fa04a6a"}, + {file = "cytoolz-0.12.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b30cd083ef8af4ba66d9fe5cc75c653ede3f2655f97a032db1a14cc8a006719c"}, + {file = "cytoolz-0.12.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bef934bd3e024d512c6c0ad1c66eb173f61d9ccb4dbca8d75f727a5604f7c2f6"}, + {file = "cytoolz-0.12.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:37320669c364f7d370392af33cc1034b4563da66c22cd3261e3530f4d30dbe4b"}, + {file = "cytoolz-0.12.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3cb95d23defb2322cddf70efb4af6dac191d95edaa343e8c1f58f1afa4f92ecd"}, + {file = "cytoolz-0.12.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ac5895d5f78dbd8646fe37266655ba4995f9cfec38a86595282fee69e41787da"}, + {file = "cytoolz-0.12.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:499af2aff04f65b4c23de1df08e1d1484a93b23ddaaa0163e44b5070b68356eb"}, + {file = "cytoolz-0.12.1-cp38-cp38-win32.whl", hash = "sha256:aa61e3da751a2dfe95aeca603f3ef510071a136ba9905f61ae6cb5d0696271ad"}, + {file = "cytoolz-0.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:f5b43ce952a5a31441556c55f5f5f5a8e62c28581a0ff2a2c31c04ef992d73bd"}, + {file = "cytoolz-0.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b8b8f88251b84b3877254cdd59c86a1dc6b2b39a03c6c9c067d344ef879562e0"}, + {file = "cytoolz-0.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d72415b0110f7958dd3a5ee98a70166f47bd42ede85e3535669c794d06f57406"}, + {file = "cytoolz-0.12.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8101ab6de5aa0b26a2b5032bc488d430010c91863e701812d65836b03a12f61"}, + {file = "cytoolz-0.12.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2eed428b5e68c28abf2c71195e799850e040d67a27c05f7785319c611665b86a"}, + {file = "cytoolz-0.12.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59641eb1f41cb688b3cb2f98c9003c493a5024325f76b5c02333d08dd972127c"}, + {file = "cytoolz-0.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a48940ff0449ffcf690310bf9228bb57885f7571406ed2fe05c98e299987195"}, + {file = "cytoolz-0.12.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bae431a5985cdb2014be09d37206c288e0d063940cf9539e9769bd2ec26b220"}, + {file = "cytoolz-0.12.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cb8b10405960a8e6801a4702af98ea640130ec6ecfc1208195762de3f5503ba9"}, + {file = "cytoolz-0.12.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3c9a16a5b4f54d5c0a131f56b0ca65998a9a74958b5b36840c280edba4f8b907"}, + {file = "cytoolz-0.12.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:49911cb533c96d275e31e7eaeb0742ac3f7afe386a1d8c40937814d75039a0f7"}, + {file = "cytoolz-0.12.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:dbae37d48ef5a0ab90cfaf2b9312d96f034b1c828208a9cbe25377a1b19ba129"}, + {file = "cytoolz-0.12.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c34e69be4429633fc614febe3127fa03aa418a1abb9252f29d9ba5b3394573a5"}, + {file = "cytoolz-0.12.1-cp39-cp39-win32.whl", hash = "sha256:0d474dacbafbdbb44c7de986bbf71ff56ae62df0d52ab3b6fa966784dc88737a"}, + {file = "cytoolz-0.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:3d6d0b0075731832343eb88229cea4bf39e96f3fc7acbc449aadbdfec2842703"}, + {file = "cytoolz-0.12.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8506d1863f30d26f577c4ed59d2cfd03d2f39569f9cbaa02a764a9de73d312d5"}, + {file = "cytoolz-0.12.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a1eae39656a1685e8b3f433eecfd72015ce5c1d7519e9c8f9402153c68331bb"}, + {file = "cytoolz-0.12.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a0055943074c6c85b77fcc3f42f7c54010a3478daa2ed9d6243d0411c84a4d3"}, + {file = "cytoolz-0.12.1-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8a7a325b8fe885a6dd91093616c703134f2dacbd869bc519970df3849c2a15b"}, + {file = "cytoolz-0.12.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:7b60caf0fa5f1b49f1062f7dc0f66c7b23e2736bad50fa8296bfb845995e3051"}, + {file = "cytoolz-0.12.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:980e7eb7205e01816a92f3290cfc80507957e64656b9271a0dfebb85fe3718c0"}, + {file = "cytoolz-0.12.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06d38a40fe153f23cda0e823413fe9d9ebee89dd461827285316eff929fb121e"}, + {file = "cytoolz-0.12.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d540e9c34a61b53b6a374ea108794a48388178f7889d772e364cdbd6df37774c"}, + {file = "cytoolz-0.12.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:117871f036926e42d3abcee587eafa9dc7383f1064ac53a806d33e76604de311"}, + {file = "cytoolz-0.12.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:31131b54a0c72efc0eb432dc66df546c6a54f2a7d396c9a34cf65ac1c26b1df8"}, + {file = "cytoolz-0.12.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4534cbfad73cdb1a6dad495530d4186d57d73089c01e9cb0558caab50e46cb3b"}, + {file = "cytoolz-0.12.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50db41e875e36aec11881b8b12bc69c6f4836b7dd9e88a9e5bbf26c2cb3ba6cd"}, + {file = "cytoolz-0.12.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6716855f9c669c9e25a185d88e0f169839bf8553d16496796325acd114607c11"}, + {file = "cytoolz-0.12.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f32452e833f0605b871626e6c61b71b0cba24233aad0e04accc3240497d4995"}, + {file = "cytoolz-0.12.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ba74c239fc6cb6e962eabc420967c7565f3f363b776c89b3df5234caecf1f463"}, + {file = "cytoolz-0.12.1.tar.gz", hash = "sha256:fc33909397481c90de3cec831bfb88d97e220dc91939d996920202f184b4648e"}, +] + +[package.dependencies] +toolz = ">=0.8.0" + +[package.extras] +cython = ["cython"] + +[[package]] +name = "eth-hash" +version = "0.5.1" +description = "eth-hash: The Ethereum hashing function, keccak256, sometimes (erroneously) called sha3" +category = "main" +optional = false +python-versions = ">=3.7, <4" +files = [ + {file = "eth-hash-0.5.1.tar.gz", hash = "sha256:9805075f653e114a31a99678e93b257fb4082337696f4eff7b4371fe65158409"}, + {file = "eth_hash-0.5.1-py3-none-any.whl", hash = "sha256:4d992e885f3ae3901abbe98bd776ba62d0f6335f98c6e9fc60a39b9d114dfb5a"}, +] + +[package.extras] +dev = ["Sphinx (>=5.0.0,<6)", "black (>=22.0,<23)", "bumpversion (>=0.5.3,<1)", "flake8 (==3.7.9)", "ipython", "isort (>=4.2.15,<5)", "jinja2 (>=3.0.0,<3.1.0)", "mypy (==0.961)", "pydocstyle (>=5.0.0,<6)", "pytest (>=6.2.5,<7)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist (>=2.4.0,<3)", "sphinx-rtd-theme (>=0.1.9,<1)", "towncrier (>=21,<22)", "tox (>=3.14.6,<4)", "twine", "wheel"] +doc = ["Sphinx (>=5.0.0,<6)", "jinja2 (>=3.0.0,<3.1.0)", "sphinx-rtd-theme (>=0.1.9,<1)", "towncrier (>=21,<22)"] +lint = ["black (>=22.0,<23)", "flake8 (==3.7.9)", "isort (>=4.2.15,<5)", "mypy (==0.961)", "pydocstyle (>=5.0.0,<6)"] +pycryptodome = ["pycryptodome (>=3.6.6,<4)"] +pysha3 = ["pysha3 (>=1.0.0,<2.0.0)", "safe-pysha3 (>=1.0.0)"] +test = ["pytest (>=6.2.5,<7)", "pytest-xdist (>=2.4.0,<3)", "tox (>=3.14.6,<4)"] + +[[package]] +name = "eth-typing" +version = "3.2.0" +description = "eth-typing: Common type annotations for ethereum python packages" +category = "main" +optional = false +python-versions = ">=3.6, <4" +files = [ + {file = "eth-typing-3.2.0.tar.gz", hash = "sha256:177e2070da9bf557fe0fd46ee467a7be2d0b6476aa4dc18680603e7da1fc5690"}, + {file = "eth_typing-3.2.0-py3-none-any.whl", hash = "sha256:2d7540c1c65c0e686c1dc357b8376a53caf4e1693724a90191ad133be568841d"}, +] + +[package.extras] +dev = ["bumpversion (>=0.5.3,<1)", "flake8 (==3.8.3)", "ipython", "isort (>=4.2.15,<5)", "mypy (==0.782)", "pydocstyle (>=3.0.0,<4)", "pytest (>=6.2.5,<7)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist", "sphinx (>=4.2.0,<5)", "sphinx-rtd-theme (>=0.1.9)", "towncrier (>=21,<22)", "tox (>=2.9.1,<3)", "twine", "wheel"] +doc = ["sphinx (>=4.2.0,<5)", "sphinx-rtd-theme (>=0.1.9)", "towncrier (>=21,<22)"] +lint = ["flake8 (==3.8.3)", "isort (>=4.2.15,<5)", "mypy (==0.782)", "pydocstyle (>=3.0.0,<4)"] +test = ["pytest (>=6.2.5,<7)", "pytest-xdist", "tox (>=2.9.1,<3)"] + +[[package]] +name = "eth-utils" +version = "2.1.0" +description = "eth-utils: Common utility functions for python code that interacts with Ethereum" +category = "main" +optional = false +python-versions = ">=3.7,<4" +files = [ + {file = "eth-utils-2.1.0.tar.gz", hash = "sha256:fcb4c3c1b32947ba92970963f9aaf40da73b04ea1034964ff8c0e70595127138"}, + {file = "eth_utils-2.1.0-py3-none-any.whl", hash = "sha256:63901e54ec9e4ac16ae0a0d28e1dc48b968c20184d22f2727e5f3ca24b6250bc"}, +] + +[package.dependencies] +cytoolz = {version = ">=0.10.1", markers = "implementation_name == \"cpython\""} +eth-hash = ">=0.3.1" +eth-typing = ">=3.0.0" +toolz = {version = ">0.8.2", markers = "implementation_name == \"pypy\""} + +[package.extras] +dev = ["Sphinx (>=1.6.5,<2)", "black (>=22)", "bumpversion (>=0.5.3,<1)", "flake8 (==3.7.9)", "hypothesis (>=4.43.0,<5.0.0)", "ipython", "isort (>=4.2.15,<5)", "jinja2 (>=3.0.0,<3.0.1)", "mypy (==0.910)", "pydocstyle (>=5.0.0,<6)", "pytest (>=6.2.5,<7)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist", "sphinx-rtd-theme (>=0.1.9,<2)", "towncrier (>=21,<22)", "tox (==3.14.6)", "twine (>=1.13,<2)", "types-setuptools", "wheel (>=0.30.0,<1.0.0)"] +doc = ["Sphinx (>=1.6.5,<2)", "jinja2 (>=3.0.0,<3.0.1)", "sphinx-rtd-theme (>=0.1.9,<2)", "towncrier (>=21,<22)"] +lint = ["black (>=22)", "flake8 (==3.7.9)", "isort (>=4.2.15,<5)", "mypy (==0.910)", "pydocstyle (>=5.0.0,<6)", "pytest (>=6.2.5,<7)", "types-setuptools"] +test = ["hypothesis (>=4.43.0,<5.0.0)", "pytest (>=6.2.5,<7)", "pytest-xdist", "tox (==3.14.6)", "types-setuptools"] + +[[package]] +name = "mypy" +version = "0.991" +description = "Optional static typing for Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mypy-0.991-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7d17e0a9707d0772f4a7b878f04b4fd11f6f5bcb9b3813975a9b13c9332153ab"}, + {file = "mypy-0.991-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0714258640194d75677e86c786e80ccf294972cc76885d3ebbb560f11db0003d"}, + {file = "mypy-0.991-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c8f3be99e8a8bd403caa8c03be619544bc2c77a7093685dcf308c6b109426c6"}, + {file = "mypy-0.991-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc9ec663ed6c8f15f4ae9d3c04c989b744436c16d26580eaa760ae9dd5d662eb"}, + {file = "mypy-0.991-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4307270436fd7694b41f913eb09210faff27ea4979ecbcd849e57d2da2f65305"}, + {file = "mypy-0.991-cp310-cp310-win_amd64.whl", hash = "sha256:901c2c269c616e6cb0998b33d4adbb4a6af0ac4ce5cd078afd7bc95830e62c1c"}, + {file = "mypy-0.991-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d13674f3fb73805ba0c45eb6c0c3053d218aa1f7abead6e446d474529aafc372"}, + {file = "mypy-0.991-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1c8cd4fb70e8584ca1ed5805cbc7c017a3d1a29fb450621089ffed3e99d1857f"}, + {file = "mypy-0.991-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:209ee89fbb0deed518605edddd234af80506aec932ad28d73c08f1400ef80a33"}, + {file = "mypy-0.991-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37bd02ebf9d10e05b00d71302d2c2e6ca333e6c2a8584a98c00e038db8121f05"}, + {file = "mypy-0.991-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:26efb2fcc6b67e4d5a55561f39176821d2adf88f2745ddc72751b7890f3194ad"}, + {file = "mypy-0.991-cp311-cp311-win_amd64.whl", hash = "sha256:3a700330b567114b673cf8ee7388e949f843b356a73b5ab22dd7cff4742a5297"}, + {file = "mypy-0.991-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1f7d1a520373e2272b10796c3ff721ea1a0712288cafaa95931e66aa15798813"}, + {file = "mypy-0.991-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:641411733b127c3e0dab94c45af15fea99e4468f99ac88b39efb1ad677da5711"}, + {file = "mypy-0.991-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3d80e36b7d7a9259b740be6d8d906221789b0d836201af4234093cae89ced0cd"}, + {file = "mypy-0.991-cp37-cp37m-win_amd64.whl", hash = "sha256:e62ebaad93be3ad1a828a11e90f0e76f15449371ffeecca4a0a0b9adc99abcef"}, + {file = "mypy-0.991-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b86ce2c1866a748c0f6faca5232059f881cda6dda2a893b9a8373353cfe3715a"}, + {file = "mypy-0.991-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac6e503823143464538efda0e8e356d871557ef60ccd38f8824a4257acc18d93"}, + {file = "mypy-0.991-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0cca5adf694af539aeaa6ac633a7afe9bbd760df9d31be55ab780b77ab5ae8bf"}, + {file = "mypy-0.991-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12c56bf73cdab116df96e4ff39610b92a348cc99a1307e1da3c3768bbb5b135"}, + {file = "mypy-0.991-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:652b651d42f155033a1967739788c436491b577b6a44e4c39fb340d0ee7f0d70"}, + {file = "mypy-0.991-cp38-cp38-win_amd64.whl", hash = "sha256:4175593dc25d9da12f7de8de873a33f9b2b8bdb4e827a7cae952e5b1a342e243"}, + {file = "mypy-0.991-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:98e781cd35c0acf33eb0295e8b9c55cdbef64fcb35f6d3aa2186f289bed6e80d"}, + {file = "mypy-0.991-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6d7464bac72a85cb3491c7e92b5b62f3dcccb8af26826257760a552a5e244aa5"}, + {file = "mypy-0.991-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c9166b3f81a10cdf9b49f2d594b21b31adadb3d5e9db9b834866c3258b695be3"}, + {file = "mypy-0.991-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8472f736a5bfb159a5e36740847808f6f5b659960115ff29c7cecec1741c648"}, + {file = "mypy-0.991-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e80e758243b97b618cdf22004beb09e8a2de1af481382e4d84bc52152d1c476"}, + {file = "mypy-0.991-cp39-cp39-win_amd64.whl", hash = "sha256:74e259b5c19f70d35fcc1ad3d56499065c601dfe94ff67ae48b85596b9ec1461"}, + {file = "mypy-0.991-py3-none-any.whl", hash = "sha256:de32edc9b0a7e67c2775e574cb061a537660e51210fbf6006b0b36ea695ae9bb"}, + {file = "mypy-0.991.tar.gz", hash = "sha256:3c0165ba8f354a6d9881809ef29f1a9318a236a6d81c690094c5df32107bde06"}, +] + +[package.dependencies] +mypy-extensions = ">=0.4.3" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=3.10" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +python2 = ["typed-ast (>=1.4.0,<2)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] + +[[package]] +name = "pathspec" +version = "0.10.3" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pathspec-0.10.3-py3-none-any.whl", hash = "sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6"}, + {file = "pathspec-0.10.3.tar.gz", hash = "sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6"}, +] + +[[package]] +name = "platformdirs" +version = "2.6.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-2.6.2-py3-none-any.whl", hash = "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490"}, + {file = "platformdirs-2.6.2.tar.gz", hash = "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2"}, +] + +[package.extras] +docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] + +[[package]] +name = "py-ecc" +version = "6.0.0" +description = "Elliptic curve crypto in python including secp256k1 and alt_bn128" +category = "main" +optional = false +python-versions = ">=3.6, <4" +files = [ + {file = "py_ecc-6.0.0-py3-none-any.whl", hash = "sha256:54e8aa4c30374fa62d582c599a99f352c153f2971352171318bd6910a643be0b"}, + {file = "py_ecc-6.0.0.tar.gz", hash = "sha256:3fc8a79e38975e05dc443d25783fd69212a1ca854cc0efef071301a8f7d6ce1d"}, +] + +[package.dependencies] +cached-property = ">=1.5.1,<2" +eth-typing = ">=3.0.0,<4" +eth-utils = ">=2.0.0,<3" +mypy-extensions = ">=0.4.1" + +[package.extras] +dev = ["bumpversion (>=0.5.3,<1)", "flake8 (==3.5.0)", "mypy (==0.641)", "mypy-extensions (>=0.4.1)", "pytest (==6.2.5)", "pytest-xdist (==1.26.0)", "twine"] +lint = ["flake8 (==3.5.0)", "mypy (==0.641)", "mypy-extensions (>=0.4.1)"] +test = ["pytest (==6.2.5)", "pytest-xdist (==1.26.0)"] + +[[package]] +name = "pycryptodome" +version = "3.16.0" +description = "Cryptographic library for Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "pycryptodome-3.16.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e061311b02cefb17ea93d4a5eb1ad36dca4792037078b43e15a653a0a4478ead"}, + {file = "pycryptodome-3.16.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:dab9359cc295160ba96738ba4912c675181c84bfdf413e5c0621cf00b7deeeaa"}, + {file = "pycryptodome-3.16.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:0198fe96c22f7bc31e7a7c27a26b2cec5af3cf6075d577295f4850856c77af32"}, + {file = "pycryptodome-3.16.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:58172080cbfaee724067a3c017add6a1a3cc167bbc8478dc5f2e5f45fa658763"}, + {file = "pycryptodome-3.16.0-cp27-cp27m-win32.whl", hash = "sha256:4d950ed2a887905b3fa709b86be5a163e26e1b174703ed59d34eb6832f213222"}, + {file = "pycryptodome-3.16.0-cp27-cp27m-win_amd64.whl", hash = "sha256:c69e19afc734b2a17b9d78b7bcb544aabd5a52ff628e14283b6e9404d27d0517"}, + {file = "pycryptodome-3.16.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:1fc16c80a5da8231fd1f953a7b8dfeb415f68120248e8d68383c5c2c4b18708c"}, + {file = "pycryptodome-3.16.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:5df582f2112dd72331de7e567837e136a9629181a8ab69ef8949e4bc294a0b99"}, + {file = "pycryptodome-3.16.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:2bf2a270906a02b7b255e1a0d7b3aea4f06b3983c51ddec1673c380e0dff5b30"}, + {file = "pycryptodome-3.16.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b12a88566a98617b1a34b4e5a805dff2da98d83fc74262aff3c3d724d0f525d6"}, + {file = "pycryptodome-3.16.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:69adf32522b75968e1cbf25b5d83e87c04cd9a55610ce1e4a19012e58e7e4023"}, + {file = "pycryptodome-3.16.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d67a2d2fe344953e4572a7d30668cceb516b04287b8638170d562065e53ee2e0"}, + {file = "pycryptodome-3.16.0-cp35-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e750a21d8a265b1f9bfb1a28822995ea33511ba7db5e2b55f41fb30781d0d073"}, + {file = "pycryptodome-3.16.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:47c71a0347847b747ba1349767b16cde049bc36f21654eb09cc82306ef5fdcf8"}, + {file = "pycryptodome-3.16.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:856ebf822d08d754af62c22e2b93626509a72773214f92db1551e2b68d9e2a1b"}, + {file = "pycryptodome-3.16.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6016269bb56caf0327f6d42e7bad1247e08b78407446dff562240c65f85d5a5e"}, + {file = "pycryptodome-3.16.0-cp35-abi3-win32.whl", hash = "sha256:1047ac2b9847ae84ea454e6e20db7dcb755a81c1b1631a879213d2b0ad835ff2"}, + {file = "pycryptodome-3.16.0-cp35-abi3-win_amd64.whl", hash = "sha256:13b3e610a2f8938c61a90b20625069ab7a77ccea20d65a9a0f926cc0cc1314b1"}, + {file = "pycryptodome-3.16.0-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:265bfcbbf20d58e6871ce695a7a08aac9b41a0553060d9c05363abd6f3391bdd"}, + {file = "pycryptodome-3.16.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:54d807314c66785c69cd25425933d4bd4c23547a593cdcf49d962fa3e0081336"}, + {file = "pycryptodome-3.16.0-pp27-pypy_73-win32.whl", hash = "sha256:63165fbdc247450017eb9ef04cfe15cb3a72ca48ffcc3a3b75b08c0340bf3647"}, + {file = "pycryptodome-3.16.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:95069fd9e2813668a2713a1efcc65cc26d2c7e741401ac46628f1ec957511f1b"}, + {file = "pycryptodome-3.16.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d1daec4d31bb00918e4e178297ac6ca6f86ec4c851ba584770533ece554d29e2"}, + {file = "pycryptodome-3.16.0-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:48d99869d58f3979d72f6fa0c50f48d16f14973bc4a3adb0ce3b8325fdd7e223"}, + {file = "pycryptodome-3.16.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:c82e3bc1e70dde153b0956bffe20a15715a1fe3e00bc23e88d6973eda4505944"}, + {file = "pycryptodome-3.16.0.tar.gz", hash = "sha256:0e45d2d852a66ecfb904f090c3f87dc0dfb89a499570abad8590f10d9cffb350"}, +] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "toolz" +version = "0.12.0" +description = "List processing tools and functional utilities" +category = "main" +optional = false +python-versions = ">=3.5" +files = [ + {file = "toolz-0.12.0-py3-none-any.whl", hash = "sha256:2059bd4148deb1884bb0eb770a3cde70e7f954cfbbdc2285f1f2de01fd21eb6f"}, + {file = "toolz-0.12.0.tar.gz", hash = "sha256:88c570861c440ee3f2f6037c4654613228ff40c93a6c25e0eba70d17282c6194"}, +] + +[[package]] +name = "typing-extensions" +version = "4.4.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, + {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, +] + +[metadata] +lock-version = "2.0" +python-versions = "^3.8" +content-hash = "faa85bd3e79515f2f7007294a2ab8e352cd058bf125d67363bf274bc80f62d7b" diff --git a/prover.py b/prover.py index 877f87e..a0e65c9 100644 --- a/prover.py +++ b/prover.py @@ -7,14 +7,10 @@ from transcript import Transcript from curve import Scalar + @dataclass class Prover: - def prove( - self, - setup: Setup, - program: Program, - witness: dict[Optional[str], int] - ): + def prove(self, setup: Setup, program: Program, witness: dict[Optional[str], int]): self.group_order = program.group_order proof = {} @@ -29,23 +25,23 @@ def prove( # - [b(x)]₁ (commitment to right wire polynomial) # - [c(x)]₁ (commitment to output wire polynomial) a_1, b_1, c_1 = self.round_1(program, witness, transcript, setup) - proof['a_1'] = a_1 - proof['b_1'] = b_1 - proof['c_1'] = c_1 + proof["a_1"] = a_1 + proof["b_1"] = b_1 + proof["c_1"] = c_1 # Round 2 # - [z(x)]₁ (commitment to permutation polynomial) z_1 = self.round_2(transcript, setup) - proof['z_1'] = z_1 + proof["z_1"] = z_1 # Round 3 # - [t_lo(x)]₁ (commitment to t_lo(X), the low chunk of the quotient polynomial t(X)) # - [t_mid(x)]₁ (commitment to t_mid(X), the middle chunk of the quotient polynomial t(X)) # - [t_hi(x)]₁ (commitment to t_hi(X), the high chunk of the quotient polynomial t(X)) t_lo_1, t_mid_1, t_hi_1 = self.round_3(transcript, setup) - proof['t_lo_1'] = t_lo_1 - proof['t_mid_1'] = t_mid_1 - proof['t_hi_1'] = t_hi_1 + proof["t_lo_1"] = t_lo_1 + proof["t_mid_1"] = t_mid_1 + proof["t_hi_1"] = t_hi_1 # Round 4 # - Evaluation of a(X) at evaluation challenge ζ @@ -54,28 +50,26 @@ def prove( # - Evaluation of the first permutation polynomial S_σ1(X) at evaluation challenge ζ # - Evaluation of the second permutation polynomial S_σ2(X) at evaluation challenge ζ # - Evaluation of the shifted permutation polynomial z(X) at the shifted evaluation challenge ζω - a_eval, b_eval, c_eval, s1_eval, s2_eval, z_shifted_eval = self.round_4(transcript) - proof['a_eval'] = a_eval - proof['b_eval'] = b_eval - proof['c_eval'] = c_eval - proof['s1_eval'] = s1_eval - proof['s2_eval'] = s2_eval - proof['z_shifted_eval'] = z_shifted_eval + a_eval, b_eval, c_eval, s1_eval, s2_eval, z_shifted_eval = self.round_4( + transcript + ) + proof["a_eval"] = a_eval + proof["b_eval"] = b_eval + proof["c_eval"] = c_eval + proof["s1_eval"] = s1_eval + proof["s2_eval"] = s2_eval + proof["z_shifted_eval"] = z_shifted_eval # Round 5 # - [W_ζ(X)]₁ (commitment to the opening proof polynomial) # - [W_ζω(X)]₁ (commitment to the opening proof polynomial) W_z_1, W_zw_1 = self.round_5(transcript, setup) - proof['W_z_1'] = W_z_1 - proof['W_zw_1'] = W_zw_1 + proof["W_z_1"] = W_z_1 + proof["W_zw_1"] = W_zw_1 return proof - def init( - self, - program: Program, - witness: dict[Optional[str], int] - ): + def init(self, program: Program, witness: dict[Optional[str], int]): group_order = self.group_order QL, QR, QM, QO, QC = program.make_gate_polynomials() @@ -86,10 +80,9 @@ def init( S3 = S[Column.OUTPUT] public_vars = program.get_public_assignments() - PI = ( - [Scalar(-witness[v]) for v in public_vars] + - [Scalar(0) for _ in range(group_order - len(public_vars))] - ) + PI = [Scalar(-witness[v]) for v in public_vars] + [ + Scalar(0) for _ in range(group_order - len(public_vars)) + ] self.QL = QL self.QR = QR @@ -105,7 +98,8 @@ def round_1( self, program: Program, witness: dict[Optional[str], int], - transcript: Transcript, setup: Setup + transcript: Transcript, + setup: Setup, ): group_order = self.group_order @@ -138,8 +132,13 @@ def round_1( # Sanity check that witness fulfils gate constraints for i in range(group_order): assert ( - A[i] * self.QL[i] + B[i] * self.QR[i] + A[i] * B[i] * self.QM[i] + - C[i] * self.QO[i] + self.PI[i] + self.QC[i] == 0 + A[i] * self.QL[i] + + B[i] * self.QR[i] + + A[i] * B[i] * self.QM[i] + + C[i] * self.QO[i] + + self.PI[i] + + self.QC[i] + == 0 ) return a_1, b_1, c_1 @@ -164,27 +163,29 @@ def round_2( roots_of_unity = get_roots_of_unity(group_order) for i in range(group_order): Z.append( - Z[-1] * - (self.A[i] + beta * roots_of_unity[i] + gamma) * - (self.B[i] + beta * 2 * roots_of_unity[i] + gamma) * - (self.C[i] + beta * 3 * roots_of_unity[i] + gamma) / - (self.A[i] + beta * self.S1[i] + gamma) / - (self.B[i] + beta * self.S2[i] + gamma) / - (self.C[i] + beta * self.S3[i] + gamma) + Z[-1] + * (self.A[i] + beta * roots_of_unity[i] + gamma) + * (self.B[i] + beta * 2 * roots_of_unity[i] + gamma) + * (self.C[i] + beta * 3 * roots_of_unity[i] + gamma) + / (self.A[i] + beta * self.S1[i] + gamma) + / (self.B[i] + beta * self.S2[i] + gamma) + / (self.C[i] + beta * self.S3[i] + gamma) ) assert Z.pop() == 1 # Sanity-check that Z was computed correctly for i in range(group_order): assert ( - self.rlc(self.A[i], roots_of_unity[i]) * - self.rlc(self.B[i], 2 * roots_of_unity[i]) * - self.rlc(self.C[i], 3 * roots_of_unity[i]) + self.rlc(self.A[i], roots_of_unity[i]) + * self.rlc(self.B[i], 2 * roots_of_unity[i]) + * self.rlc(self.C[i], 3 * roots_of_unity[i]) ) * Z[i] - ( - self.rlc(self.A[i], self.S1[i]) * - self.rlc(self.B[i], self.S2[i]) * - self.rlc(self.C[i], self.S3[i]) - ) * Z[(i+1) % group_order] == 0 + self.rlc(self.A[i], self.S1[i]) + * self.rlc(self.B[i], self.S2[i]) + * self.rlc(self.C[i], self.S3[i]) + ) * Z[ + (i + 1) % group_order + ] == 0 z_1 = setup.commit(Z) transcript.hash_point(z_1) @@ -216,12 +217,13 @@ def round_3(self, transcript: Transcript, setup: Setup): C_big = self.fft_expand(self.C) # Z_H = X^N - 1, also in evaluation form in the coset ZH_big = [ - ((Scalar(r) * fft_cofactor) ** group_order - 1) - for r in quarter_roots + ((Scalar(r) * fft_cofactor) ** group_order - 1) for r in quarter_roots ] - QL_big, QR_big, QM_big, QO_big, QC_big, PI_big = \ - (self.fft_expand(x) for x in (self.QL, self.QR, self.QM, self.QO, self.QC, self.PI)) + QL_big, QR_big, QM_big, QO_big, QC_big, PI_big = ( + self.fft_expand(x) + for x in (self.QL, self.QR, self.QM, self.QO, self.QC, self.PI) + ) Z_big = self.fft_expand(self.Z) Z_shifted_big = Z_big[4:] + Z_big[:4] @@ -247,47 +249,61 @@ def round_3(self, transcript: Transcript, setup: Setup): # (Z - 1) * L1 = 0 # L1 = Lagrange polynomial, equal at all roots of unity except 1 + assert transcript.beta is not None + assert transcript.gamma is not None + beta = transcript.beta gamma = transcript.gamma - QUOT_big = [(( - A_big[i] * QL_big[i] + - B_big[i] * QR_big[i] + - A_big[i] * B_big[i] * QM_big[i] + - C_big[i] * QO_big[i] + - PI_big[i] + - QC_big[i] - ) + ( - (A_big[i] + beta * fft_cofactor * quarter_roots[i] + gamma) * - (B_big[i] + beta * 2 * fft_cofactor * quarter_roots[i] + gamma) * - (C_big[i] + beta * 3 * fft_cofactor * quarter_roots[i] + gamma) - ) * alpha * Z_big[i] - ( - (A_big[i] + beta * S1_big[i] + gamma) * - (B_big[i] + beta * S2_big[i] + gamma) * - (C_big[i] + beta * S3_big[i] + gamma) - ) * alpha * Z_shifted_big[i] + ( - (Z_big[i] - 1) * L1_big[i] * alpha**2 - )) / ZH_big[i] for i in range(group_order * 4)] + QUOT_big = [ + ( + ( + A_big[i] * QL_big[i] + + B_big[i] * QR_big[i] + + A_big[i] * B_big[i] * QM_big[i] + + C_big[i] * QO_big[i] + + PI_big[i] + + QC_big[i] + ) + + ( + (A_big[i] + beta * fft_cofactor * quarter_roots[i] + gamma) + * (B_big[i] + beta * 2 * fft_cofactor * quarter_roots[i] + gamma) + * (C_big[i] + beta * 3 * fft_cofactor * quarter_roots[i] + gamma) + ) + * alpha + * Z_big[i] + - ( + (A_big[i] + beta * S1_big[i] + gamma) + * (B_big[i] + beta * S2_big[i] + gamma) + * (C_big[i] + beta * S3_big[i] + gamma) + ) + * alpha + * Z_shifted_big[i] + + ((Z_big[i] - 1) * L1_big[i] * alpha**2) + ) + / ZH_big[i] + for i in range(group_order * 4) + ] all_coeffs = self.expanded_evals_to_coeffs(QUOT_big) # Sanity check: QUOT has degree < 3n assert ( - self.expanded_evals_to_coeffs(QUOT_big)[-group_order:] == - [0] * group_order + self.expanded_evals_to_coeffs(QUOT_big)[-group_order:] == [0] * group_order ) print("Generated the quotient polynomial") # Split up T into T1, T2 and T3 (needed because T has degree 3n, so is # too big for the trusted setup) T1 = f_inner_fft(all_coeffs[:group_order]) - T2 = f_inner_fft(all_coeffs[group_order: group_order * 2]) - T3 = f_inner_fft(all_coeffs[group_order * 2: group_order * 3]) + T2 = f_inner_fft(all_coeffs[group_order : group_order * 2]) + T3 = f_inner_fft(all_coeffs[group_order * 2 : group_order * 3]) # Sanity check that we've computed T1, T2, T3 correctly assert ( - barycentric_eval_at_point(T1, fft_cofactor) + - barycentric_eval_at_point(T2, fft_cofactor) * fft_cofactor**group_order + - barycentric_eval_at_point(T3, fft_cofactor) * fft_cofactor**(group_order*2) + barycentric_eval_at_point(T1, fft_cofactor) + + barycentric_eval_at_point(T2, fft_cofactor) * fft_cofactor**group_order + + barycentric_eval_at_point(T3, fft_cofactor) + * fft_cofactor ** (group_order * 2) ) == QUOT_big[0] print("Generated T1, T2, T3 polynomials") @@ -361,51 +377,68 @@ def round_5(self, transcript: Transcript, setup: Setup): v = transcript.squeeze() transcript.v = v + assert transcript.zed is not None zed = transcript.zed L1_ev = barycentric_eval_at_point([1] + [0] * (group_order - 1), zed) - ZH_ev = zed ** group_order - 1 + ZH_ev = zed**group_order - 1 PI_ev = barycentric_eval_at_point(self.PI, zed) T1_big = self.fft_expand(self.T1) T2_big = self.fft_expand(self.T2) T3_big = self.fft_expand(self.T3) - QL_big, QR_big, QM_big, QO_big, QC_big, PI_big = \ - (self.fft_expand(x) for x in (self.QL, self.QR, self.QM, self.QO, self.QC, self.PI)) + QL_big, QR_big, QM_big, QO_big, QC_big, PI_big = ( + self.fft_expand(x) + for x in (self.QL, self.QR, self.QM, self.QO, self.QC, self.PI) + ) Z_big = self.fft_expand(self.Z) S3_big = self.fft_expand(self.S3) + assert transcript.beta is not None + assert transcript.gamma is not None + assert transcript.alpha is not None + beta = transcript.beta gamma = transcript.gamma alpha = transcript.alpha - R_big = [( - self.a_eval * QL_big[i] + - self.b_eval * QR_big[i] + - self.a_eval * self.b_eval * QM_big[i] + - self.c_eval * QO_big[i] + - PI_ev + - QC_big[i] - ) + ( - (self.a_eval + beta * zed + gamma) * - (self.b_eval + beta * 2 * zed + gamma) * - (self.c_eval + beta * 3 * zed + gamma) - ) * alpha * Z_big[i] - ( - (self.a_eval + beta * self.s1_eval + gamma) * - (self.b_eval + beta * self.s2_eval + gamma) * - (self.c_eval + beta * S3_big[i] + gamma) - ) * alpha * self.z_shifted_eval + ( - (Z_big[i] - 1) * L1_ev - ) * alpha**2 - ( - T1_big[i] + - zed ** group_order * T2_big[i] + - zed ** (group_order * 2) * T3_big[i] - ) * ZH_ev for i in range(4 * group_order)] + R_big = [ + ( + self.a_eval * QL_big[i] + + self.b_eval * QR_big[i] + + self.a_eval * self.b_eval * QM_big[i] + + self.c_eval * QO_big[i] + + PI_ev + + QC_big[i] + ) + + ( + (self.a_eval + beta * zed + gamma) + * (self.b_eval + beta * 2 * zed + gamma) + * (self.c_eval + beta * 3 * zed + gamma) + ) + * alpha + * Z_big[i] + - ( + (self.a_eval + beta * self.s1_eval + gamma) + * (self.b_eval + beta * self.s2_eval + gamma) + * (self.c_eval + beta * S3_big[i] + gamma) + ) + * alpha + * self.z_shifted_eval + + ((Z_big[i] - 1) * L1_ev) * alpha**2 + - ( + T1_big[i] + + zed**group_order * T2_big[i] + + zed ** (group_order * 2) * T3_big[i] + ) + * ZH_ev + for i in range(4 * group_order) + ] R_coeffs = self.expanded_evals_to_coeffs(R_big) assert R_coeffs[group_order:] == [0] * (group_order * 3) R = f_inner_fft(R_coeffs[:group_order]) - print('R_pt', setup.commit(R)) + print("R_pt", setup.commit(R)) assert barycentric_eval_at_point(R, zed) == 0 @@ -417,8 +450,10 @@ def round_5(self, transcript: Transcript, setup: Setup): B_big = self.fft_expand(self.B) C_big = self.fft_expand(self.C) - QL_big, QR_big, QM_big, QO_big, QC_big, PI_big = \ - (self.fft_expand(x) for x in (self.QL, self.QR, self.QM, self.QO, self.QC, self.PI)) + QL_big, QR_big, QM_big, QO_big, QC_big, PI_big = ( + self.fft_expand(x) + for x in (self.QL, self.QR, self.QM, self.QO, self.QC, self.PI) + ) S1_big = self.fft_expand(self.S1) S2_big = self.fft_expand(self.S2) S3_big = self.fft_expand(self.S3) @@ -426,14 +461,18 @@ def round_5(self, transcript: Transcript, setup: Setup): roots_of_unity = get_roots_of_unity(group_order) quarter_roots = get_roots_of_unity(group_order * 4) - W_z_big = [( - R_big[i] + - v * (A_big[i] - self.a_eval) + - v**2 * (B_big[i] - self.b_eval) + - v**3 * (C_big[i] - self.c_eval) + - v**4 * (S1_big[i] - self.s1_eval) + - v**5 * (S2_big[i] - self.s2_eval) - ) / (transcript.fft_cofactor * quarter_roots[i] - zed) for i in range(group_order * 4)] + W_z_big = [ + ( + R_big[i] + + v * (A_big[i] - self.a_eval) + + v**2 * (B_big[i] - self.b_eval) + + v**3 * (C_big[i] - self.c_eval) + + v**4 * (S1_big[i] - self.s1_eval) + + v**5 * (S2_big[i] - self.s2_eval) + ) + / (transcript.fft_cofactor * quarter_roots[i] - zed) + for i in range(group_order * 4) + ] W_z_coeffs = self.expanded_evals_to_coeffs(W_z_big) assert W_z_coeffs[group_order:] == [0] * (group_order * 3) @@ -446,9 +485,10 @@ def round_5(self, transcript: Transcript, setup: Setup): # coordinates, and not just within one coordinate. W_zw_big = [ - (Z_big[i] - self.z_shifted_eval) / - (transcript.fft_cofactor * quarter_roots[i] - zed * roots_of_unity[1]) - for i in range(group_order * 4)] + (Z_big[i] - self.z_shifted_eval) + / (transcript.fft_cofactor * quarter_roots[i] - zed * roots_of_unity[1]) + for i in range(group_order * 4) + ] W_zw_coeffs = self.expanded_evals_to_coeffs(W_zw_big) assert W_zw_coeffs[group_order:] == [0] * (group_order * 3) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..225f1a8 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,23 @@ +[tool.poetry] +name = "plonkathon" +version = "0.1.0" +description = "A simple Python implementation of PLONK adapted from py_plonk" +authors = ["0xPARC / Vitalik Buterin"] +license = "MIT" +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.8" +py-ecc = "^6.0.0" +pycryptodome = "^3.16.0" + +[tool.poetry.group.dev.dependencies] +mypy = "^0.991" +black = "^22.12.0" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.mypy] +explicit_package_bases = true \ No newline at end of file diff --git a/setup.py b/setup.py index c24cfdd..9c17719 100644 --- a/setup.py +++ b/setup.py @@ -3,14 +3,15 @@ from typing import NewType from curve import ec_lincomb, Scalar -G1Point = NewType('G1Point', tuple[b.FQ, b.FQ]) -G2Point = NewType('G2Point', tuple[b.FQ2, b.FQ2]) +G1Point = NewType("G1Point", tuple[b.FQ, b.FQ]) +G2Point = NewType("G2Point", tuple[b.FQ2, b.FQ2]) # Recover the trusted setup from a file in the format used in # https://github.com/iden3/snarkjs#7-prepare-phase-2 SETUP_FILE_G1_STARTPOS = 80 SETUP_FILE_POWERS_POS = 60 -Commitment = NewType('Commitment', G1Point) +Commitment = NewType("Commitment", G1Point) + class Setup(object): # ([1]₁, [x]₁, ..., [x^{d-1}]₁) @@ -25,14 +26,15 @@ def __init__(self, G1_side: list[G1Point], X2: G2Point): @classmethod def from_file(cls, filename): - contents = open(filename, 'rb').read() + contents = open(filename, "rb").read() # Byte 60 gives you the base-2 log of how many powers there are - powers = 2**contents[SETUP_FILE_POWERS_POS] + powers = 2 ** contents[SETUP_FILE_POWERS_POS] # Extract G1 points, which start at byte 80 values = [ - int.from_bytes(contents[i: i+32], 'little') - for i in range(SETUP_FILE_G1_STARTPOS, - SETUP_FILE_G1_STARTPOS + 32 * powers * 2, 32) + int.from_bytes(contents[i : i + 32], "little") + for i in range( + SETUP_FILE_G1_STARTPOS, SETUP_FILE_G1_STARTPOS + 32 * powers * 2, 32 + ) ] assert max(values) < b.field_modulus # The points are encoded in a weird encoding, where all x and y points @@ -40,21 +42,21 @@ def from_file(cls, filename): # extract the factor because we know the first point is the generator. factor = b.FQ(values[0]) / b.G1[0] values = [b.FQ(x) / factor for x in values] - G1_side = [(values[i*2], values[i*2+1]) for i in range(powers)] + G1_side = [(values[i * 2], values[i * 2 + 1]) for i in range(powers)] print("Extracted G1 side, X^1 point: {}".format(G1_side[1])) # Search for start of G2 points. We again know that the first point is # the generator. pos = SETUP_FILE_G1_STARTPOS + 32 * powers * 2 target = (factor * b.G2[0].coeffs[0]).n while pos < len(contents): - v = int.from_bytes(contents[pos: pos+32], 'little') + v = int.from_bytes(contents[pos : pos + 32], "little") if v == target: break pos += 1 print("Detected start of G2 side at byte {}".format(pos)) - X2_encoding = contents[pos + 32 * 4: pos + 32 * 8] + X2_encoding = contents[pos + 32 * 4 : pos + 32 * 8] X2_values = [ - b.FQ(int.from_bytes(X2_encoding[i: i + 32], 'little')) / factor + b.FQ(int.from_bytes(X2_encoding[i : i + 32], "little")) / factor for i in range(0, 128, 32) ] X2 = (b.FQ2(X2_values[:2]), b.FQ2(X2_values[2:])) @@ -69,5 +71,5 @@ def commit(self, values) -> Commitment: # inverse FFT from Lagrange basis to monomial basis coeffs = f_inner_fft(values, inv=True) if len(coeffs) > len(self.G1_side): - raise Exception("Not enough powers in setup") + raise Exception("Not enough powers in setup") return ec_lincomb([(s, x) for s, x in zip(self.G1_side, coeffs)]) diff --git a/test.py b/test.py index a37f35a..15fe33e 100644 --- a/test.py +++ b/test.py @@ -6,22 +6,27 @@ from test.mini_poseidon import rc, mds, poseidon_hash from utils import * + def basic_test(): # Extract 2^28 powers of tau - setup = Setup.from_file('test/powersOfTau28_hez_final_11.ptau') + setup = Setup.from_file("test/powersOfTau28_hez_final_11.ptau") print("Extracted setup") - program = Program(['c <== a * b'], 8) + program = Program(["c <== a * b"], 8) vk = VerificationKey.make_verification_key(program, setup) print("Generated verification key") - their_output = json.load(open('test/main.plonk.vkey.json')) - for key in ('Qm', 'Ql', 'Qr', 'Qo', 'Qc', 'S1', 'S2', 'S3', 'X_2'): + their_output = json.load(open("test/main.plonk.vkey.json")) + for key in ("Qm", "Ql", "Qr", "Qo", "Qc", "S1", "S2", "S3", "X_2"): if interpret_json_point(their_output[key]) != getattr(vk, key): - raise Exception("Mismatch {}: ours {} theirs {}" - .format(key, getattr(vk, key), their_output[key])) - assert getattr(vk, 'w') == int(their_output['w']) + raise Exception( + "Mismatch {}: ours {} theirs {}".format( + key, getattr(vk, key), their_output[key] + ) + ) + assert getattr(vk, "w") == int(their_output["w"]) print("Basic test success") return setup + # Equivalent to this zkrepl code: # # template Example () { @@ -31,45 +36,55 @@ def basic_test(): # c <== a * b + a; # } def ab_plus_a_test(setup): - program = Program(['ab === a - c', '-ab === a * b'], 8) + program = Program(["ab === a - c", "-ab === a * b"], 8) vk = VerificationKey.make_verification_key(program, setup) print("Generated verification key") - their_output = json.load(open('test/main.plonk.vkey-58.json')) - for key in ('Qm', 'Ql', 'Qr', 'Qo', 'Qc', 'S1', 'S2', 'S3', 'X_2'): + their_output = json.load(open("test/main.plonk.vkey-58.json")) + for key in ("Qm", "Ql", "Qr", "Qo", "Qc", "S1", "S2", "S3", "X_2"): if interpret_json_point(their_output[key]) != getattr(vk, key): - raise Exception("Mismatch {}: ours {} theirs {}" - .format(key, getattr(vk, key), their_output[key])) - assert getattr(vk, 'w') == int(their_output['w']) + raise Exception( + "Mismatch {}: ours {} theirs {}".format( + key, getattr(vk, key), their_output[key] + ) + ) + assert getattr(vk, "w") == int(their_output["w"]) print("ab+a test success") + def one_public_input_test(setup): - program = Program(['c public', 'c === a * b'], 8) + program = Program(["c public", "c === a * b"], 8) vk = VerificationKey.make_verification_key(program, setup) print("Generated verification key") - their_output = json.load(open('test/main.plonk.vkey-59.json')) - for key in ('Qm', 'Ql', 'Qr', 'Qo', 'Qc', 'S1', 'S2', 'S3', 'X_2'): + their_output = json.load(open("test/main.plonk.vkey-59.json")) + for key in ("Qm", "Ql", "Qr", "Qo", "Qc", "S1", "S2", "S3", "X_2"): if interpret_json_point(their_output[key]) != getattr(vk, key): - raise Exception("Mismatch {}: ours {} theirs {}" - .format(key, getattr(vk, key), their_output[key])) - assert getattr(vk, 'w') == int(their_output['w']) + raise Exception( + "Mismatch {}: ours {} theirs {}".format( + key, getattr(vk, key), their_output[key] + ) + ) + assert getattr(vk, "w") == int(their_output["w"]) print("One public input test success") + def prover_test(setup): print("Beginning prover test") - program = Program(['e public', 'c <== a * b', 'e <== c * d'], 8) - assignments = {'a': 3, 'b': 4, 'c': 12, 'd': 5, 'e': 60} + program = Program(["e public", "c <== a * b", "e <== c * d"], 8) + assignments = {"a": 3, "b": 4, "c": 12, "d": 5, "e": 60} return Prover().prove(setup, program, assignments) print("Prover test success") + def verifier_test(setup, proof): print("Beginning verifier test") - program = Program(['e public', 'c <== a * b', 'e <== c * d'], 8) + program = Program(["e public", "c <== a * b", "e <== c * d"], 8) public = [60] vk = VerificationKey.make_verification_key(program, setup) assert vk.verify_proof(8, proof, public, optimized=False) assert vk.verify_proof(8, proof, public, optimized=True) print("Verifier test success") + def factorization_test(setup): print("Beginning test: prove you know small integers that multiply to 91") program = Program.from_str( @@ -89,44 +104,54 @@ def factorization_test(setup): qb012 <== qb01 + 4 * qb2 q <== qb012 + 8 * qb3 n <== p * q""", - 16 + 16, ) public = [91] vk = VerificationKey.make_verification_key(program, setup) print("Generated verification key") - assignments = program.fill_variable_assignments({ - 'pb3': 1, 'pb2': 1, 'pb1': 0, 'pb0': 1, - 'qb3': 0, 'qb2': 1, 'qb1': 1, 'qb0': 1, - }) + assignments = program.fill_variable_assignments( + { + "pb3": 1, + "pb2": 1, + "pb1": 0, + "pb0": 1, + "qb3": 0, + "qb2": 1, + "qb1": 1, + "qb0": 1, + } + ) proof = Prover().prove(setup, program, assignments) print("Generated proof") assert vk.verify_proof(16, proof, public, optimized=True) print("Factorization test success!") + def output_proof_lang() -> str: o = [] - o.append('L0 public') - o.append('M0 public') - o.append('M64 public') - o.append('R0 <== 0') + o.append("L0 public") + o.append("M0 public") + o.append("M64 public") + o.append("R0 <== 0") for i in range(64): - for j, pos in enumerate(('L', 'M', 'R')): - f = {'x': i, 'r': rc[i][j], 'p': pos} - if i < 4 or i >= 60 or pos == 'L': - o.append('{p}adj{x} <== {p}{x} + {r}'.format(**f)) - o.append('{p}sq{x} <== {p}adj{x} * {p}adj{x}'.format(**f)) - o.append('{p}qd{x} <== {p}sq{x} * {p}sq{x}'.format(**f)) - o.append('{p}qn{x} <== {p}qd{x} * {p}adj{x}'.format(**f)) + for j, pos in enumerate(("L", "M", "R")): + f = {"x": i, "r": rc[i][j], "p": pos} + if i < 4 or i >= 60 or pos == "L": + o.append("{p}adj{x} <== {p}{x} + {r}".format(**f)) + o.append("{p}sq{x} <== {p}adj{x} * {p}adj{x}".format(**f)) + o.append("{p}qd{x} <== {p}sq{x} * {p}sq{x}".format(**f)) + o.append("{p}qn{x} <== {p}qd{x} * {p}adj{x}".format(**f)) else: - o.append('{p}qn{x} <== {p}{x} + {r}'.format(**f)) - for j, pos in enumerate(('L', 'M', 'R')): - f = {'x': i, 'p': pos, 'm': mds[j]} - o.append('{p}suma{x} <== Lqn{x} * {m}'.format(**f)) - f = {'x': i, 'p': pos, 'm': mds[j+1]} - o.append('{p}sumb{x} <== {p}suma{x} + Mqn{x} * {m}'.format(**f)) - f = {'x': i, 'xp1': i+1, 'p': pos, 'm': mds[j+2]} - o.append('{p}{xp1} <== {p}sumb{x} + Rqn{x} * {m}'.format(**f)) - return '\n'.join(o) + o.append("{p}qn{x} <== {p}{x} + {r}".format(**f)) + for j, pos in enumerate(("L", "M", "R")): + f = {"x": i, "p": pos, "m": mds[j]} + o.append("{p}suma{x} <== Lqn{x} * {m}".format(**f)) + f = {"x": i, "p": pos, "m": mds[j + 1]} + o.append("{p}sumb{x} <== {p}suma{x} + Mqn{x} * {m}".format(**f)) + f = {"x": i, "xp1": i + 1, "p": pos, "m": mds[j + 2]} + o.append("{p}{xp1} <== {p}sumb{x} + Rqn{x} * {m}".format(**f)) + return "\n".join(o) + def poseidon_test(setup): # PLONK-prove the correctness of a Poseidon execution. Note that this is @@ -136,14 +161,16 @@ def poseidon_test(setup): # Generate code for proof program = Program.from_str(output_proof_lang(), 1024) print("Generated code for Poseidon test") - assignments = program.fill_variable_assignments({'L0': 1, 'M0': 2}) + assignments = program.fill_variable_assignments({"L0": 1, "M0": 2}) vk = VerificationKey.make_verification_key(program, setup) print("Generated verification key") proof = Prover().prove(setup, program, assignments) print("Generated proof") assert vk.verify_proof(1024, proof, [1, 2, expected_value]) print("Verified proof!") -if __name__ == '__main__': + + +if __name__ == "__main__": setup = basic_test() ab_plus_a_test(setup) one_public_input_test(setup) diff --git a/test/mini_poseidon.py b/test/mini_poseidon.py index c2f350c..bb4b7ff 100644 --- a/test/mini_poseidon.py +++ b/test/mini_poseidon.py @@ -2,9 +2,11 @@ from py_ecc import bn128 as b import json + class f_inner(Field): field_modulus = b.curve_order + # Mimics the Poseidon hash for params: # # p = b.curve_order @@ -20,11 +22,12 @@ class f_inner(Field): rc = [ [f_inner(a), f_inner(b), f_inner(c)] - for (a,b,c) in json.load(open('test/poseidon_rc.json')) + for (a, b, c) in json.load(open("test/poseidon_rc.json")) ] mds = [f_inner(1) / i for i in range(3, 8)] + def poseidon_hash(in1, in2): L, M, R = f_inner(in1), f_inner(in2), f_inner(0) for i in range(64): @@ -32,8 +35,8 @@ def poseidon_hash(in1, in2): M += rc[i][1] R += rc[i][2] if i < 4 or i >= 60: - M = M ** 5 - R = R ** 5 + M = M**5 + R = R**5 (L, M, R) = ( (L * mds[0] + M * mds[1] + R * mds[2]), diff --git a/transcript.py b/transcript.py index a7afa56..d8b0ec0 100644 --- a/transcript.py +++ b/transcript.py @@ -3,6 +3,7 @@ from curve import Scalar from setup import G1Point + class Transcript: beta: Optional[Scalar] = None gamma: Optional[Scalar] = None @@ -15,14 +16,14 @@ def __init__(self): self.state = keccak.new(digest_bits=256) def hash_scalar(self, scalar: Scalar): - string = scalar.n.to_bytes(32, 'big') + string = scalar.n.to_bytes(32, "big") self.state.update(string) def hash_point(self, point: G1Point): - string = point[0].n.to_bytes(32, 'big') + point[1].n.to_bytes(32, 'big') + string = point[0].n.to_bytes(32, "big") + point[1].n.to_bytes(32, "big") self.state.update(string) def squeeze(self): digest = self.state.digest() self.state = keccak.new(digest_bits=256).update(digest) - return Scalar(int.from_bytes(digest, 'big')) + return Scalar(int.from_bytes(digest, "big")) diff --git a/utils.py b/utils.py index f353ed1..f9c539b 100644 --- a/utils.py +++ b/utils.py @@ -6,9 +6,11 @@ f = b.FQ f2 = b.FQ2 + class f_inner(Field): field_modulus = b.curve_order + primitive_root = 5 # Gets the first root of unity of a given group order @@ -16,6 +18,7 @@ class f_inner(Field): def get_root_of_unity(group_order) -> f_inner: return f_inner(5) ** ((b.curve_order - 1) // group_order) + # Gets the full list of roots of unity of a given group order @cache def get_roots_of_unity(group_order: int) -> list[f_inner]: @@ -24,24 +27,30 @@ def get_roots_of_unity(group_order: int) -> list[f_inner]: o.append(o[-1] * o[1]) return o + def keccak256(x): return keccak.new(digest_bits=256).update(x).digest() + def serialize_int(x): - return x.n.to_bytes(32, 'big') + return x.n.to_bytes(32, "big") + def serialize_point(pt): - return pt[0].n.to_bytes(32, 'big') + pt[1].n.to_bytes(32, 'big') + return pt[0].n.to_bytes(32, "big") + pt[1].n.to_bytes(32, "big") + # Converts a hash to a f_inner element def binhash_to_f_inner(h): - return f_inner(int.from_bytes(h, 'big')) + return f_inner(int.from_bytes(h, "big")) + def ec_mul(pt, coeff): - if hasattr(coeff, 'n'): + if hasattr(coeff, "n"): coeff = coeff.n return b.multiply(pt, coeff % b.curve_order) + # Elliptic curve linear combination. A truly optimized implementation # would replace this with a fast lin-comb algo, see https://ethresear.ch/t/7238 def ec_lincomb(pairs): @@ -49,7 +58,7 @@ def ec_lincomb(pairs): [pt for (pt, _) in pairs], [int(n) % b.curve_order for (_, n) in pairs], b.add, - b.Z1 + b.Z1, ) # Equivalent to: # o = b.Z1 @@ -57,6 +66,7 @@ def ec_lincomb(pairs): # o = b.add(o, ec_mul(pt, coeff)) # return o + # Extracts a point from JSON in zkrepl's format def interpret_json_point(p): if len(p) == 3 and isinstance(p[0], str) and p[2] == "1": @@ -72,6 +82,7 @@ def interpret_json_point(p): return b.Z2 raise Exception("cannot interpret that point: {}".format(p)) + # Fast Fourier transform, used to convert between polynomial coefficients # and a list of evaluations at the roots of unity # See https://vitalik.ca/general/2019/05/12/fft.html @@ -82,11 +93,12 @@ def _fft(vals, modulus, roots_of_unity): R = _fft(vals[1::2], modulus, roots_of_unity[::2]) o = [0 for i in vals] for i, (x, y) in enumerate(zip(L, R)): - y_times_root = y*roots_of_unity[i] - o[i] = (x+y_times_root) % modulus - o[i+len(L)] = (x-y_times_root) % modulus + y_times_root = y * roots_of_unity[i] + o[i] = (x + y_times_root) % modulus + o[i + len(L)] = (x - y_times_root) % modulus return o + # Convenience method to do FFTs specifically over the subgroup over which # all of the proofs are operating def f_inner_fft(vals, inv=False): @@ -101,6 +113,7 @@ def f_inner_fft(vals, inv=False): # Regular FFT return [f_inner(x) for x in _fft(nvals, o, roots)] + # Converts a list of evaluations at [1, w, w**2... w**(n-1)] to # a list of evaluations at # [offset, offset * q, offset * q**2 ... offset * q**(4n-1)] where q = w**(1/4) @@ -110,19 +123,21 @@ def f_inner_fft(vals, inv=False): def fft_expand_with_offset(vals, offset): group_order = len(vals) x_powers = f_inner_fft(vals, inv=True) - x_powers = [ - (offset**i * x) for i, x in enumerate(x_powers) - ] + [f_inner(0)] * (group_order * 3) + x_powers = [(offset**i * x) for i, x in enumerate(x_powers)] + [f_inner(0)] * ( + group_order * 3 + ) return f_inner_fft(x_powers) + # Convert from offset form into coefficients # Note that we can't make a full inverse function of fft_expand_with_offset # because the output of this might be a deg >= n polynomial, which cannot # be expressed via evaluations at n roots of unity def offset_evals_to_coeffs(evals, offset): shifted_coeffs = f_inner_fft(evals, inv=True) - inv_offset = (1 / offset) - return [v * inv_offset ** i for (i, v) in enumerate(shifted_coeffs)] + inv_offset = 1 / offset + return [v * inv_offset**i for (i, v) in enumerate(shifted_coeffs)] + # Given a polynomial expressed as a list of evaluations at roots of unity, # evaluate it at x directly, without using an FFT to covert to coeffs first @@ -130,20 +145,22 @@ def barycentric_eval_at_point(values, x): order = len(values) roots_of_unity = get_roots_of_unity(order) return ( - (f_inner(x)**order - 1) / order * - sum([ - value * root / (x - root) - for value, root in zip(values, roots_of_unity) - ]) + (f_inner(x) ** order - 1) + / order + * sum( + [value * root / (x - root) for value, root in zip(values, roots_of_unity)] + ) ) + ################################################################ # multicombs ################################################################ import random, sys, math -def multisubset(numbers, subsets, adder=lambda x,y: x+y, zero=0): + +def multisubset(numbers, subsets, adder=lambda x, y: x + y, zero=0): # Split up the numbers into partitions partition_size = 1 + int(math.log(len(subsets) + 1)) # Align number count to partition size (for simplicity) @@ -154,7 +171,7 @@ def multisubset(numbers, subsets, adder=lambda x,y: x+y, zero=0): power_sets = [] for i in range(0, len(numbers), partition_size): new_power_set = [zero] - for dimension, value in enumerate(numbers[i:i+partition_size]): + for dimension, value in enumerate(numbers[i : i + partition_size]): new_power_set += [adder(n, value) for n in new_power_set] power_sets.append(new_power_set) # Compute subset sums, using elements from power set for each range of values @@ -167,19 +184,23 @@ def multisubset(numbers, subsets, adder=lambda x,y: x+y, zero=0): index_in_power_set = 0 for j in range(partition_size): if i * partition_size + j in subset: - index_in_power_set += 2 ** j + index_in_power_set += 2**j o = adder(o, power_sets[i][index_in_power_set]) subset_sums.append(o) return subset_sums + # Reduces a linear combination `numbers[0] * factors[0] + numbers[1] * factors[1] + ...` # into a multi-subset problem, and computes the result efficiently -def lincomb(numbers, factors, adder=lambda x,y: x+y, zero=0): +def lincomb(numbers, factors, adder=lambda x, y: x + y, zero=0): # Maximum bit length of a number; how many subsets we need to make - maxbitlen = max(len(bin(f))-2 for f in factors) + maxbitlen = max(len(bin(f)) - 2 for f in factors) # Compute the subsets: the ith subset contains the numbers whose corresponding factor # has a 1 at the ith bit - subsets = [{i for i in range(len(numbers)) if factors[i] & (1 << j)} for j in range(maxbitlen+1)] + subsets = [ + {i for i in range(len(numbers)) if factors[i] & (1 << j)} + for j in range(maxbitlen + 1) + ] subset_sums = multisubset(numbers, subsets, adder=adder, zero=zero) # For example, suppose a value V has factor 6 (011 in increasing-order binary). Subset 0 # will not have V, subset 1 will, and subset 2 will. So if we multiply the output of adding @@ -189,37 +210,48 @@ def lincomb(numbers, factors, adder=lambda x,y: x+y, zero=0): # Here, we compute this as `((subset_2_sum * 2) + subset_1_sum) * 2 + subset_0_sum` for # efficiency: an extra `maxbitlen * 2` group operations. o = zero - for i in range(len(subsets)-1, -1, -1): + for i in range(len(subsets) - 1, -1, -1): o = adder(adder(o, o), subset_sums[i]) return o + # Tests go here def make_mock_adder(): counter = [0] + def adder(x, y): if x and y: counter[0] += 1 - return x+y + return x + y + return adder, counter + def test_multisubset(numcount, setcount): numbers = [random.randrange(10**20) for _ in range(numcount)] - subsets = [{i for i in range(numcount) if random.randrange(2)} for i in range(setcount)] + subsets = [ + {i for i in range(numcount) if random.randrange(2)} for i in range(setcount) + ] adder, counter = make_mock_adder() o = multisubset(numbers, subsets, adder=adder) for output, subset in zip(o, subsets): assert output == sum([numbers[x] for x in subset]) + def test_lincomb(numcount, bitlength=256): numbers = [random.randrange(10**20) for _ in range(numcount)] factors = [random.randrange(2**bitlength) for _ in range(numcount)] adder, counter = make_mock_adder() o = lincomb(numbers, factors, adder=adder) - assert o == sum([n*f for n,f in zip(numbers, factors)]) - total_ones = sum(bin(f).count('1') for f in factors) + assert o == sum([n * f for n, f in zip(numbers, factors)]) + total_ones = sum(bin(f).count("1") for f in factors) print("Naive operation count: %d" % (bitlength * numcount + total_ones)) print("Optimized operation count: %d" % (bitlength * 2 + counter[0])) - print("Optimization factor: %.2f" % ((bitlength * numcount + total_ones) / (bitlength * 2 + counter[0]))) + print( + "Optimization factor: %.2f" + % ((bitlength * numcount + total_ones) / (bitlength * 2 + counter[0])) + ) + -if __name__ == '__main__': +if __name__ == "__main__": test_lincomb(int(sys.argv[1]) if len(sys.argv) >= 2 else 80) diff --git a/verifier.py b/verifier.py index 9c8da6f..0a42f2f 100644 --- a/verifier.py +++ b/verifier.py @@ -8,9 +8,11 @@ from setup import Setup from transcript import Transcript + @dataclass class VerificationKey: """Verification key""" + # [q_M(x)]₁ (commitment to multiplication selector polynomial) Qm: G1Point # [q_L(x)]₁ (commitment to left selector polynomial) @@ -47,7 +49,7 @@ def make_verification_key(cls, program: Program, setup: Setup): setup.commit(S[Column.RIGHT]), setup.commit(S[Column.OUTPUT]), setup.X2, - get_root_of_unity(program.group_order) + get_root_of_unity(program.group_order), ) # Basic, easier-to-understand version of what's going on @@ -66,76 +68,83 @@ def _verify_inner( gamma: f_inner, ) -> bool: root_of_unity = get_root_of_unity(group_order) - ZH_ev = zed ** group_order - 1 + ZH_ev = zed**group_order - 1 L1_ev = ZH_ev / (group_order * (zed - 1)) - R_pt = ec_lincomb([ - (self.Qm, proof['a_eval'] * proof['b_eval']), - (self.Ql, proof['a_eval']), - (self.Qr, proof['b_eval']), - (self.Qo, proof['c_eval']), - (b.G1, PI_ev), - (self.Qc, 1), - (proof['z_1'], ( - (proof['a_eval'] + beta * zed + gamma) * - (proof['b_eval'] + beta * 2 * zed + gamma) * - (proof['c_eval'] + beta * 3 * zed + gamma) * - alpha - )), - (self.S3, ( - -(proof['a_eval'] + beta * proof['s1_eval'] + gamma) * - (proof['b_eval'] + beta * proof['s2_eval'] + gamma) * - beta * - alpha * proof['z_shifted_eval'] - )), - (b.G1, ( - -(proof['a_eval'] + beta * proof['s1_eval'] + gamma) * - (proof['b_eval'] + beta * proof['s2_eval'] + gamma) * - (proof['c_eval'] + gamma) * - alpha * proof['z_shifted_eval'] - )), - (proof['z_1'], L1_ev * alpha ** 2), - (b.G1, -L1_ev * alpha ** 2), - (proof['t_lo_1'], -ZH_ev), - (proof['t_mid_1'], -ZH_ev * zed**group_order), - (proof['t_hi_1'], -ZH_ev * zed**(group_order*2)), - ]) + R_pt = ec_lincomb( + [ + (self.Qm, proof["a_eval"] * proof["b_eval"]), + (self.Ql, proof["a_eval"]), + (self.Qr, proof["b_eval"]), + (self.Qo, proof["c_eval"]), + (b.G1, PI_ev), + (self.Qc, 1), + ( + proof["z_1"], + ( + (proof["a_eval"] + beta * zed + gamma) + * (proof["b_eval"] + beta * 2 * zed + gamma) + * (proof["c_eval"] + beta * 3 * zed + gamma) + * alpha + ), + ), + ( + self.S3, + ( + -(proof["a_eval"] + beta * proof["s1_eval"] + gamma) + * (proof["b_eval"] + beta * proof["s2_eval"] + gamma) + * beta + * alpha + * proof["z_shifted_eval"] + ), + ), + ( + b.G1, + ( + -(proof["a_eval"] + beta * proof["s1_eval"] + gamma) + * (proof["b_eval"] + beta * proof["s2_eval"] + gamma) + * (proof["c_eval"] + gamma) + * alpha + * proof["z_shifted_eval"] + ), + ), + (proof["z_1"], L1_ev * alpha**2), + (b.G1, -L1_ev * alpha**2), + (proof["t_lo_1"], -ZH_ev), + (proof["t_mid_1"], -ZH_ev * zed**group_order), + (proof["t_hi_1"], -ZH_ev * zed ** (group_order * 2)), + ] + ) - print('verifier R_pt', R_pt) + print("verifier R_pt", R_pt) # Verify that R(z) = 0 and the prover-provided evaluations # A(z), B(z), C(z), S1(z), S2(z) are all correct assert b.pairing( b.G2, - ec_lincomb([ - (R_pt, 1), - (proof['a_1'], v), - (b.G1, -v * proof['a_eval']), - (proof['b_1'], v**2), - (b.G1, -v**2 * proof['b_eval']), - (proof['c_1'], v**3), - (b.G1, -v**3 * proof['c_eval']), - (self.S1, v**4), - (b.G1, -v**4 * proof['s1_eval']), - (self.S2, v**5), - (b.G1, -v**5 * proof['s2_eval']), - ]) - ) == b.pairing( - b.add(self.X_2, ec_mul(b.G2, -zed)), - proof['W_z_1'] - ) + ec_lincomb( + [ + (R_pt, 1), + (proof["a_1"], v), + (b.G1, -v * proof["a_eval"]), + (proof["b_1"], v**2), + (b.G1, -(v**2) * proof["b_eval"]), + (proof["c_1"], v**3), + (b.G1, -(v**3) * proof["c_eval"]), + (self.S1, v**4), + (b.G1, -(v**4) * proof["s1_eval"]), + (self.S2, v**5), + (b.G1, -(v**5) * proof["s2_eval"]), + ] + ), + ) == b.pairing(b.add(self.X_2, ec_mul(b.G2, -zed)), proof["W_z_1"]) print("done check 1") # Verify that the provided value of Z(zed*w) is correct assert b.pairing( - b.G2, - ec_lincomb([ - (proof['z_1'], 1), - (b.G1, -proof['z_shifted_eval']) - ]) + b.G2, ec_lincomb([(proof["z_1"], 1), (b.G1, -proof["z_shifted_eval"])]) ) == b.pairing( - b.add(self.X_2, ec_mul(b.G2, -zed * root_of_unity)), - proof['W_zw_1'] + b.add(self.X_2, ec_mul(b.G2, -zed * root_of_unity)), proof["W_zw_1"] ) print("done check 2") return True @@ -154,62 +163,85 @@ def _optimized_verify_inner( zed: f_inner, alpha: f_inner, beta: f_inner, - gamma: f_inner + gamma: f_inner, ) -> bool: root_of_unity = get_root_of_unity(group_order) - ZH_ev = zed ** group_order - 1 + ZH_ev = zed**group_order - 1 L1_ev = ZH_ev / (group_order * (zed - 1)) # Compute the constant term of R. This is not literally the degree-0 # term of the R polynomial; rather, it's the portion of R that can # be computed directly, without resorting to elliptic cutve commitments r0 = ( - PI_ev - L1_ev * alpha ** 2 - ( - alpha * - (proof['a_eval'] + beta * proof['s1_eval'] + gamma) * - (proof['b_eval'] + beta * proof['s2_eval'] + gamma) * - (proof['c_eval'] + gamma) * - proof['z_shifted_eval'] + PI_ev + - L1_ev * alpha**2 + - ( + alpha + * (proof["a_eval"] + beta * proof["s1_eval"] + gamma) + * (proof["b_eval"] + beta * proof["s2_eval"] + gamma) + * (proof["c_eval"] + gamma) + * proof["z_shifted_eval"] ) ) # D = (R - r0) + u * Z - D_pt = ec_lincomb([ - (self.Qm, proof['a_eval'] * proof['b_eval']), - (self.Ql, proof['a_eval']), - (self.Qr, proof['b_eval']), - (self.Qo, proof['c_eval']), - (self.Qc, 1), - (proof['z_1'], ( - (proof['a_eval'] + beta * zed + gamma) * - (proof['b_eval'] + beta * 2 * zed + gamma) * - (proof['c_eval'] + beta * 3 * zed + gamma) * alpha + - L1_ev * alpha ** 2 + - u - )), - (self.S3, ( - -(proof['a_eval'] + beta * proof['s1_eval'] + gamma) * - (proof['b_eval'] + beta * proof['s2_eval'] + gamma) * - alpha * beta * proof['z_shifted_eval'] - )), - (proof['t_lo_1'], -ZH_ev), - (proof['t_mid_1'], -ZH_ev * zed**group_order), - (proof['t_hi_1'], -ZH_ev * zed**(group_order*2)), - ]) + D_pt = ec_lincomb( + [ + (self.Qm, proof["a_eval"] * proof["b_eval"]), + (self.Ql, proof["a_eval"]), + (self.Qr, proof["b_eval"]), + (self.Qo, proof["c_eval"]), + (self.Qc, 1), + ( + proof["z_1"], + ( + (proof["a_eval"] + beta * zed + gamma) + * (proof["b_eval"] + beta * 2 * zed + gamma) + * (proof["c_eval"] + beta * 3 * zed + gamma) + * alpha + + L1_ev * alpha**2 + + u + ), + ), + ( + self.S3, + ( + -(proof["a_eval"] + beta * proof["s1_eval"] + gamma) + * (proof["b_eval"] + beta * proof["s2_eval"] + gamma) + * alpha + * beta + * proof["z_shifted_eval"] + ), + ), + (proof["t_lo_1"], -ZH_ev), + (proof["t_mid_1"], -ZH_ev * zed**group_order), + (proof["t_hi_1"], -ZH_ev * zed ** (group_order * 2)), + ] + ) - F_pt = ec_lincomb([ - (D_pt, 1), - (proof['a_1'], v), - (proof['b_1'], v**2), - (proof['c_1'], v**3), - (self.S1, v**4), - (self.S2, v**5), - ]) + F_pt = ec_lincomb( + [ + (D_pt, 1), + (proof["a_1"], v), + (proof["b_1"], v**2), + (proof["c_1"], v**3), + (self.S1, v**4), + (self.S2, v**5), + ] + ) - E_pt = ec_mul(b.G1, ( - -r0 + v * proof['a_eval'] + v**2 * proof['b_eval'] + v**3 * proof['c_eval'] + - v**4 * proof['s1_eval'] + v**5 * proof['s2_eval'] + u * proof['z_shifted_eval'] - )) + E_pt = ec_mul( + b.G1, + ( + -r0 + + v * proof["a_eval"] + + v**2 * proof["b_eval"] + + v**3 * proof["c_eval"] + + v**4 * proof["s1_eval"] + + v**5 * proof["s2_eval"] + + u * proof["z_shifted_eval"] + ), + ) # What's going on here is a clever re-arrangement of terms to check # the same equations that are being checked in the basic version, @@ -226,15 +258,19 @@ def _optimized_verify_inner( # # so at this point we can take a random linear combination of the two # checks, and verify it with only one pairing. - assert b.pairing(self.X_2, ec_lincomb([ - (proof['W_z_1'], 1), - (proof['W_zw_1'], u) - ])) == b.pairing(b.G2, ec_lincomb([ - (proof['W_z_1'], zed), - (proof['W_zw_1'], u * zed * root_of_unity), - (F_pt, 1), - (E_pt, -1) - ])) + assert b.pairing( + self.X_2, ec_lincomb([(proof["W_z_1"], 1), (proof["W_zw_1"], u)]) + ) == b.pairing( + b.G2, + ec_lincomb( + [ + (proof["W_z_1"], zed), + (proof["W_zw_1"], u * zed * root_of_unity), + (F_pt, 1), + (E_pt, -1), + ] + ), + ) print("done combined check") return True @@ -243,43 +279,43 @@ def verify_proof(self, group_order: int, proof, public=[], optimized=True) -> bo # Compute challenges (should be same as those computed by prover) transcript = Transcript() - transcript.hash_point(proof['a_1']) - transcript.hash_point(proof['b_1']) - transcript.hash_point(proof['c_1']) + transcript.hash_point(proof["a_1"]) + transcript.hash_point(proof["b_1"]) + transcript.hash_point(proof["c_1"]) beta = transcript.squeeze() gamma = transcript.squeeze() - transcript.hash_point(proof['z_1']) + transcript.hash_point(proof["z_1"]) alpha = transcript.squeeze() fft_cofactor = transcript.squeeze() - transcript.hash_point(proof['t_lo_1']) - transcript.hash_point(proof['t_mid_1']) - transcript.hash_point(proof['t_hi_1']) - + transcript.hash_point(proof["t_lo_1"]) + transcript.hash_point(proof["t_mid_1"]) + transcript.hash_point(proof["t_hi_1"]) + zed = transcript.squeeze() - transcript.hash_scalar(proof['a_eval']) - transcript.hash_scalar(proof['b_eval']) - transcript.hash_scalar(proof['c_eval']) - transcript.hash_scalar(proof['s1_eval']) - transcript.hash_scalar(proof['s2_eval']) - transcript.hash_scalar(proof['z_shifted_eval']) + transcript.hash_scalar(proof["a_eval"]) + transcript.hash_scalar(proof["b_eval"]) + transcript.hash_scalar(proof["c_eval"]) + transcript.hash_scalar(proof["s1_eval"]) + transcript.hash_scalar(proof["s2_eval"]) + transcript.hash_scalar(proof["z_shifted_eval"]) v = transcript.squeeze() - transcript.hash_point(proof['W_z_1']) - transcript.hash_point(proof['W_zw_1']) + transcript.hash_point(proof["W_z_1"]) + transcript.hash_point(proof["W_zw_1"]) # Does not need to be standardized, only needs to be unpredictable u = transcript.squeeze() PI_ev = barycentric_eval_at_point( - [f_inner(-x) for x in public] + - [f_inner(0) for _ in range(group_order - len(public))], - zed + [f_inner(-x) for x in public] + + [f_inner(0) for _ in range(group_order - len(public))], + zed, ) if not optimized: