├── .github └── workflows │ └── main.yml ├── .gitignore ├── Makefile ├── README.md ├── devtests ├── template.py └── test_canonicalize_members.py ├── docs ├── canonicalization.md ├── examples.md ├── images │ ├── DoubleOTP_is_ROR.png │ ├── KEMfromPKE_is_INDCPA.png │ ├── PKEfromKEM_is_INDCPA.png │ ├── SymEnc_CPADollar_is_INDCPA.png │ ├── nestedPKE_is_INDCPA_proof1.png │ ├── nestedPKE_is_INDCPA_proof2.png │ └── parallelPKE_is_INDCPA.png ├── inlining.md └── primitives.md ├── examples ├── DoubleOTP │ ├── DoubleOTP.py │ └── DoubleOTP_is_ROR.py ├── KEMfromPKE │ ├── KEMfromPKE.py │ └── KEMfromPKE_is_INDCPA.py ├── NestedPKE.ipynb ├── PKEfromKEM │ ├── PKEfromKEM.py │ └── PKEfromKEM_is_INDCPA.py ├── SymEnc_CPADollar │ ├── SE.py │ └── SymEnc_CPADollar_is_INDCPA.py ├── nestedPKE │ ├── nestedPKE.py │ └── nestedPKE_is_INDCPA.py └── parallelPKE │ ├── parallelPKE.py │ └── parallelPKE_is_INDCPA.py ├── gamehop ├── __init__.py ├── bits.py ├── filterast.py ├── format.py ├── inlining │ ├── __init__.py │ └── internal.py ├── lists.py ├── node_graph.py ├── node_traverser.py ├── primitives │ ├── Crypto.py │ ├── KDF.py │ ├── KEM.py │ ├── OTP.py │ ├── PKE.py │ ├── SymEnc.py │ └── __init__.py ├── proofs.py ├── proofs2.py ├── scope.py ├── templates │ └── tikz_figure.tex ├── utils.py └── verification │ ├── __init__.py │ └── canonicalization │ ├── __init__.py │ ├── classes.py │ ├── expand.py │ ├── ifstatements.py │ └── simplify.py ├── requirements.txt └── tests ├── examples ├── test_definitions.py └── test_proofs.py ├── gamehop ├── inlining │ ├── test_dereference_attribute.py │ ├── test_find_all_variables.py │ ├── test_inline_all_static_methods.py │ ├── test_inline_argument_into_function.py │ ├── test_inline_function_call.py │ ├── test_inline_nonstatic_methods.py │ ├── test_inline_reduction_into_game.py │ ├── test_inline_reduction_into_game_with_oracles.py │ ├── test_inline_scheme_into_game.py │ └── test_rename_variables.py ├── test_depends_assigns.py ├── test_filter_AST.py ├── test_lists.py ├── test_node_graph.py ├── test_node_traverser.py ├── test_utils.py └── verification │ └── canonicalization │ ├── expand │ ├── test_call_arguments.py │ ├── test_expanders.py │ └── test_ifexp_arguments.py │ ├── simplify │ ├── test_binary_operators.py │ ├── test_boolean_operators.py │ ├── test_compare_operators.py │ ├── test_ifexp.py │ └── test_unary_operators.py │ ├── test_argument_order.py │ ├── test_canonicalize.py │ ├── test_canonicalize_game.py │ ├── test_collapse_assigns.py │ ├── test_function_name.py │ ├── test_ifstatements.py │ ├── test_inline_lambdas.py │ ├── test_line_order.py │ ├── test_return.py │ ├── test_unnecessary_members.py │ └── test_variable_names.py └── test_template.py /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Python package 5 | 6 | on: 7 | push: 8 | pull_request: 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | python-version: ["3.10.7"] 16 | steps: 17 | - uses: actions/checkout@v3 18 | - name: Set up Python ${{ matrix.python-version }} 19 | uses: actions/setup-python@v3 20 | with: 21 | python-version: ${{ matrix.python-version }} 22 | - name: Cache pip install 23 | uses: actions/cache@v3 24 | id: cache-venv 25 | with: 26 | path: ./.venv/ 27 | key: ${{ runner.os }}-${{ matrix.python-version}}-venv-1a-${{ hashFiles('requirements.txt') }} 28 | restore-keys: ${{ runner.os }}-${{ matrix.python-version}}-venv-1a- 29 | - name: Build virtual environment with dependencies 30 | if: steps.cache-venv.outputs.cache-hit != 'true' 31 | run: | 32 | mkdir -p ./.venv 33 | python -m venv ./.venv 34 | . ./.venv/bin/activate 35 | python -m pip install --upgrade pip 36 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 37 | # - name: Run typechecker on library 38 | # run: | 39 | # . ./.venv/bin/activate 40 | # make typecheck_library 41 | - name: Run library unit tests 42 | run: | 43 | . ./.venv/bin/activate 44 | env PYTHONPATH=. make unittest_library 45 | - name: Run examples 46 | run: | 47 | . ./.venv/bin/activate 48 | env PYTHONPATH=. make test_examples 49 | -------------------------------------------------------------------------------- /.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 | 131 | .tags* 132 | 133 | examples/*/*.aux 134 | examples/*/*.log 135 | examples/*/*.pdf 136 | examples/*/*.tex 137 | .texpadtmp 138 | 139 | .vscode/ -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | MYPY ?= 'mypy' 2 | PYTEST ?= 'pytest' 3 | PYTHON ?= 'python3.9' 4 | all: 5 | 6 | 7 | # eventually just replace this with 8 | # env PYTHONPATH=. pytest -v --mypy 9 | test: typecheck_library unittest_library typecheck_examples test_examples 10 | 11 | typecheck_library: 12 | $(MYPY) --ignore-missing-imports -p gamehop.inlining 13 | $(MYPY) --ignore-missing-imports -p gamehop.verification 14 | $(MYPY) --ignore-missing-imports -p gamehop.primitives 15 | $(MYPY) --ignore-missing-imports -p gamehop 16 | 17 | unittest_library: 18 | env PYTHONPATH=. $(PYTEST) -v tests/gamehop 19 | 20 | typecheck_examples: 21 | $(MYPY) examples/nestedPKE/nestedPKE.py 22 | $(MYPY) examples/parallelPKE/parallelPKE.py 23 | $(MYPY) examples/KEMfromPKE/KEMfromPKE.py 24 | $(MYPY) examples/PKEfromKEM/PKEfromKEM.py 25 | $(MYPY) examples/SymEnc_CPADollar/SE.py 26 | $(MYPY) examples/DoubleOTP/DoubleOTP.py 27 | 28 | test_examples: 29 | env PYTHONPATH=. $(PYTEST) -v tests/examples 30 | 31 | devtest: 32 | env PYTHONPATH=. $(PYTEST) -v devtests/* 33 | 34 | example_figures: 35 | cd examples/PKEfromKEM && pdflatex PKEfromKEM_is_INDCPA.tex 36 | convert -density 144 examples/PKEfromKEM/PKEfromKEM_is_INDCPA.pdf docs/images/PKEfromKEM_is_INDCPA.png 37 | cd examples/KEMfromPKE && pdflatex KEMfromPKE_is_INDCPA.tex 38 | convert -density 144 examples/KEMfromPKE/KEMfromPKE_is_INDCPA.pdf docs/images/KEMfromPKE_is_INDCPA.png 39 | cd examples/parallelPKE && pdflatex parallelPKE_is_INDCPA.tex 40 | convert -density 144 examples/parallelPKE/parallelPKE_is_INDCPA.pdf docs/images/parallelPKE_is_INDCPA.png 41 | cd examples/nestedPKE && pdflatex nestedPKE_is_INDCPA_proof1.tex 42 | cd examples/nestedPKE && pdflatex nestedPKE_is_INDCPA_proof2.tex 43 | convert -density 144 examples/nestedPKE/nestedPKE_is_INDCPA_proof1.pdf docs/images/nestedPKE_is_INDCPA_proof1.png 44 | convert -density 144 examples/nestedPKE/nestedPKE_is_INDCPA_proof2.pdf docs/images/nestedPKE_is_INDCPA_proof2.png 45 | cd examples/SymEnc_CPADollar && pdflatex SymEnc_CPADollar_is_INDCPA.tex 46 | convert -density 144 examples/SymEnc_CPADollar/SymEnc_CPADollar_is_INDCPA.pdf docs/images/SymEnc_CPADollar_is_INDCPA.png 47 | cd examples/DoubleOTP && pdflatex DoubleOTP_is_ROR.tex 48 | convert -density 144 examples/DoubleOTP/DoubleOTP_is_ROR.pdf docs/images/DoubleOTP_is_ROR.png 49 | rm -f examples/*/*.aux 50 | rm -f examples/*/*.log 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pygamehop 2 | 3 | pygamehop is a work-in-progress tool aimed to support cryptographers writing game hopping proofs. The main goal of pygamehop is to allow a cryptographer to specify a cryptographic scheme and security property in a pseudocode-like subset of Python, encode the steps of a game-hopping proof (including reductions), and have the tool partially or fully check the sequence of games. 4 | 5 | Created by Matthew McKague (Queensland University of Technology) and Douglas Stebila (University of Waterloo). 6 | 7 | ## Current status 8 | 9 | This repository is very much a work-in-progress, and is not intended for general use, but may be of interest to people wondering about the project. 10 | 11 | ## Getting started 12 | 13 | pygamehop requires Python 3.9 or higher. Clone the repository, install the requirements, and set an environment variable for the working directory: 14 | 15 | git clone git@github.com:dstebila/pygamehop 16 | cd pygamehop 17 | python3 --version 18 | # should be 3.9 or higher 19 | pip3 install -r requirements.txt 20 | PYTHONPATH=`pwd`; export PYTHONPATH 21 | 22 | Now you can run an example. There are 5 working examples at the moment: 23 | 24 | - **examples/KEMfromPKE**: A key encapsulation mechanism (KEM) built from a public key encryption scheme; including a proof that, if the PKE is IND-CPA-secure, then the KEM is IND-CPA-secure. 25 | - **examples/PKEfromKEM**: A public key encryption scheme built from a KEM; including a proof that, if the KEM is IND-CPA-secure, then the PKE is IND-CPA-secure. 26 | - **examples/parallelPKE**: A public key encryption scheme built by running two PKEs in parallel; including a proof that the parallel PKE is IND-CPA-secure if both contributing PKEs are IND-CPA-secure. 27 | - **examples/nestedPKE**: A public key encryption scheme built by running two PKEs one after the other; including two proofs that the nested PKE is IND-CPA-secure if either contributing PKE is IND-CPA-secure. 28 | - **examples/SymEnc_CPADollar**: A proof that a symmetric encryption scheme that is indistinguishable from random under chosen plaintext attacks (IND$-CPA) is IND-CPA-secure. 29 | 30 | A good starting example is examples/KEMfromPKE/KEMfromPKE_is_INDCPA.py. 31 | 32 | - First, take a look (in an editor) at `gamehop/primitives/KEM.py` and `gamehop/primitives/PKE.py`, which show the API definition and IND-CPA security properties for KEMs and PKEs. 33 | - Next, look (in an editor) at `examples/KEMfromPKE/KEMfromPKE.py`, which is a generic construction of a KEM from a PKE scheme. 34 | - Finally, we move on to the proof in `examples/KEMfromPKE/KEMfromPKE_is_INDCPA.py`. 35 | - Open up the file in an editor and you'll see a 3-step game hopping proof. 36 | - The central hop of the proof involves a reduction to the IND-CPA security property of the PKE; the reduction is explicitly given in the file. 37 | - There are also two rewriting hops that encode a fact about length that is not known to the proof engine, and must be checked manually. 38 | - You can run the proof by typing `python3 examples/KEMfromPKE/KEMfromPKE_is_INDCPA.py`. How much detail is printed can be configured in the `proof.check` line inside the file, but the default at the moment prints out every game hop, along with the canonicalization of every game, and the diffs between the games. 39 | - A visualization of the game hops is auto-generated and can be found in `docs/images/KEMfromPKE_is_INDCPA.png`, also shown below: 40 | 41 | 42 | 43 | ## Documentation 44 | 45 | You can see more explanation of the examples in `docs/examples.md`. Also in the `docs` folder are notes about the primitives available so far, and the inlining and canonicalization procedures. Unfortunately this documentation is somewhat out-of-date at the moment; the main ideas are still there, but some of the details have changed. 46 | 47 | ## Limitations 48 | 49 | This is highly incomplete work and has many flaws. At this point it's trying to be a proof of concept. 50 | 51 | ## Contact us 52 | 53 | If you're interested in this project, email us at matthew.mckague@qut.edu.au and dstebila@uwaterloo.ca to discuss next steps. There's a lot of work to be done before this is ready for widespread use, but we'd love some help! 54 | -------------------------------------------------------------------------------- /devtests/template.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import inspect 3 | import unittest 4 | 5 | import gamehop.inlining.internal 6 | 7 | f1 = "def f(x): return x" 8 | 9 | f2 = "def f(x): return x" 10 | 11 | class TestSomething(unittest.TestCase): 12 | def test_sometest(self): 13 | self.assertEqual(f1, f2) 14 | -------------------------------------------------------------------------------- /devtests/test_canonicalize_members.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import unittest 3 | 4 | import gamehop.inlining.internal 5 | import gamehop.verification.canonicalization 6 | 7 | def expected_result(c): 8 | cdef = gamehop.utils.get_class_def(c) 9 | cdef.name = 'G' 10 | return ast.unparse(cdef) 11 | 12 | class TestCanonicalizeMembers(unittest.TestCase): 13 | 14 | def test_keep_members_that_are_used(self): 15 | class C: 16 | def f(self): 17 | self.a = 1 18 | self.b = 2 19 | return True 20 | def g(self): 21 | return self.a + self.b 22 | class C_expected_result: 23 | def f(v0): 24 | v0.a = 1 25 | v0.b = 2 26 | return True 27 | def g(v0): 28 | v1 = v0.a + v0.b 29 | return v1 30 | c = gamehop.utils.get_class_def(C) 31 | s = gamehop.verification.canonicalize_game(c) 32 | self.assertEqual(s, expected_result(C_expected_result)) 33 | 34 | def test_remove_unused_members(self): 35 | class C: 36 | def f(self): 37 | self.a = 1 38 | b = self.c() 39 | return b 40 | def g(self): return True 41 | class C_expected_result: 42 | def f(v0): 43 | v1 = v0.c() 44 | return v1 45 | def g(v0): return True 46 | c = gamehop.utils.get_class_def(C) 47 | s = gamehop.verification.canonicalize_game(c) 48 | self.assertEqual(s, expected_result(C_expected_result)) 49 | 50 | def test_doesnt_need_to_be_a_member(self): 51 | class C: 52 | def f(self): 53 | self.a = list(1) 54 | b = self.c() 55 | r = b + self.a 56 | return r 57 | def g(self): return True 58 | class C_expected_result: 59 | def f(v0): 60 | v1 = list(1) 61 | v2 = v0.c() 62 | v3 = v2 + v1 63 | return v3 64 | def g(v0): return True 65 | c = gamehop.utils.get_class_def(C) 66 | s = gamehop.verification.canonicalize_game(c) 67 | self.assertEqual(s, expected_result(C_expected_result)) 68 | 69 | def test_keep_members_that_are_used_single_function(self): 70 | class C: 71 | def f(self): 72 | self.a = 1 73 | b = self.a 74 | c = list(b) 75 | return c 76 | class C_expected_result: 77 | def f(v0): 78 | v1 = list(1) 79 | return v1 80 | c = gamehop.utils.get_class_def(C) 81 | s = gamehop.verification.canonicalize_game(c) 82 | self.assertEqual(s, expected_result(C_expected_result)) 83 | 84 | def test_remove_unused_lines_that_involve_members(self): 85 | class C: 86 | def main(self): 87 | (x, y) = g.a() 88 | u = g.b(x, x) 89 | v = g.b(x, y) 90 | return v 91 | class C_expected_result: 92 | def main(v0): 93 | (v1, v2) = g.a() 94 | v3 = g.b(v1, v2) 95 | return v3 96 | c = gamehop.utils.get_class_def(C) 97 | s = gamehop.verification.canonicalize_game(c) 98 | self.assertEqual(s, expected_result(C_expected_result)) 99 | -------------------------------------------------------------------------------- /docs/canonicalization.md: -------------------------------------------------------------------------------- 1 | *WARNING: THIS FILE IS CURRENTLY OUT OF DATE; THE MAIN IDEAS ARE STILL HERE BUT SOME DETAILS MAY HAVE CHANGED* 2 | 3 | # Canonicalization 4 | 5 | To compare two pieces of code and see if they are equivalent, pygamehop first applies a series of transformations that we call "canonicalization", with the aim that two equivalent programs should canonicalize to the same string of source code. 6 | 7 | ## List of transformations 8 | 9 | - [If statements to if expression](#if-statements-to-if-expressions) 10 | - [Expand call arguments](#expand-call-arguments) 11 | - [Canonicalize function name](#canonicalize-function-name) 12 | - [Inline lambdas](#inline-lambdas) 13 | - [Collapse assignments](#collapse-assignments) 14 | - [Simplify constant expressions](#simplify-constant-expressions) 15 | - [Canonicalize line order](#canonicalize-line-order) 16 | - [Remove irrelevant statements](#remove-irrelevant-statements) 17 | - [Canonicalize argument order](#canonicalize-argument-order) 18 | - [Canonicalize variable names](#canonicalize-variable-names) 19 | 20 | ### If statements to if expressions 21 | 22 | Converts if statements to if expressions. 23 | 24 | Example: 25 | 26 | ```python 27 | if condition: 28 | v = expression1 29 | else: 30 | v = expression2 31 | w = expression3 32 | 33 | # becomes: 34 | 35 | ifcond = condition 36 | v_if = expression1 37 | w_if = None 38 | v_else = expression2 39 | w_else = expression3 40 | v = v_if if ifcond else v_else 41 | w = w_if if ifcond else w_else 42 | ``` 43 | 44 | - Main method: `gamehop.verification.canonicalization.ifstatements.if_statements_to_expressions` 45 | 46 | ### Expand call arguments 47 | 48 | Expands arguments to function calls to be their own statements. 49 | 50 | Example: 51 | 52 | ```python 53 | a = f(g(y)) 54 | 55 | # becomes: 56 | 57 | v = g(y) 58 | a = f(v) 59 | ``` 60 | 61 | - Main method: `gamehop.verification.canonicalization.expand.call_arguments` 62 | 63 | ### Canonicalize return statement 64 | 65 | Simplify the return statement so that it consists of only a constant or a single variable. 66 | 67 | Example: 68 | 69 | ```python 70 | return x + y 71 | 72 | # becomes: 73 | 74 | z = x + y 75 | return z 76 | ``` 77 | 78 | - Main method: `gamehop.verification.canonicalization.canonicalize_return` 79 | 80 | ### Canonicalize function name 81 | 82 | Rename a function to have a fixed name. 83 | 84 | Example: 85 | 86 | ```python 87 | def myfunc(): 88 | ... 89 | 90 | # becomes: 91 | 92 | def f(): 93 | ... 94 | ``` 95 | 96 | - Main method: `gamehop.verification.canonicalization.canonicalize_function_name` 97 | 98 | ### Inline lambdas 99 | 100 | Replace all calls to lambda functions with their body. 101 | 102 | Example: 103 | 104 | ```python 105 | adder = lambda b: b + 1 106 | r = adder(c) 107 | 108 | # becomes: 109 | 110 | r = c + 1 111 | ``` 112 | 113 | - Main method: `gamehop.verification.canonicalization.inline_lambdas` 114 | 115 | ### Collapse assignments 116 | 117 | Simplify chains of assignments where possible. 118 | 119 | Example: 120 | 121 | ```python 122 | a = 7 123 | x = a 124 | b = f(x) 125 | 126 | # becomes: 127 | 128 | b = f(7) 129 | ``` 130 | 131 | Also works on tuples, such as: 132 | 133 | ```python 134 | c = (a, b) 135 | (x, y) = x 136 | return x + y 137 | 138 | # becomes: 139 | 140 | return x + b 141 | ``` 142 | 143 | - Main method: `gamehop.verification.canonicalization.collapse_useless_assigns` 144 | 145 | ### Simplify constant expressions 146 | 147 | Simplify unary, boolean, binary, and compare operators that involve constants. 148 | 149 | Example: 150 | 151 | ```python 152 | a = not(False) 153 | b = True or False 154 | c = 2 + 5 155 | d = (4 != 4) 156 | 157 | # becomes: 158 | 159 | a = True 160 | b = True 161 | c = 7 162 | d = False 163 | ``` 164 | 165 | Also works on the condition of if expressions, for example: 166 | 167 | ```python 168 | e = 1 if True else 2 169 | 170 | # becomes: 171 | 172 | e = 1 173 | ``` 174 | 175 | - Main method: `gamehop.verification.canonicalization.simplify.simplify` 176 | 177 | ### Canonicalize line order 178 | 179 | Reorder lines into a canonical order (while maintaining behaviour). 180 | 181 | Example: 182 | 183 | ```python 184 | c = h(a) 185 | d = g(c) 186 | b = g(c) 187 | return (b, c, d) 188 | 189 | # becomes: 190 | 191 | c = h(a) 192 | b = g(c) 193 | d = g(c) 194 | return (b, c, d) 195 | ``` 196 | 197 | In the example above, `c = h(a)` obviously must come before either of `b = g(c)` and `d = g(c)`, but the order of the assignments to `b` and `d` can in principle go in any order (assuming `g` is side-effect free, which we always assume). To derive a canonical order, we rely on the order in which the values appear in subsequent statements that use them. 198 | 199 | - Main method: `gamehop.verification.canonicalization.canonicalize_line_order` 200 | 201 | ### Remove irrelevant statements 202 | 203 | Remove statements that do not affect the output. 204 | 205 | Example: 206 | 207 | ```python 208 | a = 1 209 | b = 2 210 | return a 211 | 212 | # becomes: 213 | 214 | a = 1 215 | return a 216 | ``` 217 | 218 | - Main method: This is an effect of `gamehop.verification.canonicalization.canonicalize_line_order` 219 | 220 | ### Canonicalize argument order 221 | 222 | Reorder arguments to a function into a canonical order. Also remove unused arguments. 223 | 224 | Example: 225 | 226 | ```python 227 | def f(x, y, z): 228 | return y + 2 * x 229 | 230 | # becomes: 231 | 232 | def f(y, x): 233 | return y + 2 * x 234 | ``` 235 | 236 | This transformation is relevant when trying to canonicalize functions when treating the functions as a top-level object without regards to their callers, but does not necessarily make sense with inner functions where there are calls to those inner functions in other parts of the code. 237 | 238 | - Main method: `gamehop.verification.canonicalization.canonicalize_argument_order` 239 | 240 | ### Canonicalize variable names 241 | 242 | Give consistent names to the variables used in a function. 243 | 244 | Example: 245 | 246 | ```python 247 | def f(x, y): 248 | a = 7 249 | return y + a 250 | 251 | # becomes: 252 | 253 | def f(v0, v1): 254 | v2 = 7 255 | return v1 + v2 256 | ``` 257 | 258 | - Main method: `gamehop.verification.canonicalization.canonicalize_variable_names` 259 | -------------------------------------------------------------------------------- /docs/images/DoubleOTP_is_ROR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dstebila/pygamehop/6ef9a5d4fce9c38eb2617a060b13d54707c9c20e/docs/images/DoubleOTP_is_ROR.png -------------------------------------------------------------------------------- /docs/images/KEMfromPKE_is_INDCPA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dstebila/pygamehop/6ef9a5d4fce9c38eb2617a060b13d54707c9c20e/docs/images/KEMfromPKE_is_INDCPA.png -------------------------------------------------------------------------------- /docs/images/PKEfromKEM_is_INDCPA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dstebila/pygamehop/6ef9a5d4fce9c38eb2617a060b13d54707c9c20e/docs/images/PKEfromKEM_is_INDCPA.png -------------------------------------------------------------------------------- /docs/images/SymEnc_CPADollar_is_INDCPA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dstebila/pygamehop/6ef9a5d4fce9c38eb2617a060b13d54707c9c20e/docs/images/SymEnc_CPADollar_is_INDCPA.png -------------------------------------------------------------------------------- /docs/images/nestedPKE_is_INDCPA_proof1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dstebila/pygamehop/6ef9a5d4fce9c38eb2617a060b13d54707c9c20e/docs/images/nestedPKE_is_INDCPA_proof1.png -------------------------------------------------------------------------------- /docs/images/nestedPKE_is_INDCPA_proof2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dstebila/pygamehop/6ef9a5d4fce9c38eb2617a060b13d54707c9c20e/docs/images/nestedPKE_is_INDCPA_proof2.png -------------------------------------------------------------------------------- /docs/images/parallelPKE_is_INDCPA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dstebila/pygamehop/6ef9a5d4fce9c38eb2617a060b13d54707c9c20e/docs/images/parallelPKE_is_INDCPA.png -------------------------------------------------------------------------------- /docs/primitives.md: -------------------------------------------------------------------------------- 1 | *WARNING: THIS FILE IS CURRENTLY OUT OF DATE; THE MAIN IDEAS ARE STILL HERE BUT SOME DETAILS MAY HAVE CHANGED* 2 | 3 | # Cryptographic primitives and security experiments 4 | 5 | pygamehop has a variety of generic cryptographic primitives and corresponding security experiments defined. These are contained in the `gamehop/primitives` directory. 6 | 7 | ## Primitives 8 | 9 | Cryptographic primitives are defined as classes that extend the `gamehop.Crypto.Scheme` class. A primitive will typically be accompanied by classes defining associated spaces, such as key spaces or message spaces. 10 | 11 | ## Experiments 12 | 13 | Security experiments are classes that extend a subclass of the `gamehop.proofs.Experiment` class. So far, there is one such subclass: 14 | 15 | - `gamehop.proofs.DistinguishingExperiment`: A distinguishing experiment has two member functions: `main0` and `main1`, both of which take as input a scheme and adversary, and output a bit. This represents a security experiment where the adversary is interacting with one of two worlds and we measure the probability the adversary's output in the two experiments differs. 16 | 17 | Each experiment is associated to an adversary class (extending `gamehop.Crypto.Adversary`) which defines the interfaces the adversary exposes to the experiment. For example, for public key encryption this might consist of a first "challenge" phase and a second "guess" phase (see `gamehop.primitives.PKE.PKEINDCPA_adversary`). 18 | 19 | ## Common utilities 20 | 21 | Defined in: `gamehop/primitives/Crypto.py` 22 | 23 | This helper class includes: 24 | 25 | - `Crypto.Bit`: An abstract data type representing a single bit. 26 | - `Crypto.ByteString`: An abstract data type for representing byte strings. 27 | - `Crypto.UniformlySample(s)`: A function representing arbitrarily sampling from a set `s`. 28 | - `Crypto.Reject`: A rejection/failure symbol. 29 | 30 | ## List of built-in primitives and experiments 31 | 32 | - [Key derivation functions (KDFs)](#key-derivation-functions-kdfs) 33 | - [Key encapsulation mechanisms (KEMs)](#key-encapsulation-mechanisms-kems) 34 | - [One-time pad encryption (OTP)](#one-time-pad-encryption-otp) 35 | - [Public key encryption (PKE)](#public-key-encryption-pke) 36 | 37 | ### Key derivation functions (KDFs) 38 | 39 | Defined in: `gamehop/primitives/KDF.py` 40 | 41 | **Primitive.** `KDFScheme.KDF` takes as input a key, a label (represented as a string), and a length (represented as an integer), and outputs a byte string (represented as a Crypto.ByteString). 42 | 43 | **Associated spaces:** `Key`. 44 | 45 | **Security experiment.** `KDFScheme.KDFsec` is a distinguishing experiment representing indistinguishability of KDF output from random. The adversary is given access to an oracle that outputs real KDF outputs (in `main0`) or random byte strings `main1`). 46 | 47 | ### Key encapsulation mechanisms (KEMs) 48 | 49 | Defined in: `gamehop/primitives/KEM.py` 50 | 51 | **Primitive.** A `KEMScheme` consists of three algorithms: 52 | 53 | - `KEMScheme.KeyGen` outputs a public key and a secret key. 54 | - `KEMScheme.Encaps` takes as input a public key and outputs a ciphertext and a shared secret. 55 | - `KEMScheme.Decaps` takes as input a secret key and a ciphertext, and outputs a shared secret (or rejection symbol). 56 | 57 | **Associated spaces:** `PublicKey`, `SecretKey`, `Ciphertext`, `SharedSecret`. 58 | 59 | **Security experiment.** `KEMScheme.INDCPA` is a distinguishing experiment representing indistinguishability of KEM shared secrets from random under a chosen plaintext attack. The adversary is given a KEM public key and ciphertext and either the real shared secret for that ciphertext (`main0`) or a randomly chosen value (`main1`). 60 | 61 | ### One-time pad encryption (OTP) 62 | 63 | Defined in: `gamehop/primitives/OTP.py` 64 | 65 | **Primitive.** An `OTPScheme` models XORing (`^`) a message with a key. 66 | 67 | **Associated spaces:** `Message`. 68 | 69 | **Security experiment.** `OTPScheme.OTIND` is a distinguishing experiment representing semantic security of one-time pad encryption. The adversary can pick two messages and is given a ciphertext that is the one-time pad encryption of either the first message (`main0`) or the second message (`main1`) under a random key. 70 | 71 | ### Public key encryption (PKE) 72 | 73 | Defined in: `gamehop/primitives/PKE.py` 74 | 75 | **Primitive.** An `PKEScheme` consists of three algorithms: 76 | 77 | - `PKEScheme.KeyGen` outputs a public key and a secret key. 78 | - `PKEScheme.Encrypt` takes as input a public key and a message, and outputs a ciphertext. 79 | - `PKEScheme.Decrypt` takes as input a secret key and a ciphertext, and outputs a message (or rejection symbol). 80 | 81 | **Associated spaces:** `PublicKey`, `SecretKey`, `Ciphertext`, `Message`. 82 | 83 | **Security experiment.** `PKEScheme.INDCPA` is a distinguishing experiment representing semantic security of public key encryption under a chosen plaintext attack. The adversary is given a PKE public key, then can pick two messages, and is given a ciphertext that is an encryption of either the first message (`main0`) or the second message (`main1`). 84 | -------------------------------------------------------------------------------- /examples/DoubleOTP/DoubleOTP.py: -------------------------------------------------------------------------------- 1 | from typing import Generic, Optional, Sized, Tuple, TypeVar 2 | 3 | from gamehop.primitives import Crypto 4 | from gamehop.primitives.OTP import OTPScheme 5 | 6 | SK1 = TypeVar('SK1') 7 | CT1 = TypeVar('CT1', bound=Sized) 8 | PT1 = TypeVar('PT1', bound=Sized) 9 | SK2 = TypeVar('SK2') 10 | CT2 = TypeVar('CT2', bound=Sized) 11 | # Missing PT2 since PT2 will be the same as CT1 12 | 13 | InnerOTP = OTPScheme[SK1, PT1, CT1] 14 | OuterOTP = OTPScheme[SK2, CT1, CT2] 15 | 16 | class DoubleOTP( 17 | Generic[SK1, SK2, CT1, CT2, PT1], 18 | OTPScheme[Tuple[SK1, SK2], PT1, CT2] 19 | ): 20 | @staticmethod 21 | def KeyGen(keylen): 22 | sk1 = InnerOTP.KeyGen(keylen) 23 | sk2 = OuterOTP.KeyGen(keylen) 24 | dsk = (sk1, sk2) 25 | return dsk 26 | @staticmethod 27 | def Encrypt(dsk, msg): 28 | (sk1, sk2) = dsk 29 | ct1 = InnerOTP.Encrypt(sk1, msg) 30 | ct2 = OuterOTP.Encrypt(sk2, ct1) 31 | return ct2 32 | @staticmethod 33 | def Decrypt(dsk, ct2): 34 | (sk1, sk2) = dsk 35 | pt2 = OuterOTP.Decrypt(sk2, ct2) 36 | pt1 = InnerOTP.Decrypt(sk1, pt2) 37 | return pt1 38 | -------------------------------------------------------------------------------- /examples/DoubleOTP/DoubleOTP_is_ROR.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Generic, Tuple, Type 3 | 4 | from gamehop.primitives import Crypto, OTP 5 | from gamehop.proofs2 import Proof 6 | 7 | from DoubleOTP import DoubleOTP, SK1, SK2, CT1, CT2, PT1, InnerOTP, OuterOTP 8 | 9 | # Theorem: DoubleOTP is ROR-secure if OuterOTP is. 10 | proof = Proof(DoubleOTP, OTP.ROR) 11 | 12 | # Game 0 is the DoubleOTP scheme inlined into OTP.ROR_Real. 13 | 14 | # Before we can jump to game 1, we need to do a rewriting step regarding message 15 | # / ciphertext lengths that pygamehop can't deduce automatically. 16 | # In particular, that the length of the inner ciphertext is the same as the length 17 | # of the original message. 18 | # We do this using a "rewriting step" in which we specify the two versions of the game 19 | # before and after rewriting. pygame will then check that the before version 20 | # matches the previous game and the after version matches the next game. 21 | 22 | class RW1Left(Crypto.Game, Generic[SK1, SK2, CT1, CT2, PT1]): 23 | def __init__(self, Adversary: Type[OTP.ROR_Adversary[Tuple[SK1, SK2], PT1, CT2]]): 24 | self.Scheme = DoubleOTP 25 | self.adversary = Adversary(DoubleOTP) 26 | def main(self) -> Crypto.Bit: 27 | R1_challengeᴠ1ⴰm = self.adversary.challenge() 28 | R1_challengeᴠ1ⴰsk1 = InnerOTP.KeyGen(len(R1_challengeᴠ1ⴰm)) 29 | R1_challengeᴠ1ⴰct1 = InnerOTP.Encrypt(R1_challengeᴠ1ⴰsk1, R1_challengeᴠ1ⴰm) 30 | m = R1_challengeᴠ1ⴰct1 31 | k = OuterOTP.KeyGen(len(R1_challengeᴠ1ⴰm)) # This line changes 32 | ct = OuterOTP.Encrypt(k, m) 33 | r = self.adversary.guess(ct) 34 | return r 35 | 36 | class RW1Right(Crypto.Game, Generic[SK1, SK2, CT1, CT2, PT1]): 37 | def __init__(self, Adversary: Type[OTP.ROR_Adversary[Tuple[SK1, SK2], PT1, CT2]]): 38 | self.Scheme = DoubleOTP 39 | self.adversary = Adversary(DoubleOTP) 40 | def main(self) -> Crypto.Bit: 41 | R1_challengeᴠ1ⴰm = self.adversary.challenge() 42 | R1_challengeᴠ1ⴰsk1 = InnerOTP.KeyGen(len(R1_challengeᴠ1ⴰm)) 43 | R1_challengeᴠ1ⴰct1 = InnerOTP.Encrypt(R1_challengeᴠ1ⴰsk1, R1_challengeᴠ1ⴰm) 44 | m = R1_challengeᴠ1ⴰct1 45 | k = OuterOTP.KeyGen(len(m)) # This line changed 46 | ct = OuterOTP.Encrypt(k, m) 47 | r = self.adversary.guess(ct) 48 | return r 49 | 50 | proof.add_rewriting_proof_step(RW1Left, RW1Right) 51 | 52 | # Would like to be able to use the "simple" rewriting step rather than have to write 53 | # it out in full above, but this fails because one of the variables used in the rewrite 54 | # isn't defined yet and the line re-ordering won't put them into the right order, it seems. 55 | # proof.insert_simple_rewriting_proof_step_after({ 56 | # "DoubleOTP_KeyGenᴠ1ⴰsk2 = OuterOTP.KeyGen(len(m))": "DoubleOTP_KeyGenᴠ1ⴰsk2 = OuterOTP.KeyGen(len(DoubleOTP_Encryptᴠ1ⴰct1))" 57 | # }) 58 | 59 | # Now we do a reduction to the ROR security of the outer OTP scheme. 60 | # The reduction acts as an adversary against the ROR security of the outer OTP scheme. 61 | # The reduction must simulate the previous game (which is the slightly rewritten ROR.Real 62 | # security game for DoubleOTP) to the original adversary. 63 | # The technique is for the reduction to do the inner OTP encryption itself, but then 64 | # rely on the ROR security challenger for the outer OTP scheme to do the second 65 | # layer of encryption. 66 | class R1(Crypto.Reduction, 67 | Generic[SK1, SK2, CT1, CT2, PT1], 68 | OTP.ROR_Adversary[SK2, CT1, CT2] # This is an ROR adversary for outer OTP 69 | ): 70 | def __init__(self, Scheme: Type[OTP.OTPScheme[SK2, CT1, CT2]], inner_adversary: OTP.ROR_Adversary[Tuple[SK1, SK2], PT1, CT2]): 71 | self.Scheme = Scheme 72 | self.inner_adversary = inner_adversary # this is the DoubleOTP adversary 73 | def challenge(self) -> PT1: 74 | # Use the DoubleOTP adversary to generate the challenge message. 75 | m = self.inner_adversary.challenge() 76 | sk1 = InnerOTP.KeyGen(len(m)) 77 | ct1 = InnerOTP.Encrypt(sk1, m) 78 | return ct1 79 | def guess(self, ct2: CT2) -> Crypto.Bit: 80 | return self.inner_adversary.guess(ct2) 81 | 82 | proof.add_distinguishing_proof_step(R1, OTP.ROR, OuterOTP, "OuterOTP") 83 | 84 | # We have to do one more rewriting step, again about message lengths and ciphertext, 85 | # basically undoing our first rewriting step. 86 | # We are able to do this rewriting in a simpler search-and-replace way since it 87 | # doesn't require manually reordering any lines. 88 | 89 | proof.insert_simple_rewriting_proof_step_after({ 90 | "len(m)": "len(R1_challengeᴠ1ⴰm)" 91 | }) 92 | 93 | 94 | assert proof.check(print_hops=True, print_canonicalizations=True, print_diffs=True, abort_on_failure=False) 95 | print(proof.advantage_bound()) 96 | 97 | with open(os.path.join('examples', 'DoubleOTP', 'DoubleOTP_is_ROR.tex'), 'w') as fh: 98 | fh.write(proof.tikz_figure()) 99 | -------------------------------------------------------------------------------- /examples/KEMfromPKE/KEMfromPKE.py: -------------------------------------------------------------------------------- 1 | from typing import Annotated, Generic, Sized, TypeVar 2 | 3 | from gamehop.primitives import Crypto 4 | from gamehop.primitives.KEM import KEMScheme 5 | from gamehop.primitives.PKE import PKEScheme 6 | 7 | PK = TypeVar('PK') 8 | SK = TypeVar('SK') 9 | CT = TypeVar('CT') 10 | SS = TypeVar('SS', bound=Sized) 11 | 12 | InnerPKE = PKEScheme[PK, SK, CT, SS] 13 | 14 | class KEMfromPKE( 15 | Generic[PK, SK, CT, SS], 16 | KEMScheme[PK, SK, CT, SS] 17 | ): 18 | @staticmethod 19 | def uniformSharedSecret() -> Annotated[SS, Crypto.UniformlyRandom]: return KEMfromPKE.uniformSharedSecret() 20 | @staticmethod 21 | def KeyGen(): 22 | return InnerPKE.KeyGen() 23 | @staticmethod 24 | def Encaps(pk): 25 | ss = KEMfromPKE.uniformSharedSecret() 26 | ct = InnerPKE.Encrypt(pk, ss) 27 | return (ct, ss) 28 | @staticmethod 29 | def Decaps(sk, ct): 30 | return InnerPKE.Decrypt(sk, ct) 31 | -------------------------------------------------------------------------------- /examples/KEMfromPKE/KEMfromPKE_is_INDCPA.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Generic, Tuple, Type, TypeVar 3 | 4 | from gamehop.primitives import Crypto, KEM, PKE 5 | from gamehop.proofs2 import Proof 6 | 7 | from KEMfromPKE import KEMfromPKE, InnerPKE, PK, SK, CT, SS 8 | 9 | # Theorem: KEMfromPKE[InnerPKE] is IND-CPA-secure if InnerPKE is IND-CPA-secure. 10 | proof = Proof(KEMfromPKE, KEM.INDCPA) 11 | 12 | # Game 0 is the KEMfromPKE scheme inlined into KEM.INDCPA_Real (where the real shared secret is encrypted). 13 | 14 | # We want to hop to a game that encrypts an unrelated shared secret rather than the 15 | # real shared secret. Before we can do that, we need to codify an implicit assumption 16 | # in KEMfromPKE, that encryptions of equal-length messages yield equal-length ciphertexts. 17 | # This will be done by a rewriting step. 18 | # The rewriting step also renames one member variable to a local variable since the 19 | # canonicalization engine can't handle that properly yet. 20 | # This rewriting step will be phrased by rewriting the next game to get the previous 21 | # game, so we have to defer inserting this rewriting step until after the next step 22 | # has been added. 23 | 24 | # Game 2 sends the encryption of an independent random value instead of the actual shared secret. 25 | 26 | # Game 1 and Game 2 are indistinguishable under the assumption that InnerPKE is IND-CPA-secure. 27 | # This is chosen by constructing a reduction that acts an IND-CPA-adversary against InnerPKE, 28 | # and checking that this reduction, inlined into the IND-CPA experiment for InnerPKE, 29 | # is equivalent to either Game 0 or Game 1. 30 | 31 | class R1(Crypto.Reduction, 32 | Generic[PK, SK, CT, SS], 33 | PKE.INDCPA_Adversary[PK, SK, CT, SS] # This is an INDCPA adversary for InnerPKE 34 | ): 35 | def __init__(self, Scheme: Type[PKE.PKEScheme[PK, SK, CT, SS]], inner_adversary: KEM.INDCPA_Adversary[PK, SK, CT, SS]): 36 | self.Scheme = Scheme 37 | self.inner_adversary = inner_adversary # this is the KEMfromPKE adversary 38 | def challenge(self, pk: PK) -> Tuple[SS, SS]: 39 | self.pk = pk 40 | # Generate two independent shared secrets as the challenge messages. 41 | self.ss0 = KEMfromPKE.uniformSharedSecret() 42 | ss1 = KEMfromPKE.uniformSharedSecret() 43 | return (self.ss0, ss1) 44 | def guess(self, ct: CT) -> Crypto.Bit: 45 | # Given the challenge InnerPKE ciphertext from the INDCPA challenger for InnerPKE, 46 | # pass it (as a KEMfromPKE ciphertext) to the KEMfromPKE adversary. 47 | return self.inner_adversary.guess(self.pk, ct, self.ss0) 48 | 49 | proof.add_distinguishing_proof_step(R1, PKE.INDCPA, InnerPKE, "InnerPKE") 50 | 51 | proof.insert_simple_rewriting_proof_step_before({ 52 | "if len(m0) == len(m1)": "if True" 53 | }) 54 | 55 | # Need to again codify an implicit assumption in KEMfromPKE that encryptions of 56 | # equal-length messages yield equal-length ciphertexts. 57 | # This will be done by a rewriting step. 58 | # The rewriting step also renames one member variable to a local variable since the 59 | # canonicalization engine can't handle that properly yet. 60 | 61 | proof.insert_simple_rewriting_proof_step_after({ 62 | "if len(m0) == len(m1)": "if True" 63 | }) 64 | 65 | assert proof.check(print_hops=True, print_canonicalizations=True, print_diffs=True, abort_on_failure=False) 66 | print("Theorem:") 67 | print(proof.advantage_bound()) 68 | 69 | with open(os.path.join('examples', 'KEMfromPKE', 'KEMfromPKE_is_INDCPA.tex'), 'w') as fh: 70 | fh.write(proof.tikz_figure()) 71 | -------------------------------------------------------------------------------- /examples/PKEfromKEM/PKEfromKEM.py: -------------------------------------------------------------------------------- 1 | from typing import Generic, Optional, Sized, Tuple, TypeVar 2 | 3 | from gamehop.primitives import Crypto 4 | from gamehop.primitives.KDF import KDFScheme 5 | from gamehop.primitives.KEM import KEMScheme 6 | from gamehop.primitives.OTP import OTPScheme 7 | from gamehop.primitives.PKE import PKEScheme 8 | 9 | PK = TypeVar('PK') 10 | SK = TypeVar('SK') 11 | CT_KEM = TypeVar('CT_KEM') 12 | CT_BODY = TypeVar('CT_BODY', bound=Sized) 13 | SS = TypeVar('SS') 14 | PT = TypeVar('PT', bound=Sized) 15 | 16 | InnerKDF = KDFScheme[SS, CT_BODY] 17 | InnerKEM = KEMScheme[PK, SK, CT_KEM, SS] 18 | InnerOTP = OTPScheme[CT_BODY, PT, CT_BODY] 19 | 20 | class PKEfromKEM( 21 | Generic[PK, SK, CT_KEM, CT_BODY, SS, PT], 22 | PKEScheme[PK, SK, Tuple[CT_KEM, CT_BODY], PT] 23 | ): 24 | @staticmethod 25 | def KeyGen(): 26 | return InnerKEM.KeyGen() 27 | @staticmethod 28 | def Encrypt(pk, msg): 29 | (ct_kem, ss) = InnerKEM.Encaps(pk) 30 | mask = InnerKDF.Eval(ss, "label", len(msg)) 31 | ct_body = InnerOTP.Encrypt(mask, msg) 32 | return (ct_kem, ct_body) 33 | @staticmethod 34 | def Decrypt(sk, ct): 35 | (ct_kem, ct_body) = ct 36 | ss = InnerKEM.Decaps(sk, ct_kem) 37 | if ss is None: 38 | retvalue: Optional[PT] = None 39 | else: 40 | mask = InnerKDF.Eval(ss, "label", len(ct_body)) 41 | retvalue = InnerOTP.Decrypt(mask, ct_body) 42 | return retvalue 43 | -------------------------------------------------------------------------------- /examples/SymEnc_CPADollar/SE.py: -------------------------------------------------------------------------------- 1 | from typing import Annotated, Generic, Sized, TypeVar 2 | 3 | from gamehop.primitives import Crypto 4 | from gamehop.primitives.SymEnc import SymEncScheme 5 | 6 | SK = TypeVar('SK') 7 | MSG = TypeVar('MSG') 8 | CT = TypeVar('CT') 9 | 10 | InnerSymEnc = SymEncScheme[SK, MSG, CT] 11 | 12 | class SE( 13 | Generic[SK, MSG, CT], 14 | SymEncScheme[SK, MSG, CT] 15 | ): 16 | @staticmethod 17 | def uniformKey() -> Annotated[SK, Crypto.UniformlyRandom]: return InnerSymEnc.uniformKey() 18 | @staticmethod 19 | def uniformCiphertext() -> Annotated[CT, Crypto.UniformlyRandom]: return InnerSymEnc.uniformCiphertext() 20 | @staticmethod 21 | def Encrypt(key, msg): 22 | return InnerSymEnc.Encrypt(key, msg) 23 | @staticmethod 24 | def Decrypt(key, ctxt): 25 | return InnerSymEnc.Decrypt(key, ctxt) 26 | -------------------------------------------------------------------------------- /examples/SymEnc_CPADollar/SymEnc_CPADollar_is_INDCPA.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Callable, Generic, Tuple, Type, TypeVar 3 | 4 | from gamehop.primitives import Crypto, SymEnc 5 | from gamehop.proofs2 import Proof 6 | 7 | from SE import SE, InnerSymEnc, SK, MSG, CT 8 | 9 | # Theorem: SE is IND-CPA-secure if InnerSymEnc is IND-CPADollar-secure. 10 | proof = Proof(SE, SymEnc.INDCPA) 11 | 12 | class R1(Crypto.Reduction, 13 | Generic[SK, MSG, CT], 14 | SymEnc.INDCPADollar_Adversary[SK, MSG, CT] 15 | ): 16 | def __init__(self, Scheme: Type[SymEnc.SymEncScheme[SK, MSG, CT]], inner_adversary: SymEnc.INDCPA_Adversary[SK, MSG, CT]): 17 | self.Scheme = Scheme 18 | self.inner_adversary = inner_adversary # this is the INDCPA adversary 19 | def run(self, o_ctxt: Callable[[MSG], CT]) -> Crypto.Bit: 20 | self.io_ctxt = o_ctxt 21 | r = self.inner_adversary.run(self.o_eavesdrop) 22 | return r 23 | def o_eavesdrop(self, msg_L: MSG, msg_R: MSG) -> CT: 24 | ctxt = self.io_ctxt(msg_L) 25 | return ctxt 26 | 27 | proof.add_distinguishing_proof_step(R1, SymEnc.INDCPADollar, InnerSymEnc, "InnerSymEnc") 28 | 29 | class R2(Crypto.Reduction, 30 | Generic[SK, MSG, CT], 31 | SymEnc.INDCPADollar_Adversary[SK, MSG, CT] 32 | ): 33 | def __init__(self, Scheme: Type[SymEnc.SymEncScheme[SK, MSG, CT]], inner_adversary: SymEnc.INDCPA_Adversary[SK, MSG, CT]): 34 | self.Scheme = Scheme 35 | self.inner_adversary = inner_adversary # this is the INDCPA adversary 36 | def run(self, o_ctxt: Callable[[MSG], CT]) -> Crypto.Bit: 37 | self.io_ctxt = o_ctxt 38 | r = self.inner_adversary.run(self.o_eavesdrop) 39 | return r 40 | def o_eavesdrop(self, msg_L: MSG, msg_R: MSG) -> CT: 41 | ctxt = self.io_ctxt(msg_R) 42 | return ctxt 43 | 44 | proof.add_distinguishing_proof_step(R2, SymEnc.INDCPADollar, InnerSymEnc, "InnerSymEnc", reverse_direction = True) 45 | 46 | assert proof.check(print_hops=True, print_canonicalizations=True, print_diffs=True, abort_on_failure=True) 47 | print("Theorem:") 48 | print(proof.advantage_bound()) 49 | 50 | with open(os.path.join('examples', 'SymEnc_CPADollar', 'SymEnc_CPADollar_is_INDCPA.tex'), 'w') as fh: 51 | fh.write(proof.tikz_figure()) 52 | -------------------------------------------------------------------------------- /examples/nestedPKE/nestedPKE.py: -------------------------------------------------------------------------------- 1 | from typing import Generic, Optional, Sized, Tuple, TypeVar 2 | 3 | from gamehop.primitives import Crypto 4 | from gamehop.primitives.PKE import PKEScheme 5 | 6 | PK1 = TypeVar('PK1') 7 | SK1 = TypeVar('SK1') 8 | CT1 = TypeVar('CT1', bound=Sized) 9 | PT1 = TypeVar('PT1', bound=Sized) 10 | PK2 = TypeVar('PK2') 11 | SK2 = TypeVar('SK2') 12 | CT2 = TypeVar('CT2') 13 | # Missing PT2 since PT2 will be the same as CT1 14 | 15 | PKE1 = PKEScheme[PK1, SK1, CT1, PT1] 16 | PKE2 = PKEScheme[PK2, SK2, CT2, CT1] 17 | 18 | class NestedPKE( 19 | Generic[PK1, PK2, SK1, SK2, CT1, CT2, PT1], 20 | PKEScheme[Tuple[PK1, PK2], Tuple[SK1, SK2], CT2, PT1] 21 | ): 22 | @staticmethod 23 | def KeyGen(): 24 | (pk1, sk1) = PKE1.KeyGen() 25 | (pk2, sk2) = PKE2.KeyGen() 26 | return ((pk1, pk2), (sk1, sk2)) 27 | @staticmethod 28 | def Encrypt(npk, msg): 29 | (pk1, pk2) = npk 30 | ct1 = PKE1.Encrypt(pk1, msg) 31 | ct2 = PKE2.Encrypt(pk2, ct1) 32 | return ct2 33 | @staticmethod 34 | def Decrypt(nsk, ct2): 35 | (sk1, sk2) = nsk 36 | pt2 = PKE2.Decrypt(sk2, ct2) 37 | if pt2 == None: 38 | r: Optional[PT1] = None 39 | else: 40 | r = PKE1.Decrypt(sk1, pt2) 41 | return r 42 | -------------------------------------------------------------------------------- /examples/nestedPKE/nestedPKE_is_INDCPA.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Generic, Tuple, Type 3 | 4 | from gamehop.primitives import Crypto, PKE 5 | from gamehop.proofs2 import Proof 6 | 7 | from nestedPKE import NestedPKE, PKE1, PKE2, PK1, PK2, SK1, SK2, CT1, CT2, PT1 8 | 9 | # Theorem: NestedPKE[PKE1, PKE2] is IND-CPA-secure if either PKE1 is IND-CPA-secure or PKE2 is IND-CPA-secure. 10 | # This shown via two separate proofs: 11 | # 1) If PKE1 is IND-CPA-secure, then NestedPKE[PKE1, PKE2] is IND-CPA-secure. 12 | # 2) If PKE2 is IND-CPA-secure, then NestedPKE[PKE1, PKE2] is IND-CPA-secure. 13 | 14 | # First proof: If PKE1 is IND-CPA-secure, then NestedPKE[PKE1, PKE2] is IND-CPA-secure. 15 | # This is the statement we're trying to prove: NestedPKE is IND-CPA-secure. 16 | proof1 = Proof(NestedPKE, PKE.INDCPA) 17 | 18 | # Game 0 is the NestedPKE scheme inlined into PKE.INDCPA_Left (where m0 is encrypted). 19 | 20 | # Game 1 encrypts m1 rather than m0. 21 | # Game 1 is equivalent to the NestedPKE scheme inlined into PKE.INDCPA_Right (where m1 is encrypted). 22 | 23 | # Game 0 and Game 1 are indistinguishable under the assumption that PKE1 is IND-CPA-secure. 24 | # This is chosen by constructing a reduction that acts an IND-CPA-adversary against PKE1, 25 | # and checking that this reduction, inlined into the IND-CPA experiment for PKE1, 26 | # is equivalent to either Game 0 or Game 1. 27 | class R1(Crypto.Reduction, 28 | Generic[PK1, PK2, SK1, SK2, CT1, CT2, PT1], 29 | PKE.INDCPA_Adversary[PK1, SK1, CT1, PT1] # This is an INDCPA adversary for PKE1 30 | ): 31 | def __init__(self, Scheme: Type[PKE.PKEScheme[PK1, SK1, CT1, PT1]], inner_adversary: PKE.INDCPA_Adversary[Tuple[PK1, PK2], Tuple[SK1, SK2], CT2, PT1]): 32 | self.Scheme = Scheme 33 | self.inner_adversary = inner_adversary # this is the NestedPKE adversary 34 | def challenge(self, pk1: PK1) -> Tuple[PT1, PT1]: 35 | # Use the NestedPKE adversary to generate the two challenge messages. 36 | # To construct the NestedPKE public key, we use the PKE1 public key given 37 | # by the INDCPA challenger for PKE1, and generate the PKE2 keypair ourselves. 38 | (pk2, sk2) = PKE2.KeyGen() 39 | npk = (pk1, pk2) 40 | (m0, m1) = self.inner_adversary.challenge(npk) 41 | self.pk2 = pk2 42 | return (m0, m1) 43 | def guess(self, ct1: CT1) -> Crypto.Bit: 44 | # Given the challenge PKE1 ciphertext from the INDCPA challenger for PKE1, 45 | # construct a NestedPKE ciphertext by encrypting it under the PKE2 public key, 46 | # then pass the NestedPKE ciphertext to the NestedPKE adversary. 47 | ct2 = PKE2.Encrypt(self.pk2, ct1) 48 | return self.inner_adversary.guess(ct2) 49 | 50 | proof1.add_distinguishing_proof_step(R1, PKE.INDCPA, PKE1, "PKE1") 51 | 52 | assert proof1.check(print_hops=True, print_canonicalizations=True, print_diffs=True, abort_on_failure=False) 53 | print("Theorem 1:") 54 | print(proof1.advantage_bound()) 55 | 56 | with open(os.path.join('examples', 'nestedPKE', 'nestedPKE_is_INDCPA_proof1.tex'), 'w') as fh: 57 | fh.write(proof1.tikz_figure()) 58 | 59 | # Second proof: If PKE2 is IND-CPA-secure, then NestedPKE[PKE1, PKE2] is IND-CPA-secure. 60 | # This is the statement we're trying to prove: NestedPKE is IND-CPA-secure. 61 | proof2 = Proof(NestedPKE, PKE.INDCPA) 62 | 63 | # Game 0 is the NestedPKE scheme inlined into PKE.INDCPA_Left (where m0 is encrypted). 64 | 65 | # We want to hop to a game that encrypts m1 rather than m0. Before we can do that, 66 | # we need to codify an implicit assumption in NestedPKE, encryptions of equal-length 67 | # messages yield equal-length ciphertexts. 68 | # This will be done by a rewriting step. 69 | # The rewriting step also renames one member variable to a local variable since the 70 | # canonicalization engine can't handle that properly yet. 71 | # This rewriting step will be phrased by rewriting the next game to get the previous 72 | # game, so we have to defer inserting this rewriting step until after the next step 73 | # has been added. 74 | 75 | # Game 2 encrypts m1 rather than m0. 76 | # Game 2 is equivalent to the NestedPKE scheme inlined into PKE.INDCPA_Right (where m1 is encrypted). 77 | 78 | # Game 1 and Game 2 are indistinguishable under the assumption that PKE2 is IND-CPA-secure. 79 | # This is chosen by constructing a reduction that acts an IND-CPA-adversary against PKE2, 80 | # and checking that this reduction, inlined into the IND-CPA experiment for PKE2, 81 | # is equivalent to either Game 1 or Game 2. 82 | class R2(Crypto.Reduction, 83 | Generic[PK1, PK2, SK1, SK2, CT1, CT2, PT1], 84 | PKE.INDCPA_Adversary[PK2, SK2, CT2, CT1] # This is an INDCPA adversary for PKE2 85 | ): 86 | def __init__(self, Scheme: Type[PKE.PKEScheme[PK2, SK2, CT2, CT1]], inner_adversary: PKE.INDCPA_Adversary[Tuple[PK1, PK2], Tuple[SK1, SK2], CT2, PT1]): 87 | self.Scheme = Scheme 88 | self.inner_adversary = inner_adversary # this is the NestedPKE adversary 89 | def challenge(self, pk2: PK2) -> Tuple[CT1, CT1]: 90 | # Use the NestedPKE adversary to generate the two challenge messages. 91 | # To construct the NestedPKE public key, we use the PKE2 public key given 92 | # by the INDCPA challenger for PKE2, and generate the PKE1 keypair ourselves. 93 | # Once we get the challenge messages from the adversary, we have to encrypt 94 | # them under PKE1 so that the ciphertext we will eventually get back from 95 | # the PKE2 challenger is a NestedPKE ciphertext 96 | (pk1, sk1) = PKE1.KeyGen() 97 | npk = (pk1, pk2) 98 | (m0, m1) = self.inner_adversary.challenge(npk) 99 | self.ok = len(m0) == len(m1) 100 | c0 = PKE1.Encrypt(pk1, m0) 101 | c1 = PKE1.Encrypt(pk1, m1) 102 | return (c0, c1) 103 | def guess(self, ct2: CT2) -> Crypto.Bit: 104 | # The challenge PKE2 ciphertext from the INDCPA challenger for PKE2 contains 105 | # a PKE1 ciphertext of either m0 or m1, so it is immediately the NestedPKE 106 | # challenge ciphertext. 107 | r = self.inner_adversary.guess(ct2) 108 | return r if self.ok else Crypto.Bit(0) 109 | 110 | proof2.add_distinguishing_proof_step(R2, PKE.INDCPA, PKE2, 'PKE2') 111 | 112 | # Here's the deferred rewriting step as noted above. 113 | proof2.insert_simple_rewriting_proof_step_before( 114 | { 115 | "if len(m0) == len(m1)": "if True" 116 | } 117 | ) 118 | 119 | # Need to again codify an implicit assumption in NestedPKE that encryptions of 120 | # equal-length messages yield equal-length ciphertexts. 121 | # This will be done by a rewriting step. 122 | # The rewriting step also renames one member variable to a local variable since the 123 | # canonicalization engine can't handle that properly yet. 124 | 125 | proof2.insert_simple_rewriting_proof_step_after( 126 | { 127 | "if len(m0) == len(m1)": "if True" 128 | } 129 | ) 130 | 131 | assert proof2.check(print_hops=True, print_canonicalizations=True, print_diffs=True, abort_on_failure=False) 132 | print("Theorem 2:") 133 | print(proof2.advantage_bound()) 134 | 135 | with open(os.path.join('examples', 'nestedPKE', 'nestedPKE_is_INDCPA_proof2.tex'), 'w') as fh: 136 | fh.write(proof2.tikz_figure()) 137 | -------------------------------------------------------------------------------- /examples/parallelPKE/parallelPKE.py: -------------------------------------------------------------------------------- 1 | from typing import cast, Generic, Optional, Sized, Tuple, TypeVar 2 | 3 | from gamehop.primitives import Crypto 4 | from gamehop.primitives.PKE import PKEScheme 5 | 6 | PK1 = TypeVar('PK1') 7 | SK1 = TypeVar('SK1') 8 | CT1 = TypeVar('CT1') 9 | PK2 = TypeVar('PK2') 10 | SK2 = TypeVar('SK2') 11 | CT2 = TypeVar('CT2') 12 | PT12 = TypeVar('PT12', bound=Sized) # Use the same plaintext space for both schemes 13 | 14 | PKE1 = PKEScheme[PK1, SK1, CT1, PT12] 15 | PKE2 = PKEScheme[PK2, SK2, CT2, PT12] 16 | 17 | class ParallelPKE( 18 | Generic[PK1, PK2, SK1, SK2, CT1, CT2, PT12], 19 | PKEScheme[Tuple[PK1, PK2], Tuple[SK1, SK2], Tuple[CT1, CT2], PT12]): 20 | @staticmethod 21 | def KeyGen(): 22 | (pk1, sk1) = PKE1.KeyGen() 23 | (pk2, sk2) = PKE2.KeyGen() 24 | return ((pk1, pk2), (sk1, sk2)) 25 | @staticmethod 26 | def Encrypt(npk, msg): 27 | (pk1, pk2) = npk 28 | ct1 = PKE1.Encrypt(pk1, msg) 29 | ct2 = PKE2.Encrypt(pk2, msg) 30 | return (ct1, ct2) 31 | @staticmethod 32 | def Decrypt(nsk, nct): 33 | (sk1, sk2) = nsk 34 | (ct1, ct2) = nct 35 | pt1 = PKE1.Decrypt(sk1, ct1) 36 | pt2 = PKE2.Decrypt(sk2, ct2) 37 | if pt1 is None or pt2 is None or pt1 != pt2: 38 | r: Optional[PT12] = None 39 | else: 40 | r = pt1 41 | return r 42 | -------------------------------------------------------------------------------- /examples/parallelPKE/parallelPKE_is_INDCPA.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Generic, Tuple, Type 3 | 4 | from gamehop.primitives import Crypto, PKE 5 | from gamehop.proofs2 import Proof 6 | 7 | from parallelPKE import ParallelPKE, PKE1, PKE2, PK1, PK2, SK1, SK2, CT1, CT2, PT12 8 | 9 | # Theorem: ParallelPKE[PKE1, PKE2] is IND-CPA-secure if both PKE1 and PKE2 are IND-CPA-secure. 10 | proof = Proof(ParallelPKE, PKE.INDCPA) 11 | 12 | # Game 0 is the ParallelPKE scheme inlined into PKE.INDCPA_Left (where m0 is encrypted). 13 | 14 | # Game 1 encrypts m1 rather than m0 in PKE1. 15 | 16 | # Game 0 and Game 1 are indistinguishable under the assumption that PKE1 is IND-CPA-secure. 17 | # This is chosen by constructing a reduction that acts an IND-CPA-adversary against PKE1, 18 | # and checking that this reduction, inlined into the IND-CPA experiment for PKE1, 19 | # is equivalent to either Game 0 or Game 1. 20 | class R1(Crypto.Reduction, 21 | Generic[PK1, PK2, SK1, SK2, CT1, CT2, PT12], 22 | PKE.INDCPA_Adversary[PK1, SK1, CT1, PT12] # This is an INDCPA adversary for PKE1 23 | ): 24 | def __init__(self, Scheme: Type[PKE.PKEScheme[PK1, SK1, CT1, PT12]], inner_adversary: PKE.INDCPA_Adversary[Tuple[PK1, PK2], Tuple[SK1, SK2], Tuple[CT1, CT2], PT12]): 25 | self.Scheme = Scheme 26 | self.inner_adversary = inner_adversary # this is the ParallelPKE adversary 27 | def challenge(self, pk1: PK1) -> Tuple[PT12, PT12]: 28 | # Use the ParallelPKE adversary to generate the two challenge messages. 29 | # To construct the ParallelPKE public key, we use the PKE1 public key given 30 | # by the INDCPA challenger for PKE1, and generate the PKE2 keypair ourselves. 31 | (pk2, sk2) = PKE2.KeyGen() 32 | (m0, m1) = self.inner_adversary.challenge((pk1, pk2)) 33 | self.pk2 = pk2 34 | self.m0 = m0 35 | self.m1 = m1 36 | return (m0, m1) 37 | def guess(self, ct1: CT1) -> Crypto.Bit: 38 | # Given the challenge PKE1 ciphertext from the INDCPA challenger for PKE1, 39 | # construct a ParallelPKE ciphertext by encrypting m0 under the PKE2 public key, 40 | # then pass the ParallelPKE ciphertext to the ParallelPKE adversary. 41 | ct2 = PKE2.Encrypt(self.pk2, self.m0) 42 | return self.inner_adversary.guess((ct1, ct2)) 43 | 44 | proof.add_distinguishing_proof_step(R1, PKE.INDCPA, PKE1, "PKE1") 45 | 46 | # Game 2 encrypts m1 rather than m0 in PKE2. 47 | 48 | # Game 1 and Game 2 are indistinguishable under the assumption that PKE2 is IND-CPA-secure. 49 | # This is chosen by constructing a reduction that acts an IND-CPA-adversary against PKE2, 50 | # and checking that this reduction, inlined into the IND-CPA experiment for PKE2, 51 | # is equivalent to either Game 1 or Game 2. 52 | class R2(Crypto.Reduction, 53 | Generic[PK1, PK2, SK1, SK2, CT1, CT2, PT12], 54 | PKE.INDCPA_Adversary[PK2, SK2, CT2, PT12] # This is an INDCPA adversary for PKE2 55 | ): 56 | def __init__(self, Scheme: Type[PKE.PKEScheme[PK2, SK2, CT2, PT12]], inner_adversary: PKE.INDCPA_Adversary[Tuple[PK1, PK2], Tuple[SK1, SK2], Tuple[CT1, CT2], PT12]): 57 | self.Scheme = Scheme 58 | self.inner_adversary = inner_adversary # this is the ParallelPKE adversary 59 | def challenge(self, pk2: PK2) -> Tuple[PT12, PT12]: 60 | # Use the ParallelPKE adversary to generate the two challenge messages. 61 | # To construct the ParallelPKE public key, we use the PKE2 public key given 62 | # by the INDCPA challenger for PKE2, and generate the PKE1 keypair ourselves. 63 | (pk1, sk1) = PKE1.KeyGen() 64 | (m0, m1) = self.inner_adversary.challenge((pk1, pk2)) 65 | self.pk1 = pk1 66 | self.m0 = m0 67 | self.m1 = m1 68 | return (m0, m1) 69 | def guess(self, ct2: CT2) -> Crypto.Bit: 70 | # Given the challenge PKE2 ciphertext from the INDCPA challenger for PKE2, 71 | # construct a ParallelPKE ciphertext by encrypting m1 under the PKE1 public key, 72 | # then pass the ParallelPKE ciphertext to the ParallelPKE adversary. 73 | ct1 = PKE1.Encrypt(self.pk1, self.m1) 74 | return self.inner_adversary.guess((ct1, ct2)) 75 | 76 | proof.add_distinguishing_proof_step(R2, PKE.INDCPA, PKE2, "PKE2") 77 | 78 | assert proof.check(print_hops=False, print_canonicalizations=False, print_diffs=False, abort_on_failure=False) 79 | print("Theorem:") 80 | print(proof.advantage_bound()) 81 | 82 | with open(os.path.join('examples', 'parallelPKE', 'parallelPKE_is_INDCPA.tex'), 'w') as fh: 83 | fh.write(proof.tikz_figure()) 84 | -------------------------------------------------------------------------------- /gamehop/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ['inlining', 'primitives', 'verification', 'lists', 'proofs', 'utils'] 2 | -------------------------------------------------------------------------------- /gamehop/bits.py: -------------------------------------------------------------------------------- 1 | import ast 2 | from typing import Union, List, TypeVar 3 | 4 | T = TypeVar('T') 5 | def ensure_list(thing: Union[T, List[T]]) -> List[T]: 6 | ''' Argument is wrapped in a list() if it is not already a list''' 7 | if isinstance(thing, list): 8 | return thing 9 | else: 10 | return [ thing ] 11 | 12 | def glue_list_and_vals(vals: List[Union[T, List[T], None]]) -> List[T]: 13 | ''' Create a single list out of vals. If a val is a list, then it's 14 | contents are added to the list, otherwise the val itself is added. 15 | Any None values are removed.''' 16 | ret_val: List[T] = list() 17 | for v in vals: 18 | if isinstance(v, list): 19 | ret_val.extend(v) 20 | elif v is None: 21 | continue 22 | else: 23 | ret_val.append(v) 24 | return ret_val 25 | 26 | def append_if_unique(l, val): 27 | if not val in l: 28 | l.append(val) 29 | 30 | def unique_elements(l: List[T]) -> List[T]: 31 | ''' Returns a list where elements are taken from the given list, but only the first occurance of 32 | any particular element is kept. Subsequent occurances of each element are removed. 33 | ''' 34 | ret = list() 35 | for v in l: 36 | if v not in ret: 37 | ret.append(v) 38 | return ret 39 | 40 | 41 | def attribute_fqn(node: ast.expr) -> List[str]: 42 | '''From an attribute node, determine the full name, given as a list of strings. Handles 43 | attributes of attributes etc. recursively. 44 | Note that the outer Attribute represents the rightmost name in a full name of an attribute, i.e. 45 | for a.b.c, the outer Attribute node represents 'c' and the innermost node is a Name with id 'a'. 46 | 47 | ''' 48 | fqn: List[str] = [ ] 49 | if isinstance(node, ast.Attribute): 50 | val = node.value 51 | fqn.insert(0, node.attr) 52 | # Keep on adding prefixes (object names) to the varname until we are at the outer name 53 | while isinstance(val, ast.Attribute): 54 | fqn.insert(0, val.attr) 55 | val = val.value 56 | 57 | # At the outer name 58 | if isinstance(val, ast.Name): 59 | fqn.insert(0, val.id) 60 | elif isinstance(val, ast.arg): 61 | fqn.insert(0, val.arg) 62 | else: 63 | # Not sure what else will come up! 64 | assert(False) 65 | 66 | return fqn 67 | 68 | def fqn_str(fqn: List[str]) -> str: 69 | return ".".join(fqn) 70 | 71 | def str_fqn(varname: str) -> List[str]: 72 | return varname.split('.') 73 | 74 | def called_function_name(node: ast.Call): 75 | if isinstance(node.func, ast.Name): 76 | return node.func.id 77 | if isinstance(node.func, ast.Attribute): 78 | return ".".join(attribute_fqn(node.func)) 79 | # Don't know what else might come up! 80 | assert(False) 81 | -------------------------------------------------------------------------------- /gamehop/format.py: -------------------------------------------------------------------------------- 1 | import ast 2 | 3 | import gamehop.utils 4 | 5 | def indent_every_line(s: str) -> str: 6 | lines = s.splitlines() 7 | newlines = list() 8 | for line in lines: newlines.append(" " + line) 9 | return "\n".join(newlines) 10 | 11 | class Textify(ast.NodeVisitor): 12 | 13 | def visit_ClassDef(self, cdef): 14 | s = "Class " + cdef.name 15 | s += "\n" + ''.join(["=" for i in range(len(s))]) + "\n" 16 | for node in cdef.body: 17 | if isinstance(node, ast.FunctionDef): 18 | s += "\n" + self.visit_FunctionDef(node) 19 | else: raise ValueError(f"Unknown node type {ast.unparse(node)}") 20 | return s 21 | 22 | def visit_FunctionDef(self, fdef): 23 | s = "" 24 | if fdef.name.startswith("o_"): s += "Oracle " + fdef.name 25 | elif fdef.name == "__init__": return "" 26 | else: s += fdef.name 27 | s += "(" 28 | s += ', '.join([arg.arg for arg in fdef.args.args[1:]]) 29 | s += "):" 30 | s += "\n" + ''.join(["-" for i in range(len(s))]) + "\n" 31 | s_body = "" 32 | for node in fdef.body: 33 | s_body += str(self.visit(node)) + "\n" 34 | s += indent_every_line(s_body) 35 | return s 36 | 37 | def visit_Assign(self, node): 38 | s = "" 39 | for target in node.targets: 40 | s += str(self.visit(target)) + " <- " 41 | s += str(self.visit(node.value)) 42 | return s 43 | 44 | def visit_Attribute(self, node): 45 | base = self.visit(node.value) 46 | if base == 'self': return node.attr 47 | else: return base + "." + node.attr 48 | 49 | def visit_BinOp(self, node): 50 | return str(self.visit(node.left)) + " " + self.visit(node.op) + " " + str(self.visit(node.right)) 51 | def visit_Add(self, node): return "+" 52 | def visit_Sub(self, node): return "-" 53 | def visit_Mult(self, node): return "*" 54 | def visit_Div(self, node): return "/" 55 | def visit_FloorDiv(self, node): return "//" 56 | def visit_Mod(self, node): return "mod" 57 | def visit_Pow(self, node): return "^" 58 | def visit_LShift(self, node): return "<<" 59 | def visit_RShift(self, node): return ">>" 60 | def visit_BitOr(self, node): return "|" 61 | def visit_BitXor(self, node): return "xor" 62 | def visit_BitAnd(self, node): return "&" 63 | def visit_MatMult(self, node): return "*" 64 | 65 | def visit_BoolOp(self, node): 66 | op = " " + str(self.visit(node.op)) + " " 67 | return op.join([str(self.visit(val)) for val in node.values]) 68 | def visit_And(self, node): return "and" 69 | def visit_Or(self, node): return "or" 70 | 71 | def visit_Break(self, node): return "break" 72 | 73 | def visit_Call(self, node): 74 | s = str(self.visit(node.func)) 75 | s += "(" 76 | s += ', '.join([str(self.visit(arg)) for arg in node.args]) 77 | s += ")" 78 | return s 79 | 80 | def visit_Compare(self, node): 81 | s = str(self.visit(node.left)) 82 | for i in range(len(node.ops)): 83 | s += " " + str(self.visit(node.ops[i])) + " " + str(self.visit(node.comparators[i])) 84 | return s 85 | def visit_Eq(self, node): return "=" 86 | def visit_NotEq(self, node): return "!=" 87 | def visit_Lt(self, node): return "<" 88 | def visit_LtE(self, node): return "≤" 89 | def visit_Gt(self, node): return ">" 90 | def visit_GtE(self, node): return "≥" 91 | def visit_Is(self, node): return "is" 92 | def visit_IsNot(self, node): return "is not" 93 | def visit_In(self, node): return "in" 94 | def visit_NotIn(self, node): return "not in" 95 | 96 | def visit_Constant(self, node): 97 | return str(node.value) 98 | 99 | def visit_Continue(self, node): return "continue" 100 | 101 | def visit_For(self, node): 102 | s = "for " + str(self.visit(node.target)) + " in " + str(self.visit(node.iter)) + ":\n" 103 | for line in node.body: 104 | s += " " + str(self.visit(line)) + "\n" 105 | if node.orelse: 106 | s += "else:\n" 107 | for line in node.orelse: 108 | s += " " + str(self.visit(line)) + "\n" 109 | return s 110 | 111 | def visit_If(self, node): 112 | s = "if " + str(self.visit(node.test)) + ":\n" 113 | for line in node.body: 114 | s += " " + str(self.visit(line)) + "\n" 115 | if node.orelse: 116 | s += "else:\n" 117 | for line in node.orelse: 118 | s += " " + str(self.visit(line)) + "\n" 119 | return s 120 | 121 | def visit_IfExp(self, node): 122 | return str(self.visit(node.body)) + " if " + str(self.visit(node.test)) + " else " + str(self.visit(node.orelse)) 123 | 124 | def visit_Name(self, node): 125 | return node.id 126 | 127 | def visit_Return(self, node): 128 | return "return " + str(self.visit(node.value)) 129 | 130 | def visit_Tuple(self, node): 131 | s = "(" 132 | s += ', '.join([str(self.visit(elt)) for elt in node.elts]) 133 | s += ")" 134 | return s 135 | 136 | def visit_UnaryOp(self, node): 137 | return str(self.visit(node.operand)) + " " + str(self.visit(node.op)) 138 | 139 | def visit_AnnAssign(self, node): 140 | raise ValueError("Unsupported node type: AnnAssign") 141 | def visit_Assert(self, node): 142 | raise ValueError("Unsupported node type: Assert") 143 | def visit_AugAssign(self, node): 144 | raise ValueError("Unsupported node type: AugAssign") 145 | def visit_Delete(self, node): 146 | raise ValueError("Unsupported node type: Delete") 147 | def visit_Dict(self, node): 148 | raise ValueError("Unsupported node type: Dict") 149 | def visit_DictComp(self, node): 150 | raise ValueError("Unsupported node type: DictComp") 151 | def visit_Expr(self, node): 152 | raise ValueError("Unsupported node type: Expr") 153 | def visit_GeneratorExp(self, node): 154 | raise ValueError("Unsupported node type: GeneratorExp") 155 | def visit_Global(self, node): 156 | raise ValueError("Unsupported node type: Global") 157 | def visit_Lambda(self, node): 158 | raise ValueError("Unsupported node type: Lambda") 159 | def visit_List(self, node): 160 | raise ValueError("Unsupported node type: List") 161 | def visit_ListComp(self, node): 162 | raise ValueError("Unsupported node type: ListComp") 163 | def visit_Match(self, node): 164 | raise ValueError("Unsupported node type: Match") 165 | def visit_NamedExpr(self, node): 166 | raise ValueError("Unsupported node type: NamedExpr") 167 | def visit_Nonlocal(self, node): 168 | raise ValueError("Unsupported node type: Nonlocal") 169 | def visit_Set(self, node): 170 | raise ValueError("Unsupported node type: Set") 171 | def visit_SetComp(self, node): 172 | raise ValueError("Unsupported node type: SetComp") 173 | def visit_Slice(self, node): 174 | raise ValueError("Unsupported node type: Slice") 175 | def visit_Starred(self, node): 176 | raise ValueError("Unsupported node type: Starred") 177 | def visit_Subscript(self, node): 178 | raise ValueError("Unsupported node type: Subscript") 179 | def visit_Try(self, node): 180 | raise ValueError("Unsupported node type: Try") 181 | def visit_While(self, node): 182 | raise ValueError("Unsupported node type: While") 183 | def visit_With(self, node): 184 | raise ValueError("Unsupported node type: With") 185 | def visit_Yield(self, node): 186 | raise ValueError("Unsupported node type: Yield") 187 | def visit_YieldFrom(self, node): 188 | raise ValueError("Unsupported node type: YieldFrom") 189 | 190 | def textify(cdef: ast.ClassDef) -> str: 191 | return Textify().visit(gamehop.utils.get_class_def(cdef)) 192 | -------------------------------------------------------------------------------- /gamehop/inlining/internal.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import copy 3 | import inspect 4 | import types 5 | from typing import Any, Callable, List, Optional, Set, Union 6 | 7 | from .. import utils 8 | 9 | def find_all_variables(f: Union[Callable, str, ast.FunctionDef]) -> List[str]: 10 | """Return a set of all variables in the function, including function parameters.""" 11 | fdef = utils.get_function_def(f) 12 | vars = list() 13 | # function arguments 14 | args = fdef.args 15 | if len(args.posonlyargs) > 0: raise NotImplementedError("No support for position-only variables") 16 | if len(args.kwonlyargs) > 0: raise NotImplementedError("No support for keyword-only variables") 17 | if len(args.kw_defaults) > 0: raise NotImplementedError("No support for keyword defaults") 18 | if len(args.defaults) > 0: raise NotImplementedError("No support for argument defaults") 19 | for arg in args.args: 20 | if arg.arg not in vars: vars.append(arg.arg) 21 | # find all assigned variables 22 | for stmt in fdef.body: 23 | if isinstance(stmt, ast.Assign): 24 | for target in stmt.targets: 25 | if isinstance(target, ast.Name): 26 | if target.id not in vars: vars.append(target.id) 27 | elif isinstance(target, ast.Tuple) or isinstance(target, ast.List): 28 | for elt in target.elts: 29 | if isinstance(elt, ast.Name) and elt.id not in vars: vars.append(elt.id) 30 | elif isinstance(target, ast.Attribute): 31 | t: Any = target 32 | while isinstance(t, ast.Attribute): t = t.value 33 | if isinstance(t, ast.Name): 34 | if t.id not in vars: vars.append(t.id) 35 | else: 36 | raise NotImplementedError("Can't deal with assignment target type " + str(type(target))) 37 | return vars 38 | 39 | def dereference_attribute(f: Union[Callable, str, ast.FunctionDef], name: str, formatstr: str) -> str: 40 | """Returns a string representing a function with all references to 'name.whatever' replaced with formatstr.format(whatever) (e.g., with 'name_whatever'). Only replaces top-level calls (a.b.c -> a_b.c) but not within (w.a.b does not go to w.a_b).""" 41 | fdef = copy.deepcopy(utils.get_function_def(f)) 42 | # fortunately a.b.c is parsed as ((a.b).c) 43 | # and we only want to replace the a.b 44 | class AttributeDereferencer(ast.NodeTransformer): 45 | def __init__(self, name, formatstr): 46 | self.name = name 47 | self.formatstr = formatstr 48 | def visit_Attribute(self, node): 49 | # replace a.b 50 | if isinstance(node.value, ast.Name) and node.value.id == self.name: 51 | return ast.Name(id=formatstr.format(node.attr), ctx=node.ctx) 52 | # else if we're at the (a.b).c level, we need to recurse into (a.b), 53 | # replace there if needed, and then add the attribute c 54 | elif isinstance(node.value, ast.Attribute): 55 | return ast.Attribute(value=self.visit(node.value), attr=node.attr, ctx=node.ctx) 56 | # else: nothing to change 57 | else: return node 58 | return ast.unparse(ast.fix_missing_locations(AttributeDereferencer(name, formatstr).visit(fdef))) 59 | -------------------------------------------------------------------------------- /gamehop/lists.py: -------------------------------------------------------------------------------- 1 | # the following built-ins and library functions should also work just fine 2 | # although many return iterators/generators instead of tuples. Syntactically this 3 | # doesn't matter. It can have problems with equality checking, though. range(0,2) != (0,1) 4 | # this can be solved by wrapping in tuple() like tuple(range(0,2)), which is the official python way 5 | # to get a tuple from a generator/iterator. 6 | # range() 7 | # len() 8 | # min() 9 | # max() 10 | # map() 11 | # filter() 12 | # itertools.* 13 | # functools.reduce() 14 | # 15 | # some possible things to add: 16 | # in(thelist, item) x in L 17 | # not_in(thelist, item) x not in L 18 | # repeat(thelist, n) thelist * n 19 | # pre tuple() wrapped versions of built-in generator/iterator functions 20 | 21 | 22 | def new_empty_list(): 23 | return () 24 | 25 | def new_filled_list(length, fillitem): 26 | return tuple([ fillitem for i in range(0, length)]) 27 | 28 | def append_item(thelist, item): 29 | return thelist + (item,) 30 | 31 | def set_item(thelist, index, item): 32 | l = list(thelist) 33 | l[index] = item 34 | return tuple(l) 35 | 36 | def get_item(thelist, index): 37 | return thelist[index] 38 | 39 | def index_of(thelist, item): 40 | return thelist.index(item) 41 | 42 | def concat(l1, l2): 43 | return l1 + l2 44 | 45 | def slice(thelist, start, end, step = 1): 46 | return thelist[start:end:step] 47 | -------------------------------------------------------------------------------- /gamehop/primitives/Crypto.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Annotated, Type 2 | 3 | class Bit(int): pass 4 | class UniformlyRandom(): pass 5 | class Reject(): pass 6 | 7 | class BitString(): 8 | # def __xor__(self, other: ByteString) -> ByteString: pass 9 | def __xor__(self, other): pass 10 | @staticmethod 11 | def uniformly_random(length: int): pass 12 | 13 | T = TypeVar('T') 14 | def UniformlySample(s: Type[T]) -> Annotated[T, UniformlyRandom]: pass 15 | 16 | class Scheme(): pass 17 | class Adversary(): 18 | def __init__(self, scheme: Type[Scheme]) -> None: pass 19 | class Reduction(Adversary): 20 | def __init__(self, scheme: Type[Scheme], inner_adversary: Adversary) -> None: pass 21 | 22 | class AbstractGame(): 23 | def main(self) -> Bit: pass 24 | 25 | class Game(AbstractGame): 26 | def __init__(self, Scheme: Type[Scheme], Adversary: Type[Adversary]) -> None: pass 27 | 28 | class GameParameterizedByBit(AbstractGame): 29 | def __init__(self, Scheme: Type[Scheme], Adversary: Type[Adversary], b: Bit) -> None: pass 30 | 31 | class Experiment(): 32 | def get_primitive_name(self) -> str: pass 33 | def get_experiment_name(self) -> str: pass 34 | def get_target_game(self) -> Type[Game]: pass 35 | def get_adversary(self) -> Type[Adversary]: pass 36 | 37 | class DistinguishingExperiment(Experiment): 38 | def get_adversary(self): return self.adversary 39 | def get_left(self): raise NotImplementedError() 40 | def get_right(self): raise NotImplementedError() 41 | 42 | class DistinguishingExperimentLeftOrRight(DistinguishingExperiment): 43 | def __init__(self, primitive_name: str, experiment_name: str, left: Type[Game], right: Type[Game], adversary: Type[Adversary]): 44 | self.primitive_name = primitive_name 45 | self.experiment_name = experiment_name 46 | self.left = left 47 | self.right = right 48 | self.adversary = adversary 49 | def get_primitive_name(self): return self.primitive_name 50 | def get_experiment_name(self): return self.experiment_name 51 | def get_target_game(self): return self.left 52 | def get_left(self): return self.left 53 | def get_right(self): return self.right 54 | 55 | class DistinguishingExperimentRealOrRandom(DistinguishingExperiment): 56 | def __init__(self, primitive_name: str, experiment_name: str, real: Type[Game], random: Type[Game], adversary: Type[Adversary]): 57 | self.primitive_name = primitive_name 58 | self.experiment_name = experiment_name 59 | self.real = real 60 | self.random = random 61 | self.adversary = adversary 62 | def get_primitive_name(self): return self.primitive_name 63 | def get_experiment_name(self): return self.experiment_name 64 | def get_target_game(self): return self.real 65 | def get_left(self): return self.real 66 | def get_right(self): return self.random 67 | 68 | class DistinguishingExperimentHiddenBit(DistinguishingExperiment): 69 | def __init__(self, primitive_name: str, experiment_name: str, game: Type[GameParameterizedByBit], adversary: Type[Adversary]): 70 | self.primitive_name = primitive_name 71 | self.experiment_name = experiment_name 72 | self.game = game 73 | self.adversary = adversary 74 | def get_primitive_name(self): return self.primitive_name 75 | def get_experiment_name(self): return self.experiment_name 76 | def get_target_game(self): return self.game 77 | 78 | class WinLoseExperiment(Experiment): 79 | def __init__(self, primitive_name: str, experiment_name: str, game: Type[Game], adversary: Type[Adversary]): 80 | self.primitive_name = primitive_name 81 | self.experiment_name = experiment_name 82 | self.game = game 83 | self.adversary = adversary 84 | def get_primitive_name(self): return self.primitive_name 85 | def get_experiment_name(self): return self.experiment_name 86 | def get_target_game(self): return self.game 87 | losing_game = """class LosingGame(Crypto.Game): 88 | def main(self) -> Crypto.Bit: 89 | return Crypto.Bit(0)""" 90 | -------------------------------------------------------------------------------- /gamehop/primitives/KDF.py: -------------------------------------------------------------------------------- 1 | from typing import Annotated, Callable, Generic, Sized, Tuple, Type, TypeVar 2 | 3 | from . import Crypto 4 | from .. import lists 5 | 6 | Key = TypeVar('Key') 7 | Output = TypeVar('Output', bound=Sized) 8 | 9 | class KDFScheme(Crypto.Scheme, Generic[Key, Output]): 10 | @staticmethod 11 | def uniformKey() -> Annotated[Key, Crypto.UniformlyRandom]: pass 12 | @staticmethod 13 | def uniformOutput(outlen: int) -> Annotated[Output, Crypto.UniformlyRandom]: pass 14 | @staticmethod 15 | def Eval(k: Key, label: str, outlen: int) -> Output: pass 16 | 17 | class ROR_Adversary(Crypto.Adversary, Generic[Key, Output]): 18 | def run(self, o_eval: Callable[[str, int], Output]) -> Crypto.Bit: pass 19 | 20 | class ROR_Real(Crypto.Game, Generic[Key, Output]): 21 | def __init__(self, Scheme: Type[KDFScheme[Key, Output]], Adversary: Type[ROR_Adversary[Key, Output]]): 22 | self.Scheme = Scheme 23 | self.adversary = Adversary(Scheme) 24 | def main(self) -> Crypto.Bit: 25 | self.k = self.Scheme.uniformKey() 26 | r = self.adversary.run(self.o_eval) 27 | return r 28 | def o_eval(self, label: str, outlen: int) -> Output: 29 | return self.Scheme.Eval(self.k, label, outlen) 30 | 31 | class ROR_Random(Crypto.Game, Generic[Key, Output]): 32 | def __init__(self, Scheme: Type[KDFScheme[Key, Output]], Adversary: Type[ROR_Adversary[Key, Output]]): 33 | self.Scheme = Scheme 34 | self.adversary = Adversary(Scheme) 35 | def main(self) -> Crypto.Bit: 36 | self.query_list = lists.new_empty_list() 37 | r = self.adversary.run(self.o_eval) 38 | return r 39 | def o_eval(self, label: str, outlen: int) -> Output: 40 | if (label, outlen) not in self.query_list: 41 | self.query_list = lists.set_item(self.query_list, (label, outlen), self.Scheme.uniformOutput(outlen)) 42 | return self.query_list.get_item((label, outlen)) 43 | 44 | ROR = Crypto.DistinguishingExperimentRealOrRandom("KDF", "ROR", ROR_Real, ROR_Random, ROR_Adversary) 45 | 46 | class ROR1_Adversary(Crypto.Adversary, Generic[Key, Output]): 47 | def challenge(self) -> Tuple[str, int]: pass 48 | def guess(self, v: Output) -> Crypto.Bit: pass 49 | 50 | class ROR1_Real(Crypto.Game, Generic[Key, Output]): 51 | def __init__(self, Scheme: Type[KDFScheme[Key, Output]], Adversary: Type[ROR1_Adversary[Key, Output]]): 52 | self.Scheme = Scheme 53 | self.adversary = Adversary(Scheme) 54 | def main(self) -> Crypto.Bit: 55 | self.k = self.Scheme.uniformKey() 56 | (label, outlen) = self.adversary.challenge() 57 | v = self.Scheme.Eval(self.k, label, outlen) 58 | r = self.adversary.guess(v) 59 | return r 60 | 61 | class ROR1_Random(Crypto.Game, Generic[Key, Output]): 62 | def __init__(self, Scheme: Type[KDFScheme[Key, Output]], Adversary: Type[ROR1_Adversary[Key, Output]]): 63 | self.Scheme = Scheme 64 | self.adversary = Adversary(Scheme) 65 | def main(self) -> Crypto.Bit: 66 | self.k = self.Scheme.uniformKey() 67 | (label, outlen) = self.adversary.challenge() 68 | v = self.Scheme.uniformOutput(outlen) 69 | r = self.adversary.guess(v) 70 | return r 71 | 72 | ROR1 = Crypto.DistinguishingExperimentRealOrRandom("KDF", "ROR1", ROR1_Real, ROR1_Random, ROR1_Adversary) 73 | -------------------------------------------------------------------------------- /gamehop/primitives/KEM.py: -------------------------------------------------------------------------------- 1 | from typing import Annotated, Generic, Optional, Tuple, Type, TypeVar 2 | 3 | from . import Crypto 4 | 5 | PublicKey = TypeVar('PublicKey') 6 | SecretKey = TypeVar('SecretKey') 7 | Ciphertext = TypeVar('Ciphertext') 8 | SharedSecret = TypeVar('SharedSecret') 9 | 10 | class KEMScheme(Crypto.Scheme, Generic[PublicKey, SecretKey, Ciphertext, SharedSecret]): 11 | @staticmethod 12 | def uniformSharedSecret() -> Annotated[SharedSecret, Crypto.UniformlyRandom]: pass 13 | @staticmethod 14 | def KeyGen() -> Tuple[PublicKey, SecretKey]: pass 15 | @staticmethod 16 | def Encaps(pk: PublicKey) -> Tuple[Ciphertext, SharedSecret]: pass 17 | @staticmethod 18 | def Decaps(sk: SecretKey, ct: Ciphertext) -> Optional[SharedSecret]: pass 19 | 20 | class INDCPA_Adversary(Crypto.Adversary, Generic[PublicKey, SecretKey, Ciphertext, SharedSecret]): 21 | def guess(self, pk: PublicKey, ct: Ciphertext, ss: SharedSecret) -> Crypto.Bit: pass 22 | 23 | class INDCPA_Real(Crypto.Game, Generic[PublicKey, SecretKey, Ciphertext, SharedSecret]): 24 | def __init__(self, Scheme: Type[KEMScheme[PublicKey, SecretKey, Ciphertext, SharedSecret]], Adversary: Type[INDCPA_Adversary[PublicKey, SecretKey, Ciphertext, SharedSecret]]): 25 | self.Scheme = Scheme 26 | self.adversary = Adversary(Scheme) 27 | def main(self) -> Crypto.Bit: 28 | (pk, sk) = self.Scheme.KeyGen() 29 | (ct, ss_real) = self.Scheme.Encaps(pk) 30 | r = self.adversary.guess(pk, ct, ss_real) 31 | return r 32 | 33 | class INDCPA_Random(Crypto.Game, Generic[PublicKey, SecretKey, Ciphertext, SharedSecret]): 34 | def __init__(self, Scheme: Type[KEMScheme[PublicKey, SecretKey, Ciphertext, SharedSecret]], Adversary: Type[INDCPA_Adversary[PublicKey, SecretKey, Ciphertext, SharedSecret]]): 35 | self.Scheme = Scheme 36 | self.adversary = Adversary(Scheme) 37 | def main(self) -> Crypto.Bit: 38 | (pk, sk) = self.Scheme.KeyGen() 39 | (ct, _) = self.Scheme.Encaps(pk) 40 | ss_rand = self.Scheme.uniformSharedSecret() 41 | r = self.adversary.guess(pk, ct, ss_rand) 42 | return r 43 | 44 | INDCPA = Crypto.DistinguishingExperimentRealOrRandom("KEM", "INDCPA", INDCPA_Real, INDCPA_Random, INDCPA_Adversary) 45 | 46 | # an alternative formulation of INDCPA using a hidden bit 47 | # class INDCPA_HiddenBit(Crypto.Game): 48 | # def main(self, kem: KEMScheme, Adversary: Type[INDCPA_Adversary], b: Crypto.Bit) -> Crypto.Bit: 49 | # adversary = Adversary(kem) 50 | # (pk, sk) = kem.KeyGen() 51 | # (ct, ss_real) = kem.Encaps(pk) 52 | # ss_rand = Crypto.UniformlySample(SharedSecret) 53 | # ss_challenge = ss_real if b == 0 else ss_rand 54 | # return adversary.guess(pk, ct, ss_challenge) 55 | # 56 | # INDCPAv2 = Crypto.DistinguishingExperimentHiddenBit(INDCPA_HiddenBit, INDCPA_Adversary) 57 | -------------------------------------------------------------------------------- /gamehop/primitives/OTP.py: -------------------------------------------------------------------------------- 1 | from typing import Generic, Sized, Tuple, Type, TypeVar 2 | 3 | from . import Crypto 4 | 5 | Key = TypeVar('Key') 6 | Message = TypeVar('Message', bound=Sized) 7 | Ciphertext = TypeVar('Ciphertext', bound=Sized) 8 | 9 | class OTPScheme(Crypto.Scheme, Generic[Key, Message, Ciphertext]): 10 | @staticmethod 11 | def KeyGen(keylen: int) -> Key: pass 12 | @staticmethod 13 | def Encrypt(key: Key, msg: Message) -> Ciphertext: pass 14 | @staticmethod 15 | def Decrypt(key: Key, ctxt: Ciphertext) -> Message: pass 16 | 17 | class IND_Adversary(Crypto.Adversary, Generic[Key, Message, Ciphertext]): 18 | def challenge(self) -> Tuple[Message, Message]: pass 19 | def guess(self, ct: Ciphertext) -> Crypto.Bit: pass 20 | 21 | class IND_Left(Crypto.Game, Generic[Key, Message, Ciphertext]): 22 | def __init__(self, Scheme: Type[OTPScheme[Key, Message, Ciphertext]], Adversary: Type[IND_Adversary[Key, Message, Ciphertext]]): 23 | self.Scheme = Scheme 24 | self.adversary = Adversary(Scheme) 25 | def main(self) -> Crypto.Bit: 26 | (m0, m1) = self.adversary.challenge() 27 | k = self.Scheme.KeyGen(len(m0)) 28 | ct = self.Scheme.Encrypt(k, m0) 29 | r = self.adversary.guess(ct) 30 | ret = r if len(m0) == len(m1) else Crypto.Bit(0) 31 | return ret 32 | 33 | class IND_Right(Crypto.Game, Generic[Key, Message, Ciphertext]): 34 | def __init__(self, Scheme: Type[OTPScheme[Key, Message, Ciphertext]], Adversary: Type[IND_Adversary[Key, Message, Ciphertext]]): 35 | self.Scheme = Scheme 36 | self.adversary = Adversary(Scheme) 37 | def main(self) -> Crypto.Bit: 38 | (m0, m1) = self.adversary.challenge() 39 | k = self.Scheme.KeyGen(len(m1)) 40 | ct = self.Scheme.Encrypt(k, m1) 41 | r = self.adversary.guess(ct) 42 | ret = r if len(m0) == len(m1) else Crypto.Bit(0) 43 | return ret 44 | 45 | IND = Crypto.DistinguishingExperimentLeftOrRight("OTP", "IND", IND_Left, IND_Right, IND_Adversary) 46 | 47 | class ROR_Adversary(Crypto.Adversary, Generic[Key, Message, Ciphertext]): 48 | def challenge(self) -> Message: pass 49 | def guess(self, ct: Ciphertext) -> Crypto.Bit: pass 50 | 51 | class ROR_Real(Crypto.Game, Generic[Key, Message, Ciphertext]): 52 | def __init__(self, Scheme: Type[OTPScheme[Key, Message, Ciphertext]], Adversary: Type[ROR_Adversary[Key, Message, Ciphertext]]): 53 | self.Scheme = Scheme 54 | self.adversary = Adversary(Scheme) 55 | def main(self) -> Crypto.Bit: 56 | m = self.adversary.challenge() 57 | k = self.Scheme.KeyGen(len(m)) 58 | ct = self.Scheme.Encrypt(k, m) 59 | r = self.adversary.guess(ct) 60 | return r 61 | 62 | class ROR_Random(Crypto.Game, Generic[Key, Message, Ciphertext]): 63 | def __init__(self, Scheme: Type[OTPScheme[Key, Message, Ciphertext]], Adversary: Type[ROR_Adversary[Key, Message, Ciphertext]]): 64 | self.Scheme = Scheme 65 | self.adversary = Adversary(Scheme) 66 | def main(self) -> Crypto.Bit: 67 | m = self.adversary.challenge() 68 | ct = Crypto.BitString.uniformly_random(len(m)) 69 | r = self.adversary.guess(ct) 70 | return r 71 | 72 | ROR = Crypto.DistinguishingExperimentLeftOrRight("OTP", "ROR", ROR_Real, ROR_Random, ROR_Adversary) 73 | -------------------------------------------------------------------------------- /gamehop/primitives/PKE.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Generic, Optional, Sized, Tuple, Type, TypeVar 2 | 3 | from . import Crypto 4 | 5 | PublicKey = TypeVar('PublicKey') 6 | SecretKey = TypeVar('SecretKey') 7 | Ciphertext = TypeVar('Ciphertext') 8 | Message = TypeVar('Message', bound=Sized) 9 | 10 | class PKEScheme(Crypto.Scheme, Generic[PublicKey, SecretKey, Ciphertext, Message]): 11 | @staticmethod 12 | def KeyGen() -> Tuple[PublicKey, SecretKey]: pass 13 | @staticmethod 14 | def Encrypt(pk: PublicKey, msg: Message) -> Ciphertext: pass 15 | @staticmethod 16 | def Decrypt(sk: SecretKey, ct: Ciphertext) -> Optional[Message]: pass 17 | 18 | class INDCPA_Adversary(Crypto.Adversary, Generic[PublicKey, SecretKey, Ciphertext, Message]): 19 | def challenge(self, pk: PublicKey) -> Tuple[Message, Message]: pass 20 | def guess(self, ct: Ciphertext) -> Crypto.Bit: pass 21 | 22 | class INDCPA_Left(Crypto.Game, Generic[PublicKey, SecretKey, Ciphertext, Message]): 23 | def __init__(self, Scheme: Type[PKEScheme[PublicKey, SecretKey, Ciphertext, Message]], Adversary: Type[INDCPA_Adversary[PublicKey, SecretKey, Ciphertext, Message]]): 24 | self.Scheme = Scheme 25 | self.adversary = Adversary(Scheme) 26 | def main(self) -> Crypto.Bit: 27 | (pk, sk) = self.Scheme.KeyGen() 28 | (m0, m1) = self.adversary.challenge(pk) 29 | ct = self.Scheme.Encrypt(pk, m0) 30 | r = self.adversary.guess(ct) 31 | if len(m0) == len(m1): ret = r 32 | else: ret = Crypto.Bit(0) 33 | return ret 34 | 35 | class INDCPA_Right(Crypto.Game, Generic[PublicKey, SecretKey, Ciphertext, Message]): 36 | def __init__(self, Scheme: Type[PKEScheme[PublicKey, SecretKey, Ciphertext, Message]], Adversary: Type[INDCPA_Adversary[PublicKey, SecretKey, Ciphertext, Message]]): 37 | self.Scheme = Scheme 38 | self.adversary = Adversary(Scheme) 39 | def main(self) -> Crypto.Bit: 40 | (pk, sk) = self.Scheme.KeyGen() 41 | (m0, m1) = self.adversary.challenge(pk) 42 | ct = self.Scheme.Encrypt(pk, m1) 43 | r = self.adversary.guess(ct) 44 | if len(m0) == len(m1): ret = r 45 | else: ret = Crypto.Bit(0) 46 | return ret 47 | 48 | INDCPA = Crypto.DistinguishingExperimentLeftOrRight("PKE", "INDCPA", INDCPA_Left, INDCPA_Right, INDCPA_Adversary) 49 | 50 | class INDCCA2_Adversary(Crypto.Adversary, Generic[PublicKey, SecretKey, Ciphertext, Message]): 51 | def challenge(self, pk: PublicKey, o_decrypt: Callable[[Ciphertext], Optional[Message]]) -> Tuple[Message, Message]: pass 52 | def guess(self, ct: Ciphertext, o_decrypt: Callable[[Ciphertext], Optional[Message]]) -> Crypto.Bit: pass 53 | 54 | class INDCCA2_Left(Crypto.Game, Generic[PublicKey, SecretKey, Ciphertext, Message]): 55 | def __init__(self, Scheme: Type[PKEScheme[PublicKey, SecretKey, Ciphertext, Message]], Adversary: Type[INDCCA2_Adversary[PublicKey, SecretKey, Ciphertext, Message]]): 56 | self.Scheme = Scheme 57 | self.adversary = Adversary(Scheme) 58 | def main(self) -> Crypto.Bit: 59 | (pk, sk) = self.Scheme.KeyGen() 60 | self.sk = sk 61 | self.ctstar = None 62 | (m0, m1) = self.adversary.challenge(pk, self.o_decrypt) 63 | self.ctstar = self.Scheme.Encrypt(pk, m0) 64 | r = self.adversary.guess(self.ctstar, self.o_decrypt) 65 | ret = r if len(m0) == len(m1) else Crypto.Bit(0) 66 | return ret 67 | def o_decrypt(self, ct: Ciphertext) -> Optional[Message]: 68 | ret: Optional[Message] = None 69 | if ct != self.ctstar: ret = self.Scheme.Decrypt(self.sk, ct) 70 | return ret 71 | 72 | class INDCCA2_Right(Crypto.Game, Generic[PublicKey, SecretKey, Ciphertext, Message]): 73 | def __init__(self, Scheme: Type[PKEScheme[PublicKey, SecretKey, Ciphertext, Message]], Adversary: Type[INDCCA2_Adversary[PublicKey, SecretKey, Ciphertext, Message]]): 74 | self.Scheme = Scheme 75 | self.adversary = Adversary(Scheme) 76 | def main(self) -> Crypto.Bit: 77 | (pk, sk) = self.Scheme.KeyGen() 78 | self.sk = sk 79 | self.ctstar = None 80 | (m0, m1) = self.adversary.challenge(pk, self.o_decrypt) 81 | self.ctstar = self.Scheme.Encrypt(pk, m1) 82 | r = self.adversary.guess(self.ctstar, self.o_decrypt) 83 | ret = r if len(m0) == len(m1) else Crypto.Bit(0) 84 | return ret 85 | def o_decrypt(self, ct: Ciphertext) -> Optional[Message]: 86 | ret: Optional[Message] = None 87 | if ct != self.ctstar: ret = self.Scheme.Decrypt(self.sk, ct) 88 | return ret 89 | 90 | INDCCA2 = Crypto.DistinguishingExperimentLeftOrRight("PKE", "INDCCA2", INDCCA2_Left, INDCCA2_Right, INDCCA2_Adversary) 91 | -------------------------------------------------------------------------------- /gamehop/primitives/SymEnc.py: -------------------------------------------------------------------------------- 1 | from typing import Annotated, Callable, Generic, Type, TypeVar 2 | 3 | from . import Crypto 4 | from .. import lists 5 | 6 | Key = TypeVar('Key') 7 | Message = TypeVar('Message') 8 | Ciphertext = TypeVar('Ciphertext') 9 | 10 | class SymEncScheme(Crypto.Scheme, Generic[Key, Message, Ciphertext]): 11 | @staticmethod 12 | def uniformKey() -> Annotated[Key, Crypto.UniformlyRandom]: pass 13 | @staticmethod 14 | def uniformCiphertext() -> Annotated[Ciphertext, Crypto.UniformlyRandom]: pass 15 | @staticmethod 16 | def Encrypt(key: Key, msg: Message) -> Ciphertext: pass 17 | @staticmethod 18 | def Decrypt(key: Key, ctxt: Ciphertext) -> Message: pass 19 | 20 | class INDCPA_Adversary(Crypto.Adversary, Generic[Key, Message, Ciphertext]): 21 | def run(self, o_eavesdrop: Callable[[Message, Message], Ciphertext]) -> Crypto.Bit: pass 22 | 23 | class INDCPA_Left(Crypto.Game, Generic[Key, Message, Ciphertext]): 24 | def __init__(self, Scheme: Type[SymEncScheme[Key, Message, Ciphertext]], Adversary: Type[INDCPA_Adversary[Key, Message, Ciphertext]]): 25 | self.Scheme = Scheme 26 | self.adversary = Adversary(Scheme) 27 | def main(self) -> Crypto.Bit: 28 | self.k = self.Scheme.uniformKey() 29 | r = self.adversary.run(self.o_eavesdrop) 30 | return r 31 | def o_eavesdrop(self, msg_L: Message, msg_R: Message) -> Ciphertext: 32 | c = self.Scheme.Encrypt(self.k, msg_L) 33 | return c 34 | 35 | class INDCPA_Right(Crypto.Game, Generic[Key, Message, Ciphertext]): 36 | def __init__(self, Scheme: Type[SymEncScheme[Key, Message, Ciphertext]], Adversary: Type[INDCPA_Adversary[Key, Message, Ciphertext]]): 37 | self.Scheme = Scheme 38 | self.adversary = Adversary(Scheme) 39 | def main(self) -> Crypto.Bit: 40 | self.k = self.Scheme.uniformKey() 41 | r = self.adversary.run(self.o_eavesdrop) 42 | return r 43 | def o_eavesdrop(self, msg_L: Message, msg_R: Message) -> Ciphertext: 44 | c = self.Scheme.Encrypt(self.k, msg_R) 45 | return c 46 | 47 | INDCPA = Crypto.DistinguishingExperimentLeftOrRight("SymEnc", "INDCPA", INDCPA_Left, INDCPA_Right, INDCPA_Adversary) 48 | 49 | class INDCPADollar_Adversary(Crypto.Adversary, Generic[Key, Message, Ciphertext]): 50 | def run(self, o_ctxt: Callable[[Message], Ciphertext]) -> Crypto.Bit: pass 51 | 52 | class INDCPADollar_Real(Crypto.Game, Generic[Key, Message, Ciphertext]): 53 | def __init__(self, Scheme: Type[SymEncScheme[Key, Message, Ciphertext]], Adversary: Type[INDCPADollar_Adversary[Key, Message, Ciphertext]]): 54 | self.Scheme = Scheme 55 | self.adversary = Adversary(Scheme) 56 | def main(self) -> Crypto.Bit: 57 | self.k = self.Scheme.uniformKey() 58 | r = self.adversary.run(self.o_ctxt) 59 | return r 60 | def o_ctxt(self, msg: Message) -> Ciphertext: 61 | c = self.Scheme.Encrypt(self.k, msg) 62 | return c 63 | 64 | class INDCPADollar_Random(Crypto.Game, Generic[Key, Message, Ciphertext]): 65 | def __init__(self, Scheme: Type[SymEncScheme[Key, Message, Ciphertext]], Adversary: Type[INDCPADollar_Adversary[Key, Message, Ciphertext]]): 66 | self.Scheme = Scheme 67 | self.adversary = Adversary(Scheme) 68 | def main(self) -> Crypto.Bit: 69 | self.k = self.Scheme.uniformKey() 70 | r = self.adversary.run(self.o_ctxt) 71 | return r 72 | def o_ctxt(self, msg: Message) -> Ciphertext: 73 | c = self.Scheme.uniformCiphertext() 74 | return c 75 | 76 | INDCPADollar = Crypto.DistinguishingExperimentRealOrRandom("SymEnc", "INDCPADollar", INDCPADollar_Real, INDCPADollar_Random, INDCPADollar_Adversary) 77 | -------------------------------------------------------------------------------- /gamehop/primitives/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ['Crypto', 'KEM', 'PKE'] 2 | -------------------------------------------------------------------------------- /gamehop/templates/tikz_figure.tex: -------------------------------------------------------------------------------- 1 | \documentclass{standalone} 2 | 3 | \usepackage{tikz} 4 | \usetikzlibrary{shapes} 5 | \usetikzlibrary{decorations.pathreplacing} 6 | 7 | \pagecolor{white} 8 | 9 | \begin{document} 10 | 11 | \begin{tikzpicture}[ 12 | game/.style={draw,text width=3cm,text centered}, 13 | reduction/.style={draw,thick,text width=3cm,text centered,ellipse,inner sep=-3pt}, 14 | isequivalentto/.style={<->}, 15 | iscomputationallyindistuishable/.style={<->,dashed}, 16 | mainarrowlabel/.style={text width=2.7cm,text centered,font=\scriptsize}, 17 | xscale=3, 18 | yscale=3, 19 | ] 20 | 21 | % STARTING GAME 22 | ≤% if proof.experiment|isinstance("Crypto.DistinguishingExperiment") %≥ 23 | \node [game] (startinggame) at (0,0) {\textbf{Starting game:} \\ \texttt{≤≤ proof.scheme|classname|texify ≥≥} \\ inlined into \\ \texttt{≤≤ proof.experiment.get_left()|classname|texify ≥≥}}; 24 | ≤% endif %≥ 25 | 26 | % ITERATE THROUGH PROOF STEPS 27 | ≤% for proofstep in proof.proof_steps %≥ 28 | 29 | ≤% if proofstep|type == "DistinguishingProofStep" %≥ 30 | 31 | % BOX FOR THE HOP'S LEFT COMPONENT 32 | \node [game] 33 | (hop≤≤ loop.index ≥≥left) 34 | at (≤≤ loop.index * 4 - 2 ≥≥,0) 35 | {\textbf{Hop ≤≤ loop.index ≥≥:} \\ \texttt{≤≤ proofstep.reduction|classname|texify ≥≥} \\ inlined into \\ \texttt{≤≤ proofstep.experiment.get_right()|classname|texify if proofstep.reverseDirection else proofstep.experiment.get_left()|classname|texify ≥≥}}; 36 | 37 | % BOX FOR THE HOP'S RIGHT COMPONENT 38 | \node [game] 39 | (hop≤≤ loop.index ≥≥right) 40 | at (≤≤ loop.index * 4 ≥≥,0) 41 | {\textbf{Hop ≤≤ loop.index ≥≥:} \\ \texttt{≤≤ proofstep.reduction|classname|texify ≥≥} \\ inlined into \\ \texttt{≤≤ proofstep.experiment.get_left()|classname|texify if proofstep.reverseDirection else proofstep.experiment.get_right()|classname|texify ≥≥}}; 42 | 43 | ≤% elif proofstep|type == "RewritingStep" %≥ 44 | 45 | % BOX FOR THE HOP'S LEFT COMPONENT 46 | \node [game] 47 | (hop≤≤ loop.index ≥≥left) 48 | at (≤≤ loop.index * 4 - 2 ≥≥,0) 49 | {\textbf{Hop ≤≤ loop.index ≥≥:} \\ rewriting step \\ (before rewrite) \\ \ }; 50 | 51 | % BOX FOR THE HOP'S RIGHT COMPONENT 52 | \node [game] 53 | (hop≤≤ loop.index ≥≥right) 54 | at (≤≤ loop.index * 4 ≥≥,0) 55 | {\textbf{Hop ≤≤ loop.index ≥≥:} \\ rewriting step \\ (after rewrite) \\ \ }; 56 | 57 | ≤% endif %≥ 58 | 59 | ≤% if loop.index0 == 0 %≥ 60 | 61 | % ARROW BETWEEN STARTING GAME AND HOP 1 LEFT 62 | \draw [isequivalentto] 63 | (startinggame) 64 | -- 65 | node[mainarrowlabel,above] {code-wise equivalent} 66 | (hop1left); 67 | % BRACE UNDER GAME 0 68 | \draw [decorate,decoration={brace,amplitude=10pt,mirror}] 69 | (startinggame.south west) -- (hop≤≤ loop.index ≥≥left.south east) 70 | node[black,midway,yshift=-0.6cm] {game ≤≤ loop.index0 ≥≥}; 71 | 72 | ≤% else %≥ 73 | 74 | % ARROW BETWEEN PREVIOUS GAME AND THIS GAME LEFT 75 | \draw [isequivalentto] 76 | (hop≤≤ loop.index-1 ≥≥right) 77 | -- 78 | node[mainarrowlabel,above] {code-wise equivalent} 79 | (hop≤≤ loop.index ≥≥left); 80 | % BRACE UNDER THIS GAME 81 | \draw [decorate,decoration={brace,amplitude=10pt,mirror}] 82 | (hop≤≤ loop.index - 1 ≥≥right.south west) -- (hop≤≤ loop.index ≥≥left.south east) 83 | node[black,midway,yshift=-0.6cm] {game ≤≤ loop.index0 ≥≥}; 84 | 85 | ≤% endif %≥ 86 | 87 | ≤% if proofstep|type == 'DistinguishingProofStep' %≥ 88 | 89 | % NODE FOR THE REDUCTION 90 | \node [reduction] 91 | (reduction≤≤ loop.index ≥≥) 92 | at (≤≤ loop.index * 4 - 1 ≥≥,1) 93 | {\texttt{≤≤ proofstep.reduction|classname|texify ≥≥:} \\ \texttt{≤≤ proofstep.experiment.get_primitive_name()|texify ≥≥.≤≤ proofstep.experiment.get_experiment_name()|texify ≥≥} \\ adversary against \\ \texttt{≤≤ proofstep.schemeName|texify ≥≥}}; 94 | 95 | % ARROWS FROM THE REDUCTION TO THE MAIN LINE GAMES 96 | \draw [->] (reduction≤≤ loop.index ≥≥) -- (hop≤≤ loop.index ≥≥left); 97 | \draw [->] (reduction≤≤ loop.index ≥≥) -- (hop≤≤ loop.index ≥≥right); 98 | 99 | % ARROW BETWEEN THE MAIN LINE GAMES 100 | \draw [iscomputationallyindistuishable] 101 | (hop≤≤ loop.index ≥≥left) 102 | -- 103 | node[mainarrowlabel,above] {computationally \\ indistinguishable} 104 | node[mainarrowlabel,below] {$\mathrm{Adv}^{\texttt{≤≤ proofstep.experiment.get_primitive_name()|texify ≥≥.≤≤ proofstep.experiment.get_experiment_name()|texify ≥≥}}_{\texttt{≤≤ proofstep.schemeName|texify ≥≥}}(\texttt{≤≤ proofstep.reduction|classname|texify ≥≥})$} 105 | (hop≤≤ loop.index ≥≥right); 106 | 107 | ≤% elif proofstep|type == 'RewritingStep' %≥ 108 | 109 | % ARROW BETWEEN THE MAIN LINE GAMES 110 | \draw [iscomputationallyindistuishable] 111 | (hop≤≤ loop.index ≥≥left) 112 | -- 113 | node[mainarrowlabel,above] {equivalence \\ manually checked} 114 | (hop≤≤ loop.index ≥≥right); 115 | 116 | ≤% endif %≥ 117 | 118 | ≤% endfor %≥ 119 | 120 | % ENDING GAME 121 | \node [game] (endinggame) at (≤≤ proof.proof_steps|length * 4 + 2 ≥≥,0) {\textbf{Ending game:} \\ \texttt{≤≤ proof.scheme|classname|texify ≥≥} \\ inlined into \\ \texttt{≤≤ proof.experiment.get_right()|classname|texify ≥≥}}; 122 | 123 | % ARROW BETWEEN PREVIOUS GAME RIGHT AND THE ENDING GAME 124 | \draw [isequivalentto] (hop≤≤ proof.proof_steps|length ≥≥right) -- node[mainarrowlabel,above] {code-wise equivalent} (endinggame); 125 | 126 | % BRACE UNDER ENDING GAME 127 | \draw [decorate,decoration={brace,amplitude=10pt,mirror}] 128 | (hop≤≤ proof.proof_steps|length ≥≥right.south west) -- (endinggame.south east) 129 | node[black,midway,yshift=-0.6cm] {game ≤≤ proof.proof_steps|length ≥≥}; 130 | 131 | \end{tikzpicture} 132 | 133 | \end{document} 134 | -------------------------------------------------------------------------------- /gamehop/verification/__init__.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import copy 3 | import inspect 4 | import random 5 | import re 6 | 7 | from typing import Any, Callable, Dict, List, Set, Type, Union 8 | from types import FunctionType 9 | 10 | from . import canonicalization 11 | from .. import utils 12 | from .canonicalization import expand 13 | from .canonicalization import simplify 14 | from .canonicalization import ifstatements 15 | from .canonicalization.classes import unnecessary_members 16 | 17 | def debug_helper(x, label): 18 | if False: # change this to True to print some debugging info 19 | print("======================") 20 | print("after {:s}".format(label)) 21 | print(ast.unparse(x)) 22 | 23 | def canonicalize_function(f: Union[Callable, str]) -> str: 24 | """Returns a string representing a canonicalized version of the given function. 25 | 26 | It applies the following canonicalizations: 27 | - return statements only return a single variable or a constant 28 | - function name is 'f' 29 | - variable names are 'v0', 'v1', ... 30 | - lines are reordered based on variable dependencies""" 31 | # parse the function 32 | functionDef = utils.get_function_def(f) 33 | assert isinstance(functionDef, ast.FunctionDef) 34 | str_previous = "" 35 | str_current = ast.unparse(ast.fix_missing_locations(functionDef)) 36 | while str_previous != str_current: 37 | str_previous = str_current 38 | # Inline lambdas first so that the inlined expression will be expanded later 39 | canonicalization.inline_lambdas(functionDef) 40 | debug_helper(functionDef, "canonicalization.inline_lambdas") 41 | ifstatements.if_statements_to_expressions(functionDef) 42 | debug_helper(functionDef, "ifstatements.if_statements_to_expressions") 43 | expand.expand_non_compact_expressions(functionDef) 44 | debug_helper(functionDef, "expand.expand_non_compact_expressions") 45 | # canonicalize function name 46 | canonicalization.canonicalize_function_name(functionDef) 47 | debug_helper(functionDef, "canonicalization.canonicalize_function_name") 48 | canonicalization.collapse_useless_assigns(functionDef) 49 | debug_helper(functionDef, "canonicalization.collapse_useless_assigns") 50 | canonicalization.simplify.simplify(functionDef) 51 | debug_helper(functionDef, "canonicalization.simplify.simplify") 52 | canonicalization.canonicalize_line_order(functionDef) 53 | debug_helper(functionDef, "canonicalization.canonicalize_line_order") 54 | canonicalization.canonicalize_argument_order(functionDef) 55 | debug_helper(functionDef, "canonicalization.canonicalize_argument_order") 56 | canonicalization.canonicalize_variable_names(functionDef) 57 | debug_helper(functionDef, "canonicalization.canonicalize_variable_names") 58 | str_current = ast.unparse(ast.fix_missing_locations(functionDef)) 59 | return str_current 60 | 61 | def canonicalize_game(c: Union[Type[Any], str, ast.ClassDef]) -> str: 62 | cdef = utils.get_class_def(c) 63 | cdef.name = "G" 64 | str_previous = "" 65 | str_current = ast.unparse(ast.fix_missing_locations(cdef)) 66 | while str_previous != str_current: 67 | str_previous = str_current 68 | # determine which members are used within each function, so that we can pass that 69 | # list of dependencies to canonicalize_line_order 70 | members_in_scope: Dict[str, List[str]] = dict() 71 | for f in cdef.body: 72 | if not isinstance(f, ast.FunctionDef): 73 | raise ValueError(f"Cannot canonicalize games containing anything other than functions; {cdef.name} contains a node of type {type(f).__name__}") 74 | selfname = f.args.args[0].arg 75 | members_in_scope[selfname + "." + f.name] = list() 76 | for v in utils.vars_depends_on(f): 77 | if v.startswith(selfname + "."): 78 | members_in_scope[selfname + "." + f.name].append(v) 79 | for i, f in enumerate(cdef.body): 80 | if not isinstance(f, ast.FunctionDef): 81 | raise ValueError(f"Cannot canonicalize games containing anything other than functions; {cdef.name} contains a node of type {type(f).__name__}") 82 | ifstatements.if_statements_to_expressions(f) 83 | debug_helper(f, "ifstatements.if_statements_to_expressions") 84 | expand.expand_non_compact_expressions(f) 85 | debug_helper(f, "expand.expand_non_compact_expressions") 86 | canonicalization.collapse_useless_assigns(f) 87 | debug_helper(f, "canonicalization.collapse_useless_assigns") 88 | canonicalization.simplify.simplify(f) 89 | debug_helper(f, "canonicalization.simplify.simplify") 90 | if f.name != "__init__": 91 | canonicalization.canonicalize_line_order(f, members_in_scope) 92 | debug_helper(f, "canonicalization.canonicalize_line_order") 93 | canonicalization.canonicalize_variable_names(f) 94 | debug_helper(f, "canonicalization.canonicalize_variable_names") 95 | cdef.body[i] = f 96 | unnecessary_members(cdef) 97 | debug_helper(cdef, "canonicalization.classes.unnecessary_members") 98 | str_current = ast.unparse(ast.fix_missing_locations(cdef)) 99 | return ast.unparse(ast.fix_missing_locations(cdef)) 100 | -------------------------------------------------------------------------------- /gamehop/verification/canonicalization/__init__.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import copy 3 | import secrets 4 | from typing import Dict, List, Union 5 | 6 | from ...inlining import internal 7 | from ... import utils 8 | from ... import node_traverser as nt 9 | from ... import node_graph as ng 10 | from ... import bits 11 | 12 | def canonicalize_function_name(f: ast.FunctionDef, name = 'f') -> None: 13 | """Modify (in place) the given function definition to have a canonical name.""" 14 | f.name = name 15 | ast.fix_missing_locations(f) 16 | 17 | def canonicalize_variable_names(f: ast.FunctionDef, prefix = 'v') -> None: 18 | """Modify (in place) the given function definition to give variables canonical names.""" 19 | # first rename everything to a random string followed by a counter 20 | # then rename them to v0, v1, v2, ... 21 | tmpname = 'tmp_' + secrets.token_hex(10) 22 | # set up the mappings 23 | vars = internal.find_all_variables(f) 24 | mappings_1stpass = dict() 25 | mappings_2ndpass = dict() 26 | for i in range(len(vars)): 27 | mappings_1stpass[vars[i]] = '{:s}{:d}'.format(tmpname, i) 28 | mappings_2ndpass[mappings_1stpass[vars[i]]] = '{:s}{:d}'.format(prefix, i) 29 | utils.rename_function_body_variables(f, mappings_1stpass) 30 | utils.rename_function_body_variables(f, mappings_2ndpass) 31 | ast.fix_missing_locations(f) 32 | 33 | # apparently not used 34 | def contains_name(node: Union[ast.AST, List], name: str) -> bool: 35 | """Determines whether the given node (or list of nodes) contains a variable with the given name.""" 36 | return any( True for n in nt.nodes(node, nodetype = ast.Name) if n.id == name ) 37 | 38 | class VariableCollapser(nt.NodeTraverser): 39 | def visit_Name(self, node): 40 | node = self.generic_visit(node) 41 | if not isinstance(node.ctx, ast.Load): 42 | return node 43 | 44 | if not self.in_scope(node.id): # this includes cases like function names 45 | return node 46 | 47 | value = self.var_value(node.id) 48 | 49 | if isinstance(value, ast.Constant) or isinstance(value, ast.Name) or isinstance(value, ast.Tuple) or isinstance(value, ast.Attribute): 50 | return copy.deepcopy(value) 51 | 52 | return node 53 | 54 | def visit_Attribute(self, node): 55 | node = self.generic_visit(node) 56 | if not isinstance(node.ctx, ast.Load): 57 | return node 58 | 59 | fqn = ".".join(bits.attribute_fqn(node)) 60 | 61 | if not self.in_scope(fqn): # this includes cases like function names 62 | return node 63 | 64 | value = self.var_value(fqn) 65 | 66 | if isinstance(value, ast.Constant) or isinstance(value, ast.Name) or isinstance(value, ast.Tuple) or isinstance(value, ast.Attribute): 67 | return copy.deepcopy(value) 68 | 69 | return node 70 | 71 | 72 | def collapse_useless_assigns(f: ast.FunctionDef) -> None: 73 | """Modify (in place) the given function definition to remove all lines containing tautological/useless assignments. For example, if the code contains a line "x = a" followed by a line "y = x + b", it replaces all subsequent instances of x with a, yielding the single line "y = a + b", up until x is set in another assignment statement. Handles tuples. Doesn't handle any kind of logic involving if statements or loops.""" 74 | 75 | VariableCollapser().visit(f) 76 | ast.fix_missing_locations(f) 77 | 78 | # apparently not used 79 | def assignee_vars(stmt: ast.Assign) -> List[str]: 80 | if len(stmt.targets) != 1: raise NotImplementedError("Cannot handle assignment statements with multiple targets") 81 | if isinstance(stmt.targets[0], ast.Name): return [stmt.targets[0].id] 82 | elif isinstance(stmt.targets[0], ast.Tuple): 83 | ret = list() 84 | for x in stmt.targets[0].elts: 85 | if not isinstance(x, ast.Name): raise NotImplementedError("Cannot handle tuples containing things other than variables") 86 | ret.append(x.id) 87 | return ret 88 | else: raise NotImplementedError("Cannot handle assignments with left sides of the type " + str(type(stmt.targets[0]).__name__)) 89 | 90 | def canonicalize_line_order(f: ast.FunctionDef, extra_dependencies: Dict[str, List[str]] = {}) -> None: 91 | """Modify (in place) the given function definition to canonicalize the order of lines 92 | based on the order in which the returned variable depends on previous lines. Lines 93 | that do not affect the return variable are removed. Assumes that the return statement 94 | is the last statement in the function body""" 95 | G = ng.Graph.from_stmts(f.body, extra_dependencies) 96 | assert isinstance(f.body[-1], ast.Return) 97 | return_stmt = f.body[-1] 98 | G = G.reachable_subgraph([ return_stmt ], True) 99 | G.canonical_sort() 100 | f.body = G.vertices 101 | 102 | class ArgumentReorderer(nt.NodeTraverser): 103 | def visit_FunctionDef(self, node): 104 | # visit the body to get the scope set up 105 | node = self.generic_visit(node) 106 | s = self.local_scope() 107 | node.args.args = [ ast.arg(arg = parname , annotation = parannotation) for parname, parannotation in s.parameters_loaded() ] 108 | return node 109 | 110 | def canonicalize_argument_order(f: ast.FunctionDef) -> None: 111 | """Modify (in place) the given function definition to canonicalize the order of the arguments 112 | based on the order in which the variables appear. Arguments that are not referred to are removed. 113 | Note that this also applies to any inner functions.""" 114 | 115 | ArgumentReorderer().visit(f) 116 | ast.fix_missing_locations(f) 117 | 118 | class LambdaReplacer(nt.NodeTraverser): 119 | def visit_Call(self, node): 120 | node = self.generic_visit(node) 121 | if not isinstance(node.func, ast.Name): return node 122 | if not self.in_scope(node.func.id): return node 123 | lam = self.var_value(node.func.id) 124 | if not isinstance(lam, ast.Lambda): return node 125 | 126 | lamargs = lam.args.args 127 | callargs = node.args 128 | assert len(lamargs) == len(callargs) 129 | lambody = copy.deepcopy(lam.body) 130 | mappings = dict() 131 | for i in range(len(lamargs)): 132 | mappings[lamargs[i].arg] = callargs[i] 133 | return utils.NameNodeReplacer(mappings).visit(lambody) 134 | 135 | 136 | def inline_lambdas(f: ast.FunctionDef) -> None: 137 | """Modify (in place) the given function definition to replace all calls to lambdas with their body.""" 138 | f = LambdaReplacer().visit(f) 139 | ast.fix_missing_locations(f) 140 | -------------------------------------------------------------------------------- /gamehop/verification/canonicalization/classes.py: -------------------------------------------------------------------------------- 1 | import ast 2 | 3 | from ... import node_traverser as nt 4 | from ... import utils 5 | 6 | class MembersUsedInMethod(nt.NodeTraverser): 7 | def visit_FunctionDef(self, node): 8 | self.generic_visit(node) 9 | for d in node.decorator_list: 10 | if isinstance(d, ast.Name) and d.id == 'staticmethod': return [] 11 | if len(node.args.args) == 0: return [] 12 | selfname = node.args.args[0].arg 13 | s = self.local_scope() 14 | return [a for a in s.variables[selfname].attributes] 15 | 16 | def unnecessary_members(c: ast.ClassDef) -> None: 17 | selfattributes = dict() 18 | for fdef in c.body: 19 | if not isinstance(fdef, ast.FunctionDef): continue 20 | selfattributes[fdef] = MembersUsedInMethod().visit(fdef) 21 | for fdef in selfattributes: 22 | usedhere = selfattributes[fdef] 23 | for a in usedhere: 24 | a_used_elsewhere = False 25 | for fdefprime in selfattributes: 26 | if fdefprime == fdef: continue 27 | if a == fdefprime.name: a_used_elsewhere = True 28 | if a in selfattributes[fdefprime]: a_used_elsewhere = True 29 | if not(a_used_elsewhere): 30 | selfname = fdef.args.args[0].arg 31 | fdefnew = utils.AttributeNodeReplacer([selfname, a], f"self_{a}").visit(fdef) 32 | fdef.body = fdefnew.body 33 | ast.fix_missing_locations(c) 34 | -------------------------------------------------------------------------------- /gamehop/verification/canonicalization/expand.py: -------------------------------------------------------------------------------- 1 | import ast 2 | from ... import node_traverser as nt 3 | 4 | class ExpandNonCompactExpressions(nt.NodeTraverser): 5 | # class variable 6 | valid_expression_containers = { 7 | ast.Assign, 8 | ast.Expr, # bare function calls and expressions as statements 9 | ast.Lambda # we can't expand out lambda bodies 10 | } 11 | def value_to_name(self, node): 12 | # create a new assign statement to capture the value 13 | newvar = self.unique_variable_name() 14 | newassign = ast.Assign( 15 | targets = [ ast.Name(id = newvar, ctx = ast.Store()) ], 16 | value = node 17 | ) 18 | self.add_prelude_statement(newassign) 19 | 20 | # return a new Name node that refers to the value 21 | return ast.Name(id = newvar, ctx = ast.Load()) 22 | 23 | def visit_expr(self, node): 24 | newval = self.generic_visit(node) # fix up children first 25 | 26 | # Keep statements and compact values intact 27 | if (isinstance(newval, ast.Constant) or 28 | isinstance(newval, ast.Name) or 29 | isinstance(newval, ast.Attribute)): 30 | return newval 31 | # At this point node must be an expression so newval will be too 32 | 33 | if type(self.parent()) in self.valid_expression_containers: 34 | return newval 35 | 36 | # Anything else, assign value to a variable and use that instead 37 | return self.value_to_name(newval) 38 | 39 | 40 | def expand_non_compact_expressions(f: ast.FunctionDef) -> None: 41 | """Modify (in place) the given function definition so that all non-compact 42 | (not a constant, not a variable name, not an attribute) expressions appear as assignments or 43 | as a statement (in an Expr). New assignments to intermediate values are 44 | created if necessary to make this so.""" 45 | 46 | f.body = ExpandNonCompactExpressions(var_format = "φ{:d}").visit_statements(f.body) 47 | ast.fix_missing_locations(f) 48 | -------------------------------------------------------------------------------- /gamehop/verification/canonicalization/ifstatements.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import copy 3 | from ... import utils 4 | from typing import List 5 | from ... import filterast 6 | from ... import node_traverser as nt 7 | 8 | 9 | def if_statements_to_expressions(f: ast.FunctionDef ) -> None: 10 | """Modify, in place, f so that all if statements become if expressions like so: 11 | if condition: 12 | v = expression1 13 | else: 14 | v = expression2 15 | 16 | becomes: 17 | 18 | v_if = expression1 19 | v_else = expression2 20 | v = v_if if condition else v_else 21 | 22 | Limitations: 23 | The resulting code will only be correct if there are no side effects in the body or orelse. Also, the resulting 24 | code may not be safe to run, for example 25 | 26 | if x >= 0: 27 | v = math.sqrt(x) 28 | 29 | will result in 30 | 31 | v_if = math.sqrt(x) 32 | v_else = None 33 | v = v_if if x >= 0 else v_else 34 | 35 | so that now the math.sqrt(x) is run even if x < 0, resulting in an exception. 36 | """ 37 | filterast.filter_AST(f.body, noifs=False) 38 | iftransformer = IfTransformer() 39 | iftransformer.visit_statements(f.body) 40 | ast.fix_missing_locations(f) 41 | filterast.filter_AST(f.body, noifs=True) 42 | 43 | class IfTransformer(nt.NodeTraverser): 44 | def __init__(self): 45 | self.replacement_count = 0 46 | super().__init__() 47 | 48 | def visit_If(self, node: ast.If): 49 | # first fix up the bodies 50 | self.visit_statements(node.body) 51 | self.visit_statements(node.orelse) 52 | 53 | # find all the variables written to in the bodies 54 | body_stored_vars = utils.stored_vars(node.body) 55 | orelse_stored_vars = utils.stored_vars(node.orelse) 56 | all_stored_vars = list() 57 | for var in body_stored_vars: all_stored_vars.append(var) 58 | for var in orelse_stored_vars: 59 | if var not in all_stored_vars: all_stored_vars.append(var) 60 | 61 | # prefix all variables in the bodies so that they don't conflict 62 | body_prefix = "body_{:d}_".format(self.replacement_count) 63 | orelse_prefix = "orelse_{:d}_".format(self.replacement_count) 64 | newnodes = [] 65 | newnodes.extend(utils.prefix_names(node.body, body_prefix)) 66 | newnodes.extend(utils.prefix_names(node.orelse, orelse_prefix)) 67 | 68 | # if one of the bodies doesn't assign to a variable that the other does, fix this 69 | for v in all_stored_vars: 70 | if v not in body_stored_vars: 71 | newnodes.append(ast.Assign(targets=[ast.Name(id=body_prefix + v, ctx=ast.Store())], value=ast.Constant(None))) 72 | if v not in orelse_stored_vars: 73 | newnodes.append(ast.Assign(targets=[ast.Name(id=orelse_prefix + v, ctx=ast.Store())], value=ast.Constant(None))) 74 | 75 | # choose if or else body variables based on node.test 76 | if len(all_stored_vars) == 1: 77 | newnodes.append( 78 | ast.Assign( 79 | targets=[ast.Name(id=all_stored_vars[0], ctx=ast.Store())], 80 | value=ast.IfExp( 81 | test=node.test, 82 | body=ast.Name(id=body_prefix + all_stored_vars[0], ctx=ast.Load()), 83 | orelse=ast.Name(id=orelse_prefix + all_stored_vars[0], ctx=ast.Load()) 84 | ) 85 | ) 86 | ) 87 | else: 88 | ifcondvar = "ifcond_{:d}".format(self.replacement_count) 89 | newnodes.append(ast.Assign( 90 | targets=[ast.Name(id=ifcondvar, ctx=ast.Store())], 91 | value=node.test 92 | )) 93 | for var in all_stored_vars: 94 | newnodes.append( 95 | ast.Assign( 96 | targets=[ast.Name(id=var, ctx=ast.Store())], 97 | value=ast.IfExp( 98 | test=ast.Name(id=ifcondvar, ctx=ast.Load()), 99 | body=ast.Name(id=body_prefix + var, ctx=ast.Load()), 100 | orelse=ast.Name(id=orelse_prefix + var, ctx=ast.Load()) 101 | ) 102 | ) 103 | ) 104 | 105 | self.replacement_count += 1 106 | return newnodes 107 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | jinja2 2 | jupyterlab 3 | matplotlib 4 | mypy 5 | networkx 6 | numpy 7 | pytest 8 | pytest-mypy 9 | scipy 10 | -------------------------------------------------------------------------------- /tests/examples/test_definitions.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from gamehop.primitives import KEM 4 | import gamehop.inlining 5 | from gamehop.verification import canonicalize_function 6 | 7 | class TestDefinitions(unittest.TestCase): 8 | 9 | def oldtest_KEM_INDCPA_definitions_equivalent(self): 10 | assert canonicalize_function(KEM.INDCPAv2.get_left()) == canonicalize_function(KEM.INDCPA.get_left()) 11 | assert canonicalize_function(KEM.INDCPAv2.get_right()) == canonicalize_function(KEM.INDCPA.get_right()) 12 | -------------------------------------------------------------------------------- /tests/examples/test_proofs.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import sys 3 | import unittest 4 | 5 | def run_example(name, property): 6 | subprocess.run( 7 | [sys.executable, "examples/" + name + "/" + name + "_is_" + property + ".py"], 8 | check=True 9 | ) 10 | 11 | class TestProofs(unittest.TestCase): 12 | def test_KEMfromPKE_is_INDCPA(self): run_example("KEMfromPKE", "INDCPA") 13 | def test_parallelPKE_is_INDCPA(self): run_example("parallelPKE", "INDCPA") 14 | def test_nestedPKE_is_INDCPA(self): run_example("nestedPKE", "INDCPA") 15 | def test_PKEfromKEM_is_INDCPA(self): run_example("PKEfromKEM", "INDCPA") 16 | def test_SymEnc_CPADollar_is_INDCPA(self): run_example("SymEnc_CPADollar", "INDCPA") 17 | def test_DoubleOTP_is_ROR(self): run_example("DoubleOTP", "ROR") 18 | -------------------------------------------------------------------------------- /tests/gamehop/inlining/test_dereference_attribute.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import inspect 3 | import unittest 4 | 5 | import gamehop.inlining.internal 6 | 7 | def f_basic(x): 8 | y = x.z 9 | x.y = z 10 | def f_basic_expected_result(x): 11 | y = x_z 12 | x_y = z 13 | def f_nested(x): 14 | y = x.z.w 15 | def f_nested_expected_result(x): 16 | y = x_z.w 17 | def f_not_actually_nested(x): 18 | y = z.x.w 19 | def f_not_actually_nested_2(x): 20 | y = z.x.x.w 21 | 22 | def expected_result(f): 23 | s = inspect.getsource(f) 24 | s = s.replace('_expected_result', '') 25 | return ast.unparse(ast.parse(s)) 26 | 27 | class TestDereferenceAttribute(unittest.TestCase): 28 | def test_basic(self): 29 | self.assertEqual( 30 | gamehop.inlining.internal.dereference_attribute(f_basic, 'x', 'x_{:s}'), 31 | expected_result(f_basic_expected_result)) 32 | def test_nested(self): 33 | self.assertEqual( 34 | gamehop.inlining.internal.dereference_attribute(f_nested, 'x', 'x_{:s}'), 35 | expected_result(f_nested_expected_result)) 36 | def test_not_actually_nested(self): 37 | self.assertEqual( 38 | gamehop.inlining.internal.dereference_attribute(f_not_actually_nested, 'x', 'x_{:s}'), 39 | expected_result(f_not_actually_nested)) 40 | def test_not_actually_nested_2(self): 41 | self.assertEqual( 42 | gamehop.inlining.internal.dereference_attribute(f_not_actually_nested_2, 'x', 'x_{:s}'), 43 | expected_result(f_not_actually_nested_2)) 44 | -------------------------------------------------------------------------------- /tests/gamehop/inlining/test_find_all_variables.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import inspect 3 | import unittest 4 | 5 | import gamehop.inlining.internal 6 | 7 | def f_basic(x, y): 8 | a = 7 9 | b = len(x) 10 | c = b + y 11 | y = 4 + a 12 | (w, z) = (3, len(x)) 13 | v, u = 4, len(x) 14 | return y + b 15 | def f_attributed_assign(x, y): 16 | a = 7 17 | y.z = 3 18 | return y + a 19 | 20 | class TestFindAllVariables(unittest.TestCase): 21 | def test_basic(self): 22 | self.assertEqual( 23 | gamehop.inlining.internal.find_all_variables(f_basic), 24 | list(['x', 'y', 'a', 'b', 'c', 'w', 'z', 'v', 'u']) 25 | ) 26 | def test_attributed_assign(self): 27 | self.assertEqual( 28 | gamehop.inlining.internal.find_all_variables(f_attributed_assign), 29 | list(['x', 'y', 'a']) 30 | ) 31 | -------------------------------------------------------------------------------- /tests/gamehop/inlining/test_inline_all_static_methods.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import inspect 3 | import unittest 4 | 5 | import gamehop.inlining 6 | 7 | def expected_result(f): 8 | fdef = gamehop.utils.get_function_def(f) 9 | fdef.name = fdef.name.replace('_expected_result', '') 10 | return ast.unparse(fdef) 11 | 12 | class TestInlineAllMethodsIntoFunction(unittest.TestCase): 13 | 14 | def test_basic(self): 15 | class C(): 16 | @staticmethod 17 | def A(x, y): 18 | w = x + y 19 | return w 20 | @staticmethod 21 | def B(x, y, z): return x * y * z 22 | def f(x): 23 | y = C.A(x, x) 24 | z = C.B(1, 2, x) 25 | r = 2 26 | def f_expected_result(x): 27 | C_Aᴠ1ⴰw = x + x 28 | y = C_Aᴠ1ⴰw 29 | z = 1 * 2 * x 30 | r = 2 31 | self.assertEqual( 32 | gamehop.inlining.inline_all_static_method_calls(C, f), 33 | expected_result(f_expected_result)) 34 | -------------------------------------------------------------------------------- /tests/gamehop/inlining/test_inline_argument_into_function.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import inspect 3 | import unittest 4 | 5 | import gamehop.inlining 6 | 7 | def expected_result(f): 8 | fdef = gamehop.utils.get_function_def(f) 9 | fdef.name = fdef.name.replace('_expected_result', '') 10 | return ast.unparse(fdef) 11 | 12 | class TestInlineArgumentIntoFunction(unittest.TestCase): 13 | 14 | def test_arg_not_found(self): 15 | def f(x): print(x) 16 | with self.assertRaisesRegex(KeyError, "Argument y not found in list of arguments to function f"): 17 | gamehop.inlining.inline_argument_into_function('y', True, f) 18 | 19 | def test_bool(self): 20 | def f(x, y): print(x) 21 | def f_expected_result(y): print(True) 22 | self.assertEqual( 23 | gamehop.inlining.inline_argument_into_function('x', True, f), 24 | expected_result(f_expected_result)) 25 | 26 | def test_float(self): 27 | def f(x): print(x) 28 | def f_expected_result(): print(3.14152) 29 | self.assertEqual( 30 | gamehop.inlining.inline_argument_into_function('x', 3.14152, f), 31 | expected_result(f_expected_result)) 32 | 33 | def test_int(self): 34 | def f(x): print(x) 35 | def f_expected_result(): print(42) 36 | self.assertEqual( 37 | gamehop.inlining.inline_argument_into_function('x', 42, f), 38 | expected_result(f_expected_result)) 39 | 40 | def test_str(self): 41 | def f(x): print(x) 42 | def f_expected_result(): print("potato") 43 | self.assertEqual( 44 | gamehop.inlining.inline_argument_into_function('x', 'potato', f), 45 | expected_result(f_expected_result)) 46 | 47 | def test_tuple(self): 48 | def f(x, y): print(x) 49 | def f_expected_result(y): print((1, 2, 3)) 50 | self.assertEqual( 51 | gamehop.inlining.inline_argument_into_function('x', (1, 2, 3), f), 52 | expected_result(f_expected_result)) 53 | 54 | def test_tuple_ast(self): 55 | def f(x, y): print(x) 56 | def f_expected_result(y): print((1, 2, 3)) 57 | tupleast = ast.Tuple([ast.Constant(1), ast.Constant(2), ast.Constant(3)], ctx=ast.Load()) 58 | self.assertEqual( 59 | gamehop.inlining.inline_argument_into_function('x', tupleast, f), 60 | expected_result(f_expected_result)) 61 | 62 | def test_f_as_string(self): 63 | str_bool_f = "def f(x, y): print(x)" 64 | def f_expected_result(y): print(True) 65 | self.assertEqual( 66 | gamehop.inlining.inline_argument_into_function('x', True, str_bool_f), 67 | expected_result(f_expected_result)) 68 | 69 | def test_assigned(self): 70 | def f(x): 71 | print(x) 72 | x = 123 73 | with self.assertRaisesRegex(ValueError, "Error inlining argument x into function f: x is assigned to in the body of f"): 74 | gamehop.inlining.inline_argument_into_function('x', True, f) 75 | 76 | def test_class(self): 77 | class Foo(): 78 | @staticmethod 79 | def Encrypt(m): pass 80 | def f(x, y): z = x.Encrypt(y) 81 | def f_expected_result(y): z = Foo.Encrypt(y) 82 | self.assertEqual( 83 | gamehop.inlining.inline_argument_into_function('x', Foo, f), 84 | expected_result(f_expected_result)) 85 | -------------------------------------------------------------------------------- /tests/gamehop/inlining/test_inline_function_call.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import inspect 3 | import unittest 4 | 5 | import gamehop.inlining 6 | 7 | def inlinand(a, b): 8 | c = a + b 9 | return c 10 | 11 | def expected_result(f): 12 | fdef = gamehop.utils.get_function_def(f) 13 | fdef.name = fdef.name.replace('_expected_result', '') 14 | return ast.unparse(fdef) 15 | 16 | class TestInlineFunction(unittest.TestCase): 17 | 18 | def test_variable(self): 19 | def f(x): 20 | y = inlinand(x, x) 21 | z = 2 22 | def f_expected_result(x): 23 | inlinandᴠ1ⴰc = x + x 24 | y = inlinandᴠ1ⴰc 25 | z = 2 26 | self.assertEqual( 27 | gamehop.inlining.inline_function_call(inlinand, f), 28 | expected_result(f_expected_result)) 29 | 30 | def test_constant(self): 31 | def f(x): y = inlinand(x, 3) 32 | def f_expected_result(x): 33 | inlinandᴠ1ⴰc = x + 3 34 | y = inlinandᴠ1ⴰc 35 | self.assertEqual( 36 | gamehop.inlining.inline_function_call(inlinand, f), 37 | expected_result(f_expected_result)) 38 | 39 | def test_several_calls(self): 40 | self.maxDiff = None 41 | def f(x): 42 | w = inlinand(x, x) 43 | y = inlinand(x, x) 44 | def f_expected_result(x): 45 | inlinandᴠ1ⴰc = x + x 46 | w = inlinandᴠ1ⴰc 47 | inlinandᴠ2ⴰc = x + x 48 | y = inlinandᴠ2ⴰc 49 | self.assertEqual( 50 | gamehop.inlining.inline_function_call(inlinand, f), 51 | expected_result(f_expected_result)) 52 | 53 | def test_compound_return(self): 54 | def inlinand_compound_return(a, b): 55 | c = a + b 56 | return c + 7 57 | def f(x): y = inlinand_compound_return(x, 3) 58 | def f_expected_result(x): 59 | inlinand_compound_returnᴠ1ⴰc = x + 3 60 | y = inlinand_compound_returnᴠ1ⴰc + 7 61 | self.assertEqual( 62 | gamehop.inlining.inline_function_call(inlinand_compound_return, f), 63 | expected_result(f_expected_result)) 64 | 65 | def test_inlinand_nested(self): 66 | def f(x): y = inlinand(inlinand(x, 3), 4) 67 | with self.assertRaisesRegex(ValueError, "Could not fully inline inlinand into f since f calls inlinand in an unsupported way; the only supported way is an assignment statement of the form foo = inlinand\\(bar\\)"): 68 | gamehop.inlining.inline_function_call(inlinand, f) 69 | 70 | def test_inlinand_in_operation(self): 71 | def f(x): y = inlinand(x, 3) + 4 72 | with self.assertRaisesRegex(ValueError, "Could not fully inline inlinand into f since f calls inlinand in an unsupported way; the only supported way is an assignment statement of the form foo = inlinand\\(bar\\)"): 73 | gamehop.inlining.inline_function_call(inlinand, f) 74 | 75 | def test_no_return_at_end_but_expected(self): 76 | def inlinand_no_return_at_end(a, b): c = a + b 77 | def f(a, b): y = inlinand_no_return_at_end(a, 3) 78 | with self.assertRaisesRegex(ValueError, "Cannot inline function inlinand_no_return_at_end into statement y = inlinand_no_return_at_end\\(a, 3\\) in function f since inlinand_no_return_at_end does not return anything"): 79 | gamehop.inlining.inline_function_call(inlinand_no_return_at_end, f) 80 | 81 | def test_multiple_returns(self): 82 | def inlinand_multiple_returns(a, b): 83 | if a: return b 84 | elif not(a): return a 85 | return a + b 86 | def f(x): y = inlinand_multiple_returns(x, x) 87 | with self.assertRaisesRegex(NotImplementedError, "Inlining function inlinand_multiple_returns into f since inlinand_multiple_returns contains a return statement somewhere other than the last line \\(namely, line 1\\)"): 88 | gamehop.inlining.inline_function_call(inlinand_multiple_returns, f) 89 | 90 | def test_no_assignment_with_return(self): 91 | def f(a, b): inlinand(a, 3) 92 | def f_expected_result(a, b): 93 | inlinandᴠ1ⴰc = a + 3 94 | inlinandᴠ1ⴰc 95 | self.assertEqual( 96 | gamehop.inlining.inline_function_call(inlinand, f), 97 | expected_result(f_expected_result)) 98 | 99 | def test_if_body(self): 100 | def f(a,b): 101 | r = 3 102 | if a: r = inlinand(a, b) 103 | return r 104 | def f_expected_result(a, b): 105 | r = 3 106 | if a: 107 | inlinandᴠ1ⴰc = a + b 108 | r = inlinandᴠ1ⴰc 109 | return r 110 | self.assertEqual( 111 | gamehop.inlining.inline_function_call(inlinand, f), 112 | expected_result(f_expected_result)) 113 | 114 | def test_if_body_else(self): 115 | def f(a,b): 116 | r = 3 117 | if a: r = inlinand(a, b) 118 | else: r = inlinand(a, b) 119 | return r 120 | def f_expected_result(a, b): 121 | r = 3 122 | if a: 123 | inlinandᴠ1ⴰc = a + b 124 | r = inlinandᴠ1ⴰc 125 | else: 126 | inlinandᴠ2ⴰc = a + b 127 | r = inlinandᴠ2ⴰc 128 | return r 129 | self.assertEqual( 130 | gamehop.inlining.inline_function_call(inlinand, f), 131 | expected_result(f_expected_result)) 132 | 133 | def test_static_method(self): 134 | class Foo(): 135 | @staticmethod 136 | def F(a, b): 137 | c = a + b 138 | return c 139 | def f(x): r = Foo.F(x, 3) 140 | def f_expected_result(x): 141 | Foo_Fᴠ1ⴰc = x + 3 142 | r = Foo_Fᴠ1ⴰc 143 | self.assertEqual( 144 | gamehop.inlining.inline_function_call(Foo.F, f), 145 | expected_result(f_expected_result)) 146 | -------------------------------------------------------------------------------- /tests/gamehop/inlining/test_inline_nonstatic_methods.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import inspect 3 | import unittest 4 | 5 | import gamehop.inlining 6 | 7 | def expected_result(f): 8 | fdef = gamehop.utils.get_function_def(f) 9 | fdef.name = fdef.name.replace('_expected_result', '') 10 | return ast.unparse(fdef) 11 | 12 | class TestInlineNonStaticMethods(unittest.TestCase): 13 | 14 | def test_nonstatic_method(self): 15 | class Foo(): 16 | def F(self, a, b): 17 | d = a + b + self.c 18 | return d 19 | def f(x): 20 | y = 1 21 | r = x.F(y, 3) 22 | return r 23 | def f_expected_result(x): 24 | y = 1 25 | x_Fᴠ1ⴰd = y + 3 + x.c 26 | r = x_Fᴠ1ⴰd 27 | return r 28 | self.assertEqual( 29 | gamehop.inlining.inline_all_nonstatic_method_calls('x', Foo, f), 30 | expected_result(f_expected_result)) 31 | -------------------------------------------------------------------------------- /tests/gamehop/inlining/test_inline_reduction_into_game.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import random 3 | import unittest 4 | 5 | from typing import Generic, Type, TypeVar 6 | 7 | import gamehop.inlining 8 | from gamehop.primitives import Crypto 9 | 10 | def expected_result(g): 11 | gdef = gamehop.utils.get_class_def(g) 12 | gdef.name = gdef.name.replace('_expected_result', '') 13 | return ast.unparse(gdef) 14 | 15 | # This is a pretty complicated test case 16 | # The idea is that we have two primitives P1 and P2, with corresponding games G1 and G2. 17 | # We also have a generic construction of a P2 from a P1, called P2fromP1. 18 | # We give a reduction R which is a G1 adversary against P1 which makes use of a G2 adversary against P2fromP1. 19 | # We want to check that R inlined into G1 for P1 looks like G2 for P2fromP1. 20 | # First we have to construct G2 for P2fromP1, which we do by inline_scheme_into_game(P2fromP1, G2) and check that against the expected result. (Technically this test case is checking inline_scheme_into_game rather than inline_reduction_into_game, but we put it into this test file rather than test_inline_scheme_into_game since we need the objects to be present here for the next step.) 21 | # Then we have inline R into G1 which we do by inline_reduction_into_game(R, G1, G2) and check that against the same expected result from the previous step. 22 | # Well, these two different inlinings won't actually be equal as strings yet because we haven't applied canonicalization, so we'll list the two different versions we expect to get, which I have inspected and believe are logically equivalent. 23 | 24 | PublicKey1 = TypeVar('PublicKey1') 25 | Ciphertext1 = TypeVar('Ciphertext1') 26 | 27 | class P1(Crypto.Scheme, Generic[PublicKey1, Ciphertext1]): 28 | @staticmethod 29 | def KeyGen() -> PublicKey1: pass 30 | @staticmethod 31 | def Encrypt(pk: PublicKey1, msg: str) -> Ciphertext1: pass 32 | class G1_Adversary(Crypto.Adversary, Generic[PublicKey1, Ciphertext1]): # game idea: is ct the encryption of "hi" or "bye"? 33 | def hi_or_bye(self, pk: PublicKey1, ct: Ciphertext1) -> int: pass 34 | class G1(Crypto.Game, Generic[PublicKey1, Ciphertext1]): 35 | def __init__(self, Scheme: Type[P1[PublicKey1, Ciphertext1]], Adversary: Type[G1_Adversary[PublicKey1, Ciphertext1]]): 36 | self.Scheme = Scheme 37 | self.adversary = Adversary(Scheme) 38 | def main(self) -> Crypto.Bit: 39 | pk = self.Scheme.KeyGen() 40 | b = random.choice([0,1]) 41 | msge = "hi!" if b == 0 else "bye" 42 | ct = self.Scheme.Encrypt(pk, msge) 43 | bstar = self.adversary.hi_or_bye(pk, ct) 44 | ret = 1 if b == bstar else 0 45 | return Crypto.Bit(ret) 46 | 47 | PublicKey2 = TypeVar('PublicKey2') 48 | Ciphertext2 = TypeVar('Ciphertext2') 49 | 50 | class P2(Crypto.Scheme, Generic[PublicKey2, Ciphertext2]): 51 | @staticmethod 52 | def KG() -> PublicKey2: pass 53 | @staticmethod 54 | def ENC(pk: PublicKey2, msg: str) -> Ciphertext2: pass 55 | class G2_Adversary(Crypto.Adversary, Generic[PublicKey2, Ciphertext2]): # game idea: is ct the encryption of "hi!" or not?" 56 | def hi_or_not(self, pk: PublicKey2, ct: Ciphertext2) -> bool: pass 57 | class G2(Crypto.Game, Generic[PublicKey2, Ciphertext2]): 58 | def __init__(self, Scheme: Type[P2[PublicKey2, Ciphertext2]], Adversary: Type[G2_Adversary[PublicKey2, Ciphertext2]]): 59 | self.Scheme = Scheme 60 | self.adversary = Adversary(Scheme) 61 | def main(self) -> Crypto.Bit: 62 | pk = self.Scheme.KG() 63 | b = random.choice([0,1]) 64 | msge = "hi!" if b == 0 else "bye" 65 | ct = self.Scheme.ENC(pk, msge) 66 | bnew = self.adversary.hi_or_not(pk, ct) 67 | bstar = 0 if bnew else 1 68 | ret = 1 if b == bstar else 0 69 | return Crypto.Bit(ret) 70 | 71 | PK = TypeVar('PK') 72 | CT = TypeVar('CT') 73 | 74 | P1Instance = P1[PK, CT] 75 | 76 | class P2fromP1(Generic[PK, CT], P2[PK, CT]): 77 | @staticmethod 78 | def KG(): 79 | return P1Instance.KeyGen() 80 | @staticmethod 81 | def ENC(pk, msg): 82 | return P1Instance.Encrypt(pk, msg) 83 | class R(Generic[PK, CT], Crypto.Reduction, G1_Adversary[PK, CT]): 84 | def __init__(self, Scheme: Type[P1[PK, CT]], inner_adversary: G2_Adversary[PK, CT]): 85 | self.Scheme = Scheme 86 | self.inner_adversary = inner_adversary 87 | def hi_or_bye(self, pk: PK, ct: CT) -> int: 88 | g = self.inner_adversary.hi_or_not(pk, ct) 89 | ret = 0 if g else 1 90 | return ret 91 | 92 | class TestInlineReductionIntoGame(unittest.TestCase): 93 | 94 | def test_P2fromP1_into_G2(self): 95 | self.maxDiff = None 96 | class G2_expected_result(Crypto.Game, Generic[PK, CT]): 97 | def __init__(self, Adversary: Type[G2_Adversary[PK, CT]]): 98 | self.Scheme = P2fromP1 99 | self.adversary = Adversary(P2fromP1) 100 | def main(self) -> Crypto.Bit: 101 | pk = P1Instance.KeyGen() 102 | b = random.choice([0, 1]) 103 | msge = "hi!" if b == 0 else "bye" 104 | ct = P1Instance.Encrypt(pk, msge) 105 | bnew = self.adversary.hi_or_not(pk, ct) 106 | bstar = 0 if bnew else 1 107 | ret = 1 if b == bstar else 0 108 | return Crypto.Bit(ret) 109 | self.assertEqual( 110 | gamehop.inlining.inline_scheme_into_game(P2fromP1, G2), 111 | expected_result(G2_expected_result)) 112 | 113 | def test_R_into_G1(self): 114 | self.maxDiff = None 115 | class G2_expected_result(Crypto.Game, Generic[PK, CT]): 116 | def __init__(self, Adversary: Type[G2_Adversary[PK, CT]]): 117 | self.Scheme = P2fromP1 118 | self.adversary = Adversary(P2fromP1) 119 | def main(self) -> Crypto.Bit: 120 | pk = P1Instance.KeyGen() 121 | b = random.choice([0, 1]) 122 | msge = 'hi!' if b == 0 else 'bye' 123 | ct = P1Instance.Encrypt(pk, msge) 124 | R_hi_or_byeᴠ1ⴰg = self.adversary.hi_or_not(pk, ct) 125 | R_hi_or_byeᴠ1ⴰret = 0 if R_hi_or_byeᴠ1ⴰg else 1 126 | bstar = R_hi_or_byeᴠ1ⴰret 127 | ret = 1 if b == bstar else 0 128 | return Crypto.Bit(ret) 129 | self.assertEqual( 130 | gamehop.inlining.inline_reduction_into_game(R, G1, P1Instance, "P1Instance", G2, P2fromP1, G2_Adversary), 131 | expected_result(G2_expected_result)) 132 | -------------------------------------------------------------------------------- /tests/gamehop/inlining/test_inline_reduction_into_game_with_oracles.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import random 3 | import unittest 4 | 5 | from typing import Callable, Generic, Type, TypeVar 6 | 7 | import gamehop.inlining 8 | from gamehop.primitives import Crypto 9 | 10 | def expected_result(g): 11 | gdef = gamehop.utils.get_class_def(g) 12 | gdef.name = gdef.name.replace('_expected_result', '') 13 | return ast.unparse(gdef) 14 | 15 | # This is a pretty complicated test case 16 | # The idea is that we have two primitives P1 and P2, with corresponding games G1 and G2. 17 | # We also have a generic construction of a P2 from a P1, called P2fromP1. 18 | # We give a reduction R which is a G1 adversary against P1 which makes use of a G2 adversary against P2fromP1. 19 | # We want to check that R inlined into G1 for P1 looks like G2 for P2fromP1. 20 | # First we have to construct G2 for P2fromP1, which we do by inline_scheme_into_game(P2fromP1, G2) and check that against the expected result. (Technically this test case is checking inline_scheme_into_game rather than inline_reduction_into_game, but we put it into this test file rather than test_inline_scheme_into_game since we need the objects to be present here for the next step.) 21 | # Then we have inline R into G1 which we do by inline_reduction_into_game(R, G1, G2) and check that against the same expected result from the previous step. 22 | # Well, these two different inlinings won't actually be equal as strings yet because we haven't applied canonicalization, so we'll list the two different versions we expect to get, which I have inspected and believe are logically equivalent. 23 | 24 | PublicKey1 = TypeVar('PublicKey1') 25 | Ciphertext1 = TypeVar('Ciphertext1') 26 | 27 | class P1(Crypto.Scheme, Generic[PublicKey1, Ciphertext1]): 28 | @staticmethod 29 | def KeyGen() -> PublicKey1: pass 30 | @staticmethod 31 | def Encrypt(pk: PublicKey1, msg: str) -> Ciphertext1: pass 32 | class G1_Adversary(Crypto.Adversary, Generic[PublicKey1, Ciphertext1]): # game idea: is ct the encryption of "hi" or "bye"? 33 | def hi_or_bye(self, pk: PublicKey1, ct: Ciphertext1, o_encrypter: Callable[[str], Ciphertext1]) -> int: pass 34 | class G1(Crypto.Game, Generic[PublicKey1, Ciphertext1]): 35 | def __init__(self, Scheme: Type[P1[PublicKey1, Ciphertext1]], Adversary: Type[G1_Adversary[PublicKey1, Ciphertext1]]): 36 | self.Scheme = Scheme 37 | self.adversary = Adversary(Scheme) 38 | def main(self) -> Crypto.Bit: 39 | self.pk = self.Scheme.KeyGen() 40 | b = random.choice([0,1]) 41 | msge = "hi!" if b == 0 else "bye" 42 | ct = self.Scheme.Encrypt(self.pk, msge) 43 | bstar = self.adversary.hi_or_bye(self.pk, ct, self.o_encrypter) 44 | ret = 1 if b == bstar else 0 45 | return Crypto.Bit(ret) 46 | def o_encrypter(self, m: str) -> Ciphertext1: 47 | return self.Scheme.Encrypt(self.pk, m) 48 | 49 | PublicKey2 = TypeVar('PublicKey2') 50 | Ciphertext2 = TypeVar('Ciphertext2') 51 | 52 | class P2(Crypto.Scheme, Generic[PublicKey2, Ciphertext2]): 53 | @staticmethod 54 | def KG() -> PublicKey2: pass 55 | @staticmethod 56 | def ENC(pk: PublicKey2, msg: str) -> Ciphertext2: pass 57 | class G2_Adversary(Crypto.Adversary, Generic[PublicKey2, Ciphertext2]): # game idea: is ct the encryption of "hi!" or not?" 58 | def hi_or_not(self, pk: PublicKey2, ct: Ciphertext2, o_encconcat: Callable[[str, str], Ciphertext2]) -> bool: pass 59 | class G2(Crypto.Game, Generic[PublicKey2, Ciphertext2]): 60 | def __init__(self, Scheme: Type[P2[PublicKey2, Ciphertext2]], Adversary: Type[G2_Adversary[PublicKey2, Ciphertext2]]): 61 | self.Scheme = Scheme 62 | self.adversary = Adversary(Scheme) 63 | def main(self) -> Crypto.Bit: 64 | pk = self.Scheme.KG() 65 | self.pk = pk 66 | b = random.choice([0,1]) 67 | msge = "hi!" if b == 0 else "bye" 68 | ct = self.Scheme.ENC(self.pk, msge) 69 | bnew = self.adversary.hi_or_not(self.pk, ct, self.o_encconcat) 70 | bstar = 0 if bnew else 1 71 | ret = 1 if b == bstar else 0 72 | return Crypto.Bit(ret) 73 | def o_encconcat(self, m1: str, m2: str) -> Ciphertext2: 74 | return self.Scheme.ENC(self.pk, m1 + m2) 75 | 76 | PK = TypeVar('PK') 77 | CT = TypeVar('CT') 78 | 79 | P1Instance = P1[PK, CT] 80 | 81 | class P2fromP1(Generic[PK, CT], P2[PK, CT]): 82 | @staticmethod 83 | def KG(): 84 | return P1Instance.KeyGen() 85 | @staticmethod 86 | def ENC(pk, msg): 87 | return P1Instance.Encrypt(pk, msg) 88 | class R(Generic[PK, CT], Crypto.Reduction, G1_Adversary[PK, CT]): 89 | def __init__(self, Scheme: Type[P1[PK, CT]], inner_adversary: G2_Adversary[PK, CT]): 90 | self.Scheme = Scheme 91 | self.inner_adversary = inner_adversary 92 | def hi_or_bye(self, pk: PK, ct: CT, o_encrypter: Callable[[str], CT]) -> int: 93 | self.io_encrypter = o_encrypter 94 | g = self.inner_adversary.hi_or_not(pk, ct, self.o_encconcat) 95 | ret = 0 if g else 1 96 | return ret 97 | def o_encconcat(self, m1: str, m2: str) -> CT: 98 | r = self.io_encrypter(m1 + m2) 99 | return r 100 | 101 | class TestInlineReductionIntoGameWithOracle(unittest.TestCase): 102 | 103 | def test_P2fromP1_into_G2(self): 104 | self.maxDiff = None 105 | class G2_expected_result(Crypto.Game, Generic[PK, CT]): 106 | def __init__(self, Adversary: Type[G2_Adversary[PK, CT]]): 107 | self.Scheme = P2fromP1 108 | self.adversary = Adversary(P2fromP1) 109 | def main(self) -> Crypto.Bit: 110 | pk = P1Instance.KeyGen() 111 | self.pk = pk 112 | b = random.choice([0, 1]) 113 | msge = "hi!" if b == 0 else "bye" 114 | ct = P1Instance.Encrypt(self.pk, msge) 115 | bnew = self.adversary.hi_or_not(self.pk, ct, self.o_encconcat) 116 | bstar = 0 if bnew else 1 117 | ret = 1 if b == bstar else 0 118 | return Crypto.Bit(ret) 119 | def o_encconcat(self, m1: str, m2: str) -> CT: 120 | return P2fromP1.ENC(self.pk, m1 + m2) 121 | self.assertEqual( 122 | gamehop.inlining.inline_scheme_into_game(P2fromP1, G2), 123 | expected_result(G2_expected_result)) 124 | 125 | def test_R_into_G1(self): 126 | self.maxDiff = None 127 | class G2_expected_result(Crypto.Game, Generic[PK, CT]): 128 | def __init__(self, Adversary: Type[G2_Adversary[PK, CT]]): 129 | self.Scheme = P2fromP1 130 | self.adversary = Adversary(P2fromP1) 131 | def main(self) -> Crypto.Bit: 132 | self.pk = P1Instance.KeyGen() 133 | b = random.choice([0, 1]) 134 | msge = 'hi!' if b == 0 else 'bye' 135 | ct = P1Instance.Encrypt(self.pk, msge) 136 | self.io_encrypter = self.o_encrypter 137 | R_hi_or_byeᴠ1ⴰg = self.adversary.hi_or_not(self.pk, ct, self.o_encconcat) 138 | R_hi_or_byeᴠ1ⴰret = 0 if R_hi_or_byeᴠ1ⴰg else 1 139 | bstar = R_hi_or_byeᴠ1ⴰret 140 | ret = 1 if b == bstar else 0 141 | return Crypto.Bit(ret) 142 | def o_encconcat(self, m1: str, m2: str) -> CT: 143 | r = P1Instance.Encrypt(self.pk, m1 + m2) 144 | return r 145 | self.assertEqual( 146 | gamehop.inlining.inline_reduction_into_game(R, G1, P1Instance, 'P1Instance', G2, P2fromP1, G2_Adversary), 147 | expected_result(G2_expected_result)) 148 | -------------------------------------------------------------------------------- /tests/gamehop/inlining/test_inline_scheme_into_game.py: -------------------------------------------------------------------------------- 1 | import ast 2 | from typing import Type 3 | import unittest 4 | 5 | import gamehop.inlining 6 | from gamehop.primitives import Crypto 7 | 8 | def expected_result(g): 9 | gdef = gamehop.utils.get_class_def(g) 10 | gdef.name = gdef.name.replace('_expected_result', '') 11 | return ast.unparse(gdef) 12 | 13 | class TestInlineSchemeIntoGame(unittest.TestCase): 14 | 15 | def test_basic(self): 16 | class P(Crypto.Scheme): 17 | @staticmethod 18 | def KeyGen(): return (1, 2) 19 | class G(Crypto.Game): 20 | def __init__(self, Scheme: Type[P], Adversary): 21 | self.Scheme = Scheme 22 | self.Adversary = Adversary 23 | def main(self) -> Crypto.Bit: 24 | (pk, _) = self.Scheme.KeyGen() 25 | return pk 26 | class G_expected_result(Crypto.Game): 27 | def __init__(self, Adversary): 28 | self.Scheme = P 29 | self.Adversary = Adversary 30 | def main(self) -> Crypto.Bit: 31 | (pk, _) = (1, 2) 32 | return pk 33 | self.assertEqual( 34 | gamehop.inlining.inline_scheme_into_game(P, G), 35 | expected_result(G_expected_result)) 36 | -------------------------------------------------------------------------------- /tests/gamehop/inlining/test_rename_variables.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import inspect 3 | import unittest 4 | 5 | import gamehop.inlining.internal 6 | import gamehop.utils as utils 7 | def f_basic(x, y): 8 | a = 7 9 | b = len(x) 10 | c = b + y 11 | y = 4 + a 12 | return y + b 13 | def f_basic_expected_result(v_1, v_2): 14 | v_3 = 7 15 | b = len(v_1) 16 | c = b + v_2 17 | v_2 = 4 + v_3 18 | return v_2 + b 19 | def f_attribute(a): 20 | a.b = 7 21 | def f_attribute_expected_result(z): 22 | z.b = 7 23 | 24 | def expected_result(f): 25 | s = inspect.getsource(f) 26 | s = s.replace('_expected_result', '') 27 | return ast.unparse(ast.parse(s)) 28 | 29 | class TestRenameVariables(unittest.TestCase): 30 | def test_basic(self): 31 | mappings = {'x': 'v_1', 'y': 'v_2', 'a': 'v_3'} 32 | self.assertEqual( 33 | ast.unparse(utils.rename_function_body_variables(utils.get_function_def(f_basic), mappings)), 34 | expected_result(f_basic_expected_result)) 35 | def test_name_collision_in_body(self): 36 | mappings = {'x': 'a'} 37 | with self.assertRaises(ValueError): 38 | utils.rename_function_body_variables(utils.get_function_def(f_basic), mappings) 39 | def test_name_collision_in_args(self): 40 | mappings = {'x': 'y'} 41 | with self.assertRaises(ValueError): 42 | utils.rename_function_body_variables(utils.get_function_def(f_basic), mappings) 43 | def test_attribute(self): 44 | mappings = {'a': 'z'} 45 | self.assertEqual( 46 | ast.unparse(utils.rename_function_body_variables(utils.get_function_def(f_attribute), mappings)), 47 | expected_result(f_attribute_expected_result)) 48 | -------------------------------------------------------------------------------- /tests/gamehop/test_depends_assigns.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import inspect 3 | import unittest 4 | 5 | import gamehop.utils 6 | 7 | class TestDependsOn(unittest.TestCase): 8 | 9 | def test_Assign(self): 10 | s = 'a = b' 11 | self.assertEqual(gamehop.utils.vars_depends_on(ast.parse(s).body[0]), ['b']) 12 | 13 | def test_Attribute(self): 14 | s = 'a = x.y(z)' 15 | self.assertEqual(gamehop.utils.vars_depends_on(ast.parse(s).body[0]), ['x.y', 'x', 'z']) 16 | 17 | def test_Attribute2(self): 18 | s = 'a.b = x.y(z)' 19 | self.assertEqual(gamehop.utils.vars_depends_on(ast.parse(s).body[0]), ['a', 'x.y', 'x', 'z']) 20 | 21 | def test_BinOp(self): 22 | s = 'a = x + y' 23 | self.assertEqual(gamehop.utils.vars_depends_on(ast.parse(s).body[0]), ['x', 'y']) 24 | 25 | def test_Call(self): 26 | s = 'a = x(y, z)' 27 | self.assertEqual(gamehop.utils.vars_depends_on(ast.parse(s).body[0]), ['x', 'y', 'z']) 28 | 29 | def test_CallAttribute(self): 30 | s = 'a = b.x(y, z)' 31 | self.assertEqual(gamehop.utils.vars_depends_on(ast.parse(s).body[0]), ['b.x', 'b', 'y', 'z']) 32 | 33 | def test_Compare(self): 34 | s = 'r = a == b' 35 | self.assertEqual(gamehop.utils.vars_depends_on(ast.parse(s).body[0]), ['a', 'b']) 36 | 37 | def test_Constant(self): 38 | s = 'a = 7' 39 | self.assertEqual(gamehop.utils.vars_depends_on(ast.parse(s).body[0]), []) 40 | 41 | def test_IfExp(self): 42 | s = 'a = b if c else d' 43 | self.assertEqual(gamehop.utils.vars_depends_on(ast.parse(s).body[0]), ['c', 'b', 'd']) 44 | 45 | def test_Name(self): 46 | s = 'a = b' 47 | self.assertEqual(gamehop.utils.vars_depends_on(ast.parse(s).body[0]), ['b']) 48 | 49 | def test_Return(self): 50 | s = 'return b' 51 | self.assertEqual(gamehop.utils.vars_depends_on(ast.parse(s).body[0]), ['b']) 52 | 53 | def test_Tuple(self): 54 | s = 'a = (b, c)' 55 | self.assertEqual(gamehop.utils.vars_depends_on(ast.parse(s).body[0]), ['b', 'c']) 56 | 57 | class TestAssignsTo(unittest.TestCase): 58 | 59 | def test_Assign(self): 60 | s = 'a = b' 61 | self.assertEqual(gamehop.utils.vars_assigns_to(ast.parse(s).body[0]), ['a']) 62 | 63 | def test_Attribute(self): 64 | s = 'a = x.y(z)' 65 | self.assertEqual(gamehop.utils.vars_assigns_to(ast.parse(s).body[0]), ['a']) 66 | 67 | def test_Attribute2(self): 68 | s = 'a.b = x.y(z)' 69 | self.assertEqual(gamehop.utils.vars_assigns_to(ast.parse(s).body[0]), ['a']) 70 | 71 | def test_BinOp(self): 72 | s = 'a = x + y' 73 | self.assertEqual(gamehop.utils.vars_assigns_to(ast.parse(s).body[0]), ['a']) 74 | 75 | def test_Call(self): 76 | s = 'a = x(y, z)' 77 | self.assertEqual(gamehop.utils.vars_assigns_to(ast.parse(s).body[0]), ['a']) 78 | 79 | def test_CallAttribute(self): 80 | s = 'a = b.x(y, z)' 81 | self.assertEqual(gamehop.utils.vars_assigns_to(ast.parse(s).body[0]), ['a']) 82 | 83 | def test_Compare(self): 84 | s = 'r = a == b' 85 | self.assertEqual(gamehop.utils.vars_assigns_to(ast.parse(s).body[0]), ['r']) 86 | 87 | def test_Constant(self): 88 | s = 'a = 7' 89 | self.assertEqual(gamehop.utils.vars_assigns_to(ast.parse(s).body[0]), ['a']) 90 | 91 | def test_IfExp(self): 92 | s = 'a = b if c else d' 93 | self.assertEqual(gamehop.utils.vars_assigns_to(ast.parse(s).body[0]), ['a']) 94 | 95 | def test_Name(self): 96 | s = 'a = b' 97 | self.assertEqual(gamehop.utils.vars_assigns_to(ast.parse(s).body[0]), ['a']) 98 | 99 | def test_Return(self): 100 | s = 'return b' 101 | self.assertEqual(gamehop.utils.vars_assigns_to(ast.parse(s).body[0]), []) 102 | 103 | def test_Tuple(self): 104 | s = 'a = (b, c)' 105 | self.assertEqual(gamehop.utils.vars_assigns_to(ast.parse(s).body[0]), ['a']) 106 | 107 | def test_Tuple2(self): 108 | s = '(a1, a2) = (b, c)' 109 | self.assertEqual(gamehop.utils.vars_assigns_to(ast.parse(s).body[0]), ['a1', 'a2']) 110 | 111 | def test_cryptoexample1(self): 112 | s = '(v2, v3) = v1.KeyGen()' 113 | self.assertEqual(gamehop.utils.vars_depends_on(ast.parse(s).body[0]), ['v1.KeyGen', 'v1']) 114 | self.assertEqual(gamehop.utils.vars_assigns_to(ast.parse(s).body[0]), ['v2', 'v3']) 115 | s = 'v4 = v1.Encrypt(v2, v5)' 116 | self.assertEqual(gamehop.utils.vars_depends_on(ast.parse(s).body[0]), ['v1.Encrypt', 'v1', 'v2', 'v5']) 117 | self.assertEqual(gamehop.utils.vars_assigns_to(ast.parse(s).body[0]), ['v4']) 118 | s = 'v5 = Crypto.UniformlySample(SharedSecret)' 119 | self.assertEqual(gamehop.utils.vars_depends_on(ast.parse(s).body[0]), ['Crypto.UniformlySample', 'Crypto', 'SharedSecret']) 120 | self.assertEqual(gamehop.utils.vars_assigns_to(ast.parse(s).body[0]), ['v5']) 121 | s = 'v6 = v0.guess(v2, v4, v5)' 122 | self.assertEqual(gamehop.utils.vars_depends_on(ast.parse(s).body[0]), ['v0.guess', 'v0', 'v2', 'v4', 'v5']) 123 | self.assertEqual(gamehop.utils.vars_assigns_to(ast.parse(s).body[0]), ['v6']) 124 | s = 'return v6' 125 | self.assertEqual(gamehop.utils.vars_depends_on(ast.parse(s).body[0]), ['v6']) 126 | self.assertEqual(gamehop.utils.vars_assigns_to(ast.parse(s).body[0]), []) 127 | -------------------------------------------------------------------------------- /tests/gamehop/test_lists.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from gamehop.lists import * 3 | 4 | class TestFilterAST(unittest.TestCase): 5 | def test_new_empty_list(self): 6 | def f(): 7 | return new_empty_list() 8 | self.assertEqual(f(), ()) 9 | 10 | def test_append_item(self): 11 | def f(): 12 | l1 = new_empty_list() 13 | l2 = append_item(l1, 3) 14 | return l2 15 | self.assertEqual(f(), (3,)) 16 | 17 | def test_set_item(self): 18 | def f(): 19 | l1 = new_empty_list() 20 | l2 = append_item(l1, 3) 21 | l3 = set_item(l2, 0, 4) 22 | return l3 23 | self.assertEqual(f(), (4,)) 24 | 25 | def test_set_item(self): 26 | def f(): 27 | l1 = new_empty_list() 28 | l2 = append_item(l1, 3) 29 | return get_item(l2, 0) 30 | self.assertEqual(f(), 3) 31 | 32 | 33 | def test_index_of(self): 34 | def f(): 35 | l1 = new_empty_list() 36 | l2 = append_item(l1, 3) 37 | l3 = append_item(l2, 4) 38 | l4 = append_item(l3, 5) 39 | return index_of(l4, 4) 40 | self.assertEqual(f(), 1) 41 | 42 | def test_concat(self): 43 | def f(): 44 | l1 = new_empty_list() 45 | l2 = append_item(l1, 3) 46 | l3 = append_item(l2, 4) 47 | l4 = append_item(l3, 5) 48 | return concat(l3, l4) 49 | self.assertEqual(f(), (3,4,3,4,5)) 50 | 51 | def test_slice(self): 52 | def f(): 53 | l1 = new_empty_list() 54 | l2 = append_item(l1, 3) 55 | l3 = append_item(l2, 4) 56 | l4 = append_item(l3, 5) 57 | l5 = append_item(l4, 6) 58 | return slice(l4, 1, 3) 59 | self.assertEqual(f(), (4,5)) 60 | def test_slice_step(self): 61 | def f(): 62 | l1 = tuple(range(0, 5)) 63 | return slice(l1, 3, 1, -1) 64 | self.assertEqual(f(), (3, 2)) 65 | -------------------------------------------------------------------------------- /tests/gamehop/test_utils.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import inspect 3 | import unittest 4 | import gamehop.utils 5 | 6 | def expected_result(f): 7 | fdef = gamehop.utils.get_function_def(f) 8 | fdef.name = fdef.name.replace('_expected_result', '') 9 | return ast.unparse(fdef) 10 | 11 | class TestAttributeNodeReplacer(unittest.TestCase): 12 | 13 | def test_one_level(self): 14 | def f(a, y): 15 | v1 = a.b(y) # should be replaced 16 | v2 = a.b.c(y) # should be replaced 17 | v3 = a.a.b(y) # should not be replaced 18 | return v1 + v2 + v3 19 | def f_expected_result(a, y): 20 | v1 = int(y) 21 | v2 = int.c(y) 22 | v3 = a.a.b(y) 23 | return v1 + v2 + v3 24 | x = gamehop.utils.AttributeNodeReplacer(['a', 'b'], 'int').visit(gamehop.utils.get_function_def(f)) 25 | self.assertEqual( 26 | ast.unparse(x), 27 | expected_result(f_expected_result) 28 | ) 29 | 30 | def test_two_level(self): 31 | def f(a, y): 32 | v1 = a.b(y) # should not be replaced 33 | v2 = a.b.c(y) # should be replaced 34 | v3 = a.b.c.d(y) # should be replaced 35 | v4 = a.a.b.c(y) # should not be replaced 36 | return v1 + v2 + v3 + v4 37 | def f_expected_result(a, y): 38 | v1 = a.b(y) 39 | v2 = int(y) 40 | v3 = int.d(y) 41 | v4 = a.a.b.c(y) 42 | return v1 + v2 + v3 + v4 43 | x = gamehop.utils.AttributeNodeReplacer(['a', 'b', 'c'], 'int').visit(gamehop.utils.get_function_def(f)) 44 | self.assertEqual( 45 | ast.unparse(x), 46 | expected_result(f_expected_result) 47 | ) 48 | 49 | def test_two_level_wildcard(self): 50 | def f(a, y): 51 | v1 = a.b(y) # should not be replaced 52 | v2 = a.b.charlie(y) # should be replaced 53 | v3 = a.b.christof.d(y) # should be replaced 54 | v4 = a.a.b.chuck(y) # should not be replaced 55 | return v1 + v2 + v3 + v4 56 | def f_expected_result(a, y): 57 | v1 = a.b(y) 58 | v2 = xx.yyarlie(y) 59 | v3 = xx.yyristof.d(y) 60 | v4 = a.a.b.chuck(y) 61 | return v1 + v2 + v3 + v4 62 | x = gamehop.utils.AttributeNodeReplacer(['a', 'b', 'ch*'], 'xx.yy').visit(gamehop.utils.get_function_def(f)) 63 | self.assertEqual( 64 | ast.unparse(x), 65 | expected_result(f_expected_result) 66 | ) 67 | -------------------------------------------------------------------------------- /tests/gamehop/verification/canonicalization/expand/test_call_arguments.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import inspect 3 | import unittest 4 | 5 | import gamehop.inlining.internal 6 | import gamehop.verification.canonicalization.expand as expand 7 | 8 | def f_noop(y): 9 | a = f(y) 10 | def f_noop_expected_result(y): 11 | a = f(y) 12 | def f_basic(y): 13 | a = f(g(y)) 14 | def f_basic_expected_result(y): 15 | φ0 = g(y) 16 | a = f(φ0) 17 | def f_many(y): 18 | a = f(g(y), h(i(z, j(w)))) 19 | b = f(a, k(y)) 20 | def f_many_expected_result(y): 21 | φ0 = g(y) 22 | φ1 = j(w) 23 | φ2 = i(z, φ1) 24 | φ3 = h(φ2) 25 | a = f(φ0, φ3) 26 | φ4 = k(y) 27 | b = f(a, φ4) 28 | def f_barecall(y): 29 | f(g(y)) 30 | def f_barecall_expected_result(y): 31 | φ0 = g(y) 32 | f(φ0) 33 | 34 | def expected_result(f): 35 | s = inspect.getsource(f) 36 | s = s.replace('_expected_result', '') 37 | return ast.unparse(ast.parse(s)) 38 | 39 | class TestExpandCallArguments(unittest.TestCase): 40 | def test_noop(self): 41 | f = gamehop.utils.get_function_def(f_noop) 42 | expand.expand_non_compact_expressions(f) 43 | self.assertEqual( 44 | ast.unparse(f), 45 | expected_result(f_noop_expected_result) 46 | ) 47 | def test_basic(self): 48 | f = gamehop.utils.get_function_def(f_basic) 49 | expand.expand_non_compact_expressions(f) 50 | self.assertEqual( 51 | ast.unparse(f), 52 | expected_result(f_basic_expected_result) 53 | ) 54 | def test_many(self): 55 | f = gamehop.utils.get_function_def(f_many) 56 | expand.expand_non_compact_expressions(f) 57 | self.assertEqual( 58 | ast.unparse(f), 59 | expected_result(f_many_expected_result) 60 | ) 61 | def test_barecall(self): 62 | f = gamehop.utils.get_function_def(f_barecall) 63 | expand.expand_non_compact_expressions(f) 64 | self.assertEqual( 65 | ast.unparse(f), 66 | expected_result(f_barecall_expected_result) 67 | ) 68 | -------------------------------------------------------------------------------- /tests/gamehop/verification/canonicalization/expand/test_expanders.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import inspect 3 | import unittest 4 | from typing import Type 5 | 6 | import gamehop.inlining.internal 7 | import gamehop.verification.canonicalization.expand as expand 8 | 9 | def expected_result(f): 10 | fdef = gamehop.utils.get_function_def(f) 11 | fdef.name = fdef.name.replace('_expected_result', '') 12 | return ast.unparse(fdef) 13 | 14 | class TestExpandCallArguments(unittest.TestCase): 15 | def test_lambda(self): 16 | ''' We have to be careful not to expand out the bodies of lambdas, 17 | because they make no sense when taken out of the lambda context: the 18 | arguments are not variables in scope outside the lambda body. 19 | 20 | Probably this doesn't matter since we now inline lambdas before expanding. 21 | ''' 22 | def f(): 23 | somef = lambda x: x + 1 24 | return somef(1) 25 | 26 | def f_expected_result(): 27 | somef = lambda x: x + 1 28 | φ0 = somef(1) 29 | return φ0 30 | 31 | f = gamehop.utils.get_function_def(f) 32 | expand.expand_non_compact_expressions(f) 33 | self.assertEqual( 34 | ast.unparse(f), 35 | expected_result(f_expected_result) 36 | ) 37 | def test_function_call_in_return(self): 38 | class Potato: pass 39 | def g(x): 40 | return x 41 | def f(z: Type[Potato]): 42 | return g(z) 43 | 44 | def f_expected_result(z: Type[Potato]): 45 | φ0 = g (z) 46 | return φ0 47 | 48 | fdef = gamehop.utils.get_function_def(f) 49 | expand.expand_non_compact_expressions(fdef) 50 | 51 | s1 = ast.unparse(fdef) 52 | s2 = expected_result(f_expected_result) 53 | self.assertEqual(s1, s2) 54 | 55 | def test_function_call_in_if(self): 56 | def g(x): 57 | return x 58 | def f(z): 59 | if g(z): 60 | pass 61 | 62 | def f_expected_result(z): 63 | φ0 = g(z) 64 | if φ0: 65 | pass 66 | 67 | fdef = gamehop.utils.get_function_def(f) 68 | expand.expand_non_compact_expressions(fdef) 69 | 70 | s1 = ast.unparse(fdef) 71 | s2 = expected_result(f_expected_result) 72 | self.assertEqual(s1, s2) 73 | -------------------------------------------------------------------------------- /tests/gamehop/verification/canonicalization/expand/test_ifexp_arguments.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import inspect 3 | import unittest 4 | 5 | import gamehop.utils 6 | import gamehop.verification.canonicalization.expand 7 | 8 | def expected_result(f): 9 | fdef = gamehop.utils.get_function_def(f) 10 | fdef.name = fdef.name.replace('_expected_result', '') 11 | return ast.unparse(fdef) 12 | 13 | def do_it(tester, f, f_expected_result): 14 | fdef = gamehop.utils.get_function_def(f) 15 | gamehop.verification.canonicalization.expand.expand_non_compact_expressions(fdef) 16 | tester.assertEqual(ast.unparse(fdef), expected_result(f_expected_result)) 17 | 18 | class TestExpand(unittest.TestCase): 19 | 20 | def test_basic(self): 21 | def f(): a = b if c else d 22 | def f_expected_result(): a = b if c else d 23 | do_it(self, f, f_expected_result) 24 | 25 | def test_basic(self): 26 | def f(): a = b if c else d 27 | def f_expected_result(): a = b if c else d 28 | do_it(self, f, f_expected_result) 29 | 30 | def test_many(self): 31 | def f(y): 32 | a = b(y) if c(y) else d(y) 33 | e = f(y) if g(y) else h 34 | def f_expected_result(y): 35 | φ0 = c(y) 36 | φ1 = b(y) 37 | φ2 = d(y) 38 | a = φ1 if φ0 else φ2 39 | φ3 = g(y) 40 | φ4 = f(y) 41 | e = φ4 if φ3 else h 42 | do_it(self, f, f_expected_result) 43 | 44 | def test_recursive(self): 45 | def f(y): 46 | a = b(y) if c(y) else d(y) if e(y) else f(y) 47 | def f_expected_result(y): 48 | φ0 = c(y) 49 | φ1 = b(y) 50 | φ2 = e(y) 51 | φ3 = d(y) 52 | φ4 = f(y) 53 | φ5 = φ3 if φ2 else φ4 54 | a = φ1 if φ0 else φ5 55 | do_it(self, f, f_expected_result) 56 | -------------------------------------------------------------------------------- /tests/gamehop/verification/canonicalization/simplify/test_binary_operators.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import inspect 3 | import unittest 4 | 5 | import gamehop.inlining.internal 6 | import gamehop.verification.canonicalization.simplify as simplify 7 | 8 | def f_add(y): 9 | a = 3 + 4 10 | b = 0 + y 11 | def f_add_expected_result(y): 12 | a = 7 13 | b = y 14 | def f_sub(y): 15 | a = 3 - 4 16 | b = 0 - y 17 | c = y - 0 18 | def f_sub_expected_result(y): 19 | a = -1 20 | b = -y 21 | c = y 22 | def f_mult(y): 23 | a = 3 * 4 24 | b = 0 * y 25 | c = 1 * y 26 | d = y * -1 27 | def f_mult_expected_result(y): 28 | a = 12 29 | b = 0 30 | c = y 31 | d = -y 32 | def f_div(y): 33 | a = 6 / 2 34 | b = 0 / y 35 | c = y / 1 36 | d = y / -1 37 | def f_div_expected_result(y): 38 | a = 3.0 39 | b = 0 40 | c = y 41 | d = -y 42 | def f_floordiv(y): 43 | a = 6 // 2 44 | b = 7 // 3 45 | c = 0 // y 46 | d = y // 1 47 | def f_floordiv_expected_result(y): 48 | a = 3 49 | b = 2 50 | c = 0 51 | d = y 52 | def f_mod(y): 53 | a = 6 % 2 54 | b = 7 % 3 55 | c = 0 % y 56 | d = y % 1 57 | def f_mod_expected_result(y): 58 | a = 0 59 | b = 1 60 | c = 0 61 | d = 0 62 | def f_pow(y): 63 | a = 2 ** 3 64 | b = 2 ** y 65 | c = 1 ** y 66 | d = 0 ** y 67 | e = y ** 2 68 | f = y ** 1 69 | g = y ** 0 70 | h = 0 ** 0 71 | def f_pow_expected_result(y): 72 | a = 8 73 | b = 2 ** y 74 | c = 1 75 | d = 0 ** y 76 | e = y ** 2 77 | f = y 78 | g = 1 79 | h = 1 80 | def f_lshift(y): 81 | a = 6 << 2 82 | b = 6 << 1 83 | c = 6 << 0 84 | d = y << 2 85 | e = y << 1 86 | f = y << 0 87 | g = 2 << y 88 | h = 1 << y 89 | i = 0 << y 90 | def f_lshift_expected_result(y): 91 | a = 24 92 | b = 12 93 | c = 6 94 | d = y << 2 95 | e = y << 1 96 | f = y 97 | g = 2 << y 98 | h = 1 << y 99 | i = 0 100 | def f_rshift(y): 101 | a = 6 >> 2 102 | b = 6 >> 1 103 | c = 6 >> 0 104 | d = y >> 2 105 | e = y >> 1 106 | f = y >> 0 107 | g = 2 >> y 108 | h = 1 >> y 109 | i = 0 >> y 110 | def f_rshift_expected_result(y): 111 | a = 1 112 | b = 3 113 | c = 6 114 | d = y >> 2 115 | e = y >> 1 116 | f = y 117 | g = 2 >> y 118 | h = 1 >> y 119 | i = 0 120 | def f_bitor(y): 121 | a = 5 | 2 122 | b = 5 | 1 123 | c = 5 | 0 124 | d = y | 2 125 | e = y | 1 126 | f = y | 0 127 | g = 2 | y 128 | h = 1 | y 129 | i = 0 | y 130 | def f_bitor_expected_result(y): 131 | a = 7 132 | b = 5 133 | c = 5 134 | d = y | 2 135 | e = y | 1 136 | f = y 137 | g = 2 | y 138 | h = 1 | y 139 | i = y 140 | def f_bitxor(y): 141 | a = 5 ^ 2 142 | b = 5 ^ 1 143 | c = 5 ^ 0 144 | d = y ^ 2 145 | e = y ^ 1 146 | f = y ^ 0 147 | g = 2 ^ y 148 | h = 1 ^ y 149 | i = 0 ^ y 150 | def f_bitxor_expected_result(y): 151 | a = 7 152 | b = 4 153 | c = 5 154 | d = y ^ 2 155 | e = y ^ 1 156 | f = y 157 | g = 2 ^ y 158 | h = 1 ^ y 159 | i = y 160 | def f_bitand(y): 161 | a = 5 & 2 162 | b = 5 & 1 163 | c = 5 & 0 164 | d = y & 2 165 | e = y & 1 166 | f = y & 0 167 | g = 2 & y 168 | h = 1 & y 169 | i = 0 & y 170 | def f_bitand_expected_result(y): 171 | a = 0 172 | b = 1 173 | c = 0 174 | d = y & 2 175 | e = y & 1 176 | f = 0 177 | g = 2 & y 178 | h = 1 & y 179 | i = 0 180 | 181 | 182 | def expected_result(f): 183 | s = inspect.getsource(f) 184 | s = s.replace('_expected_result', '') 185 | return ast.unparse(ast.parse(s)) 186 | 187 | class TestSimplifyBinaryOperators(unittest.TestCase): 188 | def test_add(self): 189 | f = gamehop.utils.get_function_def(f_add) 190 | f = simplify.simplify(f) 191 | self.assertEqual( 192 | ast.unparse(f), 193 | expected_result(f_add_expected_result) 194 | ) 195 | def test_sub(self): 196 | f = gamehop.utils.get_function_def(f_sub) 197 | f = simplify.simplify(f) 198 | self.assertEqual( 199 | ast.unparse(f), 200 | expected_result(f_sub_expected_result) 201 | ) 202 | def test_mult(self): 203 | f = gamehop.utils.get_function_def(f_mult) 204 | f = simplify.simplify(f) 205 | self.assertEqual( 206 | ast.unparse(f), 207 | expected_result(f_mult_expected_result) 208 | ) 209 | def test_div(self): 210 | f = gamehop.utils.get_function_def(f_div) 211 | f = simplify.simplify(f) 212 | self.assertEqual( 213 | ast.unparse(f), 214 | expected_result(f_div_expected_result) 215 | ) 216 | def test_floordiv(self): 217 | f = gamehop.utils.get_function_def(f_floordiv) 218 | f = simplify.simplify(f) 219 | self.assertEqual( 220 | ast.unparse(f), 221 | expected_result(f_floordiv_expected_result) 222 | ) 223 | def test_mod(self): 224 | f = gamehop.utils.get_function_def(f_mod) 225 | f = simplify.simplify(f) 226 | self.assertEqual( 227 | ast.unparse(f), 228 | expected_result(f_mod_expected_result) 229 | ) 230 | def test_pow(self): 231 | f = gamehop.utils.get_function_def(f_pow) 232 | f = simplify.simplify(f) 233 | self.assertEqual( 234 | ast.unparse(f), 235 | expected_result(f_pow_expected_result) 236 | ) 237 | def test_lshift(self): 238 | f = gamehop.utils.get_function_def(f_lshift) 239 | f = simplify.simplify(f) 240 | self.assertEqual( 241 | ast.unparse(f), 242 | expected_result(f_lshift_expected_result) 243 | ) 244 | def test_rshift(self): 245 | f = gamehop.utils.get_function_def(f_rshift) 246 | f = simplify.simplify(f) 247 | self.assertEqual( 248 | ast.unparse(f), 249 | expected_result(f_rshift_expected_result) 250 | ) 251 | def test_bitor(self): 252 | f = gamehop.utils.get_function_def(f_bitor) 253 | f = simplify.simplify(f) 254 | self.assertEqual( 255 | ast.unparse(f), 256 | expected_result(f_bitor_expected_result) 257 | ) 258 | def test_bitxor(self): 259 | f = gamehop.utils.get_function_def(f_bitxor) 260 | f = simplify.simplify(f) 261 | self.assertEqual( 262 | ast.unparse(f), 263 | expected_result(f_bitxor_expected_result) 264 | ) 265 | def test_bitand(self): 266 | f = gamehop.utils.get_function_def(f_bitand) 267 | f = simplify.simplify(f) 268 | self.assertEqual( 269 | ast.unparse(f), 270 | expected_result(f_bitand_expected_result) 271 | ) 272 | -------------------------------------------------------------------------------- /tests/gamehop/verification/canonicalization/simplify/test_boolean_operators.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import inspect 3 | import unittest 4 | 5 | import gamehop.inlining.internal 6 | import gamehop.verification.canonicalization.simplify as simplify 7 | 8 | def f_andT(y): 9 | a = True and True and True 10 | def f_andT_expected_result(y): 11 | a = True 12 | def f_andF(y): 13 | a = True and False 14 | def f_andF_expected_result(y): 15 | a = False 16 | def f_orT(y): 17 | a = True or False or True 18 | def f_orT_expected_result(y): 19 | a = True 20 | def f_orF(y): 21 | a = False or False 22 | def f_orF_expected_result(y): 23 | a = False 24 | def f_manyT(y): 25 | a = True and (True or False) and (False or False or True) 26 | def f_manyT_expected_result(y): 27 | a = True 28 | def f_manyF(y): 29 | a = (False or (True and True)) and (True or (False and False)) and (False or (True and False)) 30 | def f_manyF_expected_result(y): 31 | a = False 32 | def f_manyT_with_nonconst(y): 33 | a = True and (True or False) and (False or False or True or y) 34 | def f_manyT_with_nonconst_expected_result(y): 35 | a = True 36 | def f_manyF_with_nonconst(y): 37 | a = (False or (True and True and y)) and (True or (False and False)) and (False or (True and False)) 38 | def f_manyF_with_nonconst_expected_result(y): 39 | a = False 40 | 41 | 42 | def expected_result(f): 43 | s = inspect.getsource(f) 44 | s = s.replace('_expected_result', '') 45 | return ast.unparse(ast.parse(s)) 46 | 47 | class TestSimplifyBooleanOperators(unittest.TestCase): 48 | def test_andT(self): 49 | f = gamehop.utils.get_function_def(f_andT) 50 | f = simplify.simplify(f) 51 | self.assertEqual( 52 | ast.unparse(f), 53 | expected_result(f_andT_expected_result) 54 | ) 55 | def test_andF(self): 56 | f = gamehop.utils.get_function_def(f_andF) 57 | f = simplify.simplify(f) 58 | self.assertEqual( 59 | ast.unparse(f), 60 | expected_result(f_andF_expected_result) 61 | ) 62 | def test_orT(self): 63 | f = gamehop.utils.get_function_def(f_orT) 64 | f = simplify.simplify(f) 65 | self.assertEqual( 66 | ast.unparse(f), 67 | expected_result(f_orT_expected_result) 68 | ) 69 | def test_orF(self): 70 | f = gamehop.utils.get_function_def(f_orF) 71 | f = simplify.simplify(f) 72 | self.assertEqual( 73 | ast.unparse(f), 74 | expected_result(f_orF_expected_result) 75 | ) 76 | def test_manyT(self): 77 | f = gamehop.utils.get_function_def(f_manyT) 78 | f = simplify.simplify(f) 79 | self.assertEqual( 80 | ast.unparse(f), 81 | expected_result(f_manyT_expected_result) 82 | ) 83 | def test_manyF(self): 84 | f = gamehop.utils.get_function_def(f_manyF) 85 | f = simplify.simplify(f) 86 | self.assertEqual( 87 | ast.unparse(f), 88 | expected_result(f_manyF_expected_result) 89 | ) 90 | def test_manyT_with_nonconst(self): 91 | f = gamehop.utils.get_function_def(f_manyT_with_nonconst) 92 | f = simplify.simplify(f) 93 | self.assertEqual( 94 | ast.unparse(f), 95 | expected_result(f_manyT_with_nonconst_expected_result) 96 | ) 97 | def test_manyF_with_nonconst(self): 98 | f = gamehop.utils.get_function_def(f_manyF_with_nonconst) 99 | f = simplify.simplify(f) 100 | self.assertEqual( 101 | ast.unparse(f), 102 | expected_result(f_manyF_with_nonconst_expected_result) 103 | ) 104 | -------------------------------------------------------------------------------- /tests/gamehop/verification/canonicalization/simplify/test_compare_operators.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import inspect 3 | import unittest 4 | 5 | import gamehop.inlining.internal 6 | import gamehop.verification.canonicalization.simplify as simplify 7 | 8 | def f_compareF(y): 9 | a = 1 == 2 == 3 10 | b = 1 != 1 11 | c = 1 < 0 12 | d = 1 <= 0 13 | e = 1 > 2 14 | f = 1 >= 2 15 | g = 's' in 'potato' 16 | h = 's' not in 'test' 17 | i = 1 < 2 < 4 < 8 < 0 18 | def f_compareF_expected_result(y): 19 | a = False 20 | b = False 21 | c = False 22 | d = False 23 | e = False 24 | f = False 25 | g = False 26 | h = False 27 | i = False 28 | def f_compareT(y): 29 | a = 1 == 1 30 | b = 1 != 2 31 | c = 1 < 2 32 | d = 1 <= 2 < 3 33 | e = 1 > 0 34 | f = 1 >= 0 35 | g = 's' in 'test' 36 | h = 's' not in 'potato' 37 | i = 1 < 2 < 4 < 8 < 16 38 | def f_compareT_expected_result(y): 39 | a = True 40 | b = True 41 | c = True 42 | d = True 43 | e = True 44 | f = True 45 | g = True 46 | h = True 47 | i = True 48 | 49 | 50 | def expected_result(f): 51 | s = inspect.getsource(f) 52 | s = s.replace('_expected_result', '') 53 | return ast.unparse(ast.parse(s)) 54 | 55 | class TestSimplifyCompareOperators(unittest.TestCase): 56 | def test_compareF(self): 57 | f = gamehop.utils.get_function_def(f_compareF) 58 | f = simplify.simplify(f) 59 | self.assertEqual( 60 | ast.unparse(f), 61 | expected_result(f_compareF_expected_result) 62 | ) 63 | def test_compareT(self): 64 | f = gamehop.utils.get_function_def(f_compareT) 65 | f = simplify.simplify(f) 66 | self.assertEqual( 67 | ast.unparse(f), 68 | expected_result(f_compareT_expected_result) 69 | ) 70 | -------------------------------------------------------------------------------- /tests/gamehop/verification/canonicalization/simplify/test_ifexp.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import unittest 3 | import gamehop.utils as utils 4 | import gamehop.inlining.internal 5 | import gamehop.verification.canonicalization.simplify as simplify 6 | 7 | def f_ifexp(y): 8 | a = 1 if y else 2 9 | b = 1 if True else 2 10 | c = 1 if (3 == 4) else 2 11 | d = 1 if (3 <= 4) else 2 12 | e = 1 if (3 != 4) else 2 13 | f = 1 if (3 > 4) else 2 14 | g = 1 if (3 > 4) else 2 if (3 > 5) else 3 15 | def f_ifexp_expected_result(y): 16 | a = 1 if y else 2 17 | b = 1 18 | c = 2 19 | d = 1 20 | e = 1 21 | f = 2 22 | g = 3 23 | 24 | def expected_result(f): 25 | fdef = utils.get_function_def(f) 26 | fdef.name = fdef.name.replace('_expected_result', '') 27 | return ast.unparse(fdef) 28 | 29 | class TestSimplifyIfExp(unittest.TestCase): 30 | def test_ifexp_constant_test(self): 31 | f = gamehop.utils.get_function_def(f_ifexp) 32 | f = simplify.simplify(f) 33 | self.assertEqual( 34 | ast.unparse(f), 35 | expected_result(f_ifexp_expected_result) 36 | ) 37 | 38 | def test_ifexp_body_equals_orelse(self): 39 | def f(b,c): 40 | a = b if c else b 41 | 42 | def f_expected_result(b,c): 43 | a = b 44 | 45 | f_def = gamehop.utils.get_function_def(f) 46 | f_def = simplify.simplify(f_def) 47 | self.assertEqual( 48 | ast.unparse(f_def), 49 | expected_result(f_expected_result) 50 | ) 51 | 52 | 53 | def test_if_constant_test(self): 54 | def f(y): 55 | if y: 56 | z = 1 57 | else: 58 | z = 2 59 | 60 | if True: 61 | a = 1 62 | else: 63 | a = 0 64 | 65 | if False: 66 | b = 0 67 | else: 68 | b = 1 69 | 70 | if (3 == 4): 71 | c = 0 72 | else: 73 | c = 1 74 | 75 | if (3 != 4): 76 | d = 1 77 | else: 78 | d = 0 79 | 80 | def f_expected_result(y): 81 | if y: 82 | z = 1 83 | else: 84 | z = 2 85 | 86 | a = 1 87 | 88 | b = 1 89 | 90 | c = 1 91 | 92 | d = 1 93 | 94 | f_def = gamehop.utils.get_function_def(f) 95 | f_def = simplify.simplify(f_def) 96 | self.assertEqual( 97 | ast.unparse(f_def), 98 | expected_result(f_expected_result) 99 | ) 100 | 101 | def test_if_body_equals_orelse(self): 102 | def f(b,c): 103 | if c: 104 | a = b 105 | else: 106 | a = b 107 | 108 | def f_expected_result(b,c): 109 | a = b 110 | 111 | f_def = gamehop.utils.get_function_def(f) 112 | f_def = simplify.simplify(f_def) 113 | self.assertEqual( 114 | ast.unparse(f_def), 115 | expected_result(f_expected_result) 116 | ) 117 | 118 | -------------------------------------------------------------------------------- /tests/gamehop/verification/canonicalization/simplify/test_unary_operators.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import inspect 3 | import unittest 4 | 5 | import gamehop.inlining.internal 6 | import gamehop.verification.canonicalization.simplify as simplify 7 | 8 | def f_uadd(y): 9 | a = +3 10 | def f_uadd_expected_result(y): 11 | a = 3 12 | def f_nested_usub(y): 13 | a = -(-(3)) 14 | def f_nested_usub_expected_result(y): 15 | a = 3 16 | def f_not(y): 17 | a = not(False) 18 | def f_not_expected_result(y): 19 | a = True 20 | def f_nested_not(y): 21 | a = not(not(False)) 22 | def f_nested_not_expected_result(y): 23 | a = False 24 | 25 | 26 | def expected_result(f): 27 | s = inspect.getsource(f) 28 | s = s.replace('_expected_result', '') 29 | return ast.unparse(ast.parse(s)) 30 | 31 | class TestSimplifyUnaryOperators(unittest.TestCase): 32 | def test_uadd(self): 33 | f = gamehop.utils.get_function_def(f_uadd) 34 | f = simplify.simplify(f) 35 | self.assertEqual( 36 | ast.unparse(f), 37 | expected_result(f_uadd_expected_result) 38 | ) 39 | def test_nested_usub(self): 40 | f = gamehop.utils.get_function_def(f_nested_usub) 41 | f = simplify.simplify(f) 42 | self.assertEqual( 43 | ast.unparse(f), 44 | expected_result(f_nested_usub_expected_result) 45 | ) 46 | def test_not(self): 47 | f = gamehop.utils.get_function_def(f_not) 48 | f = simplify.simplify(f) 49 | self.assertEqual( 50 | ast.unparse(f), 51 | expected_result(f_not_expected_result) 52 | ) 53 | def test_nested_not(self): 54 | f = gamehop.utils.get_function_def(f_nested_not) 55 | f = simplify.simplify(f) 56 | self.assertEqual( 57 | ast.unparse(f), 58 | expected_result(f_nested_not_expected_result) 59 | ) 60 | -------------------------------------------------------------------------------- /tests/gamehop/verification/canonicalization/test_argument_order.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import inspect 3 | import unittest 4 | 5 | import gamehop.inlining.internal 6 | import gamehop.verification.canonicalization as canonicalization 7 | import gamehop.utils as utils 8 | 9 | 10 | def f_basic1(x, y): 11 | r = (x, y) 12 | return r 13 | def f_basic1_expected_result(x, y): 14 | r = (x, y) 15 | return r 16 | def f_basic2(x, y): 17 | r = (y, x) 18 | return r 19 | def f_basic2_expected_result(y, x): 20 | r = (y, x) 21 | return r 22 | def f_unused_arg(x, y): 23 | r = (7, y) 24 | return r 25 | def f_unused_arg_expected_result(y): 26 | r = (7, y) 27 | return r 28 | def f_arith(y: int, x: int): 29 | r = x + 2 * y 30 | return r 31 | def f_arith_expected_result(x: int, y: int): 32 | r = x + 2 * y 33 | return r 34 | 35 | def expected_result(f): 36 | fdef = gamehop.utils.get_function_def(f) 37 | fdef.name = fdef.name.replace('_expected_result', '') 38 | return ast.unparse(fdef) 39 | 40 | class TestCanonicalizeArgumentOrder(unittest.TestCase): 41 | def test_basic1(self): 42 | f = gamehop.utils.get_function_def(f_basic1) 43 | gamehop.verification.canonicalization.canonicalize_argument_order(f) 44 | self.assertEqual( 45 | ast.unparse(f), 46 | expected_result(f_basic1_expected_result) 47 | ) 48 | def test_basic2(self): 49 | f = gamehop.utils.get_function_def(f_basic2) 50 | gamehop.verification.canonicalization.canonicalize_argument_order(f) 51 | self.assertEqual( 52 | ast.unparse(f), 53 | expected_result(f_basic2_expected_result) 54 | ) 55 | def test_unused_arg(self): 56 | f = gamehop.utils.get_function_def(f_unused_arg) 57 | gamehop.verification.canonicalization.canonicalize_argument_order(f) 58 | self.assertEqual( 59 | ast.unparse(f), 60 | expected_result(f_unused_arg_expected_result) 61 | ) 62 | def test_arith(self): 63 | f = gamehop.utils.get_function_def(f_arith) 64 | gamehop.verification.canonicalization.canonicalize_argument_order(f) 65 | self.assertEqual( 66 | ast.unparse(f), 67 | expected_result(f_arith_expected_result) 68 | ) 69 | 70 | def test_two_uses(self): 71 | def g(x): pass 72 | def f(a,b): 73 | g(a) 74 | g(b) 75 | g(a) 76 | 77 | def f_expected_result(a,b): 78 | g(a) 79 | g(b) 80 | g(a) 81 | 82 | fdef = utils.get_function_def(f) 83 | canonicalization.canonicalize_argument_order(fdef) 84 | self.assertEqual( 85 | ast.unparse(fdef), 86 | expected_result(f_expected_result) 87 | ) 88 | 89 | def test_assign_overwrites(self): 90 | def f(a,b): 91 | a = 3 92 | return a 93 | 94 | def f_expected_result(): 95 | a = 3 96 | return a 97 | 98 | fdef = utils.get_function_def(f) 99 | canonicalization.canonicalize_argument_order(fdef) 100 | self.assertEqual( 101 | ast.unparse(fdef), 102 | expected_result(f_expected_result) 103 | ) 104 | 105 | def test_inner_function_argument_overwrites(self): 106 | def f(a,b): 107 | def g(a,c): 108 | return a 109 | 110 | def f_expected_result(): 111 | def g(a): 112 | return a 113 | 114 | fdef = utils.get_function_def(f) 115 | canonicalization.canonicalize_argument_order(fdef) 116 | self.assertEqual( 117 | ast.unparse(fdef), 118 | expected_result(f_expected_result) 119 | ) 120 | 121 | def test_inner_function_assign_overwrites(self): 122 | def f(a,b): 123 | def g(c): 124 | a = 3 125 | return a 126 | 127 | def f_expected_result(): 128 | def g(): 129 | a = 3 130 | return a 131 | 132 | fdef = utils.get_function_def(f) 133 | canonicalization.canonicalize_argument_order(fdef) 134 | self.assertEqual( 135 | ast.unparse(fdef), 136 | expected_result(f_expected_result) 137 | ) 138 | -------------------------------------------------------------------------------- /tests/gamehop/verification/canonicalization/test_canonicalize.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import inspect 3 | import unittest 4 | 5 | import gamehop 6 | import gamehop.verification 7 | import gamehop.verification.canonicalization as canonicalization 8 | 9 | def f_inline_function_order_helper(x): 10 | return x + 1 11 | class class_inline_function_order(): 12 | def f(self): 13 | return f_inline_function_order_helper(1) 14 | def f_inline_function_order(v: class_inline_function_order): 15 | x = v.f() 16 | y = v.f() 17 | return y + 2 * x 18 | def f_inline_function_order_expected_result(): 19 | v0 = f_inline_function_order_helper(1) 20 | v1 = f_inline_function_order_helper(1) 21 | v2 = v0 + 2 * v1 22 | return v2 23 | 24 | def f_constant_return(a, b, c): 25 | d = a + b 26 | return 1 27 | def f_constant_return_expected_result(): 28 | return 1 29 | 30 | class class_inline_init_arg_helper(): 31 | prop: 1 32 | class class_inline_init_arg(): 33 | def __init__(self, x: class_inline_init_arg_helper): 34 | self.x = x 35 | def f_inline_init_arg(v: class_inline_init_arg, x: class_inline_init_arg_helper): 36 | return v.x.prop 37 | def f_inline_init_arg_expected_result(v0: class_inline_init_arg_helper): 38 | v1 = v0.prop 39 | return v1 40 | 41 | class class_inline_init_arg2_helper(): 42 | prop: 1 43 | class class_inline_init_arg2(): 44 | def setCommonClass(self, x: class_inline_init_arg2_helper): 45 | self.x = x 46 | def f_inline_init_arg2(v: class_inline_init_arg2, x: class_inline_init_arg2_helper): 47 | v.setCommonClass(x) 48 | return v.x.prop 49 | def f_inline_init_arg2_expected_result(v0: class_inline_init_arg2_helper): 50 | v1 = v0.prop 51 | return v1 52 | 53 | class class_inline_init_arg3_helper(): 54 | prop: 1 55 | class class_inline_init_arg3(): 56 | x: None 57 | def f_inline_init_arg3(v: class_inline_init_arg3, x: class_inline_init_arg3_helper): 58 | v.x = x 59 | return v.x.prop 60 | def f_inline_init_arg3_expected_result(v0: class_inline_init_arg3_helper): 61 | v1 = v0.prop 62 | return v1 63 | 64 | 65 | def expected_result(f): 66 | fdef = gamehop.utils.get_function_def(f) 67 | fdef.name = fdef.name.replace('_expected_result', '') 68 | return ast.unparse(fdef) 69 | 70 | class TestCanonicalize(unittest.TestCase): 71 | def oldtest_inline_function_order(self): 72 | f = gamehop.inlining.inline_class(f_inline_function_order, 'v', class_inline_function_order) 73 | s1 = gamehop.verification.canonicalize_function(f) 74 | f2 = gamehop.utils.get_function_def(f_inline_function_order_expected_result) 75 | gamehop.verification.canonicalization.canonicalize_function_name(f2) 76 | s2 = ast.unparse(f2) 77 | self.assertEqual(s1, s2) 78 | def test_constant_return(self): 79 | s1 = gamehop.verification.canonicalize_function(f_constant_return) 80 | f2 = gamehop.utils.get_function_def(f_constant_return_expected_result) 81 | gamehop.verification.canonicalization.canonicalize_function_name(f2) 82 | s2 = ast.unparse(f2) 83 | self.assertEqual(s1, s2) 84 | def oldtest_inline_init_arg(self): 85 | test1 = gamehop.inlining.inline_class(f_inline_init_arg, 'v', class_inline_init_arg) 86 | s1 = gamehop.verification.canonicalize_function(test1) 87 | f2 = gamehop.utils.get_function_def(f_inline_init_arg_expected_result) 88 | gamehop.verification.canonicalization.canonicalize_function_name(f2) 89 | s2 = ast.unparse(f2) 90 | self.assertEqual(s1, s2) 91 | def oldtest_inline_init_arg2(self): 92 | test1 = gamehop.inlining.inline_class(f_inline_init_arg2, 'v', class_inline_init_arg2) 93 | s1 = gamehop.verification.canonicalize_function(test1) 94 | f2 = gamehop.utils.get_function_def(f_inline_init_arg2_expected_result) 95 | gamehop.verification.canonicalization.canonicalize_function_name(f2) 96 | s2 = ast.unparse(f2) 97 | self.assertEqual(s1, s2) 98 | def oldtest_inline_init_arg3(self): 99 | test1 = gamehop.inlining.inline_class(f_inline_init_arg3, 'v', class_inline_init_arg3) 100 | s1 = gamehop.verification.canonicalize_function(test1) 101 | f2 = gamehop.utils.get_function_def(f_inline_init_arg3_expected_result) 102 | gamehop.verification.canonicalization.canonicalize_function_name(f2) 103 | s2 = ast.unparse(f2) 104 | self.assertEqual(s1, s2) 105 | 106 | def test_inline_lamda_then_expand(self): 107 | def g(x): 108 | return x 109 | def f(z): 110 | h = lambda x: g(x) 111 | r = h(z) 112 | return r 113 | 114 | def f_expected_result(v0): 115 | v1 = g(v0) 116 | return v1 117 | 118 | fdef = gamehop.utils.get_function_def(f) 119 | s1 = gamehop.verification.canonicalize_function(fdef) 120 | self.assertEqual( 121 | s1, 122 | expected_result(f_expected_result) 123 | ) 124 | 125 | def test_inline_lamda_then_expand_2(self): 126 | def g(x): 127 | return x 128 | def f(z): 129 | h = lambda x: g(x) 130 | return h(z) 131 | 132 | def f_expected_result(v0): 133 | v1 = g(v0) 134 | return v1 135 | 136 | fdef = gamehop.utils.get_function_def(f) 137 | 138 | self.assertEqual( 139 | gamehop.verification.canonicalize_function(fdef), 140 | expected_result(f_expected_result) 141 | ) 142 | 143 | -------------------------------------------------------------------------------- /tests/gamehop/verification/canonicalization/test_canonicalize_game.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import inspect 3 | import unittest 4 | 5 | from gamehop.primitives import Crypto 6 | import gamehop 7 | import gamehop.verification 8 | import gamehop.verification.canonicalization as canonicalization 9 | 10 | class G(Crypto.Game): 11 | def main(self): 12 | self.k = 1 13 | r = run(self.o_test) 14 | return r 15 | def o_test(self, b): 16 | return self.k + b 17 | 18 | class G_expected_result(Crypto.Game): 19 | def main(v0): 20 | v0.k = 1 21 | v1 = run(v0.o_test) 22 | return v1 23 | def o_test(v0, v1): 24 | v2 = v0.k + v1 25 | return v2 26 | 27 | def expected_result(c): 28 | cdef = gamehop.utils.get_class_def(c) 29 | cdef.name = 'G' 30 | return ast.unparse(cdef) 31 | 32 | class TestCanonicalizeGame(unittest.TestCase): 33 | 34 | def test_member(self): 35 | self.maxDiff = None 36 | c = gamehop.utils.get_class_def(G) 37 | s = gamehop.verification.canonicalize_game(c) 38 | self.assertEqual(s, expected_result(G_expected_result)) 39 | -------------------------------------------------------------------------------- /tests/gamehop/verification/canonicalization/test_collapse_assigns.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import inspect 3 | import unittest 4 | 5 | import gamehop.inlining.internal 6 | import gamehop.verification.canonicalization 7 | 8 | def f_constant(y): 9 | a = 7 10 | x = a 11 | return x 12 | def f_constant_expected_result(y): 13 | a = 7 14 | x = 7 15 | return 7 16 | def f_variable(y): 17 | a = y 18 | x = a 19 | return x 20 | def f_variable_expected_result(y): 21 | a = y 22 | x = y 23 | return y 24 | def f_reassign(y): 25 | a = y 26 | x = a 27 | g(x) 28 | a = 7 29 | x = a 30 | g(x) 31 | def f_reassign_expected_result(y): 32 | a = y 33 | x = y 34 | g(y) 35 | a = 7 36 | x = 7 37 | g(7) 38 | def f_tuple(y): 39 | x = 4 40 | z = y 41 | (a, b, c) = (x, y, z) 42 | g(a + 1, b + 2, c + 3, x + 4) 43 | def f_tuple_expected_result(y): 44 | x = 4 45 | z = y 46 | (a, b, c) = (4, y, y) 47 | g(4 + 1, y + 2, y + 3, 4 + 4) 48 | def f_tuple2(): 49 | c = 1 50 | d = 2 51 | (a,b) = (c,d) 52 | return a 53 | def f_tuple2_expected_result(): 54 | c = 1 55 | d = 2 56 | (a,b) = (1,2) 57 | return 1 58 | def f_tuple3(a, b): 59 | c = (a, b) 60 | (x, y) = c 61 | return x + y 62 | def f_tuple3_expected_result(a, b): 63 | c = (a, b) 64 | (x, y) = (a, b) 65 | return a + b 66 | 67 | 68 | def expected_result(f): 69 | fdef = gamehop.utils.get_function_def(f) 70 | fdef.name = fdef.name.replace('_expected_result', '') 71 | return ast.unparse(fdef) 72 | 73 | class TestCollapseAssigns(unittest.TestCase): 74 | def test_constant(self): 75 | f = gamehop.utils.get_function_def(f_constant) 76 | gamehop.verification.canonicalization.collapse_useless_assigns(f) 77 | self.assertEqual( 78 | ast.unparse(f), 79 | expected_result(f_constant_expected_result) 80 | ) 81 | def test_variable(self): 82 | f = gamehop.utils.get_function_def(f_variable) 83 | gamehop.verification.canonicalization.collapse_useless_assigns(f) 84 | self.assertEqual( 85 | ast.unparse(f), 86 | expected_result(f_variable_expected_result) 87 | ) 88 | def test_reassign(self): 89 | f = gamehop.utils.get_function_def(f_reassign) 90 | gamehop.verification.canonicalization.collapse_useless_assigns(f) 91 | self.assertEqual( 92 | ast.unparse(f), 93 | expected_result(f_reassign_expected_result) 94 | ) 95 | def test_tuple(self): 96 | f = gamehop.utils.get_function_def(f_tuple) 97 | gamehop.verification.canonicalization.collapse_useless_assigns(f) 98 | self.assertEqual( 99 | ast.unparse(f), 100 | expected_result(f_tuple_expected_result) 101 | ) 102 | def test_tuple2(self): 103 | f = gamehop.utils.get_function_def(f_tuple2) 104 | gamehop.verification.canonicalization.collapse_useless_assigns(f) 105 | self.assertEqual( 106 | ast.unparse(f), 107 | expected_result(f_tuple2_expected_result) 108 | ) 109 | def test_tuple3(self): 110 | f = gamehop.utils.get_function_def(f_tuple3) 111 | gamehop.verification.canonicalization.collapse_useless_assigns(f) 112 | self.assertEqual( 113 | ast.unparse(f), 114 | expected_result(f_tuple3_expected_result) 115 | ) 116 | 117 | def test_basic(self): 118 | def f(x): 119 | if x: 120 | a = 1 121 | else: 122 | a = 2 123 | return a 124 | 125 | def f_expected_result(x): 126 | if x: 127 | a = 1 128 | else: 129 | a = 2 130 | return a 131 | 132 | fdef = gamehop.utils.get_function_def(f) 133 | gamehop.verification.canonicalization.collapse_useless_assigns(fdef) 134 | self.assertEqual( 135 | ast.unparse(fdef), 136 | expected_result(f_expected_result) 137 | ) 138 | 139 | def test_attributes(self): 140 | def f(): 141 | (a,b) = A() 142 | (c,d) = B() 143 | e = C() 144 | e.a = a 145 | e.b = c 146 | 147 | def f_expected_result(): 148 | (a,b) = A() 149 | (c,d) = B() 150 | e = C() 151 | e.a = a 152 | e.b = c 153 | 154 | fdef = gamehop.utils.get_function_def(f) 155 | gamehop.verification.canonicalization.collapse_useless_assigns(fdef) 156 | self.assertEqual( 157 | ast.unparse(fdef), 158 | expected_result(f_expected_result) 159 | ) 160 | 161 | def test_attribute_collapse(self): 162 | def g(): pass 163 | def f(z): 164 | a = g() 165 | a.b = z 166 | return a.b 167 | 168 | def f_expected_result(z): 169 | a = g() 170 | a.b = z 171 | return z 172 | 173 | fdef = gamehop.utils.get_function_def(f) 174 | gamehop.verification.canonicalization.collapse_useless_assigns(fdef) 175 | self.assertEqual( 176 | ast.unparse(fdef), 177 | expected_result(f_expected_result) 178 | ) 179 | 180 | def test_attribute_collapse_2(self): 181 | def g(): pass 182 | def f(z, x): 183 | a = g() 184 | a.b = z 185 | a.c = x 186 | return a.b 187 | 188 | def f_expected_result(z,x): 189 | a = g() 190 | a.b = z 191 | a.c = x 192 | return z 193 | 194 | fdef = gamehop.utils.get_function_def(f) 195 | gamehop.verification.canonicalization.collapse_useless_assigns(fdef) 196 | self.assertEqual( 197 | ast.unparse(fdef), 198 | expected_result(f_expected_result) 199 | ) 200 | 201 | 202 | def test_attribute_collapse_no(self): 203 | def g(): pass 204 | def f(z): 205 | a = g() 206 | a.b = z 207 | a = g() 208 | return a.b 209 | 210 | def f_expected_result(z): 211 | a = g() 212 | a.b = z 213 | a = g() 214 | return a.b 215 | 216 | fdef = gamehop.utils.get_function_def(f) 217 | gamehop.verification.canonicalization.collapse_useless_assigns(fdef) 218 | self.assertEqual( 219 | ast.unparse(fdef), 220 | expected_result(f_expected_result) 221 | ) 222 | 223 | 224 | -------------------------------------------------------------------------------- /tests/gamehop/verification/canonicalization/test_function_name.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import inspect 3 | import unittest 4 | 5 | import gamehop.inlining.internal 6 | import gamehop.verification.canonicalization 7 | 8 | def f_basic(x, y): 9 | a = 7 10 | return y + b 11 | f_basic_expected_result = """def f(x, y): 12 | a = 7 13 | return y + b""" 14 | 15 | class TestCanonicalizeFunctionName(unittest.TestCase): 16 | def test_basic(self): 17 | f = gamehop.utils.get_function_def(f_basic) 18 | gamehop.verification.canonicalization.canonicalize_function_name(f) 19 | self.assertEqual( 20 | ast.unparse(f), 21 | f_basic_expected_result 22 | ) 23 | -------------------------------------------------------------------------------- /tests/gamehop/verification/canonicalization/test_ifstatements.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import inspect 3 | import unittest 4 | 5 | import gamehop.inlining.internal 6 | import gamehop.verification.canonicalization 7 | import gamehop.verification.canonicalization.ifstatements 8 | 9 | 10 | def f_basic_if(x): 11 | if x: 12 | z = 1 13 | else: 14 | z = 2 15 | return z 16 | def f_basic_if_expected_result(x): 17 | body_0_z = 1 18 | orelse_0_z = 2 19 | z = body_0_z if x else orelse_0_z 20 | return z 21 | 22 | def f_if_missing_vars(x): 23 | if x: 24 | w = 1 25 | else: 26 | z = 2 27 | return z 28 | def f_if_missing_vars_expected_result(x): 29 | body_0_w = 1 30 | orelse_0_z = 2 31 | orelse_0_w = None 32 | body_0_z = None 33 | ifcond_0 = x 34 | w = body_0_w if ifcond_0 else orelse_0_w 35 | z = body_0_z if ifcond_0 else orelse_0_z 36 | return z 37 | 38 | def f_multiple_ifs(x): 39 | if x: w = 1 40 | else: w = 2 41 | if x == 3: w = 3 42 | else: w = 4 43 | return w 44 | def f_multiple_ifs_expected_result(x): 45 | body_0_w = 1 46 | orelse_0_w = 2 47 | w = body_0_w if x else orelse_0_w 48 | body_1_w = 3 49 | orelse_1_w = 4 50 | w = body_1_w if x == 3 else orelse_1_w 51 | return w 52 | 53 | def f_elif(x): 54 | if x == 1: w = 1 55 | elif x == 2: w = 2 56 | else: w = 3 57 | return w 58 | def f_elif_expected_result(x): 59 | body_1_w = 1 60 | orelse_1_body_0_w = 2 61 | orelse_1_orelse_0_w = 3 62 | orelse_1_w = orelse_1_body_0_w if x == 2 else orelse_1_orelse_0_w 63 | body_1_body_0_w = None 64 | body_1_orelse_0_w = None 65 | ifcond_1 = x == 1 66 | w = body_1_w if ifcond_1 else orelse_1_w 67 | body_0_w = body_1_body_0_w if ifcond_1 else orelse_1_body_0_w 68 | orelse_0_w = body_1_orelse_0_w if ifcond_1 else orelse_1_orelse_0_w 69 | return w 70 | 71 | def expected_result(f): 72 | s = inspect.getsource(f) 73 | s = s.replace('_expected_result', '') 74 | return ast.unparse(ast.parse(s)) 75 | 76 | class TestIfStatementsToExpressions(unittest.TestCase): 77 | def test_basic1(self): 78 | f = gamehop.utils.get_function_def(f_basic_if) 79 | gamehop.verification.canonicalization.ifstatements.if_statements_to_expressions(f) 80 | self.assertEqual( 81 | ast.unparse(f), 82 | expected_result(f_basic_if_expected_result) 83 | ) 84 | def test_if_missing_vars(self): 85 | f = gamehop.utils.get_function_def(f_if_missing_vars) 86 | gamehop.verification.canonicalization.ifstatements.if_statements_to_expressions(f) 87 | self.assertEqual( 88 | ast.unparse(f), 89 | expected_result(f_if_missing_vars_expected_result) 90 | ) 91 | def test_multiple_ifs(self): 92 | f = gamehop.utils.get_function_def(f_multiple_ifs) 93 | gamehop.verification.canonicalization.ifstatements.if_statements_to_expressions(f) 94 | self.assertEqual( 95 | ast.unparse(f), 96 | expected_result(f_multiple_ifs_expected_result) 97 | ) 98 | def test_elif(self): 99 | f = gamehop.utils.get_function_def(f_elif) 100 | gamehop.verification.canonicalization.ifstatements.if_statements_to_expressions(f) 101 | self.assertEqual( 102 | ast.unparse(f), 103 | expected_result(f_elif_expected_result) 104 | ) 105 | -------------------------------------------------------------------------------- /tests/gamehop/verification/canonicalization/test_inline_lambdas.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import inspect 3 | import unittest 4 | 5 | import gamehop.inlining.internal 6 | import gamehop.verification.canonicalization 7 | 8 | def expected_result(f): 9 | fdef = gamehop.utils.get_function_def(f) 10 | fdef.name = fdef.name.replace('_expected_result', '') 11 | return ast.unparse(fdef) 12 | 13 | class TestCanonicalizeInlineLambda(unittest.TestCase): 14 | def test_basic(self): 15 | def f_basic(x, y): 16 | g = lambda z: z + 7 + x 17 | r = g(y) 18 | return r 19 | def f_basic_expected_result(x, y): 20 | g = lambda z: z + 7 + x 21 | r = y + 7 + x 22 | return r 23 | 24 | f = gamehop.utils.get_function_def(f_basic) 25 | gamehop.verification.canonicalization.inline_lambdas(f) 26 | self.assertEqual( 27 | ast.unparse(f), 28 | expected_result(f_basic_expected_result) 29 | ) 30 | -------------------------------------------------------------------------------- /tests/gamehop/verification/canonicalization/test_line_order.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import inspect 3 | import unittest 4 | 5 | import gamehop.inlining.internal 6 | import gamehop.verification.canonicalization as canonicalization 7 | 8 | def f_basic1(x, y): 9 | v = 3 10 | (u, w) = (1, 2) 11 | r = (u, v) 12 | return r 13 | def f_basic1_expected_result(x, y): 14 | (u, w) = (1, 2) 15 | v = 3 16 | r = (u, v) 17 | return r 18 | def f_basic2(x, y): 19 | (u, v) = (1, 2) 20 | v = 3 21 | r = (u, v) 22 | return r 23 | def f_basic2_expected_result(x, y): 24 | (u, v) = (1, 2) 25 | v = 3 26 | r = (u, v) 27 | return r 28 | def f_basic3(x, y): 29 | u = 1 30 | w = 3 31 | v = 2 32 | r = (u, v, w) 33 | return r 34 | def f_basic3_expected_result(x, y): 35 | u = 1 36 | v = 2 37 | w = 3 38 | r = (u, v, w) 39 | return r 40 | def f_ordering(a): 41 | c = h(a) 42 | d = g(c) 43 | b = g(c) 44 | r = (b, c, d) 45 | return r 46 | def f_ordering_expected_result(a): 47 | c = h(a) 48 | b = g(c) 49 | d = g(c) 50 | r = (b, c, d) 51 | return r 52 | def f_KEMfromPKEtestcase1(pke): 53 | m0 = h(pke.MS) 54 | m1 = h(pke.MS) 55 | ct = E(m0) 56 | r = g(ct, m1) 57 | return r 58 | def f_KEMfromPKEtestcase2(pke): 59 | m0 = h(pke.MS) 60 | ct = E(m0) 61 | m1 = h(pke.MS) 62 | r = g(ct, m1) 63 | return r 64 | def f_degenerate(self, A, B, C, D, E): 65 | pk = A() 66 | ct = B(pk) 67 | m0 = C(pk) 68 | mask = D('label', m0) 69 | r = E(ct, mask, m0) 70 | return r 71 | def f_degenerate_expected_result(self, A, B, C, D, E): 72 | pk = A() 73 | m0 = C(pk) 74 | ct = B(pk) 75 | mask = D('label', m0) 76 | r = E(ct, mask, m0) 77 | return r 78 | def f_attribute(A, a, b): 79 | x = A() 80 | (x.u, x.v) = a, b 81 | y = x.u + x.v 82 | return y 83 | def f_attribute_expected_result(A, a, b): 84 | x = A() 85 | (x.u, x.v) = a, b 86 | y = x.u + x.v 87 | return y 88 | 89 | def expected_result(f): 90 | fdef = gamehop.utils.get_function_def(f) 91 | fdef.name = fdef.name.replace('_expected_result', '') 92 | return ast.unparse(fdef) 93 | 94 | class TestCanonicalizeLineOrder(unittest.TestCase): 95 | def test_basic1(self): 96 | f = gamehop.utils.get_function_def(f_basic1) 97 | gamehop.verification.canonicalization.canonicalize_line_order(f) 98 | self.assertEqual( 99 | ast.unparse(f), 100 | expected_result(f_basic1_expected_result) 101 | ) 102 | def test_basic2(self): 103 | f = gamehop.utils.get_function_def(f_basic2) 104 | gamehop.verification.canonicalization.canonicalize_line_order(f) 105 | self.assertEqual( 106 | ast.unparse(f), 107 | expected_result(f_basic2_expected_result) 108 | ) 109 | def test_basic3(self): 110 | f = gamehop.utils.get_function_def(f_basic3) 111 | gamehop.verification.canonicalization.canonicalize_line_order(f) 112 | self.assertEqual( 113 | ast.unparse(f), 114 | expected_result(f_basic3_expected_result) 115 | ) 116 | def test_ordering(self): 117 | f = gamehop.utils.get_function_def(f_ordering) 118 | gamehop.verification.canonicalization.canonicalize_line_order(f) 119 | self.assertEqual( 120 | ast.unparse(f), 121 | expected_result(f_ordering_expected_result) 122 | ) 123 | def test_attribute(self): 124 | f = gamehop.utils.get_function_def(f_attribute) 125 | gamehop.verification.canonicalization.canonicalize_line_order(f) 126 | self.assertEqual( 127 | ast.unparse(f), 128 | expected_result(f_attribute_expected_result) 129 | ) 130 | def test_KEMfromPKEtestcase(self): 131 | f1 = gamehop.utils.get_function_def(f_KEMfromPKEtestcase1) 132 | f2 = gamehop.utils.get_function_def(f_KEMfromPKEtestcase2) 133 | gamehop.verification.canonicalization.canonicalize_line_order(f1) 134 | gamehop.verification.canonicalization.canonicalize_line_order(f2) 135 | s1 = ast.unparse(f1).replace('f_KEMfromPKEtestcase1', 'f') 136 | s2 = ast.unparse(f2).replace('f_KEMfromPKEtestcase2', 'f') 137 | self.assertEqual(s1, s2) 138 | def test_degenerate(self): 139 | f = gamehop.utils.get_function_def(f_degenerate) 140 | gamehop.verification.canonicalization.canonicalize_line_order(f) 141 | self.assertEqual( 142 | ast.unparse(f), 143 | expected_result(f_degenerate_expected_result) 144 | ) 145 | 146 | def test_KEMfromPKEtestcase2(self): 147 | def f(self, scheme, adversaryⴰinitⴰkem_adversary): 148 | (pk, sk) = scheme.KeyGen() 149 | adversaryⴰss0 = Crypto.UniformlySample(SharedSecret) 150 | adversaryⴰct0 = scheme.Encrypt(pk, adversaryⴰss0) 151 | adversaryⴰss1 = Crypto.UniformlySample(SharedSecret) 152 | adversaryⴰct1 = scheme.Encrypt(pk, adversaryⴰss1) 153 | ct = scheme.Encrypt(pk, adversaryⴰss1) 154 | r = adversaryⴰinitⴰkem_adversary.guess(pk, ct, adversaryⴰss0) 155 | ifexp0 = len(adversaryⴰss0) == len(adversaryⴰss1) 156 | return (r, ifexp0) 157 | def f_expected_result(self, scheme, adversaryⴰinitⴰkem_adversary): 158 | (pk, sk) = scheme.KeyGen() 159 | adversaryⴰss1 = Crypto.UniformlySample(SharedSecret) 160 | ct = scheme.Encrypt(pk, adversaryⴰss1) 161 | adversaryⴰss0 = Crypto.UniformlySample(SharedSecret) 162 | r = adversaryⴰinitⴰkem_adversary.guess(pk, ct, adversaryⴰss0) 163 | ifexp0 = len(adversaryⴰss0) == len(adversaryⴰss1) 164 | return (r, ifexp0) 165 | f1 = gamehop.utils.get_function_def(f) 166 | f2 = gamehop.utils.get_function_def(f_expected_result) 167 | gamehop.verification.canonicalization.canonicalize_line_order(f1) 168 | s1 = ast.unparse(f1) 169 | s2 = ast.unparse(f2).replace('f_expected_result', 'f') 170 | self.assertEqual(s1, s2) 171 | 172 | def test_blah(self): 173 | def g(x): 174 | return x 175 | # def f(z): 176 | # return g(z) 177 | 178 | def f(z): 179 | return g(z) 180 | 181 | 182 | def f_expected_result(z): 183 | φ0 = g(z) 184 | return φ0 185 | 186 | fdef = gamehop.utils.get_function_def(f) 187 | canonicalization.expand.expand_non_compact_expressions(fdef) 188 | canonicalization.canonicalize_line_order(fdef) 189 | 190 | self.assertEqual( 191 | ast.unparse(fdef), 192 | expected_result(f_expected_result) 193 | ) 194 | -------------------------------------------------------------------------------- /tests/gamehop/verification/canonicalization/test_return.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import inspect 3 | import unittest 4 | 5 | import gamehop.inlining.internal 6 | import gamehop.verification.canonicalization 7 | 8 | def expected_result(f): 9 | fdef = gamehop.utils.get_function_def(f) 10 | fdef.name = fdef.name.replace('_expected_result', '') 11 | return ast.unparse(fdef) 12 | 13 | class TestCanonicalizeReturn(unittest.TestCase): 14 | def test_constant(self): 15 | def f_constant(x, y): 16 | a = 7 17 | return 3 18 | def f_constant_expected_result(x, y): 19 | a = 7 20 | return 3 21 | f = gamehop.utils.get_function_def(f_constant) 22 | gamehop.verification.expand.expand_non_compact_expressions(f) 23 | self.assertEqual( 24 | ast.unparse(f), 25 | expected_result(f_constant_expected_result) 26 | ) 27 | def test_variable(self): 28 | def f_variable(x, y): 29 | a = 7 30 | return a 31 | def f_variable_expected_result(x, y): 32 | a = 7 33 | return a 34 | f = gamehop.utils.get_function_def(f_variable) 35 | gamehop.verification.expand.expand_non_compact_expressions(f) 36 | self.assertEqual( 37 | ast.unparse(f), 38 | expected_result(f_variable_expected_result) 39 | ) 40 | def test_tuple(self): 41 | def f_tuple(x, y): 42 | a = 7 43 | return (a, x, y) 44 | def f_tuple_expected_result(x, y): 45 | a = 7 46 | φ0 = (a, x, y) 47 | return φ0 48 | f = gamehop.utils.get_function_def(f_tuple) 49 | gamehop.verification.expand.expand_non_compact_expressions(f) 50 | self.assertEqual( 51 | ast.unparse(f), 52 | expected_result(f_tuple_expected_result) 53 | ) 54 | def test_attribute(self): 55 | def f_attribute(x, y): 56 | return x.a 57 | def f_attribute_expected_result(x, y): 58 | return x.a 59 | f = gamehop.utils.get_function_def(f_attribute) 60 | gamehop.verification.expand.expand_non_compact_expressions(f) 61 | self.assertEqual( 62 | ast.unparse(f), 63 | expected_result(f_attribute_expected_result) 64 | ) 65 | def test_operator(self): 66 | def f_operator(x, y): 67 | a = 7 68 | return y + a 69 | def f_operator_expected_result(x, y): 70 | a = 7 71 | φ0 = y + a 72 | return φ0 73 | f = gamehop.utils.get_function_def(f_operator) 74 | gamehop.verification.expand.expand_non_compact_expressions(f) 75 | self.assertEqual( 76 | ast.unparse(f), 77 | expected_result(f_operator_expected_result) 78 | ) 79 | -------------------------------------------------------------------------------- /tests/gamehop/verification/canonicalization/test_unnecessary_members.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import inspect 3 | import unittest 4 | 5 | import gamehop.inlining.internal 6 | import gamehop.verification.canonicalization.classes 7 | 8 | def expected_result(c): 9 | cdef = gamehop.utils.get_class_def(c) 10 | cdef.name = cdef.name.replace('_expected_result', '') 11 | return ast.unparse(cdef) 12 | 13 | class TestUnnecessaryMembers(unittest.TestCase): 14 | def test_basic(self): 15 | class C: 16 | def __init__(self): 17 | self.u = 1 18 | self.w = 3 19 | @staticmethod 20 | def potato(s): 21 | return s 22 | def chicken(self): 23 | return 7 24 | def beef(self): 25 | self.v = 4 26 | return self.u 27 | class C_expected_result: 28 | def __init__(self): 29 | self.u = 1 30 | self_w = 3 31 | @staticmethod 32 | def potato(s): 33 | return s 34 | def chicken(self): 35 | return 7 36 | def beef(self): 37 | self_v = 4 38 | return self.u 39 | c = gamehop.utils.get_class_def(C) 40 | gamehop.verification.canonicalization.classes.unnecessary_members(c) 41 | self.assertEqual( 42 | ast.unparse(c), 43 | expected_result(C_expected_result) 44 | ) 45 | def test_with_oracle(self): 46 | class C: 47 | def chicken(self): 48 | return 7 + self.o_beef() 49 | def o_beef(self): 50 | return 5 51 | class C_expected_result: 52 | def chicken(self): 53 | return 7 + self.o_beef() 54 | def o_beef(self): 55 | return 5 56 | c = gamehop.utils.get_class_def(C) 57 | gamehop.verification.canonicalization.classes.unnecessary_members(c) 58 | self.assertEqual( 59 | ast.unparse(c), 60 | expected_result(C_expected_result) 61 | ) 62 | -------------------------------------------------------------------------------- /tests/gamehop/verification/canonicalization/test_variable_names.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import inspect 3 | import unittest 4 | 5 | import gamehop.inlining.internal 6 | import gamehop.verification.canonicalization 7 | 8 | def f_basic(x, y): 9 | a = 7 10 | return y + a 11 | def f_basic_expected_result(v0, v1): 12 | v2 = 7 13 | return v1 + v2 14 | 15 | 16 | def expected_result(f): 17 | s = inspect.getsource(f) 18 | s = s.replace('_expected_result', '') 19 | return ast.unparse(ast.parse(s)) 20 | 21 | class TestCanonicalizeVariableNames(unittest.TestCase): 22 | def test_basic(self): 23 | f = gamehop.utils.get_function_def(f_basic) 24 | gamehop.verification.canonicalization.canonicalize_variable_names(f) 25 | self.assertEqual( 26 | ast.unparse(f), 27 | expected_result(f_basic_expected_result) 28 | ) 29 | -------------------------------------------------------------------------------- /tests/test_template.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import inspect 3 | import unittest 4 | import gamehop.utils as utils 5 | 6 | 7 | def expected_result(f): 8 | fdef = utils.get_function_def(f) 9 | fdef.name = fdef.name.replace('_expected_result', '') 10 | return ast.unparse(fdef) 11 | 12 | class TestTemplate(unittest.TestCase): 13 | def test_template(self): 14 | def f(): 15 | return 1 16 | 17 | def f_expected_result(): 18 | return 1 19 | 20 | fdef = utils.get_function_def(f) 21 | self.assertEqual( 22 | ast.unparse(fdef), 23 | expected_result(f_expected_result) 24 | ) 25 | --------------------------------------------------------------------------------