├── verifiable_mpc ├── __init__.py ├── ac20 │ ├── __init__.py │ ├── test │ │ ├── __init__.py │ │ ├── test_nullity.py │ │ ├── test_koe.py │ │ └── test_pivot.py │ ├── nullity.py │ ├── recombine.py │ ├── knowledge_of_exponent.py │ ├── pivot.py │ ├── mpc_ac20_cb.py │ ├── compressed_pivot.py │ ├── circuit_sat_cb.py │ ├── pairing.py │ ├── mpc_ac20.py │ └── circuit_builder.py ├── tools │ ├── __init__.py │ ├── code_to_qap.py │ ├── qap_creator.py │ └── code_to_r1cs.py ├── trinocchio │ ├── __init__.py │ ├── README.md │ ├── trinocchio.py │ ├── wip_keygen_geppetri.py │ └── pynocchio.py └── wip_sigmaproof.py ├── circuit.png ├── .travis.yml ├── setup.py ├── LICENSE ├── test ├── test_demo_zkp_ac20.py ├── test_demo_zkp_mpc_ac20.py └── test_demo_zkp_pynocchio.py ├── .gitignore ├── README.md ├── demos ├── demo_zkp_trinocchio.py ├── demo_circuit_builder.py ├── demo_zkp_ac20.py ├── demo_zkp_mpc_ac20.py └── demo_zkp_pynocchio.py └── wip.py /verifiable_mpc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /verifiable_mpc/ac20/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /verifiable_mpc/tools/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /verifiable_mpc/ac20/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /verifiable_mpc/trinocchio/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /circuit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toonsegers/verifiable_mpc/HEAD/circuit.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | # - 3.6 4 | # - 3.7 5 | # - 3.8 6 | - 3.9 7 | # - pypy3 8 | before_install: 9 | - sudo apt-get -y install libmpc-dev 10 | install: 11 | - git clone https://github.com/lschoe/mpyc 12 | - cd mpyc 13 | - python setup.py install 14 | - pip install gmpy2 15 | - cd .. 16 | script: python -m unittest discover -------------------------------------------------------------------------------- /verifiable_mpc/trinocchio/README.md: -------------------------------------------------------------------------------- 1 | # `pynocchio`: Proof of correct function evaluation and verifiable MPC 2 | 3 | Implement Pinocchio, Trinocchio and Geppetri protocols on top of MPyC ([https://github.com/lschoe/mpyc]) 4 | 5 | *Work-in-progress* 6 | 7 | ## Overview 8 | 9 | `pynocchio.py` implements the Pinocchio protocol from Parno and Gentry, 2013: [https://eprint.iacr.org/2013/279]. 10 | 11 | `trinocchio.py` implements the Trinocchio protocol from Schoenmakers, Veeningen and De Vreede, 2015: [https://eprint.iacr.org/2015/480] 12 | 13 | 14 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | from setuptools import setup 3 | 4 | # The directory containing this file 5 | HERE = pathlib.Path(__file__).parent 6 | 7 | # The text of the README file 8 | README = (HERE / "README.md").read_text() 9 | 10 | # This call to setup() does all the work 11 | setup( 12 | name="verifiable-mpc", 13 | version="0.0.1", 14 | description="Implements the verfiable MPC scheme.", 15 | long_description=README, 16 | long_description_content_type="text/markdown", 17 | url="https://github.com/toonsegers/verifiable_mpc", 18 | author="Toon Segers", 19 | author_email="a.j.m.segers@tue.nl", 20 | license="MIT", 21 | classifiers=[ 22 | "License :: OSI Approved :: MIT License", 23 | "Programming Language :: Python :: 3", 24 | "Programming Language :: Python :: 3.6", 25 | ], 26 | packages=["verifiable_mpc", "verifiable_mpc.tools", "verifiable_mpc.ac20", "verifiable_mpc.trinocchio"], 27 | include_package_data=True, 28 | install_requires=["mpyc >= 0.8"], 29 | python_requires='>=3.6', 30 | ) 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Toon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /verifiable_mpc/trinocchio/trinocchio.py: -------------------------------------------------------------------------------- 1 | """Implementation of Trinocchio [SVV15] in Python. 2 | 3 | Paper by Schoenmakers, Veeningen and De Vreede: 4 | https://eprint.iacr.org/2015/480 5 | 6 | Adaptations to original Trinocchio protocol: 7 | * TBD 8 | 9 | Credits: 10 | * Meilof Veeningen's PySNARK: https://github.com/meilof/pysnark 11 | """ 12 | 13 | import os, sys 14 | from mpyc.runtime import mpc 15 | from mpyc.fingroups import EllipticCurve 16 | from mpyc.fingroups import FiniteGroupElement 17 | 18 | 19 | bn_curve = EllipticCurve('BN256', 'jacobian') 20 | g1 = bn_curve.generator 21 | bn_twist = EllipticCurve('BN256_twist', 'jacobian') 22 | g2 = bn_twist.generator 23 | modulus = bn_curve.order 24 | point_add = FiniteGroupElement.__matmul__ 25 | 26 | 27 | # TODO list 28 | # TODO-1: Implement ZK variant 29 | # TODO-2: Remove progress bar (Bar) 30 | # TODO-1: Apply Mark's trick consistently 31 | # TODO-1: Implement multi-client with basic Trinocchio 32 | # TODO-1: Implement full Trinocchio scheme (Alg. 4, p. 25) 33 | # TODO-1: Enable __neg__ and __sub__ in bn256 classes, and then don't set gf.is_signed=False 34 | # TODO-3: Loosely couple/decouple type detection from "g1"/"g2" in key name in: pt_type = bn256.CurvePoint if "g1" in key else bn256.CurveTwist 35 | -------------------------------------------------------------------------------- /test/test_demo_zkp_ac20.py: -------------------------------------------------------------------------------- 1 | import sys, os 2 | import unittest 3 | import verifiable_mpc.ac20.circuit_sat_cb as cs 4 | from demos import demo_zkp_ac20 5 | 6 | 7 | class CircuitSat(unittest.TestCase): 8 | @classmethod 9 | def setUpClass(cls): 10 | pass 11 | 12 | @classmethod 13 | def tearDownClass(cls): 14 | pass 15 | 16 | def test_cs_pivot(self): 17 | verification = demo_zkp_ac20.main(cs.PivotChoice.pivot) 18 | self.assertEqual(verification["y1*y2=y3"], True) 19 | self.assertEqual(verification["L_wellformed_from_Cfgh_forms"], True) 20 | self.assertEqual(verification["pivot_verification"], True) 21 | 22 | def test_cs_compressed(self): 23 | verification = demo_zkp_ac20.main(cs.PivotChoice.compressed) 24 | self.assertEqual(verification["y1*y2=y3"], True) 25 | self.assertEqual(verification["L_wellformed_from_Cfgh_forms"], True) 26 | self.assertEqual(verification["pivot_verification"], True) 27 | 28 | def test_cs_koe(self): 29 | verification = demo_zkp_ac20.main(cs.PivotChoice.koe) 30 | self.assertEqual(verification["y1*y2=y3"], True) 31 | self.assertEqual(verification["L_wellformed_from_Cfgh_forms"], True) 32 | self.assertEqual(verification["pivot_verification"]["restriction_arg_check"], True) 33 | self.assertEqual(verification["pivot_verification"]["PRQ_check"], True) 34 | 35 | 36 | if __name__ == "__main__": 37 | unittest.main() 38 | -------------------------------------------------------------------------------- /verifiable_mpc/ac20/nullity.py: -------------------------------------------------------------------------------- 1 | """ Implementation of https://eprint.iacr.org/2020/152 2 | ``Compressed Σ-Protocol Theory and Practical Application 3 | to Plug & Play Secure Algorithmics'' 4 | 5 | Protocols: 6 | * Protocol Pi_Nullity, page 17-18, to proof null-evaluations with "polynomial amortization trick" using pivot 7 | 8 | """ 9 | 10 | import os 11 | import sys 12 | from random import SystemRandom 13 | import pprint 14 | import verifiable_mpc.ac20.pivot as pivot 15 | import verifiable_mpc.ac20.compressed_pivot as compressed_pivot 16 | 17 | prng = SystemRandom() 18 | pp = pprint.PrettyPrinter(indent=4) 19 | 20 | 21 | def prove_nullity_compressed(generators, P, lin_forms, x, gamma, gf): 22 | 23 | input_list = [P, lin_forms] 24 | rho = pivot.fiat_shamir_hash(input_list, gf.order) 25 | L = sum((linform_i) * (rho ** i) for i, linform_i in enumerate(lin_forms)) 26 | y = L(x) 27 | proof = compressed_pivot.protocol_5_prover(generators, P, L, y, x, gamma, gf) 28 | return proof, L, y, rho 29 | 30 | 31 | def verify_nullity_compressed(generators, P, L, lin_forms, rho, y, proof, gf): 32 | L_check = sum((linform_i) * (rho ** i) for i, linform_i in enumerate(lin_forms)) 33 | if not L_check == L: 34 | print( 35 | "Linear form L does not correspond to reconstructed linear form with rho." 36 | ) 37 | verification = False 38 | return verification 39 | verification = compressed_pivot.protocol_5_verifier(generators, P, L, y, proof, gf) 40 | return verification 41 | -------------------------------------------------------------------------------- /test/test_demo_zkp_mpc_ac20.py: -------------------------------------------------------------------------------- 1 | import sys, os 2 | import unittest 3 | import verifiable_mpc.ac20.circuit_sat_cb as cs 4 | from demos import demo_zkp_mpc_ac20 5 | from mpyc.runtime import mpc 6 | 7 | 8 | class CircuitSat(unittest.TestCase): 9 | @classmethod 10 | def setUpClass(cls): 11 | pass 12 | 13 | @classmethod 14 | def tearDownClass(cls): 15 | pass 16 | 17 | def test_cs_pivot_elliptic(self): 18 | verification = mpc.run(demo_zkp_mpc_ac20.main(cs.PivotChoice.pivot, "Elliptic", 2)) 19 | self.assertEqual(all(check == True for check in verification.values()), True) 20 | 21 | def test_cs_compressed_elliptic(self): 22 | verification = mpc.run(demo_zkp_mpc_ac20.main(cs.PivotChoice.compressed, "Elliptic", 2)) 23 | self.assertEqual(all(check == True for check in verification.values()), True) 24 | 25 | def test_cs_knowledgeofexponent_elliptic(self): 26 | verification = mpc.run(demo_zkp_mpc_ac20.main(cs.PivotChoice.koe, "Elliptic", 2)) 27 | self.assertEqual(verification["y1*y2=y3"], True) 28 | self.assertEqual(verification["L_wellformed_from_Cfgh_forms"], True) 29 | self.assertEqual(verification["pivot_verification"]["restriction_arg_check"], True) 30 | self.assertEqual(verification["pivot_verification"]["PRQ_check"], True) 31 | 32 | def test_cs_pivot_quadratic_residues(self): 33 | verification = mpc.run(demo_zkp_mpc_ac20.main(cs.PivotChoice.pivot, "QR", 10)) 34 | self.assertEqual(all(check == True for check in verification.values()), True) 35 | 36 | def test_cs_compressed_quadratic_residues(self): 37 | verification = mpc.run(demo_zkp_mpc_ac20.main(cs.PivotChoice.compressed, "QR", 10)) 38 | self.assertEqual(all(check == True for check in verification.values()), True) 39 | 40 | 41 | if __name__ == "__main__": 42 | unittest.main() 43 | -------------------------------------------------------------------------------- /verifiable_mpc/ac20/recombine.py: -------------------------------------------------------------------------------- 1 | import functools 2 | 3 | 4 | @functools.lru_cache(maxsize=None) 5 | def _recombination_vectors(field, xs, xr): 6 | """Compute and store recombination vectors. 7 | 8 | Recombination vectors depend on the field, the x-coordinates xs 9 | of the shares and the x-coordinates xr of the recombination points. 10 | """ 11 | modulus = field.modulus 12 | xs = [x % modulus for x in xs] # also for conversion from 13 | xr = [x % modulus for x in xr] # int to type(modulus) 14 | d = [None] * len(xs) 15 | for i, x_i in enumerate(xs): 16 | q = 1 17 | for j, x_j in enumerate(xs): 18 | if i != j: 19 | q *= x_i - x_j 20 | q %= modulus 21 | d[i] = q 22 | matrix = [None] * len(xr) 23 | for r, x_r in enumerate(xr): 24 | matrix[r] = [None] * len(xs) 25 | p = 1 26 | for j, x_j in enumerate(xs): 27 | p *= x_r - x_j 28 | p %= modulus 29 | p = field(p) 30 | for i, x_i in enumerate(xs): 31 | matrix[r][i] = (p / field((x_r - x_i) * d[i])).value 32 | return matrix 33 | 34 | 35 | def recombine(field, points, x_rs=0): ##### ONLY for shares that are single numbers 36 | """Recombine shares given by points into secrets. 37 | 38 | Recombination is done for x-coordinates x_rs. 39 | """ 40 | xs, shares = list(zip(*points)) 41 | if not isinstance(x_rs, list): 42 | x_rs = (x_rs,) 43 | m = len(shares) 44 | width = len(x_rs) 45 | T_is_field = isinstance(shares[0], field) # all elts assumed of same type 46 | vector = _recombination_vectors(field, xs, tuple(x_rs)) 47 | sums = [0] * width 48 | for i in range(m): 49 | s = shares[i] 50 | if T_is_field: 51 | s = s.value 52 | # type(s) is int or gfpx.Polynomial 53 | for r in range(width): 54 | sums[r] += s * vector[r][i] 55 | for r in range(width): 56 | sums[r] = field(sums[r]) 57 | if isinstance(x_rs, tuple): 58 | return sums[0] 59 | 60 | return sums -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /verifiable_mpc/ac20/test/test_nullity.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import unittest 4 | from random import SystemRandom 5 | import verifiable_mpc.ac20.circuit_sat_r1cs as cs 6 | import verifiable_mpc.ac20.pivot as pivot 7 | import verifiable_mpc.ac20.nullity as nullity 8 | import verifiable_mpc.ac20.compressed_pivot as compressed_pivot 9 | from mpyc.finfields import GF 10 | from mpyc.fingroups import QuadraticResidues 11 | 12 | 13 | class Nullity(unittest.TestCase): 14 | @classmethod 15 | def setUpClass(cls): 16 | cls.prng = SystemRandom() 17 | cls.group = QuadraticResidues(l=64) 18 | cls.order = cls.group.order 19 | cls.gf = GF(modulus=cls.order) 20 | 21 | @classmethod 22 | def tearDownClass(cls): 23 | pass 24 | 25 | def setUp(self): 26 | # Define input 27 | gf = self.gf 28 | self.x = [gf(1), gf(2), gf(3)] 29 | self.lin_forms = [ 30 | pivot.LinearForm([6, 0, -2]), 31 | pivot.LinearForm([0, 3, -2]), 32 | pivot.LinearForm([2, 2, -2]), 33 | ] 34 | 35 | # Create random generators 36 | self.n = len(self.x) 37 | self.generators = cs.create_generators( 38 | self.n, cs.PivotChoice.compressed, self.group, progress_bar=False 39 | ) 40 | self.g = self.generators["g"] 41 | self.h = self.generators["h"] 42 | self.k = self.generators["k"] 43 | 44 | self.s = len(self.lin_forms) 45 | # Create random element for commitments 46 | self.gamma = self.prng.randrange(self.order) 47 | # Verifier samples random rho 48 | self.rho = self.prng.randrange(self.order) 49 | 50 | # Prover to open L(x) = Sum L_i(x)*rho**(i-1) for i = 1 .. s 51 | self.P = pivot.vector_commitment( 52 | self.x, self.gamma, self.generators["g"], self.generators["h"] 53 | ) 54 | self.L = sum((self.lin_forms[i]) * (self.rho ** i) for i in range(self.s)) 55 | self.y = self.L(self.x) 56 | 57 | def test_nullity_compressed_noninteractive(self): 58 | # Non-interactive version of nullity check 59 | 60 | proof, L, y, rho = nullity.prove_nullity_compressed( 61 | self.generators, self.P, self.lin_forms, self.x, self.gamma, self.gf 62 | ) 63 | verification = nullity.verify_nullity_compressed( 64 | self.generators, self.P, L, self.lin_forms, rho, y, proof, self.gf 65 | ) 66 | self.assertEqual(verification, True) 67 | 68 | 69 | if __name__ == "__main__": 70 | unittest.main() 71 | -------------------------------------------------------------------------------- /verifiable_mpc/tools/code_to_qap.py: -------------------------------------------------------------------------------- 1 | import verifiable_mpc.tools.code_to_r1cs as c2r 2 | import verifiable_mpc.tools.qap_creator as qc 3 | 4 | 5 | class QAP: 6 | 7 | def __init__(self, code, field): 8 | inputs, body = c2r.extract_inputs_and_body(c2r.parse(code)) 9 | flatcode = c2r.flatten_body(body) 10 | varnames = c2r.get_var_placement(inputs, flatcode) 11 | V, W, Y = c2r.flatcode_to_r1cs(inputs, flatcode) 12 | V = for_each_in(int, lambda x: field(x), V) 13 | W = for_each_in(int, lambda x: field(x), W) 14 | Y = for_each_in(int, lambda x: field(x), Y) 15 | v, w, y, t = qc.r1cs_to_qap_ff(V, W, Y, field) 16 | v = [qc.Poly(coeffs) for coeffs in v] 17 | w = [qc.Poly(coeffs) for coeffs in w] 18 | y = [qc.Poly(coeffs) for coeffs in y] 19 | t = qc.Poly(t) 20 | self.v = v 21 | self.w = w 22 | self.y = y 23 | self.t = t 24 | self.inputs = inputs 25 | self.flatcode = flatcode 26 | self.varnames = varnames 27 | self.d = len(flatcode) 28 | self.m = len(varnames) - 1 # Note: `~one` is not included in count 29 | self.out_ix = varnames.index("~out") 30 | # TODO: allow for multiple output indices (previous line, also below) 31 | self.indices = range(self.m+1) 32 | self.indices_io_and_0 = range(0, self.out_ix + 1) # includes "one" 33 | self.indices_io = range(1, self.out_ix + 1) # io indices exclude "one" 34 | self.indices_mid = range(self.out_ix+1, self.m+1) 35 | 36 | def calculate_witness(self, input_vars): 37 | witness = c2r.assign_variables(self.inputs, input_vars, self.flatcode) 38 | assert int(witness[0]) == 1, "First coordinate of witness != 1" 39 | return witness 40 | 41 | 42 | # Helper method from pysnark.runtime (credits to Meilof Veeningen) 43 | def for_each_in(cls, f, struct): 44 | """ Recursively traversing all lists and tuples in struct, apply f to each 45 | element that is an instance of cls. Returns structure with f applied. """ 46 | if isinstance(struct, list): 47 | return list(map(lambda x: for_each_in(cls, f, x), struct)) 48 | elif isinstance(struct, tuple): 49 | return tuple(map(lambda x: for_each_in(cls, f, x), struct)) 50 | else: 51 | if isinstance(struct, cls): 52 | return f(struct) 53 | else: 54 | return struct 55 | 56 | 57 | def calculate_witness(code, input_vars): 58 | inputs, body = c2r.extract_inputs_and_body(c2r.parse(code)) 59 | flatcode = c2r.flatten_body(body) 60 | witness = c2r.assign_variables(inputs, input_vars, flatcode) 61 | return witness 62 | -------------------------------------------------------------------------------- /verifiable_mpc/trinocchio/wip_keygen_geppetri.py: -------------------------------------------------------------------------------- 1 | # WORK-IN-PROGRESS 2 | 3 | """ Implementation of Geppetri key gen steps with BN256 curves 4 | 5 | Using pairings.py from https://github.com/randombit/pairings.py 6 | Alternative for bn128 by Ethereum: https://github.com/ethereum/py_ecc/blob/master/py_ecc/bn128/bn128_curve.py 7 | 8 | """ 9 | 10 | import os, sys 11 | from random import SystemRandom 12 | 13 | import zk_helpers.pairings.bn256 as bn256 14 | 15 | 16 | # k,g = bn256.g1_random() 17 | # k,g = bn256.g2_random() 18 | 19 | # in qap2key: 20 | # void generate_master_skey(mastersk& sk) { 21 | # sk.s = modp::rand(); 22 | # sk.rc = modp::rand(); 23 | # sk.al = modp::rand(); 24 | # } 25 | 26 | DEFAULT_QAP_DEGREE = 20 27 | 28 | def list_add(grp_elements): 29 | n = len(grp_elements) 30 | if n == 1: 31 | return grp_elements[0] 32 | 33 | m0 = list_add(grp_elements[:n//2]) 34 | m1 = list_add(grp_elements[n//2:]) 35 | return m0.add(m1) 36 | 37 | 38 | def generate_s(): 39 | """ Generate secret s """ 40 | s = prng.randrange(bn256.order) 41 | return s 42 | 43 | 44 | def generate_crs(s, qap_degree = DEFAULT_QAP_DEGREE): 45 | """ Generate common reference string per function G01, p. 7 """ 46 | crs_g1 = {"x^"+str(i)+"_g1" : g1.scalar_mul(s**i) for i in range(qap_degree + 1)} 47 | crs_g2 = {"x^"+str(i)+"_g2" : g2.scalar_mul(s**i) for i in range(qap_degree + 1)} 48 | return {**crs_g1, **crs_g2} 49 | 50 | 51 | def generate_commitment_key(qap_degree = DEFAULT_QAP_DEGREE): 52 | """ Generate commitment key per function Gc1, p. 7 """ 53 | alpha = prng.randrange(bn256.order) 54 | ck_g1 = {"x^"+str(i)+"_g1": g1.scalar_mul(s**i) for i in range(qap_degree + 1)} 55 | ck_g2 = {"ax^"+str(i)+"_g2": g2.scalar_mul(alpha * (s**i)) for i in range(qap_degree + 1)} 56 | return {**ck_g1, **ck_g2} 57 | 58 | 59 | def commit(v, r, ck): 60 | """ Commit to input v per function C1, p. 7 """ 61 | c_g1 = ck["x^"+str(0)+"_g1"].scalar_mul(r) 62 | x_terms = list_add([ck["x^"+str(i+1)+"_g1"].scalar_mul(v[i]) for i in range(len(v))]) 63 | c_g1 = c_g1.add(x_terms) 64 | 65 | c_g2 = ck["ax^"+str(0)+"_g2"].scalar_mul(r) 66 | x_terms = list_add([ck["ax^"+str(i+1)+"_g2"].scalar_mul(v[i]) for i in range(len(v))]) 67 | c_g2 = c_g2.add(x_terms) 68 | 69 | return (c_g1, c_g2) 70 | 71 | 72 | def code_to_qap(code, ffield): 73 | r1cs = code_to_r1cs(code, ffield) 74 | qap = r1cs_to_qap(r1cs, ffield) 75 | return qap # v, w, y, t 76 | 77 | 78 | 79 | if __name__ == '__main__': 80 | prng = SystemRandom() 81 | g1 = bn256.curve_G 82 | g2 = bn256.twist_G 83 | d = 5 # degree of the QAP 84 | n = 5 # input length 85 | 86 | s = generate_s() 87 | crs = generate_crs(s, d) 88 | ck = generate_commitment_key(d) 89 | v = [1, 2, 3, 4, 5] 90 | r = prng.randrange(bn256.order) 91 | c = commit(v, r, ck) 92 | print(c) 93 | 94 | 95 | -------------------------------------------------------------------------------- /test/test_demo_zkp_pynocchio.py: -------------------------------------------------------------------------------- 1 | """ Tests for pynocchio.py 2 | 3 | """ 4 | 5 | import sys, os 6 | import unittest 7 | from demos import demo_zkp_pynocchio 8 | 9 | 10 | class CircuitSat(unittest.TestCase): 11 | @classmethod 12 | def setUpClass(cls): 13 | pass 14 | 15 | @classmethod 16 | def tearDownClass(cls): 17 | pass 18 | 19 | def test_pynocchio(self): 20 | verification = demo_zkp_pynocchio.main() 21 | self.assertEqual(all(verification.values()), True) 22 | 23 | 24 | if __name__ == "__main__": 25 | unittest.main() 26 | 27 | 28 | # import pprint 29 | # from random import SystemRandom 30 | 31 | # import bn256 32 | # import code_to_qap as c2q 33 | # import trinocchi.pynocchio 34 | # import tools.code_to_r1cs_and_qap.qap_creator as qc 35 | # from mpyc.finfields import GF, FiniteFieldElement 36 | 37 | # pp = pprint.PrettyPrinter(indent=4) 38 | 39 | # code = """ 40 | # def qeval(x): 41 | # y = x**3 42 | # return y + x + 5 43 | # """ 44 | 45 | # if __name__ == '__main__': 46 | # gf = GF(modulus = bn256.order) 47 | # gf.is_signed = False 48 | # prng = SystemRandom() 49 | # g1 = bn256.curve_G 50 | # g2 = bn256.twist_G 51 | 52 | # print("Test polynomial interpolation over finite field (randomized)...") 53 | # degree = 10 54 | # poly = [gf(prng.randrange(bn256.order)) for i in range(degree+1)] 55 | # y = [qc.eval_poly(poly, i+1) for i in range(degree+1)] 56 | # poly2 = qc.lagrange_interp_ff(y, gf) 57 | # assert poly == poly2, "Polynomial interpolation error" 58 | 59 | # print("Test correctness of target polynomial (randomized)...") 60 | # r1 = prng.randint(0, bn256.order) 61 | # r2 = prng.randint(0, bn256.order) 62 | # r3 = prng.randint(0, bn256.order) 63 | # code = f""" 64 | # def qeval(x): 65 | # y = x**3 + {gf(r1)}*x**2 + {gf(r2)}*x 66 | # return y + x + {gf(r3)} 67 | # """ 68 | # qap = c2q.QAP(code, gf) 69 | # y = [qc.eval_poly(qap.t, i) for i in range(1, qap.d+1)] 70 | # assert y == [0]*qap.d, "Target polynomial is not zero for all roots corresponding to gates" 71 | 72 | # print("Test if trapdoor contains None...") 73 | # td = pynocchio.Trapdoor(bn256.order) 74 | # assert(None not in [td.r_v, td.r_w, td.r_y, td.s, td.alpha_v, td.alpha_w, td.alpha_y, td.beta, td.gamma]), "Trapdoor contains None" 75 | 76 | # print("Test g_eval method...") 77 | # assert str(g2.scalar_mul(0)) == str(pynocchio.g_eval(g2, qap.t, 4, td.alpha_v)), "g^(alpha*t(4)) != id; should be identity element" 78 | # vs = qc.eval_poly(qap.v[3], td.s) 79 | # g1vs = g1.scalar_mul(int(vs)) 80 | # assert str(pynocchio.g_eval(g1, qap.v[3], td.s)) == str(g1vs) 81 | 82 | # print("Test qap indices...") 83 | # print(f"{qap.varnames=}") 84 | # print(f"{len(qap.varnames)=}") 85 | # print(f"{qap.m=}") 86 | # print(f"{qap.d=}") 87 | # print(f"{qap.indices_io=}") 88 | # print(f"{qap.indices_mid=}") 89 | # print(f"{qap.indices=}") 90 | -------------------------------------------------------------------------------- /verifiable_mpc/ac20/test/test_koe.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import unittest 4 | from random import SystemRandom 5 | import verifiable_mpc.ac20.pivot as pivot 6 | import verifiable_mpc.ac20.knowledge_of_exponent as koe 7 | from mpyc.finfields import GF 8 | from mpyc.fingroups import QuadraticResidues, EllipticCurve 9 | 10 | 11 | class KoE(unittest.TestCase): 12 | 13 | @classmethod 14 | def setUpClass(cls): 15 | cls.prng = SystemRandom() 16 | group1 = EllipticCurve('BN256', 'projective') # 'jacobian' 17 | group2 = EllipticCurve('BN256_twist', 'projective') # 'jacobian' 18 | group1.is_additive = False 19 | group1.is_multiplicative = True 20 | group2.is_additive = False 21 | group2.is_multiplicative = True 22 | cls.order = group1.order 23 | cls._g1 = group1.generator 24 | cls._g2 = group2.generator 25 | cls.gf = GF(modulus = cls.order) 26 | 27 | @classmethod 28 | def tearDownClass(cls): 29 | pass 30 | 31 | def setUp(self): 32 | # Randomness for commitment 33 | self.gamma = self.gf(self.prng.randrange(1, self.order)) 34 | 35 | 36 | def test_open_linear_form_koe(self): 37 | gf = self.gf 38 | 39 | # Inputs 40 | x = [gf(1), gf(2), gf(0), gf(0)] 41 | n = len(x) 42 | 43 | # Trusted setup 44 | pp = koe.trusted_setup(self._g1, self._g2, n, self.order) 45 | 46 | # Create random linear form L 47 | random_coeffs = list(gf(self.prng.randrange(self.order)) for i in range(n)) 48 | self.L = pivot.LinearForm(random_coeffs) 49 | 50 | # Commit via [Gro10] restriction argument, the prove linear form opening 51 | P, pi = koe.restriction_argument_prover(range(len(x)), x, self.gamma, pp) 52 | proof, u = koe.opening_linear_form_prover(self.L, x, self.gamma, pp, P, pi) 53 | verification = koe.opening_linear_form_verifier(self.L, pp, proof, u) 54 | self.assertEqual(all(check == True for check in verification.values()), True) 55 | 56 | 57 | def test_nullity_koe(self): 58 | gf = self.gf 59 | 60 | x = [gf(1), gf(2), gf(3)] 61 | n = len(x) 62 | lin_forms = [ 63 | pivot.LinearForm([6, 0, -2]), 64 | pivot.LinearForm([0, 3, -2]), 65 | pivot.LinearForm([2, 2, -2])] 66 | 67 | # Trusted setup 68 | pp = koe.trusted_setup(self._g1, self._g2, n, self.order) 69 | 70 | 71 | """ Prover calculates proof for multiple linear forms 72 | using nullity protocol with KoE assumption. 73 | """ 74 | S = range(len(x)) 75 | P, pi = koe.restriction_argument_prover(S, x, self.gamma, pp) 76 | proof, L, u = koe.prove_nullity_koe(pp, lin_forms, x, self.gamma, gf, P, pi) 77 | 78 | # Verifier checks proof 79 | verification = koe.opening_linear_form_verifier(L, pp, proof, u) 80 | self.assertEqual(all(check == True for check in verification.values()), True) 81 | 82 | 83 | if __name__ == '__main__': 84 | unittest.main() -------------------------------------------------------------------------------- /verifiable_mpc/ac20/test/test_pivot.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import unittest 4 | from random import SystemRandom 5 | import verifiable_mpc.ac20.pivot as pivot 6 | from mpyc.finfields import GF 7 | from mpyc.fingroups import QuadraticResidues 8 | 9 | 10 | class Pivot(unittest.TestCase): 11 | @classmethod 12 | def setUpClass(cls): 13 | cls.prng = SystemRandom() 14 | cls.group = QuadraticResidues(l=64) 15 | cls.order = cls.group.order 16 | cls.h = cls.group.generator 17 | cls.gf = GF(modulus=cls.order) 18 | 19 | @classmethod 20 | def tearDownClass(cls): 21 | pass 22 | 23 | def setUp(self): 24 | # Define input 25 | gf = self.gf 26 | self.x = [gf(1), gf(2), gf(0), gf(0)] 27 | 28 | # Create random generators 29 | self.n = len(self.x) 30 | random_exponents = [] 31 | while len(set(random_exponents)) != self.n: 32 | random_exponents = list( 33 | self.prng.randrange(1, self.order) for i in range(self.n) 34 | ) 35 | self.g = [self.h ** i for i in random_exponents] 36 | self.assertEqual(len({int(g_i) for g_i in self.g}), self.n) 37 | 38 | def test_pivot_interactive(self): 39 | # Create random element for commitments 40 | gamma = self.prng.randrange(self.order) 41 | # Public parameters 42 | P = pivot.vector_commitment(self.x, gamma, self.g, self.h) 43 | # Create random linear form L for testing 44 | random_coeffs = list(self.prng.randrange(self.order) for i in range(self.n)) 45 | L = pivot.LinearForm(random_coeffs) 46 | y = L(self.x) 47 | 48 | # Interactive proof 49 | # Step 1: Prover announces with t, A 50 | r = list(self.prng.randrange(self.order) for i in range(self.n)) 51 | rho = self.prng.randrange(self.order) 52 | t = L(r) 53 | A = pivot.vector_commitment(r, rho, self.g, self.h) 54 | 55 | # Step 2: Verifier responds with challenge 56 | c = self.prng.randrange(self.order) 57 | 58 | # Step 3: Prover responds 59 | z = [c * x_i + r[i] for i, x_i in enumerate(self.x)] 60 | phi = c * gamma + rho 61 | 62 | # Step 4: Verifier checks 63 | self.assertEqual(pivot.vector_commitment(z, phi, self.g, self.h), A * (P ** c)) 64 | self.assertEqual(L(z), c * y + t) 65 | 66 | def test_pivot_noninteractive(self): 67 | # Create random element for commitments 68 | gamma = self.prng.randrange(self.order) 69 | # Public parameters 70 | P = pivot.vector_commitment(self.x, gamma, self.g, self.h) 71 | # Create random linear form L for testing 72 | random_coeffs = list(self.prng.randrange(self.order) for i in range(self.n)) 73 | L = pivot.LinearForm(random_coeffs) 74 | y = L(self.x) 75 | 76 | z, phi, c = pivot.prove_linear_form_eval( 77 | self.g, self.h, P, L, y, self.x, gamma, self.gf 78 | ) 79 | verification = pivot.verify_linear_form_proof( 80 | self.g, self.h, P, L, y, z, phi, c 81 | ) 82 | self.assertEqual(verification, True) 83 | 84 | def test_linear_form(self): 85 | # Test LinearForm and AffineForm classes 86 | lf = pivot.LinearForm([0, 1, 2]) 87 | self.assertEqual( 88 | (lf + lf + 2 * lf + lf.eval([1, 1, 1]) - lf).eval([1, 2, 3]), 27 89 | ) 90 | self.assertEqual(lf([1, 2, 3]), 8) 91 | 92 | 93 | if __name__ == "__main__": 94 | unittest.main() 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://app.travis-ci.com/toonsegers/verifiable_mpc.svg)](https://app.travis-ci.com/toonsegers/verifiable_mpc) 2 | 3 | # Verifiable MPC 4 | The 'Verifiable MPC' Python package implements the verifiable secure multi-party computation (MPC) scheme. 5 | 6 | ## Verifiable MPC scheme 7 | Electronic voting protocols are prime examples of secure multi-party computation (MPC) that separate input and compute parties. In this setting, there are many input parties that outsource the tallying operation to a set of compute parties. This setting introduces new requirements versus classical MPC protocols where parties are considered both input and compute party. A necessary requirement for voting protocols is public verifiability. While voting protocols specialize in the linear operation of tallying votes, the focus of this work is a scheme that defines *publicly verifiable MPC for general arithmetic circuits*. 8 | 9 | The compute parties produce a zero-knowledge proof of correctness of the computation that allows anyone, particularly someone external to the secure computation, to check the correctness of the output, while preserving the privacy properties of the MPC protocol. 10 | 11 | Our scheme addresses the challenge of general arithmetic circuits using recent results in zero-knowledge proof systems, particularly compressed Sigma-protocols [https://eprint.iacr.org/2020/152] by Attema and Cramer (AC20), and Bulletproofs [https://eprint.iacr.org/2017/1066] by Bünz, Bootle, Boneh, Poelstra, Wuille and Maxwell (BBB+17). 12 | 13 | Our construction is based on AC20, which reconciles Bulletproofs with Sigma-protocol theory. The construction yields proofs of logarithmic size and does not require a trusted setup, i.e., the setup does not require knowledge of a trapdoor. 14 | 15 | For our implementation we use the MPyC framework: [https://github.com/lschoe/]. 16 | 17 | Please find the write-up of this work in Chapter 8 of this Horizon2020 deliverable (published on June 30, 2021): [https://media.voog.com/0000/0042/1115/files/D3.3%20-%20Revision%20of%20Extended%20Core%20Protocols.pdf] 18 | 19 | *Note: This implementation is work-in-progress. Expect many bugs/issues.* 20 | 21 | ## Installation 22 | 23 | This implementation depends on MPyC (version 0.8 or above), which is automatically installed with the following command. 24 | In the project root, enter: 25 | 26 | pip install . 27 | 28 | Or alternatively: 29 | 30 | pip install -e . 31 | 32 | to overwrite the directory in site-packages with a symbolic link to this local project directory, making local changes directly available. 33 | 34 | Preferably, install 'gmpy2' for better performance: 35 | 36 | pip install gmpy2 # for Linux (first running `apt install libmpc-dev` may be necessary) 37 | pip install gmpy2-[version etc].whl # for Windows, see Gohlke's unofficial binaries [https://www.lfd.uci.edu/~gohlke/pythonlibs/] 38 | 39 | ## Demos 40 | 41 | The following demos are included: 42 | 43 | * `demo_circuit_builder.py` to use standard Python and automatically construct an arithmetic circuit in memory; 44 | * `demo_zkp_*ac20*.py` to use the AC20/Bulletproofs proof system to prove correctness of the (MPC) computation; 45 | * `demo_zkp_pynocchio.py and *trinocchio.py` to use the Pinocchio zk-SNARK to prove correctness of the (MPC) computation. 46 | 47 | Run the demos as follows. From the project root, for example: 48 | 49 | python ./demos/demo_zkp_mpc_ac20.py -M3 --elliptic 50 | 51 | This runs the prover side of the AC20 ZK-proof system in MPC using three local parties and the Ed25519 elliptic curve group. 52 | 53 | ## Testing 54 | 55 | Run the following commands: 56 | 57 | python -m unittest discover . 58 | 59 | ## Acknowledgements 60 | 61 | This work has received funding from the European Union's Horizon 2020 research and innovation program under grant agreements No 780477 (PRIViLEDGE). 62 | -------------------------------------------------------------------------------- /verifiable_mpc/wip_sigmaproof.py: -------------------------------------------------------------------------------- 1 | # Work in progress 2 | # Implement example(s) from exercise 5.3.2 from Cryptographic Protocols lecture 3 | # See page 53 of: https://www.win.tue.nl/~berry/CryptographicProtocols/LectureNotes.pdf 4 | # Goal: Combine shorter sigma-proofs for gadgets with circuit proofs 5 | 6 | from enum import Enum 7 | from random import SystemRandom 8 | from mpyc.finfields import GF 9 | from mpyc.fingroups import QuadraticResidues, EllipticCurve 10 | from mpyc.runtime import mpc 11 | from mpyc.secgroups import repeat_public_base_public_output as secure_repeat 12 | import verifiable_mpc.ac20.pivot as pivot 13 | from mpyc.sectypes import SecureFiniteField, SecureInteger 14 | 15 | 16 | class SigmaProof(Enum): 17 | """Predicate of proof. """ 18 | 19 | not_zero = 1 20 | 21 | 22 | prng = SystemRandom() 23 | 24 | 25 | async def sigma_prove_not_zero(x, group): 26 | """Generate sigma proof that x != 0, using the Discrete Log assumption for given group. 27 | 28 | Prove x != 0. (Exercise 5.3.2.g of Cryptographic Protocols, answer on p. 101) 29 | Relation {(B;x,y): B=g^x h^y, psi(x,y)} 30 | psi(x,y) = x!=0 31 | """ 32 | # TODO: Consider separating generators and commitment from return value 33 | gf = GF(modulus=group.order) 34 | g = group.generator 35 | y = 1 # TODO: randomize to make B a hiding commitment, masking input x 36 | # TODO: Follow exercise 5.3.4, e.g. a), where fresh masks are introduced 37 | # TODO: See exercise 7.1.1. for working with ElGamal encryptions 38 | 39 | if isinstance(x, (SecureFiniteField, SecureInteger)): 40 | sectype = type(x) 41 | secgrp = mpc.SecGrp(group) 42 | r = mpc._random(sectype) 43 | h = await secgrp.repeat_public(g, r) 44 | 45 | B = await secgrp.repeat_public([g, h], [x, y]) 46 | u, v = [mpc._random(sectype) for i in range(2)] 47 | a = await secgrp.repeat_public([B, h], [u, v]) 48 | input_list = [a, B] 49 | c = gf(pivot.fiat_shamir_hash(input_list, gf.order)) 50 | r = await mpc.gather(u + c/x) 51 | s = await mpc.gather(v - c*y/x) 52 | elif isinstance(x, int): 53 | r = gf(prng.randrange(1, group.order)) 54 | h = g^int(r) 55 | 56 | B = (g^x)@(h^y) 57 | u, v = [gf(prng.randrange(1, group.order)) for i in range(2)] 58 | a = (B^int(u))@(h^int(v)) 59 | # In interactive protocol, verifier samples c: c = gf(prng.randrange(1, n)) 60 | # Make non-interactive with Fiat-Shamir heuristic 61 | input_list = [a, B] 62 | c = gf(pivot.fiat_shamir_hash(input_list, gf.order)) 63 | r = u + c/x 64 | s = v - c*y/x 65 | else: 66 | raise TypeError 67 | 68 | proof = { 69 | "predicate": SigmaProof.not_zero, 70 | "generators": (g, h), 71 | "commitment": B, 72 | "proof": (a, r, s), 73 | } 74 | return proof 75 | 76 | 77 | def sigma_verify_not_zero(proof): 78 | assert proof["predicate"] == SigmaProof.not_zero 79 | g, h = proof["generators"] 80 | B = proof["commitment"] 81 | a, r, s = proof["proof"] 82 | input_list = [a, B] 83 | gf = GF(modulus=type(g).order) 84 | c = gf(pivot.fiat_shamir_hash(input_list, gf.order)) 85 | return (B^int(r))@(h^int(s)) == a@(g^int(c)) 86 | 87 | 88 | async def example_not_zero(): 89 | await mpc.start() 90 | group = QuadraticResidues(l=64) 91 | secgrp = mpc.SecGrp(group) 92 | sectype = mpc.SecFld(modulus=group.order) 93 | # Prover 94 | # x = sectype(1) 95 | x = 1 96 | proof = await sigma_prove_not_zero(x, group) 97 | # Verifier 98 | print(sigma_verify_not_zero(proof)) 99 | await mpc.shutdown() 100 | 101 | 102 | if __name__ == "__main__": 103 | mpc.run(example_not_zero()) 104 | 105 | 106 | -------------------------------------------------------------------------------- /demos/demo_zkp_trinocchio.py: -------------------------------------------------------------------------------- 1 | """Demo Trinocchio protocol based on pynocchio module. 2 | 3 | Credits: 4 | * Trinocchio by Berry Schoenmakers, Meilof Veeningen and 5 | Niels de Vreede: https://eprint.iacr.org/2015/480 6 | * MPyC by Berry Schoenmakers: https://github.com/lschoe/mpyc 7 | * bn256 by Jack Lloyd: https://github.com/randombit/pairings.py/ 8 | (BSD-2-Clause license) 9 | * r1cs and qap tools by Vitalik Buterin: 10 | https://github.com/ethereum/research/tree/master/zksnark (MIT license) 11 | """ 12 | 13 | import os, sys 14 | import pprint as pp 15 | import verifiable_mpc.trinocchio.pynocchio as pynocchio 16 | import verifiable_mpc.trinocchio.trinocchio as trinocchio 17 | from mpyc.runtime import mpc 18 | from mpyc.thresha import _recombination_vector 19 | import verifiable_mpc.tools.code_to_qap as c2q 20 | import verifiable_mpc.tools.qap_creator as qc 21 | 22 | 23 | async def main(): 24 | await mpc.start() 25 | print(f"MPC parties {mpc.parties}") 26 | m = len(mpc.parties) 27 | trusted_party_id = 0 28 | print(f"Trusted party PID: {trusted_party_id}") 29 | 30 | secfld = mpc.SecFld(modulus=trinocchio.modulus) 31 | gf = secfld.field 32 | gf.is_signed = False 33 | 34 | # Client provides function code and inputs 35 | 36 | # inputs = [secfld(3)] 37 | # code = """ 38 | # def qeval(x): 39 | # y = x**3 40 | # return y + x + 5 41 | # """ 42 | inputs = [secfld(3), secfld(2)] 43 | code = """ 44 | def qeval(x, y): 45 | z = x**3 + 2*y**2 46 | return z + x + 5 47 | """ 48 | 49 | # QAP creation step 50 | qap = c2q.QAP(code, gf) 51 | print(f"QAP created. Size: {qap.m}, degree {qap.d}.") 52 | 53 | # Trusted party's KeyGen step 54 | if mpc.pid == trusted_party_id: 55 | td = pynocchio.Trapdoor(trinocchio.modulus) 56 | gen = pynocchio.Generators(td, trinocchio.g1, trinocchio.g2) 57 | evalkey = pynocchio.generate_evalkey(td, qap, gen) 58 | verikey = pynocchio.generate_verikey(td, qap, gen) 59 | else: 60 | evalkey = None 61 | verikey = None 62 | print("Keysets generated.") 63 | 64 | # Trusted party communicates keysets 65 | evalkey = await mpc.transfer(evalkey, trusted_party_id) 66 | verikey = await mpc.transfer(verikey, trusted_party_id) 67 | print("Trusted setup completed. Keysets received by parties.") 68 | 69 | # Prover's steps 70 | c = qap.calculate_witness(inputs) 71 | p = pynocchio.compute_p_poly(qap, c) 72 | h, r = p / qap.t 73 | 74 | # Compute proof_input for this MPC party 75 | # TODO: Proof not zero knowledge (not required for verification to work); make it ZK 76 | c_shares = await mpc.gather(c) 77 | # Compute shares of coefficients of h polynomial 78 | h_coeffs_shares = await mpc.gather(h.coeffs) 79 | h_shares = qc.Poly(h_coeffs_shares) 80 | proof_input = pynocchio.compute_proof(qap, c_shares, h_shares, evalkey) 81 | print("Proof computed.") 82 | 83 | # Communicate proof elements to all parties 84 | proof_inputs = await mpc.transfer(proof_input) 85 | 86 | # Recombine proof elements 87 | xcoords = tuple(i + 1 for i in range(m)) 88 | lagrange_vect = _recombination_vector(gf, xcoords, 0) 89 | proof = {} 90 | for key in proof_input.keys(): 91 | points_lambda = [lagrange_vect[i] * proof_inputs[i][key] for i in range(m)] 92 | proof_element = pynocchio.apply_to_list(trinocchio.point_add, points_lambda) 93 | proof[key] = proof_element 94 | print("Proof recombined.") 95 | 96 | c_out = await mpc.output(c[1:]) 97 | c_out = [1] + c_out 98 | c_client = c_out[: qap.out_ix + 1] 99 | 100 | verifications = pynocchio.verify(qap, verikey, proof, c_client) 101 | if all(verifications.values()): 102 | print("All checks passed.") 103 | else: 104 | print("Not all checks passed.") 105 | pp.pprint(verifications) 106 | 107 | await mpc.shutdown() 108 | 109 | 110 | if __name__ == "__main__": 111 | mpc.run(main()) 112 | 113 | 114 | # TODO list 115 | # TODO-1: Implement ZK variant 116 | # TODO-1: Implement multi-client with basic Trinocchio 117 | # TODO-1: Implement full Trinocchio scheme (Alg. 4, p. 25) 118 | -------------------------------------------------------------------------------- /demos/demo_circuit_builder.py: -------------------------------------------------------------------------------- 1 | """Demonstrate circuit_builder module. 2 | 3 | Initialize a circuit using the Circuit class. 4 | The Circuit class tracks gates and variables corresponding to a circuit. 5 | A circuit is constructed by using instances of CircuitVar in arithmetic. 6 | """ 7 | import sys, os 8 | from mpyc.finfields import GF 9 | from mpyc.runtime import mpc 10 | import verifiable_mpc.ac20.circuit_builder as cb 11 | 12 | 13 | if __name__ == "__main__": 14 | circuit = cb.Circuit() 15 | 16 | # Initialize field with modulus 2^31-1; https://en.wikipedia.org/wiki/2,147,483,647 17 | gf = GF(2_147_483_647) 18 | 19 | # Initialize input variables b, c. 20 | b = cb.CircuitVar(gf(2), circuit, "b") 21 | c = cb.CircuitVar(gf(2), circuit, "c") 22 | 23 | # Construct arithmetic circuit using pure Python. 24 | # d = 10 - c 25 | # d = c - 10 26 | d = c + c + c * c + c * c * 1 + 1 + b 27 | e = d * d + c + 10 28 | f = d * c + e 29 | # f = b * b + c - c 30 | # f = b * b 31 | 32 | # Label the variables that represent output gates. 33 | f.label_output("f") 34 | g = f + 100 35 | g.label_output("g") 36 | print(f"Output gates: {f=} and {g=}") 37 | 38 | # Print attributes of the circuit up to this point (circuits can extended). 39 | print("Circuit attributes:") 40 | print(f"{circuit.mul_ct=}") 41 | print(f"{circuit.add_ct=}") 42 | print(f"{circuit.input_ct=}") 43 | print(f"{circuit.output_ct=}") 44 | print("String representation of circuit:") 45 | print(cb.print_circuit(circuit)) 46 | print("CircuitVars: ", circuit.circuitvars) 47 | print("Input gates (indexes): ", circuit.input_gates) 48 | 49 | # Evaluate the circuit for given inputs: 50 | inputs = [gf(2), gf(2)] 51 | print(f"Evaluate circuit for {inputs=}: {circuit(inputs)}") 52 | 53 | # print(f"Evaluate circuit up to and including a specified gate:") 54 | # for g in circuit.gates: 55 | # y = circuit.eval(inputs, g) 56 | # print(f"{y=} for {str(g)}") 57 | 58 | print("Construct objects for AC20 proof system.") 59 | alpha, beta, gamma = circuit.multiplication_triples(inputs) 60 | f_poly, g_poly, h_poly = cb.calculate_fgh_polys(alpha, beta, None, gf) 61 | challenge = 15 62 | f_form = cb.calculate_fg_form(circuit, wire=0, challenge=challenge, gf=gf) 63 | g_form = cb.calculate_fg_form(circuit, wire=1, challenge=challenge, gf=gf) 64 | h_form = cb.calculate_h_form(circuit, challenge, gf) 65 | print(f"{f_form=}") 66 | print(f"{g_form=}") 67 | print(f"{h_form=}") 68 | 69 | # TODO: construct z_vector without construction of f_, g_, h_poly (see mpc_ac20.py) 70 | z_vector = inputs + [f_poly(0), g_poly(0)] + [h_poly(i) for i in range(2 * circuit.mul_ct + 1)] 71 | 72 | assert f_form(z_vector) * g_form(z_vector) == h_form(z_vector) 73 | assert f_poly(challenge) * g_poly(challenge) == h_poly(challenge) 74 | assert h_form(z_vector) == h_poly(challenge) 75 | 76 | print("Construct form(s) corresponding to circuit output(s), and evaluate up to and including that gate.") 77 | # See also Section 5.2 and 6.1 of 'Pinocchio-Based Adaptive zk-SNARKs and Secure/Correct Adaptive Function Evaluation' [Vee17] 78 | # Link to [Vee17]: https://eprint.iacr.org/2017/013.pdf 79 | forms = cb.calculate_circuit_forms(circuit) 80 | print("Circuit output forms: ", forms) 81 | print([form(inputs+gamma) for form in forms]) 82 | 83 | 84 | print("*** Demo gadgets ***") 85 | print("Demonstrate != 0 gadget, particularly b = (a != 0) ? 1 : 0") 86 | # Start with new circuit 87 | circuit = cb.Circuit() 88 | # Initialize input variables b, c. 89 | a = cb.CircuitVar(gf(3), circuit, "a") # Input variable, in this case != 0 90 | print(f"Input a = {a}") 91 | b = a != 0 # b = (a != 0) ? 1 : 0 92 | b.label_output("b") 93 | print("b=", b) 94 | # Circuit builder writes equations a · c = b, a · (1 − b) = 0 95 | print(circuit) 96 | a = gf(3) 97 | # Retrieve inputs (including auxiliary inputs) that were recorded during circuit construction. 98 | inputs = circuit.initial_inputs() 99 | print(f"Evaluate circuit for {inputs=}: {circuit(inputs)}") 100 | 101 | # Demo a <= b gadget, start with new circuit. 102 | print("Demonstrate <= gadget.") 103 | circuit = cb.Circuit() 104 | secint = mpc.SecInt() 105 | a = cb.CircuitVar(secint(5), circuit, "b") 106 | b = cb.CircuitVar(secint(3), circuit, "c") 107 | c = a <= b 108 | c.label_output("c") 109 | # print(cb.print_out_gate(circuit, circuit.out_gates()[0])) 110 | # print(circuit) 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /demos/demo_zkp_ac20.py: -------------------------------------------------------------------------------- 1 | """Demo ``Compressed Sigma-Protocol Theory'' by Attema and Cramer. 2 | 3 | Full title: ``Compressed Sigma-Protocol Theory and Practical 4 | Application to Plug & Play Secure Algorithmics'' 5 | by Thomas Attema and Ronald Cramer. 6 | Link: https://eprint.iacr.org/2020/152 7 | 8 | This demo proves circuit satisfiability using the pivots 9 | explained in the paper (regular, compressed and KoE-based pivot). 10 | 11 | Credits: 12 | * r1cs and qap tools by Vitalik Buterin: 13 | https://github.com/ethereum/research/tree/master/zksnark (MIT license) 14 | (Adapted for this purpose). 15 | """ 16 | 17 | import pprint 18 | import sys, os 19 | from mpyc.finfields import GF 20 | import verifiable_mpc.ac20.circuit_sat_cb as cs 21 | import verifiable_mpc.ac20.circuit_builder as cb 22 | from mpyc.fingroups import QuadraticResidues, EllipticCurve 23 | 24 | 25 | pp = pprint.PrettyPrinter(indent=4) 26 | 27 | PIVOT = cs.PivotChoice.compressed 28 | GROUP = "QR" 29 | 30 | 31 | def main(pivot_choice, n=3): 32 | print("Pivot selected: ", pivot_choice) 33 | 34 | if pivot_choice == cs.PivotChoice.koe: 35 | # TODO: improve syntax for passing two groups to create_generators 36 | group1 = EllipticCurve('BN256', 'jacobian') # 'projective' 37 | group2 = EllipticCurve('BN256_twist', 'jacobian') #'projective' 38 | group1.is_additive = False 39 | group1.is_multiplicative = True 40 | group2.is_additive = False 41 | group2.is_multiplicative = True 42 | group = [group1, group2] 43 | order = group1.order 44 | gf = GF(modulus=order) 45 | elif GROUP == "Elliptic": 46 | group = EllipticCurve('Ed25519', 'projective') 47 | group.is_additive = False 48 | group.is_multiplicative = True 49 | gf = GF(modulus=group.order) 50 | elif GROUP == "QR": 51 | group = QuadraticResidues(l=1024) 52 | gf = GF(modulus=group.order) 53 | 54 | circuit = cb.Circuit() 55 | # b = cb.CircuitVar(gf(1), circuit, "b") 56 | # c = cb.CircuitVar(gf(2), circuit, "c") 57 | b = cb.CircuitVar(1, circuit, "b") 58 | c = cb.CircuitVar(2, circuit, "c") 59 | 60 | d = c + c + c * c + c * c * 1 + 1 + b 61 | e = d*d + c**n + 10 62 | f = d*c + e 63 | f.label_output("f") 64 | g = f != 100 65 | g.label_output("g") 66 | h = g >= 10 # Note: comparison only works for integers 67 | h.label_output("h") 68 | 69 | x = circuit.initial_inputs() 70 | # Check if resulting commitment vector is of appropriate length. 71 | check, padding, g_length = cs.check_input_length_power_of_2(x, circuit) 72 | # Add unused variables to pad the length of the commitment vector to power of 2 minus 1. 73 | unused = [cb.CircuitVar(0, circuit, "unused_"+str(i)) for i in range(padding)] 74 | x = circuit.initial_inputs() 75 | print("Length of input vector including auxiliary inputs (witnesses for special gates): ", len(x)) 76 | print("Length of commitment vector: ", g_length) 77 | 78 | generators = cs.create_generators(g_length, pivot_choice, group, progress_bar=True) 79 | print("Generators created/trusted setup done.") 80 | 81 | print("Start non-interactive circuit satisfiability proof with compressed pivot. ") 82 | proof = cs.circuit_sat_prover(generators, circuit, x, gf, pivot_choice) 83 | print("Proof:") 84 | pp.pprint(proof) 85 | print("Start verification.") 86 | verification = cs.circuit_sat_verifier(proof, generators, circuit, gf, pivot_choice) 87 | print("Verification checks: ") 88 | pp.pprint(verification) 89 | 90 | return verification 91 | 92 | 93 | 94 | if __name__ == "__main__": 95 | import argparse 96 | 97 | parser = argparse.ArgumentParser() 98 | parser.add_argument("-n", type=int, help="roughly number of multiplications") 99 | parser.add_argument("--elliptic", action="store_true", 100 | help="use elliptic curve groups (default QR groups)") 101 | parser.add_argument('--basic', action='store_true', 102 | help='use basic pivot (not the compressed pivot)') 103 | parser.add_argument('--koe', action='store_true', 104 | help='use pivot based on Knowledge-of-Exponent assumption and BN256 curves') 105 | parser.set_defaults(n=3) 106 | args = parser.parse_args() 107 | if args.elliptic: 108 | GROUP = "Elliptic" 109 | elif args.basic: 110 | PIVOT = cs.PivotChoice.pivot 111 | elif args.koe: 112 | PIVOT = cs.PivotChoice.koe 113 | 114 | verification = main(PIVOT, args.n) 115 | 116 | 117 | 118 | # TODO: 119 | # TS-1: Make interface of create_generators(,,group,) more intuitive when working with KOE 120 | # TS-1: Remove L from proof; non-interactive nullity proof does not have to pass it to verifier. 121 | -------------------------------------------------------------------------------- /demos/demo_zkp_mpc_ac20.py: -------------------------------------------------------------------------------- 1 | """Demo prover side of Circuit Sat ZK from [AC20] in MPC. 2 | 3 | Based on: "Compressed Sigma-Protocol Theory and Practical 4 | Application to Plug & Play Secure Algorithmics" 5 | by Thomas Attema and Ronald Cramer. 6 | Link: https://eprint.iacr.org/2020/152 7 | 8 | Uses MPyC framework and 'Secure Groups' abstraction. 9 | 10 | Credits: 11 | * Verifiable MPC introduced by Berry Schoenmakers, Meilof 12 | Veeningen and Niels de Vreede: https://eprint.iacr.org/2015/480 13 | * "Compressed Sigma-Protocol Theory" by Thomas Attema and Ronald Cramer. 14 | """ 15 | 16 | import pprint 17 | import os 18 | import sys 19 | from mpyc.fingroups import QuadraticResidues, EllipticCurve 20 | from mpyc.runtime import mpc 21 | import verifiable_mpc.ac20.circuit_builder as cb 22 | import verifiable_mpc.ac20.circuit_sat_cb as cs 23 | import verifiable_mpc.ac20.mpc_ac20_cb as mpc_cs 24 | 25 | 26 | pp = pprint.PrettyPrinter(indent=4) 27 | 28 | PIVOT = cs.PivotChoice.compressed 29 | GROUP = "QR" 30 | 31 | 32 | async def main(pivot_choice, group_choice, n): 33 | await mpc.start() 34 | 35 | # General setup 36 | if pivot_choice == cs.PivotChoice.koe: 37 | group1 = EllipticCurve('BN256', 'projective') # 'jacobian' 38 | group2 = EllipticCurve('BN256_twist', 'projective') # 'jacobian' 39 | # NB: using projective here because we need oblivious implementation of groups 40 | group1.is_additive = False 41 | group1.is_multiplicative = True 42 | group2.is_additive = False 43 | group2.is_multiplicative = True 44 | group = [group1, group2] 45 | sec_grp = mpc.SecGrp(group1) 46 | sec_grp2 = mpc.SecGrp(group2) 47 | assert sec_grp.group.order == sec_grp2.group.order 48 | elif group_choice == "Elliptic": 49 | group = EllipticCurve('Ed25519', 'projective') 50 | group.is_additive = False 51 | group.is_multiplicative = True 52 | sec_grp = mpc.SecGrp(group) 53 | elif group_choice == "QR": 54 | group = QuadraticResidues(l=1024) 55 | sec_grp = mpc.SecGrp(group) 56 | else: 57 | raise ValueError 58 | 59 | print("Start AC20 with group: ", group) 60 | 61 | sectype = mpc.SecInt(l=16, p=sec_grp.group.order) 62 | print("sectype.bit_length:", sectype.bit_length) 63 | # sectype = mpc.SecFld(modulus=sec_grp.group.order) 64 | gf = sectype.field 65 | 66 | circuit = cb.Circuit() 67 | # For integers, ensure enough (30 bit) headroom vs SecInt.p 68 | b = cb.CircuitVar(sectype(1), circuit, "b") 69 | c = cb.CircuitVar(sectype(2), circuit, "c") 70 | 71 | d = c + c + c * c + c * c * 1 + 1 + b 72 | e = d * d + c**n + 10 73 | f = d * c + e 74 | f.label_output("f") 75 | g = f != 100 76 | g.label_output("g") 77 | h = g >= 10 # Comparison requires sectype = mpc.Secint(..). Slow with KoE pivot. 78 | h.label_output("h") 79 | 80 | x = circuit.initial_inputs() 81 | # Check if resulting commitment vector is of appropriate length. 82 | check, padding, g_length = cs.check_input_length_power_of_2(x, circuit) 83 | # Add unused variables to pad the length of the commitment vector to power of 2 minus 1. 84 | unused = [cb.CircuitVar(0, circuit, "unused_"+str(i)) for i in range(padding)] 85 | x = circuit.initial_inputs() 86 | 87 | print("Length of input vector including auxiliary inputs (witnesses for special gates): ", len(x)) 88 | print("Length of commitment vector: ", g_length) 89 | 90 | print("Create generators.") 91 | if pivot_choice in [cs.PivotChoice.pivot, cs.PivotChoice.compressed]: 92 | generators = await mpc_cs.create_generators(group, sectype, g_length) 93 | elif pivot_choice in [cs.PivotChoice.koe]: 94 | generators = await mpc_cs.koe_trusted_setup(group, sectype, g_length, progress_bar=True) 95 | else: 96 | raise NotImplementedError 97 | 98 | print("Start non-interactive circuit satisfiability proof. ") 99 | proof = await mpc_cs.circuit_sat_prover(generators, circuit, x, gf, pivot_choice) 100 | 101 | print("Start verification.") 102 | verification = cs.circuit_sat_verifier(proof, generators, circuit, gf, pivot_choice) 103 | 104 | if all(verification.values()): 105 | print("All checks passed.") 106 | else: 107 | print("Not all checks passed.") 108 | pp.pprint(verification) 109 | 110 | await mpc.shutdown() 111 | return verification 112 | 113 | 114 | if __name__ == "__main__": 115 | import argparse 116 | 117 | parser = argparse.ArgumentParser() 118 | parser.add_argument("-n", type=int, help="roughly number of multiplications") 119 | parser.add_argument("--elliptic", action="store_true", 120 | help="use elliptic curve groups (default QR groups)") 121 | parser.add_argument('--basic', action='store_true', 122 | help='use basic pivot (not the compressed pivot)') 123 | parser.add_argument('--koe', action='store_true', 124 | help='use pivot based on Knowledge-of-Exponent assumption and BN256 curves') 125 | parser.set_defaults(n=3) 126 | args = parser.parse_args() 127 | if args.elliptic: 128 | GROUP = "Elliptic" 129 | elif args.basic: 130 | PIVOT = cs.PivotChoice.pivot 131 | elif args.koe: 132 | PIVOT = cs.PivotChoice.koe 133 | 134 | verification = mpc.run(main(PIVOT, GROUP, args.n)) 135 | 136 | 137 | # TODO-list 138 | # TODO-2: improve syntax for passing two groups to create_generators 139 | # TODO-2: Check if necessary to open z_prime in protocol_4_prover 140 | -------------------------------------------------------------------------------- /demos/demo_zkp_pynocchio.py: -------------------------------------------------------------------------------- 1 | """Demo Pinocchio protocol based on pynocchio module. 2 | 3 | Credits: 4 | * Trinocchio by Berry Schoenmakers, Meilof Veeningen 5 | and Niels de Vreede: https://eprint.iacr.org/2015/480 6 | * MPyC by Berry Schoenmakers: https://github.com/lschoe/mpyc 7 | * Original bn256 module by Jack Lloyd: 8 | https://github.com/randombit/pairings.py/ (BSD-2-Clause license) 9 | * r1cs and qap modules by Vitalik Buterin: 10 | https://github.com/ethereum/research/tree/master/zksnark (MIT license) 11 | """ 12 | 13 | import logging 14 | import pprint 15 | from random import SystemRandom 16 | import os, sys 17 | import verifiable_mpc.trinocchio.pynocchio as pynocchio 18 | from mpyc.finfields import GF, FiniteFieldElement 19 | from mpyc.fingroups import EllipticCurve 20 | import verifiable_mpc.tools.code_to_qap as c2q 21 | import verifiable_mpc.tools.qap_creator as qc 22 | 23 | 24 | def main(): 25 | pp = pprint.PrettyPrinter(indent=4) 26 | 27 | bn_curve = EllipticCurve('BN256', 'jacobian') 28 | g1 = bn_curve.generator 29 | bn_twist = EllipticCurve('BN256_twist', 'jacobian') 30 | g2 = bn_twist.generator 31 | bn_curve.is_additive = True 32 | bn_curve.is_multiplicative = False 33 | bn_twist.is_additive = True 34 | bn_twist.is_multiplicative = False 35 | # TODO: reconsider global setting of is_additive 36 | # interaction with other demos, e.g., in python -m unittest 37 | # necessitates resetting to default settings at this point 38 | 39 | modulus = bn_curve.order 40 | 41 | gf = GF(modulus=modulus) 42 | gf.is_signed = False 43 | 44 | # Set inputs 45 | inputs = [gf(3)] 46 | code = """ 47 | def qeval(x): 48 | y = x**3 + x**2 + x 49 | return y + x + 5 50 | """ 51 | 52 | # inputs = [gf(3), gf(2)] 53 | # code = """ 54 | # def qeval(x, y): 55 | # z = x**3 + 2*y**2 56 | # return z + x + 5 57 | # """ 58 | 59 | # QAP creation step 60 | qap = c2q.QAP(code, gf) 61 | print(f"QAP created. Size: {qap.m}, degree {qap.d}.") 62 | 63 | # Trusted Party's KeyGen step 64 | td = pynocchio.Trapdoor(modulus) 65 | gen = pynocchio.Generators(td, g1, g2) 66 | evalkey = pynocchio.generate_evalkey(td, qap, gen) 67 | verikey = pynocchio.generate_verikey(td, qap, gen) 68 | print("Trusted setup completed.") 69 | 70 | # Prover's steps 71 | c = qap.calculate_witness(inputs) 72 | p = pynocchio.compute_p_poly(qap, c) 73 | h, r = p / qap.t 74 | assert r == qc.Poly( 75 | [0] * qap.d 76 | ), "Remainder of p(x)/t(x) for given witness is not 0" 77 | deltas = pynocchio.SampleDeltas(modulus) 78 | # Create zero knowledge variant of h poly 79 | h_zk = h + pynocchio.compute_h_zk_terms(qap, c, deltas) 80 | h = h_zk 81 | # Compute proof 82 | proof = pynocchio.compute_proof(qap, c, h, evalkey, deltas) 83 | print("Proof computed.") 84 | 85 | # Verifier's step 86 | verifications = pynocchio.verify(qap, verikey, proof, c[: qap.out_ix + 1]) 87 | if all(verifications.values()): 88 | print("All checks passed.") 89 | else: 90 | print("Not all checks passed.") 91 | pp.pprint(verifications) 92 | return verifications 93 | 94 | 95 | if __name__ == "__main__": 96 | verifications = main() 97 | # # Globals when running script 98 | # pp = pprint.PrettyPrinter(indent=4) 99 | 100 | # bn_curve = EllipticCurve(ell.BN256, ell.WEI_JAC, ell.Weierstr_Jacobian_Arithm) 101 | # g1 = bn_curve.base_pt 102 | # bn_twist = EllipticCurve(ell.BN256_TWIST, ell.WEI_JAC, ell.Weierstr_Jacobian_Arithm) 103 | # g2 = bn_twist.base_pt 104 | 105 | # modulus = bn_curve.order 106 | 107 | # gf = GF(modulus=modulus) 108 | # gf.is_signed = False 109 | 110 | # # Set inputs 111 | # inputs = [gf(3)] 112 | # code = """ 113 | # def qeval(x): 114 | # y = x**3 + x**2 + x 115 | # return y + x + 5 116 | # """ 117 | 118 | # # inputs = [gf(3), gf(2)] 119 | # # code = """ 120 | # # def qeval(x, y): 121 | # # z = x**3 + 2*y**2 122 | # # return z + x + 5 123 | # # """ 124 | 125 | # # QAP creation step 126 | # qap = c2q.QAP(code, gf) 127 | # print(f"QAP created. Size: {qap.m}, degree {qap.d}.") 128 | 129 | # # Trusted Party's KeyGen step 130 | # td = pynocchio.Trapdoor(modulus) 131 | # gen = pynocchio.Generators(td, g1, g2) 132 | # evalkey = pynocchio.generate_evalkey(td, qap, gen) 133 | # verikey = pynocchio.generate_verikey(td, qap, gen) 134 | # print("Trusted setup completed.") 135 | 136 | # # Prover's steps 137 | # c = qap.calculate_witness(inputs) 138 | # p = pynocchio.compute_p_poly(qap, c) 139 | # h, r = p / qap.t 140 | # assert r == qc.Poly( 141 | # [0] * qap.d 142 | # ), "Remainder of p(x)/t(x) for given witness is not 0" 143 | # deltas = pynocchio.SampleDeltas(modulus) 144 | # # Create zero knowledge variant of h poly 145 | # h_zk = h + pynocchio.compute_h_zk_terms(qap, c, deltas) 146 | # h = h_zk 147 | # # Compute proof 148 | # proof = pynocchio.compute_proof(qap, c, h, evalkey, deltas) 149 | # print("Proof computed.") 150 | 151 | # # Verifier's step 152 | # verifications = pynocchio.verify(qap, verikey, proof, c[: qap.out_ix + 1]) 153 | # if all(verifications.values()): 154 | # print("All checks passed.") 155 | # else: 156 | # print("Not all checks passed.") 157 | # pp.pprint(verifications) 158 | 159 | 160 | # TODO list 161 | # TODO-1: Replace simple classes by SimpleNamespaces: https://stackoverflow.com/questions/16279212/how-to-use-dot-notation-for-dict-in-python 162 | # TODO-2: Optimize g_eval: consider caching, or reuse of v_g1 set for alpha v_g1 set 163 | # TODO-3: Pinocchio Section 4.2.1 (p.8) discusses bad asymptotics with naive algorithms instead of FFT; update with FFT 164 | -------------------------------------------------------------------------------- /verifiable_mpc/ac20/knowledge_of_exponent.py: -------------------------------------------------------------------------------- 1 | """ Implementation of https://eprint.iacr.org/2020/152 2 | ``Compressed Σ-Protocol Theory and Practical Application 3 | to Plug & Play Secure Algorithmics'' 4 | 5 | Constant-sized, non-interactive proof based on the n-power 6 | Knowledge-of-Exponent Assumption (n-PKEA). 7 | 8 | Protocols: 9 | * Program from the Knowledge-of-Exponent Assumption, 10 | Section 9, page 25. Based on personal communication with authors. 11 | """ 12 | 13 | import os 14 | import sys 15 | from random import SystemRandom 16 | import mpyc.mpctools as mpctools 17 | import verifiable_mpc.ac20.pivot as pivot 18 | import verifiable_mpc.tools.qap_creator as qc 19 | import verifiable_mpc.ac20.pairing as pairing 20 | 21 | prng = SystemRandom() 22 | 23 | 24 | 25 | def list_mul(x): 26 | return mpctools.reduce(type(x[0]).operation, x) 27 | 28 | 29 | def vector_commitment(x, gamma, g, h): 30 | """ Pedersen vector commitment. Definition 1 of AC20. 31 | """ 32 | assert len(g) >= len(x), "Not enough generators." 33 | gpowers = [g[i] ** int(x_i) for i, x_i in enumerate(x)] 34 | # prod = mpctools.reduce(type(h).operation, gpowers) 35 | prod = list_mul(gpowers) 36 | 37 | c = (h ** gamma) * prod 38 | return c 39 | 40 | 41 | def _pairing(a, b): 42 | """Flip inputs to pairing.optimal_ate() function. 43 | 44 | First input "base" curve, second input "twist" curve 45 | as per Pinocchio/Trinocchio notation. 46 | """ 47 | return pairing.optimal_ate(b, a) 48 | 49 | 50 | def trusted_setup(_g1, _g2, n, order, progress_bar=False): 51 | g_exp = prng.randrange(1, order) 52 | alpha = prng.randrange(order) 53 | z = prng.randrange(order) 54 | g1 = _g1 ** g_exp 55 | g2 = (_g2 ** g_exp) ** alpha 56 | 57 | pp_lhs = [] 58 | pp_rhs = [] 59 | g1_base = g1 60 | g2_base = g2 61 | for i in range(2 * n): 62 | g1_base = g1_base ** z 63 | g2_base = g2_base ** z 64 | 65 | pp_lhs.append(g1_base) 66 | pp_rhs.append(g2_base) 67 | 68 | if progress_bar: 69 | print(f"Generating keys: {round(100*i/(2*n+1))}%", end="\r") 70 | 71 | pp = {"pp_lhs": pp_lhs, "pp_rhs": pp_rhs} 72 | return pp 73 | 74 | 75 | def restriction_argument_prover(S, x, gamma, pp): 76 | """Resriction argument from [Gro10]: Prover's side. 77 | 78 | Extension of the Knowledge Commitment Scheme, but proofs that a subset 79 | of indices S of {0, ..., n-1}, corresponding to vector x, was used. 80 | """ 81 | 82 | # Prover commits only to S-indices of x with (new) commitment P, with secret random gamma 83 | P = (pp["pp_lhs"][0] ** int(gamma)) * list_mul([pp["pp_lhs"][i + 1] ** int(x[i]) for i in S]) 84 | 85 | # Trusted party or verifier samples beta; only required in designated verifier scenario 86 | # beta = prng.randrange(1, order) 87 | # sigma = (g1**beta, [(g2**(z**i))**beta for i in S]) 88 | 89 | # Prover computes pi^alpha (slight deviation from Thomas' notes, who computes pi) 90 | pi = (pp["pp_rhs"][0] ** int(gamma)) * list_mul([pp["pp_rhs"][i + 1] ** int(x[i]) for i in S]) 91 | return P, pi 92 | 93 | 94 | def restriction_argument_verifier(P, pi, pp): 95 | """Restriction argument from [Gro10]: Verifier's side. 96 | """ 97 | verification = _pairing(P, pp["pp_rhs"][0]) == _pairing(pp["pp_lhs"][0], pi) 98 | return verification 99 | 100 | 101 | def opening_linear_form_prover(L, x, gamma, pp, P=None, pi=None): 102 | """ZK argument of knowledge for the opening of a linear form. 103 | 104 | Using adaptation of the multiplication argument of [Gro10]. 105 | """ 106 | proof = {} 107 | n = len(x) 108 | S = range(n) 109 | assert 2*n - 1 <= len(pp["pp_lhs"]), "Requirement does not hold: 2*len(x)-1 <= number of generators in first group." 110 | # Run Restriction argument on (P, {1, ..., n}; x, gamma) to show that 111 | # (P, P_bar) is indeed a commitment to vector x in Z_q 112 | if P is None: 113 | P, pi = restriction_argument_prover(S, x, gamma, pp) 114 | proof["P"] = P 115 | proof["pi"] = pi 116 | 117 | # Prover computes coefficients of c_poly and Q, sends Q to verifier 118 | u = L(x) 119 | L_linear, u_linear = pivot.affine_to_linear(L, u, n) 120 | 121 | c_poly_lhs = qc.Poly([gamma] + [x_i for x_i in x]) 122 | c_poly_rhs = qc.Poly([L_linear.coeffs[n - (j + 1)] for j in range(n)]) 123 | c_poly = c_poly_lhs * c_poly_rhs 124 | 125 | assert u_linear == c_poly.coeffs[n], "L(x) not equal to n-th coefficient of c_poly" 126 | c_bar = c_poly.coeffs 127 | c_bar[n] = 0 128 | assert len(pp["pp_lhs"]) == 2 * n 129 | Q = list_mul([g_i ** (-1 * int(c_bar[i])) for i, g_i in enumerate(pp["pp_lhs"])]) 130 | proof["Q"] = Q 131 | return proof, u 132 | 133 | 134 | def opening_linear_form_verifier(L, pp, proof, u): 135 | n = len(L.coeffs) 136 | g1 = pp["pp_lhs"][0] 137 | g2 = pp["pp_rhs"][0] 138 | L_linear, u_linear = pivot.affine_to_linear(L, u, n) 139 | P = proof["P"] 140 | pi = proof["pi"] 141 | Q = proof["Q"] 142 | verification = {} 143 | # verification["restriction_arg_check"] = _pairing(P, g2) == _pairing(g1, pi) 144 | verification["restriction_arg_check"] = restriction_argument_verifier(P, pi, pp) 145 | # R = list_mul([pp["pp_rhs"][j] ** (L_linear.coeffs[n - (j + 1)]) for j in range(n)]) 146 | R = list_mul([pp["pp_rhs"][j] ** int(L_linear.coeffs[n - (j + 1)]) for j in range(n)]) 147 | check_lhs = _pairing(P, R) * _pairing(Q, g2) 148 | check_rhs = _pairing(g1, pp["pp_rhs"][n] ** int(u_linear)) 149 | verification["PRQ_check"] = check_lhs == check_rhs 150 | return verification 151 | 152 | 153 | def prove_nullity_koe(pp, lin_forms, x, gamma, gf, P, pi): 154 | """Nullity protocol calling the ZK Proof for lienar form openings 155 | based on Knowledge of Exponent assumption. 156 | """ 157 | input_list = [P, lin_forms] 158 | rho = pivot.fiat_shamir_hash(input_list, gf.order) 159 | L = sum((linform_i) * (rho ** i) for i, linform_i in enumerate(lin_forms)) 160 | L = pivot.LinearForm([gf(c) if isinstance(c, int) else c for c in L.coeffs]) 161 | proof, u = opening_linear_form_prover(L, x, gamma, pp, P, pi) 162 | return proof, L, u 163 | -------------------------------------------------------------------------------- /verifiable_mpc/ac20/pivot.py: -------------------------------------------------------------------------------- 1 | """ Implementation of https://eprint.iacr.org/2020/152 2 | ``Compressed Σ-Protocol Theory and Practical Application 3 | to Plug & Play Secure Algorithmics'' 4 | 5 | Protocols: 6 | * Protocol 2, page 13: Pi_s ("pivot") 7 | 8 | """ 9 | 10 | import os 11 | import sys 12 | import hashlib 13 | from random import SystemRandom 14 | from mpyc.finfields import FiniteFieldElement 15 | import mpyc.mpctools as mpctools 16 | from mpyc.runtime import logging 17 | from mpyc.sectypes import SecureObject 18 | from mpyc.fingroups import EllipticCurvePoint as EllipticCurveElement 19 | 20 | 21 | prng = SystemRandom() 22 | 23 | logger_piv = logging.getLogger("pivot") 24 | logger_piv.setLevel(logging.INFO) 25 | 26 | def list_mul(x): 27 | rettype = type(x[0]) 28 | return mpctools.reduce(rettype.operation, x, initial=rettype.identity) 29 | 30 | 31 | class AffineForm: 32 | def __init__(self, coeffs, constant): 33 | self.coeffs = coeffs 34 | self.constant = constant 35 | 36 | def __add__(self, other): 37 | if isinstance(other, AffineForm): 38 | assert len(self) == len( 39 | other 40 | ), "Length of linear forms to add not consistent." 41 | new_coeffs = [self.coeffs[i] + other.coeffs[i] for i in range(len(self))] 42 | new_constant = self.constant + other.constant 43 | elif isinstance(other, (int, FiniteFieldElement, SecureObject)): 44 | new_coeffs = self.coeffs 45 | new_constant = self.constant + other 46 | else: 47 | raise NotImplementedError( 48 | f"Addition of form not defined for type: {type(other)}" 49 | ) 50 | 51 | return type(self)(new_coeffs, new_constant) 52 | 53 | def __radd__(self, other): 54 | if other == 0: 55 | return self 56 | else: 57 | return self.__add__(other) 58 | 59 | def __sub__(self, other): 60 | return self + (-1) * other 61 | 62 | def __mul__(self, other): 63 | if isinstance(other, (int, FiniteFieldElement)): 64 | new_coeffs = [coeffs_i * other for coeffs_i in self.coeffs] 65 | new_constant = self.constant * other 66 | else: 67 | raise NotImplementedError( 68 | f"Multiplication of form not defined for type: {type(other)}" 69 | ) 70 | return type(self)(new_coeffs, new_constant) 71 | 72 | def __rmul__(self, other): 73 | return self * other 74 | 75 | def __len__(self): 76 | return len(self.coeffs) 77 | 78 | def __eq__(self, other): 79 | return self.coeffs == other.coeffs 80 | 81 | def __repr__(self): 82 | return f"{str(self.coeffs)}, {str(self.constant)}" 83 | 84 | def eval(self, values): 85 | assert len(values) == len( 86 | self.coeffs 87 | ), "Length of inputs to be equal to coefficients of linear form." 88 | result = ( 89 | sum([self.coeffs[i] * values_i for i, values_i in enumerate(values)]) 90 | + self.constant 91 | ) 92 | return result 93 | 94 | def __call__(self, values): 95 | return self.eval(values) 96 | 97 | 98 | class LinearForm(AffineForm): 99 | def __init__(self, coeffs, constant=0): 100 | self.coeffs = coeffs 101 | self.constant = 0 102 | 103 | def __add__(self, other): 104 | if isinstance(other, AffineForm): 105 | assert len(self) == len( 106 | other 107 | ), "Length of linear forms to add not consistent." 108 | new_coeffs = [self.coeffs[i] + other.coeffs[i] for i in range(len(self))] 109 | new_constant = self.constant + other.constant 110 | elif isinstance(other, (int, FiniteFieldElement, SecureObject)): 111 | new_coeffs = self.coeffs 112 | new_constant = self.constant + other 113 | else: 114 | raise NotImplementedError 115 | 116 | return AffineForm(new_coeffs, new_constant) 117 | 118 | 119 | def _int(value): 120 | """ Convert FiniteFieldElements to ints, for sectypes (type: SecureObject) do nothing. 121 | 122 | """ 123 | if isinstance(value, (int, SecureObject)): 124 | return value 125 | elif isinstance(value, FiniteFieldElement): 126 | return int(value) 127 | else: 128 | raise NotImplementedError 129 | 130 | 131 | def fiat_shamir_hash(input_list, order): 132 | # TODO: Review / Ensure that hash lengths are not ambiguous for group elements. 133 | # TODO: add to_bytes as a method for Group elements (adds padding to e.g. 200 bytes, not necessarily reversible) 134 | hash_input = str(input_list).encode("utf-8") 135 | c = int.from_bytes(hashlib.sha256(hash_input).digest(), "little") % order 136 | return c 137 | 138 | 139 | def vector_commitment(x, gamma, g, h): 140 | """ Pedersen vector commitment. Definition 1 of AC20. 141 | """ 142 | assert len(g) >= len(x), "Not enough generators." 143 | prod = list_mul([g[i] ** _int(x_i) for i, x_i in enumerate(x)]) 144 | c = (h ** gamma) * prod 145 | return c 146 | 147 | 148 | def affine_to_linear(L, y, n): 149 | zeros = [0] * n 150 | constant = L(zeros) 151 | L_linear = L - constant 152 | y_linear = y - constant 153 | return L_linear, y_linear 154 | 155 | 156 | def prove_linear_form_eval(g, h, P, L, y, x, gamma, gf): 157 | """ Sigma protocol Pi_s (protocol 2) from AC20. 158 | 159 | Non-interactive version. 160 | """ 161 | n = len(x) 162 | L, y = affine_to_linear(L, y, n) 163 | r = list(gf(prng.randrange(gf.order)) for i in range(n)) 164 | rho = prng.randrange(gf.order) 165 | t = L(r) 166 | A = vector_commitment(r, rho, g, h) 167 | logger_piv.debug(f"Prover computed A={A}.") 168 | 169 | if isinstance(A, EllipticCurveElement): 170 | input_list = [t, A.normalize(), g, h, P.normalize(), L, y] 171 | else: 172 | input_list = [t, A, g, h, P, L, y] 173 | 174 | c = fiat_shamir_hash(input_list, gf.order) 175 | z = [c * x_i + r[i] for i, x_i in enumerate(x)] 176 | phi = (c * gamma + rho) % gf.order 177 | return ( 178 | z, 179 | phi, 180 | c, 181 | ) # TODO: check if it's correct to return c as well (Required to reconstruct A in non-interactive proof) 182 | 183 | 184 | def verify_linear_form_proof(g, h, P, L, y, z, phi, c): 185 | n = len(z) 186 | L, y = affine_to_linear(L, y, n) 187 | A_check = vector_commitment(z, phi, g, h) * ((P ** c) ** (-1)) 188 | t_check = L(z) - c * y 189 | logger_piv.debug(f"Verifier computed A_check={A_check}.") 190 | logger_piv.debug(f"Verifier computed t_check={t_check}.") 191 | order = type(t_check).order 192 | 193 | if isinstance(A_check, EllipticCurveElement): 194 | input_list = [t_check, A_check.normalize(), g, h, P.normalize(), L, y] 195 | else: 196 | input_list = [t_check, A_check, g, h, P, L, y] 197 | 198 | logger_piv.debug(f"Method verify_linear_form_proof: input_list={input_list}.") 199 | hash_check = fiat_shamir_hash(input_list, order) 200 | logger_piv.debug(f"Value of c ={c}") 201 | logger_piv.debug(f"Value of hash_check={hash_check}") 202 | if c == hash_check: 203 | return True 204 | else: 205 | return False 206 | -------------------------------------------------------------------------------- /verifiable_mpc/ac20/mpc_ac20_cb.py: -------------------------------------------------------------------------------- 1 | """MPC interface for circuit_sat and compressed_pivot modules. 2 | 3 | Circuit_sat implementation of https://eprint.iacr.org/2020/152 4 | ``Compressed Sigma-Protocol Theory and Practical Application 5 | to Plug & Play Secure Algorithmics'' 6 | 7 | """ 8 | 9 | import mpyc.mpctools as mpctools 10 | import verifiable_mpc.ac20.pivot as pivot 11 | import verifiable_mpc.ac20.circuit_builder as cb 12 | import verifiable_mpc.ac20.circuit_sat_r1cs as cs 13 | from mpyc.runtime import mpc, logging 14 | from verifiable_mpc.ac20.mpc_ac20 import ( 15 | list_mul, 16 | vector_commitment, 17 | create_generators, 18 | koe_trusted_setup, 19 | koe_restriction_argument_prover, 20 | koe_opening_linear_form_prover, 21 | protocol_4_prover, 22 | protocol_5_prover, 23 | calculate_fgh_polys, 24 | _recombination_vectors, 25 | recombine, 26 | prove_linear_form_eval, 27 | ) 28 | 29 | logger_cs_mpc_cb = logging.getLogger("cs_mpc") 30 | logger_cs_mpc_cb.setLevel(logging.INFO) 31 | 32 | logger_cs_mpc_cb_hin = logging.getLogger("cs_mpc_hash_inputs") 33 | logger_cs_mpc_cb_hin.setLevel(logging.INFO) 34 | 35 | logger_cs_mpc_cb_hout = logging.getLogger("cs_mpc_hash_outputs") 36 | logger_cs_mpc_cb_hout.setLevel(logging.INFO) 37 | 38 | 39 | async def protocol_8_excl_pivot_prover(generators, circuit, x, gf, use_koe=False): 40 | """Non-interactive implementation of Protocol 8, 41 | including nullity protocol, but excluding the call 42 | to the compressed pivot protocol. 43 | 44 | """ 45 | secfld = type(x[0]) 46 | if "g" in generators: 47 | g = generators["g"] 48 | h = generators["h"] 49 | elif "pp_lhs" in generators: 50 | use_koe = True 51 | pp = generators 52 | else: 53 | raise NotImplementedError 54 | 55 | logger_cs_mpc_cb.debug(f"Calculate witness.") 56 | n = len(x) 57 | assert n == circuit.input_ct 58 | proof = {} 59 | m = circuit.mul_ct 60 | 61 | # Calculate a, b, c vectors, only for mul gates 62 | logger_cs_mpc_cb.debug(f"Calculate a, b, c vectors.") 63 | a, b, c = circuit.multiplication_triples(x) 64 | 65 | logger_cs_mpc_cb.debug(f"Calculate z.") 66 | f0 = mpc._random(secfld) 67 | g0 = mpc._random(secfld) 68 | a = [f0] + a 69 | b = [g0] + b 70 | logger_cs_mpc_cb.debug(f"Calculate z: a, b = await mpc.gather(a,b)") 71 | a, b = await mpc.gather(a, b) 72 | logger_cs_mpc_cb.debug(f"Calculate z: fs = recombine(...)") 73 | fs = recombine(gf, list(zip(range(m + 1), a)), list(range(m + 1, 2 * m + 1))) 74 | logger_cs_mpc_cb.debug(f"Calculate z: gs = recombine(...)") 75 | gs = recombine(gf, list(zip(range(m + 1), b)), list(range(m + 1, 2 * m + 1))) 76 | logger_cs_mpc_cb.debug(f"Calculate z: hs = list(...)") 77 | hs = list(map(secfld, await mpc.schur_prod(fs, gs))) 78 | z = x + [f0, g0, f0 * g0] + c + hs 79 | 80 | gamma = mpc._random(secfld) 81 | 82 | if use_koe: 83 | S = range(len(z)) 84 | z_commitment_P, z_commitment_pi = await koe_restriction_argument_prover( 85 | S, z, gamma, pp 86 | ) 87 | z_commitment = {"P": z_commitment_P, "pi": z_commitment_pi} 88 | proof["z_commitment"] = z_commitment 89 | else: 90 | logger_cs_mpc_cb.debug("Calculate commitment for z, denoted by [z].") 91 | z_commitment = await vector_commitment(z, gamma, g, h) 92 | # logger_cs_mpc_cb.debug("Calculated [z] in secret shared form.") 93 | logger_cs_mpc_cb.debug(f"Opened [z] (z_commitment).") 94 | proof["z_commitment"] = z_commitment 95 | 96 | logger_cs_mpc_cb.debug("Apply first hash of circuit satisfiability protocol.") 97 | input_list = [z_commitment, str(circuit), "First hash circuit satisfiability protocol"] 98 | logger_cs_mpc_cb_hin.debug( 99 | f"Method protocol_8_excl_pivot_prover (1): input_list={input_list}" 100 | ) 101 | c = pivot.fiat_shamir_hash( 102 | input_list, gf.order 103 | ) # TODO: name clash c = a * b from above 104 | logger_cs_mpc_cb_hout.debug(f"After hash, hash=\n{c}") 105 | 106 | # Prover's response 107 | linform_f = cb.calculate_fg_form(circuit, wire=0, challenge=c, gf=gf) 108 | linform_g = cb.calculate_fg_form(circuit, wire=1, challenge=c, gf=gf) 109 | linform_h = cb.calculate_h_form(circuit, c, gf) 110 | 111 | y1 = linform_f(z) 112 | y2 = linform_g(z) 113 | y3 = linform_h(z) 114 | y1 = await mpc.output(y1, raw = True) # raw = True ensures that we return field elements 115 | y2 = await mpc.output(y2, raw = True) 116 | y3 = await mpc.output(y3, raw = True) 117 | 118 | assert y1 * y2 == y3 119 | proof["y1"] = y1 120 | proof["y2"] = y2 121 | proof["y3"] = y3 122 | 123 | circuit_forms = cb.calculate_circuit_forms(circuit) 124 | circuit_forms = [cb.convert_to_ac20(f, circuit) for f in circuit_forms] 125 | outputs = circuit(x) 126 | outputs = await mpc.output(outputs) 127 | proof["outputs"] = outputs 128 | 129 | lin_forms = [form - y for form, y in zip(circuit_forms, outputs)] + [ 130 | linform_f - y1, 131 | linform_g - y2, 132 | linform_h - y3, 133 | ] 134 | 135 | # Follow prove_nullity_compressed but add more inputs to Fiat-Shamir hash 136 | logger_cs_mpc_cb.debug("Apply second hash of circuit satisfiability protocol.") 137 | input_list = [ 138 | y1, 139 | y2, 140 | y3, 141 | z_commitment, 142 | outputs, 143 | circuit_forms, 144 | lin_forms, 145 | "Second hash circuit satisfiability protocol", 146 | ] 147 | logger_cs_mpc_cb_hin.debug( 148 | f"Method protocol_8_excl_pivot_prover (2): input_list={input_list}" 149 | ) 150 | rho = pivot.fiat_shamir_hash(input_list, gf.order) 151 | logger_cs_mpc_cb_hout.debug(f"After hash, hash=\n{rho}") 152 | L = sum((linform_i) * (rho ** i) for i, linform_i in enumerate(lin_forms)) 153 | proof["L"] = L 154 | return proof, z_commitment, L, z, gamma 155 | 156 | 157 | async def circuit_sat_prover( 158 | generators, circuit, x, gf, pivot_choice=cs.PivotChoice.compressed 159 | ): 160 | """Non-interactive implementation of Protocol 8, prover-side, 161 | including Nullity using compressed pivot (Protocol 5). 162 | """ 163 | logger_cs_mpc_cb.debug(f"Enter circuit_sat_prover. pivot_choice={pivot_choice}") 164 | 165 | logger_cs_mpc_cb.debug(f"Start protocol 8, excluding pivot proof.") 166 | proof, z_commitment, L, z, gamma = await protocol_8_excl_pivot_prover( 167 | generators, circuit, x, gf 168 | ) 169 | 170 | if pivot_choice == cs.PivotChoice.compressed: 171 | pivot_proof = await protocol_5_prover( 172 | generators, z_commitment, L, L(z), z, gamma, gf 173 | ) 174 | elif pivot_choice == cs.PivotChoice.pivot: 175 | g = generators["g"] 176 | h = generators["h"] 177 | pivot_proof = await prove_linear_form_eval( 178 | g, h, z_commitment, L, L(z), z, gamma, gf 179 | ) 180 | elif pivot_choice == cs.PivotChoice.koe: 181 | L = proof["L"] 182 | P = proof["z_commitment"]["P"] 183 | pi = proof["z_commitment"]["pi"] 184 | pivot_proof, u = await koe_opening_linear_form_prover(L, z, gamma, generators, P, pi) 185 | else: 186 | raise NotImplementedError 187 | proof["pivot_proof"] = pivot_proof 188 | 189 | return proof 190 | 191 | 192 | -------------------------------------------------------------------------------- /wip.py: -------------------------------------------------------------------------------- 1 | """WIP: Plot circuit graphs with networkx. *unfinished* 2 | 3 | When ready, move this into /demos/demo_circuit_builder.py 4 | """ 5 | import pprint 6 | from mpyc.finfields import GF 7 | from mpyc.fingroups import QuadraticResidues 8 | import verifiable_mpc.ac20.circuit_sat_cb as cs 9 | import verifiable_mpc.ac20.circuit_builder as cb 10 | import verifiable_mpc.tools.code_to_r1cs as r1cs 11 | 12 | import matplotlib.pyplot as plt 13 | import networkx as nx 14 | 15 | 16 | pp = pprint.PrettyPrinter(indent=4) 17 | 18 | 19 | def save_circuit(circuit): 20 | # Maybe: Use list returned by this method for print_circuit (DRY) 21 | ret = [] 22 | for gate in circuit.out_gates(): 23 | ret += save_out_gate(circuit, gate) 24 | return ret 25 | 26 | 27 | def save_out_gate(circuit, gate, level=0): 28 | ret = [{ 29 | "level": level, 30 | "in": [i.name if isinstance(i, cb.CircuitVar) else i for i in gate.inputs], 31 | "out": gate.output.name if isinstance(gate.output, cb.CircuitVar) else gate.output, 32 | "op": gate.op, 33 | "index": gate.index 34 | }] 35 | for child in circuit.children(gate): 36 | ret += save_out_gate(circuit, child, level + 1) 37 | return ret 38 | 39 | 40 | def main(pivot_choice, n): 41 | print("Pivot selected: ", pivot_choice) 42 | 43 | group = QuadraticResidues(l=1024) 44 | gf = GF(modulus=group.order) 45 | 46 | circuit = cb.Circuit() 47 | # Using field elements 48 | # b = cb.CircuitVar(gf(1), circuit, "b") 49 | # c = cb.CircuitVar(gf(2), circuit, "c") 50 | # Using integers 51 | b = cb.CircuitVar(1, circuit, "b") 52 | c = cb.CircuitVar(2, circuit, "c") 53 | 54 | # Example: Mini circuit 55 | # d = c + c 56 | # e = d * c 57 | # e.label_output("e") 58 | 59 | # Example: Not-equal circuit 60 | g = b != c 61 | g.label_output("g") 62 | 63 | # Example: larger circuit with >n mul-gates 64 | # d = c + c + c * c + c * c * 1 + 1 + b 65 | # e = d*d + c**n + 10 66 | # f = d*c + e 67 | # f.label_output("f") 68 | # g = f != 100 69 | # g.label_output("g") 70 | # h = g >= 10 # Note: comparison only works for integers 71 | # h.label_output("h") 72 | 73 | print(circuit) 74 | 75 | circ_lst = save_circuit(circuit) 76 | print(circ_lst) 77 | 78 | # assumes binary tree (fan-in 2) just as AC20 79 | notflat = [[ 80 | ((i["in"][0], i["level"]), (i["op"], i["index"], i["level"])), 81 | ((i["in"][1], i["level"]), (i["op"], i["index"], i["level"])), 82 | ((i["op"], i["index"], i["level"]), (i["out"], i["level"]-1)), 83 | ] for i in circ_lst] 84 | 85 | edges = [j for sub in notflat for j in sub] 86 | print(edges) 87 | 88 | G = nx.MultiDiGraph() 89 | G.add_edges_from(edges) 90 | print("Edges:") 91 | print([e for e in G.edges]) 92 | print("Nodes:") 93 | print([n for n in G.nodes]) 94 | 95 | print("Is tree/arborescence/forest:") 96 | print(nx.is_tree(G)) 97 | print(nx.is_arborescence(G)) 98 | print(nx.is_forest(G)) 99 | 100 | pos = nx.nx_agraph.graphviz_layout(G, prog="dot", args="") 101 | plt.figure(figsize=(20, 10)) 102 | nx.draw(G, pos, node_size=20, alpha=0.5, node_color="blue", with_labels=True) 103 | plt.axis("equal") 104 | plt.savefig('circuit.png') 105 | 106 | # Convert circuit to flatcode 107 | flatcode = [[gate.op, gate.output, gate.inputs[0], gate.inputs[1]] for gate in circuit.gates] 108 | # Parse into format required by Vitalik's R1CS code 109 | flatcode = [["+" if line[0] == cb.op.add else "*", line[1], line[2], line[3]] for line in flatcode] 110 | # TODO: Make an alternative version of py- and trinocchio (code_to_qap.QAP) that handles flatcode from circuit_builder 111 | # TODO: or make a simple circuit-to-qap function based on pinocchio paper 112 | # TODO: Handle scalar mul efficiently (collapse these gates/lines in flatcode using linear algebra) 113 | # TODO: include "minus" and "division" gates 114 | # TODO: R1CS methods require use of "~out", "set", "/", etc. 115 | # TODO: in circuit_builder, 'set' operation is implicitly used (an input CircuitVar is initialized with a value) 116 | # Does it need to be included in the flatcode? 117 | flatcode = [[line[0], *[i.name if isinstance(i, cb.CircuitVar) else i for i in line[1:]]] for line in flatcode] 118 | print("Flatcode:") 119 | for i in flatcode: 120 | print(i) 121 | 122 | inputs = [v.name for v in circuit.circuitvars if v.input_index != None] 123 | print("Inputs:") 124 | print(inputs) 125 | print("Circuit vars, len():") 126 | print(circuit.circuitvars) 127 | # TODO: get_var_placement plaatst ook "~one" vooraan. 128 | print(len(circuit.circuitvars)) 129 | A, B, C = r1cs.flatcode_to_r1cs(inputs, flatcode, circuit.circuitvars) 130 | print('A') 131 | for x in A: 132 | print(x) 133 | print('B') 134 | for x in B: 135 | print(x) 136 | print('C') 137 | for x in C: 138 | print(x) 139 | 140 | 141 | 142 | if __name__ == "__main__": 143 | import argparse 144 | 145 | parser = argparse.ArgumentParser() 146 | parser.add_argument("-n", type=int, help="roughly number of multiplications") 147 | parser.set_defaults(n=3) 148 | args = parser.parse_args() 149 | 150 | verification = main(cs.PivotChoice.compressed, args.n) 151 | 152 | 153 | 154 | ## TODOS 155 | # When ready, move this into /demos/demo_circuit_builder.py 156 | 157 | 158 | 159 | # Graveyard 160 | # nx.draw(G, with_labels=True) 161 | # plt.savefig('hierarchy.png') 162 | 163 | # # assumes binary tree (fan-in 2) just as AC20 164 | 165 | # # notflat = [[(t["in"][0], t["out"]), (t["in"][1], t["out"])] for i, t in enumerate(circ_lst)] 166 | # # notflat = [[(i["in"][0], i["out"]), (i["in"][1], i["out"])] for i in circ_lst] 167 | # # edges = [j for sub in [[(i["in"][0], i["out"]), (i["in"][1], i["out"])] for i in circ_lst] for j in sub] 168 | 169 | # # TODO: make gates part of the graph 170 | # notflat = [[ 171 | # ((i["in"][0],0,i["level"]), (i["out"],0,i["level"]-1)), 172 | # ((i["in"][1],1,i["level"]), (i["out"],0,i["level"]-1)), 173 | # ] for i in circ_lst] 174 | # edges = [j for sub in notflat for j in sub] 175 | # print(edges) 176 | 177 | # G = nx.MultiDiGraph() 178 | # G.add_edges_from(edges) 179 | # print("Edges:") 180 | # print([e for e in G.edges]) 181 | # print("Nodes:") 182 | # print([n for n in G.nodes]) 183 | 184 | # print("Is tree/arborescence/forest:") 185 | # print(nx.is_tree(G)) 186 | # print(nx.is_arborescence(G)) 187 | # print(nx.is_forest(G)) 188 | 189 | # nx.draw(G, with_labels=True) 190 | # plt.savefig('hierarchy.png') 191 | 192 | # pos = nx.nx_agraph.graphviz_layout(G, prog="dot", args="") 193 | # plt.figure(figsize=(100, 100)) 194 | # nx.draw(G, pos, node_size=20, alpha=0.5, node_color="blue", with_labels=True) 195 | # plt.axis("equal") 196 | # plt.savefig('hierarchy2.png') 197 | 198 | 199 | 200 | 201 | # # Show forms corresponding to circuit 202 | # circuit_forms = cb.calculate_circuit_forms(circuit) 203 | # # # AC20 form requires the inclusion of variables corresponding to f(0), g(0), h(0) and h(i), i = m+1, ..., 2m 204 | # # circuit_forms = [cb.convert_to_ac20(f, circuit) for f in circuit_forms] 205 | # print("Circuit forms:") 206 | # print(circuit_forms) 207 | # print("Circuit vars:") 208 | # print(circuit.circuitvars) 209 | # print("Circuit #input vars and #mul vars:") 210 | # print(circuit.input_ct) 211 | # print(circuit.mul_ct) 212 | -------------------------------------------------------------------------------- /verifiable_mpc/tools/qap_creator.py: -------------------------------------------------------------------------------- 1 | """ Adaptation from work by Vitalik Buterin: 2 | https://github.com/ethereum/research/tree/master/zksnark 3 | 4 | Original code under MIT license: 5 | https://github.com/ethereum/research/blob/master/LICENSE 6 | 7 | Adaptations: 8 | * Updated code from Python 2 to Python 3. 9 | * Updated methods mk_singleton, lagrange_interp and r1cs_to_qap to handle finite field elements. 10 | * Added Poly class 11 | 12 | """ 13 | 14 | # Polynomials are stored as arrays, where the ith element in 15 | # the array is the ith degree coefficient 16 | 17 | 18 | class Poly(): 19 | def __init__(self, coeffs): 20 | self.coeffs = coeffs 21 | 22 | def __add__(self, other): 23 | return type(self)(add_polys(self.coeffs, other.coeffs)) 24 | 25 | def __sub__(self, other): 26 | return type(self)(subtract_polys(self.coeffs, other.coeffs)) 27 | 28 | def __mul__(self, other): 29 | if type(other) == Poly: 30 | return type(self)(multiply_polys(self.coeffs, other.coeffs)) 31 | else: # int or finite field element 32 | return type(self)(scale(other, self.coeffs)) 33 | 34 | # def __rmul__(self, other): 35 | # return self * other 36 | 37 | def __truediv__(self, other): 38 | d, r = div_polys(self.coeffs, other.coeffs) 39 | return type(self)(d), type(self)(r) 40 | 41 | def __len__(self): 42 | return len(self.coeffs) 43 | 44 | def __eq__(self, other): 45 | return self.coeffs == other.coeffs 46 | 47 | def eval(self, value): 48 | return eval_poly(self.coeffs, value) 49 | 50 | def __call__(self, value): 51 | return self.eval(value) 52 | 53 | def __str__(self): 54 | return str(self.coeffs) 55 | 56 | 57 | def scale(c, poly): 58 | return [c*poly[j] for j in range(len(poly))] 59 | 60 | 61 | # Multiply two polynomials 62 | def multiply_polys(a, b): 63 | o = [0] * (len(a) + len(b) - 1) 64 | for i in range(len(a)): 65 | for j in range(len(b)): 66 | o[i + j] += a[i] * b[j] 67 | return o 68 | 69 | 70 | # Add two polynomials 71 | def add_polys(a, b, subtract=False): 72 | input_was_Poly = False 73 | if type(a) == Poly: 74 | input_was_Poly = True 75 | a = a.coeffs 76 | b = b.coeffs 77 | # if type(a) == list or a == None or b == None: 78 | o = [0] * max(len(a), len(b)) 79 | for i in range(len(a)): 80 | o[i] += a[i] 81 | for i in range(len(b)): 82 | o[i] += b[i] * (-1 if subtract else 1) # Reuse the function structure for subtraction 83 | # return o 84 | # else: 85 | # raise NotImplementedError 86 | if input_was_Poly: 87 | return Poly(o) 88 | else: 89 | return o 90 | 91 | 92 | def subtract_polys(a, b): 93 | return add_polys(a, b, subtract=True) 94 | 95 | # Divide a/b, return quotient and remainder 96 | def div_polys(a, b): 97 | o = [0] * (len(a) - len(b) + 1) 98 | remainder = a 99 | while len(remainder) >= len(b): 100 | leading_fac = remainder[-1] / b[-1] 101 | pos = len(remainder) - len(b) 102 | o[pos] = leading_fac 103 | remainder = subtract_polys(remainder, multiply_polys(b, [0] * pos + [leading_fac]))[:-1] 104 | return o, remainder 105 | 106 | 107 | # Evaluate a polynomial at a point 108 | def eval_poly(poly, x): 109 | return sum([poly[i] * x**i for i in range(len(poly))]) 110 | 111 | 112 | # Make a polynomial which is zero at {1, 2 ... total_pts}, except 113 | # for `point_loc` where the value is `height` 114 | def mk_singleton(point_loc, height, total_pts): 115 | fac = 1 116 | for i in range(1, total_pts + 1): 117 | if i != point_loc: 118 | fac *= point_loc - i 119 | o = [height * 1.0 / fac] 120 | for i in range(1, total_pts + 1): 121 | if i != point_loc: 122 | o = multiply_polys(o, [-i, 1]) 123 | return o 124 | 125 | 126 | # Assumes vec[0] = p(1), vec[1] = p(2), etc, tries to find p, 127 | # expresses result as [deg 0 coeff, deg 1 coeff...] 128 | def lagrange_interp(vec): 129 | o = [] 130 | for i in range(len(vec)): 131 | o = add_polys(o, mk_singleton(i + 1, vec[i], len(vec))) 132 | for i in range(len(vec)): 133 | assert abs(eval_poly(o, i + 1) - vec[i] < 10**-10), \ 134 | (o, eval_poly(o, i + 1), i+1) 135 | return o 136 | 137 | 138 | # TS: version of mk_singleton that is able to handle finite field elements 139 | def mk_singleton_ff(point_loc, height, total_pts): 140 | fac = type(point_loc)(1) 141 | for i in range(1, total_pts + 1): 142 | if i != point_loc: 143 | fac *= point_loc - i 144 | o = [fac**-1] 145 | for i in range(1, total_pts + 1): 146 | if i != point_loc: 147 | o = multiply_polys(o, [-i, 1]) 148 | o = multiply_polys(o, [height]) 149 | return o 150 | 151 | 152 | # TS: version of lagrange_interp that is able to handle finite field elements 153 | # input ff is a finite field 154 | def lagrange_interp_ff(vec, ff): 155 | o = [] 156 | for i in range(len(vec)): 157 | o = add_polys(o, mk_singleton_ff(ff(i) + 1, vec[i], len(vec))) 158 | for i in range(len(vec)): 159 | # assert abs(eval_poly(o, i + 1) - vec[i] < 10**-10), (o, eval_poly(o, i + 1), i+1) 160 | """ TS: Commented out line below (not above) and added `pass` to allow for sectypes. 161 | """ 162 | # assert abs(eval_poly(o, i + 1) - vec[i] == ff(0)), (o, eval_poly(o, i + 1), i+1) 163 | pass 164 | return o 165 | 166 | 167 | # TS: version of r1cs_to_qap that is able to handle finite field elements 168 | def r1cs_to_qap_ff(A, B, C, ff): 169 | A, B, C = transpose(A), transpose(B), transpose(C) 170 | new_A = [lagrange_interp_ff(a, ff) for a in A] 171 | new_B = [lagrange_interp_ff(b, ff) for b in B] 172 | new_C = [lagrange_interp_ff(c, ff) for c in C] 173 | Z = [ff(1)] 174 | for i in range(1, len(A[0]) + 1): 175 | Z = multiply_polys(Z, [ff(-i), ff(1)]) 176 | return (new_A, new_B, new_C, Z) 177 | 178 | 179 | def transpose(matrix): 180 | return list(map(list, list(zip(*matrix)))) 181 | 182 | 183 | # A, B, C = matrices of m vectors of length n, where for each 184 | # 0 <= i < m, we want to satisfy A[i] * B[i] - C[i] = 0 185 | def r1cs_to_qap(A, B, C): 186 | A, B, C = transpose(A), transpose(B), transpose(C) 187 | new_A = [lagrange_interp(a) for a in A] 188 | new_B = [lagrange_interp(b) for b in B] 189 | new_C = [lagrange_interp(c) for c in C] 190 | Z = [1] 191 | for i in range(1, len(A[0]) + 1): 192 | Z = multiply_polys(Z, [-i, 1]) 193 | return (new_A, new_B, new_C, Z) 194 | 195 | 196 | def create_solution_polynomials(r, new_A, new_B, new_C): 197 | Apoly = [] 198 | for rval, a in zip(r, new_A): 199 | Apoly = add_polys(Apoly, multiply_polys([rval], a)) 200 | Bpoly = [] 201 | for rval, b in zip(r, new_B): 202 | Bpoly = add_polys(Bpoly, multiply_polys([rval], b)) 203 | Cpoly = [] 204 | for rval, c in zip(r, new_C): 205 | Cpoly = add_polys(Cpoly, multiply_polys([rval], c)) 206 | o = subtract_polys(multiply_polys(Apoly, Bpoly), Cpoly) 207 | for i in range(1, len(new_A[0]) + 1): 208 | assert abs(eval_poly(o, i)) < 10**-10, (eval_poly(o, i), i) 209 | return Apoly, Bpoly, Cpoly, o 210 | 211 | 212 | def create_divisor_polynomial(sol, Z): 213 | quot, rem = div_polys(sol, Z) 214 | for x in rem: 215 | assert abs(x) < 10**-10 216 | return quot 217 | 218 | 219 | if __name__ == '__main__': 220 | r = [1, 3, 35, 9, 27, 30] 221 | A = [[0, 1, 0, 0, 0, 0], 222 | [0, 0, 0, 1, 0, 0], 223 | [0, 1, 0, 0, 1, 0], 224 | [5, 0, 0, 0, 0, 1]] 225 | B = [[0, 1, 0, 0, 0, 0], 226 | [0, 1, 0, 0, 0, 0], 227 | [1, 0, 0, 0, 0, 0], 228 | [1, 0, 0, 0, 0, 0]] 229 | C = [[0, 0, 0, 1, 0, 0], 230 | [0, 0, 0, 0, 1, 0], 231 | [0, 0, 0, 0, 0, 1], 232 | [0, 0, 1, 0, 0, 0]] 233 | 234 | Ap, Bp, Cp, Z = r1cs_to_qap(A, B, C) 235 | print('Ap') 236 | for x in Ap: print(x) 237 | print('Bp') 238 | for x in Bp: print(x) 239 | print('Cp') 240 | for x in Cp: print(x) 241 | print('Z') 242 | print(Z) 243 | Apoly, Bpoly, Cpoly, sol = create_solution_polynomials(r, Ap, Bp, Cp) 244 | print('Apoly') 245 | print(Apoly) 246 | print('Bpoly') 247 | print(Bpoly) 248 | print('Cpoly') 249 | print(Cpoly) 250 | print('Sol') 251 | print(sol) 252 | print('Z cofactor') 253 | print(create_divisor_polynomial(sol, Z)) 254 | -------------------------------------------------------------------------------- /verifiable_mpc/ac20/compressed_pivot.py: -------------------------------------------------------------------------------- 1 | """ Implementation of https://eprint.iacr.org/2020/152 2 | ``Compressed Σ-Protocol Theory and Practical Application 3 | to Plug & Play Secure Algorithmics'' 4 | 5 | Protocols: 6 | * Compressed Σ-protocol Π_c for relation R, page 15, protocol 5 7 | """ 8 | 9 | import os 10 | import sys 11 | import logging 12 | from random import SystemRandom 13 | import verifiable_mpc.ac20.pivot as pivot 14 | from mpyc.fingroups import EllipticCurvePoint as EllipticCurveElement 15 | 16 | 17 | prng = SystemRandom() 18 | 19 | logger_cp = logging.getLogger("compressed_pivot") 20 | logger_cp.setLevel(logging.INFO) 21 | 22 | logger_cp_hin = logging.getLogger("compressed_pivot_hash_inputs") 23 | logger_cp_hin.setLevel(logging.INFO) 24 | 25 | logger_cp_hout = logging.getLogger("compressed_pivot_hash_outputs") 26 | logger_cp_hout.setLevel(logging.INFO) 27 | 28 | 29 | def protocol_4_prover(g_hat, k, Q, L_tilde, z_hat, gf, proof={}, round_i=0): 30 | """ Non-interactive version of Protocol 4 from Section 4.2: Prover's side 31 | 32 | """ 33 | 34 | # Step 5: Prover calculates A, B 35 | half = len(g_hat) // 2 36 | g_hat_l = g_hat[:half] 37 | g_hat_r = g_hat[half:] 38 | z_hat_l = z_hat[:half] 39 | z_hat_r = z_hat[half:] 40 | logger_cp.debug("Calculate A_i, B_i.") 41 | A = pivot.vector_commitment(z_hat_l, int(L_tilde([0] * half + z_hat_l)), g_hat_r, k) 42 | B = pivot.vector_commitment(z_hat_r, int(L_tilde(z_hat_r + [0] * half)), g_hat_l, k) 43 | 44 | proof["A" + str(round_i)] = A 45 | proof["B" + str(round_i)] = B 46 | 47 | # Step 6: Prover calculates challenge 48 | order = k.order 49 | # Hash A, B and all public elements of Protocol 4 50 | # input_list = [A, B, g_hat, k, Q, L_tilde] 51 | if isinstance(A, EllipticCurveElement): 52 | input_list = [A.normalize(), B.normalize(), g_hat, k, Q.normalize(), L_tilde] 53 | else: 54 | input_list = [A, B, g_hat, k, Q, L_tilde] 55 | 56 | logger_cp_hin.debug( 57 | f"Method protocol_4_prover: Before fiat_shamir_hash, input_list=\n{input_list}" 58 | ) 59 | c = pivot.fiat_shamir_hash(input_list, order) 60 | logger_cp_hout.debug(f"After hash, hash=\n{c}") 61 | 62 | # Step 7: Prover calculates following public values 63 | logger_cp.debug("Calculate g_prime.") 64 | g_prime = [(g_hat_l[i] ** c) * g_hat_r[i] for i in range(half)] 65 | logger_cp.debug("Calculate Q_prime.") 66 | Q_prime = A * (Q ** c) * (B ** (c ** 2)) 67 | assert ( 68 | L_tilde.constant == 0 69 | ), "Next line assumes L_tilde is a linear form, not affine form." 70 | c_L_tilde_l_coeffs = [coeff * gf(c) for coeff in L_tilde.coeffs[:half]] 71 | L_prime = pivot.LinearForm(c_L_tilde_l_coeffs) + pivot.LinearForm( 72 | L_tilde.coeffs[half:] 73 | ) 74 | 75 | # Step 8: Prover calculates z_prime and tests if recursion is required (if z' in Z or Z^2) 76 | z_prime = [z_hat_l[i] + c * z_hat_r[i] for i in range(half)] 77 | if len(z_prime) <= 2: 78 | proof["z_prime"] = z_prime 79 | return proof 80 | else: 81 | # Step 9: Prover sends z_prime and starts recursion of protocol 4 82 | round_i += 1 83 | proof = protocol_4_prover( 84 | g_prime, k, Q_prime, L_prime, z_prime, gf, proof, round_i 85 | ) 86 | return proof 87 | 88 | 89 | def protocol_5_prover(generators, P, L, y, x, gamma, gf): 90 | g = generators["g"] 91 | h = generators["h"] 92 | k = generators["k"] 93 | 94 | proof = {} 95 | # Public parameters 96 | n = len(x) 97 | L, y = pivot.affine_to_linear(L, y, n) 98 | assert ( 99 | bin(n + 1).count("1") == 1 100 | ), "This implementation requires n+1 to be power of 2 (else, use padding with zeros)." 101 | 102 | # Non-interactive proof 103 | # Step 1: Prover calculates ("announcement") t, A 104 | order = gf.order 105 | r = list(prng.randrange(order) for i in range(n)) 106 | rho = prng.randrange(order) 107 | logger_cp.debug("Calculate t.") 108 | t = L(r) 109 | logger_cp.debug("Calculate A.") 110 | A = pivot.vector_commitment(r, rho, g, h) 111 | 112 | proof["t"] = t 113 | proof["A"] = A 114 | 115 | # Step 2: Prover computes challenge 116 | # input_list = [t, A, generators, P, L, y] 117 | if isinstance(A, EllipticCurveElement): 118 | input_list = [t, A.normalize(), generators, P.normalize(), L, y] 119 | else: 120 | input_list = [t, A, generators, P, L, y] 121 | 122 | logger_cp_hin.debug( 123 | f"Method protocol_5_prover: Before fiat_shamir_hash, input_list=\n{input_list}" 124 | ) 125 | c0 = pivot.fiat_shamir_hash( 126 | input_list + [0] + ["First hash of compressed pivot"], order 127 | ) 128 | c1 = pivot.fiat_shamir_hash( 129 | input_list + [1] + ["First hash of compressed pivot"], order 130 | ) 131 | logger_cp_hout.debug(f"After hash, hash=\n{c0}, {c1}") 132 | 133 | # Step 3: Prover calculates 134 | z = [c0 * x_i + r[i] for i, x_i in enumerate(x)] 135 | phi = gf(c0 * gamma + rho) 136 | z_hat = z + [phi] 137 | # Step 4: Prover calculates following public variables 138 | g_hat = g + [h] 139 | logger_cp.debug("Calculate Q.") 140 | Q = A * (P ** c0) * (k ** int(c1 * (c0 * y + t))) 141 | L_tilde = pivot.LinearForm(L.coeffs + [0]) * c1 142 | assert L(z) * c1 == L_tilde(z_hat) 143 | 144 | proof = protocol_4_prover(g_hat, k, Q, L_tilde, z_hat, gf, proof) 145 | return proof 146 | 147 | 148 | def protocol_4_verifier(g_hat, k, Q, L_tilde, gf, proof, round_i=0): 149 | """ Non-interactive version of Protocol 4 from Section 4.2: Verifier's side 150 | 151 | """ 152 | 153 | # Step 5 154 | half = len(g_hat) // 2 155 | g_hat_l = g_hat[:half] 156 | g_hat_r = g_hat[half:] 157 | 158 | logger_cp.debug("Load from proof: A_i, B_i.") 159 | A = proof["A" + str(round_i)] 160 | B = proof["B" + str(round_i)] 161 | 162 | # Step 6 163 | order = k.order 164 | # Hash A, B and all public elements of Protocol 4 165 | # input_list = [A, B, g_hat, k, Q, L_tilde] 166 | if isinstance(A, EllipticCurveElement): 167 | input_list = [A.normalize(), B.normalize(), g_hat, k, Q.normalize(), L_tilde] 168 | else: 169 | input_list = [A, B, g_hat, k, Q, L_tilde] 170 | logger_cp_hin.debug( 171 | f"Method protocol_4_verifier: Before fiat_shamir_hash, input_list=\n{input_list}" 172 | ) 173 | c = pivot.fiat_shamir_hash(input_list, order) 174 | logger_cp_hout.debug(f"After hash, hash=\n{c}") 175 | 176 | # Step 7 177 | logger_cp.debug("Calculate g_prime.") 178 | g_prime = [(g_hat_l[i] ** c) * g_hat_r[i] for i in range(half)] 179 | logger_cp.debug("Calculate Q_prime.") 180 | Q_prime = A * (Q ** c) * (B ** (c ** 2)) 181 | 182 | assert ( 183 | L_tilde.constant == 0 184 | ), "Next line assumes L_tilde is a linear form, not affine form." 185 | c_L_tilde_l_coeffs = [coeff * gf(c) for coeff in L_tilde.coeffs[:half]] 186 | L_prime = pivot.LinearForm(c_L_tilde_l_coeffs) + pivot.LinearForm( 187 | L_tilde.coeffs[half:] 188 | ) 189 | 190 | # Step 8 191 | if len(g_prime) <= 2: 192 | z_prime = proof["z_prime"] 193 | Q_check = pivot.vector_commitment(z_prime, int(L_prime(z_prime)), g_prime, k) 194 | logger_cp.debug("Arrived in final step of protocol_4_verifier.") 195 | logger_cp.debug(f"Q_check= {Q_check}") 196 | logger_cp.debug(f"Q_prime= {Q_prime}") 197 | verification = Q_check == Q_prime 198 | return verification 199 | else: 200 | # Step 9 201 | round_i += 1 202 | return protocol_4_verifier(g_prime, k, Q_prime, L_prime, gf, proof, round_i) 203 | 204 | 205 | def protocol_5_verifier(generators, P, L, y, proof, gf): 206 | g = generators["g"] 207 | h = generators["h"] 208 | k = generators["k"] 209 | 210 | order = gf.order 211 | n = len(g) 212 | L, y = pivot.affine_to_linear(L, y, n) 213 | logger_cp.debug("Load from proof: t, A.") 214 | t = proof["t"] 215 | A = proof["A"] 216 | # input_list = [t, A, generators, P, L, y] 217 | if isinstance(A, EllipticCurveElement): 218 | input_list = [t, A.normalize(), generators, P.normalize(), L, y] 219 | else: 220 | input_list = [t, A, generators, P, L, y] 221 | 222 | logger_cp_hin.debug( 223 | f"Method protocol_5_verifier: Before fiat_shamir_hash, input_list=\n{input_list}" 224 | ) 225 | c0 = pivot.fiat_shamir_hash( 226 | input_list + [0] + ["First hash of compressed pivot"], order 227 | ) 228 | c1 = pivot.fiat_shamir_hash( 229 | input_list + [1] + ["First hash of compressed pivot"], order 230 | ) 231 | logger_cp_hout.debug(f"After hash, hash=\n{c0}, {c1}") 232 | 233 | g_hat = g + [h] 234 | logger_cp.debug("Calculate Q.") 235 | Q = A * (P ** c0) * (k ** int(c1 * (c0 * y + t))) 236 | L_tilde = pivot.LinearForm(L.coeffs + [0]) * c1 237 | 238 | verification = protocol_4_verifier(g_hat, k, Q, L_tilde, gf, proof) 239 | return verification 240 | 241 | 242 | # TODO-list 243 | # TODO1: Input y can be removed from inputs to protocol5 method. 244 | -------------------------------------------------------------------------------- /verifiable_mpc/tools/code_to_r1cs.py: -------------------------------------------------------------------------------- 1 | """ Adaptation from work by Vitalik Buterin: 2 | https://github.com/ethereum/research/tree/master/zksnark 3 | 4 | MIT license: 5 | https://github.com/ethereum/research/blob/master/LICENSE 6 | 7 | Adaptations: 8 | * Updated code from Python 2 to Python 3. 9 | * flatten_stmt() method updated to handle multiple assignments 10 | 11 | """ 12 | 13 | import ast 14 | import pprint 15 | 16 | pp = pprint.PrettyPrinter(indent=4) 17 | 18 | if 'arg' not in dir(ast): 19 | ast.arg = type(None) 20 | 21 | 22 | def parse(code): 23 | return ast.parse(code).body 24 | 25 | # Takes code of the form 26 | # def foo(arg1, arg2 ...): 27 | # x = arg1 + arg2 28 | # y = ... 29 | # return x + y 30 | # And extracts the inputs and the body, where 31 | # it expects the body to be a sequence of 32 | # variable assignments (variables are immutable; 33 | # can only be set once) and a return statement at the end 34 | 35 | 36 | def extract_inputs_and_body(code): 37 | o = [] 38 | if len(code) != 1 or not isinstance(code[0], ast.FunctionDef): 39 | raise Exception("Expecting function declaration") 40 | # Gather the list of input variables 41 | inputs = [] 42 | for arg in code[0].args.args: 43 | if isinstance(arg, ast.arg): 44 | assert isinstance(arg.arg, str) 45 | inputs.append(arg.arg) 46 | elif isinstance(arg, ast.Name): 47 | inputs.append(arg.id) 48 | else: 49 | raise Exception("Invalid arg: %r" % ast.dump(arg)) 50 | # Gather the body 51 | body = [] 52 | returned = False 53 | for c in code[0].body: 54 | if not isinstance(c, (ast.Assign, ast.Return)): 55 | raise Exception("Expected variable assignment or return") 56 | if returned: 57 | raise Exception("Cannot do stuff after a return statement") 58 | if isinstance(c, ast.Return): 59 | returned = True 60 | body.append(c) 61 | return inputs, body 62 | 63 | # Convert a body with potentially complex expressions into 64 | # simple expressions of the form x = y or x = y * z 65 | 66 | 67 | def flatten_body(body): 68 | o = [] 69 | for c in body: 70 | o.extend(flatten_stmt(c)) 71 | return o 72 | 73 | # Generate a dummy variable 74 | next_symbol = [0] 75 | 76 | 77 | def mksymbol(): 78 | next_symbol[0] += 1 79 | return 'sym_' + str(next_symbol[0]) 80 | 81 | # "Flatten" a single statement into a list of simple statements. 82 | # First extract the target variable, then flatten the expression 83 | 84 | 85 | def flatten_stmt(stmt): 86 | """ Get target variable(s) 87 | TS: Updated to handle case of multiple variable assignments 88 | on one line of Python code. 89 | 90 | """ 91 | 92 | if isinstance(stmt, ast.Assign): 93 | if len(stmt.targets) == 1 and isinstance(stmt.targets[0], ast.Name): 94 | targets = [(stmt.targets[0].id, stmt.value)] 95 | elif isinstance(stmt.targets[0], ast.Tuple): 96 | targets = [(t.id, stmt.value.elts[i]) for i, t in enumerate(stmt.targets[0].elts)] 97 | else: 98 | raise NotImplementedError 99 | 100 | elif isinstance(stmt, ast.Return): 101 | if isinstance(stmt.value, (ast.Name, ast.BinOp)): 102 | targets = [('~out', stmt.value)] 103 | elif isinstance(stmt.value, ast.Tuple): 104 | targets = [(f'~out_{i}', stmt.value.elts[i]) for i, t in enumerate(stmt.value.elts)] 105 | else: 106 | raise NotImplementedError 107 | 108 | # Get inner content 109 | flattened = [] 110 | for t in targets: 111 | flattened.extend(flatten_expr(*t)) 112 | return flattened 113 | 114 | # Main method for flattening an expression 115 | 116 | 117 | def flatten_expr(target, expr): 118 | # x = y 119 | if isinstance(expr, ast.Name): 120 | return [['set', target, expr.id]] 121 | # x = 5 122 | elif isinstance(expr, ast.Num): 123 | return [['set', target, expr.n]] 124 | # x = y (op) z 125 | # Or, for that matter, x = y (op) 5 126 | elif isinstance(expr, ast.BinOp): 127 | if isinstance(expr.op, ast.Add): 128 | op = '+' 129 | elif isinstance(expr.op, ast.Mult): 130 | op = '*' 131 | elif isinstance(expr.op, ast.Sub): 132 | op = '-' 133 | elif isinstance(expr.op, ast.Div): 134 | op = '/' 135 | # Exponentiation gets compiled to repeat multiplication, 136 | # requires constant exponent 137 | elif isinstance(expr.op, ast.Pow): 138 | assert isinstance(expr.right, ast.Num) 139 | if expr.right.n == 0: 140 | return [['set', target, 1]] 141 | elif expr.right.n == 1: 142 | return flatten_expr(target, expr.left) 143 | else: # This could be made more efficient via square-and-multiply but oh well 144 | if isinstance(expr.left, (ast.Name, ast.Num)): 145 | nxt = base = expr.left.id if isinstance( 146 | expr.left, ast.Name) else expr.left.n 147 | o = [] 148 | else: 149 | nxt = base = mksymbol() 150 | o = flatten_expr(base, expr.left) 151 | for i in range(1, expr.right.n): 152 | latest = nxt 153 | nxt = target if i == expr.right.n - 1 else mksymbol() 154 | o.append(['*', nxt, latest, base]) 155 | return o 156 | else: 157 | raise Exception("Bad operation: " % ast.dump(stmt.op)) 158 | # If the subexpression is a variable or a number, then include it 159 | # directly 160 | if isinstance(expr.left, (ast.Name, ast.Num)): 161 | var1 = expr.left.id if isinstance( 162 | expr.left, ast.Name) else expr.left.n 163 | sub1 = [] 164 | # If one of the subexpressions is itself a compound expression, recursively 165 | # apply this method to it using an intermediate variable 166 | else: 167 | var1 = mksymbol() 168 | sub1 = flatten_expr(var1, expr.left) 169 | # Same for right subexpression as for left subexpression 170 | if isinstance(expr.right, (ast.Name, ast.Num)): 171 | var2 = expr.right.id if isinstance( 172 | expr.right, ast.Name) else expr.right.n 173 | sub2 = [] 174 | else: 175 | var2 = mksymbol() 176 | sub2 = flatten_expr(var2, expr.right) 177 | # Last expression represents the assignment; sub1 and sub2 represent the 178 | # processing for the subexpression if any 179 | return sub1 + sub2 + [[op, target, var1, var2]] 180 | else: 181 | raise Exception("Unexpected statement value: %r" % stmt.value) 182 | 183 | # Adds a variable or number into one of the vectors; if it's a variable 184 | # then the slot associated with that variable is set to 1, and if it's 185 | # a number then the slot associated with 1 gets set to that number 186 | 187 | 188 | def insert_var(arr, varz, var, used, reverse=False): 189 | if isinstance(var, str): 190 | if var not in used: 191 | raise Exception("Using a variable before it is set!") 192 | arr[varz.index(var)] += (-1 if reverse else 1) 193 | elif isinstance(var, int): 194 | arr[0] += var * (-1 if reverse else 1) 195 | 196 | # Maps input, output and intermediate variables to indices 197 | 198 | 199 | def get_var_placement(inputs, flatcode): 200 | # return ['~one'] + [x for x in inputs] + ['~out'] + [c[1] for c in flatcode if c[1] not in inputs and c[1] != '~out'] 201 | inputs = [x for x in inputs] 202 | outputs = [c[1] for c in flatcode if c[1] not in inputs and c[1].startswith('~out')] 203 | interims = [c[1] for c in flatcode if c[1] not in inputs and not c[1].startswith('~out')] 204 | return ['~one'] + inputs + outputs + interims 205 | 206 | 207 | # Convert the flattened code generated above into a rank-1 constraint system 208 | def flatcode_to_r1cs(inputs, flatcode, var_placement = None): 209 | varz = get_var_placement(inputs, flatcode) 210 | A, B, C = [], [], [] 211 | used = {i: True for i in inputs} 212 | for x in flatcode: 213 | a, b, c = [0] * len(varz), [0] * len(varz), [0] * len(varz) 214 | if x[1] in used: 215 | raise Exception("Variable already used: %r" % x[1]) 216 | used[x[1]] = True 217 | if x[0] == 'set': 218 | a[varz.index(x[1])] += 1 219 | insert_var(a, varz, x[2], used, reverse=True) 220 | b[0] = 1 221 | elif x[0] == '+' or x[0] == '-': 222 | c[varz.index(x[1])] = 1 223 | insert_var(a, varz, x[2], used) 224 | insert_var(a, varz, x[3], used, reverse=(x[0] == '-')) 225 | b[0] = 1 226 | elif x[0] == '*': 227 | c[varz.index(x[1])] = 1 228 | insert_var(a, varz, x[2], used) 229 | insert_var(b, varz, x[3], used) 230 | elif x[0] == '/': 231 | insert_var(c, varz, x[2], used) 232 | a[varz.index(x[1])] = 1 233 | insert_var(b, varz, x[3], used) 234 | A.append(a) 235 | B.append(b) 236 | C.append(c) 237 | return A, B, C 238 | 239 | # Get a variable or number given an existing input vector 240 | 241 | 242 | def grab_var(varz, assignment, var): 243 | if isinstance(var, str): 244 | return assignment[varz.index(var)] 245 | elif isinstance(var, int): 246 | return var 247 | else: 248 | raise Exception("What kind of expression is this? %r" % var) 249 | 250 | # Goes through flattened code and completes the input vector 251 | 252 | 253 | def assign_variables(inputs, input_vars, flatcode): 254 | varz = get_var_placement(inputs, flatcode) 255 | assignment = [0] * len(varz) 256 | assignment[0] = 1 257 | for i, inp in enumerate(input_vars): 258 | assignment[i + 1] = inp 259 | for x in flatcode: 260 | if x[0] == 'set': 261 | assignment[varz.index(x[1])] = grab_var(varz, assignment, x[2]) 262 | elif x[0] == '+': 263 | assignment[varz.index(x[1])] = \ 264 | grab_var(varz, assignment, x[2]) + grab_var(varz, assignment, x[3]) 265 | elif x[0] == '-': 266 | assignment[varz.index(x[1])] = \ 267 | grab_var(varz, assignment, x[2]) - grab_var(varz, assignment, x[3]) 268 | elif x[0] == '*': 269 | assignment[varz.index(x[1])] = \ 270 | grab_var(varz, assignment, x[2]) * grab_var(varz, assignment, x[3]) 271 | elif x[0] == '/': 272 | assignment[varz.index(x[1])] = \ 273 | grab_var(varz, assignment, x[2]) / grab_var(varz, assignment, x[3]) 274 | return assignment 275 | 276 | 277 | def code_to_r1cs_with_inputs(code, input_vars): 278 | inputs, body = extract_inputs_and_body(parse(code)) 279 | print('Inputs') 280 | print(inputs) 281 | print('Body') 282 | print(body) 283 | flatcode = flatten_body(body) 284 | print('Flatcode') 285 | pp.pprint(flatcode) 286 | print('Input var assignment') 287 | print(get_var_placement(inputs, flatcode)) 288 | A, B, C = flatcode_to_r1cs(inputs, flatcode) 289 | r = assign_variables(inputs, input_vars, flatcode) 290 | return r, A, B, C 291 | 292 | 293 | if __name__ == '__main__': 294 | r, A, B, C = code_to_r1cs_with_inputs(""" 295 | def qeval(x): 296 | y = x**3 + 6*x 297 | return y + x + 5 298 | """, [3]) 299 | print('r') 300 | print(r) 301 | print('A') 302 | for x in A: 303 | print(x) 304 | print('B') 305 | for x in B: 306 | print(x) 307 | print('C') 308 | for x in C: 309 | print(x) 310 | -------------------------------------------------------------------------------- /verifiable_mpc/ac20/circuit_sat_cb.py: -------------------------------------------------------------------------------- 1 | """Implements https://eprint.iacr.org/2020/152. 2 | 3 | This module implements the circuit satisfiability protocol 4 | (Protocol 8) from ``Compressed Sigma-Protocol Theory and 5 | Practical Application to Plug & Play Secure Algorithmics''. 6 | 7 | Example: 8 | >>> proof = circuit_sat_prover(generators, circuit, x, ...) 9 | >>> tests = circuit_sat_verifier(proof, ...) 10 | 11 | """ 12 | 13 | import os 14 | import sys 15 | # from enum import Enum 16 | import logging 17 | from random import SystemRandom 18 | # import re 19 | 20 | import verifiable_mpc.ac20.pivot as pivot 21 | import verifiable_mpc.ac20.compressed_pivot as compressed_pivot 22 | import verifiable_mpc.ac20.knowledge_of_exponent as koe 23 | from verifiable_mpc.ac20.circuit_sat_r1cs import ( 24 | PivotChoice, 25 | create_generators, 26 | next_power_of_2, 27 | lagrange, 28 | calculate_fgh_polys, 29 | ) 30 | 31 | # from mpyc.thresha import _recombination_vector as lagrange ## see below 32 | import verifiable_mpc.ac20.circuit_builder as cb 33 | 34 | prng = SystemRandom() 35 | 36 | logger_cs2 = logging.getLogger("circuit_sat") 37 | logger_cs2.setLevel(logging.INFO) 38 | 39 | logger_cs2_hin = logging.getLogger("circuit_sat_hash_inputs") 40 | logger_cs2_hin.setLevel(logging.INFO) 41 | 42 | logger_cs2_hout = logging.getLogger("circuit_sat_hash_outputs") 43 | logger_cs2_hout.setLevel(logging.INFO) 44 | 45 | 46 | def check_input_length_power_of_2(x, circuit, padding_value=0): 47 | """Pad code and x to meet requirements of compressed pivot protocol.""" 48 | assert circuit.input_ct == len(x) 49 | z_len = circuit.input_ct + 3 + 2 * circuit.mul_ct 50 | # Calculate required padding of x such that len(z) + 1 is a power of 2 51 | if not bin(z_len + 1).count("1") == 1: 52 | padding = next_power_of_2(z_len) - z_len - 1 53 | else: 54 | padding = 0 55 | check = padding == 0 56 | return check, padding, z_len + padding 57 | 58 | 59 | def protocol_8_excl_pivot_prover(generators, circuit, x, gf, use_koe=False): 60 | """Non-interactive implementation of Protocol 8, 61 | including nullity protocol, but excluding the call 62 | to the compressed pivot protocol. 63 | 64 | """ 65 | if "g" in generators: 66 | g = generators["g"] 67 | h = generators["h"] 68 | elif "pp_lhs" in generators: 69 | use_koe = True 70 | pp = generators 71 | else: 72 | raise NotImplementedError 73 | 74 | n = len(x) 75 | assert n == circuit.input_ct 76 | proof = {} 77 | 78 | m = circuit.mul_ct 79 | 80 | # Calculate a, b, c vectors, only for mul gates 81 | a, b, c = circuit.multiplication_triples(x) 82 | 83 | # Calculate random polynomials f, g, h 84 | f_poly, g_poly, h_poly = calculate_fgh_polys( 85 | a, b, None, gf 86 | ) # TODO: remove third param from function 87 | 88 | # Prover commits to (x, f(0), g(0), h(0), h(1), .., h(2m)) 89 | h_evaluations = [h_poly.eval(i + 1) for i in range(2 * m)] 90 | z = x + [f_poly.eval(0), g_poly.eval(0), h_poly.eval(0)] + h_evaluations 91 | 92 | gamma = prng.randrange(1, gf.order) 93 | 94 | if use_koe: 95 | S = range(len(z)) 96 | z_commitment_P, z_commitment_pi = koe.restriction_argument_prover( 97 | S, z, gamma, pp 98 | ) 99 | z_commitment = {"P": z_commitment_P, "pi": z_commitment_pi} 100 | proof["z_commitment"] = z_commitment 101 | else: 102 | logger_cs2.debug("Calculate [Z].") 103 | z_commitment = pivot.vector_commitment(z, gamma, g, h) 104 | proof["z_commitment"] = z_commitment 105 | 106 | logger_cs2.debug("Apply first hash of circuit satisfiability protocol.") 107 | input_list = [z_commitment, str(circuit), "First hash circuit satisfiability protocol"] 108 | logger_cs2_hin.debug( 109 | f"Method protocol_8_excl_pivot_prover: input_list=\n{input_list}" 110 | ) 111 | c = pivot.fiat_shamir_hash(input_list, gf.order) 112 | logger_cs2_hout.debug(f"After hash, hash=\n{c}") 113 | 114 | # Prover's response 115 | y1 = f_poly.eval(c) 116 | y2 = g_poly.eval(c) 117 | y3 = h_poly.eval(c) 118 | assert y3 == y1 * y2 119 | 120 | """ Create linear forms corresponding to f(c), g(c), h(c) for Pi_Nullity step 121 | in Protocol 6 (Protocol 8 in updated version) 122 | """ 123 | linform_f = cb.calculate_fg_form(circuit, wire=0, challenge=c, gf=gf) 124 | linform_g = cb.calculate_fg_form(circuit, wire=1, challenge=c, gf=gf) 125 | linform_h = cb.calculate_h_form(circuit, c, gf) 126 | 127 | y1 = linform_f(z) 128 | y2 = linform_g(z) 129 | y3 = linform_h(z) 130 | assert y1 * y2 == y3 131 | proof["y1"] = y1 132 | proof["y2"] = y2 133 | proof["y3"] = y3 134 | 135 | # Calculate linear form(s) for circuit, and output value(s) 136 | circuit_forms = cb.calculate_circuit_forms(circuit) 137 | circuit_forms = [cb.convert_to_ac20(f, circuit) for f in circuit_forms] 138 | outputs = circuit(x) 139 | proof["outputs"] = outputs 140 | 141 | lin_forms = [form - y for form, y in zip(circuit_forms, outputs)] + [ 142 | linform_f - y1, 143 | linform_g - y2, 144 | linform_h - y3, 145 | ] 146 | 147 | # Follow prove_nullity_compressed but add more inputs to Fiat-Shamir hash 148 | logger_cs2.debug("Apply second hash of circuit satisfiability protocol.") 149 | input_list = [ 150 | y1, 151 | y2, 152 | y3, 153 | z_commitment, 154 | outputs, 155 | circuit_forms, 156 | lin_forms, 157 | "Second hash circuit satisfiability protocol", 158 | ] 159 | logger_cs2_hin.debug( 160 | f"Method: protocol_8_excl_pivot_prover, input_list=\n{input_list}" 161 | ) 162 | rho = pivot.fiat_shamir_hash(input_list, gf.order) 163 | logger_cs2_hout.debug(f"After hash, hash=\n{rho}") 164 | L = sum((linform_i) * (rho ** i) for i, linform_i in enumerate(lin_forms)) 165 | proof["L"] = L 166 | return proof, z_commitment, L, z, gamma 167 | 168 | 169 | def protocol_8_excl_pivot_verifier(proof, circuit, gf, use_koe=False): 170 | verification = {} 171 | y1 = proof["y1"] 172 | y2 = proof["y2"] 173 | y3 = proof["y3"] 174 | if not y1 * y2 == y3: 175 | verification["y1*y2=y3"] = False 176 | return verification 177 | else: 178 | verification["y1*y2=y3"] = True 179 | 180 | n = circuit.input_ct 181 | m = circuit.output_ct 182 | 183 | if "P" in proof: 184 | use_koe = True 185 | if use_koe: 186 | z_commitment_P = proof["z_commitment"]["P"] 187 | z_commitment_pi = proof["z_commitment"]["pi"] 188 | logger_cs2.debug("Apply first hash of circuit satisfiability protocol.") 189 | input_list = [ 190 | z_commitment_P, 191 | z_commitment_pi, 192 | str(circuit), 193 | "First hash circuit satisfiability protocol", 194 | ] 195 | logger_cs2_hin.debug( 196 | f"Method: protocol_8_excl_pivot_verifier (1), input_list=\n{input_list}" 197 | ) 198 | c = pivot.fiat_shamir_hash(input_list, gf.order) 199 | logger_cs2_hout.debug(f"After hash, hash=\n{c}") 200 | else: 201 | z_commitment = proof["z_commitment"] 202 | logger_cs2.debug("Apply first hash of circuit satisfiability protocol.") 203 | input_list = [ 204 | z_commitment, 205 | str(circuit), 206 | "First hash circuit satisfiability protocol", 207 | ] 208 | logger_cs2_hin.debug( 209 | f"Method: protocol_8_excl_pivot_verifier (1), input_list=\n{input_list}" 210 | ) 211 | c = pivot.fiat_shamir_hash(input_list, gf.order) 212 | logger_cs2_hout.debug(f"After hash, hash=\n{c}") 213 | 214 | linform_f = cb.calculate_fg_form(circuit, wire=0, challenge=c, gf=gf) 215 | linform_g = cb.calculate_fg_form(circuit, wire=1, challenge=c, gf=gf) 216 | linform_h = cb.calculate_h_form(circuit, c, gf) 217 | 218 | outputs = proof["outputs"] 219 | circuit_forms = cb.calculate_circuit_forms(circuit) 220 | circuit_forms = [cb.convert_to_ac20(f, circuit) for f in circuit_forms] 221 | 222 | lin_forms = [form - output for form, output in zip(circuit_forms, outputs)] + [ 223 | linform_f - y1, 224 | linform_g - y2, 225 | linform_h - y3, 226 | ] 227 | 228 | logger_cs2.debug("Apply second hash of circuit satisfiability protocol.") 229 | input_list = [ 230 | y1, 231 | y2, 232 | y3, 233 | z_commitment, 234 | outputs, 235 | circuit_forms, 236 | lin_forms, 237 | "Second hash circuit satisfiability protocol", 238 | ] 239 | logger_cs2_hin.debug( 240 | f"Method: protocol_8_excl_pivot_verifier (2), input_list=\n{input_list}" 241 | ) 242 | rho = pivot.fiat_shamir_hash(input_list, gf.order) 243 | logger_cs2_hout.debug(f"After hash, hash=\n{rho}") 244 | L = sum((linform_i) * (rho ** i) for i, linform_i in enumerate(lin_forms)) 245 | 246 | if not L == proof["L"]: 247 | verification["L_wellformed_from_Cfgh_forms"] = False 248 | return verification, L 249 | else: 250 | verification["L_wellformed_from_Cfgh_forms"] = True 251 | 252 | return verification, L 253 | 254 | 255 | def circuit_sat_prover(generators, circuit, x, gf, pivot_choice=PivotChoice.compressed): 256 | """Non-interactive implementation of Protocol 8, prover-side, 257 | including Nullity using arbitrary pivot. 258 | """ 259 | proof, z_commitment, L, z, gamma = protocol_8_excl_pivot_prover( 260 | generators, circuit, x, gf 261 | ) 262 | 263 | if pivot_choice == PivotChoice.compressed: 264 | pivot_proof = compressed_pivot.protocol_5_prover( 265 | generators, z_commitment, L, L(z), z, gamma, gf 266 | ) 267 | elif pivot_choice == PivotChoice.pivot: 268 | g = generators["g"] 269 | h = generators["h"] 270 | pivot_proof = pivot.prove_linear_form_eval( 271 | g, h, z_commitment, L, L(z), z, gamma, gf 272 | ) 273 | elif pivot_choice == PivotChoice.koe: 274 | L = proof["L"] 275 | P = proof["z_commitment"]["P"] 276 | pi = proof["z_commitment"]["pi"] 277 | pivot_proof, u = koe.opening_linear_form_prover(L, z, gamma, generators, P, pi) 278 | else: 279 | raise NotImplementedError 280 | proof["pivot_proof"] = pivot_proof 281 | 282 | return proof 283 | 284 | 285 | def circuit_sat_verifier( 286 | proof, generators, circuit, gf, pivot_choice=PivotChoice.compressed 287 | ): 288 | """Non-interactive implementation of Protocol 8, verifier-side, 289 | including Nullity using arbitrary pivot. 290 | """ 291 | verification, L = protocol_8_excl_pivot_verifier(proof, circuit, gf) 292 | 293 | # Run pivot protocol based on enum `pivot_choice` 294 | if pivot_choice == PivotChoice.compressed: 295 | z_commitment = proof["z_commitment"] 296 | pivot_proof = proof["pivot_proof"] 297 | pivot_verification = compressed_pivot.protocol_5_verifier( 298 | generators, z_commitment, L, 0, pivot_proof, gf 299 | ) 300 | elif pivot_choice == PivotChoice.pivot: 301 | z_commitment = proof["z_commitment"] 302 | g = generators["g"] 303 | h = generators["h"] 304 | z, phi, c = proof["pivot_proof"] 305 | pivot_verification = pivot.verify_linear_form_proof( 306 | g, h, z_commitment, L, 0, z, phi, c 307 | ) 308 | elif pivot_choice == PivotChoice.koe: 309 | koe_pivot_proof = proof["pivot_proof"] 310 | pivot_verification = koe.opening_linear_form_verifier( 311 | L, generators, koe_pivot_proof, 0 312 | ) 313 | verification["pivot_verification"] = pivot_verification 314 | else: 315 | raise NotImplementedError 316 | verification["pivot_verification"] = pivot_verification 317 | 318 | return verification 319 | 320 | 321 | # TODO-list 322 | # TODO-1: Avoid double work: compute z_commitment once, don't compute P (equal to z_commitment) again 323 | # TODO-1: Apply DRY to circuit_sat sub-methods; remove redundant code 324 | # TODO-1: Rename `symbols` to `vars` 325 | # TODO-1: Clean up create_fgh_linear_forms 326 | # TODO-1: Add "tag" to hashes 327 | # TODO-3: Reduce mul-gates: represent multiply-by-constant lines in flatcode by constant in R1CS matrices 328 | -------------------------------------------------------------------------------- /verifiable_mpc/trinocchio/pynocchio.py: -------------------------------------------------------------------------------- 1 | """Implementation of Pinocchio [PGHR13] Protocol 2. 2 | 3 | Paper by Parno, Gentry, Howell, Raykova (2013): 4 | https://eprint.iacr.org/2013/279 5 | Python implementation using BN256 curve and asymmetric bilinear map. 6 | 7 | Small adaptations to original Pinocchio protocol: 8 | * H-check and construction of h-polynomial in zero knowledge case 9 | follows Trinocchio: https://eprint.iacr.org/2015/480 10 | * Compute s-powers in evalkey with range(0, qap.d + 1) instead of 11 | range(1, qap.d + 1), same for h_g1_terms in compute_proof() 12 | 13 | Credits: 14 | * Original bn256 module by Jack Lloyd: 15 | https://github.com/randombit/pairings.py/ (BSD-2-Clause license) 16 | * Original r1cs and qap tools by Vitalik Buterin: 17 | https://github.com/ethereum/research/tree/master/zksnark (MIT license) 18 | """ 19 | 20 | import os, sys 21 | import pprint 22 | from random import SystemRandom 23 | 24 | from mpyc.finfields import GF 25 | import verifiable_mpc.tools.code_to_qap as c2q 26 | import verifiable_mpc.tools.qap_creator as qc 27 | from verifiable_mpc.ac20.pairing import optimal_ate 28 | from mpyc.fingroups import FiniteGroupElement 29 | 30 | 31 | # Globals for module 32 | prng = SystemRandom() 33 | # Point point_add to generic group operation 34 | point_add = FiniteGroupElement.__matmul__ 35 | 36 | class Trapdoor: 37 | def __init__(self, modulus): 38 | _td = list(prng.randrange(modulus) for i in range(8)) 39 | r_v, r_w, s, alpha_v, alpha_w, alpha_y, beta, gamma = _td 40 | r_y = r_v * r_w % modulus 41 | self.r_v = r_v 42 | self.r_w = r_w 43 | self.r_y = r_y 44 | self.s = s 45 | self.alpha_v = alpha_v 46 | self.alpha_w = alpha_w 47 | self.alpha_y = alpha_y 48 | self.beta = beta 49 | self.gamma = gamma 50 | 51 | 52 | class SampleDeltas: 53 | def __init__(self, modulus): 54 | _deltas = list(prng.randrange(modulus) for i in range(3)) 55 | delta_v, delta_w, delta_y = _deltas 56 | self.v = delta_v 57 | self.w = delta_w 58 | self.y = delta_y 59 | 60 | 61 | class Generators: 62 | def __init__(self, td, g1, g2): 63 | self.g1 = g1 64 | self.g2 = g2 65 | self.g1_v = td.r_v * g1 66 | self.g1_w = td.r_w * g1 67 | self.g2_w = td.r_w * g2 68 | self.g1_y = td.r_y * g1 69 | self.g2_y = td.r_y * g2 70 | 71 | 72 | def pairing(a, b): 73 | """Perform pairing operation. 74 | 75 | First input "base" curve, second input "twist" curve as per Pinocchio/Trinocchio notation. 76 | """ 77 | return optimal_ate(b, a) 78 | 79 | 80 | 81 | 82 | def apply_to_list(op, inputs): 83 | """Apply operator `op` to list `inputs` via binary tree 84 | """ 85 | n = len(inputs) 86 | if n == 1: 87 | return inputs[0] 88 | 89 | m0 = apply_to_list(op, inputs[: n // 2]) 90 | m1 = apply_to_list(op, inputs[n // 2 :]) 91 | return op(m0, m1) 92 | 93 | 94 | def g_eval(gen, poly, s, alpha=1): 95 | """ Evaluate poly at s, then scale curve point with outcome 96 | """ 97 | poly_at_s = poly.eval(s) 98 | return int(alpha * poly_at_s) * gen 99 | 100 | 101 | def generate_evalkey(td, qap, gen): 102 | """ Generate public evaluation key. 103 | 104 | Note that g_w^w(s) terms are on twist curve (with generator g2) (See Pinocchio, p. 6) 105 | """ 106 | v_g1 = { 107 | "r_v*v" + str(i) + "*g1": g_eval(gen.g1_v, qap.v[i], td.s) 108 | for i in qap.indices_mid 109 | } 110 | w_g2 = { 111 | "r_w*w" + str(i) + "*g2": g_eval(gen.g2_w, qap.w[i], td.s) 112 | for i in qap.indices_mid 113 | } 114 | y_g1 = { 115 | "r_y*y" + str(i) + "*g1": g_eval(gen.g1_y, qap.y[i], td.s) 116 | for i in qap.indices_mid 117 | } 118 | alphav_g1 = { 119 | "r_v*alpha_v*v" + str(i) + "*g1": g_eval(gen.g1_v, qap.v[i], td.s, td.alpha_v) 120 | for i in qap.indices_mid 121 | } 122 | alphaw_g1 = { 123 | "r_w*alpha_w*w" + str(i) + "*g1": g_eval(gen.g1_w, qap.w[i], td.s, td.alpha_w) 124 | for i in qap.indices_mid 125 | } 126 | alphay_g1 = { 127 | "r_y*alpha_y*y" + str(i) + "*g1": g_eval(gen.g1_y, qap.y[i], td.s, td.alpha_y) 128 | for i in qap.indices_mid 129 | } 130 | spowers_g1 = { 131 | "s^" + str(i) + "*g1": td.s**i * gen.g1 for i in range(0, qap.d + 1) 132 | } 133 | betavwy_g1 = { 134 | "r_v*beta*v+r_w*beta*w+r_y*beta*y" 135 | + str(i) 136 | + "_g1": g_eval(gen.g1_v, qap.v[i], td.s, td.beta) 137 | + (g_eval(gen.g1_w, qap.w[i], td.s, td.beta)) 138 | + (g_eval(gen.g1_y, qap.y[i], td.s, td.beta)) 139 | for i in qap.indices_mid 140 | } 141 | 142 | # Elements required to make proofs zero knowledge 143 | zk_elements = { 144 | "r_v*t*g1": g_eval(gen.g1_v, qap.t, td.s), 145 | "r_w*t*g2": g_eval(gen.g2_w, qap.t, td.s), 146 | "r_y*t*g1": g_eval(gen.g1_y, qap.t, td.s), 147 | "r_v*alpha_v*t*g1": g_eval(gen.g1_v, qap.t, td.s, td.alpha_v), 148 | "r_w*alpha_w*t*g1": g_eval(gen.g1_w, qap.t, td.s, td.alpha_w), 149 | "r_y*alpha_y*t*g1": g_eval(gen.g1_y, qap.t, td.s, td.alpha_y), 150 | "r_v*beta*t*g1": g_eval(gen.g1_v, qap.t, td.s, td.beta), 151 | "r_w*beta*t*g1": g_eval(gen.g1_w, qap.t, td.s, td.beta), 152 | "r_y*beta*t*g1": g_eval(gen.g1_y, qap.t, td.s, td.beta), 153 | "t*g1": g_eval(gen.g1, qap.t, td.s), 154 | } 155 | 156 | evalkey = { 157 | **v_g1, 158 | **w_g2, 159 | **y_g1, 160 | **alphav_g1, 161 | **alphaw_g1, 162 | **alphay_g1, 163 | **spowers_g1, 164 | **betavwy_g1, 165 | **zk_elements, 166 | } 167 | return evalkey 168 | 169 | 170 | def generate_verikey(td, qap, gen): 171 | """ Generate public verification key. 172 | 173 | """ 174 | part1 = { 175 | "g1": gen.g1, 176 | "g2": gen.g2, 177 | "alpha_v*g2": td.alpha_v * gen.g2, 178 | "alpha_w*g1": td.alpha_w * gen.g1, 179 | "alpha_y*g2": td.alpha_y * gen.g2, 180 | "gamma*g2": td.gamma * gen.g2, 181 | "beta*gamma*g1": (td.beta * td.gamma) * gen.g1, 182 | "beta*gamma*g2": (td.beta * td.gamma) * gen.g2, 183 | "r_y*t*g2": g_eval( 184 | gen.g2_y, qap.t, td.s 185 | ), # TS: added when following Trinocchio verification steps 186 | } 187 | v_g1 = { 188 | "r_v*v" + str(i) + "*g1": g_eval(gen.g1_v, qap.v[i], td.s) 189 | for i in qap.indices_io_and_0 190 | } 191 | w_g2 = { 192 | "r_w*w" + str(i) + "*g2": g_eval(gen.g2_w, qap.w[i], td.s) 193 | for i in qap.indices_io_and_0 194 | } 195 | y_g1 = { 196 | "r_y*y" + str(i) + "*g1": g_eval(gen.g1_y, qap.y[i], td.s) 197 | for i in qap.indices_io_and_0 198 | } 199 | verikey = {**part1, **v_g1, **w_g2, **y_g1} 200 | return verikey 201 | 202 | 203 | def compute_p_poly(qap, c): 204 | """ Create QAP polynomial p, as per Definition 2 of Pinocchio, p. 3 205 | 206 | """ 207 | v_terms = apply_to_list(qc.add_polys, [qap.v[i] * c[i] for i in qap.indices]) 208 | w_terms = apply_to_list(qc.add_polys, [qap.w[i] * c[i] for i in qap.indices]) 209 | y_terms = apply_to_list(qc.add_polys, [qap.y[i] * c[i] for i in qap.indices]) 210 | p = (v_terms * w_terms) - y_terms 211 | return p 212 | 213 | 214 | def compute_h_zk_terms(qap, c, deltas): 215 | """ Create QAP polynomial p, as per Definition 2 of Pinocchio, p. 3 216 | 217 | """ 218 | v_terms = apply_to_list( 219 | qc.add_polys, [qap.w[i] * (c[i] * deltas.v) for i in qap.indices] 220 | ) 221 | w_terms = apply_to_list( 222 | qc.add_polys, [qap.v[i] * (c[i] * deltas.w) for i in qap.indices] 223 | ) 224 | h_zk_terms = v_terms + w_terms + qap.t * (deltas.v * deltas.w) - qc.Poly([deltas.y]) 225 | return h_zk_terms 226 | 227 | 228 | def compute_proof(qap, c, h, evalkey, deltas=None): 229 | vmid_g1_terms = [int(c[i]) * evalkey["r_v*v" + str(i) + "*g1"] for i in qap.indices_mid] 230 | wmid_g2_terms = [int(c[i]) * evalkey["r_w*w" + str(i) + "*g2"] for i in qap.indices_mid] 231 | ymid_g1_terms = [int(c[i]) * evalkey["r_y*y" + str(i) + "*g1"] for i in qap.indices_mid] 232 | alphavmid_g1_terms = [int(c[i]) * evalkey[f'r_v*alpha_v*v{i}*g1'] for i in qap.indices_mid] 233 | alphawmid_g1_terms = [int(c[i]) * evalkey[f'r_w*alpha_w*w{i}*g1'] for i in qap.indices_mid] 234 | alphaymid_g1_terms = [int(c[i]) * evalkey[f'r_y*alpha_y*y{i}*g1'] for i in qap.indices_mid] 235 | betavwymid_g1_terms = [int(c[i]) * evalkey[f'r_v*beta*v+r_w*beta*w+r_y*beta*y{i}_g1'] for i in qap.indices_mid] 236 | # h_g1_first_term = gen.g1 * h.coeffs[0] 237 | h_g1_terms = [int(h.coeffs[i]) * evalkey["s^" + str(i) + "*g1"] for i in range(0, len(h))] 238 | # h_g1_terms.append(h_g1_first_term) 239 | vmid_g1 = apply_to_list(point_add, vmid_g1_terms) 240 | wmid_g2 = apply_to_list(point_add, wmid_g2_terms) 241 | ymid_g1 = apply_to_list(point_add, ymid_g1_terms) 242 | alphavmid_g1 = apply_to_list(point_add, alphavmid_g1_terms) 243 | alphawmid_g1 = apply_to_list(point_add, alphawmid_g1_terms) 244 | alphaymid_g1 = apply_to_list(point_add, alphaymid_g1_terms) 245 | betavwymid_g1 = apply_to_list(point_add, betavwymid_g1_terms) 246 | h_g1 = apply_to_list(point_add, h_g1_terms) 247 | 248 | # In zero knowledge case, add terms required to make proof zero knowledge 249 | if deltas != None: 250 | vmid_g1 = vmid_g1 + deltas.v * evalkey["r_v*t*g1"] 251 | wmid_g2 = wmid_g2 + deltas.w * evalkey["r_w*t*g2"] 252 | ymid_g1 = ymid_g1 + deltas.y * evalkey["r_y*t*g1"] 253 | alphavmid_g1 = alphavmid_g1 + deltas.v * evalkey["r_v*alpha_v*t*g1"] 254 | alphawmid_g1 = alphawmid_g1 + deltas.w * evalkey["r_w*alpha_w*t*g1"] 255 | alphaymid_g1 = alphaymid_g1 + deltas.y * evalkey["r_y*alpha_y*t*g1"] 256 | betavwymid_g1 = ( 257 | betavwymid_g1 258 | + deltas.v * evalkey["r_v*beta*t*g1"] 259 | + deltas.w * evalkey["r_w*beta*t*g1"] 260 | + deltas.y * evalkey["r_y*beta*t*g1"] 261 | ) 262 | 263 | proof = { 264 | "r_v*v_mid*g1": vmid_g1, 265 | "r_w*w_mid*g2": wmid_g2, 266 | "r_y*y_mid*g1": ymid_g1, 267 | "r_v*alpha_v*v_mid*g1": alphavmid_g1, 268 | "r_w*alpha_w*w_mid*g1": alphawmid_g1, 269 | "r_y*alpha_y*y_mid*g1": alphaymid_g1, 270 | "r_v*beta*v_mid+r_w*beta*w_mid+r_y*beta*y_mid*g1": betavwymid_g1, 271 | "h*g1": h_g1, 272 | } 273 | return proof 274 | 275 | 276 | def verify(qap, verikey, proof, c): 277 | verification = {} 278 | 279 | # Divisibility check 280 | vio_g1_terms = [int(c[i]) * verikey["r_v*v" + str(i) + "*g1"] for i in qap.indices_io] 281 | wio_g2_terms = [int(c[i]) * verikey["r_w*w" + str(i) + "*g2"] for i in qap.indices_io] 282 | yio_g1_terms = [int(c[i]) * verikey["r_y*y" + str(i) + "*g1"] for i in qap.indices_io] 283 | vio_g1 = apply_to_list(point_add, vio_g1_terms) 284 | wio_g2 = apply_to_list(point_add, wio_g2_terms) 285 | yio_g1 = apply_to_list(point_add, yio_g1_terms) 286 | lhs1 = pairing( 287 | verikey["r_v*v0*g1"] + vio_g1 + proof["r_v*v_mid*g1"], 288 | verikey["r_w*w0*g2"] + wio_g2 + (proof["r_w*w_mid*g2"]), 289 | ) 290 | lhs2 = pairing(yio_g1 + proof["r_y*y_mid*g1"], verikey["g2"]).inverse() 291 | rhs = pairing(proof["h*g1"], verikey["r_y*t*g2"]) 292 | lhs = lhs1 * lhs2 293 | verification["H"] = lhs == rhs 294 | 295 | # Linear combination check 296 | lhs = pairing( 297 | proof["r_v*v_mid*g1"], verikey["alpha_v*g2"] 298 | ) # following order as per Trinocchio p. 19 299 | rhs = pairing(proof["r_v*alpha_v*v_mid*g1"], verikey["g2"]) 300 | verification["V"] = lhs == rhs 301 | 302 | lhs = pairing( 303 | verikey["alpha_w*g1"], proof["r_w*w_mid*g2"] 304 | ) # following order as per Trinocchio p. 19 305 | rhs = pairing(proof["r_w*alpha_w*w_mid*g1"], verikey["g2"]) 306 | alphawmid_g1 = proof["r_w*alpha_w*w_mid*g1"] 307 | verikey_g2 = verikey["g2"] 308 | verification["W"] = lhs == rhs 309 | 310 | lhs = pairing(proof["r_y*alpha_y*y_mid*g1"], verikey["g2"]) 311 | rhs = pairing(proof["r_y*y_mid*g1"], verikey["alpha_y*g2"]) 312 | verification["Y"] = lhs == rhs 313 | 314 | # Check if same witness was used 315 | lhs = pairing( 316 | proof["r_v*beta*v_mid+r_w*beta*w_mid+r_y*beta*y_mid*g1"], verikey["gamma*g2"] 317 | ) 318 | rhs1 = pairing( 319 | proof["r_v*v_mid*g1"] + (proof["r_y*y_mid*g1"]), verikey["beta*gamma*g2"] 320 | ) 321 | rhs2 = pairing(verikey["beta*gamma*g1"], proof["r_w*w_mid*g2"]) 322 | rhs = rhs1 * rhs2 323 | verification["Z"] = lhs == rhs 324 | 325 | return verification 326 | 327 | 328 | # if __name__ == "__main__": 329 | # # Globals when running script 330 | # pp = pprint.PrettyPrinter(indent=4) 331 | # modulus = bn256.order 332 | # g1 = bn256.curve_G 333 | # g2 = bn256.twist_G 334 | # gf = GF(modulus=modulus) 335 | # gf.is_signed = False 336 | 337 | # # Set inputs 338 | # inputs = [gf(3)] 339 | # code = """ 340 | # def qeval(x): 341 | # y = x**3 342 | # return y + x + 5 343 | # """ 344 | 345 | # # inputs = [gf(3), gf(2)] 346 | # # code = """ 347 | # # def qeval(x, y): 348 | # # z = x**3 + 2*y**2 349 | # # return z + x + 5 350 | # # """ 351 | 352 | # # QAP creation step 353 | # qap = c2q.QAP(code, gf) 354 | # print(f"QAP created. Size: {qap.m}, degree {qap.d}.") 355 | 356 | # # Trusted Party's KeyGen step 357 | # td = Trapdoor(modulus) 358 | # gen = Generators(td, g1, g2) 359 | # evalkey = generate_evalkey(td, qap, gen) 360 | # verikey = generate_verikey(td, qap, gen) 361 | # print("Trusted setup completed.") 362 | 363 | # # Prover's steps 364 | # c = qap.calculate_witness(inputs) 365 | # p = compute_p_poly(qap, c) 366 | # h, r = p / qap.t 367 | # assert r == qc.Poly( 368 | # [0] * qap.d 369 | # ), "Remainder of p(x)/t(x) for given witness is not 0" 370 | # deltas = SampleDeltas(modulus) 371 | # # Create zero knowledge variant of h poly 372 | # h_zk = h + compute_h_zk_terms(qap, c, deltas) 373 | # h = h_zk 374 | # print(len(h.coeffs)) 375 | # # Compute proof 376 | # proof = compute_proof(qap, c, h, evalkey, deltas) 377 | # print("Proof computed.") 378 | 379 | # # Verifier's step 380 | # verifications = verify(qap, verikey, proof, c[: qap.out_ix + 1]) 381 | # if all(check == True for check in verifications.values()): 382 | # print("All checks passed.") 383 | # else: 384 | # print("Not all checks passed.") 385 | # pp.pprint(verifications) 386 | 387 | 388 | # TODO list 389 | # TODO-1: Replace simple classes by SimpleNamespaces: https://stackoverflow.com/questions/16279212/how-to-use-dot-notation-for-dict-in-python 390 | # TODO-2: Optimize g_eval: consider caching, or reuse of v_g1 set for alpha v_g1 set 391 | # TODO-3: Pinochcio Section 4.2.1 (p.8) discusses bad asymptotics with naive algorithms instead of FFT; update with FFT 392 | -------------------------------------------------------------------------------- /verifiable_mpc/ac20/pairing.py: -------------------------------------------------------------------------------- 1 | """ Adaptation of work by Jack Lloyd: 2 | https://github.com/randombit/pairings.py/ 3 | 4 | Original code under BSD-2-Clause license: 5 | https://github.com/randombit/pairings.py/blob/master/LICENSE.md 6 | 7 | ---------------------------------------------------------------------- 8 | Copyright and license information of the original work. 9 | ---------------------------------------------------------------------- 10 | 11 | Pairings over a 256-bit BN curve 12 | (C) 2017 Jack Lloyd 13 | 14 | Redistribution and use in source and binary forms, with or without 15 | modification, are permitted provided that the following conditions are 16 | met: 17 | 18 | 1. Redistributions of source code must retain the above copyright 19 | notice, this list of conditions and the following disclaimer. 20 | 21 | 2. Redistributions in binary form must reproduce the above copyright 22 | notice, this list of conditions and the following disclaimer in the 23 | documentation and/or other materials provided with the distribution. 24 | 25 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 26 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 27 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 28 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 29 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 30 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 31 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 32 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 33 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 34 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 35 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36 | 37 | ---------------------------------------------------------------------- 38 | Note: Code below is adapted from its original to directly use extension 39 | fields as defined in MPyC (mpyc.finfields.ExtensionFieldElement). 40 | ---------------------------------------------------------------------- 41 | 42 | """ 43 | import copy 44 | from mpyc.fingroups import EllipticCurve 45 | BN256 = EllipticCurve('BN256') 46 | BN256_TWIST = EllipticCurve('BN256_twist') 47 | 48 | GFp_1 = BN256.field 49 | GFp_2 = BN256_TWIST.field 50 | 51 | v = 1868033 52 | u = pow(v, 3) 53 | p = 36 * u ** 4 + 36 * u ** 3 + 24 * u ** 2 + 6 * u + 1 54 | 55 | i = GFp_2([0, 1, 0]) 56 | xi = i + GFp_2([3, 0, 0]) 57 | 58 | xi1 = [ 59 | xi ** (1 * (p - 1) // 6), 60 | xi ** (2 * (p - 1) // 6), 61 | xi ** (3 * (p - 1) // 6), 62 | xi ** (4 * (p - 1) // 6), 63 | xi ** (5 * (p - 1) // 6), 64 | ] 65 | 66 | 67 | gfp_2_zero = GFp_2(0) 68 | gfp_2_one = GFp_2(1) 69 | 70 | 71 | def conjugate(a): 72 | assert isinstance(a, BN256_TWIST.field) 73 | a_const = a.value.value[0] 74 | a_i = a.value.value[1] 75 | return type(a)([a_const, p - a_i]) 76 | 77 | 78 | xi2 = [(x * conjugate(x)) for x in xi1] 79 | 80 | 81 | def to_naf(x): 82 | z = [] 83 | while x > 0: 84 | if x % 2 == 0: 85 | z.append(0) 86 | else: 87 | zi = 2 - (x % 4) 88 | x -= zi 89 | z.append(zi) 90 | x = x // 2 91 | return z 92 | 93 | 94 | # 6u+2 in NAF 95 | naf_6up2 = list(reversed(to_naf(6 * u + 2)))[1:] 96 | 97 | 98 | def bits_of(k): 99 | return [int(c) for c in "{0:b}".format(k)] 100 | 101 | 102 | # cubic extension of GFp_2 103 | class GFp_6(object): 104 | def __init__(self, x, y, z): 105 | assert type(x) == GFp_2 and type(y) == GFp_2 and type(z) == GFp_2 106 | self.x = x 107 | self.y = y 108 | self.z = z 109 | 110 | def __eq__(self, other): 111 | return self.x == other.x and self.y == other.y and self.z == other.z 112 | 113 | def __repr__(self): 114 | return "(%s,%s,%s)" % (self.x, self.y, self.z) 115 | 116 | def is_zero(self): 117 | return int(self) == 0 118 | 119 | def is_one(self): 120 | return int(self) == 1 121 | 122 | def negative_of(self): 123 | return GFp_6(-self.x, -self.y, -self.z) 124 | 125 | def add(a, b): 126 | return GFp_6(a.x + b.x, a.y + b.y, a.z + b.z) 127 | 128 | def sub(a, b): 129 | return GFp_6(a.x - b.x, a.y - b.y, a.z - b.z) 130 | 131 | def double(self): 132 | return GFp_6(2 * self.x, 2 * self.y, 2 * self.z) 133 | 134 | def mul(a, b): 135 | # Algorithm 13 from http://eprint.iacr.org/2010/354.pdf 136 | # plus some short-circuits 137 | 138 | # if a.x.is_zero(): 139 | # if a.y.is_zero(): 140 | if int(a.x) == 0: 141 | if int(a.y) == 0: 142 | return b.mul_scalar(a.z) 143 | 144 | t0 = b.z * a.z 145 | t1 = b.y * a.y 146 | 147 | tz = (b.x + b.y) * (a.y) 148 | tz -= t1 149 | tz = tz * xi 150 | tz += t0 151 | 152 | ty = (b.y + b.z) * (a.y + a.z) 153 | ty -= t0 154 | ty -= t1 155 | 156 | tx = (b.x) * (a.z) 157 | tx += t1 158 | 159 | return GFp_6(tx, ty, tz) 160 | 161 | if int(b.x) == 0: 162 | if int(b.y) == 0: 163 | return a.mul_scalar(b.z) 164 | 165 | t0 = a.z * b.z 166 | t1 = a.y * b.y 167 | 168 | tz = (a.x + a.y) * (b.y) 169 | tz -= t1 170 | tz = tz * xi 171 | tz += t0 172 | 173 | ty = (a.y + a.z) * (b.y + b.z) 174 | ty -= t0 175 | ty -= t1 176 | 177 | tx = (a.x) * (b.z) 178 | tx += t1 179 | 180 | return GFp_6(tx, ty, tz) 181 | 182 | t0 = a.z * b.z 183 | t1 = a.y * b.y 184 | t2 = a.x * b.x 185 | 186 | tz = (a.x + a.y) * (b.x + b.y) 187 | tz -= t1 188 | tz -= t2 189 | tz = tz * xi 190 | tz += t0 191 | 192 | ty = (a.y + a.z) * (b.y + b.z) 193 | ty -= t0 194 | ty -= t1 195 | ty += t2 * xi 196 | 197 | tx = (a.x + a.z) * (b.x + b.z) 198 | tx -= t0 199 | tx += t1 200 | tx -= t2 201 | 202 | return GFp_6(tx, ty, tz) 203 | 204 | def __mul__(a, b): 205 | return a.mul(b) 206 | 207 | def __add__(a, b): 208 | return a.add(b) 209 | 210 | def __sub__(a, b): 211 | return a.sub(b) 212 | 213 | def mul_scalar(self, k): 214 | assert type(k) == GFp_2 215 | return GFp_6(self.x * k, self.y * k, self.z * k) 216 | 217 | def mul_tau(a): 218 | tx = a.y 219 | ty = a.z 220 | tz = a.x * xi 221 | return GFp_6(tx, ty, tz) 222 | 223 | def square(a): 224 | # Algorithm 16 from http://eprint.iacr.org/2010/354.pdf 225 | ay2 = a.y * 2 226 | c4 = a.z * ay2 227 | c5 = a.x ** 2 228 | c1 = c5 * xi + c4 229 | c2 = c4 - c5 230 | c3 = a.z ** 2 231 | c4 = a.x + a.z - a.y 232 | c5 = ay2 * a.x 233 | c4 = c4 ** 2 234 | c0 = c5 * xi + c3 235 | c2 = c2 + c4 + c5 - c3 236 | n = GFp_6(c2, c1, c0) 237 | return n 238 | 239 | def inverse(a): 240 | # Algorithm 17 241 | XX = a.x ** 2 242 | YY = a.y ** 2 243 | ZZ = a.z ** 2 244 | 245 | XY = a.x * a.y 246 | XZ = a.x * a.z 247 | YZ = a.y * a.z 248 | 249 | A = ZZ - XY * xi 250 | B = XX * xi - YZ 251 | # There is an error in the paper for this line 252 | C = YY - XZ 253 | 254 | F = (C * a.y) * xi 255 | F += A * a.z 256 | F += (B * a.x) * xi 257 | 258 | F = F.reciprocal() 259 | 260 | c_x = C * F 261 | c_y = B * F 262 | c_z = A * F 263 | return GFp_6(c_x, c_y, c_z) 264 | 265 | 266 | gfp_6_zero = GFp_6(gfp_2_zero, gfp_2_zero, gfp_2_zero) 267 | gfp_6_one = GFp_6(gfp_2_zero, gfp_2_zero, gfp_2_one) 268 | 269 | 270 | class GFp_12(object): 271 | def __init__(self, x, y=None): 272 | assert type(x) == GFp_6 273 | assert type(y) == GFp_6 274 | self.x = x 275 | self.y = y 276 | 277 | def __mul__(self, other): 278 | return self.mul(other) 279 | 280 | def __eq__(self, other): 281 | return self.x == other.x and self.y == other.y 282 | 283 | def __repr__(self): 284 | return "(%s,%s)" % (self.x, self.y) 285 | 286 | def is_zero(self): 287 | return self.x.is_zero() and self.y.is_zero() 288 | 289 | def is_one(self): 290 | return self.x.is_zero() and self.y.is_one() 291 | 292 | def conjugate_of(self): 293 | return GFp_12(self.x.negative_of(), self.y) 294 | 295 | def negative_of(self): 296 | return GFp_12(self.x.negative_of(), self.y.negative_of()) 297 | 298 | def frobenius(self): 299 | e1_x = conjugate(self.x.x) * xi1[4] 300 | e1_y = conjugate(self.x.y) * xi1[2] 301 | e1_z = conjugate(self.x.z) * xi1[0] 302 | 303 | e2_x = conjugate(self.y.x) * xi1[3] 304 | e2_y = conjugate(self.y.y) * xi1[1] 305 | e2_z = conjugate(self.y.z) 306 | 307 | return GFp_12(GFp_6(e1_x, e1_y, e1_z), GFp_6(e2_x, e2_y, e2_z)) 308 | 309 | def frobenius_p2(self): 310 | e1_x = self.x.x * xi2[4] 311 | e1_y = self.x.y * xi2[2] 312 | e1_z = self.x.z * xi2[0] 313 | 314 | e2_x = self.y.x * xi2[3] 315 | e2_y = self.y.y * xi2[1] 316 | e2_z = self.y.z 317 | 318 | return GFp_12(GFp_6(e1_x, e1_y, e1_z), GFp_6(e2_x, e2_y, e2_z)) 319 | 320 | def sub(a, b): 321 | return GFp_12(a.x - b.x, a.y - b.y) 322 | 323 | def mul(a, b): 324 | # TODO Karatsuba (algo 20) 325 | AXBX = a.x * b.x 326 | AXBY = a.x * b.y 327 | AYBX = a.y * b.x 328 | AYBY = a.y * b.y 329 | return GFp_12(AXBY + AYBX, AYBY + AXBX.mul_tau()) 330 | 331 | def mul_scalar(self, k): 332 | assert type(k) == GFp_6 333 | return GFp_12(self.x.mul(k), self.y.mul(k)) 334 | 335 | def exp(self, k): 336 | assert isinstance(k, int) 337 | 338 | R = [GFp_12(gfp_6_zero, gfp_6_one), self] 339 | 340 | for kb in bits_of(k): 341 | R[kb ^ 1] = R[kb].mul(R[kb ^ 1]) 342 | R[kb] = R[kb].square() 343 | 344 | return R[0] 345 | 346 | def square(a): 347 | v0 = a.x * a.y 348 | t = a.x.mul_tau() 349 | t += a.y 350 | ty = a.x + a.y 351 | ty *= t 352 | ty -= v0 353 | t = v0.mul_tau() 354 | ty -= t 355 | 356 | c_x = v0.double() 357 | c_y = ty 358 | 359 | return GFp_12(c_x, c_y) 360 | 361 | def inverse(a): 362 | e = GFp_12(a.x.negative_of(), a.y) 363 | 364 | t1 = a.x.square() 365 | t2 = a.y.square() 366 | t1 = t1.mul_tau() 367 | t1 = t2 - t1 368 | t2 = t1.inverse() 369 | 370 | e = e.mul_scalar(t2) 371 | return e 372 | 373 | 374 | def line_func_add(r, p, q, r2): 375 | """ Requires twisted BN256 points r, p, BN256 point q and GFp_2 element r2.""" 376 | 377 | assert type(r2) == GFp_2 378 | 379 | r_t = r.z ** 2 380 | B = p.x * r_t 381 | D = p.y + r.z 382 | D = D ** 2 383 | D -= r2 384 | D -= r_t 385 | D *= r_t 386 | 387 | H = B - r.x 388 | I = H ** 2 389 | 390 | E = I * 4 391 | 392 | J = H * E 393 | L1 = D - r.y 394 | L1 -= r.y 395 | 396 | V = r.x * E 397 | 398 | r_x = L1 ** 2 399 | r_x -= J 400 | r_x -= V * 2 401 | 402 | r_z = r.z + H 403 | r_z = r_z ** 2 404 | r_z -= r_t 405 | r_z -= I 406 | 407 | t = V - r_x 408 | t *= L1 409 | t2 = r.y * J 410 | t2 = t2 * 2 411 | r_y = t - t2 412 | 413 | r_out = type(r)((r_x, r_y, r_z), check=False) 414 | 415 | t = p.y + r_z 416 | t = t ** 2 417 | t = t - r2 418 | t = t - r_z ** 2 419 | 420 | t2 = L1 * p.x 421 | t2 = t2 * 2 422 | a = t2 - t 423 | 424 | c = r_z * int(q.y * 2) 425 | 426 | b = -L1 427 | b = b * int(q.x * 2) 428 | 429 | return (a, b, c, r_out) 430 | 431 | 432 | def line_func_double(r, q): 433 | """ Requires twisted BN256 point r and BN256 point q. """ 434 | 435 | # cache this? 436 | r_t = r.z ** 2 437 | 438 | A = r.x ** 2 439 | B = r.y ** 2 440 | C = B ** 2 441 | 442 | D = r.x + B 443 | D = D ** 2 444 | D -= A 445 | D -= C 446 | D = D * 2 447 | 448 | E = A * 2 + A 449 | F = E ** 2 450 | 451 | C8 = C * 8 # C*8 452 | 453 | r_x = F - D * 2 454 | r_y = E * (D - r_x) - C8 455 | 456 | # (y+z)*(y+z) - (y*y) - (z*z) = 2*y*z 457 | r_z = (r.y + r.z) ** 2 - B - r_t 458 | 459 | # assert r_z == r.y * r.z * 2 # TS/TODO: commented out to test secure computation 460 | 461 | r_out = type(r)((r_x, r_y, r_z), check=False) 462 | # assert r_out.is_on_curve() 463 | 464 | a = r.x + E 465 | a = a ** 2 466 | a -= A + F + B * 4 467 | 468 | t = E * r_t 469 | t = t * 2 470 | b = -t 471 | # print(type(b)) 472 | # print(type(q.x)) 473 | b = b * int(q.x) 474 | 475 | c = r_z * r_t 476 | 477 | c = (c * 2) * int(q.y) 478 | 479 | return (a, b, c, r_out) 480 | 481 | 482 | def mul_line(r, a, b, c): 483 | assert type(r) == GFp_12 484 | assert type(a) == GFp_2 485 | assert type(b) == GFp_2 486 | assert type(c) == GFp_2 487 | 488 | # See function fp12e_mul_line in dclxvi 489 | 490 | t1 = GFp_6(gfp_2_zero, a, b) 491 | t2 = GFp_6(gfp_2_zero, a, b + c) 492 | 493 | t1 = t1 * r.x 494 | t3 = r.y.mul_scalar(c) 495 | r.x += r.y 496 | r.y = t3 497 | r.x *= t2 498 | r.x -= t1 499 | r.x -= r.y 500 | r.y += t1.mul_tau() 501 | 502 | 503 | def miller(q, p): 504 | """ Apply Miller loop on the twisted BN256 point q and BN256 point p. 505 | 506 | Args: 507 | q, p (CurveElement): twisted and regular BN256 point. 508 | 509 | Returns: 510 | type(GFp_12): element of tower field GFp_12 511 | 512 | Implementation follows [BDM+10] and [NNS10]: 513 | [BDM+10]: https://link.springer.com/chapter/10.1007/978-3-642-17455-1_2 514 | [NNS10]: https://link.springer.com/chapter/10.1007/978-3-642-14712-8_7 515 | """ 516 | Q = copy.deepcopy(q) 517 | P = copy.deepcopy(p) 518 | # TODO: Are Jacobian or affine coordinates required? 519 | # TODO: Add `assert p.z == 1`? And same for q? 520 | 521 | mQ = ~Q 522 | 523 | f = GFp_12(gfp_6_zero, gfp_6_one) 524 | T = Q 525 | 526 | Qp = Q.y ** 2 527 | 528 | for naf_i in naf_6up2: 529 | # Skip on first iteration? 530 | f = f.square() 531 | 532 | a, b, c, T = line_func_double(T, P) 533 | mul_line(f, a, b, c) 534 | 535 | if naf_i == 1: 536 | a, b, c, T = line_func_add(T, Q, P, Qp) 537 | mul_line(f, a, b, c) 538 | elif naf_i == -1: 539 | a, b, c, T = line_func_add(T, mQ, P, Qp) 540 | mul_line(f, a, b, c) 541 | 542 | Q1 = type(q)((conjugate(Q.x) * xi1[1], conjugate(Q.y) * xi1[2], gfp_2_one)) 543 | 544 | Q2 = type(q)((Q.x * (xi2[1].value.value[0]), Q.y, gfp_2_one)) 545 | 546 | Qp = Q1.y ** 2 547 | a, b, c, T = line_func_add(T, Q1, P, Qp) 548 | mul_line(f, a, b, c) 549 | 550 | Qp = Q2.y ** 2 551 | a, b, c, T = line_func_add(T, Q2, P, Qp) 552 | mul_line(f, a, b, c) 553 | 554 | return f 555 | 556 | 557 | def final_exp(inp): 558 | assert type(inp) == GFp_12 559 | 560 | # Algorithm 31 from https://eprint.iacr.org/2010/354.pdf 561 | 562 | t1 = inp.conjugate_of() 563 | inv = inp.inverse() 564 | 565 | t1 = t1.mul(inv) 566 | # Now t1 = inp^(p**6-1) 567 | 568 | t2 = t1.frobenius_p2() 569 | t1 = t1.mul(t2) 570 | 571 | fp1 = t1.frobenius() 572 | fp2 = t1.frobenius_p2() 573 | fp3 = fp2.frobenius() 574 | 575 | fu1 = t1.exp(u) 576 | fu2 = fu1.exp(u) 577 | fu3 = fu2.exp(u) 578 | 579 | y3 = fu1.frobenius() 580 | fu2p = fu2.frobenius() 581 | fu3p = fu3.frobenius() 582 | y2 = fu2.frobenius_p2() 583 | 584 | y0 = fp1.mul(fp2) 585 | y0 = y0.mul(fp3) 586 | 587 | y1 = t1.conjugate_of() 588 | y5 = fu2.conjugate_of() 589 | y3 = y3.conjugate_of() 590 | y4 = fu1.mul(fu2p) 591 | y4 = y4.conjugate_of() 592 | 593 | y6 = fu3.mul(fu3p) 594 | y6 = y6.conjugate_of() 595 | 596 | t0 = y6.square() 597 | t0 = t0.mul(y4) 598 | t0 = t0.mul(y5) 599 | 600 | t1 = y3.mul(y5) 601 | t1 = t1.mul(t0) 602 | t0 = t0.mul(y2) 603 | t1 = t1.square() 604 | t1 = t1.mul(t0) 605 | t1 = t1.square() 606 | t0 = t1.mul(y1) 607 | t1 = t1.mul(y0) 608 | t0 = t0.square() 609 | t0 = t0.mul(t1) 610 | 611 | return t0 612 | 613 | 614 | def optimal_ate(a, b): 615 | """Optimal ate pairing. 616 | 617 | Original code by Jack Lloyd, see: https://github.com/randombit/pairings.py 618 | Adapted to integrate with secure_groups and mpyc APIs. 619 | 620 | Invariants: 621 | a, b are in short weierstrass form. 622 | 623 | Args: 624 | a (Curve(BN256_TWIST)): BN256_TWIST point 625 | b (Curve(BN256)): BN256 point 626 | 627 | Returns: 628 | GFp_12: pairing operation on a, b 629 | """ 630 | 631 | # Convert to Jacobian coordinates with Z==1. 632 | # NB: a and b can actually be projective. After normalization rest of code will 633 | # continue as if points are Jacobian. 634 | a = a.normalize() 635 | b = b.normalize() 636 | 637 | # if int(a.z) == 0 or int(b.z) == 0: 638 | # return GFp_12(gfp_6_zero, gfp_6_one) 639 | if a is a.identity or b is b.identity: 640 | return GFp_12(gfp_6_zero, gfp_6_one) 641 | 642 | e = miller(a, b) 643 | ret = final_exp(e) 644 | 645 | return ret 646 | 647 | 648 | # TODO: 649 | # 1: Check if conjugate() works if input is 0 or of the form 0i + a; fix 650 | -------------------------------------------------------------------------------- /verifiable_mpc/ac20/mpc_ac20.py: -------------------------------------------------------------------------------- 1 | """MPC interface for circuit_sat and compressed_pivot modules. 2 | 3 | Circuit_sat implementation of https://eprint.iacr.org/2020/152 4 | ``Compressed Sigma-Protocol Theory and Practical Application 5 | to Plug & Play Secure Algorithmics'' 6 | 7 | """ 8 | 9 | import mpyc.mpctools as mpctools 10 | from mpyc.runtime import mpc, logging 11 | from mpyc.fingroups import EllipticCurvePoint as EllipticCurveElement 12 | from mpyc.secgroups import repeat_public_base_public_output as secure_repeat 13 | import verifiable_mpc.tools.qap_creator as qc 14 | import verifiable_mpc.ac20.pivot as pivot 15 | import verifiable_mpc.ac20.circuit_sat_r1cs as cs 16 | import verifiable_mpc.ac20.knowledge_of_exponent as koe 17 | from verifiable_mpc.ac20.recombine import recombine, _recombination_vectors 18 | from verifiable_mpc.ac20.pivot import _int as _int 19 | 20 | 21 | logger_cs_mpc = logging.getLogger("cs_mpc") 22 | logger_cs_mpc.setLevel(logging.INFO) 23 | 24 | logger_cs_mpc_hin = logging.getLogger("cs_mpc_hash_inputs") 25 | logger_cs_mpc_hin.setLevel(logging.INFO) 26 | 27 | logger_cs_mpc_hout = logging.getLogger("cs_mpc_hash_outputs") 28 | logger_cs_mpc_hout.setLevel(logging.INFO) 29 | 30 | 31 | def list_mul(x): 32 | return mpctools.reduce(type(x[0]).operation, x) 33 | 34 | 35 | def vector_commitment(x, gamma, g, h): 36 | """Pedersen vector commitment. Definition 1 of AC20. 37 | 38 | Uses secure groups to locally compute Prod_j g_j^{share_i of x_j}, 39 | reduce result and publish this to other parties for recombination. 40 | """ 41 | c = secure_repeat(g + [h], x + [gamma]) 42 | return c 43 | 44 | 45 | async def create_generators(group, sectype, input_length): 46 | # TODO: consider setup to exclude trapdoors 47 | h = group.generator 48 | random_exponents = [mpc._random(sectype) for i in range(input_length+1)] 49 | kg = await mpc.gather([secure_repeat(h, u) for u in random_exponents]) 50 | generators = {"g": kg[1:], "h": h, "k": kg[0]} 51 | return generators 52 | 53 | 54 | async def koe_trusted_setup(group, sectype, input_length, progress_bar=False): 55 | group1 = group[0] 56 | group2 = group[1] 57 | order = group1.order 58 | _g1 = group1.generator # BN256 regular 59 | _g2 = group2.generator # BN256 twist 60 | 61 | g_exp = mpc._random(sectype) # TODO: sample > 0 62 | alpha = mpc._random(sectype) 63 | z = mpc._random(sectype) 64 | g1 = await secure_repeat(_g1, g_exp) 65 | g2 = await secure_repeat(_g2, g_exp * alpha) 66 | 67 | pp_lhs = [] 68 | pp_rhs = [] 69 | g1_base = g1 70 | g2_base = g2 71 | for i in range(2 * input_length): 72 | g1 = await secure_repeat(g1, z) 73 | g2 = await secure_repeat(g2, z) 74 | 75 | pp_lhs.append(g1_base) 76 | pp_rhs.append(g2_base) 77 | 78 | if progress_bar: 79 | print(f"Generating keys: {round(100*i/(2*input_length+1))}%", end="\r") 80 | 81 | pp = {"pp_lhs": pp_lhs, "pp_rhs": pp_rhs} 82 | return pp 83 | 84 | 85 | async def koe_restriction_argument_prover(S, x, gamma, pp): 86 | """Resriction argument from [Gro10]: Prover's side. 87 | 88 | Extension of the Knowledge Commitment Scheme, but proofs that a subset 89 | of indices S of {0, ..., n-1}, corresponding to vector x, was used. 90 | """ 91 | 92 | # Prover commits only to S-indices of x with (new) commitment P, with secret random gamma 93 | P = await secure_repeat([pp["pp_lhs"][0]] + [pp["pp_lhs"][i+1] for i in S], 94 | [gamma] + [x[i] for i in S]) 95 | 96 | # Trusted party or verifier samples beta; only required in designated verifier scenario 97 | # beta = prng.randrange(1, order) 98 | # sigma = (g1**beta, [(g2**(z**i))**beta for i in S]) 99 | 100 | # Prover computes pi^alpha (slight deviation from Thomas' notes, who computes pi) 101 | pi= await secure_repeat([pp["pp_rhs"][0]] + [pp["pp_rhs"][i+1] for i in S], 102 | [gamma] + [x[i] for i in S]) 103 | return P, pi 104 | 105 | 106 | async def koe_opening_linear_form_prover(L, x, gamma, pp, P=None, pi=None): 107 | """ZK argument of knowledge for the opening of a linear form. 108 | 109 | Using adaptation of the multiplication argument of [Gro10]. 110 | """ 111 | proof = {} 112 | n = len(x) 113 | S = range(n) 114 | assert 2*n - 1 <= len(pp["pp_lhs"]), "Requirement does not hold: 2*len(x)-1 <= number of generators in first group." 115 | # """Run Restriction argument on (P, {1, ..., n}; x, gamma) to show that 116 | # (P, P_bar) is indeed a commitment to vector x in Z_q 117 | # """ 118 | if P is None: 119 | P, pi = await koe_restriction_argument_prover(S, x, gamma, pp) 120 | proof["P"] = P 121 | proof["pi"] = pi 122 | 123 | # Prover computes coefficients of c_poly and Q, sends Q to verifier 124 | u = L(x) 125 | L_linear, u_linear = pivot.affine_to_linear(L, u, n) 126 | 127 | c_poly_lhs = qc.Poly([gamma] + [x_i for x_i in x]) 128 | c_poly_rhs = qc.Poly([L_linear.coeffs[n - (j + 1)] for j in range(n)]) 129 | c_poly = c_poly_lhs * c_poly_rhs 130 | 131 | # assert u_linear == c_poly.coeffs[n], "L(x) not equal to n-th coefficient of c_poly" 132 | c_bar = c_poly.coeffs 133 | sectype = type(c_bar[0]) 134 | c_bar[n] = sectype(0) # Ensure c_bar[n] is also a sectype (for secure_repeat) 135 | assert len(pp["pp_lhs"]) == 2 * n 136 | Q = await secure_repeat(pp["pp_lhs"], [-c for c in c_bar]) 137 | proof["Q"] = Q 138 | return proof, u 139 | 140 | 141 | async def protocol_4_prover(g_hat, k, Q, L_tilde, z_hat, gf, proof={}, round_i=0): 142 | """Non-interactive version of Protocol 4 from Section 4.2: Prover's side.""" 143 | 144 | # Step 5: Prover calculates A, B 145 | half = len(g_hat) // 2 146 | g_hat_l = g_hat[:half] 147 | g_hat_r = g_hat[half:] 148 | z_hat_l = z_hat[:half] 149 | z_hat_r = z_hat[half:] 150 | logger_cs_mpc.debug("Calculate A_i, B_i.") 151 | A = await vector_commitment(z_hat_l, _int(L_tilde([0] * half + z_hat_l)), g_hat_r, k) 152 | B = await vector_commitment(z_hat_r, _int(L_tilde(z_hat_r + [0] * half)), g_hat_l, k) 153 | logger_cs_mpc.debug(f"Provers opened A{str(round_i)}, B{str(round_i)}") 154 | 155 | proof["A" + str(round_i)] = A 156 | proof["B" + str(round_i)] = B 157 | 158 | # Step 6: Prover calculates challenge 159 | order = k.order 160 | # Hash A, B and all public elements of Protocol 4 161 | 162 | # input_list = [A, B, g_hat, k, Q, L_tilde] 163 | if isinstance(A, EllipticCurveElement): 164 | input_list = [A.normalize(), B.normalize(), g_hat, k, Q.normalize(), L_tilde] 165 | else: 166 | input_list = [A, B, g_hat, k, Q, L_tilde] 167 | 168 | logger_cs_mpc_hin.debug( 169 | f"Method protocol_4_prover: Before fiat_shamir_hash, input_list=\n{input_list}" 170 | ) 171 | c = pivot.fiat_shamir_hash(input_list, order) 172 | logger_cs_mpc_hout.debug(f"After hash, hash=\n{c}") 173 | 174 | # Step 7: Prover calculates following public values 175 | logger_cs_mpc.debug("Calculate g_prime.") 176 | g_prime = [(g_hat_l[i] ** c) * g_hat_r[i] for i in range(half)] 177 | logger_cs_mpc.debug("Calculate Q_prime.") 178 | Q_prime = A * (Q ** c) * (B ** (c ** 2)) 179 | 180 | assert ( 181 | L_tilde.constant == 0 182 | ), "Next line assumes L_tilde is a linear form, not affine form." 183 | c_L_tilde_l_coeffs = [coeff * gf(c) for coeff in L_tilde.coeffs[:half]] 184 | L_prime = pivot.LinearForm(c_L_tilde_l_coeffs) + pivot.LinearForm( 185 | L_tilde.coeffs[half:] 186 | ) 187 | 188 | # Step 8: Prover calculates z_prime and tests if recursion is required (if z' in Z or Z^2) 189 | z_prime = [z_hat_l[i] + c * z_hat_r[i] for i in range(half)] 190 | if len(z_prime) <= 2: 191 | z_prime = await mpc.output( 192 | z_prime 193 | ) # TODO: perhaps not necessary to open z_prime; double check 194 | logger_cs_mpc.debug(f"Provers opened z_prime") 195 | proof["z_prime"] = z_prime 196 | return proof 197 | else: 198 | # Step 9: Prover sends z_prime and starts recursion of protocol 4 199 | round_i += 1 200 | proof = await protocol_4_prover( 201 | g_prime, k, Q_prime, L_prime, z_prime, gf, proof, round_i 202 | ) 203 | return proof 204 | 205 | 206 | async def protocol_5_prover(generators, P, L, y, x, gamma, gf): 207 | secfld = type(x[0]) 208 | g = generators["g"] 209 | h = generators["h"] 210 | k = generators["k"] 211 | 212 | proof = {} 213 | 214 | # Public parameters 215 | n = len(x) 216 | L, y = pivot.affine_to_linear(L, y, n) 217 | L.constant = await mpc.output(L.constant) 218 | 219 | # Open P, y, L to ensure consistent output of Fiat-Shamir hash 220 | y = await mpc.output(y) 221 | assert ( 222 | bin(n + 1).count("1") == 1 223 | ), "This implementation requires n+1 to be power of 2 (else, use padding with zeros)." 224 | logger_cs_mpc.debug(f"Provers opened y.") 225 | 226 | # Non-interactive proof 227 | # Step 1: Prover calculates ("announcement") t, A 228 | order = gf.order 229 | r = list(mpc._random(secfld) for i in range(n)) 230 | rho = mpc._random(secfld) 231 | t = L(r) 232 | logger_cs_mpc.debug("Calculate A.") 233 | A = await vector_commitment(r, rho, g, h) 234 | t = await mpc.output(t) 235 | logger_cs_mpc.debug(f"Provers opened t, A.") 236 | proof["t"] = t 237 | proof["A"] = A 238 | 239 | # Step 2: Prover computes challenge 240 | # input_list = [t, A, generators, P, L, y] 241 | if isinstance(A, EllipticCurveElement): 242 | input_list = [t, A.normalize(), generators, P.normalize(), L, y] 243 | else: 244 | input_list = [t, A, generators, P, L, y] 245 | 246 | logger_cs_mpc_hin.debug( 247 | f"Method protocol_5_prover: Before fiat_shamir_hash, input_list=\n{input_list}" 248 | ) 249 | c0 = pivot.fiat_shamir_hash( 250 | input_list + [0] + ["First hash of compressed pivot"], order 251 | ) 252 | c1 = pivot.fiat_shamir_hash( 253 | input_list + [1] + ["First hash of compressed pivot"], order 254 | ) 255 | logger_cs_mpc_hout.debug(f"After hash, hash=\n{c0}, {c1}") 256 | 257 | # Step 3: Prover calculates 258 | z = [c0 * x_i + r[i] for i, x_i in enumerate(x)] 259 | phi = c0 * gamma + rho 260 | z_hat = z + [phi] 261 | 262 | # Step 4: Prover calculates following public variables 263 | g_hat = g + [h] 264 | logger_cs_mpc.debug("Calculate Q.") 265 | Q = A * (P ** c0) * (k ** _int(c1 * (c0 * y + t))) 266 | L_tilde = pivot.LinearForm(L.coeffs + [0]) * c1 267 | # assert L(z)*c1 == L_tilde(z_hat) 268 | proof = await protocol_4_prover(g_hat, k, Q, L_tilde, z_hat, gf, proof) 269 | return proof 270 | 271 | 272 | def calculate_fgh_polys(a, b, c, gf, secfld): 273 | # Calculate random polynomials f, g, h 274 | r_a = mpc._random(secfld) 275 | r_b = mpc._random(secfld) 276 | logger_cs_mpc.debug("Calculate f_poly.") 277 | f_poly = qc.Poly(qc.lagrange_interp_ff(a + [r_a], gf)) 278 | logger_cs_mpc.debug("Calculate g_poly.") 279 | g_poly = qc.Poly(qc.lagrange_interp_ff(b + [r_b], gf)) 280 | logger_cs_mpc.debug("Calculate h_poly.") 281 | h_poly = f_poly * g_poly 282 | logger_cs_mpc.debug("Done calculating f, g, h.") 283 | # assert c == [h_poly.eval(i+1) for i in range(m)], "Evaluations of h at 1..m not equal to vector c" 284 | return f_poly, g_poly, h_poly 285 | 286 | 287 | async def protocol_8_excl_pivot_prover(generators, code, x, gf, use_koe=False): 288 | """Non-interactive implementation of Protocol 8, 289 | including nullity protocol, but excluding the call 290 | to the compressed pivot protocol. 291 | 292 | """ 293 | secfld = type(x[0]) 294 | if "g" in generators: 295 | g = generators["g"] 296 | h = generators["h"] 297 | elif "pp_lhs" in generators: 298 | use_koe = True 299 | pp = generators 300 | else: 301 | raise NotImplementedError 302 | 303 | logger_cs_mpc.debug(f"Calculate witness.") 304 | n = len(x) 305 | xc = cs.calculate_witness(code, x) 306 | proof = {} 307 | logger_cs_mpc.debug(f"Transform code to flatcode (circuit).") 308 | flatcode, inputs, varnames, r1cs = cs.code_to_flatcode_and_r1cs(code) 309 | 310 | mul_indices_of_flatcode = cs.mul_in_flatcode(flatcode) 311 | m = len(mul_indices_of_flatcode) 312 | output_variables = [symbol for symbol in varnames if symbol.startswith("~out")] 313 | 314 | # Calculate a, b, c vectors, only for mul gates 315 | logger_cs_mpc.debug(f"Calculate a, b, c vectors.") 316 | a, b = cs.calculate_ab_vectors(r1cs, xc, mul_indices_of_flatcode) 317 | c = mpc.schur_prod(a, b) 318 | 319 | # # Calculate random polynomials f, g, h 320 | # f_poly, g_poly, h_poly = calculate_fgh_polys(a, b, c, gf, secfld) 321 | 322 | # # Prover commits to (x, f(0), g(0), h(0), h(1), .., h(2m)) 323 | # h_evaluations = [h_poly.eval(i + 1) for i in range(2 * m)] 324 | # z = x + [f_poly.eval(0), g_poly.eval(0), h_poly.eval(0)] + h_evaluations 325 | 326 | logger_cs_mpc.debug(f"Calculate z.") 327 | f0 = mpc._random(secfld) 328 | g0 = mpc._random(secfld) 329 | a = [f0] + a 330 | b = [g0] + b 331 | logger_cs_mpc.debug(f"Calculate z: a, b = await mpc.gather(a,b)") 332 | a, b = await mpc.gather(a, b) 333 | logger_cs_mpc.debug(f"Calculate z: fs = recombine(...)") 334 | fs = recombine(gf, list(zip(range(m+1), a)), list(range(m+1, 2*m+1))) 335 | logger_cs_mpc.debug(f"Calculate z: gs = recombine(...)") 336 | gs = recombine(gf, list(zip(range(m+1), b)), list(range(m+1, 2*m+1))) 337 | logger_cs_mpc.debug(f"Calculate z: hs = list(...)") 338 | hs = list(map(secfld, await mpc.schur_prod(fs, gs))) 339 | z = x + [f0, g0, f0 * g0] + c + hs 340 | 341 | gamma = mpc._random(secfld) 342 | 343 | if use_koe: 344 | S = range(len(z)) 345 | z_commitment_P, z_commitment_pi = await koe_restriction_argument_prover( 346 | S, z, gamma, pp 347 | ) 348 | z_commitment = {"P": z_commitment_P, "pi": z_commitment_pi} 349 | proof["z_commitment"] = z_commitment 350 | else: 351 | logger_cs_mpc.debug("Calculate commitment for z, denoted by [z].") 352 | z_commitment = await vector_commitment(z, gamma, g, h) 353 | # logger_cs_mpc.debug("Calculated [z] in secret shared form.") 354 | logger_cs_mpc.debug(f"Opened [z] (z_commitment).") 355 | proof["z_commitment"] = z_commitment 356 | 357 | logger_cs_mpc.debug("Apply first hash of circuit satisfiability protocol.") 358 | input_list = [z_commitment, code, "First hash circuit satisfiability protocol"] 359 | logger_cs_mpc_hin.debug( 360 | f"Method protocol_8_excl_pivot_prover (1): input_list={input_list}" 361 | ) 362 | c = pivot.fiat_shamir_hash(input_list, gf.order) # TODO: name clash c = a * b from above 363 | logger_cs_mpc_hout.debug(f"After hash, hash=\n{c}") 364 | 365 | # Prover's response 366 | # y1 = recombine(gf, list(zip(range(m+1), b)), c) # f_poly.eval(c) # TODO: typo!?? b -> a? 367 | # y2 = recombine(gf, list(zip(range(m+1), b)), c) # g_poly.eval(c) 368 | # y3 = y1 * y2 369 | # NB: these values are not used below! TS: These previous lines (y1, y2, y3) are for debugging/checking consistency only. 370 | 371 | # Create linear forms corresponding to f(c), g(c), h(c) for Pi_Nullity step in Protocol 6 (Protocol 8 in updated version) 372 | linform_f, linform_g, linform_h = cs.create_fgh_linear_forms( 373 | r1cs, c, varnames, flatcode, mul_indices_of_flatcode, n, m, gf 374 | ) 375 | 376 | y1 = linform_f(z) 377 | y2 = linform_g(z) 378 | y3 = linform_h(z) 379 | y1 = await mpc.output(y1) 380 | y2 = await mpc.output(y2) 381 | y3 = await mpc.output(y3) 382 | assert y1 * y2 == y3 383 | proof["y1"] = y1 384 | proof["y2"] = y2 385 | proof["y3"] = y3 386 | 387 | circuits = [] 388 | outputs = [] 389 | for output_var in output_variables: 390 | circuit = cs.express_as_x_or_gamma(output_var, flatcode, varnames, n) 391 | y = circuit(z) 392 | y = await mpc.output(y) 393 | # assert y == xc[varnames.index(output_var)], f"Output of circuit {y} not equal to ~out in witness." 394 | circuits.append(circuit) 395 | outputs.append(y) 396 | proof["outputs"] = outputs 397 | 398 | lin_forms = [circuit - y for circuit, y in zip(circuits, outputs)] + [ 399 | linform_f - y1, 400 | linform_g - y2, 401 | linform_h - y3, 402 | ] 403 | 404 | # Follow prove_nullity_compressed but add more inputs to Fiat-Shamir hash 405 | logger_cs_mpc.debug("Apply second hash of circuit satisfiability protocol.") 406 | input_list = [ 407 | y1, 408 | y2, 409 | y3, 410 | z_commitment, 411 | outputs, 412 | circuits, 413 | lin_forms, 414 | "Second hash circuit satisfiability protocol", 415 | ] 416 | logger_cs_mpc_hin.debug( 417 | f"Method protocol_8_excl_pivot_prover (2): input_list={input_list}" 418 | ) 419 | rho = pivot.fiat_shamir_hash(input_list, gf.order) 420 | logger_cs_mpc_hout.debug(f"After hash, hash=\n{rho}") 421 | L = sum((linform_i) * (rho ** i) for i, linform_i in enumerate(lin_forms)) 422 | proof["L"] = L 423 | return proof, z_commitment, L, z, gamma 424 | 425 | 426 | async def prove_linear_form_eval(g, h, P, L, y, x, gamma, gf): 427 | """Sigma protocol Pi_s (protocol 2) from AC20. 428 | 429 | Non-interactive version. 430 | """ 431 | secfld = type(x[0]) 432 | n = len(x) 433 | L, y = pivot.affine_to_linear(L, y, n) 434 | y = await mpc.output(y) 435 | 436 | r = list(mpc._random(secfld) for i in range(n)) 437 | rho = mpc._random(secfld) 438 | 439 | t = L(r) 440 | A = await vector_commitment(r, rho, g, h) 441 | t = await mpc.output(t) 442 | logger_cs_mpc.debug(f"Provers opened t.") 443 | logger_cs_mpc.debug(f"Provers opened A.") 444 | 445 | if isinstance(A, EllipticCurveElement): 446 | input_list = [t, A.normalize(), g, h, P.normalize(), L, y] 447 | else: 448 | input_list = [t, A, g, h, P, L, y] 449 | logger_cs_mpc_hin.debug(f"Method prove_linear_form_eval: input_list={input_list}.") 450 | c = pivot.fiat_shamir_hash(input_list, gf.order) 451 | logger_cs_mpc_hout.debug(f"After hash, hash=\n{c}") 452 | z = [c * x_i + r[i] for i, x_i in enumerate(x)] 453 | # phi = (c*gamma + rho) % gf.order 454 | phi = c * gamma + rho 455 | 456 | z = await mpc.output(z) 457 | phi = await mpc.output(phi) 458 | 459 | return ( 460 | z, 461 | phi, 462 | c, 463 | ) # TODO: check if it's correct to return c as well (Required to reconstruct A in non-interactive proof) 464 | 465 | 466 | async def circuit_sat_prover( 467 | generators, code, x, gf, pivot_choice=cs.PivotChoice.compressed 468 | ): 469 | """Non-interactive implementation of Protocol 8, prover-side, 470 | including Nullity using compressed pivot (Protocol 5). 471 | """ 472 | logger_cs_mpc.debug(f"Enter circuit_sat_prover. pivot_choice={pivot_choice}") 473 | 474 | logger_cs_mpc.debug(f"Start protocol 8, excluding pivot proof.") 475 | proof, z_commitment, L, z, gamma = await protocol_8_excl_pivot_prover( 476 | generators, code, x, gf 477 | ) 478 | 479 | if pivot_choice == cs.PivotChoice.compressed: 480 | pivot_proof = await protocol_5_prover( 481 | generators, z_commitment, L, L(z), z, gamma, gf 482 | ) 483 | elif pivot_choice == cs.PivotChoice.pivot: 484 | g = generators["g"] 485 | h = generators["h"] 486 | pivot_proof = await prove_linear_form_eval( 487 | g, h, z_commitment, L, L(z), z, gamma, gf 488 | ) 489 | elif pivot_choice == cs.PivotChoice.koe: 490 | L = proof["L"] 491 | P = proof["z_commitment"]["P"] 492 | pi = proof["z_commitment"]["pi"] 493 | pivot_proof, u = await koe_opening_linear_form_prover(L, z, gamma, generators, P, pi) 494 | else: 495 | raise NotImplementedError 496 | proof["pivot_proof"] = pivot_proof 497 | 498 | return proof 499 | 500 | 501 | # TODO-list 502 | # TS-1: Include option that provers don't learn output y (or multiple outputs) and only keep y in secret shared form while proving. 503 | # TS-1: Do y and outputs have to be part of the hash? its in second hash of protocol 8 (circuit sat) and hash of protocol 5 (compressed pivot) 504 | # TS-1: Can the prove be forged when y is not part of the hash? 505 | # TS-1: Avoid double work: compute z_commitment once, don't compute P (equal to z_commitment) again 506 | # TS-1: Enable sectype operations for SecureEdwardsGroup as it does not inherit from Share but from Point 507 | # * Also in _int() helper method? 508 | # TS-2: In method output(), also catch SecEdwardsGroup types (check spelling) 509 | -------------------------------------------------------------------------------- /verifiable_mpc/ac20/circuit_builder.py: -------------------------------------------------------------------------------- 1 | # TODO/NOTE: All gadgets are work in progress, incomplete 2 | from enum import Enum 3 | 4 | from mpyc.finfields import GF, FiniteFieldElement, PrimeFieldElement 5 | import mpyc.mpctools as mpctools 6 | from mpyc.runtime import mpc 7 | from mpyc.sectypes import SecureFiniteField, SecureInteger 8 | import verifiable_mpc.ac20.mpc_ac20 9 | from verifiable_mpc.ac20.pivot import AffineForm, LinearForm 10 | from verifiable_mpc.ac20.circuit_sat_r1cs import calculate_fgh_polys 11 | import verifiable_mpc.tools.qap_creator as qc 12 | from random import SystemRandom 13 | 14 | 15 | class op(Enum): 16 | add = "add" 17 | mul = "mul" 18 | scalar_mul = "scalar mul" 19 | 20 | 21 | class Gate: 22 | """Note: AC20 requires fan-in 2 (unbounded fan-out)""" 23 | 24 | def __init__(self, op, output, inputs): 25 | self.op = op 26 | # TODO: allow for multiple outputs 27 | self.output = output # Includes output variables 28 | self.inputs = inputs # Includes input variables 29 | self.mul_index = None 30 | self.index = None 31 | 32 | def __str__(self): 33 | inputs = str([i.name if isinstance(i, CircuitVar) else i for i in self.inputs]) 34 | output = str( 35 | self.output.name if isinstance(self.output, CircuitVar) else self.output 36 | ) 37 | return output + " <- " + str(self.op) + "(" + inputs + ")" 38 | 39 | 40 | class Circuit: 41 | def __init__(self): 42 | self.gates = [] 43 | self.gate_ct = 0 44 | self.input_ct = 0 # Count input variables 45 | self.output_ct = 0 # Count output variables 46 | self.add_ct = 0 # Count add gates 47 | self.mul_ct = 0 # Count mul gates 48 | self.scalar_mul_ct = 0 # Count scalar_mul gates 49 | self._dummy_ct = 0 # Count dummy gates 50 | self.input_gates = [] # Track indices 51 | self.output_gates = [] # Track indices 52 | self.circuitvars = [] 53 | 54 | def add_gate(self, gate): 55 | """Add gate to circuit.""" 56 | self.gates.append(gate) 57 | self.gate_ct += 1 58 | gate.index = self.gate_ct - 1 59 | 60 | # Add gate.index to input CircuitVars. Add CircuitVar to self.circuitvars. (If not present in list.) 61 | for i in [0, 1]: 62 | if isinstance(gate.inputs[i], CircuitVar): 63 | if gate.index not in gate.inputs[i].gates: 64 | gate.inputs[i].gates.append(gate.index) 65 | 66 | # If input-wire CircuitVars are circuit inputs, then append to circuit.input_gates list 67 | if ( 68 | isinstance(gate.inputs[0], CircuitVar) 69 | and gate.inputs[0].input_index is not None 70 | ): 71 | self.input_gates.append(gate.index) 72 | elif ( 73 | isinstance(gate.inputs[1], CircuitVar) 74 | and gate.inputs[1].input_index is not None 75 | ): 76 | self.input_gates.append(gate.index) 77 | 78 | # Increment add, mul, scalar_mul counters and give gate a mul_index if needed. 79 | if gate.op == op.add: 80 | self.add_ct += 1 81 | elif gate.op == op.mul: 82 | assert isinstance(gate.inputs[0], CircuitVar) and isinstance( 83 | gate.inputs[1], CircuitVar 84 | ) 85 | self.mul_ct += 1 86 | # Give each mul-gate an index 87 | gate.mul_index = self.mul_ct - 1 # index := count-1 88 | elif gate.op == op.scalar_mul: 89 | self.scalar_mul_ct += 1 90 | else: 91 | raise NotImplementedError 92 | 93 | def name_dummy(self): 94 | """Give new circuit variable a dummy name.""" 95 | name = "dummy_" + str(self._dummy_ct) 96 | self._dummy_ct += 1 97 | return name 98 | 99 | # TODO: change to property 100 | def parents(self, gate): 101 | """Returns parent gates of gate.""" 102 | parents = [ 103 | g 104 | for g in self.gates 105 | if gate.output.name 106 | in [v.name for v in g.inputs if isinstance(v, CircuitVar)] 107 | ] 108 | return parents 109 | 110 | # TODO: change to property 111 | def children(self, gate): 112 | """Returns children gates of gate.""" 113 | gate_inputs = [v.name for v in gate.inputs if isinstance(v, CircuitVar)] 114 | children = [g for g in self.gates if g.output.name in gate_inputs] 115 | return children 116 | 117 | # TODO: change to property 118 | def mul_gates(self): 119 | return [g for g in self.gates if g.op == op.mul] 120 | 121 | def out_gates(self): 122 | """Returns output gates (not indices of output gates).""" 123 | return [self.gates[ix] for ix in self.output_gates] 124 | 125 | def in_gates(self): 126 | """Returns input gates (not indices of input gates).""" 127 | return [self.gates[ix] for ix in self.input_gates] 128 | 129 | def initial_inputs(self): 130 | # TODO: Ensure that list is always ordered by .input_index 131 | return [v.value for v in self.circuitvars if v.input_index != None] 132 | 133 | def multiplication_triples(self, inputs): 134 | """Return left, right and output wire values for all mul-gates.""" 135 | left_wire_forms = [ 136 | construct_affine_form(g, self, wire=0) 137 | for i, g in enumerate(self.mul_gates()) 138 | ] 139 | right_wire_forms = [ 140 | construct_affine_form(g, self, wire=1) 141 | for i, g in enumerate(self.mul_gates()) 142 | ] 143 | wire_forms = zip(left_wire_forms, right_wire_forms) 144 | alpha = [0] * self.mul_ct 145 | beta = [0] * self.mul_ct 146 | gamma = [0] * self.mul_ct 147 | for i, (left, right) in enumerate(wire_forms): 148 | alpha[i] = left(inputs + gamma) 149 | beta[i] = right(inputs + gamma) 150 | gamma[i] = alpha[i] * beta[i] 151 | return alpha, beta, gamma 152 | 153 | def eval(self, inputs, gate): 154 | """Evaluate circuit up to given gate.""" 155 | # inputs = inputs[:-1*self.input_padding] # If we applied padding to the input vector, remove it. 156 | _, _, gamma = self.multiplication_triples(inputs) 157 | form_gate_l = construct_affine_form(gate, self, wire=0) 158 | form_gate_r = construct_affine_form(gate, self, wire=1) 159 | left = form_gate_l(inputs + gamma) 160 | right = form_gate_r(inputs + gamma) 161 | if gate.op == op.add: 162 | y = left + right 163 | elif gate.op in [op.mul, op.scalar_mul]: 164 | y = left * right 165 | else: 166 | raise ValueError 167 | return y 168 | 169 | def __call__(self, inputs): 170 | """Evaluate circuit for all output gates.""" 171 | y = [self.eval(inputs, self.gates[gate_ix]) for gate_ix in self.output_gates] 172 | return y 173 | 174 | def __str__(self): 175 | return print_circuit(self) 176 | 177 | 178 | class CircuitVar: 179 | """Wrap type with methods, attributes that record circuit attributes (variables, gates). 180 | 181 | Inputs: 182 | value input instance 183 | circuit Circuit instance 184 | name name of variable 185 | input_var flag to indicate input variable (for circuit.input_ct counter) 186 | output_var flag to indicate output variable (for circuit.output_ct counter) 187 | 188 | """ 189 | # TODO/NOTE: All gadgets are work in progress, incomplete 190 | 191 | def __init__(self, value, circuit, name=None, input_var=True): 192 | self.value = value 193 | self.circuit = circuit 194 | self.name = name 195 | self.input_index = None # if index != None, then self is an input 196 | self.output_index = None # if index != None, then self is an output 197 | self.gates = [] # Track indices of gates that use given CircuitVar 198 | 199 | # Track input variables, index them 200 | if input_var: 201 | circuit.input_ct += 1 202 | self.input_index = circuit.input_ct - 1 # index := count-1 203 | self.name += "_input_" + str(self.input_index) 204 | 205 | circuit.circuitvars.append(self) 206 | 207 | def label_output(a, name): 208 | # Allow user to index variable as output after instantiation. 209 | if a.output_index == None: 210 | a.circuit.output_ct += 1 211 | a.output_index = a.circuit.output_ct - 1 # index := count-1 212 | if name: 213 | a.name = name + "_output_" + str(a.output_index) 214 | else: 215 | a.name = a.name + "_output_" + str(a.output_index) 216 | 217 | # Add gates that include a to circuit.output_gates 218 | output_gates = [g.index for g in a.circuit.gates if g.output is a] 219 | a.circuit.output_gates.extend(output_gates) 220 | 221 | def __add__(self, right): 222 | if isinstance(right, CircuitVar): 223 | value = self.value + right.value 224 | elif isinstance(right, (int, FiniteFieldElement)): 225 | value = self.value + right 226 | else: 227 | raise NotImplementedError 228 | out = type(self)( 229 | value, self.circuit, name=self.circuit.name_dummy(), input_var=False 230 | ) 231 | g = Gate(op.add, out, [self, right]) 232 | self.circuit.add_gate(g) 233 | return out 234 | 235 | def __radd__(self, right): 236 | return self + right 237 | 238 | def __sub__(self, right): 239 | return self + (-1 * right) 240 | 241 | def __rsub__(self, right): 242 | return (-1 * self) + right 243 | 244 | def __mul__(self, right): 245 | if isinstance(right, CircuitVar): 246 | value = self.value * right.value 247 | elif isinstance(right, (int, FiniteFieldElement)): 248 | value = self.value * right 249 | else: 250 | raise NotImplementedError 251 | 252 | out = type(self)( 253 | value, self.circuit, name=self.circuit.name_dummy(), input_var=False 254 | ) 255 | if isinstance(right, CircuitVar): 256 | g = Gate(op.mul, out, [self, right]) 257 | elif isinstance(right, (int, FiniteFieldElement)): 258 | g = Gate(op.scalar_mul, out, [self, right]) 259 | else: 260 | raise NotImplementedError 261 | self.circuit.add_gate(g) 262 | return out 263 | 264 | def __rmul__(self, right): 265 | return self * right 266 | 267 | def check_not_zero(self): 268 | """Gadget that implements b = (a != 0) ? 1 : 0. 269 | 270 | Computes witnesses, required new gates and output. 271 | """ 272 | # TODO: Correct/complete gadget, review 273 | # TODO: Consider replacing circuit proof with a specialized Sigma-proof 274 | a = self.value 275 | 276 | if isinstance(a, (FiniteFieldElement, SecureFiniteField, SecureInteger)): 277 | b = mpc.if_else(a == 0, 0, 1) 278 | # We can use the fact that we are working in a field (implicitly for ints) 279 | c = (a + (1 - b)) ** (-1) 280 | cv_c = type(self)(c, self.circuit, name="witness_{" + self.name + "!=0}", input_var=True) 281 | # Expand circuit with gates for equations a · c = b, a · (1 − b) = 0 282 | cv_b = self * cv_c 283 | cv_d = self * (1 - cv_b) 284 | cv_d.label_output("witness_{" + self.name + "!=0}") 285 | elif isinstance(a, int): 286 | # Create witness c, which is the two's complement bit decomposition of a 287 | c = twos_complement(a, a.bit_length()+1) 288 | cv_c = [ 289 | type(self)(c_i, self.circuit, name="witness_{" + self.name + "!=0}", input_var=True) 290 | for c_i in c 291 | ] 292 | # Create output gate s.t. inverse of twos-complement(witness) - a = 0 293 | # Proves that witness corresponds to two's complement notation of a 294 | cv_a = -1*cv_c[-1]*2**(len(cv_c)-1) + sum(cv_c_i * 2 ** i for i, cv_c_i in enumerate(cv_c[:-1])) 295 | cv_d = cv_a - self 296 | cv_d.label_output("witness_{" + self.name + "!=0}") 297 | 298 | # Compute output, based on the new witness, using mini-gadget for __or__ 299 | cv_b = mpctools.reduce(type(cv_c[0]).__or__, cv_c) 300 | else: 301 | raise NotImplementedError 302 | 303 | return cv_b 304 | 305 | def __ne__(self, other): 306 | return (self - other).check_not_zero() 307 | 308 | def __eq__(self, other): 309 | return (self - other).check_not_zero()*-1 + 1 # TODO rewrite to 1 - (self.. and test 310 | 311 | def check_bit_decomp_positive(self, bit_length): 312 | """Gadget that checks if self has a bit-decomposition of bit_length bits. 313 | 314 | Very similar to check if self >= 0. 315 | Computes witnesses, required new gates and output. 316 | """ 317 | # Calculate witnesses 318 | a = self.value 319 | assert isinstance(a, (int, SecureInteger)) 320 | if isinstance(a, SecureInteger): 321 | c = mpc.to_bits(a, l = bit_length) 322 | elif isinstance(a, int): 323 | # TODO: review edge cases 324 | c = twos_complement(a, bit_length + 1) 325 | else: 326 | raise NotImplementedError 327 | cv_c = [ 328 | type(self)(c_i, self.circuit, name="witness_{" + self.name + ">=0}", input_var=True) 329 | for c_i in c[:bit_length - 1] # Only take first l (bit_length) bits. 330 | ] 331 | 332 | # Expand circuit 333 | # Proves that a = sum_{i=0 to l-1} c_i * 2^i (in twos complement) 334 | # and c_i * c_i - c_i = 0 (public output) 335 | cv_a = sum(cv_c_i * 2 ** i for i, cv_c_i in enumerate(cv_c)) 336 | e = [cv_c_i * cv_c_i - cv_c_i for cv_c_i in cv_c] 337 | [e_i.label_output("witness_{" + self.name + ">=0}") for e_i in e] 338 | 339 | # Compute output, using gadget for == 340 | cv_b = self == cv_a 341 | return cv_b 342 | 343 | def check_ge_zero(self): 344 | """Gadget that implements b = (a >= 0) ? 1 : 0 using check_bit_decomp_positive. 345 | 346 | Computes output, witnesses and required gates. 347 | """ 348 | a = self.value 349 | if isinstance(a, SecureInteger): 350 | bit_length = a.bit_length 351 | elif isinstance(a, int): 352 | bit_length = a.bit_length() 353 | else: 354 | raise NotImplementedError 355 | return self.check_bit_decomp_positive(bit_length) 356 | 357 | def __le__(self, other): 358 | # TODO: check if input(s) are in range 359 | return (other - self).check_ge_zero() 360 | 361 | def __lt__(self, other): 362 | return (other - self - 1).check_ge_zero() 363 | 364 | def __gt__(self, other): 365 | return (self - other - 1).check_ge_zero() 366 | 367 | def __ge__(self, other): 368 | return (self - other).check_ge_zero() 369 | 370 | def __str__(self): 371 | return str(self.value) 372 | 373 | def __repr__(self): 374 | return self.name + "{" + str(self.value) + "}" 375 | 376 | def __pow__(self, other, mod=None): # TODO: add or remove mod argument? 377 | """Exponentiation with public integral power p>=0.""" 378 | if other<0: raise ValueError("Exponent cannot be negative: ", other) 379 | if other==0: return 1 380 | if other==1: return self 381 | return self*pow(self, other-1) 382 | 383 | def __and__(self, other): 384 | # Assumes input is 0 or 1, i.e. inputs are not verified for correctness. 385 | # Typical scenario where this is valid: when inputs are witnesses to a verifiable comparison. 386 | # TODO: Consider adding flag to test if self is 0 or 1. 387 | return self * other 388 | 389 | def __or__(self, other): 390 | # Assumes input is 0 or 1. (See also __and__) 391 | # TODO: Consider adding flag to test if self is 0 or 1. 392 | return 1 - (1-self) * (1-other) 393 | 394 | 395 | 396 | def twos_complement(value, bit_length): 397 | # return bin(value & (2 ** bit_length - 1)) 398 | x = bin(value & (2 ** bit_length - 1)) 399 | x = x[2:] 400 | return ([0]*(bit_length - len(x)) + [int(d) for d in x])[::-1] 401 | 402 | 403 | def print_circuit(circuit): 404 | ret = "" 405 | for gate in circuit.out_gates(): 406 | ret += print_out_gate(circuit, gate) 407 | return ret 408 | 409 | 410 | def print_out_gate(circuit, gate, level=0): 411 | ret = "\t" * level + str(gate) + "\n" 412 | for child in circuit.children(gate): 413 | ret += print_out_gate(circuit, child, level + 1) 414 | return ret 415 | 416 | 417 | def construct_affine_form(gate, circuit, wire=None): 418 | """Construct affine form for left (resp. right) wire of gate. 419 | 420 | Inputs: 421 | gate Gate instance indicating which mul-gate 422 | circuit Circuit instance indicating circuit to traverse 423 | wire 0 indicates left, 1 indicates right wire, None indicates all 424 | 425 | Output: 426 | AffireForm of length circuit.input_ct + circuit.mul_ct 427 | Note: AC20 requires AffineForm of length input_ct + 3 + 2*mul_ct. Convert later. 428 | """ 429 | 430 | def construct_for_wire(gate, circuit, wire): 431 | """Update given affine form for left or right input wire.""" 432 | assert wire in [0, 1] # wire is either left or right 433 | 434 | # Start with zero 435 | ret = AffineForm([0] * circuit.input_ct + [0] * circuit.mul_ct, 0) 436 | 437 | # If input is a constant (not a Circuit_Var) 438 | if not isinstance(gate.inputs[wire], CircuitVar): 439 | ret.constant += gate.inputs[wire] 440 | # If input is Circuit_Var 441 | else: 442 | # If input is circuit input 443 | if gate.inputs[wire].input_index is not None: 444 | ret.coeffs[gate.inputs[wire].input_index] += 1 445 | # Input is mul-gate or add-gate 446 | else: 447 | gate_input = gate.inputs[wire].name 448 | child_gate = [g for g in circuit.gates if g.output.name == gate_input][0] 449 | # If input is mul-gate 450 | if child_gate.op == op.mul: 451 | ret.coeffs[circuit.input_ct + child_gate.mul_index] += 1 452 | # If input is add-gate 453 | elif child_gate.op == op.add: 454 | ret = construct_affine_form(child_gate, circuit, wire=None) 455 | # If input is scalar_mul-gate 456 | elif child_gate.op == op.scalar_mul: 457 | ret = construct_affine_form(child_gate, circuit, wire=None) 458 | else: 459 | # All valid options are tested at this point. 460 | raise ValueError 461 | return ret 462 | 463 | # Start with all-zero affine form, also when called from construct_for_wire 464 | ret = AffineForm([0] * circuit.input_ct + [0] * circuit.mul_ct, 0) 465 | 466 | # Input is mul-gate and designation to traverse left or right wire 467 | if wire is not None: 468 | # Construct affine form for specified input wire (pass all-zero affine form) 469 | ret = construct_for_wire(gate, circuit, wire) 470 | # wire == None; we are in the recursion and need to traverse left and right wires, or we have passed an output wire 471 | elif wire == None: 472 | if gate.op == op.add: 473 | for _wire in [0, 1]: 474 | ret += construct_for_wire(gate, circuit, _wire) 475 | elif gate.op == op.scalar_mul: 476 | # left wire is CircuitVar, then right wire should be scalar 477 | if isinstance(gate.inputs[0], CircuitVar): 478 | ret = construct_for_wire(gate, circuit, 0) 479 | ret *= gate.inputs[1] 480 | # right wire is CircuitVar, then left wire should be scalar 481 | elif isinstance(gate.inputs[1], CircuitVar): 482 | ret = construct_for_wire(gate, circuit, 1) 483 | ret *= gate.inputs[0] 484 | # no input wires connect to a CircuitVar. Hence, both are scalars 485 | else: 486 | s = gate.inputs[0] * gate.inputs[1] 487 | ret.constant = s 488 | elif gate.op == op.mul: 489 | # If we passed an output gate that is a mul-gate, then set coefficient corresponding to mul-gate to 1. 490 | assert gate.output.output_index != None 491 | ret.coeffs[circuit.input_ct + gate.mul_index] = 1 492 | # Mul-gate is not possible in the recursion: construct_for_wire cannot call construct_affine_form 493 | # for mul-gate, it should return an affine form with increased mul-index 494 | else: 495 | raise ValueError 496 | else: 497 | raise ValueError 498 | return ret 499 | 500 | 501 | def convert_to_ac20(form, circuit): 502 | """Include 0 coefficients for f(0), g(0), h(0) and h(i), i = m+1, ..., 2m. 503 | 504 | Ensures that input vector to form() corresponds to z-vector in AC20 of length n + 3 + 2m. 505 | """ 506 | newform = AffineForm( 507 | form.coeffs[: circuit.input_ct] 508 | + [0] * 3 509 | + form.coeffs[circuit.input_ct :] 510 | + [0] * circuit.mul_ct, 511 | form.constant, 512 | ) 513 | assert len(newform.coeffs) == circuit.input_ct + 3 + 2 * circuit.mul_ct 514 | return newform 515 | 516 | 517 | def calculate_fg_form(circuit, wire, challenge, gf): 518 | # Construct form for each mul-gate. 519 | forms = [construct_affine_form(g, circuit, wire) for g in circuit.mul_gates()] 520 | # Assert that form is consistent with length of z-vector. 521 | forms = [convert_to_ac20(f, circuit) for f in forms] 522 | 523 | lagr_range = range(circuit.mul_ct + 1) 524 | lagr_vect = lagrange(gf, lagr_range, challenge) 525 | 526 | # Construct form corresponding to poly evaluation at c, in coefficients of z-vector 527 | form = AffineForm([0] * circuit.input_ct + [0, 0, 0] + [0] * 2 * circuit.mul_ct, 0) 528 | form.coeffs[circuit.input_ct + wire] = 1 * lagr_vect[0] 529 | form += sum(forms[j] * l_j for j, l_j in enumerate(lagr_vect[1:])) 530 | return form 531 | 532 | 533 | def calculate_h_form(circuit, challenge, gf): 534 | lagr_range = range(2 * circuit.mul_ct + 1) 535 | lagr_vect = lagrange(gf, lagr_range, challenge) 536 | form = LinearForm([0] * circuit.input_ct + [0] * 2 + lagr_vect) 537 | return form 538 | 539 | 540 | def calculate_circuit_forms(circuit): 541 | forms = [ 542 | construct_affine_form(circuit.gates[gate_ix], circuit, None) 543 | for gate_ix in circuit.output_gates 544 | ] 545 | return forms 546 | 547 | 548 | def lagrange(gf, lagr_range, c): 549 | return verifiable_mpc.ac20.mpc_ac20._recombination_vectors(gf, lagr_range, (c,))[0] 550 | 551 | 552 | # TODOs: 553 | # 2. Add __sub__ gates (instead of -1*var + ...) 554 | # 2. Name auxiliary inputs/witnesses? Witnesses and inputs are treated equally. However, witness can be created after inputs, e.g. for zero-test. Should we treat them separately? 555 | # 3. circuit.output_gates list and circuit.out_gates() method: name consistently and unambiguously 556 | 557 | # Notes 558 | # https://stackoverflow.com/questions/2673651/inheritance-from-str-or-int 559 | # https://stackoverflow.com/questions/20242479/printing-a-tree-data-structure-in-python 560 | # https://github.com/egonSchiele/grokking_algorithms/blob/master/06_breadth-first_search/python/01_breadth-first_search.py 561 | --------------------------------------------------------------------------------