[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: 🐞 Bug\ndescription: Submit a bug if something isn't working as expected.\ntitle: \"🐞 <title>\"\nlabels: [bug, triage]\nbody:\n  - type: checkboxes\n    attributes:\n      label: Is there an existing issue for this?\n      description: Please search to see if an issue already exists for the bug you encountered.\n      options:\n        - label: I have searched the existing issues\n          required: true\n  - type: textarea\n    attributes:\n      label: Current Behavior\n      description: A concise description of what you're experiencing.\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Expected Behavior\n      description: A concise description of what you expected to happen.\n    validations:\n      required: false\n  - type: textarea\n    attributes:\n      label: Steps To Reproduce\n      description: Steps to reproduce the behavior.\n      placeholder: |\n        1. In this environment...\n        2. With this config...\n        3. Run '...'\n        4. See error...\n    validations:\n      required: false\n  - type: textarea\n    attributes:\n      label: Environment\n      description: |\n        examples:\n          - **OS**: Ubuntu 20.04\n      value: |\n        - OS:\n      render: markdown\n    validations:\n      required: false\n  - type: textarea\n    attributes:\n      label: Anything else?\n      description: |\n        Links? References? Screenshots? Possible Solution? Anything that will give us more context about the issue you are encountering!\n\n        Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.\n    validations:\n      required: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: Discussions\n    url: https://github.com/microsoft/electionguard/discussions\n    about: Discuss suggestions and new enhancements here.\n  - name: ElectionGuard Info\n    url: https://https://www.electionguard.vote/\n    about: Learn more about ElectionGuard or email the team.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/enhancement.yml",
    "content": "name: ✨ Enhancement\ndescription: Suggest an enhancement or an improvement.\ntitle: \"✨ <title>\"\nlabels: [enhancement, triage]\nbody:\n  - type: checkboxes\n    attributes:\n      label: Is there an existing issue for this?\n      description: Please search to see if an issue already exists for the suggestion.\n      options:\n        - label: I have searched the existing issues\n          required: true\n  - type: textarea\n    attributes:\n      label: Suggestion\n      description: Tell us how we could improve ElectionGuard.\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Possible Implementation\n      description: Not obligatory, but ideas as to the implementation of the suggestion.\n    validations:\n      required: false\n  - type: textarea\n    attributes:\n      label: Anything else?\n      description: |\n        What are you trying to accomplish?\n\n        Links? References? Anything that will give us more context about the suggestion!\n\n        Tip: You can attach images by clicking this area to highlight it and then dragging files in.\n    validations:\n      required: false\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "[//]: # (🚨 Please review the CONTRIBUTING.md in this repository. 💔Thank you!)\n\n### Issue\n*Link your PR to an issue*\n\nFixes #___\n\n### Description\n*Please describe your pull request.*\n\n### Testing\n*Describe the best way to test or validate your PR.*\n"
  },
  {
    "path": ".github/workflows/pull_request.yml",
    "content": "name: Validate Pull Request\n\non:\n  push:\n    branches: [main, \"integration/**\", \"releases/**\"]\n  pull_request:\n    branches: [main, \"integration/**\", \"releases/**\"]\n  repository_dispatch:\n    types: [pull_request]\n  schedule:\n    - cron: \"30 23 * * 1\" # 2330 UTC Every Monday\n\nenv:\n  PYTHON_VERSION: 3.9\n  POETRY_PATH: \"$HOME/.poetry/bin\"\n\njobs:\n  code_analysis:\n    name: Code Analysis\n    runs-on: ubuntu-latest\n    permissions:\n      actions: read\n      contents: read\n      security-events: write\n    strategy:\n      fail-fast: false\n      matrix:\n        language: [\"python\"]\n    steps:\n      - name: Checkout Code\n        uses: actions/checkout@v2\n      - name: Set up Python ${{ env.PYTHON_VERSION }}\n        uses: actions/setup-python@v2\n        with:\n          python-version: ${{ env.PYTHON_VERSION }}\n      - name: Change Directory\n        run: cd ${{ github.workspace }}\n      - name: Setup Environment\n        run: make environment\n      - name: Add Poetry Path\n        run: echo ${{ env.POETRY_PATH }} >> $GITHUB_PATH\n      - name: Install Dependencies\n        run: make install\n      - name: Lint\n        run: make lint\n      - name: Initialize CodeQL\n        uses: github/codeql-action/init@v2\n        with:\n          languages: \"${{ matrix.language }}\"\n      - name: Autobuild\n        uses: github/codeql-action/autobuild@v2\n      - name: Perform CodeQL Analysis\n        uses: github/codeql-action/analyze@v2\n\n  linux_check:\n    name: Linux Check\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout Code\n        uses: actions/checkout@v2\n      - name: Set up Python ${{ env.PYTHON_VERSION }}\n        uses: actions/setup-python@v2\n        with:\n          python-version: ${{ env.PYTHON_VERSION }}\n      - name: Change Directory\n        run: cd ${{ github.workspace }}\n      - name: Setup Environment\n        run: make environment\n      - name: Add Poetry Path\n        run: echo ${{ env.POETRY_PATH }} >> $GITHUB_PATH\n      - name: Install Dependencies\n        run: make install\n      - name: Build\n        run: make build validate\n      - name: Full Test Suite & Coverage\n        uses: nick-fields/retry@v2\n        with:\n          timeout_minutes: 10\n          max_attempts: 1\n          retry_on: timeout\n          command: make coverage\n\n  mac_check:\n    name: MacOS Check\n    runs-on: macos-latest\n    steps:\n      - name: Checkout Code\n        uses: actions/checkout@v2\n      - name: Set up Python ${{ env.PYTHON_VERSION }}\n        uses: actions/setup-python@v2\n        with:\n          python-version: ${{ env.PYTHON_VERSION }}\n      - name: Change Directory\n        run: cd ${{ github.workspace }}\n      - name: Setup Environment\n        run: make environment\n      - name: Add Poetry Path\n        run: echo ${{ env.POETRY_PATH }} >> $GITHUB_PATH\n      - name: Install Dependencies\n        run: make install\n      - name: Build\n        run: make build validate\n      - name: Integration Tests\n        uses: nick-fields/retry@v2\n        with:\n          timeout_minutes: 3\n          max_attempts: 1\n          retry_on: timeout\n          command: make test-integration\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release Build\n\non:\n  milestone:\n    types: [closed]\n\nenv:\n  PYTHON_VERSION: 3.9\n  POETRY_PATH: \"$HOME/.poetry/bin\"\n\njobs:\n  code_analysis:\n    name: Code Analysis\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout Code\n        uses: actions/checkout@v2\n      - name: Set up Python ${{ env.PYTHON_VERSION }}\n        uses: actions/setup-python@v2\n        with:\n          python-version: ${{ env.PYTHON_VERSION }}\n      - name: Change Directory\n        run: cd ${{ github.workspace }}\n      - name: Setup Environment\n        run: make environment\n      - name: Add Poetry Path\n        run: echo ${{ env.POETRY_PATH }} >> $GITHUB_PATH\n      - name: Install Dependencies\n        run: make install\n      - name: Lint\n        run: make lint\n      - name: Initialize CodeQL\n        uses: github/codeql-action/init@v2\n        with:\n          languages: python\n      - name: Autobuild\n        uses: github/codeql-action/autobuild@v2\n      - name: Perform CodeQL Analysis\n        uses: github/codeql-action/analyze@v2\n\n  linux_check:\n    name: Linux Check\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout Code\n        uses: actions/checkout@v2\n      - name: Set up Python ${{ env.PYTHON_VERSION }}\n        uses: actions/setup-python@v2\n        with:\n          python-version: ${{ env.PYTHON_VERSION }}\n      - name: Change Directory\n        run: cd ${{ github.workspace }}\n      - name: Setup Environment\n        run: make environment\n      - name: Add Poetry Path\n        run: echo ${{ env.POETRY_PATH }} >> $GITHUB_PATH\n      - name: Install Dependencies\n        run: make install\n      - name: Build\n        run: make build validate\n      - name: Full Test Suite & Coverage\n        run: make coverage\n\n  mac_check:\n    name: MacOS Check\n    runs-on: macos-latest\n    steps:\n      - name: Checkout Code\n        uses: actions/checkout@v2\n      - name: Set up Python ${{ env.PYTHON_VERSION }}\n        uses: actions/setup-python@v2\n        with:\n          python-version: ${{ env.PYTHON_VERSION }}\n      - name: Change Directory\n        run: cd ${{ github.workspace }}\n      - name: Setup Environment\n        run: make environment\n      - name: Add Poetry Path\n        run: echo ${{ env.POETRY_PATH }} >> $GITHUB_PATH\n      - name: Install Dependencies\n        run: make install\n      - name: Build\n        run: make build validate\n      - name: Integration Tests\n        run: make test-integration\n\n  release:\n    name: Release\n    runs-on: ubuntu-latest\n    needs: [code_analysis, mac_check, linux_check]\n    steps:\n      - name: Checkout Code\n        uses: actions/checkout@v2\n      - name: Set up Python ${{ env.PYTHON_VERSION }}\n        uses: actions/setup-python@v1\n        with:\n          python-version: ${{ env.PYTHON_VERSION }}\n      - name: Change Directory\n        run: cd ${{ github.workspace }}\n      - name: Setup Environment\n        run: make environment\n      - name: Add Poetry Path\n        run: echo ${{ env.POETRY_PATH }} >> $GITHUB_PATH\n      - name: Install Dependencies\n        run: make install\n      - name: Get Version\n        run: echo \"PACKAGE_VERSION=$(echo $VERSION | poetry version --short)\" >> $GITHUB_ENV\n      - name: Generate release notes\n        run: make release-notes\n      - name: Create Release\n        id: create_release\n        uses: actions/create-release@v1.0.0\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          tag_name: ${{ env.PACKAGE_VERSION }}\n          release_name: Release ${{ env.PACKAGE_VERSION }}\n          draft: false\n          prerelease: false\n      - name: Dependency Graph\n        run: make dependency-graph-ci\n      - name: Upload Dependency Graph as Artifact\n        uses: actions/upload-artifact@v2\n        with:\n          name: dependency-graph\n          path: dependency-graph.svg\n      - name: Build\n        run: make build\n      - name: Upload Package as Artifact\n        uses: actions/upload-artifact@v2\n        with:\n          name: package\n          path: dist/\n      - name: Upload Package to PyPi\n        env:\n          TEST_PYPI_TOKEN: ${{ secrets.TEST_PYPI_TOKEN }}\n          PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}\n        run: make publish-ci\n      - name: Zip Artifacts\n        run: make release-zip-ci\n      - name: Add Artifacts to Release\n        id: upload-release-asset_1\n        uses: actions/upload-release-asset@v1.0.1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          upload_url: ${{ steps.create_release.outputs.upload_url }}\n          asset_path: ./electionguard.zip\n          asset_name: electionguard.zip\n          asset_content_type: application/zip\n      - name: Deploy Github Pages\n        run: make docs-deploy-ci\n"
  },
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\ndata/0.95.0/\ndata/1.0/\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\npip-wheel-metadata/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\ncoverage/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n.python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# PyCharm\n.idea/\n\ncoverage/\n\ncov.xml\n\n.DS_Store\n\n# File output\nelection_record\nelection_private_data\nguardian_private_data.json\nelection_record.zip\nelection_private_data.zip\nschemas\n\n# VS Code\n.vscode/settings.json\nsample-data.zip*\n\nresults/\ngui_private_keys/\ndatabase/\negui_mnt/\n"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n  // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.\n  // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp\n\n  // List of extensions which should be recommended for users of this workspace.\n  \"recommendations\": [\n    \"ms-python.python\",\n    \"visualstudioexptteam.vscodeintellicode\",\n    \"magicstack.magicpython\",\n    \"hbenl.vscode-test-explorer\",\n    \"littlefoxteam.vscode-python-test-adapter\",\n    \"cschleiden.vscode-github-actions\",\n    \"bungcip.better-toml\"\n  ],\n  // List of extensions recommended by VS Code that should not be recommended for users of this workspace.\n  \"unwantedRecommendations\": []\n}\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Microsoft Open Source Code of Conduct\r\n\r\nThis project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).\r\n\r\nResources:\r\n\r\n- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)\r\n- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)\r\n- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns\r\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "## Contributing\n\nThis project welcomes contributions and suggestions. Before you get started, you should read the [readme](README.md) and [the design and architecture notes](docs/Design_and_Architecture.md), which describe design & architecture goals of the code and tools & techniques used.\n\n- 🤔 **CONSIDER** adding a unit test if your PR resolves an issue.\n- ✅ **DO** check open PR's to avoid duplicates.\n- ✅ **DO** keep pull requests small so they can be easily reviewed.\n- ✅ **DO** build locally before pushing.\n- ✅ **DO** make sure tests pass.\n- ✅ **DO** make sure any new changes are documented.\n- ✅ **DO** make sure not to introduce any compiler warnings.\n- ❌**AVOID** breaking the continuous integration build.\n- ❌**AVOID** making significant changes to the overall architecture.\n\n### Creating a Pull Request\n\nAll pull requests should have an accompanying issue. Create one if there is not one matching your code. The code will be checked by continuous integration. Once this CI passes, the code will be reviewed, ideally approved, then merged.\n\n### CLA\n\nOpen source contributions require you to agree to a standard Microsoft Contributor License Agreement (CLA) declaring that you grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.\n\nWhen you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.\n\n### Code of Conduct\n\nThis project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.\n"
  },
  {
    "path": "LICENSE",
    "content": "    MIT License\r\n\r\n    Copyright (c) Microsoft Corporation.\r\n\r\n    Permission is hereby granted, free of charge, to any person obtaining a copy\r\n    of this software and associated documentation files (the \"Software\"), to deal\r\n    in the Software without restriction, including without limitation the rights\r\n    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n    copies of the Software, and to permit persons to whom the Software is\r\n    furnished to do so, subject to the following conditions:\r\n\r\n    The above copyright notice and this permission notice shall be included in all\r\n    copies or substantial portions of the Software.\r\n\r\n    THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\n    SOFTWARE\r\n"
  },
  {
    "path": "Makefile",
    "content": ".PHONY: all environment openssl-fix install install-gmp install-gmp-mac install-gmp-linux install-gmp-windows install-mkdocs auto-lint validate test test-example bench coverage coverage-html coverage-xml coverage-erase fetch-sample-data\n\nCODE_COVERAGE ?= 90\nOS ?= $(shell python3 -c 'import platform; print(platform.system())')\nifeq ($(OS), Linux)\nPKG_MGR ?= $(shell python3 -c 'import subprocess as sub; print(next(filter(None, (sub.getstatusoutput(f\"command -v {pm}\")[0] == 0 and pm for pm in [\"apt-get\", \"pacman\"])), \"undefined\"))')\nendif\nSAMPLE_BALLOT_COUNT ?= 5\nSAMPLE_BALLOT_SPOIL_RATE ?= 50\n\nall: environment install build validate auto-lint coverage\n\nenvironment:\n\t@echo 🔧 ENVIRONMENT SETUP\n\tmake install-gmp\n\tpython3 -m pip install -U pip\n\tpip3 install 'poetry==2.2.1'\n\tpoetry config virtualenvs.in-project true \n\tprintf \"Cython<3\\n\" > /tmp/pip-constraints.txt\n\tPIP_CONSTRAINT=/tmp/pip-constraints.txt poetry install\n\tpoetry run pip install 'setuptools<83'\n\t@echo 🚨 Be sure to add poetry to PATH\n\tmake fetch-sample-data\n\ninstall:\n\t@echo 🔧 INSTALL\n\tpoetry install\n\tpoetry run pip install 'setuptools<83'\n\nbuild:\n\t@echo 🔨 BUILD\n\tpoetry build\n\tpoetry install \n\tpoetry run pip install 'setuptools<83'\n\nopenssl-fix:\n\texport LDFLAGS=-L/usr/local/opt/openssl/lib\n\texport CPPFLAGS=-I/usr/local/opt/openssl/include \n\ninstall-gmp:\n\t@echo 📦 Install gmp\n\t@echo Operating System identified as $(OS)\nifeq ($(OS), Linux)\n\tmake install-gmp-linux\nendif\nifeq ($(OS), Darwin)\n\tmake install-gmp-mac\nendif\n\ninstall-gmp-mac:\n\t@echo 🍎 MACOS INSTALL\n\tbrew install gmp || true\n\tbrew install mpfr || true\n\tbrew install libmpc || true\n\ninstall-gmp-linux:\n\t@echo 🐧 LINUX INSTALL\nifeq ($(PKG_MGR), apt-get)\n\tsudo apt-get update\n\tsudo apt-get install libgmp-dev\n\tsudo apt-get install libmpfr-dev\n\tsudo apt-get install libmpc-dev\nelse ifeq ($(PKG_MGR), pacman)\n\tsudo pacman -S gmp\nelse ifeq ($(PKG_MGR), undefined)\n\t@echo \"We could not install GMP automatically for your Linux distribution. Please, install GMP manually.\"\nendif\n\nlint:\n\t@echo 💚 LINT\n\t@echo 1.Pylint\n\tmake pylint\n\t@echo 2.Black Formatting\n\tmake blackcheck\n\t@echo 3.Mypy Static Typing\n\tmake mypy\n\t@echo 4.Package Metadata\n\tpoetry build\n\tpoetry run twine check dist/*\n\t@echo 5.Documentation\n\tpoetry run mkdocs build --strict\n\nauto-lint:\n\t@echo 💚 AUTO LINT\n\t@echo Auto-generating __init__\n\tpoetry run mkinit src/electionguard --write --black\n\tpoetry run mkinit src/electionguard_tools --write --recursive --black\n\tpoetry run mkinit src/electionguard_verify --write --black\n\tpoetry run mkinit src/electionguard_cli --write --recursive --black\n\tpoetry run mkinit src/electionguard_gui --write --recursive --black\n\t@echo Reformatting using Black\n\tmake blackformat\n\tmake lint\n\t\npylint:\n\tpoetry run pylint --extension-pkg-allow-list=dependency_injector ./src ./tests\n\nblackformat:\n\tpoetry run black .\n\nblackcheck:\n\tpoetry run black --check .\n\nmypy:\n\tpoetry run mypy src/electionguard src/electionguard_tools src/electionguard_cli src/electionguard_gui stubs\n\nvalidate: \n\t@echo ✅ VALIDATE\n\t@poetry run python3 -c 'import electionguard; print(electionguard.__package__ + \" successfully imported\")'\n\n# Test\nunit-tests:\n\t@echo ✅ UNIT TESTS\n\tpoetry run pytest tests/unit\n\nproperty-tests:\n\t@echo ✅ PROPERTY TESTS\n\tpoetry run pytest tests/property\n\nintegration-tests:\n\t@echo ✅ INTEGRATION TESTS\n\tpoetry run pytest tests/integration\n\ntest: \n\t@echo ✅ ALL TESTS\n\tmake unit-tests\n\tmake property-tests\n\tmake integration-tests\n\ntest-example:\n\t@echo ✅ TEST Example\n\tpoetry run python3 -m pytest -s tests/integration/test_end_to_end_election.py\n\ntest-integration:\n\t@echo ✅ INTEGRATION TESTS\n\tpoetry run pytest tests/integration\n\n# Coverage\ncoverage:\n\t@echo ✅ COVERAGE\n\tpoetry run coverage run -m pytest\n\tpoetry run coverage report --fail-under=$(CODE_COVERAGE)\n\ncoverage-html:\n\tpoetry run coverage html -d coverage\n\ncoverage-xml:\n\tpoetry run coverage xml\n\ncoverage-erase:\n\t@poetry run coverage erase\n\n# Benchmark\nbench:\n\t@echo 📊 BENCHMARKS\n\tpoetry run python3 -s tests/bench/bench_chaum_pedersen.py\n\n# Documentation\ninstall-mkdocs:\n\tpip install mkdocs\n\tpip install mkdocs-jupyter\n\ndocs-serve:\n\tpoetry run mkdocs serve\n\ndocs-build:\n\tpoetry run mkdocs build\n\ndocs-deploy:\n\t@echo 🚀 DEPLOY to Github Pages\n\tpoetry run mkdocs gh-deploy --force\n\ndocs-deploy-ci:\n\t@echo 🚀 DEPLOY to Github Pages\n\tpoetry run mkdocs gh-deploy --force\n\ndependency-graph:\n\tpoetry run pydeps --noshow --max-bacon 2 -o dependency-graph.svg src/electionguard\n\ndependency-graph-ci:\n\tsudo apt install graphviz\n\tpoetry run pydeps --noshow --max-bacon 2 -o dependency-graph.svg src/electionguard\n\n# Sample Data\nfetch-sample-data:\n\t@echo ⬇️ FETCH Sample Data\nifeq ($(OS), Windows)\n\tchoco install wget\n\tchoco install unzip\nendif\n\twget -O sample-data.zip https://github.com/microsoft/electionguard/releases/download/v1.0/sample-data.zip\n\tunzip -o sample-data.zip\n\ngenerate-sample-data:\n\t@echo 🔁 GENERATE Sample Data\n\tpoetry run python3 src/electionguard_tools/scripts/sample_generator.py -m \"hamilton-general\" -n $(SAMPLE_BALLOT_COUNT) -s $(SAMPLE_BALLOT_SPOIL_RATE)\n\n# Publish\npublish:\n\tpoetry publish\n\npublish-ci:\n\t@echo 🚀 PUBLISH\n\tpoetry publish --username __token__ --password $(PYPI_TOKEN)\n\npublish-test:\n\tpoetry publish --repository testpypi\n\npublish-test-ci:\n\t@echo 🚀 PUBLISH TEST\n\tpoetry publish --repository testpypi --username __token__ --password $(TEST_PYPI_TOKEN)\n\n# Release\nrelease-zip-ci:\n\t@echo 📁 ZIP RELEASE ARTIFACTS\n\tmv dist electionguard\n\tmv dependency-graph.svg electionguard\n\tzip -r electionguard.zip electionguard\n\nrelease-notes:\n\t@echo 📝 GENERATE RELEASE NOTES\n\texport MILESTONE_NUM=$(cat ${GITHUB_EVENT_PATH} | jq '.milestone.number')\n\texport MILESTONE_URL=$(cat ${GITHUB_EVENT_PATH} | jq '.milestone.url')\n\texport MILESTONE_TITLE=$(cat ${GITHUB_EVENT_PATH} | jq '.milestone.title')\n\texport MILESTONE_DESCRIPTION=$(cat ${GITHUB_EVENT_PATH} | jq '.milestone.description')\n\ttouch release_notes.md\n\techo \"# ${MILESTONE_TITLE}\" >> release_notes.md\n\techo \"${MILESTONE_DESCRIPTION}\" >> release_notes.md\n\techo -en \"\\n\" >> release_notes.md\n\techo \"## Issues\" >> release_notes.md\n\tcurl \"${GITHUB_API_URL}/${GITHUB_REPOSITORY}/issues?milestone=${MILESTONE_NUM}&state=all\" | jq '.[].title' | while read i; do echo \"[$i](${MILESTONE_URL})\" >> release_notes.md; done\n\negui:\nifeq \"${EG_DB_PASSWORD}\" \"\"\n\t@echo \"Set the EG_DB_PASSWORD environment variable\"\n\texit 1\nendif\n\tpoetry run egui\n\nstart-db:\nifeq \"${EG_DB_PASSWORD}\" \"\"\n\t@echo \"Set the EG_DB_PASSWORD environment variable\"\n\texit 1\nendif\n\tdocker compose --env-file ./.env -f src/electionguard_db/docker-compose.db.yml up -d\n\nstop-db:\n\tdocker compose --env-file ./.env -f src/electionguard_db/docker-compose.db.yml down\n\nbuild-egui:\n\tdocker build -t egui -f ./src/electionguard_gui/Dockerfile .\n\nstart-egui: build-egui\nifeq \"${EG_DB_PASSWORD}\" \"\"\n\t@echo \"Set the EG_DB_PASSWORD environment variable\"\n\texit 1\nendif\n\tdocker compose --env-file ./.env -f src/electionguard_gui/docker-compose.yml up -d\n\nstop-egui:\n\tdocker compose --env-file ./.env -f src/electionguard_gui/docker-compose.yml down\n\neg-e2e-simple-election:\n\tpoetry run eg e2e --guardian-count=2 --quorum=2 --manifest=data/election_manifest_simple.json --ballots=data/plaintext_ballots_simple.json --spoil-id=25a7111b-4334-425a-87c1-f7a49f42b3a2 --output-record=\"./election_record.zip\"\n\neg-setup-simple-election:\n\tpoetry run eg setup --guardian-count=2 --quorum=2 --manifest=data/election_manifest_simple.json  --package-dir=../data/out/public_encryption_package --keys-dir=../data/out/test_data_private_guardian_data\n"
  },
  {
    "path": "README.md",
    "content": "![Microsoft Defending Democracy Program: ElectionGuard Python][banner image]\r\n\r\n# 🗳 ElectionGuard Python\r\n\r\n[![ElectionGuard Specification 0.95.0](https://img.shields.io/badge/🗳%20ElectionGuard%20Specification-0.95.0-green)](https://www.electionguard.vote) ![Github Package Action](https://github.com/microsoft/electionguard-python/workflows/Release%20Build/badge.svg) [![](https://img.shields.io/pypi/v/electionguard)](https://pypi.org/project/electionguard/) [![](https://img.shields.io/pypi/dm/electionguard)](https://pypi.org/project/electionguard/) [![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/microsoft/electionguard-python.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/microsoft/electionguard-python/context:python) [![Total alerts](https://img.shields.io/lgtm/alerts/g/microsoft/electionguard-python.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/microsoft/electionguard-python/alerts/) [![Documentation Status](https://readthedocs.org/projects/electionguard-python/badge/?version=latest)](https://electionguard-python.readthedocs.io) [![license](https://img.shields.io/github/license/microsoft/electionguard)](https://github.com/microsoft/electionguard-python/blob/main/LICENSE)\r\n\r\nThis repository is a \"reference implementation\" of ElectionGuard written in Python 3. This implementation can be used to conduct End-to-End Verifiable Elections as well as privacy-enhanced risk-limiting audits. Components of this library can also be used to construct \"Verifiers\" to validate the results of an ElectionGuard election.\r\n\r\n## 📁 In This Repository\r\n\r\n| File/folder                                             | Description                                    |\r\n| ------------------------------------------------------- | ---------------------------------------------- |\r\n| [docs](/docs)                                           | Documentation for using the library.           |\r\n| [src/electionguard](/src/electionguard)                 | ElectionGuard library.                         |\r\n| [src/electionguard_tools](/src/electionguard_tools)     | Tools for testing and sample data.             |\r\n| [src/electionguard_verifier](/src/electionguard_verify) | Verifier to validate the validity of a ballot. |\r\n| [stubs](/stubs)                                         | Type annotations for external libraries.       |\r\n| [tests](/tests)                                         | Tests to exercise this codebase.               |\r\n| [CONTRIBUTING.md](/CONTRIBUTING.md)                     | Guidelines for contributing.                   |\r\n| [README.md](/README.md)                                 | This README file.                              |\r\n| [LICENSE](/LICENSE)                                     | The license for ElectionGuard-Python.          |\r\n| [data](/data)                                           | Sample election data.                          |\r\n\r\n## ❓ What Is ElectionGuard?\r\n\r\nElectionGuard is an open source software development kit (SDK) that makes voting more secure, transparent and accessible. The ElectionGuard SDK leverages homomorphic encryption to ensure that votes recorded by electronic systems of any type remain encrypted, secure, and secret. Meanwhile, ElectionGuard also allows verifiable and accurate tallying of ballots by any 3rd party organization without compromising secrecy or security.\r\n\r\nLearn More in the [ElectionGuard Repository](https://github.com/microsoft/electionguard)\r\n\r\n## 🦸 How Can I use ElectionGuard?\r\n\r\nElectionGuard supports a variety of use cases. The Primary use case is to generate verifiable end-to-end (E2E) encrypted elections. The ElectionGuard process can also be used for other use cases such as privacy enhanced risk-limiting audits (RLAs).\r\n\r\n## 💻 Requirements\r\n\r\n- [Python 3.9+](https://www.python.org/downloads/) is <ins>**required**</ins> to develop this SDK. If developer uses multiple versions of python, [pyenv](https://github.com/pyenv/pyenv) is suggested to assist version management.\r\n- [GNU Make](https://www.gnu.org/software/make/manual/make.html) is used to simplify the commands and GitHub Actions. This approach is recommended to simplify the command line experience. This is built in for MacOS and Linux. For Windows, setup is simpler with [Chocolatey](https://chocolatey.org/install) and installing the provided [make package](https://chocolatey.org/packages/make). The other Windows option is [manually installing make](http://gnuwin32.sourceforge.net/packages/make.htm).\r\n- [Gmpy2](https://gmpy2.readthedocs.io/en/latest/) is used for [Arbitrary-precision arithmetic](https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic) which\r\n  has its own [installation requirements (native C libraries)](https://gmpy2.readthedocs.io/en/latest/intro.html#installation) on Linux and MacOS. **⚠️ Note:** _This is not required for Windows since the gmpy2 precompiled libraries are provided._\r\n- [poetry 2.2.1](https://python-poetry.org/) is used to configure the python environment. Installation instructions can be found [here](https://python-poetry.org/docs/#installation).\r\n\r\n## 🚀 Quick Start\r\n\r\nUsing [**make**](https://www.gnu.org/software/make/manual/make.html), the entire [GitHub Action workflow][pull request workflow] can be run with one command:\r\n\r\n```\r\nmake\r\n```\r\n\r\nThe unit and integration tests can also be run with make:\r\n\r\n```\r\nmake test\r\n```\r\n\r\nA complete end-to-end election example can be run independently by executing:\r\n\r\n```\r\nmake test-example\r\n```\r\n\r\nFor more detailed build and run options, see the [documentation][build and run].\r\n\r\n## 📄 Documentation\r\n\r\nOverviews:\r\n\r\n- [GitHub Pages](https://microsoft.github.io/electionguard-python/)\r\n- [Read the Docs](https://electionguard-python.readthedocs.io/)\r\n\r\nSections:\r\n\r\n- [Design and Architecture]\r\n- [Build and Run]\r\n- [Project Workflow]\r\n- [Election Manifest]\r\n\r\nStep-by-Step Process:\r\n\r\n0. [Configure Election]\r\n1. [Key Ceremony]\r\n2. [Encrypt Ballots]\r\n3. [Cast and Spoil]\r\n4. [Decrypt Tally]\r\n5. [Publish and Verify]\r\n\r\n## Contributing\r\n\r\nThis project encourages community contributions for development, testing, documentation, code review, and performance analysis, etc. For more information on how to contribute, see [the contribution guidelines][contributing]\r\n\r\n### Code of Conduct\r\n\r\nThis project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.\r\n\r\n### Reporting Issues\r\n\r\nPlease report any bugs, feature requests, or enhancements using the [GitHub Issue Tracker](https://github.com/microsoft/electionguard-python/issues). Please do not report any security vulnerabilities using the Issue Tracker. Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). See the [Security Documentation][security] for more information.\r\n\r\n### Have Questions?\r\n\r\nElectionguard would love for you to ask questions out in the open using GitHub Issues. If you really want to email the ElectionGuard team, reach out at electionguard@microsoft.com.\r\n\r\n## License\r\n\r\nThis repository is licensed under the [MIT License]\r\n\r\n## Thanks! 🎉\r\n\r\nA huge thank you to those who helped to contribute to this project so far, including:\r\n\r\n**[Josh Benaloh _(Microsoft)_](https://www.microsoft.com/en-us/research/people/benaloh/)**\r\n\r\n<a href=\"https://www.microsoft.com/en-us/research/people/benaloh/\"><img src=\"https://www.microsoft.com/en-us/research/wp-content/uploads/2016/09/avatar_user__1473484671-180x180.jpg\" title=\"Josh Benaloh\" width=\"80\" height=\"80\"></a>\r\n\r\n**[Keith Fung](https://github.com/keithrfung) [_(InfernoRed Technology)_](https://infernored.com/)**\r\n\r\n<a href=\"https://github.com/keithrfung\"><img src=\"https://avatars2.githubusercontent.com/u/10125297?v=4\" title=\"keithrfung\" width=\"80\" height=\"80\"></a>\r\n\r\n**[Matt Wilhelm](https://github.com/AddressXception) [_(InfernoRed Technology)_](https://infernored.com/)**\r\n\r\n<a href=\"https://github.com/AddressXception\"><img src=\"https://avatars0.githubusercontent.com/u/6232853?s=460&u=8fec95386acad6109ad71a2aad2d097b607ebd6a&v=4\" title=\"AddressXception\" width=\"80\" height=\"80\"></a>\r\n\r\n**[Dan S. Wallach](https://www.cs.rice.edu/~dwallach/) [_(Rice University)_](https://www.rice.edu/)**\r\n\r\n<a href=\"https://www.cs.rice.edu/~dwallach/\"><img src=\"https://avatars2.githubusercontent.com/u/743029?v=4\" title=\"danwallach\" width=\"80\" height=\"80\"></a>\r\n\r\n<!-- Links -->\r\n\r\n[banner image]: https://raw.githubusercontent.com/microsoft/electionguard-python/main/images/electionguard-banner.svg\r\n[pull request workflow]: https://github.com/microsoft/electionguard-python/blob/main/.github/workflows/pull_request.yml\r\n[contributing]: https://github.com/microsoft/electionguard-python/blob/main/CONTRIBUTING.md\r\n[security]: https://github.com/microsoft/electionguard-python/blob/main/SECURITY.md\r\n[design and architecture]: https://github.com/microsoft/electionguard-python/blob/main/docs/Design_and_Architecture.md\r\n[build and run]: https://github.com/microsoft/electionguard-python/blob/main/docs/Build_and_Run.md\r\n[project workflow]: https://github.com/microsoft/electionguard-python/blob/main/docs/Project_Workflow.md\r\n[election manifest]: https://github.com/microsoft/electionguard-python/blob/main/docs/Election_Manifest.md\r\n[configure election]: https://github.com/microsoft/electionguard-python/blob/main/docs/0_Configure_Election.md\r\n[key ceremony]: https://github.com/microsoft/electionguard-python/blob/main/docs/1_Key_Ceremony.md\r\n[encrypt ballots]: https://github.com/microsoft/electionguard-python/blob/main/docs/2_Encrypt_Ballots.md\r\n[cast and spoil]: https://github.com/microsoft/electionguard-python/blob/main/docs/3_Cast_and_Spoil.md\r\n[decrypt tally]: https://github.com/microsoft/electionguard-python/blob/main/docs/4_Decrypt_Tally.md\r\n[publish and verify]: https://github.com/microsoft/electionguard-python/blob/main/docs/5_Publish_and_Verify.md\r\n[mit license]: https://github.com/microsoft/electionguard-python/blob/main/LICENSE\r\n"
  },
  {
    "path": "SECURITY.md",
    "content": "<!-- BEGIN MICROSOFT SECURITY.MD V0.0.3 BLOCK -->\r\n\r\n## Security\r\n\r\nMicrosoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).\r\n\r\nIf you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)) of a security vulnerability, please report it to us as described below.\r\n\r\n## Reporting Security Issues\r\n\r\n**Please do not report security vulnerabilities through public GitHub issues.**\r\n\r\nInstead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report).\r\n\r\nIf you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com).  If possible, encrypt your message with our PGP key; please download it from the the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc).\r\n\r\nYou should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).\r\n\r\nPlease include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:\r\n\r\n  * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)\r\n  * Full paths of source file(s) related to the manifestation of the issue\r\n  * The location of the affected source code (tag/branch/commit or direct URL)\r\n  * Any special configuration required to reproduce the issue\r\n  * Step-by-step instructions to reproduce the issue\r\n  * Proof-of-concept or exploit code (if possible)\r\n  * Impact of the issue, including how an attacker might exploit the issue\r\n\r\nThis information will help us triage your report more quickly.\r\n\r\nIf you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs.\r\n\r\n## Preferred Languages\r\n\r\nWe prefer all communications to be in English.\r\n\r\n## Policy\r\n\r\nMicrosoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd).\r\n\r\n<!-- END MICROSOFT SECURITY.MD BLOCK -->\r\n"
  },
  {
    "path": "data/ballot_in_simple.json",
    "content": "{\n  \"object_id\": \"some-external-id-string-123\",\n  \"style_id\": \"jefferson-county-ballot-style\",\n  \"contests\": [\n    {\n      \"object_id\": \"justice-supreme-court\",\n      \"sequence_order\": 0,\n      \"ballot_selections\": [\n        {\n          \"object_id\": \"john-adams-selection\",\n          \"sequence_order\": 0,\n          \"vote\": 1\n        },\n        {\n          \"object_id\": \"write-in-selection\",\n          \"sequence_order\": 3,\n          \"vote\": 1,\n          \"extended_data\": {\n            \"value\": \"Susan B. Anthony\",\n            \"length\": 16\n          }\n        }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "data/election_manifest_simple.json",
    "content": "{\n  \"spec_version\": \"v0.95\",\n  \"geopolitical_units\": [\n    {\n      \"object_id\": \"jefferson-county\",\n      \"name\": \"Jefferson County\",\n      \"type\": \"county\",\n      \"contact_information\": {\n        \"address_line\": [\"1234 Samuel Adams Way\", \"Jefferson, Hamilton 999999\"],\n        \"name\": \"Jefferson County Clerk\",\n        \"email\": [\n          {\n            \"annotation\": \"inquiries\",\n            \"value\": \"inquiries@jefferson.hamilton.state.gov\"\n          }\n        ],\n        \"phone\": [\n          {\n            \"annotation\": \"domestic\",\n            \"value\": \"123-456-7890\"\n          }\n        ]\n      }\n    },\n    {\n      \"object_id\": \"harrison-township\",\n      \"name\": \"Harrison Township\",\n      \"type\": \"township\",\n      \"contact_information\": {\n        \"address_line\": [\"1234 Thorton Drive\", \"Harrison, Hamilton 999999\"],\n        \"name\": \"Harrison Town Hall\",\n        \"email\": [\n          {\n            \"annotation\": \"inquiries\",\n            \"value\": \"inquiries@harrison.hamilton.state.gov\"\n          }\n        ],\n        \"phone\": [\n          {\n            \"annotation\": \"domestic\",\n            \"value\": \"123-456-7890\"\n          }\n        ]\n      }\n    },\n    {\n      \"object_id\": \"harrison-township-precinct-east\",\n      \"name\": \"Harrison Township Precinct\",\n      \"type\": \"township\",\n      \"contact_information\": {\n        \"address_line\": [\"1234 Thorton Drive\", \"Harrison, Hamilton 999999\"],\n        \"name\": \"Harrison Town Hall\",\n        \"email\": [\n          {\n            \"annotation\": \"inquiries\",\n            \"value\": \"inquiries@harrison.hamilton.state.gov\"\n          }\n        ],\n        \"phone\": [\n          {\n            \"annotation\": \"domestic\",\n            \"value\": \"123-456-7890\"\n          }\n        ]\n      }\n    },\n    {\n      \"object_id\": \"rutledge-elementary\",\n      \"name\": \"Rutledge Elementary School district\",\n      \"type\": \"school\",\n      \"contact_information\": {\n        \"address_line\": [\"1234 Wolcott Parkway\", \"Harrison, Hamilton 999999\"],\n        \"name\": \"Rutledge Elementary School\",\n        \"email\": [\n          {\n            \"annotation\": \"inquiries\",\n            \"value\": \"inquiries@harrison.hamilton.state.gov\"\n          }\n        ],\n        \"phone\": [\n          {\n            \"annotation\": \"domestic\",\n            \"value\": \"123-456-7890\"\n          }\n        ]\n      }\n    }\n  ],\n  \"parties\": [\n    {\n      \"object_id\": \"whig\",\n      \"abbreviation\": \"WHI\",\n      \"color\": \"AAAAAA\",\n      \"logo_uri\": \"http://some/path/to/whig.svg\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Whig Party\",\n            \"language\": \"en\"\n          }\n        ]\n      }\n    },\n    {\n      \"object_id\": \"federalist\",\n      \"abbreviation\": \"FED\",\n      \"color\": \"CCCCCC\",\n      \"logo_uri\": \"http://some/path/to/federalist.svg\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Federalist Party\",\n            \"language\": \"en\"\n          }\n        ]\n      }\n    },\n    {\n      \"object_id\": \"democratic-republican\",\n      \"abbreviation\": \"DEMREP\",\n      \"color\": \"EEEEEE\",\n      \"logo_uri\": \"http://some/path/to/democratic-repulbican.svg\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Democratic Republican Party\",\n            \"language\": \"en\"\n          }\n        ]\n      }\n    }\n  ],\n  \"candidates\": [\n    {\n      \"object_id\": \"benjamin-franklin\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Benjamin Franklin\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"whig\"\n    },\n    {\n      \"object_id\": \"john-adams\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"John Adams\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"federalist\"\n    },\n    {\n      \"object_id\": \"john-hancock\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"John Hancock\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"democratic-republican\"\n    },\n    {\n      \"object_id\": \"write-in\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Write In Candidate\",\n            \"language\": \"en\"\n          },\n          {\n            \"value\": \"Escribir en la candidata\",\n            \"language\": \"es\"\n          }\n        ]\n      },\n      \"is_write_in\": true\n    },\n    {\n      \"object_id\": \"referendum-pineapple-affirmative\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Pineapple should be banned on pizza\",\n            \"language\": \"en\"\n          }\n        ]\n      }\n    },\n    {\n      \"object_id\": \"referendum-pineapple-negative\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Pineapple should not be banned on pizza\",\n            \"language\": \"en\"\n          }\n        ]\n      }\n    }\n  ],\n  \"contests\": [\n    {\n      \"object_id\": \"justice-supreme-court\",\n      \"sequence_order\": 1,\n      \"ballot_selections\": [\n        {\n          \"object_id\": \"john-adams-selection\",\n          \"sequence_order\": 1,\n          \"candidate_id\": \"john-adams\"\n        },\n        {\n          \"object_id\": \"benjamin-franklin-selection\",\n          \"sequence_order\": 2,\n          \"candidate_id\": \"benjamin-franklin\"\n        },\n        {\n          \"object_id\": \"john-hancock-selection\",\n          \"sequence_order\": 3,\n          \"candidate_id\": \"john-hancock\"\n        },\n        {\n          \"object_id\": \"write-in-selection\",\n          \"sequence_order\": 4,\n          \"candidate_id\": \"write-in\"\n        }\n      ],\n      \"ballot_title\": {\n        \"text\": [\n          {\n            \"value\": \"Justice of the Supreme Court\",\n            \"language\": \"en\"\n          },\n          {\n            \"value\": \"Juez de la corte suprema\",\n            \"language\": \"es\"\n          }\n        ]\n      },\n      \"ballot_subtitle\": {\n        \"text\": [\n          {\n            \"value\": \"Please choose up to two candidates\",\n            \"language\": \"en\"\n          },\n          {\n            \"value\": \"Uno\",\n            \"language\": \"es\"\n          }\n        ]\n      },\n      \"vote_variation\": \"n_of_m\",\n      \"electoral_district_id\": \"jefferson-county\",\n      \"name\": \"Justice of the Supreme Court\",\n      \"number_elected\": 2,\n      \"votes_allowed\": 2\n    },\n    {\n      \"object_id\": \"referendum-pineapple\",\n      \"sequence_order\": 2,\n      \"ballot_selections\": [\n        {\n          \"object_id\": \"referendum-pineapple-affirmative-selection\",\n          \"sequence_order\": 1,\n          \"candidate_id\": \"referendum-pineapple-affirmative\"\n        },\n        {\n          \"object_id\": \"referendum-pineapple-negative-selection\",\n          \"sequence_order\": 2,\n          \"candidate_id\": \"referendum-pineapple-negative\"\n        }\n      ],\n      \"ballot_title\": {\n        \"text\": [\n          {\n            \"value\": \"Should pineapple be banned on pizza?\",\n            \"language\": \"en\"\n          },\n          {\n            \"value\": \"¿Debería prohibirse la piña en la pizza?\",\n            \"language\": \"es\"\n          }\n        ]\n      },\n      \"ballot_subtitle\": {\n        \"text\": [\n          {\n            \"value\": \"The township considers this issue to be very important\",\n            \"language\": \"en\"\n          },\n          {\n            \"value\": \"El municipio considera que esta cuestión es muy importante\",\n            \"language\": \"es\"\n          }\n        ]\n      },\n      \"vote_variation\": \"one_of_m\",\n      \"electoral_district_id\": \"harrison-township\",\n      \"name\": \"The Pineapple Question\",\n      \"number_elected\": 1,\n      \"votes_allowed\": 1\n    }\n  ],\n  \"ballot_styles\": [\n    {\n      \"object_id\": \"jefferson-county-ballot-style\",\n      \"geopolitical_unit_ids\": [\"jefferson-county\"]\n    },\n    {\n      \"object_id\": \"harrison-township-ballot-style\",\n      \"geopolitical_unit_ids\": [\"jefferson-county\", \"harrison-township\"]\n    },\n    {\n      \"object_id\": \"harrison-township-precinct-east-ballot-style\",\n      \"geopolitical_unit_ids\": [\n        \"jefferson-county\",\n        \"harrison-township\",\n        \"harrison-township-precinct-east\",\n        \"rutledge-elementary\"\n      ]\n    },\n    {\n      \"object_id\": \"rutledge-elementary-ballot-style\",\n      \"geopolitical_unit_ids\": [\n        \"jefferson-county\",\n        \"harrison-township\",\n        \"rutledge-elementary\"\n      ]\n    }\n  ],\n  \"name\": {\n    \"text\": [\n      {\n        \"value\": \"Jefferson County Spring Primary\",\n        \"language\": \"en\"\n      },\n      {\n        \"value\": \"Primaria de primavera del condado de Jefferson\",\n        \"language\": \"es\"\n      }\n    ]\n  },\n  \"contact_information\": {\n    \"address_line\": [\"1234 Paul Revere Run\", \"Jefferson, Hamilton 999999\"],\n    \"name\": \"Hamilton State Election Commission\",\n    \"email\": [\n      {\n        \"annotation\": \"press\",\n        \"value\": \"inquiries@hamilton.state.gov\"\n      },\n      {\n        \"annotation\": \"federal\",\n        \"value\": \"commissioner@hamilton.state.gov\"\n      }\n    ],\n    \"phone\": [\n      {\n        \"annotation\": \"domestic\",\n        \"value\": \"123-456-7890\"\n      },\n      {\n        \"annotation\": \"international\",\n        \"value\": \"+1-123-456-7890\"\n      }\n    ]\n  },\n  \"start_date\": \"2020-03-01T08:00:00-05:00\",\n  \"end_date\": \"2020-03-01T20:00:00-05:00\",\n  \"election_scope_id\": \"jefferson-county-primary\",\n  \"type\": \"primary\"\n}\n"
  },
  {
    "path": "data/manifest-full.json",
    "content": "{\n  \"election_scope_id\": \"jefferson-county-primary\",\n  \"spec_version\": \"1.0\",\n  \"type\": \"primary\",\n  \"start_date\": \"2020-03-01T08:00:00-05:00\",\n  \"end_date\": \"2020-03-01T20:00:00-05:00\",\n  \"geopolitical_units\": [\n    {\n      \"object_id\": \"jefferson-county\",\n      \"name\": \"Jefferson County\",\n      \"type\": \"county\",\n      \"contact_information\": {\n        \"address_line\": [\n          \"1234 Samuel Adams Way\",\n          \"Jefferson, Hamilton 999999\"\n        ],\n        \"email\": [\n          {\n            \"annotation\": \"inquiries\",\n            \"value\": \"inquiries@jefferson.hamilton.state.gov\"\n          }\n        ],\n        \"phone\": [\n          {\n            \"annotation\": \"domestic\",\n            \"value\": \"123-456-7890\"\n          }\n        ],\n        \"name\": \"Jefferson County Clerk\"\n      }\n    },\n    {\n      \"object_id\": \"harrison-township\",\n      \"name\": \"Harrison Township\",\n      \"type\": \"township\",\n      \"contact_information\": {\n        \"address_line\": [\n          \"1234 Thorton Drive\",\n          \"Harrison, Hamilton 999999\"\n        ],\n        \"email\": [\n          {\n            \"annotation\": \"inquiries\",\n            \"value\": \"inquiries@harrison.hamilton.state.gov\"\n          }\n        ],\n        \"phone\": [\n          {\n            \"annotation\": \"domestic\",\n            \"value\": \"123-456-7890\"\n          }\n        ],\n        \"name\": \"Harrison Town Hall\"\n      }\n    },\n    {\n      \"object_id\": \"harrison-township-precinct-east\",\n      \"name\": \"Harrison Township Precinct\",\n      \"type\": \"township\",\n      \"contact_information\": {\n        \"address_line\": [\n          \"1234 Thorton Drive\",\n          \"Harrison, Hamilton 999999\"\n        ],\n        \"email\": [\n          {\n            \"annotation\": \"inquiries\",\n            \"value\": \"inquiries@harrison.hamilton.state.gov\"\n          }\n        ],\n        \"phone\": [\n          {\n            \"annotation\": \"domestic\",\n            \"value\": \"123-456-7890\"\n          }\n        ],\n        \"name\": \"Harrison Town Hall\"\n      }\n    },\n    {\n      \"object_id\": \"rutledge-elementary\",\n      \"name\": \"Rutledge Elementary School district\",\n      \"type\": \"school\",\n      \"contact_information\": {\n        \"address_line\": [\n          \"1234 Wolcott Parkway\",\n          \"Harrison, Hamilton 999999\"\n        ],\n        \"email\": [\n          {\n            \"annotation\": \"inquiries\",\n            \"value\": \"inquiries@harrison.hamilton.state.gov\"\n          }\n        ],\n        \"phone\": [\n          {\n            \"annotation\": \"domestic\",\n            \"value\": \"123-456-7890\"\n          }\n        ],\n        \"name\": \"Rutledge Elementary School\"\n      }\n    }\n  ],\n  \"parties\": [\n    {\n      \"object_id\": \"whig\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Whig Party\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"abbreviation\": \"WHI\",\n      \"color\": \"AAAAAA\",\n      \"logo_uri\": \"http://some/path/to/whig.svg\"\n    },\n    {\n      \"object_id\": \"federalist\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Federalist Party\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"abbreviation\": \"FED\",\n      \"color\": \"CCCCCC\",\n      \"logo_uri\": \"http://some/path/to/federalist.svg\"\n    },\n    {\n      \"object_id\": \"democratic-republican\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Democratic Republican Party\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"abbreviation\": \"DEMREP\",\n      \"color\": \"EEEEEE\",\n      \"logo_uri\": \"http://some/path/to/democratic-repulbican.svg\"\n    }\n  ],\n  \"candidates\": [\n    {\n      \"object_id\": \"benjamin-franklin\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Benjamin Franklin\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"whig\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"john-adams\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"John Adams\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"federalist\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"john-hancock\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"John Hancock\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"democratic-republican\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"write-in\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Write In Candidate\",\n            \"language\": \"en\"\n          },\n          {\n            \"value\": \"Escribir en la candidata\",\n            \"language\": \"es\"\n          }\n        ]\n      },\n      \"party_id\": null,\n      \"image_uri\": null,\n      \"is_write_in\": true\n    },\n    {\n      \"object_id\": \"referendum-pineapple-affirmative\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Pineapple should be banned on pizza\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": null,\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"referendum-pineapple-negative\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Pineapple should not be banned on pizza\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": null,\n      \"image_uri\": null,\n      \"is_write_in\": null\n    }\n  ],\n  \"contests\": [\n    {\n      \"object_id\": \"justice-supreme-court\",\n      \"sequence_order\": 0,\n      \"electoral_district_id\": \"jefferson-county\",\n      \"vote_variation\": \"n_of_m\",\n      \"number_elected\": 2,\n      \"votes_allowed\": 2,\n      \"name\": \"Justice of the Supreme Court\",\n      \"ballot_selections\": [\n        {\n          \"object_id\": \"john-adams-selection\",\n          \"sequence_order\": 0,\n          \"candidate_id\": \"john-adams\"\n        },\n        {\n          \"object_id\": \"benjamin-franklin-selection\",\n          \"sequence_order\": 1,\n          \"candidate_id\": \"benjamin-franklin\"\n        },\n        {\n          \"object_id\": \"john-hancock-selection\",\n          \"sequence_order\": 2,\n          \"candidate_id\": \"john-hancock\"\n        },\n        {\n          \"object_id\": \"write-in-selection\",\n          \"sequence_order\": 3,\n          \"candidate_id\": \"write-in\"\n        }\n      ],\n      \"ballot_title\": {\n        \"text\": [\n          {\n            \"value\": \"Justice of the Supreme Court\",\n            \"language\": \"en\"\n          },\n          {\n            \"value\": \"Juez de la corte suprema\",\n            \"language\": \"es\"\n          }\n        ]\n      },\n      \"ballot_subtitle\": {\n        \"text\": [\n          {\n            \"value\": \"Please choose up to two candidates\",\n            \"language\": \"en\"\n          },\n          {\n            \"value\": \"Uno\",\n            \"language\": \"es\"\n          }\n        ]\n      }\n    },\n    {\n      \"object_id\": \"referendum-pineapple\",\n      \"sequence_order\": 1,\n      \"electoral_district_id\": \"harrison-township\",\n      \"vote_variation\": \"one_of_m\",\n      \"number_elected\": 1,\n      \"votes_allowed\": 1,\n      \"name\": \"The Pineapple Question\",\n      \"ballot_selections\": [\n        {\n          \"object_id\": \"referendum-pineapple-affirmative-selection\",\n          \"sequence_order\": 0,\n          \"candidate_id\": \"referendum-pineapple-affirmative\"\n        },\n        {\n          \"object_id\": \"referendum-pineapple-negative-selection\",\n          \"sequence_order\": 1,\n          \"candidate_id\": \"referendum-pineapple-negative\"\n        }\n      ],\n      \"ballot_title\": {\n        \"text\": [\n          {\n            \"value\": \"Should pineapple be banned on pizza?\",\n            \"language\": \"en\"\n          },\n          {\n            \"value\": \"\\u00bfDeber\\u00eda prohibirse la pi\\u00f1a en la pizza?\",\n            \"language\": \"es\"\n          }\n        ]\n      },\n      \"ballot_subtitle\": {\n        \"text\": [\n          {\n            \"value\": \"The township considers this issue to be very important\",\n            \"language\": \"en\"\n          },\n          {\n            \"value\": \"El municipio considera que esta cuesti\\u00f3n es muy importante\",\n            \"language\": \"es\"\n          }\n        ]\n      }\n    }\n  ],\n  \"ballot_styles\": [\n    {\n      \"object_id\": \"jefferson-county-ballot-style\",\n      \"geopolitical_unit_ids\": [\n        \"jefferson-county\"\n      ],\n      \"party_ids\": null,\n      \"image_uri\": null\n    },\n    {\n      \"object_id\": \"harrison-township-ballot-style\",\n      \"geopolitical_unit_ids\": [\n        \"jefferson-county\",\n        \"harrison-township\"\n      ],\n      \"party_ids\": null,\n      \"image_uri\": null\n    },\n    {\n      \"object_id\": \"harrison-township-precinct-east-ballot-style\",\n      \"geopolitical_unit_ids\": [\n        \"jefferson-county\",\n        \"harrison-township\",\n        \"harrison-township-precinct-east\",\n        \"rutledge-elementary\"\n      ],\n      \"party_ids\": null,\n      \"image_uri\": null\n    },\n    {\n      \"object_id\": \"rutledge-elementary-ballot-style\",\n      \"geopolitical_unit_ids\": [\n        \"jefferson-county\",\n        \"harrison-township\",\n        \"rutledge-elementary\"\n      ],\n      \"party_ids\": null,\n      \"image_uri\": null\n    }\n  ],\n  \"name\": {\n    \"text\": [\n      {\n        \"value\": \"Jefferson County Spring Primary\",\n        \"language\": \"en\"\n      },\n      {\n        \"value\": \"Primaria de primavera del condado de Jefferson\",\n        \"language\": \"es\"\n      }\n    ]\n  },\n  \"contact_information\": {\n    \"address_line\": [\n      \"1234 Paul Revere Run\",\n      \"Jefferson, Hamilton 999999\"\n    ],\n    \"email\": [\n      {\n        \"annotation\": \"press\",\n        \"value\": \"inquiries@hamilton.state.gov\"\n      },\n      {\n        \"annotation\": \"federal\",\n        \"value\": \"commissioner@hamilton.state.gov\"\n      }\n    ],\n    \"phone\": [\n      {\n        \"annotation\": \"domestic\",\n        \"value\": \"123-456-7890\"\n      },\n      {\n        \"annotation\": \"international\",\n        \"value\": \"+1-123-456-7890\"\n      }\n    ],\n    \"name\": \"Hamilton State Election Commission\"\n  }\n}"
  },
  {
    "path": "data/manifest-hamilton-general.json",
    "content": "{\n  \"election_scope_id\": \"hamilton-county-general-election\",\n  \"spec_version\": \"1.0\",\n  \"type\": \"general\",\n  \"start_date\": \"2020-03-01T08:00:00-05:00\",\n  \"end_date\": \"2020-03-01T20:00:00-05:00\",\n  \"geopolitical_units\": [\n    {\n      \"object_id\": \"hamilton-county\",\n      \"name\": \"Hamilton County\",\n      \"type\": \"county\",\n      \"contact_information\": {\n        \"address_line\": [\n          \"1234 Samuel Adams Way\",\n          \"Hamilton, Ozark 99999\"\n        ],\n        \"email\": [\n          {\n            \"annotation\": \"inquiries\",\n            \"value\": \"inquiries@hamiltoncounty.gov\"\n          }\n        ],\n        \"phone\": [\n          {\n            \"annotation\": \"domestic\",\n            \"value\": \"123-456-7890\"\n          }\n        ],\n        \"name\": \"Hamilton County Clerk\"\n      }\n    },\n    {\n      \"object_id\": \"congress-district-5\",\n      \"name\": \"Congressional District 5\",\n      \"type\": \"congressional\",\n      \"contact_information\": {\n        \"address_line\": [\n          \"1234 Somerville Gateway\",\n          \"Medford, Ozark 999999\"\n        ],\n        \"email\": [\n          {\n            \"annotation\": \"inquiries\",\n            \"value\": \"inquiries@congressional-district-5.gov\"\n          }\n        ],\n        \"phone\": [\n          {\n            \"annotation\": \"domestic\",\n            \"value\": \"123-456-7890\"\n          }\n        ],\n        \"name\": \"Medford Town Hall\"\n      }\n    },\n    {\n      \"object_id\": \"congress-district-7\",\n      \"name\": \"Congressional District 7\",\n      \"type\": \"congressional\",\n      \"contact_information\": {\n        \"address_line\": [\n          \"1234 Somerville Gateway\",\n          \"Arlington, Ozark 999999\"\n        ],\n        \"email\": [\n          {\n            \"annotation\": \"inquiries\",\n            \"value\": \"inquiries@congressional-district-7.gov\"\n          }\n        ],\n        \"phone\": [\n          {\n            \"annotation\": \"domestic\",\n            \"value\": \"123-456-7890\"\n          }\n        ],\n        \"name\": \"Arlington Town Hall\"\n      }\n    },\n    {\n      \"object_id\": \"lacroix-township-precinct-1\",\n      \"name\": \"LaCroix Township Precinct 1\",\n      \"type\": \"precinct\",\n      \"contact_information\": {\n        \"address_line\": [\n          \"1234 Thorton Drive\",\n          \"LaCroix, Ozark 99999\"\n        ],\n        \"email\": [\n          {\n            \"annotation\": \"inquiries\",\n            \"value\": \"inquiries@lacrox.gov\"\n          }\n        ],\n        \"phone\": [\n          {\n            \"annotation\": \"domestic\",\n            \"value\": \"123-456-7890\"\n          }\n        ],\n        \"name\": \"LaCroix Town Hall\"\n      }\n    },\n    {\n      \"object_id\": \"lacroix-exeter-utility-district\",\n      \"name\": \"Exeter Utility District\",\n      \"type\": \"utility\",\n      \"contact_information\": {\n        \"address_line\": [\n          \"1234 Watt Drive\",\n          \"LaCroix, Ozark 99999\"\n        ],\n        \"email\": [\n          {\n            \"annotation\": \"inquiries\",\n            \"value\": \"inquiries@exeter-utility.com\"\n          }\n        ],\n        \"phone\": [\n          {\n            \"annotation\": \"domestic\",\n            \"value\": \"123-456-7890\"\n          }\n        ],\n        \"name\": \"Exeter Utility District coordinator\"\n      }\n    },\n    {\n      \"object_id\": \"arlington-township-precinct-1\",\n      \"name\": \"Arlington Township Precinct 1\",\n      \"type\": \"precinct\",\n      \"contact_information\": {\n        \"address_line\": [\n          \"1234 Pahk Avenue\",\n          \"Arlinton, Ozark 99999\"\n        ],\n        \"email\": [\n          {\n            \"annotation\": \"inquiries\",\n            \"value\": \"inquiries@arlington-township.gov\"\n          }\n        ],\n        \"phone\": [\n          {\n            \"annotation\": \"domestic\",\n            \"value\": \"123-456-7890\"\n          }\n        ],\n        \"name\": \"Arlington Town Hall\"\n      }\n    },\n    {\n      \"object_id\": \"pismo-beach-school-district-precinct-1\",\n      \"name\": \"Pismo Beach School District Precinct 1\",\n      \"type\": \"school\",\n      \"contact_information\": {\n        \"address_line\": [\n          \"1234 Pismo Beach Elementary\",\n          \"Arlington, Ozark 99999\"\n        ],\n        \"email\": [\n          {\n            \"annotation\": \"inquiries\",\n            \"value\": \"inquiries@pismo-beach-school.edu\"\n          }\n        ],\n        \"phone\": [\n          {\n            \"annotation\": \"domestic\",\n            \"value\": \"123-456-7890\"\n          }\n        ],\n        \"name\": \"Pismo Beah Elementary\"\n      }\n    },\n    {\n      \"object_id\": \"somerset-school-district-precinct-1\",\n      \"name\": \"Somerset School District\",\n      \"type\": \"school\",\n      \"contact_information\": {\n        \"address_line\": [\n          \"1234 Somerset Avenue\",\n          \"Arlinton, Ozark 99999\"\n        ],\n        \"email\": [\n          {\n            \"annotation\": \"inquiries\",\n            \"value\": \"inquiries@somerset-elementary.edu\"\n          }\n        ],\n        \"phone\": [\n          {\n            \"annotation\": \"domestic\",\n            \"value\": \"123-456-7890\"\n          }\n        ],\n        \"name\": \"Someset Elementary\"\n      }\n    },\n    {\n      \"object_id\": \"harris-township\",\n      \"name\": \"Harris Township\",\n      \"type\": \"township\",\n      \"contact_information\": {\n        \"address_line\": [\n          \"1234 Pahk Avenue\",\n          \"Harris, Ozark 99999\"\n        ],\n        \"email\": [\n          {\n            \"annotation\": \"inquiries\",\n            \"value\": \"inquiries@harris-township.gov\"\n          }\n        ],\n        \"phone\": [\n          {\n            \"annotation\": \"domestic\",\n            \"value\": \"123-456-7890\"\n          }\n        ],\n        \"name\": \"harris Town Hall\"\n      }\n    }\n  ],\n  \"parties\": [\n    {\n      \"object_id\": \"whig\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Whig Party\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"abbreviation\": \"WHI\",\n      \"color\": \"AAAAAA\",\n      \"logo_uri\": \"http://some/path/to/whig.svg\"\n    },\n    {\n      \"object_id\": \"federalist\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Federalist Party\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"abbreviation\": \"FED\",\n      \"color\": \"BBBBBB\",\n      \"logo_uri\": \"http://some/path/to/federalist.svg\"\n    },\n    {\n      \"object_id\": \"peoples\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"People's Party\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"abbreviation\": \"PPL\",\n      \"color\": \"CCCCCC\",\n      \"logo_uri\": \"http://some/path/to/people-s.svg\"\n    },\n    {\n      \"object_id\": \"liberty\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Liberty Party\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"abbreviation\": \"LIB\",\n      \"color\": \"DDDDDD\",\n      \"logo_uri\": \"http://some/path/to/liberty.svg\"\n    },\n    {\n      \"object_id\": \"constitution\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Constitution Party\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"abbreviation\": \"CONST\",\n      \"color\": \"EEEEEE\",\n      \"logo_uri\": \"http://some/path/to/democratic-repulbican.svg\"\n    },\n    {\n      \"object_id\": \"labor\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Labor Party\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"abbreviation\": \"LBR\",\n      \"color\": \"FFFFFF\",\n      \"logo_uri\": \"http://some/path/to/laobr.svg\"\n    },\n    {\n      \"object_id\": \"independent\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Independent\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"abbreviation\": \"IND\",\n      \"color\": \"000000\",\n      \"logo_uri\": \"http://some/path/to/independent.svg\"\n    }\n  ],\n  \"candidates\": [\n    {\n      \"object_id\": \"barchi-hallaren\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Joseph Barchi and Joseph Hallaren\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"whig\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"cramer-vuocolo\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Adam Cramer and Greg Vuocolo\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"federalist\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"court-blumhardt\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Daniel Court and Amy Blumhardt\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"peoples\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"boone-lian\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Alvin Boone and James Lian\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"liberty\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"hildebrand-garritty\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Ashley Hildebrand-McDougall and James Garritty\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"constitution\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"patterson-lariviere\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Martin Patterson and Clay Lariviere\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"labor\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"franz\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Charlene Franz\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"federalist\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"harris\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Gerald Harris\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"peoples\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"bargmann\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Linda Bargmann\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"constitution\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"abcock\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Barbara Abcock\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"liberty\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"steel-loy\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Carrie Steel-Loy\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"labor\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"sharp\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Frederick Sharp\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"constitution\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"wallace\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Alex Wallace\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"independent\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"williams\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Barbara Williams\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"peoples\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"sharp-althea\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Althea Sharp\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"whig\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"alpern\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Douglas Alpern\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"federalist\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"windbeck\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Ann Windbeck\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"peoples\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"greher\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Mike Greher\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"constitution\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"alexander\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Patricia Alexander\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"whig\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"mitchell\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Kenneth Mitchell\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"federalist\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"lee\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Stan Lee\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"independent\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"ash\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Henry Ash\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"liberty\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"kennedy\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Karen Kennedy\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"independent\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"jackson\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Van Jackson\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"labor\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"brown\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Debbie Brown\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"peoples\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"teller\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Joseph Teller\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"peoples\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"ward\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Greg Ward\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"independent\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"murphy\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Lou Murphy\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"federalist\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"newman\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Jane Newman\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"whig\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"callanann\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Jack Callanann\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"labor\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"york\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Esther York\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"labor\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"chandler\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Glenn Chandler\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"labor\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"solis\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Andrea Solis\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"labor\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"keller\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Amos Keller\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"constitution\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"rangel\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Davitra Rangel\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"peoples\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"argent\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Camille Argent\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"liberty\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"witherspoon-smithson\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Chloe Witherspoon-Smithson\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"independent\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"bainbridge\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Clayton Bainbridge\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"peoples\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"hennessey\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Charlene Hennessey\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"whig\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"savoy\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Eric Savoy\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"labor\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"tawa\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Susan Tawa\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"constitution\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"tawa-mary\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Mary Tawa\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"independent\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"altman\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Valarie Altman\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": \"peoples\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"moore\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Helen Moore\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": null,\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"white\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"John White\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": null,\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"smallberries\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"John Smallberries\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": null,\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"warfin\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"John Warfin\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": null,\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"norberg\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Chris Norberg\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": null,\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"parks\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Abigail Parks\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": null,\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"savannah\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Harmony Savannah\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": null,\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"summers\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Buffy Summers\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": null,\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"chase\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Cordelia Chase\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": null,\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"osborne\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Daniel Osborne\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": null,\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"rosenberg\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Willow Rosenberg\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": null,\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"head\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Anthony Stewart Head\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": null,\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"marsters\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"James Marsters\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": null,\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"write-in-1\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Write In Candidate\",\n            \"language\": \"en\"\n          },\n          {\n            \"value\": \"Escribir en la candidata\",\n            \"language\": \"es\"\n          }\n        ]\n      },\n      \"party_id\": null,\n      \"image_uri\": null,\n      \"is_write_in\": true\n    },\n    {\n      \"object_id\": \"write-in-2\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Write In Candidate\",\n            \"language\": \"en\"\n          },\n          {\n            \"value\": \"Escribir en la candidata\",\n            \"language\": \"es\"\n          }\n        ]\n      },\n      \"party_id\": null,\n      \"image_uri\": null,\n      \"is_write_in\": true\n    },\n    {\n      \"object_id\": \"write-in-3\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Write In Candidate\",\n            \"language\": \"en\"\n          },\n          {\n            \"value\": \"Escribir en la candidata\",\n            \"language\": \"es\"\n          }\n        ]\n      },\n      \"party_id\": null,\n      \"image_uri\": null,\n      \"is_write_in\": true\n    },\n    {\n      \"object_id\": \"ozark-chief-justice-retain-demergue-affirmative\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Retain\",\n            \"language\": \"en\"\n          },\n          {\n            \"value\": \"Conservar\",\n            \"language\": \"es\"\n          }\n        ]\n      },\n      \"party_id\": null,\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"ozark-chief-justice-retain-demergue-negative\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Reject\",\n            \"language\": \"en\"\n          },\n          {\n            \"value\": \"Rechazar\",\n            \"language\": \"es\"\n          }\n        ]\n      },\n      \"party_id\": null,\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"exeter-utility-district-referendum-affirmative\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"Yes\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": null,\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"exeter-utility-district-referendum-negative\",\n      \"name\": {\n        \"text\": [\n          {\n            \"value\": \"No\",\n            \"language\": \"en\"\n          }\n        ]\n      },\n      \"party_id\": null,\n      \"image_uri\": null,\n      \"is_write_in\": null\n    }\n  ],\n  \"contests\": [\n    {\n      \"object_id\": \"president-vice-president-contest\",\n      \"sequence_order\": 0,\n      \"electoral_district_id\": \"hamilton-county\",\n      \"vote_variation\": \"one_of_m\",\n      \"number_elected\": 1,\n      \"votes_allowed\": 1,\n      \"name\": \"President and Vice President of the United States\",\n      \"ballot_selections\": [\n        {\n          \"object_id\": \"barchi-hallaren-selection\",\n          \"sequence_order\": 0,\n          \"candidate_id\": \"barchi-hallaren\"\n        },\n        {\n          \"object_id\": \"cramer-vuocolo-selection\",\n          \"sequence_order\": 1,\n          \"candidate_id\": \"cramer-vuocolo\"\n        },\n        {\n          \"object_id\": \"court-blumhardt-selection\",\n          \"sequence_order\": 2,\n          \"candidate_id\": \"court-blumhardt\"\n        },\n        {\n          \"object_id\": \"boone-lian-selection\",\n          \"sequence_order\": 3,\n          \"candidate_id\": \"boone-lian\"\n        },\n        {\n          \"object_id\": \"hildebrand-garritty-selection\",\n          \"sequence_order\": 4,\n          \"candidate_id\": \"hildebrand-garritty\"\n        },\n        {\n          \"object_id\": \"patterson-lariviere-selection\",\n          \"sequence_order\": 5,\n          \"candidate_id\": \"patterson-lariviere\"\n        },\n        {\n          \"object_id\": \"write-in-selection-president\",\n          \"sequence_order\": 6,\n          \"candidate_id\": \"write-in\"\n        }\n      ],\n      \"ballot_title\": {\n        \"text\": [\n          {\n            \"value\": \"President and Vice President of the United States\",\n            \"language\": \"en\"\n          },\n          {\n            \"value\": \"Presidente y Vicepresidente de los Estados Unidos\",\n            \"language\": \"es\"\n          }\n        ]\n      },\n      \"ballot_subtitle\": {\n        \"text\": [\n          {\n            \"value\": \"Vote for one\",\n            \"language\": \"en\"\n          },\n          {\n            \"value\": \"Votar por uno\",\n            \"language\": \"es\"\n          }\n        ]\n      }\n    },\n    {\n      \"object_id\": \"ozark-governor\",\n      \"sequence_order\": 1,\n      \"electoral_district_id\": \"hamilton-county\",\n      \"vote_variation\": \"one_of_m\",\n      \"number_elected\": 1,\n      \"votes_allowed\": 1,\n      \"name\": \"Governor of the Commonwealth of Ozark\",\n      \"ballot_selections\": [\n        {\n          \"object_id\": \"franz-selection\",\n          \"sequence_order\": 0,\n          \"candidate_id\": \"franz\"\n        },\n        {\n          \"object_id\": \"harris-selection\",\n          \"sequence_order\": 1,\n          \"candidate_id\": \"harris\"\n        },\n        {\n          \"object_id\": \"bargmann-selection\",\n          \"sequence_order\": 2,\n          \"candidate_id\": \"bargmann\"\n        },\n        {\n          \"object_id\": \"abcock-selection\",\n          \"sequence_order\": 3,\n          \"candidate_id\": \"abcock\"\n        },\n        {\n          \"object_id\": \"steel-loy-selection\",\n          \"sequence_order\": 4,\n          \"candidate_id\": \"steel-loy\"\n        },\n        {\n          \"object_id\": \"sharp-selection\",\n          \"sequence_order\": 5,\n          \"candidate_id\": \"sharp\"\n        },\n        {\n          \"object_id\": \"walace-selection\",\n          \"sequence_order\": 6,\n          \"candidate_id\": \"wallace\"\n        },\n        {\n          \"object_id\": \"williams-selection\",\n          \"sequence_order\": 7,\n          \"candidate_id\": \"williams\"\n        },\n        {\n          \"object_id\": \"alpern-selection\",\n          \"sequence_order\": 9,\n          \"candidate_id\": \"alpern\"\n        },\n        {\n          \"object_id\": \"windbeck-selection\",\n          \"sequence_order\": 10,\n          \"candidate_id\": \"windbeck\"\n        },\n        {\n          \"object_id\": \"sharp-althea-selection\",\n          \"sequence_order\": 11,\n          \"candidate_id\": \"sharp-althea\"\n        },\n        {\n          \"object_id\": \"greher-selection\",\n          \"sequence_order\": 12,\n          \"candidate_id\": \"greher\"\n        },\n        {\n          \"object_id\": \"alexander-selection\",\n          \"sequence_order\": 13,\n          \"candidate_id\": \"alexander\"\n        },\n        {\n          \"object_id\": \"mitchell-selection\",\n          \"sequence_order\": 14,\n          \"candidate_id\": \"mitchell\"\n        },\n        {\n          \"object_id\": \"lee-selection\",\n          \"sequence_order\": 15,\n          \"candidate_id\": \"lee\"\n        },\n        {\n          \"object_id\": \"ash-selection\",\n          \"sequence_order\": 16,\n          \"candidate_id\": \"ash\"\n        },\n        {\n          \"object_id\": \"kennedy-selection\",\n          \"sequence_order\": 17,\n          \"candidate_id\": \"kennedy\"\n        },\n        {\n          \"object_id\": \"jackson-selection\",\n          \"sequence_order\": 18,\n          \"candidate_id\": \"jackson\"\n        },\n        {\n          \"object_id\": \"brown-selection\",\n          \"sequence_order\": 19,\n          \"candidate_id\": \"brown\"\n        },\n        {\n          \"object_id\": \"teller-selection\",\n          \"sequence_order\": 20,\n          \"candidate_id\": \"teller\"\n        },\n        {\n          \"object_id\": \"ward-selection\",\n          \"sequence_order\": 21,\n          \"candidate_id\": \"ward\"\n        },\n        {\n          \"object_id\": \"murphy-selection\",\n          \"sequence_order\": 22,\n          \"candidate_id\": \"murphy\"\n        },\n        {\n          \"object_id\": \"newman-selection\",\n          \"sequence_order\": 23,\n          \"candidate_id\": \"newman\"\n        },\n        {\n          \"object_id\": \"callanann-selection\",\n          \"sequence_order\": 24,\n          \"candidate_id\": \"callanann\"\n        },\n        {\n          \"object_id\": \"york-selection\",\n          \"sequence_order\": 25,\n          \"candidate_id\": \"york\"\n        },\n        {\n          \"object_id\": \"chandler-selection\",\n          \"sequence_order\": 26,\n          \"candidate_id\": \"chandler\"\n        },\n        {\n          \"object_id\": \"write-in-selection-governor\",\n          \"sequence_order\": 27,\n          \"candidate_id\": \"write-in\"\n        }\n      ],\n      \"ballot_title\": {\n        \"text\": [\n          {\n            \"value\": \"Governor of the Commonwealth of Ozark\",\n            \"language\": \"en\"\n          },\n          {\n            \"value\": \"Gobernador de la Mancomunidad de Ozark\",\n            \"language\": \"es\"\n          }\n        ]\n      },\n      \"ballot_subtitle\": {\n        \"text\": [\n          {\n            \"value\": \"Vote for one\",\n            \"language\": \"en\"\n          },\n          {\n            \"value\": \"Votar por uno\",\n            \"language\": \"es\"\n          }\n        ]\n      }\n    },\n    {\n      \"object_id\": \"congress-district-5-contest\",\n      \"sequence_order\": 2,\n      \"electoral_district_id\": \"congress-district-5\",\n      \"vote_variation\": \"one_of_m\",\n      \"number_elected\": 1,\n      \"votes_allowed\": 1,\n      \"name\": \"Congressional District 5\",\n      \"ballot_selections\": [\n        {\n          \"object_id\": \"soliz-selection\",\n          \"sequence_order\": 0,\n          \"candidate_id\": \"soliz\"\n        },\n        {\n          \"object_id\": \"keller-selection\",\n          \"sequence_order\": 1,\n          \"candidate_id\": \"keller\"\n        },\n        {\n          \"object_id\": \"rangel-selection\",\n          \"sequence_order\": 2,\n          \"candidate_id\": \"rengel\"\n        },\n        {\n          \"object_id\": \"argent-selection\",\n          \"sequence_order\": 3,\n          \"candidate_id\": \"argent\"\n        },\n        {\n          \"object_id\": \"witherspoon-smithson-selection\",\n          \"sequence_order\": 4,\n          \"candidate_id\": \"witherspoon-smithson\"\n        },\n        {\n          \"object_id\": \"write-in-selection-us-congress-district-5\",\n          \"sequence_order\": 5,\n          \"candidate_id\": \"write-in\"\n        }\n      ],\n      \"ballot_title\": {\n        \"text\": [\n          {\n            \"value\": \"House of Representatives Congressional District 5\",\n            \"language\": \"en\"\n          },\n          {\n            \"value\": \"C\\u00e1mara de Representantes de Distrito 5 del Congreso de Ozark\",\n            \"language\": \"es\"\n          }\n        ]\n      },\n      \"ballot_subtitle\": {\n        \"text\": [\n          {\n            \"value\": \"Vote for one\",\n            \"language\": \"en\"\n          },\n          {\n            \"value\": \"Votar por uno\",\n            \"language\": \"es\"\n          }\n        ]\n      }\n    },\n    {\n      \"object_id\": \"congress-district-7-contest\",\n      \"sequence_order\": 3,\n      \"electoral_district_id\": \"congress-district-7\",\n      \"vote_variation\": \"one_of_m\",\n      \"number_elected\": 1,\n      \"votes_allowed\": 1,\n      \"name\": \"Congressional District 7\",\n      \"ballot_selections\": [\n        {\n          \"object_id\": \"bainbridge-selection\",\n          \"sequence_order\": 0,\n          \"candidate_id\": \"bainbridge\"\n        },\n        {\n          \"object_id\": \"hennessey-selection\",\n          \"sequence_order\": 1,\n          \"candidate_id\": \"hennessey\"\n        },\n        {\n          \"object_id\": \"savoy-selection\",\n          \"sequence_order\": 2,\n          \"candidate_id\": \"savoy\"\n        },\n        {\n          \"object_id\": \"tawa-selection\",\n          \"sequence_order\": 3,\n          \"candidate_id\": \"tawa\"\n        },\n        {\n          \"object_id\": \"tawa-mary-selection\",\n          \"sequence_order\": 4,\n          \"candidate_id\": \"tawa-mary\"\n        },\n        {\n          \"object_id\": \"write-in-selection-us-congress-district-7\",\n          \"sequence_order\": 5,\n          \"candidate_id\": \"write-in\"\n        }\n      ],\n      \"ballot_title\": {\n        \"text\": [\n          {\n            \"value\": \"House of Representatives Ozark Congressional District 7\",\n            \"language\": \"en\"\n          },\n          {\n            \"value\": \"C\\u00e1mara de Representantes de Distrito 7 del Congreso de Ozark\",\n            \"language\": \"es\"\n          }\n        ]\n      },\n      \"ballot_subtitle\": {\n        \"text\": [\n          {\n            \"value\": \"Vote for one\",\n            \"language\": \"en\"\n          },\n          {\n            \"value\": \"Votar por uno\",\n            \"language\": \"es\"\n          }\n        ]\n      }\n    },\n    {\n      \"object_id\": \"pismo-beach-school-board-contest\",\n      \"sequence_order\": 4,\n      \"electoral_district_id\": \"pismo-beach-school-district-precinct-1\",\n      \"vote_variation\": \"n_of_m\",\n      \"number_elected\": 3,\n      \"votes_allowed\": 3,\n      \"name\": \"Pismo Beach School Board\",\n      \"ballot_selections\": [\n        {\n          \"object_id\": \"moore-selection\",\n          \"sequence_order\": 0,\n          \"candidate_id\": \"moore\"\n        },\n        {\n          \"object_id\": \"white-selection\",\n          \"sequence_order\": 1,\n          \"candidate_id\": \"white\"\n        },\n        {\n          \"object_id\": \"smallberries-selection\",\n          \"sequence_order\": 2,\n          \"candidate_id\": \"smallberries\"\n        },\n        {\n          \"object_id\": \"warfin-selection\",\n          \"sequence_order\": 3,\n          \"candidate_id\": \"warfin\"\n        },\n        {\n          \"object_id\": \"norberg-selection\",\n          \"sequence_order\": 4,\n          \"candidate_id\": \"norberg\"\n        },\n        {\n          \"object_id\": \"parks-selection\",\n          \"sequence_order\": 5,\n          \"candidate_id\": \"parks\"\n        },\n        {\n          \"object_id\": \"savannah-selection\",\n          \"sequence_order\": 6,\n          \"candidate_id\": \"savannah\"\n        },\n        {\n          \"object_id\": \"write-in-selection-1-pismo-beach-school-board\",\n          \"sequence_order\": 7,\n          \"candidate_id\": \"write-in-1\"\n        },\n        {\n          \"object_id\": \"write-in-selection-2-pismo-beach-school-board\",\n          \"sequence_order\": 8,\n          \"candidate_id\": \"write-in-2\"\n        },\n        {\n          \"object_id\": \"write-in-selection-3-pismo-beach-school-board\",\n          \"sequence_order\": 9,\n          \"candidate_id\": \"write-in-3\"\n        }\n      ],\n      \"ballot_title\": {\n        \"text\": [\n          {\n            \"value\": \"Pismo Beach School Board\",\n            \"language\": \"en\"\n          },\n          {\n            \"value\": \"Junta Escolar de Pismo Beach\",\n            \"language\": \"es\"\n          }\n        ]\n      },\n      \"ballot_subtitle\": {\n        \"text\": [\n          {\n            \"value\": \"Vote for up to 3\",\n            \"language\": \"en\"\n          },\n          {\n            \"value\": \"Vote por hasta 3\",\n            \"language\": \"es\"\n          }\n        ]\n      }\n    },\n    {\n      \"object_id\": \"somerset-school-board-contest\",\n      \"sequence_order\": 5,\n      \"electoral_district_id\": \"somerset-school-district-precinct-1\",\n      \"vote_variation\": \"n_of_m\",\n      \"number_elected\": 2,\n      \"votes_allowed\": 2,\n      \"name\": \"Somerset School Board\",\n      \"ballot_selections\": [\n        {\n          \"object_id\": \"summers-selection\",\n          \"sequence_order\": 0,\n          \"candidate_id\": \"summers\"\n        },\n        {\n          \"object_id\": \"chase-selection\",\n          \"sequence_order\": 1,\n          \"candidate_id\": \"chase\"\n        },\n        {\n          \"object_id\": \"osborne-selection\",\n          \"sequence_order\": 2,\n          \"candidate_id\": \"osborne\"\n        },\n        {\n          \"object_id\": \"rosenberg-selection\",\n          \"sequence_order\": 3,\n          \"candidate_id\": \"rosenberg\"\n        },\n        {\n          \"object_id\": \"head-selection\",\n          \"sequence_order\": 4,\n          \"candidate_id\": \"head\"\n        },\n        {\n          \"object_id\": \"marsters-selection\",\n          \"sequence_order\": 5,\n          \"candidate_id\": \"marsters\"\n        },\n        {\n          \"object_id\": \"write-in-selection-1-somerset-school-board\",\n          \"sequence_order\": 6,\n          \"candidate_id\": \"write-in-1\"\n        },\n        {\n          \"object_id\": \"write-in-selection-2-somerset-school-board\",\n          \"sequence_order\": 7,\n          \"candidate_id\": \"write-in-2\"\n        }\n      ],\n      \"ballot_title\": {\n        \"text\": [\n          {\n            \"value\": \"Pismo Beach School Board\",\n            \"language\": \"en\"\n          },\n          {\n            \"value\": \"Junta Escolar de Somerset\",\n            \"language\": \"es\"\n          }\n        ]\n      },\n      \"ballot_subtitle\": {\n        \"text\": [\n          {\n            \"value\": \"Vote for up to 2\",\n            \"language\": \"en\"\n          },\n          {\n            \"value\": \"Vote por hasta 2\",\n            \"language\": \"es\"\n          }\n        ]\n      }\n    },\n    {\n      \"object_id\": \"arlington-chief-justice-retain-demergue\",\n      \"sequence_order\": 6,\n      \"electoral_district_id\": \"arlington-township-precinct-1\",\n      \"vote_variation\": \"one_of_m\",\n      \"number_elected\": 1,\n      \"votes_allowed\": 1,\n      \"name\": \"Retain Robert Demergue as Chief Justice?\",\n      \"ballot_selections\": [\n        {\n          \"object_id\": \"ozark-chief-justice-retain-demergue-affirmative-selection\",\n          \"sequence_order\": 0,\n          \"candidate_id\": \"ozark-chief-justice-retain-demergue-affirmative\"\n        },\n        {\n          \"object_id\": \"ozark-chief-justice-retain-demergue-negative-selection\",\n          \"sequence_order\": 1,\n          \"candidate_id\": \"ozark-chief-justice-retain-demergue-negative\"\n        }\n      ],\n      \"ballot_title\": {\n        \"text\": [\n          {\n            \"value\": \"Retain Robert Demergue as Chief Justice?\",\n            \"language\": \"en\"\n          },\n          {\n            \"value\": \"\\u00bfRetener a Robert Demergue como Presidente del Tribunal Supremo?\",\n            \"language\": \"es\"\n          }\n        ]\n      },\n      \"ballot_subtitle\": {\n        \"text\": [\n          {\n            \"value\": \"Choose 'Accept' or 'Reject'\",\n            \"language\": \"en\"\n          },\n          {\n            \"value\": \"Elija 'Aceptar' o 'Rechazar'\",\n            \"language\": \"es\"\n          }\n        ]\n      }\n    },\n    {\n      \"object_id\": \"exeter-utility-district-referendum-contest\",\n      \"sequence_order\": 7,\n      \"electoral_district_id\": \"lacroix-exeter-utility-district\",\n      \"vote_variation\": \"one_of_m\",\n      \"number_elected\": 1,\n      \"votes_allowed\": 1,\n      \"name\": \"Capital Projects Levy\",\n      \"ballot_selections\": [\n        {\n          \"object_id\": \"exeter-utility-district-referendum-affirmative-selection\",\n          \"sequence_order\": 0,\n          \"candidate_id\": \"exeter-utility-district-referendum-affirmative\"\n        },\n        {\n          \"object_id\": \"exeter-utility-district-referendum-selection\",\n          \"sequence_order\": 1,\n          \"candidate_id\": \"exeter-utility-district-referendum-negative\"\n        }\n      ],\n      \"ballot_title\": {\n        \"text\": [\n          {\n            \"value\": \"Levy Lift to Maintain Public Safety and Other Core Utility Services\",\n            \"language\": \"en\"\n          },\n          {\n            \"value\": \"Levy Lift para Mantener la Seguridad P\\u00fablica y Otros Servicios B\\u00e1sicos\",\n            \"language\": \"es\"\n          }\n        ]\n      },\n      \"ballot_subtitle\": {\n        \"text\": [\n          {\n            \"value\": \"Should this Proposition be approved?\",\n            \"language\": \"en\"\n          },\n          {\n            \"value\": \"Uno\",\n            \"language\": \"es\"\n          }\n        ]\n      }\n    }\n  ],\n  \"ballot_styles\": [\n    {\n      \"object_id\": \"congress-district-7-hamilton-county\",\n      \"geopolitical_unit_ids\": [\n        \"hamilton-county\",\n        \"congress-district-7\"\n      ],\n      \"party_ids\": null,\n      \"image_uri\": null\n    },\n    {\n      \"object_id\": \"congress-district-7-lacroix\",\n      \"geopolitical_unit_ids\": [\n        \"hamilton-county\",\n        \"congress-district-7\",\n        \"lacroix-township-precinct-1\"\n      ],\n      \"party_ids\": null,\n      \"image_uri\": null\n    },\n    {\n      \"object_id\": \"congress-district-7-lacroix-exeter\",\n      \"geopolitical_unit_ids\": [\n        \"hamilton-county\",\n        \"congress-district-7\",\n        \"lacroix-township-precinct-1\",\n        \"lacroix-exeter-utility-district\"\n      ],\n      \"party_ids\": null,\n      \"image_uri\": null\n    },\n    {\n      \"object_id\": \"congress-district-7-arlington\",\n      \"geopolitical_unit_ids\": [\n        \"hamilton-county\",\n        \"congress-district-7\",\n        \"arlington-township-precinct-1\"\n      ],\n      \"party_ids\": null,\n      \"image_uri\": null\n    },\n    {\n      \"object_id\": \"congress-district-7-arlington-pismo-beach\",\n      \"geopolitical_unit_ids\": [\n        \"hamilton-county\",\n        \"congress-district-7\",\n        \"arlington-township-precinct-1\",\n        \"pismo-beach-school-district-precinct-1\"\n      ],\n      \"party_ids\": null,\n      \"image_uri\": null\n    },\n    {\n      \"object_id\": \"congress-district-7-arlington-somerset\",\n      \"geopolitical_unit_ids\": [\n        \"hamilton-county\",\n        \"congress-district-7\",\n        \"arlington-township-precinct-1\",\n        \"somerset-school-district-precinct-1\"\n      ],\n      \"party_ids\": null,\n      \"image_uri\": null\n    },\n    {\n      \"object_id\": \"congress-district-5-hamilton-county\",\n      \"geopolitical_unit_ids\": [\n        \"hamilton-county\",\n        \"congress-district-5\"\n      ],\n      \"party_ids\": null,\n      \"image_uri\": null\n    },\n    {\n      \"object_id\": \"congress-district-5-lacroix\",\n      \"geopolitical_unit_ids\": [\n        \"hamilton-county\",\n        \"congress-district-5\",\n        \"lacroix-township-precinct-1\"\n      ],\n      \"party_ids\": null,\n      \"image_uri\": null\n    },\n    {\n      \"object_id\": \"congress-district-5-harris\",\n      \"geopolitical_unit_ids\": [\n        \"hamilton-county\",\n        \"congress-district-5\",\n        \"harris-township\"\n      ],\n      \"party_ids\": null,\n      \"image_uri\": null\n    },\n    {\n      \"object_id\": \"congress-district-5-arlington-pismo-beach\",\n      \"geopolitical_unit_ids\": [\n        \"hamilton-county\",\n        \"congress-district-5\",\n        \"arlington-township-precinct-1\",\n        \"pismo-beach-school-district-precinct-1\"\n      ],\n      \"party_ids\": null,\n      \"image_uri\": null\n    },\n    {\n      \"object_id\": \"congress-district-5-arlington-somerset\",\n      \"geopolitical_unit_ids\": [\n        \"hamilton-county\",\n        \"congress-district-5\",\n        \"arlington-township-precinct-1\",\n        \"somerset-school-district-precinct-1\"\n      ],\n      \"party_ids\": null,\n      \"image_uri\": null\n    }\n  ],\n  \"name\": {\n    \"text\": [\n      {\n        \"value\": \"Hamiltion County General Election\",\n        \"language\": \"en\"\n      },\n      {\n        \"value\": \"Elecci\\u00f3n general del condado de Hamilton\",\n        \"language\": \"es\"\n      }\n    ]\n  },\n  \"contact_information\": {\n    \"address_line\": [\n      \"1234 Paul Revere Run\",\n      \"Hamilton, Ozark 99999\"\n    ],\n    \"email\": [\n      {\n        \"annotation\": \"press\",\n        \"value\": \"inquiries@hamilton.state.gov\"\n      },\n      {\n        \"annotation\": \"federal\",\n        \"value\": \"commissioner@hamilton.state.gov\"\n      }\n    ],\n    \"phone\": [\n      {\n        \"annotation\": \"domestic\",\n        \"value\": \"123-456-7890\"\n      },\n      {\n        \"annotation\": \"international\",\n        \"value\": \"+1-123-456-7890\"\n      }\n    ],\n    \"name\": \"Hamilton State Election Commission\"\n  }\n}"
  },
  {
    "path": "data/manifest-minimal.json",
    "content": "{\n  \"election_scope_id\": \"franklin-minimal-referendum-manifest\",\n  \"spec_version\": \"1.0\",\n  \"type\": \"general\",\n  \"start_date\": \"2020-03-01T08:00:00-05:00\",\n  \"end_date\": \"2020-03-03T19:00:00-05:00\",\n  \"geopolitical_units\": [\n    {\n      \"object_id\": \"franklin-county\",\n      \"name\": \"Franklin County\",\n      \"type\": \"municipality\",\n      \"contact_information\": null\n    }\n  ],\n  \"parties\": [\n    {\n      \"object_id\": \"N/A\",\n      \"name\": {\n        \"text\": []\n      },\n      \"abbreviation\": null,\n      \"color\": null,\n      \"logo_uri\": null\n    }\n  ],\n  \"candidates\": [\n    {\n      \"object_id\": \"referendum-pineapple-affirmative\",\n      \"name\": {\n        \"text\": []\n      },\n      \"party_id\": null,\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"referendum-pineapple-negative\",\n      \"name\": {\n        \"text\": []\n      },\n      \"party_id\": null,\n      \"image_uri\": null,\n      \"is_write_in\": null\n    }\n  ],\n  \"contests\": [\n    {\n      \"object_id\": \"referendum-pineapple\",\n      \"sequence_order\": 0,\n      \"electoral_district_id\": \"franklin-county\",\n      \"vote_variation\": \"one_of_m\",\n      \"number_elected\": 1,\n      \"votes_allowed\": 1,\n      \"name\": \"Referendum for Banning Pineapple on Pizza\",\n      \"ballot_selections\": [\n        {\n          \"object_id\": \"referendum-pineapple-affirmative-selection\",\n          \"sequence_order\": 0,\n          \"candidate_id\": \"referendum-pineapple-affirmative\"\n        },\n        {\n          \"object_id\": \"referendum-pineapple-negative-selection\",\n          \"sequence_order\": 1,\n          \"candidate_id\": \"referendum-pineapple-negative\"\n        }\n      ],\n      \"ballot_title\": null,\n      \"ballot_subtitle\": null\n    }\n  ],\n  \"ballot_styles\": [\n    {\n      \"object_id\": \"ballot-style-01\",\n      \"geopolitical_unit_ids\": [\n        \"franklin-county\"\n      ],\n      \"party_ids\": null,\n      \"image_uri\": null\n    }\n  ],\n  \"name\": {\n    \"text\": [\n      {\n        \"value\": \"Franklin County Minimal General Election March 2020\",\n        \"language\": \"en\"\n      },\n      {\n        \"value\": \"Elecciones generales m\\u00ednimas del condado de Franklin marzo de 2020\",\n        \"language\": \"es\"\n      }\n    ]\n  },\n  \"contact_information\": null\n}"
  },
  {
    "path": "data/manifest-small.json",
    "content": "{\n  \"election_scope_id\": \"franklin-county-general-march2020\",\n  \"spec_version\": \"1.0\",\n  \"type\": \"general\",\n  \"start_date\": \"2020-03-01T08:00:00-05:00\",\n  \"end_date\": \"2020-03-03T19:00:00-05:00\",\n  \"geopolitical_units\": [\n    {\n      \"object_id\": \"franklin-county\",\n      \"name\": \"Franklin County\",\n      \"type\": \"county\",\n      \"contact_information\": null\n    },\n    {\n      \"object_id\": \"ozark-school-district\",\n      \"name\": \"Ozark School District in Franklin County\",\n      \"type\": \"school\",\n      \"contact_information\": null\n    }\n  ],\n  \"parties\": [\n    {\n      \"object_id\": \"party-whig\",\n      \"name\": {\n        \"text\": []\n      },\n      \"abbreviation\": \"WHI\",\n      \"color\": null,\n      \"logo_uri\": null\n    },\n    {\n      \"object_id\": \"party-federalist\",\n      \"name\": {\n        \"text\": []\n      },\n      \"abbreviation\": \"FED\",\n      \"color\": null,\n      \"logo_uri\": null\n    },\n    {\n      \"object_id\": \"party-democratic-republican\",\n      \"name\": {\n        \"text\": []\n      },\n      \"abbreviation\": \"DR\",\n      \"color\": null,\n      \"logo_uri\": null\n    }\n  ],\n  \"candidates\": [\n    {\n      \"object_id\": \"benjamin-franklin\",\n      \"name\": {\n        \"text\": []\n      },\n      \"party_id\": \"party-whig\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"john-adams\",\n      \"name\": {\n        \"text\": []\n      },\n      \"party_id\": \"party-federalist\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"john-hancock\",\n      \"name\": {\n        \"text\": []\n      },\n      \"party_id\": \"party-democratic-republican\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"samuel-adams\",\n      \"name\": {\n        \"text\": []\n      },\n      \"party_id\": \"party-democratic-republican\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"thomas-jefferson\",\n      \"name\": {\n        \"text\": []\n      },\n      \"party_id\": \"party-democratic-republican\",\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"write-in\",\n      \"name\": {\n        \"text\": []\n      },\n      \"party_id\": null,\n      \"image_uri\": null,\n      \"is_write_in\": true\n    },\n    {\n      \"object_id\": \"referendum-pineapple-affirmative\",\n      \"name\": {\n        \"text\": []\n      },\n      \"party_id\": null,\n      \"image_uri\": null,\n      \"is_write_in\": null\n    },\n    {\n      \"object_id\": \"referendum-pineapple-negative\",\n      \"name\": {\n        \"text\": []\n      },\n      \"party_id\": null,\n      \"image_uri\": null,\n      \"is_write_in\": null\n    }\n  ],\n  \"contests\": [\n    {\n      \"object_id\": \"congressional-race-01\",\n      \"sequence_order\": 0,\n      \"electoral_district_id\": \"franklin-county\",\n      \"vote_variation\": \"one_of_m\",\n      \"number_elected\": 1,\n      \"votes_allowed\": 1,\n      \"name\": \"Representative to US Congress\",\n      \"ballot_selections\": [\n        {\n          \"object_id\": \"john-hancock-selection\",\n          \"sequence_order\": 0,\n          \"candidate_id\": \"john-hancock\"\n        },\n        {\n          \"object_id\": \"benjamin-franklin-selection\",\n          \"sequence_order\": 1,\n          \"candidate_id\": \"benjamin-franklin\"\n        },\n        {\n          \"object_id\": \"write-in-selection\",\n          \"sequence_order\": 2,\n          \"candidate_id\": \"write-in\"\n        }\n      ],\n      \"ballot_title\": null,\n      \"ballot_subtitle\": null\n    },\n    {\n      \"object_id\": \"referendum-pineapple\",\n      \"sequence_order\": 1,\n      \"electoral_district_id\": \"franklin-county\",\n      \"vote_variation\": \"one_of_m\",\n      \"number_elected\": 1,\n      \"votes_allowed\": 1,\n      \"name\": \"The Pineapple Question\",\n      \"ballot_selections\": [\n        {\n          \"object_id\": \"referendum-pineapple-affirmative-selection\",\n          \"sequence_order\": 0,\n          \"candidate_id\": \"referendum-pineapple-affirmative\"\n        },\n        {\n          \"object_id\": \"referendum-pineapple-negative-selection\",\n          \"sequence_order\": 1,\n          \"candidate_id\": \"referendum-pineapple-negative\"\n        }\n      ],\n      \"ballot_title\": null,\n      \"ballot_subtitle\": null\n    },\n    {\n      \"object_id\": \"ozark-school-board-race\",\n      \"sequence_order\": 2,\n      \"electoral_district_id\": \"ozark-school-district\",\n      \"vote_variation\": \"n_of_m\",\n      \"number_elected\": 2,\n      \"votes_allowed\": 2,\n      \"name\": \"Ozark School Board\",\n      \"ballot_selections\": [\n        {\n          \"object_id\": \"john-adams-selection\",\n          \"sequence_order\": 0,\n          \"candidate_id\": \"john-adams\"\n        },\n        {\n          \"object_id\": \"samuel-adams-selection\",\n          \"sequence_order\": 1,\n          \"candidate_id\": \"samuel-adams\"\n        },\n        {\n          \"object_id\": \"thomas-jefferson-selection\",\n          \"sequence_order\": 2,\n          \"candidate_id\": \"thomas-jefferson\"\n        }\n      ],\n      \"ballot_title\": null,\n      \"ballot_subtitle\": null\n    }\n  ],\n  \"ballot_styles\": [\n    {\n      \"object_id\": \"franklin-county-no-schooldistrict\",\n      \"geopolitical_unit_ids\": [\n        \"franklin-county\"\n      ],\n      \"party_ids\": null,\n      \"image_uri\": null\n    },\n    {\n      \"object_id\": \"franklin-county-ozark-schools\",\n      \"geopolitical_unit_ids\": [\n        \"franklin-county\",\n        \"ozark-school-district\"\n      ],\n      \"party_ids\": null,\n      \"image_uri\": null\n    }\n  ],\n  \"name\": {\n    \"text\": [\n      {\n        \"value\": \"Franklin County Small General Election March 2020\",\n        \"language\": \"en\"\n      },\n      {\n        \"value\": \"Peque\\u00f1as elecciones generales del condado de Franklin marzo de 2020\",\n        \"language\": \"es\"\n      }\n    ]\n  },\n  \"contact_information\": null\n}"
  },
  {
    "path": "data/plaintext_ballots_simple.json",
    "content": "[\n  {\n    \"object_id\": \"1048ce32-f1b1-4b05-b7fb-8c615ac842ee\",\n    \"style_id\": \"jefferson-county-ballot-style\",\n    \"contests\": [\n      {\n        \"object_id\": \"justice-supreme-court\",\n        \"ballot_selections\": [\n          {\n            \"object_id\": \"john-adams-selection\",\n            \"vote\": 1\n          },\n          {\n            \"object_id\": \"write-in-selection\",\n            \"vote\": 1,\n            \"extended_data\": {\n              \"value\": \"Susan B. Anthony\",\n              \"length\": 16\n            }\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"object_id\": \"03a29d15-667c-4ac8-afd7-549f19b8e4eb\",\n    \"style_id\": \"jefferson-county-ballot-style\",\n    \"contests\": [\n      {\n        \"object_id\": \"justice-supreme-court\",\n        \"ballot_selections\": [\n          {\n            \"object_id\": \"john-adams-selection\",\n            \"vote\": 1\n          },\n          {\n            \"object_id\": \"write-in-selection\",\n            \"vote\": 1,\n            \"extended_data\": {\n              \"value\": \"Susan B. Anthony\",\n              \"length\": 16\n            }\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"object_id\": \"25a7111b-4334-425a-87c1-f7a49f42b3a2\",\n    \"style_id\": \"jefferson-county-ballot-style\",\n    \"contests\": [\n      {\n        \"object_id\": \"justice-supreme-court\",\n        \"ballot_selections\": [\n          {\n            \"object_id\": \"john-adams-selection\",\n            \"vote\": 1\n          },\n          {\n            \"object_id\": \"benjamin-franklin-selection\",\n            \"vote\": 1\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"object_id\": \"69aeacb4-64c6-4205-9bb2-5fb6b3b3ea58\",\n    \"style_id\": \"harrison-township-ballot-style\",\n    \"contests\": [\n      {\n        \"object_id\": \"justice-supreme-court\",\n        \"ballot_selections\": [\n          {\n            \"object_id\": \"john-adams-selection\",\n            \"vote\": 1\n          },\n          {\n            \"object_id\": \"john-hancock-selection\",\n            \"vote\": 1\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"object_id\": \"5a150c74-a2cb-47f6-b575-165ba8a4ce53\",\n    \"style_id\": \"harrison-township-ballot-style\",\n    \"contests\": [\n      {\n        \"object_id\": \"justice-supreme-court\",\n        \"ballot_selections\": [\n          {\n            \"object_id\": \"john-adams-selection\",\n            \"vote\": 1\n          },\n          {\n            \"object_id\": \"john-hancock-selection\",\n            \"vote\": 1\n          }\n        ]\n      },\n      {\n        \"object_id\": \"referendum-pineapple\",\n        \"ballot_selections\": [\n          {\n            \"object_id\": \"referendum-pineapple-affirmative-selection\",\n            \"vote\": 1\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"object_id\": \"9fee0e77-cfd2-401a-a210-93bbc4dd30ef\",\n    \"style_id\": \"harrison-township-ballot-style\",\n    \"contests\": [\n      {\n        \"object_id\": \"justice-supreme-court\",\n        \"ballot_selections\": [\n          {\n            \"object_id\": \"john-adams-selection\",\n            \"vote\": 1\n          },\n          {\n            \"object_id\": \"write-in-selection\",\n            \"vote\": 1,\n            \"extended_data\": {\n              \"value\": \"Susan B. Anthony\",\n              \"length\": 16\n            }\n          }\n        ]\n      },\n      {\n        \"object_id\": \"referendum-pineapple\",\n        \"ballot_selections\": [\n          {\n            \"object_id\": \"referendum-pineapple-negative-selection\",\n            \"vote\": 1\n          }\n        ]\n      }\n    ]\n  }\n]\n"
  },
  {
    "path": "data/plaintext_two_ballots_minimal.json",
    "content": "[\r\n  {\r\n    \"object_id\": \"external-ballot-id-1234\",\r\n    \"style_id\": \"ballot-style-01\",\r\n    \"contests\": [\r\n      {\r\n        \"object_id\": \"referendum-pineapple\",\r\n        \"ballot_selections\": [\r\n          {\r\n            \"object_id\": \"referendum-pineapple-affirmative-selection\",\r\n            \"vote\": 1\r\n          }\r\n        ]\r\n      }\r\n    ]\r\n  },\r\n  {\r\n    \"object_id\": \"external-ballot-id-3457\",\r\n    \"style_id\": \"ballot-style-01\",\r\n    \"contests\": [\r\n      {\r\n        \"object_id\": \"referendum-pineapple\",\r\n        \"ballot_selections\": [\r\n          {\r\n            \"object_id\": \"referendum-pineapple-negative-selection\",\r\n            \"vote\": 1\r\n          }\r\n        ]\r\n      }\r\n    ]\r\n  }\r\n]\r\n"
  },
  {
    "path": "data/plaintext_two_ballots_small.json",
    "content": "[\r\n  {\r\n    \"object_id\": \"external-ballot-id-2345\",\r\n    \"style_id\": \"franklin-county-ozark-schools\",\r\n    \"contests\": [\r\n      {\r\n        \"object_id\": \"referendum-pineapple\",\r\n        \"ballot_selections\": [\r\n          {\r\n            \"object_id\": \"referendum-pineapple-affirmative-selection\",\r\n            \"vote\": 1\r\n          }\r\n        ]\r\n      },\r\n      {\r\n        \"object_id\": \"congressional-race-01\",\r\n        \"ballot_selections\": [\r\n          {\r\n            \"object_id\": \"john-hancock-selection\",\r\n            \"vote\": 1\r\n          }\r\n        ]\r\n      },\r\n      {\r\n        \"object_id\": \"ozark-school-board-race\",\r\n        \"ballot_selections\": [\r\n          {\r\n            \"object_id\": \"samuel-adams-selection\",\r\n            \"vote\": 1\r\n          },\r\n          {\r\n            \"object_id\": \"thomas-jefferson-selection\",\r\n            \"vote\": 1\r\n          }\r\n        ]\r\n      }\r\n    ]\r\n  },\r\n  {\r\n    \"object_id\": \"external-ballot-id-9876\",\r\n    \"style_id\": \"franklin-county-no-schooldistrict\",\r\n    \"contests\": [\r\n      {\r\n        \"object_id\": \"referendum-pineapple\",\r\n        \"ballot_selections\": [\r\n          {\r\n            \"object_id\": \"referendum-pineapple-affirmative-selection\",\r\n            \"vote\": 1\r\n          }\r\n        ]\r\n      },\r\n      {\r\n        \"object_id\": \"congressional-race-01\",\r\n        \"ballot_selections\": [\r\n          {\r\n            \"object_id\": \"benjamin-franklin-selection\",\r\n            \"vote\": 1\r\n          }\r\n        ]\r\n      }\r\n    ]\r\n  }\r\n]\r\n"
  },
  {
    "path": "docs/0_Configure_Election.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"source\": [\n    \"# Election Configuration\\n\",\n    \"\\n\",\n    \"An election in ElectionGuard is defined as a set of metadata and cryptographic artifacts necessary to encrypt, conduct, tally, decrypt, and verify an election. The Data format used for election metadata is based on the [NIST Election Common Standard Data Specification](https://www.nist.gov/itl/voting/interoperability) but includes some modifications to support the end-to-end cryptography of ElectionGuard.\\n\",\n    \"\\n\",\n    \"Election metadata is described in a specific format parseable into an `Manifest` and it's validity is checked to ensure that it is of an appropriate structure to conduct an End-to-End Verified ElectionGuard Election. ElectionGuard only verifies the components of the election metadata that are necessary to encrypt and decrypt the election. Some components of the election metadata are not checked for structural validity, but are used when generating a hash representation of the `Manifest`.\\n\",\n    \"\\n\",\n    \"From an `Manifest` we derive an `InternalManifest` that includes a subset of the elements from the `Manifest` required to verify ballots are correct. Additionally a `CiphertextElectionContext` is created during the [Key Ceremony](/1_Key_Ceremony.md) that includes the cryptographic artifacts necessary for encrypting ballots.\\n\",\n    \"\\n\",\n    \"## Glossary\\n\",\n    \"\\n\",\n    \"- **Election Manifest** The election metadata in json format that is parsed into an Election Description\\n\",\n    \"- **Election Description** The election metadata that describes the structure and type of the election, including geopolitical units, contests, candidates, and ballot styles, etc.\\n\",\n    \"- **Internal Election Description** The subset of the `Manifest` required by ElectionGuard to validate ballots are correctly associated with an election. This component mutates the state of the Election Description.\\n\",\n    \"- **Ciphertext Election Context** The cryptographic context of an election that is configured during the `Key Ceremony`\\n\",\n    \"- **Description Hash** a Hash representation of the original Manifest.\\n\",\n    \"\\n\",\n    \"## Process\\n\",\n    \"\\n\",\n    \"1. Define an election according to the `Manifest` requirements.\\n\",\n    \"2. Use the [NIST Common Standard Data Specification](https://www.nist.gov/itl/voting/interoperability) as a guide, but note the differences in [election.py](https://github.com/microsoft/electionguard-python/tree/main/src/electionguard.election.py) and the provided [sample manifest](https://github.com/microsoft/electionguard-python/tree/main/data/election_manifest_simple.json).\\n\",\n    \"3. Parse the `Manifest` into the application.\\n\",\n    \"4. Define the encryption parameters necessary for conducting an election (see `Key Ceremony`).\\n\",\n    \"5. Create the Pubic Key either from a single secret, or from the Key Ceremony.\\n\",\n    \"6. Build the `InternalManifest` and `CiphertextElectionContext` from the `Manifest` and `ElGamalKeyPair.public_key`.\\n\",\n    \"\\n\",\n    \"## Usage Example\"\n   ],\n   \"metadata\": {}\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"source\": [\n    \"import os\\n\",\n    \"from electionguard.election import CiphertextElectionContext\\n\",\n    \"from electionguard.election_builder import ElectionBuilder\\n\",\n    \"from electionguard.elgamal import ElGamalKeyPair, elgamal_keypair_from_secret\\n\",\n    \"from electionguard.manifest import Manifest, InternalManifest\\n\",\n    \"\\n\",\n    \"# Open an election manifest file\\n\",\n    \"with open(os.path.join(some_path, \\\"election-manifest.json\\\"), \\\"r\\\") as manifest:\\n\",\n    \"    string_representation = manifest.read()\\n\",\n    \"    election_description = Manifest.from_json(string_representation)\\n\",\n    \"\\n\",\n    \"# Create an election builder instance, and configure it for a single public-private keypair.\\n\",\n    \"# in a real election, you would configure this for a group of guardians.  See Key Ceremony for more information.\\n\",\n    \"builder = ElectionBuilder(\\n\",\n    \"    number_of_guardians=1,  # since we will generate a single public-private keypair, we set this to 1\\n\",\n    \"    quorum=1,  # since we will generate a single public-private keypair, we set this to 1\\n\",\n    \"    description=election_description,\\n\",\n    \")\\n\",\n    \"\\n\",\n    \"# Generate an ElGamal Keypair from a secret.  In a real election you would use the Key Ceremony instead.\\n\",\n    \"some_secret_value: int = 12345\\n\",\n    \"keypair: ElGamalKeyPair = elgamal_keypair_from_secret(some_secret_value)\\n\",\n    \"\\n\",\n    \"builder.set_public_key(keypair.public_key)\\n\",\n    \"\\n\",\n    \"# get an `InternalElectionDescription` and `CiphertextElectionContext`\\n\",\n    \"# that are used for the remainder of the election.\\n\",\n    \"(internal_manifest, context) = builder.build()\"\n   ],\n   \"outputs\": [],\n   \"metadata\": {\n    \"attributes\": {\n     \"classes\": [\n      \"code-cell\"\n     ],\n     \"id\": \"\"\n    }\n   }\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"source\": [\n    \"## Constants\\n\",\n    \"\\n\",\n    \"The election constants are the four constants that sit underneath most of the mathematical operations. The election constants can be configured, but there is a standard set which is recommended for most use cases. These can be found in `constants.py`.\\n\",\n    \"\\n\",\n    \"**⚠️ Warning ⚠️**\\n\",\n    \"\\n\",\n    \"There are some test constants used for testing code, but these are never to be used in any production system. The small and extra small constants are unlikely to be used except in rare cases for property testing due to collisions that will happen for smaller number sets.\"\n   ],\n   \"metadata\": {}\n  }\n ],\n \"metadata\": {\n  \"language_info\": {\n   \"name\": \"python\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 5\n}"
  },
  {
    "path": "docs/1_Key_Ceremony.md",
    "content": "# Key Ceremony\n\nThe ElectionGuard Key Ceremony is the process used by Election Officials to share encryption keys for an election. Before an election, a fixed number of Guardians are selection to hold the private keys needed to decrypt the election results. A Quorum count of Guardians can also be specified to compensate for guardians who may be missing at the time of Decryption. For instance, 5 Guardians may be selected to hold the keys, but only 3 of them are required to decrypt the election results.\n\nGuardians are typically Election Officials, Trustees Canvass Board Members, Government Officials or other trusted authorities who are responsible and accountable for conducting the election.\n\n## Summary\n\nThe Key Ceremony is broken into several high-level steps. Each Guardian must _announce_ their _attendance_ in the key ceremony, generate their own public-private key pairs, and then _share_ those key pairs with the Quorum. Then the data that is shared is mathematically verified using Non-Interactive Zero Knowledge Proofs, and finally a _joint public key_ is created to encrypt ballots in the election.\n\n### Attendance\n\nGuardians exchange all public keys and ensure each fellow guardian has received an election public key ensuring at all guardians are in attendance.\n\n### Key Sharing\n\nGuardians generate a partial key backup for each guardian and share with that designated key with that guardian. Then each designated guardian sends a verification back to the sender. The sender then publishes to the group when all verifications are received.\n\n### Joint Key\n\nThe final step is to publish the joint election key after all keys and backups have been shared.\n\n## Glossary\n\n- **Guardian** A guardian of the election who holds the ability to partially decrypt the election results\n- **Key Ceremony Mediator** A mediator to mediate communication (if needed) of information such as keys between the guardians\n- **Election Key Pair:** Pair of keys (public & secret) used to encrypt/decrypt election\n- **Election Partial Key Backup:** A point on a secret polynomial and commitments to verify this point for a designated guardian.\n- **Election Polynomial:** The election polynomial is the mathematical expression that each Guardian defines to solve for his or her private key. A different point associated with the polynomial is shared with each of the other guardians so that the guardians can come together to derive the polynomial function and solve for the private key.\n- **Joint Key:** Combined public key from election public keys of each guardian\n- **Quorum:** Quantity of guardians (k) that is required to decrypt the election and is fewer than the total number of guardians available (n)\n\n## Process\n\nThis is a detailed description of the entire Key Ceremony Process\n\n1. The ceremony details are decided upon. These include a `number_of_guardians` and `quorum` of guardians required for decryption.\n2. Each guardian creates a unique `id` and `sequence_order`.\n3. Each guardian must generate their `election key pair` _(ElGamal key pair)_. This will generate a corresponding Schnorr `proof` and `polynomial` used for generating `election partial key backups` for sharing.\n4. Each guardian must give the other guardians their `election public key` directly or through a mediator.\n5. Each guardian must check if all `election public keys` are received.\n6. Each guardian must generate `election partial key backup` for each other guardian. The guardian will use their `polynomial` and the designated guardian's `sequence_order` to create the value.\n7. Each guardian must send each encrypted `election partial key backup` to the designated guardian directly or through a `mediator`.\n8. Each guardian checks if all encrypted `election partial key backups` have been received by their recipient guardian directly or through a mediator.\n9. Each recipient guardian decrypts each received encrypted `election partial key backup`\n10. Each recipient guardian verifies each `election partial key backup` and sends confirmation of verification\n    - If the proof verifies, continue\n    - If the proof fails\n      1. Sender guardian publishes the `election partial key backup` value sent to recipient as a `election partial key challenge` to all the other guardians\n      2. Alternate guardian (outside sender or original recipient) attempts to verify key\n         - If the proof verifies, continue\n         - If the proof fails again, the accused (sender guardian) should be evicted and process should be restarted with new guardian.\n11. On receipt of all verifications of `election partial private keys` by all guardians, generate and publish `joint key` from election public keys.\n\n## Files\n\n- [`key_ceremony.py`](https://github.com/microsoft/electionguard-python/tree/main/src/electionguard/key_ceremony.py)\n- [`guardian.py`](https://github.com/microsoft/electionguard-python/tree/main/src/electionguard/guardian.py)\n- [`key_ceremony_mediator.py`](https://github.com/microsoft/electionguard-python/tree/main/src/electionguard/key_ceremony_mediator.py)\n\n## Usage Example\n\nThis example demonstrates a convenience method to generate guardians for an election\n\n```python\n\nNUMBER_OF_GUARDIANS: int\nQUORUM: int\n\ndetails: CeremonyDetails\nguardians: List[Guardian]\n\n# Setup Guardians\nfor i in range(NUMBER_OF_GUARDIANS):\n  guardians.append(\n    Guardian.from_nonce(f\"some_guardian_id_{str(i)}\", i, NUMBER_OF_GUARDIANS, QUORUM)\n  )\n\nmediator = KeyCeremonyMediator(details)\n\n# Attendance (Public Key Share)\nfor guardian in guardians:\n  mediator.announce(guardian)\n\n# Orchestation (Private Key Share)\norchestrated = mediator.orchestrate()\n\n# Verify (Prove the guardians acted in good faith)\nverified = mediator.verify()\n\n# Publish the Joint Public Key\njoint_public_key = mediator.publish_joint_key()\n\n```\n\n## Implementation Considerations\n\nElectionGuard can be run without the key ceremony. The key ceremony is the recommended process to generate keys for live end-to-end verifiable elections, however this process may not be necessary for other use cases such as privacy preserving risk limiting audits.\n"
  },
  {
    "path": "docs/2_Encrypt_Ballots.md",
    "content": "# Encrypt Ballots\n\nThe primary function of ElectionGuard is to encrypt ballots.  Ballots are encrypted on a uniquely identified device within the context of a specific election.  The election public key is used to encrypt ballots.  A _master nonce_ value is generated for each ballot and the nonce is used to derive other nonce values for encrypting the selection on each ballot.\n\n## Glossary\n\n- **Plaintext Ballot** - The plaintext representation of a voter's selections\n- **Ciphertext Ballot** - The encrypted representation of a voter's selections\n- **Master Nonce** - A random number used to derive encryptions in a `CiphertextBallot`\n- **Verification Code or Ballot Code** - A unique hash value generated by an _Encryption Device_ to anonymously identify a ballot\n- **Encryption Device** The device that is doing the encryption\n\n## Process\n\n1. Verify the ballot is well-formed against the _Election Metadata_ (`InternalManifest`)\n2. Generate a random master nonce value to use as a secret when encrypting the ballot\n3. Using the metadata of the election and the master nonce, encrypt each selection on the ballot\n4. For each selection on the ballot, generate a disjunctive Non-Interactive Zero-Knowledge Proof that the encryption is either an encryption of zero or one\n5. For each contest on the ballot, generate a Non-Interactive Zero-Knowledge Proof that the sum of all encrypted ballots is equal to the selection limit on the contest\n6. Generate a verification code for the ballot\n\n## Usage Example\n\n```python\n\ninternal_manifest: InternalManifest\ncontext: CiphertextElectionContext\nballot: PlaintextBallot\n\n# Configure an encryption device\ndevice = EncryptionDevice(generate_device_uuid(), \"Session\", 12345, \"polling-place-one\")\nencrypter = EncryptionMediator(internal_manifest, context, device)\n\n# Encrypt the ballot\nencrypted_ballot: CiphertextBallot = encrypter.encrypt(ballot)\n\n```\n\n## Implementation Considerations\n\nWhen encrypting a ballot, a new ballot object is created that is associated with the plaintext ballot.  The encrypted representation includes all of the encryptions, hash values, nonce values, and proofs generated at each step.  For the primary end-to-end election workflow, consumers of this API should separate the `nonce` values from the `CiphertextBallot` prior to publishing the encrypted ballot representation.  \n"
  },
  {
    "path": "docs/3_Cast_and_Spoil.md",
    "content": "# Cast and Spoil Ballots\n\nEach ballot that is completed by a voter must be either cast or spoiled.  A cast ballot is a ballot that the voter accepts as valid and wishes to include in the official election tally.  A spoiled ballot, also referred to as a challenged ballot, is a ballot that the voter does not accept as valid and wishes to exclude from the official election tally.\n\nElectionGuard includes a mechanism to mark a specific ballot as either cast or spoiled.  Cast ballots are included in the tally record, while spoiled ballots are not.  Spoiled ballots are decrypted into plaintext and published along with the rest of the election record.\n\n## Jurisdictional Differences\n\nDepending on the jurisdiction conducting an election the process of casting and spoiling ballots may be handled differently. For this reason, there are multiple ways to interact with the `BallotBox` and `Tally`.\n\n- By calling [accept_ballot](###-Function-Example) - Ballots can be marked cast or spoiled manually.\n- By using the [Ballot Box](###-Class-Example) - Ballots can be marked cast or spoiled using a stateful class.\n\n### Unknown Ballots\n\nIn some jurisdictions, there is a limit on the number of ballots that may be marked as spoiled.  If this is the case, you may use the `BallotBoxState.UNKNOWN` state, or extend the enumeration to support your specific use case.\n\n## Encrypted Tally\n\nOnce all of the ballots are marked as _cast_ or _spoiled_, all of the encryptions of each option are homomorphically combined to form an encryption of the total number of times that each option was selected in the election.  \n\n> This process is completed only for cast ballot.\n\n> The spoiled ballots are simply marked for inclusion in the election results.\n\n## Glossary\n\n- **Ciphertext Ballot** An encrypted representation of a voter's filled-in ballot.\n- **Submitted Ballot** A wrapper around the `CiphertextBallot` that represents a ballot that is submitted for inclusion in election results and is either: cast or spoiled.\n- **Ballot Box** A stateful collection of ballots that are either cast or spoiled.\n- **Ballot Store** A repository for retaining cast and spoiled ballots.\n- **Cast Ballot** A ballot which a voter has accepted as valid to be included in the official election tally.\n- **Spoiled Ballot** A ballot which a voter did not accept as valid and is not included in the tally.\n- **Unknown Ballot** A ballot which may not yet be determined as cast or spoiled, or that may have been spoiled but is otherwise not published in the election results.\n- **Homomorphic Tally** An encrypted representation of every selection on every ballot that was cast.  This representation is stored in a `CiphertextTally` object.\n\n## Process\n\n1. Each ballot is loaded into memory (if it is not already).\n2. Each ballot is verified to be correct according to the specific election metadata and encryption context.\n3. Each ballot is `submitted` and identified as either being `cast` or `spoiled`.\n4. The collection of cast and spoiled ballots is cached in the `DataStore`.\n5. All ballots are tallied.  The `cast` ballots are combined to create a `CiphertextTally` The spoiled ballots are cached for decryption later.\n\n## Ballot Box\n\nThe ballot box can be interacted with via a stateful class that caches the election context, or via stateless functions.  The following examples demonstrate some ways to interact with the ballot box.\n\nDepending on the specific election workflow, the `BallotBox`class  may not be used for a given election.  For instance, in one case a ballot can be **submitted** directly on an electronic device, in which case there is no `BallotBox`.  In a different workflow, a ballot may be explicitly cast or spoiled in a later step, such as after printing for voter review.\n\nIn all cases, a ballot must be marked as either `cast` or `spoiled` to be included in a tally result.\n\n### Class Example\n\n```python\n\nfrom electionguard.ballot_box import BallotBox\n\ninternal_manifest: InternalManifest\nencryption: CiphertextElection\nstore: DataStore\nballots_to_cast: List[CiphertextBallot]\nballots_to_spoil: List[CiphertextBallot]\n\n# The Ballot Box is a thin wrapper around the `accept_ballot` function method\nballot_box = BallotBox(internal_manifest, encryption, store)\n\n# Cast the ballots\nfor ballot in ballots_to_cast:\n    submitted_ballot = ballot_box.cast(ballot)\n    # The ballot is both returned, and placed into the ballot store\n    assert(store.get(submitted_ballot.object_id) == submitted_ballot)\n\n# Spoil the ballots\nfor ballot in ballots_to_spoil:\n    assert(ballot_box.spoil(ballot) is not None)\n\n```\n\n### Function Example\n\n``` python\n\nfrom electionguard.ballot_box import accept_ballot\n\ninternal_manifest: InternalManifest\nencryption: CiphertextElection\nstore: DataStore\nballots_to_cast: List[CiphertextBallot]\nballots_to_spoil: List[CiphertextBallot]\n\nfor ballot in ballots_to_cast:\n    submitted_ballot = accept_ballot(\n        ballot, BallotBoxState.CAST, internal_manifest, encryption, store\n    )\n\nfor ballot in ballots_to_spoil:\n    submitted_ballot = accept_ballot(\n        ballot, BallotBoxState.SPOILED, internal_manifest, encryption, store\n    )\n\n```\n\n## Tally\n\nGenerating the encrypted `CiphertextTally` can be completed by creating a `CiphertextTally` stateful class and manually marshalling each cast and spoiled ballot.  Using this method is preferable when the collection of ballots is very large\n\nFor convenience, stateless functions are also provided to automatically generate the `CiphertextTally` from a `DataStore`.  This method is preferred when the collection of ballots is arbitrarily small, or when the `DataStore` is overloaded with a custom implementation.\n\n### Using the Stateful Class\n\n```python\n\ninternal_manifest: InternalManifest\ncontext: CiphertextElectionContext\n\nballots: List[SubmittedBallot]\n\ntally = CiphertextTally(internal_manifest, context)\n\nfor ballot in ballots:\n    assert(tally.append(ballot))\n\n```\n\n### Functional Method\n\n```python\n\ninternal_manifest: InternalManifest\ncontext: CiphertextElectionContext\nstore: DataStore\n\ntally = tally_ballots(store, internal_manifest, context)\nassert(tally is not None)\n\n```\n"
  },
  {
    "path": "docs/4_Decrypt_Tally.md",
    "content": "# Decryption\n\nAt the conclusion of voting, all of the cast ballots are published in their encrypted form in the election record together with the proofs that the ballots are well-formed. Additionally, all of the encryptions of each option are homomorphically-combined to form an encryption of the total number of times that each option was selected. The homomorphically-combined encryptions are decrypted to generate the election tally. Individual cast ballots are not decrypted. Individual spoiled ballots are decrypted and the plaintext values are published along with the encrypted representations and the proofs.\n\nIn order to decrypt the homomorphically-combined encryption of each selection, each `Guardian` participating in the decryption must compute a specific `Decryption Share` of the decryption.\n\nIt is preferable that all guardians be present for decryption, however in the event that guardians cannot be present, Electionguard includes a mechanism to decrypt with the `Quorum of Guardians`.\n\nDuring the [Key Ceremony](1_Key_Ceremony.md) a `Quorum of Guardians` is defined that represents the minimum number of guardians that must be present to decrypt the election. If the decryption is to proceed with a `Quorum of Guardians` greater than or equal to the `Quorum` count, but fewer than the total number of guardians, then a subset of the `Available Guardians` must also each construct a `Partial Decryption Share` for the missing `Missing Guardian`, in addition to providing their own `Decryption Share`.\n\nIt is important to note that mathematically not every present guardian has to compute a `Partial Decryption Share` for every `Missing Guardian`. Only the `Quorum Count` of guardians are necessary to construct `Partial Decryption Shares` in order to compensate for any Missing Guardian.\n\nIn this implementation, we take an approach that utilizes all Available Guardians to compensate for Missing Guardians. When it is determined that guardians are missing, all available guardians each calculate a `Partial Decryption Share` for the missing guardian and publish the result. A `Quorum of Guardians` count of available `Partial Decryption Shares` is randomly selected from the pool of available partial decryption shares for a given` Missing Guardian`. If more than one guardian is missing, we randomly choose to ignore the `Partial Decryption Share` provided by one of the Available Guardians whose partial decryption share was chosen for the previous Missing Guardian, and randomly select again from the pool of available Partial Decryption Shares. This ensures that all available guardians have the opportunity to participate in compensating for Missing Guardians.\n\n## Glossary\n\n- **Guardian** A guardian of the election who holds the ability to partially decrypt the election results\n- **Decryption Share** A guardian's partial share of a decryption\n- **Encrypted Tally** The homomorphically-combined and encrypted representation of all selections made for each option on every contest in the election. See [Ballot Box]() for more information.\n- **Key Ceremony** The process conducted at the beginning of the election to create the joint encryption context for encrypting ballots during the election. See [Key Ceremony](1_Key_Ceremony.md) for more information.\n- **Quorum of Guardians** The minimum count (_threshold_) of guardians that must be present in order to successfully decrypt the election results.\n- **Available Guardian** A guardian that has announced as _present_ for the decryption phase\n- **Missing Guardian** A guardian who was configured during the `Key Ceremony` but who is not present for the decryption of the election results.\n- **Compensated Decryption Share** - a partial decryption share value computed by an available guardian to compensate for a missing guardian so that the missing guardian's share can be generated and the election results can be successfully decrypted.\n- **Decryption Mediator** - A component or actor responsible for composing each guardian's partial decryptions or compensated decryptions into the plaintext tally\n\n## Process\n\n1. Each `Guardian` that will participate in the decryption process computes a `Decryption Share` of the _Ciphertext Tally_.\n2. Each `Guardian` also computes a Chaum-Pedersen proof of correctness of their `Decryption Share`.\n\n### Decryption when All Guardians are Present\n\n3. If all guardians are present, the Decryption Shares are combined to generate a tally for each option on every contest\n\n### Decryption when some Guardians are Missing\n\n_warning: The functionality described in this segment is still a 🚧 Work In Progress_\n\nWhen one or more of the Guardians are missing, any subset of the Guardians that are present can use the information they have about the other guardian's private keys to reconstruct the partial decryption shares for the missing guardians.\n\n4. Each `Available Guardian` computes a `Partial Decryption Share` for each `Missing Guardian`\n5. at least a `Quorum` count of `Partial Decryption Shares` are chosen from the values generated in the previous step for a specific `Missing guardian`\n6. Each chosen `Available Guardian` uses its `Partial Decryption Share` to compute a share of the missing partial decryption.\n7. the process is re-run until all Missing Guardians are compensated for.\n8. The `Compensated Decryption Shares` are combined to _reconstruct_ the missing `TallyDecryptionShare`\n9. finally, all of the `DecryptionShares` are combined to generate a tally for each option on every contest\n\n## Challenged/Spoiled Ballots\n\nIf a ballot is not to be included in the vote count, it is considered challenged, or [Spoiled](https://en.wikipedia.org/wiki/Spoilt_vote). Every ballot spoiled in an election is individually verifiably decrypted in exactly the same way that the aggregate ballot of tallies is decrypted. Since spoiled ballots are not included as part of the vote count, they are included in the Election Record with their plaintext values included along with the encrypted representations.\n\nSpoiling ballots is an important part of the ElectionGuard process as it allows voters to explicitly generate challenge ballots that are verifiable as part of the Election Record.\n\n## Usage Example\n\nHere is a simple example of how to execute the decryption process.\n\n```python\n\ninternal_manifest: InternalManifest       # Load the election manifest\ncontext: CiphertextElectionContext          # Load the election encryption context\nencrypted_Tally: CiphertextTally            # Provide a tally from the previous step\n\navailable_guardians: List[Guardian]         # Provite the list of guardians who will participate\nmissing_guardians: List[str]                # Provide a list of guardians who will not participate\n\nmediator = DecryptionMediator(internal_manifest, context, encrypted_tally)\n\n# Loop through the available guardians and annouce their presence\nfor guardian in available_guardians:\n    if (mediator.announce(guardian) is None):\n        break\n\n# loop through the missing guardians and compensate for them\nfor guardian in missing_guardians:\n    if (mediator.compensate(guardian) is None):\n        break\n\n# Generate the plaintext tally\nplaintext_tally = mediator.get_plaintext_tally()\n\n# The plaintext tally automatically includes the election tally and the spoiled ballots\ncontest_tallies = plaintext_tally.contests\nspoiled_ballots = plaintext_tally.spoiled_ballots\n\n```\n\n## Implementation Considerations\n\nIn certain use cases where the `Key Ceremony` is not used, ballots and tallies can be decrypted directly using the secret key of the election. See the [Tally Tests](https://github.com/microsoft/electionguard-python/tree/main/tests/test_tally.md) for an example of how to decrypt the tally using the secret key.\n"
  },
  {
    "path": "docs/5_Publish_and_Verify.md",
    "content": "# Publish and Verify\n\n## Publish\n\nPublishing the election artifacts helps ensure third parties can verify the election. Refer to the specification on the specific details. Below is a breakdown of the objects within the repository. These are files that should be published at the close of the election so others can verify the election.\n\n**Election Artifacts**\n\n```py\nmanifest: Manifest                        # Manifest\nconstants: ElectionConstants              # Constants\ncontext: CiphertextElectionContext        # Encryption context\ndevices: List[EncryptionDevice]           # Encryption devices\nguardian_records: List[GuardianRecord]    # Record of public guardian information\nsubmitted_ballots: List[SubmittedBallot]  # Encrypted submitted ballots\nchallenge_ballots: List[PlaintextTally]   # Decrypted challenge ballots\nciphertext_tally: CiphertextTally         # Encrypted tally\nplaintext_tally: PlaintextTally           # Decrypted tally\n```\n\nThese classes have been defined as `dataclass` to ensure that `asdict` can be used. This ensures ease of serialization to dictionaries within python, but allows customization for those wishing to use custom serialization. `electionguard_tools` includes `export.py` which can be used as an example.\n\n## Verify\n\nThe election artifacts provide a means to begin validation. Start with deserializing the election artifacts to their original classes.\n"
  },
  {
    "path": "docs/Build_and_Run.md",
    "content": "# Build and Run\n\nThese instructions can be used to build and run the project.\n\n## Setup\n\n### 1. Initialize dev environment\n\n```\nmake environment\n```\n\nOR\n\n```\npoetry install --dev\n```\n\n### 2. Install the `electionguard` module in edit mode\n\n```\nmake install\n```\n\nOR\n\n```\npoetry run python -m pip install -e .\n```\n\n!!! warning \"Note: gmpy2 Windows Installation\"\n\n    **Recommended: Use Windows Subsystem for Linux (WSL)**\n\n    _WSL supports the generic workflow for installtion._\n\n    1. Install [WSL](https://docs.microsoft.com/en-us/windows/wsl/install). \n    2. Return to **1. Initialize dev environment**\n\n    **Alternative: Install pre-compiled binary**\n\n    _Poetry does not support `pip install --find-links`, so the `pyproject.toml` must be edited and utilize a local pre-compiled binary of the gmpy2 package._\n\n    1. Determine if 64-bit:\n        _The 32 vs 64 bit is based on your installed python version NOT your system._\n        This code snippet will read true for 64 bit.\n    ```py\n    python -c 'from sys import maxsize; print(maxsize > 2**32)'\n    ```\n    2. Download [pre-compiled binary](https://www.lfd.uci.edu/~gohlke/pythonlibs/#gmpy) into project folder\n    3. Within `pyproject.toml`, replace `gmpy2` reference with direct path to downloaded file.\n    ```py\n    gmpy2 = { path = \"./packages/gmpy2-2.0.8-cp39-cp39-win_amd64.whl\" }\n    ```\n    3. Run `make install`\n\n\n### 3. Validate import of module _(Optional)_\n\n```\nmake validate\n```\n\nOR\n\n```\npoetry run python -c 'import electionguard; print(electionguard.__package__ + \" successfully imported\")'\n```\n\n## Running\n\n### Option 1: Code Coverage\n\n```\nmake coverage\n```\n\nOR\n\n```\npoetry run coverage report\n```\n\n### Option 2: Run tests in VS Code\n\nInstall recommended test explorer extensions and run unit tests through tool.\n\n**⚠️ Note:** For Windows, be sure to select the [virtual environment Python interpreter](https://docs.microsoft.com/en-us/visualstudio/python/installing-python-interpreters).\n\n### Option 3: Run test command\n\n```\nmake test\n```\n\nOR\n\n```\npoetry run python -m pytest /tests\n```\n"
  },
  {
    "path": "docs/Design_and_Architecture.md",
    "content": "# Design & Architecture\n\nThis describes the design and architecture of the `electionguard-python` project.\n\n## Design\n\n### ✅ Simplicity\n\nSimplicity is the first and foremost goal of the code. The intent is for others to be able to **easily transliterate the code to any other programming language** with little more than structures and functions. This simplicity applies to all aspects of the code design, including naming.\n\n### ✅ Extendable and Interpretable\n\nThe library is intentionally general-purpose to support the different use cases of \"end to end verifiable\" voting systems. Different projects may wish to use different layers of the library, including math primitives, encryption functions, and more.\n\n### ✅ Object Oriented Design (OOD) & Functional Methods\n\nAn additional goal is to build a familiar object oriented design with underlying functional style methods. This allows users to simply construct objects in an OOP fashion or directly call the underlying methods in a functional way. This design also facilitates easy testing and composition.\n\nClass methods are used for simplicity, but sophistication with regard to inheritance, object encapsulation, or design patterns is intentionally avoided. These class methods usually rely on the aforementioned functional methods unless the class contains state.\n\n### ✅ Immutable\n\nThe library prefers immutable objects where possible to encourage simple data structures.\n\n### ✅ Dataclasses\n\n`dataclass` is used frequently to simplify constructors. This follows the simplicity aspect, but also ensures easier serialization without being prescriptive on which library to use.\n\n### ✅ Concurrency\n\nWhile this library is not explicitly engineered to _use_ concurrency, it's definitely meant to work properly when the caller wants to run more than one thing at a time. This means there is no global, mutable state in the library with the exception of a discrete-log function doing internal memoization, itself explicitly written to be thread-safe.\n\n### ✅ Union Classes\n\nFor both naming purposes and usability, union classes are generally preferred. This can alleviate issues with [multiple inheritance](#multiple-inheritance)\n\n### 🚫 Exceptions\n\nTo allow for easier transliteration, the library will not raise exception across the API boundary since this is not available in all languages. Instead, the library will have a variety of functions that indicate failures by returning `None`; the caller is expected to check if the result is `None` before any further processing. Python 3 `typing` calls this sort of result `Optional`.This tactic also indicates all exceptions raised are expected to be from bugs.\n\n### 🚫 Multiple Inheritance\n\nAlthough a handy python feature, for implementation simplicity this feature is not used and should be avoided.\n\n## Architecture\n\n### 🤝 Approachable\n\nThe python setup is designed to be as approachable as possible from the environment to the continuous integration.\n\n#### Setup\n\nThe library contains a `Pipfile` that can be used with `pipenv`to ensure the correct dependencies are included, as well as a `setup.py` to install the package itself. There is also a `Makefile` which allows for simple `make` commands to ease new developers into the build process. If the user is a new developer, the recommendation is starting with [Visual Studio Code](https://code.visualstudio.com/) since there are many default settings and recommended extensions in the repository.\n\n#### Folder Structure\n\nThe folder structure is kept to a bare minimum. The ElectionGuard library is located in `src/electionguard` and tests are in `tests`. Standalone applications or other pieces should be in separate subdirectories. For example, the `tests/bench` directory contains a simple Chaum-Pedersen proof computation benchmark.\n\n#### Commands\n\nTo simplify the command structure, [make](https://www.gnu.org/software/make/manual/make.html) is used. A `Makefile` sits in the root directory and contains useful commands that can be used to run setup. These are shown in use in the [continuous integration](#continuous-integration).\n\n### 🧹 Clean Code\n\nThe library uses several tools to assist developers in maintaining clean code. Visual Studio Code is recommended for easier setup.\n\n#### Typing\n\nThe library uses Python 3 **type hints** throughout and ensures return types are defined. **[Mypy](https://mypy.readthedocs.io/en/stable/)** is used to statically check the typing.\n\n#### Linting\n\n[Pylint]() is used for typing; settings are in the `.pylinrc` file.\n\n#### Formatting\n\n[Black]() is used for auto-formatting and checking the formatting of the python code. Settings are in the `pyproject.toml` file.\n\n### 🧪 Testing\n\nThe goal of the project is 100% code coverage with an understanding that there are some limitations.\n\n#### Property Based\n\nProperty testing is helpful for [testing certain properties](https://fsharpforfunandprofit.com/posts/property-based-testing-2/). The library uses [Hypothesis](https://hypothesis.readthedocs.io/en/stable/) property-based testing to vigorously exercise the library. The library includes generator functions for all the core datatypes, making them easy to randomly generate.\n\n### 🚀 Continuous Integration\n\nGitHub Actions are being used for continuous integration. Cross-platform is a primary goal and the workflows provided demonstrate how a developer can build in Linux, MacOS, and Windows.\n\nThe run workflows can be seen on the GitHub repo page or a user can navigate to `.github/workflows` to inspect them.\n\n### 📦 Math Library\n\n[gmpy2](https://gmpy2.readthedocs.io/en/latest/index.html) is a multiprecision numeric library that was chosen over Python's built-ins `int` type for its speed necessary for encryption performance. The current version used is `2.0.8` which is the most stable version. This is necessary for cross-platform since the library uses precompiled libraries for Windows due to reliance on `gmp`. The gmpy2 options are chosen over the native Python equivalents as shown below.\n\n- **Integers:** `int` -> [`mpz`](https://gmpy2.readthedocs.io/en/latest/mpz.html)\n- **Exponents:** `pow` -> `powmod`\n\nWith the use of **mypy** for typing and the lack of type presence in [Typeshed](https://github.com/python/typeshed) for gmpy2, the library provides a stub in `stubs/gmpy2.pyi` to ensure the code compiles without warnings.\n"
  },
  {
    "path": "docs/Election_Manifest.md",
    "content": "# Overview\n\nThere are many types of elections. We need a base set of data that shows how these different types of elections are handled in an ElectionGuard end-to-end verifiable election (or ballot comparison audits).\n\nWe worked with InfernoRed, VotingWorks, and Dan Wallach of Rice University (thanks folks!) to develop a set of conventions, tests, and sample data (based on a starting dataset sample from the Center for Civic Design) that demonstrate how to encode the information necessary to conduct an election into a format that ElectionGuard can use. The election terms and structure are based whenever possible on the [NIST SP-1500-101 Election Event Logging Common Data Format Specification](https://pages.nist.gov/ElectionEventLogging/index.html) (with a prettier and (mostly) more functional implementation [here](https://developers.google.com/elections-data/reference) and a [PDF for version 2](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.1500-100r2.pdf)).  The information captured by the NIST standard is codified into an `election manifest` that defines common elements when conducting an election, such as locations, candidates, parties, contests, and ballot styles.\n\nElectionGuard uses the data contained in the Election Manifest to associate ballots with specific ballot styles and to verify the accuracy of data at different stages of the election process.  Note that not all of the data contained in the Election Manifest impacts the computations of tallies and zero-knowledge proofs used in the published election data that demonstrates end-to-end verifiability; however it is important to include as much data as possible in order to distinguish one election from another. With a well-defined Election Manifest, improperly formatted ballot encryption requests will fail with error messages at the moment of initial encryption; the enforcement of any logic or behavior to prevent overvoting or other malformed ballot submissions are handled by the encrypting device, not ElectionGuard.\n\nIn addition, since json files do not accommodate comments, all notations and exceptions are documented in this readme.\n\n## Election Data Structure\n\n[Elections are characterized into types by NIST](https://developers.google.com/elections-data/reference/election-type) as shown in the table below\n\nelection type | description\n------------- | ----------------\ngeneral  | For an election held typically on the national day for elections.\npartisan-primary-closed | For a primary election that is for a specific party where voter eligibility is based on registration.\npartisan-primary-open | For a primary election that is for a specific party where voter declares desired party or chooses in private.\nprimary | For a primary election without a specified type, such as a nonpartisan primary.\nrunoff | For an election to decide a prior contest that ended with no candidate receiving a majority of the votes.\nspecial | For an election held out of sequence for special circumstances, for example, to fill a vacated office.\nother | Used when the election type is not listed in this enumeration. If used, include a specific value of the OtherType element.\n\nWe present two sample manifests: `general` and `partisan-primary-closed`. The core distinction between the two samples is the role of party: in general elections voters can choose to vote for candidates from any party in a contest, regardless of party affiliation. In partisan primaries voters can only vote in contests germane to their party declaration or affiliation. As such, `special`, `runoff`, and `primary` election types will follow the `general` pattern, and `partisan-primary-open` will follow the `partisan-primary-closed` pattern. Open `primary` elections can follow either pattern as determined by their governing rules and regulations. (As noted above, ElectionGuard expects properly-formed ballots; e.g., it would error and fail to encrypt a ballot in an `open-primary-closed` election if a contest with an incorrect party affiliation were submitted (as indicated by the ).)\n\n## Ballot Styles and Geography\n\nAt least in the United States, many complications are introduced by voting simultaneously on election contests that apply in specific geographies and  jurisdictions. For example, a single election could include contests for congress, state assembly, school, and utility districts, each with their own geographic boundaries, many that do not respect town or county lines.  The ElectionGuard Election Manifest data format is flexible to accommodate most situations, but it is usually up to the election commission and the external system to determine what each component of the manifest actually means.\n\nIn the following examples, we will work through the process of defining different election types at a high level and describe the process of building the election manifest.\n\n### Geographic and Ballot Style Breakdown\n\nEach election can be thought of as a list of contests that are relevant to a certain group of people in a specific place. \n In order to determine who is supposed to vote on which contests, we first need to define the geographic jurisdictions where the election is taking place.  [The NIST Guidelines](<https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.1500-100r2.pdf>) present an excellent discussion of the geographic interplay of different contests. The diagram from page 12 is presented below.\n\n![](https://res.cloudinary.com/electionguard/image/upload/v1586960923/nist-election-model-uml.png)\n\nAs the diagram shows, congressional,  state assembly, school district and other geographic boundaries project onto towns and municipalities in different ways. Elections manage this complexity by creating unique ballot _styles_ that present to voters only the contests that pertain to them. Different jurisdictions use terms such as wards, precincts, and districts to describe the areas of overlap that guide ballot style creation. We will use `precinct` but `ward` and `district` could be used instead.\n\n### Contests, Candidates and Parties\n\nIn most cases, a resident of a specific _precinct_ or location will expect to see a certain list of contests that are relevant to them.  A contest is a specific collection of available choices (_selections_) from which the voter may choose some subset.  For the ElectionGuard Election Manifest, each possible selection in a contest must be associated with a candidate, even for Referendum-style contests.  If a contest also supports write-in values, then a write-in candidate is also defined.  Candidates may also be associated with specific parties, but this is not required for all election types.\n\n## Introducing Hamilton County, OZ\n\nTo help disambiguate, let's explore an example.\n\n### Geographic Jurisdictions\n\nHamilton County includes 3 townships: LaCroix, Arlington, Harris.  The town of LaCroix also has a utility district that comprises its own precinct for special referendums. Arlington has two distinct school districts.  The county is also split into two congressional districts, district 5 and district 7.  Harris township is entirely within Congressional District 5, but both LaCroix and Arlington are split between congressional districts 5 and 7.\n\n![Hamilton County Electoral Map](https://res.cloudinary.com/electionguard/image/upload/v1593617785/hamilton-county-district-map_xxki0z.png)\n\n#### Building the Geographic Jurisdiction Mapping (Geopolitical Units)\n\nThe Election Manifest includes an array of objects called `geopoliticalUnits` (a.k.a. _gpUnit_).  Each _Geopolitical Unit_ must include the following fields:\n- **objectId** - a unique identifier for the gpUnit.  This value is used to map a contest to a specific jurisdiction\n- **name** - the friendly name of the gp Unit\n- **type** - they _type_ of jurisdiction (one of the [Reporting Unit Types](https://developers.google.com/elections-data/reference/reporting-unit-type))\n- **contact information** - the contact info for the geopolitical unit\n\nGeopolitical units are polygons on a map represented by legal jurisdictions.  In our example Election Manifest for hamilton County, there is one geopolitical unit for each jurisdictional boundary in the image above:\n- Hamilton County\n- Congressional District 5\n- congressional District 7\n- LaCroix Township\n- Exeter Utility District (within LaCroix Township)\n- Harris Township\n- Arlington Township\n- Pismo Beach School District (within Arlington Township)\n- Somerset School District (within Arlington Township)\n\nWhen defining the geopolitical units for an election, we define all of the possible geopolitical units for an election; even if there are no contests for a specific jurisdiction.  This way, if contests are added or removed during the setup phase, you do not also have to remember to update the list of geopolitical units.  Alternatively, you can define only the GP Units for which there are contests.\n\n### The General Election Contests\n\nA **general election** will occur in Hamilton County.  The county is voting along with the rest of the province, and the county is responsible for tabulating its own election results.  This means that the _Election Scope_ is defined at the county level.\n\nFor the `general` election, the following sets of contests (and associated geographic boundaries) obtain:\n\n1. **The National Contests** - President and Vice President.  This contest demonstrates a \"vote for the ticket\" and allows write-ins\n2. **Province Contests** - Governor - this contest demonstrates a long list of candidate names\n3. **Congressional Contests** - Congress Districts 5 and 7 - these contests demonstrate how to split a district using multiple ballot styles\n4. **Township Contests** - Retain Chief Justice - This contest demonstrates a contest that applies to a specific town whose boundaries are split across multiple ballot styles\n4. **School District Contests** - School Board - these contests demonstrate contests with multiple selections (_n-of-m_) and allow write-ins\nSchool Board, and Utility district referendum to show ballot style splits\n5. **Utility District Contest** - Utility District - This contest demonstrates a referendum-style contest with long descriptions and display language translation into Spanish\n\nEach contest must be associated with exactly **ONE** `electoralDistrictId`.  The `electoralDistrictId` field on the contest is populated with the `objectId` of the associated _Geopolitical Unit_ (e.g. the Contest `congress-district-7-contest` has the `electoralDistrictId` `congress-district-7`\n\nEach contest must also define a `sequenceOrder`.  the _sequence order_ is an indexing mechanism used internally.  _It is not the sequence that the contests are displayed to the user_.  The order in which contests are displayed to the user is up to the implementing application.\n\n### The General Election Ballot Styles\n\nA ballot style is the set of contests that a specific voter should see on their ballot for a given location.  The ballot style is associated to the set of geopolitical units relevant to a specific point on a map.  Since each contest is also associated with a geopolitical unit, a mapping is created between a point on a map and the contests that are relevant to that point.\n\nFor instance, a voter that lives in the _Exeter Utility District_ should see contests that are relevant to Congressional District 7, LaCroix Township and the Exeter Utility District.  \n\n| Geopolitical Units are overlapping polygons, and ballot styles are the list of polygons relevant to a specific point on the map.\n\nSimilar to Geopolitical Units, we define all of the possible ballot styles for an election in our example, even if there are no contests specific to a ballot style.  This is subjective and the behavior may be different for the integrating system:\n- Congressional District 7 Outside Any Township\n- Congressional District 7 LaCroix Township\n- Congressional District 7 LaCroix Township Exeter Utility District\n- Congressional District 7 Arlington Township\n- Congressional District 7 Arlington Township Pismo Beach School district\n- Congressional District 7 Arlington Township Somerset School district\n- Congressional District 5 Outside Any Township\n- Congressional District 5 LaCroix Township\n- Congressional District 5 Harris Township\n- Congressional District 5 Arlington Township Pismo Beach School district\n- Congressional District 5 Arlington Township Somerset School district\n\nBy defining all of the possible ballot styles and all of the possible geopolitical units, we ensure that if a contest is added or removed, we only have to make sure the contest is correct.  We do not have to modify the list of geopolitical units or ballot styles.\n\n## Data Flexibility\n\nThe relationship between a ballot style and the contests that are displayed on it are subjective to the implementing application.  This example is just one way to define this relationship that is purposefully verbose.  For instance, in our example we define a geopolitical unit as a set of overlapping polygons, and a ballot style as the intersection of those polygons at a specific point.  This is a top-down approach.  Alternatively, we could have defined a geopolitical unit as the intersection area of those polygons and mapped one ballot style to each geopolitical unit 1 to 1.\n\nfor instance, instead of defining a single GP Unit each for:\n- Congressional District 5, \n- Congressional District 7, \n- LaCroix Township,\n- Exeter Utility district, etc; \n\nwe could have instead defined the GP Units as:\n- Congressional District 5 No Township\n- Congressional District 7 No Township\n- Congressional District 5 inside LaCroix\n- Congressional District 5 Inside LaCroix and Exeter, etc.\n\nThen, instead of each Ballot Style having multiple GP Units, each ballot style would have applied to exactly one GP Unit.\n\n### Data Validation\n\nWhen the election Manifest is loaded into ElectionGuard, its validity is checked semantically against the data format required to conduct an ElectionGuard Election. Specifically, we check that:\n- Each Geopolitical Unit has a unique objectId\n- Each Ballot Style maps to at least one valid Geopolitical Unit\n- Each Party has a unique objectId\n- Each Candidate either does not have a party, or is associated with a valid party\n- Each Contest has a unique Sequence Order\n- Each contest is associated with exactly one valid geopolitical unit\n- Each contest has a valid number of selections for the number of seats in the contest\n- Each selection on each contest is associated with a valid Candidate\n\nas long as the election manifest format matches the validation criteria, the election can proceed as an ElectionGuard election.\n\n## Frequently Asked Questions\n\nQ: What if my ballot styles are not associated with geopolitical units?\nA: There are a few ways to handle this.  In most cases, you can simply map the ballot style 1 to 1 to the geopolitical unit.  for instance, if `ballot-style-1` includes `contest-1` then you may create `geopolitical-unit-1` and associate both the ballot style and the contest to that geopolitical unit.\n\nThis documentation is under review and subject to change.  Please do not hesitate to open a github issue if you have questions, or find errors or omissions.\n"
  },
  {
    "path": "docs/Project_Workflow.md",
    "content": "# Project Workflow\n\n## ✨ Start an Iteration\nEach iteration on this repository will be tracked by a GitHub **[Milestone](https://help.github.com/en/github/managing-your-work-on-github/about-milestones)**. The completion of the milestone will queue a GitHub **[Release](https://help.github.com/en/github/administering-a-repository/managing-releases-in-a-repository)**. Issues will be added to the milestones to indicate work needed. After completion, this milestone can then act as a list of work contained within a release. \n\n## 🔀 Pull Request\n\n### Attach Issue\nEach pull request **MUST** be attached to an issue. On the surface, this ensures that closing a pull request will close an issue. This also ensures the issue can be included in the milestone. For this repository, the use of issues assists the team to use the [project board]() to track the progress towards a milestone.  \n\n### Validation\nEach pull request is validated by the [Pull Request Validation](https://github.com/microsoft/electionguard-python/blob/main/.github/workflows/pull_request.yml) GitHub [Action](https://help.github.com/en/actions). This action can be viewed from the PR or from the actions to inspect the details. \n\n### Review\nAll pull requests require a review from a **Contributor** but any reviewers are welcome.\n\n## 🏁 Create a Release\nAt the end of an iteration aka when a milestone is complete, a release can be created. \n\n### Steps\n1. Raise version number in `setup.py`\n2. Close Milestone \n3. Edit Release details _(optional)_\n\n### Release Workflow\n\nClosing the milestone queues the [Release Build](https://github.com/microsoft/electionguard-python/blob/main/.github/workflows/release.yml) GitHub [Action](https://help.github.com/en/actions). This action is designed to reduce the effort by maintainers and give the community an open view of the package flow.\n\n- Build package\n- Create dependency graph\n- Upload package to PyPi\n- Validate PyPi package\n- Upload package and graph to GitHub Workflow\n- Create Release\n- Upload zipped package and graph to Release\n- Update GitHub Pages Documentation"
  },
  {
    "path": "docs/Tablet Setup.md",
    "content": "# Setup Surface Go 3 Tablet\r\n\r\nThis documentation is a reference on how to setup a brand new computer (in this case a Surface Go 3) to use the Python repository to run the Admin and Guardian GUIs for an election. Some steps, such as \"Turn Off S Mode\" might not apply to the computer you are setting up. All usernames and passwords are set to xxx in this document and should be set to valid values for your own purpose.\r\n\r\n## Setup Windows\r\n* When prompted for email address use\r\n    * xxxx@xxxx.com\r\n    * xxxx\r\n* skip protecting account\r\n* skip hello setup\r\n* pin xxxx\r\n* default privacy\r\n* skip customization\r\n* decline office 365\r\n* installs Win11 (30 min or so)\r\n\r\n## Turn Off S Mode\r\n* settings -> system -> activation\r\n* opens windows store\r\n* hit Get button and wait for it to complete\r\n\r\n## Windows Updates\r\n* Do all the updates (a lot of updates)\r\n* Change to best performance mode\r\n    * Settings -> System -> Power and Battery\r\n    * Power Mode -> Best Performance\r\n\r\n## Install Hyper-V and Linux (admin only)\r\n* Go to Settings\r\n* Search for features\r\n* Go to optional features\r\n* More Windows Features\r\n* Install WSL, Virtual Machine Platform and Windows Hypervisor Platform settings\r\n* Restart windows\r\n* Go to windows store and install ubuntu\r\n* When running it the first time it will give a link to install a new kernel for WSL2\r\n* Run ubuntu and setup a user\r\n    * Username: xxxx\r\n    * Password: xxxx\r\n\r\n## Docker Installation (admin only)\r\n* Get docker desktop and install.\r\n* In the settings (gear icon) make the following changes:\r\n    * Make sure that \"Start Docker Desktop when you log in\" is on\r\n    * Make sure that \"Use the WSL 2 based engine\" is on\r\n\r\n## Developer Tools\r\n* Command prompt -> python3 (install from windows store)\r\n* Install chrome (does not need to be set to current browser)\r\n* Install VS Code\r\n* Git\r\n    * https://git-scm.com/download/win\r\n* Install chocolatey using powershell command from https://chocolatey.org/install\r\n* Install make\r\n    * choco install make\t\r\n* Install poetry (powershell) https://python-poetry.org/docs\r\n    * Add to path (See \"Set Environment Variables\" below on steps to get to the path)\r\n\r\n## Download Python Source Code\r\n* Open a Command prompt and use the following commands\r\n    * mkdir code\r\n    * cd code\r\n    * git clone https://github.com/microsoft/electionguard-python\r\n\r\n\r\n## Terminal Settings\r\n* Open up Terminal\r\n    * Go to settings in Terminal\r\n    * Default Profile => Command Prompt\r\n    * Profiles (left) -> Defaults \r\n        * Run this profile as Admin -> on\r\n        * Starting Starting directory to be directory where source code is downloaded\r\n    * Hit \"Save\" button at the bottom of the window\r\n\r\n## User Interface Changes\r\n* Change touch keyboard to traditional instead of default\r\n* Go into resize (using the gear icon) and set the zoom to 200 (max value)\r\n\r\n## Set Environment Variables\r\n* Go to Settings\r\n* Search for environment\r\n* Select \"Edit the system environment variables\"\r\n* Select the button \"Environment Variables\"\r\n* Select \"New…\"\r\n* Create the following settings\r\n    * EG_DB_PASSWORD = xxxx\r\n    * EG_DB_HOST = 10.10.0.100\r\n    * EG_IS_ADMIN = true for an admin and false if guardian\r\n    * admin only - EG_DB_DIR = ./database \r\n\r\n## Set Python Code\r\n* Open Terminal and run the following commands\r\n    * make environment\r\n        * There will be an error at the end.  This is normal\r\n    * poetry run eg \r\n        * should show the help for the eg command\r\n\r\n"
  },
  {
    "path": "docs/index.md",
    "content": "![Microsoft Defending Democracy Program: ElectionGuard Python](https://github.com/microsoft/electionguard-python/blob/main/images/electionguard-banner.svg?raw=true)\n\n# 🗳 ElectionGuard Python\n\n[![ElectionGuard Specification 0.95.0](https://img.shields.io/badge/🗳%20ElectionGuard%20Specification-0.95.0-green)](https://www.electionguard.vote) ![Github Package Action](https://github.com/microsoft/electionguard-python/workflows/Release%20Build/badge.svg) [![](https://img.shields.io/pypi/v/electionguard)](https://pypi.org/project/electionguard/) [![](https://img.shields.io/pypi/dm/electionguard)](https://pypi.org/project/electionguard/) [![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/microsoft/electionguard-python.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/microsoft/electionguard-python/context:python) [![Total alerts](https://img.shields.io/lgtm/alerts/g/microsoft/electionguard-python.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/microsoft/electionguard-python/alerts/) [![Documentation Status](https://readthedocs.org/projects/electionguard-python/badge/?version=latest)](https://electionguard-python.readthedocs.io) [![license](https://img.shields.io/github/license/microsoft/electionguard)](https://github.com/microsoft/electionguard-python/blob/main/LICENSE)\n\nThis repository is a \"reference implementation\" of ElectionGuard written in Python 3. This implementation can be used to conduct End-to-End Verifiable Elections as well as privacy-enhanced risk-limiting audits. Components of this library can also be used to construct \"Verifiers\" to validate the results of an ElectionGuard election.\n\n## 📁 In This Repository\n\n| File/folder                 | Description                                   |\n| --------------------------- | --------------------------------------------- |\n| docs                        | Documentation for using the library.          |\n| src/electionguard           | ElectionGuard library.                        |\n| src/electionguard_tools     | Tools for testing and sample data.            |\n| src/electionguard_verifier  | Verifier to validate the validity of a ballot.|\n| stubs                       | Type annotations for external libraries.      |\n| tests                       | Tests to exercise this codebase.              |\n| CONTRIBUTING.md             | Guidelines for contributing.                  |\n| README.md                   | This README file.                             |\n| LICENSE                     | The license for ElectionGuard-Python.         |\n| data                        | Sample election data.                         |\n\n<br/>\n\n## ❓ What Is ElectionGuard?\n\nElectionGuard is an open source software development kit (SDK) that makes voting more secure, transparent and accessible. The ElectionGuard SDK leverages homomorphic encryption to ensure that votes recorded by electronic systems of any type remain encrypted, secure, and secret. Meanwhile, ElectionGuard also allows verifiable and accurate tallying of ballots by any 3rd party organization without compromising secrecy or security.\n\nLearn More in the [ElectionGuard Repository](https://github.com/microsoft/electionguard)\n\n## 🦸 How Can I use ElectionGuard?\n\nElectionGuard supports a variety of use cases. The Primary use case is to generate verifiable end-to-end (E2E) encrypted elections. The ElectionGuard process can also be used for other use cases such as privacy enhanced risk-limiting audits (RLAs).\n\n## 💻 Requirements\n\n- [Python 3.9+](https://www.python.org/downloads/) is <ins>**required**</ins> to develop this SDK. If developer uses multiple versions of python, [pyenv](https://github.com/pyenv/pyenv) is suggested to assist version management.\n- [GNU Make](https://www.gnu.org/software/make/manual/make.html) is used to simplify the commands and GitHub Actions. This approach is recommended to simplify the command line experience. This is built in for MacOS and Linux. For Windows, setup is simpler with [Chocolatey](https://chocolatey.org/install) and installing the provided [make package](https://chocolatey.org/packages/make). The other Windows option is [manually installing make](http://gnuwin32.sourceforge.net/packages/make.htm).\n- [Gmpy2](https://gmpy2.readthedocs.io/en/latest/) is used for [Arbitrary-precision arithmetic](https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic) which\n  has its own [installation requirements (native C libraries)](https://gmpy2.readthedocs.io/en/latest/intro.html#installation) on Linux and MacOS. **⚠️ Note:** _This is not required for Windows since the gmpy2 precompiled libraries are provided._\n- [poetry 2.2.1](https://python-poetry.org/) is used to configure the python environment. Installation instructions can be found [here](https://python-poetry.org/docs/#installation).\n\n## 🚀 Quick Start\n\nUsing [**make**](https://www.gnu.org/software/make/manual/make.html), the entire [GitHub Action workflow][pull request workflow] can be run with one command:\n\n```\nmake\n```\n\nThe unit and integration tests can also be run with make:\n\n```\nmake test\n```\n\nA complete end-to-end election example can be run independently by executing:\n\n```\nmake test-example\n```\n\nFor more detailed build and run options, see the [documentation](Build_and_Run.md).\n\n## 📄 Documentation\n\nSections:\n\n- [Design and Architecture](Design_and_Architecture.md)\n- [Build and Run](Build_and_Run.md)\n- [Project Workflow](Project_Workflow.md)\n- [Election Manifest](Election_Manifest.md)\n\nStep-by-Step Process:\n\n0. [Configure Election](0_Configure_Election.ipynb)\n1. [Key Ceremony](1_Key_Ceremony.md)\n2. [Encrypt Ballots](2_Encrypt_Ballots.md)\n3. [Cast and Spoil](3_Cast_and_Spoil.md)\n4. [Decrypt Tally](4_Decrypt_Tally.md)\n5. [Publish and Verify](5_Publish_and_Verify.md)\n\n## ❓Questions\n\nElectionguard would love for you to ask questions out in the open using GitHub Issues. If you really want to email the ElectionGuard team, reach out at electionguard@microsoft.com.\n"
  },
  {
    "path": "mkdocs.yml",
    "content": "site_name: ElectionGuard Python\nsite_url: https://electionguard-python.readthedocs.io/\n# site_description:\nsite_author: Microsoft\n# google_analytics:\n# remote_branch: for gh-deploy to GithubPages\n# remote_name: for gh-deploy to Github Pages\ncopyright: \"© Microsoft 2020\"\ndocs_dir: \"docs\"\nrepo_url: https://github.com/microsoft/electionguard-python/\nnav:\n  - Home: index.md\n  - Design and Architecture: Design_and_Architecture.md\n  - Build and Run: Build_and_Run.md\n  - Project Workflow: Project_Workflow.md\n  - Election Manifest: Election_Manifest.md\n  - Steps:\n      - 0. Configure Election: 0_Configure_Election.ipynb\n      - 1. Key Ceremony: 1_Key_Ceremony.md\n      - 2. Encrypt Ballots: 2_Encrypt_Ballots.md\n      - 3. Cast and Spoil: 3_Cast_and_Spoil.md\n      - 4. Decrypt Tally: 4_Decrypt_Tally.md\n      - 5. Publish and Verify: 5_Publish_and_Verify.md\ntheme: readthedocs\nplugins:\n  - mkdocs-jupyter\nmarkdown_extensions:\n- admonition\n- pymdownx.details\n- pymdownx.superfences\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[project]\nname = \"electionguard\"\nversion = \"1.4.0\"\nrequires-python = \">=3.9.5,<4.0.0\"\ndescription = \"ElectionGuard: Support for e2e verified elections.\"\nlicense = \"MIT\"\nauthors = [{\"name\" = \"Microsoft\", \"email\" = \"electionguard@microsoft.com\"}]\nmaintainers = []\nreadme = \"README.md\"\nkeywords = []\nclassifiers = [\n    \"Development Status :: 5 - Production/Stable\",\n    \"Intended Audience :: Developers\",\n    \"Operating System :: Unix\",\n    \"Operating System :: POSIX\",\n    \"Operating System :: MacOS\",\n    \"Operating System :: Microsoft :: Windows\",\n    \"Programming Language :: Python\",\n    \"Topic :: Utilities\"\n]\ndependencies = [\n  \"gmpy2>=2.0.8,<3.0.0\",\n  \"psutil>=5.7.2\",\n  \"pydantic==2.13.0\",\n  \"click>=8.1.0,<9.0.0\",\n  \"dacite>=1.6.0,<2.0.0\",\n  \"python-dateutil>=2.8.2,<3.0.0\",\n  \"types-python-dateutil>=2.8.14,<3.0.0\",\n  \"Eel[jinja2]>=0.14.0,<1.0.0\",\n  \"pymongo>=4.1.1,<5.0.0\",\n  \"dependency-injector>=4.39.1,<5.0.0\",\n  \"pytest-mock>=3.8.2,<4.0.0\",\n]\n\n[project.urls]\nhomepage = \"https://microsoft.github.io/electionguard-python\"\nrepository = \"https://github.com/microsoft/electionguard-python\"\ndocumentation = \"https://microsoft.github.io/electionguard-python\"\n\"GitHub Pages\" = \"https://microsoft.github.io/electionguard-python\"\n\"Read the Docs\" = \"https://electionguard-python.readthedocs.io\"\n\"Releases\" = \"https://github.com/microsoft/electionguard-python/releases\"\n\"Milestones\" = \"https://github.com/microsoft/electionguard-python/milestones\"\n\"Issue Tracker\" = \"https://github.com/microsoft/electionguard-python/issues\"\n\n[tool.poetry]\npackages = [\n  { include = \"electionguard\", from = \"src\" },\n  { include = \"electionguard_tools\", from = \"src\" },\n  { include = \"electionguard_cli\", from = \"src\" },\n  { include = \"electionguard_gui\", from = \"src\" },\n]\n\n[tool.poetry.group.dev.dependencies]\natomicwrites = \"*\"\nblack = \"25.11.0\"\ncoverage = \"*\"\ndocutils = \"*\"\nhypothesis = \">=5.15.1\"\nipython = \"^7.31.1\"\nipykernel = \"^6.4.1\"\njeepney = \"*\"\njupyter-black = \"^0.3.1\"\nmkdocs = \"^1.6.1\"\nmkdocs-jupyter = \"^0.26.2\"\nmkinit = \"^0.3.3\"\nmypy = \"1.19.1\"\npydeps = \"*\"\npylint = \"*\"\npytest = \"*\"\nsecretstorage = \"*\"\ntwine = \"*\"\ntypish = '*'\n\n[project.scripts]\neg = 'electionguard_cli.start:cli'\negui = 'electionguard_gui.start:run'\n\n[tool.black]\ntarget-version = ['py39']\n\n[tool.pylint.basic]\nextension-pkg-whitelist = \"pydantic\"\n\n[tool.pylint.format]\nmax-line-length = 120\n\n# FIXME: Pylint should not require this many exceptions\n[tool.pylint.messages_control]\ndisable = '''\n  duplicate-code,\n  fixme,\n  invalid-name,\n  missing-module-docstring,\n  missing-function-docstring,\n  no-value-for-parameter,\n  redefined-builtin,\n  broad-exception-raised,\n  too-few-public-methods,\n  too-many-arguments,\n  too-many-branches,\n  too-many-function-args,\n  too-many-lines,\n  too-many-locals,\n  too-many-nested-blocks,\n  too-many-positional-arguments,\n  unnecessary-lambda,\n  '''\n\n[tool.coverage.run]\nbranch = true\nsource = [\"src/electionguard\"]\n\n[tool.coverage.html]\ndirectory = \"coverage_html_report\"\n\n[build-system]\nrequires = [\"poetry-core>=2.0.0\"]\nbuild-backend = \"poetry.core.masonry.api\"\n\n[tool.mypy]\npython_version = \"3.9\"\nwarn_return_any = true\nwarn_unused_configs = true\ndisallow_untyped_defs = true\nignore_missing_imports = true\nshow_column_numbers = true\n"
  },
  {
    "path": "src/electionguard/__init__.py",
    "content": "import importlib.metadata\n\n# <AUTOGEN_INIT>\nfrom electionguard import ballot\nfrom electionguard import ballot_box\nfrom electionguard import ballot_code\nfrom electionguard import ballot_compact\nfrom electionguard import ballot_validator\nfrom electionguard import big_integer\nfrom electionguard import byte_padding\nfrom electionguard import chaum_pedersen\nfrom electionguard import constants\nfrom electionguard import data_store\nfrom electionguard import decrypt_with_secrets\nfrom electionguard import decrypt_with_shares\nfrom electionguard import decryption\nfrom electionguard import decryption_mediator\nfrom electionguard import decryption_share\nfrom electionguard import discrete_log\nfrom electionguard import election\nfrom electionguard import election_object_base\nfrom electionguard import election_polynomial\nfrom electionguard import elgamal\nfrom electionguard import encrypt\nfrom electionguard import group\nfrom electionguard import guardian\nfrom electionguard import hash\nfrom electionguard import hmac\nfrom electionguard import key_ceremony\nfrom electionguard import key_ceremony_mediator\nfrom electionguard import logs\nfrom electionguard import manifest\nfrom electionguard import nonces\nfrom electionguard import proof\nfrom electionguard import scheduler\nfrom electionguard import schnorr\nfrom electionguard import serialize\nfrom electionguard import singleton\nfrom electionguard import tally\nfrom electionguard import type\nfrom electionguard import utils\n\nfrom electionguard.ballot import (\n    BallotBoxState,\n    CiphertextBallot,\n    CiphertextBallotContest,\n    CiphertextBallotSelection,\n    CiphertextContest,\n    CiphertextSelection,\n    PlaintextBallot,\n    PlaintextBallotContest,\n    PlaintextBallotSelection,\n    SubmittedBallot,\n    create_ballot_hash,\n    make_ciphertext_ballot,\n    make_ciphertext_ballot_contest,\n    make_ciphertext_ballot_selection,\n    make_ciphertext_submitted_ballot,\n)\nfrom electionguard.ballot_box import (\n    BallotBox,\n    cast_ballot,\n    get_ballots,\n    spoil_ballot,\n    submit_ballot,\n    submit_ballot_to_box,\n)\nfrom electionguard.ballot_code import (\n    get_ballot_code,\n    get_hash_for_device,\n)\nfrom electionguard.ballot_compact import (\n    CompactPlaintextBallot,\n    CompactSubmittedBallot,\n    NO_VOTE,\n    YES_VOTE,\n    compress_plaintext_ballot,\n    compress_submitted_ballot,\n    expand_compact_plaintext_ballot,\n    expand_compact_submitted_ballot,\n)\nfrom electionguard.ballot_validator import (\n    ballot_is_valid_for_election,\n    ballot_is_valid_for_style,\n    contest_is_valid_for_style,\n    selection_is_valid_for_style,\n)\nfrom electionguard.big_integer import (\n    BigInteger,\n    bytes_to_hex,\n)\nfrom electionguard.byte_padding import (\n    DataSize,\n    TruncationError,\n    add_padding,\n    remove_padding,\n    to_padded_bytes,\n)\nfrom electionguard.chaum_pedersen import (\n    ChaumPedersenProof,\n    ConstantChaumPedersenProof,\n    DisjunctiveChaumPedersenProof,\n    make_chaum_pedersen,\n    make_constant_chaum_pedersen,\n    make_disjunctive_chaum_pedersen,\n    make_disjunctive_chaum_pedersen_one,\n    make_disjunctive_chaum_pedersen_zero,\n)\nfrom electionguard.constants import (\n    EXTRA_SMALL_TEST_CONSTANTS,\n    ElectionConstants,\n    LARGE_TEST_CONSTANTS,\n    MEDIUM_TEST_CONSTANTS,\n    PrimeOption,\n    SMALL_TEST_CONSTANTS,\n    STANDARD_CONSTANTS,\n    create_constants,\n    get_cofactor,\n    get_constants,\n    get_generator,\n    get_large_prime,\n    get_small_prime,\n)\nfrom electionguard.data_store import (\n    DataStore,\n    ReadOnlyDataStore,\n)\nfrom electionguard.decrypt_with_secrets import (\n    decrypt_ballot_with_nonce,\n    decrypt_ballot_with_secret,\n    decrypt_contest_with_nonce,\n    decrypt_contest_with_secret,\n    decrypt_selection_with_nonce,\n    decrypt_selection_with_secret,\n)\nfrom electionguard.decrypt_with_shares import (\n    decrypt_ballot,\n    decrypt_contest_with_decryption_shares,\n    decrypt_selection_with_decryption_shares,\n    decrypt_tally,\n)\nfrom electionguard.decryption import (\n    RecoveryPublicKey,\n    compute_compensated_decryption_share,\n    compute_compensated_decryption_share_for_ballot,\n    compute_compensated_decryption_share_for_contest,\n    compute_compensated_decryption_share_for_selection,\n    compute_decryption_share,\n    compute_decryption_share_for_ballot,\n    compute_decryption_share_for_contest,\n    compute_decryption_share_for_selection,\n    compute_lagrange_coefficients_for_guardian,\n    compute_lagrange_coefficients_for_guardians,\n    compute_recovery_public_key,\n    decrypt_backup,\n    decrypt_with_threshold,\n    partially_decrypt,\n    reconstruct_decryption_contest,\n    reconstruct_decryption_share,\n    reconstruct_decryption_share_for_ballot,\n)\nfrom electionguard.decryption_mediator import (\n    DecryptionMediator,\n)\nfrom electionguard.decryption_share import (\n    CiphertextCompensatedDecryptionContest,\n    CiphertextCompensatedDecryptionSelection,\n    CiphertextDecryptionContest,\n    CiphertextDecryptionSelection,\n    CompensatedDecryptionShare,\n    DecryptionShare,\n    ProofOrRecovery,\n    create_ciphertext_decryption_selection,\n    get_shares_for_selection,\n)\nfrom electionguard.discrete_log import (\n    DiscreteLog,\n    DiscreteLogCache,\n    DiscreteLogExponentError,\n    DiscreteLogNotFoundError,\n    compute_discrete_log,\n    compute_discrete_log_async,\n    compute_discrete_log_cache,\n    precompute_discrete_log_cache,\n)\nfrom electionguard.election import (\n    CiphertextElectionContext,\n    Configuration,\n    make_ciphertext_election_context,\n)\nfrom electionguard.election_object_base import (\n    ElectionObjectBase,\n    OrderedObjectBase,\n    list_eq,\n    sequence_order_sort,\n)\nfrom electionguard.election_polynomial import (\n    Coefficient,\n    ElectionPolynomial,\n    LagrangeCoefficientsRecord,\n    PublicCommitment,\n    SecretCoefficient,\n    compute_lagrange_coefficient,\n    compute_polynomial_coordinate,\n    generate_polynomial,\n    verify_polynomial_coordinate,\n)\nfrom electionguard.elgamal import (\n    ElGamalCiphertext,\n    ElGamalKeyPair,\n    ElGamalPublicKey,\n    ElGamalSecretKey,\n    HashedElGamalCiphertext,\n    elgamal_add,\n    elgamal_combine_public_keys,\n    elgamal_encrypt,\n    elgamal_keypair_from_secret,\n    elgamal_keypair_random,\n    hashed_elgamal_encrypt,\n)\nfrom electionguard.encrypt import (\n    ContestData,\n    EncryptionDevice,\n    EncryptionMediator,\n    contest_from,\n    encrypt_ballot,\n    encrypt_ballot_contests,\n    encrypt_contest,\n    encrypt_selection,\n    generate_device_uuid,\n    selection_from,\n)\nfrom electionguard.group import (\n    BaseElement,\n    ElementModP,\n    ElementModPOrQ,\n    ElementModPOrQorInt,\n    ElementModPorInt,\n    ElementModQ,\n    ElementModQorInt,\n    a_minus_b_q,\n    a_plus_bc_q,\n    add_q,\n    div_p,\n    div_q,\n    g_pow_p,\n    hex_to_p,\n    hex_to_q,\n    int_to_p,\n    int_to_q,\n    mult_inv_p,\n    mult_p,\n    mult_q,\n    negate_q,\n    pow_p,\n    pow_q,\n    rand_q,\n    rand_range_q,\n)\nfrom electionguard.guardian import (\n    Guardian,\n    GuardianRecord,\n    PrivateGuardianRecord,\n    get_valid_ballot_shares,\n    publish_guardian_record,\n)\nfrom electionguard.hash import (\n    CryptoHashCheckable,\n    CryptoHashable,\n    CryptoHashableAll,\n    CryptoHashableT,\n    hash_elems,\n)\nfrom electionguard.hmac import (\n    get_hmac,\n)\nfrom electionguard.key_ceremony import (\n    CeremonyDetails,\n    CoordinateData,\n    ElectionJointKey,\n    ElectionKeyPair,\n    ElectionPartialKeyBackup,\n    ElectionPartialKeyChallenge,\n    ElectionPartialKeyVerification,\n    ElectionPublicKey,\n    combine_election_public_keys,\n    generate_election_key_pair,\n    generate_election_partial_key_backup,\n    generate_election_partial_key_challenge,\n    get_backup_seed,\n    verify_election_partial_key_backup,\n    verify_election_partial_key_challenge,\n)\nfrom electionguard.key_ceremony_mediator import (\n    BackupVerificationState,\n    GuardianPair,\n    KeyCeremonyMediator,\n)\nfrom electionguard.logs import (\n    ElectionGuardLog,\n    FORMAT,\n    LOG,\n    get_file_handler,\n    get_stream_handler,\n    log_add_handler,\n    log_critical,\n    log_debug,\n    log_error,\n    log_handlers,\n    log_info,\n    log_remove_handler,\n    log_warning,\n)\nfrom electionguard.manifest import (\n    AnnotatedString,\n    BallotStyle,\n    Candidate,\n    CandidateContestDescription,\n    ContactInformation,\n    ContestDescription,\n    ContestDescriptionWithPlaceholders,\n    ElectionType,\n    GeopoliticalUnit,\n    InternalManifest,\n    InternationalizedText,\n    Language,\n    Manifest,\n    Party,\n    ReferendumContestDescription,\n    ReportingUnitType,\n    SUPPORTED_VOTE_VARIATIONS,\n    SelectionDescription,\n    SpecVersion,\n    VoteVariationType,\n    contest_description_with_placeholders_from,\n    generate_placeholder_selection_from,\n    generate_placeholder_selections_from,\n    get_i8n_value,\n)\nfrom electionguard.nonces import (\n    Nonces,\n)\nfrom electionguard.proof import (\n    Proof,\n    ProofUsage,\n)\nfrom electionguard.scheduler import (\n    Scheduler,\n)\nfrom electionguard.schnorr import (\n    SchnorrProof,\n    make_schnorr_proof,\n)\nfrom electionguard.serialize import (\n    construct_path,\n    from_file,\n    from_file_wrapper,\n    from_list_in_file,\n    from_list_in_file_wrapper,\n    from_list_raw,\n    from_raw,\n    get_schema,\n    padded_decode,\n    padded_encode,\n    to_file,\n    to_raw,\n)\nfrom electionguard.singleton import (\n    Singleton,\n)\nfrom electionguard.tally import (\n    CiphertextTally,\n    CiphertextTallyContest,\n    CiphertextTallySelection,\n    PlaintextTally,\n    PlaintextTallyContest,\n    PlaintextTallySelection,\n    PublishedCiphertextTally,\n    tally_ballot,\n    tally_ballots,\n)\nfrom electionguard.type import (\n    BallotId,\n    ContestId,\n    GuardianId,\n    MediatorId,\n    SelectionId,\n    VerifierId,\n)\nfrom electionguard.utils import (\n    BYTE_ENCODING,\n    BYTE_ORDER,\n    ContestErrorType,\n    ContestException,\n    NullVoteException,\n    OverVoteException,\n    UnderVoteException,\n    flatmap_optional,\n    get_optional,\n    get_or_else_optional,\n    get_or_else_optional_func,\n    match_optional,\n    space_between_capitals,\n    to_hex_bytes,\n    to_iso_date_string,\n    to_ticks,\n)\n\n__all__ = [\n    \"AnnotatedString\",\n    \"BYTE_ENCODING\",\n    \"BYTE_ORDER\",\n    \"BackupVerificationState\",\n    \"BallotBox\",\n    \"BallotBoxState\",\n    \"BallotId\",\n    \"BallotStyle\",\n    \"BaseElement\",\n    \"BigInteger\",\n    \"Candidate\",\n    \"CandidateContestDescription\",\n    \"CeremonyDetails\",\n    \"ChaumPedersenProof\",\n    \"CiphertextBallot\",\n    \"CiphertextBallotContest\",\n    \"CiphertextBallotSelection\",\n    \"CiphertextCompensatedDecryptionContest\",\n    \"CiphertextCompensatedDecryptionSelection\",\n    \"CiphertextContest\",\n    \"CiphertextDecryptionContest\",\n    \"CiphertextDecryptionSelection\",\n    \"CiphertextElectionContext\",\n    \"CiphertextSelection\",\n    \"CiphertextTally\",\n    \"CiphertextTallyContest\",\n    \"CiphertextTallySelection\",\n    \"Coefficient\",\n    \"CompactPlaintextBallot\",\n    \"CompactSubmittedBallot\",\n    \"CompensatedDecryptionShare\",\n    \"Configuration\",\n    \"ConstantChaumPedersenProof\",\n    \"ContactInformation\",\n    \"ContestData\",\n    \"ContestDescription\",\n    \"ContestDescriptionWithPlaceholders\",\n    \"ContestErrorType\",\n    \"ContestException\",\n    \"ContestId\",\n    \"CoordinateData\",\n    \"CryptoHashCheckable\",\n    \"CryptoHashable\",\n    \"CryptoHashableAll\",\n    \"CryptoHashableT\",\n    \"DataSize\",\n    \"DataStore\",\n    \"DecryptionMediator\",\n    \"DecryptionShare\",\n    \"DiscreteLog\",\n    \"DiscreteLogCache\",\n    \"DiscreteLogExponentError\",\n    \"DiscreteLogNotFoundError\",\n    \"DisjunctiveChaumPedersenProof\",\n    \"EXTRA_SMALL_TEST_CONSTANTS\",\n    \"ElGamalCiphertext\",\n    \"ElGamalKeyPair\",\n    \"ElGamalPublicKey\",\n    \"ElGamalSecretKey\",\n    \"ElectionConstants\",\n    \"ElectionGuardLog\",\n    \"ElectionJointKey\",\n    \"ElectionKeyPair\",\n    \"ElectionObjectBase\",\n    \"ElectionPartialKeyBackup\",\n    \"ElectionPartialKeyChallenge\",\n    \"ElectionPartialKeyVerification\",\n    \"ElectionPolynomial\",\n    \"ElectionPublicKey\",\n    \"ElectionType\",\n    \"ElementModP\",\n    \"ElementModPOrQ\",\n    \"ElementModPOrQorInt\",\n    \"ElementModPorInt\",\n    \"ElementModQ\",\n    \"ElementModQorInt\",\n    \"EncryptionDevice\",\n    \"EncryptionMediator\",\n    \"FORMAT\",\n    \"GeopoliticalUnit\",\n    \"Guardian\",\n    \"GuardianId\",\n    \"GuardianPair\",\n    \"GuardianRecord\",\n    \"HashedElGamalCiphertext\",\n    \"InternalManifest\",\n    \"InternationalizedText\",\n    \"KeyCeremonyMediator\",\n    \"LARGE_TEST_CONSTANTS\",\n    \"LOG\",\n    \"LagrangeCoefficientsRecord\",\n    \"Language\",\n    \"MEDIUM_TEST_CONSTANTS\",\n    \"Manifest\",\n    \"MediatorId\",\n    \"NO_VOTE\",\n    \"Nonces\",\n    \"NullVoteException\",\n    \"OrderedObjectBase\",\n    \"OverVoteException\",\n    \"Party\",\n    \"PlaintextBallot\",\n    \"PlaintextBallotContest\",\n    \"PlaintextBallotSelection\",\n    \"PlaintextTally\",\n    \"PlaintextTallyContest\",\n    \"PlaintextTallySelection\",\n    \"PrimeOption\",\n    \"PrivateGuardianRecord\",\n    \"Proof\",\n    \"ProofOrRecovery\",\n    \"ProofUsage\",\n    \"PublicCommitment\",\n    \"PublishedCiphertextTally\",\n    \"ReadOnlyDataStore\",\n    \"RecoveryPublicKey\",\n    \"ReferendumContestDescription\",\n    \"ReportingUnitType\",\n    \"SMALL_TEST_CONSTANTS\",\n    \"STANDARD_CONSTANTS\",\n    \"SUPPORTED_VOTE_VARIATIONS\",\n    \"Scheduler\",\n    \"SchnorrProof\",\n    \"SecretCoefficient\",\n    \"SelectionDescription\",\n    \"SelectionId\",\n    \"Singleton\",\n    \"SpecVersion\",\n    \"SubmittedBallot\",\n    \"TruncationError\",\n    \"UnderVoteException\",\n    \"VerifierId\",\n    \"VoteVariationType\",\n    \"YES_VOTE\",\n    \"a_minus_b_q\",\n    \"a_plus_bc_q\",\n    \"add_padding\",\n    \"add_q\",\n    \"ballot\",\n    \"ballot_box\",\n    \"ballot_code\",\n    \"ballot_compact\",\n    \"ballot_is_valid_for_election\",\n    \"ballot_is_valid_for_style\",\n    \"ballot_validator\",\n    \"big_integer\",\n    \"byte_padding\",\n    \"bytes_to_hex\",\n    \"cast_ballot\",\n    \"chaum_pedersen\",\n    \"combine_election_public_keys\",\n    \"compress_plaintext_ballot\",\n    \"compress_submitted_ballot\",\n    \"compute_compensated_decryption_share\",\n    \"compute_compensated_decryption_share_for_ballot\",\n    \"compute_compensated_decryption_share_for_contest\",\n    \"compute_compensated_decryption_share_for_selection\",\n    \"compute_decryption_share\",\n    \"compute_decryption_share_for_ballot\",\n    \"compute_decryption_share_for_contest\",\n    \"compute_decryption_share_for_selection\",\n    \"compute_discrete_log\",\n    \"compute_discrete_log_async\",\n    \"compute_discrete_log_cache\",\n    \"compute_lagrange_coefficient\",\n    \"compute_lagrange_coefficients_for_guardian\",\n    \"compute_lagrange_coefficients_for_guardians\",\n    \"compute_polynomial_coordinate\",\n    \"compute_recovery_public_key\",\n    \"constants\",\n    \"construct_path\",\n    \"contest_description_with_placeholders_from\",\n    \"contest_from\",\n    \"contest_is_valid_for_style\",\n    \"create_ballot_hash\",\n    \"create_ciphertext_decryption_selection\",\n    \"create_constants\",\n    \"data_store\",\n    \"decrypt_backup\",\n    \"decrypt_ballot\",\n    \"decrypt_ballot_with_nonce\",\n    \"decrypt_ballot_with_secret\",\n    \"decrypt_contest_with_decryption_shares\",\n    \"decrypt_contest_with_nonce\",\n    \"decrypt_contest_with_secret\",\n    \"decrypt_selection_with_decryption_shares\",\n    \"decrypt_selection_with_nonce\",\n    \"decrypt_selection_with_secret\",\n    \"decrypt_tally\",\n    \"decrypt_with_secrets\",\n    \"decrypt_with_shares\",\n    \"decrypt_with_threshold\",\n    \"decryption\",\n    \"decryption_mediator\",\n    \"decryption_share\",\n    \"discrete_log\",\n    \"div_p\",\n    \"div_q\",\n    \"election\",\n    \"election_object_base\",\n    \"election_polynomial\",\n    \"elgamal\",\n    \"elgamal_add\",\n    \"elgamal_combine_public_keys\",\n    \"elgamal_encrypt\",\n    \"elgamal_keypair_from_secret\",\n    \"elgamal_keypair_random\",\n    \"encrypt\",\n    \"encrypt_ballot\",\n    \"encrypt_ballot_contests\",\n    \"encrypt_contest\",\n    \"encrypt_selection\",\n    \"expand_compact_plaintext_ballot\",\n    \"expand_compact_submitted_ballot\",\n    \"flatmap_optional\",\n    \"from_file\",\n    \"from_file_wrapper\",\n    \"from_list_in_file\",\n    \"from_list_in_file_wrapper\",\n    \"from_list_raw\",\n    \"from_raw\",\n    \"g_pow_p\",\n    \"generate_device_uuid\",\n    \"generate_election_key_pair\",\n    \"generate_election_partial_key_backup\",\n    \"generate_election_partial_key_challenge\",\n    \"generate_placeholder_selection_from\",\n    \"generate_placeholder_selections_from\",\n    \"generate_polynomial\",\n    \"get_backup_seed\",\n    \"get_ballot_code\",\n    \"get_ballots\",\n    \"get_cofactor\",\n    \"get_constants\",\n    \"get_file_handler\",\n    \"get_generator\",\n    \"get_hash_for_device\",\n    \"get_hmac\",\n    \"get_i8n_value\",\n    \"get_large_prime\",\n    \"get_optional\",\n    \"get_or_else_optional\",\n    \"get_or_else_optional_func\",\n    \"get_schema\",\n    \"get_shares_for_selection\",\n    \"get_small_prime\",\n    \"get_stream_handler\",\n    \"get_valid_ballot_shares\",\n    \"group\",\n    \"guardian\",\n    \"hash\",\n    \"hash_elems\",\n    \"hashed_elgamal_encrypt\",\n    \"hex_to_p\",\n    \"hex_to_q\",\n    \"hmac\",\n    \"int_to_p\",\n    \"int_to_q\",\n    \"key_ceremony\",\n    \"key_ceremony_mediator\",\n    \"list_eq\",\n    \"log_add_handler\",\n    \"log_critical\",\n    \"log_debug\",\n    \"log_error\",\n    \"log_handlers\",\n    \"log_info\",\n    \"log_remove_handler\",\n    \"log_warning\",\n    \"logs\",\n    \"make_chaum_pedersen\",\n    \"make_ciphertext_ballot\",\n    \"make_ciphertext_ballot_contest\",\n    \"make_ciphertext_ballot_selection\",\n    \"make_ciphertext_election_context\",\n    \"make_ciphertext_submitted_ballot\",\n    \"make_constant_chaum_pedersen\",\n    \"make_disjunctive_chaum_pedersen\",\n    \"make_disjunctive_chaum_pedersen_one\",\n    \"make_disjunctive_chaum_pedersen_zero\",\n    \"make_schnorr_proof\",\n    \"manifest\",\n    \"match_optional\",\n    \"mult_inv_p\",\n    \"mult_p\",\n    \"mult_q\",\n    \"negate_q\",\n    \"nonces\",\n    \"padded_decode\",\n    \"padded_encode\",\n    \"partially_decrypt\",\n    \"pow_p\",\n    \"pow_q\",\n    \"precompute_discrete_log_cache\",\n    \"proof\",\n    \"publish_guardian_record\",\n    \"rand_q\",\n    \"rand_range_q\",\n    \"reconstruct_decryption_contest\",\n    \"reconstruct_decryption_share\",\n    \"reconstruct_decryption_share_for_ballot\",\n    \"remove_padding\",\n    \"scheduler\",\n    \"schnorr\",\n    \"selection_from\",\n    \"selection_is_valid_for_style\",\n    \"sequence_order_sort\",\n    \"serialize\",\n    \"singleton\",\n    \"space_between_capitals\",\n    \"spoil_ballot\",\n    \"submit_ballot\",\n    \"submit_ballot_to_box\",\n    \"tally\",\n    \"tally_ballot\",\n    \"tally_ballots\",\n    \"to_file\",\n    \"to_hex_bytes\",\n    \"to_iso_date_string\",\n    \"to_padded_bytes\",\n    \"to_raw\",\n    \"to_ticks\",\n    \"type\",\n    \"utils\",\n    \"verify_election_partial_key_backup\",\n    \"verify_election_partial_key_challenge\",\n    \"verify_polynomial_coordinate\",\n]\n\n# </AUTOGEN_INIT>\n\n# single source version from pyproject.toml\ntry:\n    __version__ = importlib.metadata.version(__package__.split(\"_\", maxsplit=1)[0])\nexcept importlib.metadata.PackageNotFoundError:\n    __version__ = \"0.0.0\"\n"
  },
  {
    "path": "src/electionguard/ballot.py",
    "content": "from dataclasses import dataclass, field, replace\nfrom datetime import datetime, timezone\nfrom enum import Enum\nfrom functools import cached_property, reduce\nfrom typing import (\n    Any,\n    Dict,\n    List,\n    Iterable,\n    Optional,\n    Protocol,\n    runtime_checkable,\n)\n\n\nfrom .ballot_code import get_ballot_code\nfrom .chaum_pedersen import (\n    ConstantChaumPedersenProof,\n    DisjunctiveChaumPedersenProof,\n    make_constant_chaum_pedersen,\n    make_disjunctive_chaum_pedersen,\n)\nfrom .election_object_base import (\n    ElectionObjectBase,\n    OrderedObjectBase,\n    sequence_order_sort,\n    list_eq,\n)\nfrom .elgamal import (\n    ElGamalCiphertext,\n    ElGamalPublicKey,\n    HashedElGamalCiphertext,\n    elgamal_add,\n)\nfrom .group import add_q, ElementModQ, ZERO_MOD_Q\nfrom .hash import CryptoHashCheckable, hash_elems\nfrom .logs import log_warning\nfrom .manifest import ContestDescription\nfrom .type import SelectionId\nfrom .utils import (\n    ContestException,\n    NullVoteException,\n    OverVoteException,\n    UnderVoteException,\n    flatmap_optional,\n    to_ticks,\n)\n\n\n@dataclass(unsafe_hash=True)\nclass PlaintextBallotSelection(ElectionObjectBase):\n    \"\"\"\n    A BallotSelection represents an individual selection on a ballot.\n\n    This class accepts a `vote` integer field which has no constraints\n    in the ElectionGuard Data Specification, but is constrained logically\n    in the application to resolve to `False` or `True` aka only 0 and 1 is\n    supported for now.\n\n    This class can also be designated as `is_placeholder_selection` which has no\n    context to the data specification but is useful for running validity checks internally\n\n    Write_in field exists to support the cleartext representation of a write-in candidate value.\n    \"\"\"\n\n    vote: int\n\n    is_placeholder_selection: bool = field(default=False)\n    \"\"\"Determines if this is a placeholder selection\"\"\"\n\n    write_in: Optional[str] = field(default=None)\n    \"\"\"\n    Write_in field exists to support the cleartext representation of a write-in candidate value.\n    \"\"\"\n\n    def is_valid(self, expected_object_id: str) -> bool:\n        \"\"\"\n        Given a PlaintextBallotSelection validates that the object matches an expected object\n        and that the plaintext string can resolve to a valid representation\n        \"\"\"\n\n        if self.object_id != expected_object_id:\n            log_warning(\n                f\"invalid object_id: expected({expected_object_id}) actual({self.object_id})\"\n            )\n            return False\n\n        vote = self.vote\n        if vote < 0 or vote > 1:\n            log_warning(f\"Currently only supporting choices of 0 or 1: {str(self)}\")\n            return False\n\n        return True\n\n    def __eq__(self, other: Any) -> bool:\n        return (\n            isinstance(other, PlaintextBallotSelection)\n            and self.object_id == other.object_id\n            and self.vote == other.vote\n            and self.is_placeholder_selection == other.is_placeholder_selection\n            and self.write_in == other.write_in\n        )\n\n    def __ne__(self, other: Any) -> bool:\n        return not self.__eq__(other)\n\n\n@runtime_checkable\nclass CiphertextSelection(Protocol):\n    \"\"\"\n    Encrypted selection\n    \"\"\"\n\n    object_id: str\n\n    sequence_order: int\n    \"\"\"Order the selection.\"\"\"\n\n    description_hash: ElementModQ\n    \"\"\"The SelectionDescription hash\"\"\"\n\n    ciphertext: ElGamalCiphertext\n    \"\"\"The encrypted representation of the selection\"\"\"\n\n\n@dataclass(eq=True, unsafe_hash=True)\nclass CiphertextBallotSelection(\n    OrderedObjectBase, CiphertextSelection, CryptoHashCheckable\n):\n    \"\"\"\n    A CiphertextBallotSelection represents an individual encrypted selection on a ballot.\n\n    This class accepts a `description_hash` and a `ciphertext` as required parameters\n    in its constructor.\n\n    When a selection is encrypted, the `description_hash` and `ciphertext` required fields must\n    be populated at construction however the `nonce` is also usually provided by convention.\n\n    After construction, the `crypto_hash` field is populated automatically in the `__post_init__` cycle\n\n    A consumer of this object has the option to discard the `nonce` and/or discard the `proof`,\n    or keep both values.\n\n    By discarding the `nonce`, the encrypted representation and `proof`\n    can only be regenerated if the nonce was derived from the ballot's master nonce.  If the nonce\n    used for this selection is truly random, and it is discarded, then the proofs cannot be regenerated.\n\n    By keeping the `nonce`, or deriving the selection nonce from the ballot nonce, an external system can\n    regenerate the proofs on demand.  This is useful for storage or memory constrained systems.\n\n    By keeping the `proof` the nonce is not required fotor verify the encrypted selection.\n    \"\"\"\n\n    description_hash: ElementModQ\n    \"\"\"The SelectionDescription hash\"\"\"\n\n    ciphertext: ElGamalCiphertext\n    \"\"\"The encrypted representation of the vote field\"\"\"\n\n    crypto_hash: ElementModQ\n    \"\"\"The hash of the encrypted values\"\"\"\n\n    is_placeholder_selection: bool = field(default=False)\n    \"\"\"Determines if this is a placeholder selection\"\"\"\n\n    nonce: Optional[ElementModQ] = field(default=None)\n    \"\"\"The nonce used to generate the encryption. Sensitive & should be treated as a secret\"\"\"\n\n    proof: Optional[DisjunctiveChaumPedersenProof] = field(default=None)\n    \"\"\"The proof that demonstrates the selection is an encryption of 0 or 1, and was encrypted using the `nonce`\"\"\"\n\n    def is_valid_encryption(\n        self,\n        encryption_seed: ElementModQ,\n        elgamal_public_key: ElGamalPublicKey,\n        crypto_extended_base_hash: ElementModQ,\n    ) -> bool:\n        \"\"\"\n        Given an encrypted BallotSelection, validates the encryption state against a specific seed and public key.\n        Calling this function expects that the object is in a well-formed encrypted state\n        with the elgamal encrypted `message` field populated along with\n        the DisjunctiveChaumPedersenProof`proof` populated.\n        the ElementModQ `description_hash` and the ElementModQ `crypto_hash` are also checked.\n\n        :param encryption_seed: the hash of the SelectionDescription, or\n                                whatever `ElementModQ` was used to populate the `description_hash` field.\n        :param elgamal_public_key: The election public key\n        \"\"\"\n\n        if encryption_seed != self.description_hash:\n            log_warning(\n                (\n                    f\"mismatching selection hash: {self.object_id} expected({str(encryption_seed)}), \"\n                    f\"actual({str(self.description_hash)})\"\n                )\n            )\n            return False\n\n        recalculated_crypto_hash = self.crypto_hash_with(encryption_seed)\n        if self.crypto_hash != recalculated_crypto_hash:\n            log_warning(\n                (\n                    f\"mismatching crypto hash: {self.object_id} expected({str(recalculated_crypto_hash)}), \"\n                    f\"actual({str(self.crypto_hash)})\"\n                )\n            )\n            return False\n\n        if self.proof is None:\n            log_warning(f\"no proof exists for: {self.object_id}\")\n            return False\n\n        return self.proof.is_valid(\n            self.ciphertext, elgamal_public_key, crypto_extended_base_hash\n        )\n\n    def crypto_hash_with(self, encryption_seed: ElementModQ) -> ElementModQ:\n        \"\"\"\n        Given an encrypted BallotSelection, generates a hash, suitable for rolling up\n        into a hash for an entire ballot / ballot code. Of note, this particular hash examines\n        the `encryption_seed` and `message`, but not the proof.\n        This is deliberate, allowing for the possibility of ElectionGuard variants running on\n        much more limited hardware, wherein the Disjunctive Chaum-Pedersen proofs might be computed\n        later on.\n\n        In most cases the encryption_seed should match the `description_hash`\n        \"\"\"\n        return _ciphertext_ballot_selection_crypto_hash_with(\n            self.object_id, encryption_seed, self.ciphertext\n        )\n\n\ndef _ciphertext_ballot_selection_crypto_hash_with(\n    object_id: str, encryption_seed: ElementModQ, ciphertext: ElGamalCiphertext\n) -> ElementModQ:\n    return hash_elems(object_id, encryption_seed, ciphertext.crypto_hash())\n\n\ndef make_ciphertext_ballot_selection(\n    object_id: str,\n    sequence_order: int,\n    description_hash: ElementModQ,\n    ciphertext: ElGamalCiphertext,\n    elgamal_public_key: ElGamalPublicKey,\n    crypto_extended_base_hash: ElementModQ,\n    proof_seed: ElementModQ,\n    selection_representation: int,\n    is_placeholder_selection: bool = False,\n    nonce: Optional[ElementModQ] = None,\n    crypto_hash: Optional[ElementModQ] = None,\n    proof: Optional[DisjunctiveChaumPedersenProof] = None,\n) -> CiphertextBallotSelection:\n    \"\"\"\n    Constructs a `CipherTextBallotSelection` object. Most of the parameters here match up to fields\n    in the class, but this helper function will optionally compute a Chaum-Pedersen proof if the\n    given nonce isn't `None`. Likewise, if a crypto_hash is not provided, it will be derived from\n    the other fields.\n    \"\"\"\n    if crypto_hash is None:\n        crypto_hash = _ciphertext_ballot_selection_crypto_hash_with(\n            object_id, description_hash, ciphertext\n        )\n\n    if proof is None:\n        proof = flatmap_optional(\n            nonce,\n            lambda n: make_disjunctive_chaum_pedersen(\n                ciphertext,\n                n,\n                elgamal_public_key,\n                crypto_extended_base_hash,\n                proof_seed,\n                selection_representation,\n            ),\n        )\n\n    return CiphertextBallotSelection(\n        object_id,\n        sequence_order,\n        description_hash,\n        ciphertext,\n        crypto_hash,\n        is_placeholder_selection,\n        nonce,\n        proof,\n    )\n\n\n@dataclass(unsafe_hash=True)\nclass PlaintextBallotContest(ElectionObjectBase):\n    \"\"\"\n    A PlaintextBallotContest represents the selections made by a voter for a specific ContestDescription\n\n    this class can be either a partial or a complete representation of a contest dataset.  Specifically,\n    a partial representation must include at a minimum the \"affirmative\" selections of a contest.\n    A complete representation of a ballot must include both affirmative and negative selections of\n    the contest, AND the placeholder selections necessary to satisfy the ConstantChaumPedersen proof\n    in the CiphertextBallotContest.\n\n    Typically partial contests are passed into Electionguard for memory constrained systems,\n    while complete contests are passed into ElectionGuard when running encryption on an existing dataset.\n    \"\"\"\n\n    ballot_selections: List[PlaintextBallotSelection] = field(\n        default_factory=lambda: []\n    )\n    \"\"\"Collection of ballot selections\"\"\"\n\n    @cached_property\n    def selected_ids(self) -> List[SelectionId]:\n        return [\n            selection.object_id\n            for selection in self.ballot_selections\n            if selection.vote > 0\n        ]\n\n    @cached_property\n    def total_selected(self) -> int:\n        \"\"\"Returns the total number of selected selections.\"\"\"\n        return reduce(\n            lambda prev, next: prev + (1 if next.vote > 0 else 0),\n            self.ballot_selections,\n            0,\n        )\n\n    @cached_property\n    def total_votes(self) -> int:\n        \"\"\"Returns the total number of votes on selections.\"\"\"\n        return reduce(lambda prev, next: prev + next.vote, self.ballot_selections, 0)\n\n    @cached_property\n    def write_ins(self) -> Optional[Dict[SelectionId, str]]:\n        write_ins = {\n            selection.object_id: selection.write_in\n            for selection in self.ballot_selections\n            if selection.write_in is not None  # Required due to empty strings\n        }\n        return write_ins if len(write_ins) else None\n\n    def valid(self, description: ContestDescription) -> None:\n        \"\"\"Determine if a contest is valid.\"\"\"\n\n        # Contest id matches description and ballot selections don't exceed description\n        if (\n            self.object_id != description.object_id\n            or len(self.ballot_selections) > len(description.ballot_selections)\n            or not description.is_valid()\n        ):\n            raise ContestException(\n                self.object_id,\n                override_message=f\"invalid format of contest or description for contest {self.object_id}\",\n            )\n\n        # Selections ids match description\n        selection_ids = {\n            selection.object_id for selection in description.ballot_selections\n        }\n        for selection in self.ballot_selections:\n            if selection.object_id not in selection_ids:\n                raise ContestException(\n                    self.object_id,\n                    override_message=f\"invalid selection id ${selection.object_id} on contest {self.object_id}\",\n                )\n\n        # Specialty cases\n        if self.total_selected < 1:\n            raise NullVoteException(self.object_id)\n\n        if self.total_selected < description.number_elected:\n            raise UnderVoteException(self.object_id)\n\n        if self.total_selected > description.number_elected:\n            raise OverVoteException(self.object_id, self.selected_ids)\n\n        if description.votes_allowed is not None:\n            if self.total_votes > description.votes_allowed:\n                raise OverVoteException(self.object_id, self.selected_ids)\n\n            # Support for other cases such as cumulative voting not currently supported.\n            # (individual selections being an encryption of > 1)\n            if self.total_selected < description.votes_allowed:\n                raise ContestException(\n                    self.object_id,\n                    override_message=f\"`on contest {self.object_id}: only n-of-m style elections are supported\",\n                )\n\n    def is_valid(\n        self,\n        expected_object_id: str,\n        expected_number_selections: int,\n        expected_number_elected: int,\n        votes_allowed: Optional[int] = None,\n    ) -> bool:\n        \"\"\"\n        Given a PlaintextBallotContest returns true if the state is representative of the expected values.\n\n        Note: because this class supports partial representations, undervotes are considered a valid state.\n        \"\"\"\n\n        if self.object_id != expected_object_id:\n            log_warning(\n                (\n                    f\"invalid object_id: expected({expected_object_id}) \"\n                    f\"actual({self.object_id})\"\n                )\n            )\n            return False\n\n        if len(self.ballot_selections) > expected_number_selections:\n            log_warning(\n                (\n                    f\"invalid number_selections: expected({expected_number_selections}) \"\n                    f\"actual({len(self.ballot_selections)})\"\n                )\n            )\n            return False\n\n        number_elected = 0\n        votes = 0\n\n        # Verify the selections are well-formed\n        for selection in self.ballot_selections:\n            votes += selection.vote\n            if selection.vote >= 1:\n                number_elected += 1\n\n        if number_elected > expected_number_elected:\n            log_warning(\n                f\"invalid number_elected: expected({expected_number_elected}) actual({number_elected})\"\n            )\n            return False\n\n        if votes_allowed is not None and votes > votes_allowed:\n            log_warning(f\"invalid votes: expected({votes_allowed}) actual({votes})\")\n            return False\n\n        return True\n\n    def __eq__(self, other: Any) -> bool:\n        return isinstance(other, PlaintextBallotContest) and list_eq(\n            self.ballot_selections, other.ballot_selections\n        )\n\n    def __ne__(self, other: Any) -> bool:\n        return not self.__eq__(other)\n\n\n@dataclass\nclass CiphertextContest(OrderedObjectBase):\n    \"\"\"\n    Base encrypted contest for both tally and ballot\n    \"\"\"\n\n    description_hash: ElementModQ\n    \"\"\"The description hash\"\"\"\n\n    selections: Iterable[CiphertextSelection]\n    \"\"\"Collection of selections\"\"\"\n\n\n@dataclass(unsafe_hash=True)\nclass CiphertextBallotContest(OrderedObjectBase, CryptoHashCheckable):\n    \"\"\"\n    A CiphertextBallotContest represents the selections made by a voter for a specific ContestDescription\n\n    CiphertextBallotContest can only be a complete representation of a contest dataset.  While\n    PlaintextBallotContest supports a partial representation, a CiphertextBallotContest includes all data\n    necessary for a verifier to verify the contest.  Specifically, it includes both explicit affirmative\n    and negative selections of the contest, as well as the placeholder selections that satisfy\n    the ConstantChaumPedersen proof.\n\n    Similar to `CiphertextBallotSelection` the consuming application can choose to discard or keep both\n    the `nonce` and the `proof` in some circumstances.  For deterministic nonce's derived from the\n    master nonce, both values can be regenerated.  If the `nonce` for this contest is completely random,\n    then it is required in order to regenerate the proof.\n    \"\"\"\n\n    description_hash: ElementModQ\n    \"\"\"Hash from contestDescription\"\"\"\n\n    ballot_selections: List[CiphertextBallotSelection]\n    \"\"\"Collection of ballot selections\"\"\"\n\n    ciphertext_accumulation: ElGamalCiphertext\n    \"\"\"The encrypted representation of all of the vote fields (the contest total)\"\"\"\n\n    crypto_hash: ElementModQ\n    \"\"\"Hash of the encrypted values\"\"\"\n\n    nonce: Optional[ElementModQ] = None\n    \"\"\"The nonce used to generate the encryption. Sensitive & should be treated as a secret\"\"\"\n\n    proof: Optional[ConstantChaumPedersenProof] = None\n    \"\"\"\n    The proof demonstrates the sum of the selections does not exceed the maximum\n    available selections for the contest, and that the proof was generated with the nonce\n    \"\"\"\n\n    extended_data: Optional[HashedElGamalCiphertext] = field(default=None)\n    \"\"\"encrypted representation of the extended_data field\"\"\"\n\n    def __eq__(self, other: Any) -> bool:\n        return (\n            isinstance(other, CiphertextBallotContest)\n            and self.object_id == other.object_id\n            and list_eq(self.ballot_selections, other.ballot_selections)\n            and self.description_hash == other.description_hash\n            and self.crypto_hash == other.crypto_hash\n            and self.nonce == other.nonce\n            and self.proof == other.proof\n        )\n\n    def __ne__(self, other: Any) -> bool:\n        return not self.__eq__(other)\n\n    def aggregate_nonce(self) -> Optional[ElementModQ]:\n        \"\"\"\n        :return: an aggregate nonce for the contest composed of the nonces of the selections\n        \"\"\"\n        return _ciphertext_ballot_contest_aggregate_nonce(\n            self.object_id, self.ballot_selections\n        )\n\n    def crypto_hash_with(self, encryption_seed: ElementModQ) -> ElementModQ:\n        \"\"\"\n        Given an encrypted BallotContest, generates a hash, suitable for rolling up\n        into a hash for an entire ballot / ballot code. Of note, this particular hash examines\n        the `encryption_seed` and `ballot_selections`, but not the proof.\n        This is deliberate, allowing for the possibility of ElectionGuard variants running on\n        much more limited hardware, wherein the Disjunctive Chaum-Pedersen proofs might be computed\n        later on.\n\n        In most cases, the encryption_seed is the description_hash\n        \"\"\"\n        return _ciphertext_ballot_context_crypto_hash(\n            self.object_id, self.ballot_selections, encryption_seed\n        )\n\n    def elgamal_accumulate(self) -> ElGamalCiphertext:\n        \"\"\"\n        Add the individual ballot_selections `message` fields together, suitable for use\n        in a Chaum-Pedersen proof.\n        \"\"\"\n        return _ciphertext_ballot_elgamal_accumulate(self.ballot_selections)\n\n    def is_valid_encryption(\n        self,\n        encryption_seed: ElementModQ,\n        elgamal_public_key: ElGamalPublicKey,\n        crypto_extended_base_hash: ElementModQ,\n    ) -> bool:\n        \"\"\"\n        Given an encrypted BallotContest, validates the encryption state against a specific seed and public key\n        by verifying the accumulated sum of selections match the proof.\n        Calling this function expects that the object is in a well-formed encrypted state\n        with the `ballot_selections` populated with valid encrypted ballot selections,\n        the ElementModQ `description_hash`, the ElementModQ `crypto_hash`,\n        and the ConstantChaumPedersenProof all populated.\n        Specifically, the seed in this context is the hash of the ContestDescription,\n        or whatever `ElementModQ` was used to populate the `description_hash` field.\n        \"\"\"\n        if encryption_seed != self.description_hash:\n            log_warning(\n                (\n                    f\"mismatching contest hash: {self.object_id} expected({str(encryption_seed)}), \"\n                    f\"actual({str(self.description_hash)})\"\n                )\n            )\n            return False\n\n        recalculated_crypto_hash = self.crypto_hash_with(encryption_seed)\n        if self.crypto_hash != recalculated_crypto_hash:\n            log_warning(\n                (\n                    f\"mismatching crypto hash: {self.object_id} expected({str(recalculated_crypto_hash)}), \"\n                    f\"actual({str(self.crypto_hash)})\"\n                )\n            )\n            return False\n\n        # NOTE: this check does not verify the proofs of the individual selections by design.\n\n        if self.proof is None:\n            log_warning(f\"no proof exists for: {self.object_id}\")\n            return False\n\n        computed_ciphertext_accumulation = self.elgamal_accumulate()\n\n        # Verify that the contest ciphertext matches the elgamal accumulation of all selections\n        if self.ciphertext_accumulation != computed_ciphertext_accumulation:\n            log_warning(\n                f\"ciphertext does not equal elgamal accumulation for : {self.object_id}\"\n            )\n            return False\n\n        # Verify the sum of the selections matches the proof\n        return self.proof.is_valid(\n            computed_ciphertext_accumulation,\n            elgamal_public_key,\n            crypto_extended_base_hash,\n        )\n\n\ndef _ciphertext_ballot_elgamal_accumulate(\n    ballot_selections: List[CiphertextBallotSelection],\n) -> ElGamalCiphertext:\n    return elgamal_add(*[selection.ciphertext for selection in ballot_selections])\n\n\ndef _ciphertext_ballot_context_crypto_hash(\n    object_id: str,\n    ballot_selections: List[CiphertextBallotSelection],\n    encryption_seed: ElementModQ,\n) -> ElementModQ:\n    if len(ballot_selections) == 0:\n        log_warning(\n            f\"mismatching ballot_selections state: {object_id} expected(some), actual(none)\"\n        )\n        return ZERO_MOD_Q\n\n    selection_hashes = [\n        selection.crypto_hash for selection in sequence_order_sort(ballot_selections)\n    ]\n\n    return hash_elems(object_id, encryption_seed, *selection_hashes)\n\n\ndef _ciphertext_ballot_contest_aggregate_nonce(\n    object_id: str, ballot_selections: List[CiphertextBallotSelection]\n) -> Optional[ElementModQ]:\n    selection_nonces: List[ElementModQ] = []\n    for selection in ballot_selections:\n        if selection.nonce is None:\n            log_warning(\n                f\"missing nonce values for contest {object_id} cannot calculate aggregate nonce\"\n            )\n            return None\n        selection_nonces.append(selection.nonce)\n\n    return add_q(*selection_nonces)\n\n\ndef make_ciphertext_ballot_contest(\n    object_id: str,\n    sequence_order: int,\n    description_hash: ElementModQ,\n    ballot_selections: List[CiphertextBallotSelection],\n    elgamal_public_key: ElGamalPublicKey,\n    crypto_extended_base_hash: ElementModQ,\n    proof_seed: ElementModQ,\n    number_elected: int,\n    crypto_hash: Optional[ElementModQ] = None,\n    proof: Optional[ConstantChaumPedersenProof] = None,\n    nonce: Optional[ElementModQ] = None,\n    extended_data: Optional[HashedElGamalCiphertext] = None,\n) -> CiphertextBallotContest:\n    \"\"\"\n    Constructs a `CipherTextBallotContest` object. Most of the parameters here match up to fields\n    in the class, but this helper function will optionally compute a Chaum-Pedersen proof if the\n    ballot selections include their encryption nonces. Likewise, if a crypto_hash is not provided,\n    it will be derived from the other fields.\n    \"\"\"\n    if crypto_hash is None:\n        crypto_hash = _ciphertext_ballot_context_crypto_hash(\n            object_id, ballot_selections, description_hash\n        )\n\n    aggregate = _ciphertext_ballot_contest_aggregate_nonce(object_id, ballot_selections)\n    elgamal_accumulation = _ciphertext_ballot_elgamal_accumulate(ballot_selections)\n    if proof is None:\n        proof = flatmap_optional(\n            aggregate,\n            lambda ag: make_constant_chaum_pedersen(\n                elgamal_accumulation,\n                number_elected,\n                ag,\n                elgamal_public_key,\n                proof_seed,\n                crypto_extended_base_hash,\n            ),\n        )\n    return CiphertextBallotContest(\n        object_id,\n        sequence_order,\n        description_hash,\n        ballot_selections,\n        elgamal_accumulation,\n        crypto_hash,\n        nonce,\n        proof,\n        extended_data,\n    )\n\n\n@dataclass(unsafe_hash=True)\nclass PlaintextBallot(ElectionObjectBase):\n    \"\"\"\n    A PlaintextBallot represents a voters selections for a given ballot and ballot style\n    :field object_id: A unique Ballot ID that is relevant to the external system\n    \"\"\"\n\n    style_id: str\n    \"\"\"The `object_id` of the `BallotStyle` in the `Election` Manifest\"\"\"\n\n    contests: List[PlaintextBallotContest]\n    \"\"\"The list of contests for this ballot\"\"\"\n\n    def is_valid(self, expected_ballot_style_id: str) -> bool:\n        \"\"\"\n        Check if expected ballot style is valid\n        :param expected_ballot_style_id: Expected ballot style id\n        :return: True if valid\n        \"\"\"\n        if self.style_id != expected_ballot_style_id:\n            log_warning(\n                (\n                    f\"invalid ballot_style: for: {self.object_id} expected({expected_ballot_style_id}) \"\n                    f\"actual({self.style_id})\"\n                )\n            )\n            return False\n\n        return True\n\n    def __eq__(self, other: Any) -> bool:\n        return (\n            isinstance(other, PlaintextBallot)\n            and self.style_id == other.style_id\n            and list_eq(self.contests, other.contests)\n        )\n\n    def __ne__(self, other: Any) -> bool:\n        return not self.__eq__(other)\n\n\n# pylint: disable=too-many-instance-attributes\n@dataclass(unsafe_hash=True)\nclass CiphertextBallot(ElectionObjectBase, CryptoHashCheckable):\n    \"\"\"\n    A CiphertextBallot represents a voters encrypted selections for a given ballot and ballot style.\n\n    When a ballot is in it's complete, encrypted state, the `nonce` is the master nonce\n    from which all other nonces can be derived to encrypt the ballot.  Allong with the `nonce`\n    fields on `Ballotcontest` and `BallotSelection`, this value is sensitive.\n\n    Don't make this directly. Use `make_ciphertext_ballot` instead.\n    :field object_id: A unique Ballot ID that is relevant to the external system\n    \"\"\"\n\n    style_id: str\n    \"\"\"The `object_id` of the `BallotStyle` in the `Election` Manifest\"\"\"\n\n    manifest_hash: ElementModQ\n    \"\"\"Hash of the election manifest\"\"\"\n\n    code_seed: ElementModQ\n    \"\"\"Seed for ballot code\"\"\"\n\n    contests: List[CiphertextBallotContest]\n    \"\"\"List of contests for this ballot\"\"\"\n\n    code: ElementModQ\n    \"\"\"Unique ballot code for this ballot\"\"\"\n\n    timestamp: int\n    \"\"\"Timestamp at which the ballot encryption is generated in tick\"\"\"\n\n    crypto_hash: ElementModQ\n    \"\"\"The hash of the encrypted ballot representation\"\"\"\n\n    nonce: Optional[ElementModQ]\n    \"\"\"The nonce used to encrypt this ballot. Sensitive & should be treated as a secret\"\"\"\n\n    def __eq__(self, other: Any) -> bool:\n        return (\n            isinstance(other, CiphertextBallot)\n            and self.object_id == other.object_id\n            and self.style_id == other.style_id\n            and self.manifest_hash == other.manifest_hash\n            and self.code_seed == other.code_seed\n            and list_eq(self.contests, other.contests)\n            and self.code == other.code\n            and self.timestamp == other.timestamp\n            and self.crypto_hash == other.crypto_hash\n            and self.nonce == other.nonce\n        )\n\n    def __ne__(self, other: Any) -> bool:\n        return not self.__eq__(other)\n\n    @staticmethod\n    def nonce_seed(\n        manifest_hash: ElementModQ, object_id: str, nonce: ElementModQ\n    ) -> ElementModQ:\n        \"\"\"\n        :return: a representation of the election and the external Id in the nonce's used\n        to derive other nonce values on the ballot\n        \"\"\"\n        return hash_elems(manifest_hash, object_id, nonce)\n\n    def hashed_ballot_nonce(self) -> Optional[ElementModQ]:\n        \"\"\"\n        :return: a hash value derived from the description hash, the object id, and the nonce value\n                suitable for deriving other nonce values on the ballot\n        \"\"\"\n\n        if self.nonce is None:\n            log_warning(\n                f\"missing nonce for ballot {self.object_id} could not derive from null nonce\"\n            )\n            return None\n\n        return self.nonce_seed(self.manifest_hash, self.object_id, self.nonce)\n\n    def crypto_hash_with(self, encryption_seed: ElementModQ) -> ElementModQ:\n        \"\"\"\n        Given an encrypted Ballot, generates a hash, suitable for rolling up\n        into a hash for an entire ballot / ballot code. Of note, this particular hash examines\n        the `manifest_hash` and `ballot_selections`, but not the proof.\n        This is deliberate, allowing for the possibility of ElectionGuard variants running on\n        much more limited hardware, wherein the Disjunctive Chaum-Pedersen proofs might be computed\n        later on.\n        \"\"\"\n        if len(self.contests) == 0:\n            log_warning(\n                f\"mismatching contests state: {self.object_id} expected(some), actual(none)\"\n            )\n            return ZERO_MOD_Q\n\n        contest_hashes = [contest.crypto_hash for contest in self.contests]\n        return hash_elems(self.object_id, encryption_seed, *contest_hashes)\n\n    def is_valid_encryption(\n        self,\n        encryption_seed: ElementModQ,\n        elgamal_public_key: ElGamalPublicKey,\n        crypto_extended_base_hash: ElementModQ,\n    ) -> bool:\n        \"\"\"\n        Given an encrypted Ballot, validates the encryption state against a specific seed and public key\n        by verifying the states of this ballot's children (BallotContest's and BallotSelection's).\n        Calling this function expects that the object is in a well-formed encrypted state\n        with the `contests` populated with valid encrypted ballot selections,\n        and the ElementModQ `manifest_hash` also populated.\n        Specifically, the seed in this context is the hash of the Election Manifest,\n        or whatever `ElementModQ` was used to populate the `manifest_hash` field.\n        \"\"\"\n\n        if encryption_seed != self.manifest_hash:\n            log_warning(\n                (\n                    f\"mismatching ballot hash: {self.object_id} expected({str(encryption_seed)}), \"\n                    f\"actual({str(self.manifest_hash)})\"\n                )\n            )\n            return False\n\n        recalculated_crypto_hash = self.crypto_hash_with(encryption_seed)\n        if self.crypto_hash != recalculated_crypto_hash:\n            log_warning(\n                (\n                    f\"mismatching crypto hash: {self.object_id} expected({str(recalculated_crypto_hash)}), \"\n                    f\"actual({str(self.crypto_hash)})\"\n                )\n            )\n            return False\n\n        # Check the proofs on the ballot\n        valid_proofs: List[bool] = []\n\n        for contest in self.contests:\n            for selection in contest.ballot_selections:\n                valid_proofs.append(\n                    selection.is_valid_encryption(\n                        selection.description_hash,\n                        elgamal_public_key,\n                        crypto_extended_base_hash,\n                    )\n                )\n            valid_proofs.append(\n                contest.is_valid_encryption(\n                    contest.description_hash,\n                    elgamal_public_key,\n                    crypto_extended_base_hash,\n                )\n            )\n        return all(valid_proofs)\n\n\nclass BallotBoxState(Enum):\n    \"\"\"\n    Enumeration used when marking a ballot as cast or spoiled\n    \"\"\"\n\n    CAST = 1\n    \"\"\"\n    A ballot that has been explicitly cast\n    \"\"\"\n    SPOILED = 2\n    \"\"\"\n    A ballot that has been explicitly spoiled\n    \"\"\"\n    UNKNOWN = 999\n    \"\"\"\n    A ballot whose state is unknown to ElectionGuard and will not be included in any election results\n    \"\"\"\n\n\n@dataclass(unsafe_hash=True)\nclass SubmittedBallot(CiphertextBallot):\n    \"\"\"\n    A `SubmittedBallot` represents a ballot that is submitted for inclusion in election results.\n    A submitted ballot is or is about to be either cast or spoiled.\n    The state supports the `BallotBoxState.UNKNOWN` enumeration to indicate that this object is mutable\n    and has not yet been explicitly assigned a specific state.\n\n    Note, additionally, this ballot includes all proofs but no nonces.\n\n    Do not make this class directly. Use `make_ciphertext_submitted_ballot` instead.\n    \"\"\"\n\n    state: BallotBoxState\n\n    def __eq__(self, other: Any) -> bool:\n        return (\n            isinstance(other, SubmittedBallot)\n            and super().__eq__(other)\n            and self.state == other.state\n        )\n\n    def __ne__(self, other: Any) -> bool:\n        return not self.__eq__(other)\n\n\ndef make_ciphertext_ballot(\n    object_id: str,\n    style_id: str,\n    manifest_hash: ElementModQ,\n    code_seed: Optional[ElementModQ],\n    contests: List[CiphertextBallotContest],\n    nonce: Optional[ElementModQ] = None,\n    timestamp: Optional[int] = None,\n    ballot_code: Optional[ElementModQ] = None,\n) -> CiphertextBallot:\n    \"\"\"\n    Makes a `CiphertextBallot`, initially in the state where it's neither been cast nor spoiled.\n\n    :param object_id: the object_id of this specific ballot\n    :param style_id: The `object_id` of the `BallotStyle` in the `Election` Manifest\n    :param manifest_hash: Hash of the election manifest\n    :param crypto_base_hash: Hash of the cryptographic election context\n    :param contests: List of contests for this ballot\n    :param timestamp: Timestamp at which the ballot encryption is generated in tick\n    :param code_seed: Seed for ballot code\n    :param nonce: optional nonce used as part of the encryption process\n    \"\"\"\n\n    if len(contests) == 0:\n        log_warning(\"ciphertext ballot with no contests\")\n\n    contest_hash = create_ballot_hash(object_id, manifest_hash, contests)\n\n    timestamp = to_ticks(datetime.now()) if timestamp is None else timestamp\n    if code_seed is None:\n        code_seed = manifest_hash\n    if ballot_code is None:\n        ballot_code = get_ballot_code(code_seed, timestamp, contest_hash)\n\n    return CiphertextBallot(\n        object_id,\n        style_id,\n        manifest_hash,\n        code_seed,\n        contests,\n        ballot_code,\n        timestamp,\n        contest_hash,\n        nonce,\n    )\n\n\ndef create_ballot_hash(\n    ballot_id: str,\n    description_hash: ElementModQ,\n    contests: List[CiphertextBallotContest],\n) -> ElementModQ:\n    \"\"\"Create the hash of the ballot contests\"\"\"\n\n    contest_hashes = [contest.crypto_hash for contest in sequence_order_sort(contests)]\n    return hash_elems(ballot_id, description_hash, *contest_hashes)\n\n\ndef make_ciphertext_submitted_ballot(\n    object_id: str,\n    style_id: str,\n    manifest_hash: ElementModQ,\n    code_seed: Optional[ElementModQ],\n    contests: List[CiphertextBallotContest],\n    ballot_code: Optional[ElementModQ],\n    timestamp: Optional[int] = None,\n    state: BallotBoxState = BallotBoxState.UNKNOWN,\n) -> SubmittedBallot:\n    \"\"\"\n    Makes a `SubmittedBallot`, ensuring that no nonces are part of the contests.\n\n    :param object_id: the object_id of this specific ballot\n    :param style_id: The `object_id` of the `BallotStyle` in the `Election` Manifest\n    :param manifest_hash: Hash of the election manifest\n    :param code_seed: Seed for ballot code\n    :param contests: List of contests for this ballot\n    :param timestamp: Timestamp at which the ballot encryption is generated in tick\n    :param state: ballot box state\n    \"\"\"\n\n    if len(contests) == 0:\n        log_warning(\"ciphertext ballot with no contests\")\n\n    contest_hashes = [contest.crypto_hash for contest in sequence_order_sort(contests)]\n    contest_hash = hash_elems(object_id, manifest_hash, *contest_hashes)\n\n    timestamp = to_ticks(datetime.now(timezone.utc)) if timestamp is None else timestamp\n    if code_seed is None:\n        code_seed = manifest_hash\n    if ballot_code is None:\n        ballot_code = get_ballot_code(code_seed, timestamp, contest_hash)\n\n    # copy the contests and selections, removing all nonces\n    new_contests: List[CiphertextBallotContest] = []\n    for contest in contests:\n        new_selections = [\n            replace(selection, nonce=None) for selection in contest.ballot_selections\n        ]\n        new_contest = replace(contest, nonce=None, ballot_selections=new_selections)\n        new_contests.append(new_contest)\n\n    return SubmittedBallot(\n        object_id,\n        style_id,\n        manifest_hash,\n        code_seed,\n        new_contests,\n        ballot_code,\n        timestamp,\n        contest_hash,\n        None,\n        state,\n    )\n"
  },
  {
    "path": "src/electionguard/ballot_box.py",
    "content": "from dataclasses import dataclass, field\nfrom typing import Dict, Optional\n\nfrom .ballot import (\n    BallotBoxState,\n    CiphertextBallot,\n    SubmittedBallot,\n    make_ciphertext_submitted_ballot,\n)\nfrom .ballot_validator import ballot_is_valid_for_election\nfrom .data_store import DataStore\nfrom .election import CiphertextElectionContext\nfrom .logs import log_warning\nfrom .manifest import InternalManifest\nfrom .type import BallotId\n\n\n@dataclass\nclass BallotBox:\n    \"\"\"A stateful convenience wrapper to cache election data.\"\"\"\n\n    _internal_manifest: InternalManifest = field()\n    _encryption: CiphertextElectionContext = field()\n    _store: DataStore = field(default_factory=lambda: DataStore())\n\n    def cast(self, ballot: CiphertextBallot) -> Optional[SubmittedBallot]:\n        \"\"\"Cast a specific encrypted `CiphertextBallot`.\"\"\"\n        return submit_ballot_to_box(\n            ballot,\n            BallotBoxState.CAST,\n            self._internal_manifest,\n            self._encryption,\n            self._store,\n        )\n\n    def spoil(self, ballot: CiphertextBallot) -> Optional[SubmittedBallot]:\n        \"\"\"Spoil a specific encrypted `CiphertextBallot`.\"\"\"\n        return submit_ballot_to_box(\n            ballot,\n            BallotBoxState.SPOILED,\n            self._internal_manifest,\n            self._encryption,\n            self._store,\n        )\n\n\ndef submit_ballot_to_box(\n    ballot: CiphertextBallot,\n    state: BallotBoxState,\n    internal_manifest: InternalManifest,\n    context: CiphertextElectionContext,\n    store: DataStore,\n) -> Optional[SubmittedBallot]:\n    \"\"\"\n    Submit a ballot within the context of a specified election and against an existing data store\n    Verified that the ballot is valid for the election `internal_manifest` and `context` and\n    that the ballot has not already been cast or spoiled.\n    :return: a `SubmittedBallot` or `None` if there was an error\n    \"\"\"\n    if not ballot_is_valid_for_election(ballot, internal_manifest, context, True):\n        log_warning(f\"ballot: {ballot.object_id} failed validity check\")\n        return None\n\n    existing_ballot = store.get(ballot.object_id)\n    if existing_ballot is not None:\n        log_warning(\n            f\"error accepting ballot, {ballot.object_id} already exists with state: {existing_ballot.state}\"\n        )\n        return None\n\n    # TODO: ISSUE #56: check if the ballot includes the nonce, and regenerate the proofs\n    # TODO: ISSUE #56: check if the ballot includes the proofs, if it does not include the nonce\n\n    ballot_box_ballot = submit_ballot(ballot, state)\n\n    store.set(ballot_box_ballot.object_id, ballot_box_ballot)\n    return store.get(ballot_box_ballot.object_id)\n\n\ndef get_ballots(\n    store: DataStore, state: Optional[BallotBoxState]\n) -> Dict[BallotId, SubmittedBallot]:\n    \"\"\"Get ballots from the store optionally filtering on state.\"\"\"\n    return {\n        ballot_id: ballot\n        for (ballot_id, ballot) in store.items()\n        if state is None or ballot.state == state\n    }\n\n\ndef submit_ballot(\n    ballot: CiphertextBallot, state: BallotBoxState = BallotBoxState.UNKNOWN\n) -> SubmittedBallot:\n    \"\"\"\n    Convert a `CiphertextBallot` into a `SubmittedBallot`, with all nonces removed.\n    \"\"\"\n\n    return make_ciphertext_submitted_ballot(\n        ballot.object_id,\n        ballot.style_id,\n        ballot.manifest_hash,\n        ballot.code_seed,\n        ballot.contests,\n        ballot.code,\n        ballot.timestamp,\n        state,\n    )\n\n\ndef cast_ballot(ballot: CiphertextBallot) -> SubmittedBallot:\n    \"\"\"\n    Convert a `CiphertextBallot` into a `SubmittedBallot`, with all nonces removed.\n    Declare a ballot as CAST.\n    \"\"\"\n    return submit_ballot(\n        ballot,\n        BallotBoxState.CAST,\n    )\n\n\ndef spoil_ballot(ballot: CiphertextBallot) -> SubmittedBallot:\n    \"\"\"\n    Convert a `CiphertextBallot` into a `SubmittedBallot`, with all nonces removed.\n    Declare a ballot as CAST.\n    \"\"\"\n    return submit_ballot(\n        ballot,\n        BallotBoxState.SPOILED,\n    )\n"
  },
  {
    "path": "src/electionguard/ballot_code.py",
    "content": "from .hash import hash_elems\nfrom .group import ElementModQ\n\n\ndef get_hash_for_device(\n    device_id: int, session_id: int, launch_code: int, location: str\n) -> ElementModQ:\n    \"\"\"\n    Get starting hash for given device.\n\n    :param device_id: Unique identifier of device\n    :param session_id: Unique identifier for the session\n    :param launch_code: A unique launch code for the election\n    :param location: Location of device\n    :return: Starting hash of device\n    \"\"\"\n    return hash_elems(device_id, session_id, launch_code, location)\n\n\ndef get_ballot_code(\n    prev_code: ElementModQ, timestamp: int, ballot_hash: ElementModQ\n) -> ElementModQ:\n    \"\"\"\n    Get the rotated code for a particular ballot.\n    :param prev_code: Previous code or starting hash from device\n    :param timestamp: Timestamp in ticks\n    :param ballot_hash: Hash of ballot\n    :return: code\n    \"\"\"\n    return hash_elems(prev_code, timestamp, ballot_hash)\n"
  },
  {
    "path": "src/electionguard/ballot_compact.py",
    "content": "from dataclasses import dataclass\nfrom typing import Dict, List\n\n\nfrom .ballot import (\n    CiphertextBallot,\n    SubmittedBallot,\n    PlaintextBallot,\n    PlaintextBallotContest,\n    PlaintextBallotSelection,\n    make_ciphertext_submitted_ballot,\n)\nfrom .ballot_box import BallotBoxState\nfrom .election import CiphertextElectionContext\nfrom .election_object_base import sequence_order_sort\nfrom .encrypt import encrypt_ballot_contests\nfrom .group import ElementModQ\nfrom .manifest import (\n    ContestDescriptionWithPlaceholders,\n    InternalManifest,\n)\nfrom .utils import get_optional\n\nYES_VOTE = 1\nNO_VOTE = 0\n\n\n@dataclass\nclass CompactPlaintextBallot:\n    \"\"\"A compact plaintext representation of ballot minimized for data size\"\"\"\n\n    object_id: str\n    style_id: str\n    selections: List[bool]\n    write_ins: Dict[int, str]\n\n\n@dataclass\nclass CompactSubmittedBallot:\n    \"\"\"A compact submitted ballot minimized for data size\"\"\"\n\n    compact_plaintext_ballot: CompactPlaintextBallot\n    timestamp: int\n    ballot_nonce: ElementModQ\n    code_seed: ElementModQ\n    code: ElementModQ\n    ballot_box_state: BallotBoxState\n\n\ndef compress_plaintext_ballot(ballot: PlaintextBallot) -> CompactPlaintextBallot:\n    \"\"\"Compress a plaintext ballot into a compact plaintext ballot\"\"\"\n    selections = _get_compact_selections(ballot)\n    extended_data = _get_compact_write_ins(ballot)\n\n    return CompactPlaintextBallot(\n        ballot.object_id, ballot.style_id, selections, extended_data\n    )\n\n\ndef compress_submitted_ballot(\n    ballot: SubmittedBallot,\n    plaintext_ballot: PlaintextBallot,\n    ballot_nonce: ElementModQ,\n) -> CompactSubmittedBallot:\n    \"\"\"Compress a submitted ballot into a compact submitted ballot\"\"\"\n    return CompactSubmittedBallot(\n        compress_plaintext_ballot(plaintext_ballot),\n        ballot.timestamp,\n        ballot_nonce,\n        ballot.code_seed,\n        ballot.code,\n        ballot.state,\n    )\n\n\ndef expand_compact_submitted_ballot(\n    compact_ballot: CompactSubmittedBallot,\n    internal_manifest: InternalManifest,\n    context: CiphertextElectionContext,\n) -> SubmittedBallot:\n    \"\"\"\n    Expand a compact submitted ballot using context and\n    the election manifest into a submitted ballot\n    \"\"\"\n    # Expand ballot and encrypt & hash contests\n    plaintext_ballot = expand_compact_plaintext_ballot(\n        compact_ballot.compact_plaintext_ballot, internal_manifest\n    )\n    nonce_seed = CiphertextBallot.nonce_seed(\n        internal_manifest.manifest_hash,\n        compact_ballot.compact_plaintext_ballot.object_id,\n        compact_ballot.ballot_nonce,\n    )\n    contests = get_optional(\n        encrypt_ballot_contests(\n            plaintext_ballot, internal_manifest, context, nonce_seed\n        )\n    )\n\n    return make_ciphertext_submitted_ballot(\n        plaintext_ballot.object_id,\n        plaintext_ballot.style_id,\n        internal_manifest.manifest_hash,\n        compact_ballot.code_seed,\n        contests,\n        compact_ballot.code,\n        compact_ballot.timestamp,\n        compact_ballot.ballot_box_state,\n    )\n\n\ndef expand_compact_plaintext_ballot(\n    compact_ballot: CompactPlaintextBallot, internal_manifest: InternalManifest\n) -> PlaintextBallot:\n    \"\"\"Expand a compact plaintext ballot into the original plaintext ballot\"\"\"\n    return PlaintextBallot(\n        compact_ballot.object_id,\n        compact_ballot.style_id,\n        _get_plaintext_contests(compact_ballot, internal_manifest),\n    )\n\n\ndef _get_compact_selections(ballot: PlaintextBallot) -> List[bool]:\n    selections = []\n    for contest in ballot.contests:\n        for selection in contest.ballot_selections:\n            selections.append(selection.vote == YES_VOTE)\n    return selections\n\n\ndef _get_compact_write_ins(ballot: PlaintextBallot) -> Dict[int, str]:\n    write_ins = {}\n    index = 0\n    for contest in ballot.contests:\n        for selection in contest.ballot_selections:\n            index += 1\n            if selection.write_in:\n                write_ins[index] = selection.write_in\n    return write_ins\n\n\ndef _get_plaintext_contests(\n    compact_ballot: CompactPlaintextBallot, internal_manifest: InternalManifest\n) -> List[PlaintextBallotContest]:\n    \"\"\"Get ballot contests from compact plaintext ballot\"\"\"\n    index = 0\n    ballot_style_contests = _get_ballot_style_contests(\n        compact_ballot.style_id, internal_manifest\n    )\n\n    contests: List[PlaintextBallotContest] = []\n    for manifest_contest in sequence_order_sort(internal_manifest.contests):\n        contest_in_style = (\n            ballot_style_contests.get(manifest_contest.object_id) is not None\n        )\n\n        # Iterate through selections. If contest not in style, mark placeholder\n        selections: List[PlaintextBallotSelection] = []\n        for selection in sequence_order_sort(manifest_contest.ballot_selections):\n            selections.append(\n                PlaintextBallotSelection(\n                    selection.object_id,\n                    YES_VOTE if compact_ballot.selections[index] else NO_VOTE,\n                    not contest_in_style,\n                    compact_ballot.write_ins.get(index),\n                )\n            )\n            index += 1\n\n        contests.append(PlaintextBallotContest(manifest_contest.object_id, selections))\n    return contests\n\n\ndef _get_ballot_style_contests(\n    ballot_style_id: str, internal_manifest: InternalManifest\n) -> Dict[str, ContestDescriptionWithPlaceholders]:\n    ballot_style_contests = internal_manifest.get_contests_for(ballot_style_id)\n    return {contest.object_id: contest for contest in ballot_style_contests}\n"
  },
  {
    "path": "src/electionguard/ballot_validator.py",
    "content": "from .ballot import CiphertextBallot, CiphertextBallotContest, CiphertextBallotSelection\nfrom .election import CiphertextElectionContext\nfrom .logs import log_warning\nfrom .manifest import (\n    ContestDescriptionWithPlaceholders,\n    InternalManifest,\n    SelectionDescription,\n)\n\n\ndef ballot_is_valid_for_election(\n    ballot: CiphertextBallot,\n    internal_manifest: InternalManifest,\n    context: CiphertextElectionContext,\n    should_validate: bool,\n) -> bool:\n    \"\"\"\n    Determine if a ballot is valid for a given election\n    \"\"\"\n\n    if not ballot_is_valid_for_style(ballot, internal_manifest):\n        return False\n\n    if should_validate:\n        if not ballot.is_valid_encryption(\n            internal_manifest.manifest_hash,\n            context.elgamal_public_key,\n            context.crypto_extended_base_hash,\n        ):\n            log_warning(\n                f\"ballot_is_valid_for_election: mismatching ballot encryption {ballot.object_id}\"\n            )\n            return False\n\n    return True\n\n\ndef selection_is_valid_for_style(\n    selection: CiphertextBallotSelection, description: SelectionDescription\n) -> bool:\n    \"\"\"\n    Determine if selection is valid for ballot style\n    :param selection: Ballot selection\n    :param description: Selection description\n    :return: Is valid\n    \"\"\"\n    if selection.description_hash != description.crypto_hash():\n        log_warning(\n            (\n                f\"ballot is not valid for style: mismatched selection description hash {selection.description_hash} \"\n                f\"for selection {description.object_id} hash {description.crypto_hash()}\"\n            )\n        )\n        return False\n\n    return True\n\n\ndef contest_is_valid_for_style(\n    contest: CiphertextBallotContest, description: ContestDescriptionWithPlaceholders\n) -> bool:\n    \"\"\"\n    Determine if contest is valid for ballot style\n    :param contest: Contest\n    :param description: Contest description\n    :return: Is valid\n    \"\"\"\n    # verify the hash matches\n    if contest.description_hash != description.crypto_hash():\n        log_warning(\n            (\n                f\"ballot is not valid for style: mismatched description hash {contest.description_hash} \"\n                f\"for contest {description.object_id} hash {description.crypto_hash()}\"\n            )\n        )\n        return False\n\n    # verify the placeholder count\n    if len(contest.ballot_selections) != len(description.ballot_selections) + len(\n        description.placeholder_selections\n    ):\n        log_warning(\n            f\"ballot is not valid for style: mismatched selection count for contest {description.object_id}\"\n        )\n        return False\n\n    return True\n\n\ndef ballot_is_valid_for_style(\n    ballot: CiphertextBallot, internal_manifest: InternalManifest\n) -> bool:\n    \"\"\"\n    Determine if ballot is valid for ballot style\n    :param ballot: Ballot\n    :param internal_manifest: Internal election description\n    :return: Is valid\n    \"\"\"\n    descriptions = internal_manifest.get_contests_for(ballot.style_id)\n\n    for description in descriptions:\n        use_contest = None\n        for contest in ballot.contests:\n            if description.object_id == contest.object_id:\n                use_contest = contest\n                break\n\n        # verify the contest exists on the ballot\n        if use_contest is None:\n            log_warning(\n                f\"ballot is not valid for style: missing contest {description.object_id}\"\n            )\n            return False\n\n        if not contest_is_valid_for_style(use_contest, description):\n            return False\n\n        # verify the selection metadata\n        for selection_description in description.ballot_selections:\n            use_selection = None\n            for selection in use_contest.ballot_selections:\n                if selection_description.object_id == selection.object_id:\n                    use_selection = selection\n                    break\n\n            if use_selection is None:\n                log_warning(\n                    f\"ballot is not valid for style: missing selection {selection_description.object_id}\"\n                )\n                return False\n\n            if not selection_is_valid_for_style(use_selection, selection_description):\n                return False\n\n    return True\n"
  },
  {
    "path": "src/electionguard/big_integer.py",
    "content": "from typing import Any, Tuple, Union\nfrom base64 import b16decode\n\n# pylint: disable=no-name-in-module\nfrom gmpy2 import mpz\n\nfrom .utils import BYTE_ORDER\n\n\ndef _hex_to_int(input: str) -> int:\n    \"\"\"Given a hex string representing bytes, returns an int.\"\"\"\n    valid_bytes = input[1:] if (len(input) % 2 != 0 and input[0] == \"0\") else input\n    hex_bytes = bytes.fromhex(valid_bytes)\n    return int.from_bytes(hex_bytes, BYTE_ORDER)\n\n\ndef _int_to_hex(input: int) -> str:\n    \"\"\"Given an int, returns a hex string representing bytes.\"\"\"\n\n    def pad_hex(hex: str) -> str:\n        \"\"\"Pad hex to ensure 2 digit hexadecimal format maintained.\"\"\"\n        return \"0\" + hex if len(hex) % 2 else hex\n\n    hex = format(input, \"02X\")\n    return pad_hex(hex)\n\n\ndef bytes_to_hex(input: bytes) -> str:\n    return _int_to_hex(int.from_bytes(input, BYTE_ORDER))\n\n\n_zero = mpz(0)\n\n\ndef _convert_to_element(data: Union[int, str]) -> Tuple[str, int]:\n    \"\"\"Convert element to consistent types\"\"\"\n    if isinstance(data, str):\n        integer = _hex_to_int(data)\n        hex = _int_to_hex(integer)\n    else:\n        hex = _int_to_hex(data)\n        integer = data\n    return (hex, integer)\n\n\nclass BigInteger(str):\n    \"\"\"A specialized representation of a big integer in python\"\"\"\n\n    _value: mpz = _zero\n\n    def __new__(cls, data: Union[int, str]):  # type: ignore\n        (hex, integer) = _convert_to_element(data)\n        big_int = super(BigInteger, cls).__new__(cls, hex)\n        big_int._value = mpz(integer)\n        return big_int\n\n    @property\n    def value(self) -> mpz:\n        \"\"\"Get internal value for math calculations\"\"\"\n        return self._value\n\n    def __int__(self) -> int:\n        \"\"\"Overload int conversion.\"\"\"\n        return int(self.value)\n\n    def __eq__(self, other: Any) -> bool:\n        \"\"\"Overload == (equal to) operator.\"\"\"\n        return (\n            isinstance(other, BigInteger) and int(self.value) == int(other.value)\n        ) or (isinstance(other, int) and int(self.value) == other)\n\n    def __ne__(self, other: Any) -> bool:\n        \"\"\"Overload != (not equal to) operator.\"\"\"\n        return not self == other\n\n    def __lt__(self, other: Any) -> bool:\n        \"\"\"Overload <= (less than) operator.\"\"\"\n        return (\n            isinstance(other, BigInteger) and int(self.value) < int(other.value)\n        ) or (isinstance(other, int) and int(self.value) < other)\n\n    def __le__(self, other: Any) -> bool:\n        \"\"\"Overload <= (less than or equal) operator.\"\"\"\n        return self.__lt__(other) or self.__eq__(other)\n\n    def __gt__(self, other: Any) -> bool:\n        \"\"\"Overload > (greater than) operator.\"\"\"\n        return (\n            isinstance(other, BigInteger) and int(self.value) > int(other.value)\n        ) or (isinstance(other, int) and int(self.value) > other)\n\n    def __ge__(self, other: Any) -> bool:\n        \"\"\"Overload >= (greater than or equal) operator.\"\"\"\n        return self.__gt__(other) or self.__eq__(other)\n\n    def __hash__(self) -> int:\n        \"\"\"Overload the hashing function.\"\"\"\n        return hash(self.value)\n\n    def to_hex(self) -> str:\n        \"\"\"\n        Convert from the element to the hex representation of bytes.\n        \"\"\"\n        return str(self)\n\n    def to_hex_bytes(self) -> bytes:\n        \"\"\"\n        Convert from the element to the representation of bytes by first going through hex.\n        \"\"\"\n\n        return b16decode(self)\n"
  },
  {
    "path": "src/electionguard/byte_padding.py",
    "content": "from enum import IntEnum\nfrom typing import Literal\n\n\n_PAD_BYTE = b\"\\x00\"\n_BYTE_ORDER: Literal[\"little\", \"big\"] = \"big\"\n_PAD_INDICATOR_SIZE = 2\n\n\nclass DataSize(IntEnum):\n    \"\"\"Define the sizes for data.\"\"\"\n\n    Bytes_512 = 512\n\n\nclass TruncationError(ValueError):\n    \"\"\"A specific truncation error to indicate when padded data is truncated.\"\"\"\n\n\ndef to_padded_bytes(data: str, size: DataSize = DataSize.Bytes_512) -> bytes:\n    \"\"\"Returns the data field as bytes, padded to the correct size.\"\"\"\n\n    data_bytes = bytes.fromhex(data)\n    if len(data_bytes) >= size:\n        return data_bytes\n    padding_length = size - len(data_bytes)\n    return bytes(padding_length) + data_bytes\n\n\ndef add_padding(\n    message: bytes, size: DataSize = DataSize.Bytes_512, allow_truncation: bool = False\n) -> bytes:\n    \"\"\"Add padding to message in bytes.\"\"\"\n\n    message_length = len(message)\n    padded_data_size = size - _PAD_INDICATOR_SIZE\n    if message_length > padded_data_size:\n        if allow_truncation:\n            message_length = padded_data_size\n        else:\n            raise TruncationError(\n                \"Padded data exceeds allowed padded data size of {padded_data_size}.\"\n            )\n    padding_length = padded_data_size - message_length\n    leading_byte = padding_length.to_bytes(_PAD_INDICATOR_SIZE, byteorder=_BYTE_ORDER)\n    padded = leading_byte + message[:message_length] + _PAD_BYTE * padding_length\n    return padded\n\n\ndef remove_padding(padded: bytes, size: DataSize = DataSize.Bytes_512) -> bytes:\n    \"\"\"Remove padding from padded message in bytes.\"\"\"\n\n    padding_length = int.from_bytes(padded[:_PAD_INDICATOR_SIZE], byteorder=_BYTE_ORDER)\n    message_end = size - padding_length\n    return padded[_PAD_INDICATOR_SIZE:message_end]\n"
  },
  {
    "path": "src/electionguard/chaum_pedersen.py",
    "content": "# pylint: disable=too-many-instance-attributes\nfrom dataclasses import dataclass\n\nfrom .elgamal import ElGamalCiphertext\nfrom .group import (\n    ElementModQ,\n    ElementModP,\n    g_pow_p,\n    mult_p,\n    pow_p,\n    a_minus_b_q,\n    a_plus_bc_q,\n    add_q,\n    negate_q,\n    int_to_q,\n    ZERO_MOD_Q,\n)\nfrom .hash import hash_elems\nfrom .logs import log_warning\nfrom .nonces import Nonces\nfrom .proof import Proof, ProofUsage\n\n\n@dataclass\nclass DisjunctiveChaumPedersenProof(Proof):\n    \"\"\"\n    Representation of disjunctive Chaum Pederson proof\n    \"\"\"\n\n    proof_zero_pad: ElementModP\n    \"\"\"a0 in the spec\"\"\"\n    proof_zero_data: ElementModP\n    \"\"\"b0 in the spec\"\"\"\n    proof_one_pad: ElementModP\n    \"\"\"a1 in the spec\"\"\"\n    proof_one_data: ElementModP\n    \"\"\"b1 in the spec\"\"\"\n    proof_zero_challenge: ElementModQ\n    \"\"\"c0 in the spec\"\"\"\n    proof_one_challenge: ElementModQ\n    \"\"\"c1 in the spec\"\"\"\n    challenge: ElementModQ\n    \"\"\"c in the spec\"\"\"\n    proof_zero_response: ElementModQ\n    \"\"\"proof_zero_response in the spec\"\"\"\n    proof_one_response: ElementModQ\n    \"\"\"proof_one_response in the spec\"\"\"\n    usage: ProofUsage = ProofUsage.SelectionValue\n    \"\"\"a description of how to use this proof\"\"\"\n\n    def __post_init__(self) -> None:\n        super().__init__()\n\n    def is_valid(\n        self, message: ElGamalCiphertext, k: ElementModP, q: ElementModQ\n    ) -> bool:\n        \"\"\"\n        Validates a \"disjunctive\" Chaum-Pedersen (zero or one) proof.\n\n        :param message: The ciphertext message\n        :param k: The public key of the election\n        :param q: The extended base hash of the election\n        :return: True if everything is consistent. False otherwise.\n        \"\"\"\n\n        alpha = message.pad\n        beta = message.data\n        a0 = self.proof_zero_pad\n        b0 = self.proof_zero_data\n        a1 = self.proof_one_pad\n        b1 = self.proof_one_data\n        c0 = self.proof_zero_challenge\n        c1 = self.proof_one_challenge\n        c = self.challenge\n        v0 = self.proof_zero_response\n        v1 = self.proof_one_response\n        in_bounds_alpha = alpha.is_valid_residue()\n        in_bounds_beta = beta.is_valid_residue()\n        in_bounds_a0 = a0.is_valid_residue()\n        in_bounds_b0 = b0.is_valid_residue()\n        in_bounds_a1 = a1.is_valid_residue()\n        in_bounds_b1 = b1.is_valid_residue()\n        in_bounds_c0 = c0.is_in_bounds()\n        in_bounds_c1 = c1.is_in_bounds()\n        in_bounds_v0 = v0.is_in_bounds()\n        in_bounds_v1 = v1.is_in_bounds()\n        consistent_c = add_q(c0, c1) == c == hash_elems(q, alpha, beta, a0, b0, a1, b1)\n        consistent_gv0 = g_pow_p(v0) == mult_p(a0, pow_p(alpha, c0))\n        consistent_gv1 = g_pow_p(v1) == mult_p(a1, pow_p(alpha, c1))\n        consistent_kv0 = pow_p(k, v0) == mult_p(b0, pow_p(beta, c0))\n        consistent_gc1kv1 = mult_p(g_pow_p(c1), pow_p(k, v1)) == mult_p(\n            b1, pow_p(beta, c1)\n        )\n\n        success = (\n            in_bounds_alpha\n            and in_bounds_beta\n            and in_bounds_a0\n            and in_bounds_b0\n            and in_bounds_a1\n            and in_bounds_b1\n            and in_bounds_c0\n            and in_bounds_c1\n            and in_bounds_v0\n            and in_bounds_v1\n            and consistent_c\n            and consistent_gv0\n            and consistent_gv1\n            and consistent_kv0\n            and consistent_gc1kv1\n        )\n\n        if not success:\n            log_warning(\n                \"found an invalid Disjunctive Chaum-Pedersen proof: \"\n                + str(\n                    {\n                        \"in_bounds_alpha\": in_bounds_alpha,\n                        \"in_bounds_beta\": in_bounds_beta,\n                        \"in_bounds_a0\": in_bounds_a0,\n                        \"in_bounds_b0\": in_bounds_b0,\n                        \"in_bounds_a1\": in_bounds_a1,\n                        \"in_bounds_b1\": in_bounds_b1,\n                        \"in_bounds_c0\": in_bounds_c0,\n                        \"in_bounds_c1\": in_bounds_c1,\n                        \"in_bounds_v0\": in_bounds_v0,\n                        \"in_bounds_v1\": in_bounds_v1,\n                        \"consistent_c\": consistent_c,\n                        \"consistent_gv0\": consistent_gv0,\n                        \"consistent_gv1\": consistent_gv1,\n                        \"consistent_kv0\": consistent_kv0,\n                        \"consistent_gc1kv1\": consistent_gc1kv1,\n                        \"k\": k,\n                        \"proof\": self,\n                    }\n                ),\n            )\n        return success\n\n\n@dataclass\nclass ChaumPedersenProof(Proof):\n    \"\"\"\n    Representation of a generic Chaum-Pedersen Zero Knowledge proof\n    \"\"\"\n\n    pad: ElementModP\n    \"\"\"a in the spec\"\"\"\n    data: ElementModP\n    \"\"\"b in the spec\"\"\"\n    challenge: ElementModQ\n    \"\"\"c in the spec\"\"\"\n    response: ElementModQ\n    \"\"\"v in the spec\"\"\"\n    usage: ProofUsage = ProofUsage.SecretValue\n    \"\"\"a description of how to use this proof\"\"\"\n\n    def __post_init__(self) -> None:\n        super().__init__()\n\n    def is_valid(\n        self,\n        message: ElGamalCiphertext,\n        k: ElementModP,\n        m: ElementModP,\n        q: ElementModQ,\n    ) -> bool:\n        \"\"\"\n        Validates a Chaum-Pedersen proof.\n        e.g.\n        - The given value 𝑣𝑖 is in the set Z𝑞\n        - The given values 𝑎𝑖 and 𝑏𝑖 are both in the set Z𝑞^𝑟\n        - The challenge value 𝑐 satisfies 𝑐 = 𝐻(𝑄, (𝐴, 𝐵), (𝑎 , 𝑏 ), 𝑀 ).\n        - that the equations 𝑔^𝑣𝑖 = 𝑎𝑖𝐾^𝑐𝑖 mod 𝑝 and 𝐴^𝑣𝑖 = 𝑏𝑖𝑀𝑖^𝑐𝑖 mod 𝑝 are satisfied.\n\n        :param message: The ciphertext message\n        :param k: The public key corresponding to the private key used to encrypt\n                  (e.g. the Guardian public election key)\n        :param m: The value being checked for validity\n        :param q: The extended base hash of the election\n        :return: True if everything is consistent. False otherwise.\n        \"\"\"\n        alpha = message.pad\n        beta = message.data\n        a = self.pad\n        b = self.data\n        c = self.challenge\n        v = self.response\n        in_bounds_alpha = alpha.is_valid_residue()\n        in_bounds_beta = beta.is_valid_residue()\n        in_bounds_k = k.is_valid_residue()\n        in_bounds_m = m.is_valid_residue()\n        in_bounds_a = a.is_valid_residue()\n        in_bounds_b = b.is_valid_residue()\n        in_bounds_c = c.is_in_bounds()\n        in_bounds_v = v.is_in_bounds()\n        in_bounds_q = q.is_in_bounds()\n\n        same_c = c == hash_elems(q, alpha, beta, a, b, m)\n        consistent_gv = (\n            in_bounds_v\n            and in_bounds_a\n            and in_bounds_c\n            # The equation 𝑔^𝑣𝑖 = 𝑎𝑖𝐾^𝑐𝑖\n            and g_pow_p(v) == mult_p(a, pow_p(k, c))\n        )\n\n        # The equation 𝐴^𝑣𝑖 = 𝑏𝑖𝑀𝑖^𝑐𝑖 mod 𝑝\n        consistent_av = (\n            in_bounds_alpha\n            and in_bounds_b\n            and in_bounds_c\n            and in_bounds_v\n            and pow_p(alpha, v) == mult_p(b, pow_p(m, c))\n        )\n\n        success = (\n            in_bounds_alpha\n            and in_bounds_beta\n            and in_bounds_k\n            and in_bounds_m\n            and in_bounds_a\n            and in_bounds_b\n            and in_bounds_c\n            and in_bounds_v\n            and in_bounds_q\n            and same_c\n            and consistent_gv\n            and consistent_av\n        )\n\n        if not success:\n            log_warning(\n                \"found an invalid Chaum-Pedersen proof: \"\n                + str(\n                    {\n                        \"in_bounds_alpha\": in_bounds_alpha,\n                        \"in_bounds_beta\": in_bounds_beta,\n                        \"in_bounds_k\": in_bounds_k,\n                        \"in_bounds_m\": in_bounds_m,\n                        \"in_bounds_a\": in_bounds_a,\n                        \"in_bounds_b\": in_bounds_b,\n                        \"in_bounds_c\": in_bounds_c,\n                        \"in_bounds_v\": in_bounds_v,\n                        \"in_bounds_q\": in_bounds_q,\n                        \"same_c\": same_c,\n                        \"consistent_gv\": consistent_gv,\n                        \"consistent_av\": consistent_av,\n                        \"k\": k,\n                        \"q\": q,\n                        \"proof\": self,\n                    }\n                ),\n            )\n        return success\n\n\n@dataclass\nclass ConstantChaumPedersenProof(Proof):\n    \"\"\"\n    Representation of constant Chaum Pederson proof\n    \"\"\"\n\n    pad: ElementModP\n    \"\"\"a in the spec\"\"\"\n    data: ElementModP\n    \"b in the spec\"\n    challenge: ElementModQ\n    \"c in the spec\"\n    response: ElementModQ\n    \"v in the spec\"\n    constant: int\n    \"\"\"constant value\"\"\"\n    usage: ProofUsage = ProofUsage.SelectionLimit\n    \"\"\"a description of how to use this proof\"\"\"\n\n    def __post_init__(self) -> None:\n        super().__init__()\n\n    def is_valid(\n        self, message: ElGamalCiphertext, k: ElementModP, q: ElementModQ\n    ) -> bool:\n        \"\"\"\n        Validates a \"constant\" Chaum-Pedersen proof.\n        e.g. that the equations 𝑔𝑉 = 𝑎𝐴𝐶 mod 𝑝 and 𝑔𝐿𝐾𝑣 = 𝑏𝐵𝐶 mod 𝑝 are satisfied.\n\n        :param message: The ciphertext message\n        :param k: The public key of the election\n        :param q: The extended base hash of the election\n        :return: True if everything is consistent. False otherwise.\n        \"\"\"\n\n        alpha = message.pad\n        beta = message.data\n        a = self.pad\n        b = self.data\n        c = self.challenge\n        v = self.response\n        constant = self.constant\n        in_bounds_alpha = alpha.is_valid_residue()\n        in_bounds_beta = beta.is_valid_residue()\n        in_bounds_a = a.is_valid_residue()\n        in_bounds_b = b.is_valid_residue()\n        in_bounds_c = c.is_in_bounds()\n        in_bounds_v = v.is_in_bounds()\n        tmp = int_to_q(constant)\n        if tmp is None:\n            constant_q = ZERO_MOD_Q\n            in_bounds_constant = False\n        else:\n            constant_q = tmp\n            in_bounds_constant = True\n\n        # this is an arbitrary constant check to verify that decryption will be performant\n        # in some use cases this value may need to be increased\n        sane_constant = 0 <= constant < 1_000_000_000\n        same_c = c == hash_elems(q, alpha, beta, a, b)\n        consistent_gv = (\n            in_bounds_v\n            and in_bounds_a\n            and in_bounds_alpha\n            and in_bounds_c\n            # The equation 𝑔^𝑉 = 𝑎𝐴^𝐶 mod 𝑝\n            and g_pow_p(v) == mult_p(a, pow_p(alpha, c))\n        )\n\n        # The equation 𝑔^𝐿𝐾^𝑣 = 𝑏𝐵^𝐶 mod 𝑝\n        consistent_kv = in_bounds_constant and mult_p(\n            g_pow_p(mult_p(c, constant_q)), pow_p(k, v)\n        ) == mult_p(b, pow_p(beta, c))\n\n        success = (\n            in_bounds_alpha\n            and in_bounds_beta\n            and in_bounds_a\n            and in_bounds_b\n            and in_bounds_c\n            and in_bounds_v\n            and same_c\n            and in_bounds_constant\n            and sane_constant\n            and consistent_gv\n            and consistent_kv\n        )\n\n        if not success:\n            log_warning(\n                \"found an invalid Constant Chaum-Pedersen proof: \"\n                + str(\n                    {\n                        \"in_bounds_alpha\": in_bounds_alpha,\n                        \"in_bounds_beta\": in_bounds_beta,\n                        \"in_bounds_a\": in_bounds_a,\n                        \"in_bounds_b\": in_bounds_b,\n                        \"in_bounds_c\": in_bounds_c,\n                        \"in_bounds_v\": in_bounds_v,\n                        \"in_bounds_constant\": in_bounds_constant,\n                        \"sane_constant\": sane_constant,\n                        \"same_c\": same_c,\n                        \"consistent_gv\": consistent_gv,\n                        \"consistent_kv\": consistent_kv,\n                        \"k\": k,\n                        \"proof\": self,\n                    }\n                ),\n            )\n        return success\n\n\ndef make_disjunctive_chaum_pedersen(\n    message: ElGamalCiphertext,\n    r: ElementModQ,\n    k: ElementModP,\n    q: ElementModQ,\n    seed: ElementModQ,\n    plaintext: int,\n) -> DisjunctiveChaumPedersenProof:\n    \"\"\"\n    Produce a \"disjunctive\" proof that an encryption of a given plaintext is either an encrypted zero or one.\n    This is just a front-end helper for `make_disjunctive_chaum_pedersen_zero` and\n    `make_disjunctive_chaum_pedersen_one`.\n\n    :param message: An ElGamal ciphertext\n    :param r: The nonce used creating the ElGamal ciphertext\n    :param k: The ElGamal public key for the election\n    :param q: A value used when generating the challenge,\n                        usually the election extended base hash (𝑄')\n    :param seed: Used to generate other random values here\n    :param plaintext: Zero or one\n    \"\"\"\n\n    assert (\n        0 <= plaintext <= 1\n    ), \"make_disjunctive_chaum_pedersen only supports plaintexts of 0 or 1\"\n    if plaintext == 0:\n        return make_disjunctive_chaum_pedersen_zero(message, r, k, q, seed)\n    return make_disjunctive_chaum_pedersen_one(message, r, k, q, seed)\n\n\ndef make_disjunctive_chaum_pedersen_zero(\n    message: ElGamalCiphertext,\n    r: ElementModQ,\n    k: ElementModP,\n    q: ElementModQ,\n    seed: ElementModQ,\n) -> DisjunctiveChaumPedersenProof:\n    \"\"\"\n    Produces a \"disjunctive\" proof that an encryption of zero is either an encrypted zero or one.\n\n    :param message: An ElGamal ciphertext\n    :param r: The nonce used creating the ElGamal ciphertext\n    :param k: The ElGamal public key for the election\n    :param q: A value used when generating the challenge,\n                        usually the election extended base hash (𝑄')\n    :param seed: Used to generate other random values here\n    \"\"\"\n    alpha = message.pad\n    beta = message.data\n\n    # Pick three random numbers in Q.\n    c1, v, u0 = Nonces(seed, \"disjoint-chaum-pedersen-proof\")[0:3]\n\n    # Compute the NIZKP\n    a0 = g_pow_p(u0)\n    b0 = pow_p(k, u0)\n    a1 = g_pow_p(v)\n    b1 = mult_p(pow_p(k, v), g_pow_p(c1))\n    c = hash_elems(q, alpha, beta, a0, b0, a1, b1)\n    c0 = a_minus_b_q(c, c1)\n    v0 = a_plus_bc_q(u0, c0, r)\n    v1 = a_plus_bc_q(v, c1, r)\n\n    return DisjunctiveChaumPedersenProof(a0, b0, a1, b1, c0, c1, c, v0, v1)\n\n\ndef make_disjunctive_chaum_pedersen_one(\n    message: ElGamalCiphertext,\n    r: ElementModQ,\n    k: ElementModP,\n    q: ElementModQ,\n    seed: ElementModQ,\n) -> DisjunctiveChaumPedersenProof:\n    \"\"\"\n    Produces a \"disjunctive\" proof that an encryption of one is either an encrypted zero or one.\n\n    :param message: An ElGamal ciphertext\n    :param r: The nonce used creating the ElGamal ciphertext\n    :param k: The ElGamal public key for the election\n    :param q: A value used when generating the challenge,\n                        usually the election extended base hash (𝑄')\n    :param seed: Used to generate other random values here\n    \"\"\"\n    alpha = message.pad\n    beta = message.data\n\n    # Pick three random numbers in Q.\n    w, v, u1 = Nonces(seed, \"disjoint-chaum-pedersen-proof\")[0:3]\n\n    # Compute the NIZKP\n    a0 = g_pow_p(v)\n    b0 = mult_p(pow_p(k, v), g_pow_p(w))\n    a1 = g_pow_p(u1)\n    b1 = pow_p(k, u1)\n    c = hash_elems(q, alpha, beta, a0, b0, a1, b1)\n    c0 = negate_q(w)\n    c1 = add_q(c, w)\n    v0 = a_plus_bc_q(v, c0, r)\n    v1 = a_plus_bc_q(u1, c1, r)\n\n    return DisjunctiveChaumPedersenProof(a0, b0, a1, b1, c0, c1, c, v0, v1)\n\n\ndef make_chaum_pedersen(\n    message: ElGamalCiphertext,\n    s: ElementModQ,\n    m: ElementModP,\n    seed: ElementModQ,\n    hash_header: ElementModQ,\n) -> ChaumPedersenProof:\n    \"\"\"\n    Produces a proof that a given value corresponds to a specific encryption.\n    computes: 𝑀 =𝐴^𝑠𝑖 mod 𝑝 and 𝐾𝑖 = 𝑔^𝑠𝑖 mod 𝑝\n\n    :param message: An ElGamal ciphertext\n    :param s: The nonce or secret used to derive the value\n    :param m: The value we are trying to prove\n    :param seed: Used to generate other random values here\n    :param hash_header: A value used when generating the challenge,\n                        usually the election extended base hash (𝑄')\n    \"\"\"\n    alpha = message.pad\n    beta = message.data\n\n    # Pick one random number in Q.\n    u = Nonces(seed, \"constant-chaum-pedersen-proof\")[0]\n    a = g_pow_p(u)  # 𝑔^𝑢𝑖 mod 𝑝\n    b = pow_p(alpha, u)  # 𝐴^𝑢𝑖 mod 𝑝\n    c = hash_elems(hash_header, alpha, beta, a, b, m)  # sha256(𝑄', A, B, a𝑖, b𝑖, 𝑀𝑖)\n    v = a_plus_bc_q(u, c, s)  # (𝑢𝑖 + 𝑐𝑖𝑠𝑖) mod 𝑞\n\n    return ChaumPedersenProof(a, b, c, v)\n\n\ndef make_constant_chaum_pedersen(\n    message: ElGamalCiphertext,\n    constant: int,\n    r: ElementModQ,\n    k: ElementModP,\n    seed: ElementModQ,\n    hash_header: ElementModQ,\n) -> ConstantChaumPedersenProof:\n    \"\"\"\n    Produces a proof that a given encryption corresponds to a specific total value.\n\n    :param message: An ElGamal ciphertext\n    :param constant: The plaintext constant value used to make the ElGamal ciphertext (L in the spec)\n    :param r: The aggregate nonce used creating the ElGamal ciphertext\n    :param k: The ElGamal public key for the election\n    :param seed: Used to generate other random values here\n    :param hash_header: A value used when generating the challenge,\n                        usually the election extended base hash (𝑄')\n    \"\"\"\n    alpha = message.pad\n    beta = message.data\n\n    # Pick one random number in Q.\n    u = Nonces(seed, \"constant-chaum-pedersen-proof\")[0]\n    a = g_pow_p(u)  # 𝑔^𝑢𝑖 mod 𝑝\n    b = pow_p(k, u)  # 𝐴^𝑢𝑖 mod 𝑝\n    c = hash_elems(hash_header, alpha, beta, a, b)  # sha256(𝑄', A, B, a, b)\n    v = a_plus_bc_q(u, c, r)\n\n    return ConstantChaumPedersenProof(a, b, c, v, constant)\n"
  },
  {
    "path": "src/electionguard/constants.py",
    "content": "\"\"\"Creating and managing mathematic constants for the election.\"\"\"\n\nfrom os import getenv\n\nfrom dataclasses import dataclass\nfrom enum import Enum\n\nfrom .big_integer import BigInteger\n\n\n@dataclass\nclass ElectionConstants:\n    \"\"\"The constants for mathematical functions during the election.\"\"\"\n\n    large_prime: BigInteger\n    \"\"\"large prime or p\"\"\"\n\n    small_prime: BigInteger\n    \"\"\"small prime or q\"\"\"\n\n    cofactor: BigInteger  # (p - 1) / q\n    \"\"\"cofactor or r\"\"\"\n\n    generator: BigInteger\n    \"\"\"generator or g\"\"\"  # 2^r mod p\n\n\ndef create_constants(\n    large_prime: int, small_prime: int, cofactor: int, generator: int\n) -> ElectionConstants:\n    \"\"\"Create constants for election.\"\"\"\n    return ElectionConstants(\n        BigInteger(large_prime),\n        BigInteger(small_prime),\n        BigInteger(cofactor),\n        BigInteger(generator),\n    )\n\n\n# pylint: disable=line-too-long\nSTANDARD_CONSTANTS = create_constants(\n    1044388881413152506691752710716624382579964249047383780384233483283953907971553643537729993126875883902173634017777416360502926082946377942955704498542097614841825246773580689398386320439747911160897731551074903967243883427132918813748016269754522343505285898816777211761912392772914485521155521641049273446207578961939840619466145806859275053476560973295158703823395710210329314709715239251736552384080845836048778667318931418338422443891025911884723433084701207771901944593286624979917391350564662632723703007964229849154756196890615252286533089643184902706926081744149289517418249153634178342075381874131646013444796894582106870531535803666254579602632453103741452569793905551901541856173251385047414840392753585581909950158046256810542678368121278509960520957624737942914600310646609792665012858397381435755902851312071248102599442308951327039250818892493767423329663783709190716162023529669217300939783171415808233146823000766917789286154006042281423733706462905243774854543127239500245873582012663666430583862778167369547603016344242729592244544608279405999759391099769165589722584216017468464576217318557948461765770700913220460557598574717173408252913596242281190298966500668625620138188265530628036538314433100326660047110143,\n    115792089237316195423570985008687907853269984665640564039457584007913129639747,  # pow(2, 256) - 189\n    9019518416950528558373478086511232658951474842525520401496114928154304263969655687927867442562559311457926593510757267649063628681241064260953609180947464800958467390949485096429653122916928704841547265126247408167856620024815508684472819746384115369148322548696439327979752948311712506113890045287907335656308945630141969472484100558565879585476547782717283106837945923693806973017510492730838409381014701258202694245760602718602550739205297257940969992371799325870179746191672464736721424617639973324090288952006260483222894269928179970153634220390287255837625331668555933039199194619824375869291271098935000699785346405055160394688637074599519052655517388596327473273906029869030988064607361165803129718773877185415445291671089029845994683414682274353665003204293107284473196033588697845087556526514092678744031772226855409523354476737660407619436531080189837076164818131039104397776628128325247709678431023369197272126578394856752060591013812807437681624251867074769638052097737959472027002770963255207757153746376691827309573603635608169799503216990026029763868313819255248026666854405409059422844776556067163611304891154793770115766608153679099327786,\n    119359756198641231858139651428439585561105914902686985078252796680474637856752833978884422594516170665312423393830118608408063594508087813277769835084746883589963798527237870817233369094387978405585759195339509768803496494994109693743279157584139079471178850751266233150727771094796709619646350222242437970473900636242584673413224137139139346254912172628651028694427789523683070264102332413084663100402635889283790741342401259356660761075766365672754329863241692760862540151023800163269173550320623249398630247531924855997863109776955214403044727497968354022277828136634059011708099779241302941071701051050378539485717425482151777277387633806111112178267035315726401285294598397677116389893642725498831127977915200359151833767358091365292230363248410124916825814514852703770457024102738694375502049388804979035628232209959549199366986471874840784466132903083308458356458177839111623113116525230200791649979270165318729763550486200224695556789081331596212761936863634467236301450039399776963661755684863012396788149479256016157814129329192490798309248914535389650594573156725696657302152874510063002532052622638033113978672254680147128450265983503193865576932419282003012093526302631221491418211528781074474515924597472841036553107847,\n)\n\n# TEST ONLY\n# These constants serve as sets of primes for future developers\n# Currently, all the sets are all valid but may break certain tests\n# As tests adapt, these constants can be used to speed up tests\nEXTRA_SMALL_TEST_CONSTANTS = create_constants(157, 13, 12, 16)\nSMALL_TEST_CONSTANTS = create_constants(503, 251, 2, 5)\nMEDIUM_TEST_CONSTANTS = create_constants(65267, 32633, 2, 3)\nLARGE_TEST_CONSTANTS = create_constants(\n    18446744073704586917, 65521, 281539415968996, 15463152587872997502\n)\n\n\nclass PrimeOption(Enum):\n    \"\"\"Option for primes to determine election constants.\"\"\"\n\n    Standard = \"Standard\"\n    TestOnly = \"TestOnly\"\n\n\ndef get_constants() -> ElectionConstants:\n    \"\"\"Get constants for the election by the option for the primes.\"\"\"\n    env_option = getenv(\"PRIME_OPTION\")\n    option: PrimeOption = (\n        PrimeOption(env_option) if env_option is not None else PrimeOption.Standard\n    )\n\n    option_map = {\n        PrimeOption.Standard: STANDARD_CONSTANTS,\n        PrimeOption.TestOnly: LARGE_TEST_CONSTANTS,\n    }\n    return option_map.get(option) or STANDARD_CONSTANTS\n\n\ndef get_large_prime() -> int:\n    return int(get_constants().large_prime.value)\n\n\ndef get_small_prime() -> int:\n    return int(get_constants().small_prime.value)\n\n\ndef get_cofactor() -> int:\n    return int(get_constants().cofactor.value)\n\n\ndef get_generator() -> int:\n    return int(get_constants().generator.value)\n"
  },
  {
    "path": "src/electionguard/data_store.py",
    "content": "from collections.abc import Mapping\n\nfrom typing import (\n    Dict,\n    Generic,\n    Iterable,\n    Iterator,\n    List,\n    Optional,\n    Tuple,\n    TypeVar,\n)\n\n\n_T = TypeVar(\"_T\")\n_U = TypeVar(\"_U\")\n\n\nclass DataStore(Generic[_T, _U]):\n    \"\"\"\n    A lightweight convenience wrapper around a dictionary for data storage.\n    This implementation defines the common interface used to access stored\n    state elements.\n    \"\"\"\n\n    _store: Dict[_T, _U]\n\n    def __init__(self) -> None:\n        self._store = {}\n\n    def __iter__(self) -> Iterator:\n        return iter(self._store.items())\n\n    def all(self) -> List[_U]:\n        \"\"\"\n        Get all `SubmittedBallot` from the store\n        \"\"\"\n        return list(self._store.values())\n\n    def clear(self) -> None:\n        \"\"\"\n        Clear data from store\n        \"\"\"\n        self._store.clear()\n\n    def get(self, key: _T) -> Optional[_U]:\n        \"\"\"\n        Get value in store\n        :param key: key\n        :return: value if found\n        \"\"\"\n        return self._store.get(key)\n\n    def items(self) -> Iterable[Tuple[_T, _U]]:\n        \"\"\"\n        Gets all items in store as list\n        :return: List of (key, value)\n        \"\"\"\n        return self._store.items()\n\n    def keys(self) -> Iterable[_T]:\n        \"\"\"\n        Gets all keys in store as list\n        :return: List of keys\n        \"\"\"\n        return self._store.keys()\n\n    def __len__(self) -> int:\n        \"\"\"\n        Get length or count of store\n        :return: Count in store\n        \"\"\"\n        return len(self._store)\n\n    def pop(self, key: _T) -> Optional[_U]:\n        \"\"\"\n        Pop an object from the store if it exists.\n        :param key: key\n        \"\"\"\n        if key in self._store:\n            return self._store.pop(key)\n        return None\n\n    def set(self, key: _T, value: _U) -> None:\n        \"\"\"\n        Create or update a new value in store\n        :param key: key\n        :param value: value\n        \"\"\"\n        self._store[key] = value\n\n    def values(self) -> Iterable[_U]:\n        \"\"\"\n        Gets all values in store as list\n        :return: List of values\n        \"\"\"\n        return self._store.values()\n\n\nclass ReadOnlyDataStore(Generic[_T, _U], Mapping):\n    \"\"\"\n    A readonly view to a Data store\n    \"\"\"\n\n    def __init__(self, data: DataStore[_T, _U]):\n        self._data: DataStore[_T, _U] = data\n\n    def __getitem__(self, key: _T) -> Optional[_U]:\n        return self._data.get(key)\n\n    def __len__(self) -> int:\n        return len(self._data)\n\n    def __iter__(self) -> Iterator:\n        return iter(self._data.items())\n\n    def __eq__(self, other: object) -> bool:\n        if not isinstance(other, ReadOnlyDataStore):\n            return False\n        return ReadOnlyDataStore.__eq__(self, other)\n"
  },
  {
    "path": "src/electionguard/decrypt_with_secrets.py",
    "content": "from typing import List, Optional\n\nfrom .ballot import (\n    CiphertextBallot,\n    CiphertextBallotContest,\n    CiphertextBallotSelection,\n    PlaintextBallot,\n    PlaintextBallotContest,\n    PlaintextBallotSelection,\n)\nfrom .elgamal import ElGamalPublicKey, ElGamalSecretKey\nfrom .group import ElementModQ\nfrom .logs import log_warning\nfrom .manifest import (\n    InternalManifest,\n    ContestDescriptionWithPlaceholders,\n    SelectionDescription,\n)\nfrom .nonces import Nonces\n\nfrom .utils import get_optional\n\n# The Methods in this file can be used to decrypt values if private keys or nonces are known\n\n\ndef decrypt_selection_with_secret(\n    selection: CiphertextBallotSelection,\n    description: SelectionDescription,\n    public_key: ElGamalPublicKey,\n    secret_key: ElGamalSecretKey,\n    crypto_extended_base_hash: ElementModQ,\n    suppress_validity_check: bool = False,\n) -> Optional[PlaintextBallotSelection]:\n    \"\"\"\n    Decrypt the specified `CiphertextBallotSelection` within the context of the specified selection.\n\n    :param selection: the selection to decrypt\n    :param description: the qualified selection metadata\n    :param public_key: the public key for the election (K)\n    :param secret_key: the known secret key used to generate the public key for this election\n    :param crypto_extended_base_hash: the extended base hash code (𝑄') for the election\n    :param suppress_validity_check: do not validate the encryption prior to decrypting (useful for tests)\n    \"\"\"\n\n    if not suppress_validity_check and not selection.is_valid_encryption(\n        description.crypto_hash(), public_key, crypto_extended_base_hash\n    ):\n        log_warning(f\"selection: {selection.object_id} failed validity check\")\n        return None\n\n    plaintext_vote = selection.ciphertext.decrypt(secret_key)\n\n    # TODO: ISSUE #47: handle decryption of the extradata field if needed\n\n    return PlaintextBallotSelection(\n        selection.object_id,\n        plaintext_vote,\n        selection.is_placeholder_selection,\n    )\n\n\ndef decrypt_selection_with_nonce(\n    selection: CiphertextBallotSelection,\n    description: SelectionDescription,\n    public_key: ElGamalPublicKey,\n    crypto_extended_base_hash: ElementModQ,\n    nonce_seed: Optional[ElementModQ] = None,\n    suppress_validity_check: bool = False,\n) -> Optional[PlaintextBallotSelection]:\n    \"\"\"\n    Decrypt the specified `CiphertextBallotSelection` within the context of the specified selection.\n\n    :param selection: the contest selection to decrypt\n    :param description: the qualified selection metadata that may be a placeholder selection\n    :param public_key: the public key for the election (K)\n    :param crypto_extended_base_hash: the extended base hash code (𝑄') for the election\n    :param nonce_seed: the optional nonce that was seeded to the encryption function.\n                        if no value is provided, the nonce field from the selection is used\n    :param suppress_validity_check: do not validate the encryption prior to decrypting (useful for tests)\n    \"\"\"\n\n    if not suppress_validity_check and not selection.is_valid_encryption(\n        description.crypto_hash(), public_key, crypto_extended_base_hash\n    ):\n        log_warning(f\"selection: {selection.object_id} failed validity check\")\n        return None\n\n    if nonce_seed is None:\n        nonce = selection.nonce\n    else:\n        nonce_sequence = Nonces(description.crypto_hash(), nonce_seed)\n        nonce = nonce_sequence[description.sequence_order]\n\n    if nonce is None:\n        log_warning(\n            f\"missing nonce value.  decrypt could not derive a nonce value for selection {selection.object_id}\"\n        )\n        return None\n\n    if selection.nonce is not None and nonce != selection.nonce:\n        log_warning(\n            f\"decrypt could not verify a nonce value for selection {selection.object_id}\"\n        )\n        return None\n\n    plaintext_vote = selection.ciphertext.decrypt_known_nonce(public_key, nonce)\n\n    # TODO: ISSUE #35: encrypt/decrypt: handle decryption of the extradata field if needed\n\n    return PlaintextBallotSelection(\n        selection.object_id,\n        plaintext_vote,\n        selection.is_placeholder_selection,\n    )\n\n\ndef decrypt_contest_with_secret(\n    contest: CiphertextBallotContest,\n    description: ContestDescriptionWithPlaceholders,\n    public_key: ElGamalPublicKey,\n    secret_key: ElGamalSecretKey,\n    crypto_extended_base_hash: ElementModQ,\n    suppress_validity_check: bool = False,\n    remove_placeholders: bool = True,\n) -> Optional[PlaintextBallotContest]:\n    \"\"\"\n    Decrypt the specified `CiphertextBallotContest` within the context of the specified contest.\n\n    :param contest: the contest to decrypt\n    :param description: the qualified contest metadata that includes placeholder selections\n    :param public_key: the public key for the election (K)\n    :param secret_key: the known secret key used to generate the public key for this election\n    :param crypto_extended_base_hash: the extended base hash code (𝑄') for the election\n    :param suppress_validity_check: do not validate the encryption prior to decrypting (useful for tests)\n    :param remove_placeholders: filter out placeholder ciphertext selections after decryption\n    \"\"\"\n\n    if not suppress_validity_check and not contest.is_valid_encryption(\n        description.crypto_hash(), public_key, crypto_extended_base_hash\n    ):\n        log_warning(f\"contest: {contest.object_id} failed validity check\")\n        return None\n\n    plaintext_selections: List[PlaintextBallotSelection] = []\n    for selection in contest.ballot_selections:\n        selection_description = description.selection_for(selection.object_id)\n        plaintext_selection = decrypt_selection_with_secret(\n            selection,\n            get_optional(selection_description),\n            public_key,\n            secret_key,\n            crypto_extended_base_hash,\n            suppress_validity_check,\n        )\n        if plaintext_selection is not None:\n            if (\n                not remove_placeholders\n                or not plaintext_selection.is_placeholder_selection\n            ):\n                plaintext_selections.append(plaintext_selection)\n        else:\n            log_warning(\n                f\"decryption with secret failed for contest: {contest.object_id} selection: {selection.object_id}\"\n            )\n            return None\n\n    return PlaintextBallotContest(contest.object_id, plaintext_selections)\n\n\ndef decrypt_contest_with_nonce(\n    contest: CiphertextBallotContest,\n    description: ContestDescriptionWithPlaceholders,\n    public_key: ElGamalPublicKey,\n    crypto_extended_base_hash: ElementModQ,\n    nonce_seed: Optional[ElementModQ] = None,\n    suppress_validity_check: bool = False,\n    remove_placeholders: bool = True,\n) -> Optional[PlaintextBallotContest]:\n    \"\"\"\n    Decrypt the specified `CiphertextBallotContest` within the context of the specified contest.\n\n    :param contest: the contest to decrypt\n    :param description: the qualified contest metadata that includes placeholder selections\n    :param public_key: the public key for the election (K)\n    :param crypto_extended_base_hash: the extended base hash code (𝑄') for the election\n    :param nonce_seed: the optional nonce that was seeded to the encryption function\n                        if no value is provided, the nonce field from the contest is used\n    :param suppress_validity_check: do not validate the encryption prior to decrypting (useful for tests)\n    :param remove_placeholders: filter out placeholder ciphertext selections after decryption\n    \"\"\"\n    if not suppress_validity_check and not contest.is_valid_encryption(\n        description.crypto_hash(), public_key, crypto_extended_base_hash\n    ):\n        log_warning(f\"contest: {contest.object_id} failed validity check\")\n        return None\n\n    if nonce_seed is None:\n        nonce_seed = contest.nonce\n    else:\n        nonce_sequence = Nonces(description.crypto_hash(), nonce_seed)\n        nonce_seed = nonce_sequence[description.sequence_order]\n\n    if nonce_seed is None:\n        log_warning(\n            f\"missing nonce_seed value.  decrypt could not dewrive a nonce value for contest {contest.object_id}\"\n        )\n        return None\n\n    if contest.nonce is not None and nonce_seed != contest.nonce:\n        log_warning(\n            f\"decrypt could not verify a nonce_seed value for contest {contest.object_id}\"\n        )\n        return None\n\n    plaintext_selections: List[PlaintextBallotSelection] = []\n    for selection in contest.ballot_selections:\n        selection_description = description.selection_for(selection.object_id)\n        plaintext_selection = decrypt_selection_with_nonce(\n            selection,\n            get_optional(selection_description),\n            public_key,\n            crypto_extended_base_hash,\n            nonce_seed,\n            suppress_validity_check,\n        )\n        if plaintext_selection is not None:\n            if (\n                not remove_placeholders\n                or not plaintext_selection.is_placeholder_selection\n            ):\n                plaintext_selections.append(plaintext_selection)\n        else:\n            log_warning(\n                f\"decryption with nonce failed for contest: {contest.object_id} selection: {selection.object_id}\"\n            )\n            return None\n\n    return PlaintextBallotContest(contest.object_id, plaintext_selections)\n\n\ndef decrypt_ballot_with_secret(\n    ballot: CiphertextBallot,\n    internal_manifest: InternalManifest,\n    crypto_extended_base_hash: ElementModQ,\n    public_key: ElGamalPublicKey,\n    secret_key: ElGamalSecretKey,\n    suppress_validity_check: bool = False,\n    remove_placeholders: bool = True,\n) -> Optional[PlaintextBallot]:\n    \"\"\"\n    Decrypt the specified `CiphertextBallot` within the context of the specified election.\n\n    :param ballot: the ballot to decrypt\n    :param internal_manifest: the qualified election metadata that includes placeholder selections\n    :param crypto_extended_base_hash: the extended base hash code (𝑄') for the election\n    :param public_key: the public key for the election (K)\n    :param secret_key: the known secret key used to generate the public key for this election\n    :param suppress_validity_check: do not validate the encryption prior to decrypting (useful for tests)\n    :param remove_placeholders: filter out placeholder ciphertext selections after decryption\n    \"\"\"\n    if not suppress_validity_check and not ballot.is_valid_encryption(\n        internal_manifest.manifest_hash, public_key, crypto_extended_base_hash\n    ):\n        log_warning(f\"ballot: {ballot.object_id} failed validity check\")\n        return None\n\n    plaintext_contests: List[PlaintextBallotContest] = []\n\n    for contest in ballot.contests:\n        description = internal_manifest.contest_for(contest.object_id)\n        plaintext_contest = decrypt_contest_with_secret(\n            contest,\n            get_optional(description),\n            public_key,\n            secret_key,\n            crypto_extended_base_hash,\n            suppress_validity_check,\n            remove_placeholders,\n        )\n        if plaintext_contest is not None:\n            plaintext_contests.append(plaintext_contest)\n        else:\n            log_warning(\n                f\"decryption with nonce failed for ballot: {ballot.object_id} selection: {contest.object_id}\"\n            )\n            return None\n\n    return PlaintextBallot(ballot.object_id, ballot.style_id, plaintext_contests)\n\n\ndef decrypt_ballot_with_nonce(\n    ballot: CiphertextBallot,\n    internal_manifest: InternalManifest,\n    crypto_extended_base_hash: ElementModQ,\n    public_key: ElGamalPublicKey,\n    nonce: Optional[ElementModQ] = None,\n    suppress_validity_check: bool = False,\n    remove_placeholders: bool = True,\n) -> Optional[PlaintextBallot]:\n    \"\"\"\n    Decrypt the specified `CiphertextBallot` within the context of the specified election.\n\n    :param ballot: the ballot to decrypt\n    :param internal_manifest: the qualified election metadata that includes placeholder selections\n    :param crypto_extended_base_hash: the extended base hash code (𝑄') for the election\n    :param public_key: the public key for the election (K)\n    :param nonce: the optional master ballot nonce that was either seeded to, or gernated by the encryption function\n    :param suppress_validity_check: do not validate the encryption prior to decrypting (useful for tests)\n    :param remove_placeholders: filter out placeholder ciphertext selections after decryption\n    \"\"\"\n    if not suppress_validity_check and not ballot.is_valid_encryption(\n        internal_manifest.manifest_hash, public_key, crypto_extended_base_hash\n    ):\n        log_warning(f\"ballot: {ballot.object_id} failed validity check\")\n        return None\n\n    # Use the hashed representation included in the ballot\n    # or override with the provided values\n    if nonce is None:\n        nonce_seed = ballot.hashed_ballot_nonce()\n    else:\n        nonce_seed = CiphertextBallot.nonce_seed(\n            internal_manifest.manifest_hash, ballot.object_id, nonce\n        )\n\n    if nonce_seed is None:\n        log_warning(\n            f\"missing nonce_seed value. decrypt could not derive a nonce value for ballot {ballot.object_id}\"\n        )\n        return None\n\n    plaintext_contests: List[PlaintextBallotContest] = []\n\n    for contest in ballot.contests:\n        description = internal_manifest.contest_for(contest.object_id)\n        plaintext_contest = decrypt_contest_with_nonce(\n            contest,\n            get_optional(description),\n            public_key,\n            crypto_extended_base_hash,\n            nonce_seed,\n            suppress_validity_check,\n            remove_placeholders,\n        )\n        if plaintext_contest is not None:\n            plaintext_contests.append(plaintext_contest)\n        else:\n            log_warning(\n                f\"decryption with nonce failed for ballot: {ballot.object_id} selection: {contest.object_id}\"\n            )\n            return None\n\n    return PlaintextBallot(ballot.object_id, ballot.style_id, plaintext_contests)\n"
  },
  {
    "path": "src/electionguard/decrypt_with_shares.py",
    "content": "from typing import Dict, Optional, Tuple\n\n\nfrom .ballot import SubmittedBallot, CiphertextContest, CiphertextSelection\nfrom .decryption_share import (\n    CiphertextDecryptionSelection,\n    DecryptionShare,\n    get_shares_for_selection,\n)\nfrom .discrete_log import DiscreteLog\nfrom .group import ElementModP, ElementModQ, mult_p, div_p\nfrom .logs import log_warning\nfrom .manifest import (\n    ContestDescription,\n    Manifest,\n)\nfrom .tally import (\n    CiphertextTally,\n    PlaintextTally,\n    PlaintextTallyContest,\n    PlaintextTallySelection,\n)\nfrom .type import ContestId, GuardianId, SelectionId\n\n# The methods in this file can be used to decrypt values if private keys or nonces are not known\n# and the key ceremony is used to share secrets among a quorum of guardians\n\n\ndef decrypt_tally(\n    tally: CiphertextTally,\n    shares: Dict[GuardianId, DecryptionShare],\n    crypto_extended_base_hash: ElementModQ,\n    manifest: Manifest,\n    remove_placeholders: bool = True,\n) -> Optional[PlaintextTally]:\n    \"\"\"\n    Try to decrypt the tally and the spoiled ballots using the provided decryption shares.\n\n    :param tally: The CiphertextTally to decrypt\n    :param shares: The guardian Decryption Shares for all guardians\n    :param context: the Ciphertextelectioncontext\n    :return: A PlaintextTally or None if there is an error\n    \"\"\"\n    contests: Dict[ContestId, PlaintextTallyContest] = {}\n    contest_descriptions = {\n        description.object_id: description for description in manifest.contests\n    }\n\n    for contest in tally.contests.values():\n        if contest.object_id not in contest_descriptions.keys():\n            continue  # Skip contests not in manifest\n\n        plaintext_contest = decrypt_contest_with_decryption_shares(\n            CiphertextContest(\n                contest.object_id,\n                contest.sequence_order,\n                contest.description_hash,\n                list(contest.selections.values()),\n            ),\n            shares,\n            crypto_extended_base_hash,\n            contest_descriptions[contest.object_id],\n            remove_placeholders,\n        )\n        if not plaintext_contest:\n            log_warning(f\"contest: {contest.object_id} failed to decrypt with shares\")\n            return None\n        contests[contest.object_id] = plaintext_contest\n\n    return PlaintextTally(tally.object_id, contests)\n\n\ndef decrypt_ballot(\n    ballot: SubmittedBallot,\n    shares: Dict[GuardianId, DecryptionShare],\n    crypto_extended_base_hash: ElementModQ,\n    manifest: Manifest,\n    remove_placeholders: bool = True,\n) -> Optional[PlaintextTally]:\n    \"\"\"\n    Try to decrypt a single ballot using the provided decryption shares.\n\n    :param ballot: The SubmittedBallot to decrypt\n    :param shares: The guardian Decryption Shares for all guardians\n    :param crypto_extended_base_hash: The extended base hash\n    :return: A PlaintextTally or None if there is an error\n    \"\"\"\n    contests: Dict[ContestId, PlaintextTallyContest] = {}\n    contest_descriptions = {\n        description.object_id: description for description in manifest.contests\n    }\n\n    for contest in ballot.contests:\n        if contest.object_id not in contest_descriptions.keys():\n            continue  # Skip contests not in manifest\n\n        plaintext_contest = decrypt_contest_with_decryption_shares(\n            CiphertextContest(\n                contest.object_id,\n                contest.sequence_order,\n                contest.description_hash,\n                contest.ballot_selections,\n            ),\n            shares,\n            crypto_extended_base_hash,\n            contest_descriptions[contest.object_id],\n            remove_placeholders,\n        )\n        if not plaintext_contest:\n            log_warning(f\"contest: {contest.object_id} failed to decrypt with shares\")\n            return None\n        contests[contest.object_id] = plaintext_contest\n\n    return PlaintextTally(ballot.object_id, contests)\n\n\ndef decrypt_contest_with_decryption_shares(\n    contest: CiphertextContest,\n    shares: Dict[GuardianId, DecryptionShare],\n    crypto_extended_base_hash: ElementModQ,\n    contest_description: ContestDescription,\n    remove_placeholders: bool = True,\n) -> Optional[PlaintextTallyContest]:\n    \"\"\"\n    Decrypt the specified contest within the context of the specified Decryption Shares.\n\n    :param contest: the contest to decrypt\n    :param shares: a collection of `DecryptionShare` used to decrypt\n    :param crypto_extended_base_hash: the extended base hash code (𝑄') for the election\n    :return: a collection of `PlaintextTallyContest` or `None` if there is an error\n    \"\"\"\n    plaintext_selections: Dict[SelectionId, PlaintextTallySelection] = {}\n    selection_description_ids = [\n        description.object_id for description in contest_description.ballot_selections\n    ]\n\n    for selection in contest.selections:\n        if selection.object_id not in selection_description_ids and remove_placeholders:\n            continue  # Skip selections not in manifest (Such as placeholders)\n\n        tally_shares = get_shares_for_selection(selection.object_id, shares)\n        plaintext_selection = decrypt_selection_with_decryption_shares(\n            selection, tally_shares, crypto_extended_base_hash\n        )\n        if plaintext_selection is None:\n            log_warning(\n                (\n                    f\"could not decrypt contest {contest.object_id} \"\n                    f\"with selection {selection.object_id}\"\n                )\n            )\n            return None\n        plaintext_selections[plaintext_selection.object_id] = plaintext_selection\n\n    return PlaintextTallyContest(contest.object_id, plaintext_selections)\n\n\ndef decrypt_selection_with_decryption_shares(\n    selection: CiphertextSelection,\n    shares: Dict[GuardianId, Tuple[ElementModP, CiphertextDecryptionSelection]],\n    crypto_extended_base_hash: ElementModQ,\n    suppress_validity_check: bool = False,\n) -> Optional[PlaintextTallySelection]:\n    \"\"\"\n    Decrypt the specified `CiphertextSelection` with the collection of `ElementModP` decryption shares.\n    Each share is expected to be passed with the corresponding public key so that the encryption can be validated\n\n    :param selection: a `CiphertextSelection`\n    :param shares: the collection of shares to decrypt the selection\n    :param crypto_extended_base_hash: the extended base hash code (𝑄') for the election\n    :param suppress_validity_check: do not validate the encryption prior to decrypting (useful for tests)\n    :return: a `PlaintextTallySelection` or `None` if there is an error\n    \"\"\"\n    if not suppress_validity_check:\n        # Verify that all of the shares are computed correctly\n        for share in shares.values():\n            public_key, decryption = share\n            # verify we have a proof or recovered parts\n            if not decryption.is_valid(\n                selection.ciphertext, public_key, crypto_extended_base_hash\n            ):\n                log_warning(\n                    f\"share: {decryption.object_id} has invalid proof or recovered parts\"\n                )\n                return None\n\n    # accumulate all of the shares calculated for the selection\n    all_shares_product_M = mult_p(\n        *[decryption.share for (_, decryption) in shares.values()]\n    )\n\n    # Calculate 𝑀=𝐵⁄(∏𝑀𝑖) mod 𝑝.\n    decrypted_value = div_p(selection.ciphertext.data, all_shares_product_M)\n    d_log = DiscreteLog().discrete_log(decrypted_value)\n    return PlaintextTallySelection(\n        selection.object_id,\n        d_log,\n        decrypted_value,\n        selection.ciphertext,\n        [share for (guardian_id, (public_key, share)) in shares.items()],\n    )\n"
  },
  {
    "path": "src/electionguard/decryption.py",
    "content": "from typing import Dict, List, Optional, Tuple\nfrom electionguard.chaum_pedersen import ChaumPedersenProof, make_chaum_pedersen\n\nfrom electionguard.elgamal import ElGamalCiphertext\nfrom electionguard.utils import get_optional\n\nfrom .ballot import (\n    SubmittedBallot,\n    CiphertextSelection,\n    CiphertextContest,\n)\nfrom .decryption_share import (\n    CiphertextDecryptionSelection,\n    CiphertextCompensatedDecryptionSelection,\n    CiphertextDecryptionContest,\n    CiphertextCompensatedDecryptionContest,\n    create_ciphertext_decryption_selection,\n    DecryptionShare,\n    CompensatedDecryptionShare,\n)\nfrom .election import CiphertextElectionContext\nfrom .election_polynomial import compute_lagrange_coefficient\nfrom .group import (\n    ElementModP,\n    ElementModQ,\n    ONE_MOD_P,\n    mult_p,\n    pow_p,\n    pow_q,\n    rand_q,\n)\nfrom .key_ceremony import (\n    CoordinateData,\n    ElectionKeyPair,\n    ElectionPartialKeyBackup,\n    ElectionPublicKey,\n    get_backup_seed,\n)\nfrom .logs import log_warning\nfrom .scheduler import Scheduler\nfrom .tally import CiphertextTally\n\nfrom .type import ContestId, GuardianId, SelectionId\n\nRecoveryPublicKey = ElementModP\n\n\ndef compute_decryption_share(\n    key_pair: ElectionKeyPair,\n    tally: CiphertextTally,\n    context: CiphertextElectionContext,\n    scheduler: Optional[Scheduler] = None,\n) -> Optional[DecryptionShare]:\n    \"\"\"\n    Compute the decryption for all of the contests in the Ciphertext Tally\n\n    :param guardian_keys: Guardian's election key pair\n    :param tally: Encrypted tally to get decryption share of\n    :param context: Election context\n    :param scheduler: Scheduler\n    :return: Return a guardian's decryption share of tally or None if error\n    \"\"\"\n\n    contests: Dict[ContestId, CiphertextDecryptionContest] = {}\n\n    for contest in tally.contests.values():\n        contest_share = compute_decryption_share_for_contest(\n            key_pair,\n            CiphertextContest(\n                contest.object_id,\n                contest.sequence_order,\n                contest.description_hash,\n                list(contest.selections.values()),\n            ),\n            context,\n            scheduler,\n        )\n        if contest_share is None:\n            return None\n        contests[contest.object_id] = contest_share\n\n    return DecryptionShare(\n        tally.object_id,\n        key_pair.owner_id,\n        key_pair.key_pair.public_key,\n        contests,\n    )\n\n\ndef compute_compensated_decryption_share(\n    missing_guardian_coordinate: ElementModQ,\n    present_guardian_key: ElectionPublicKey,\n    missing_guardian_key: ElectionPublicKey,\n    tally: CiphertextTally,\n    context: CiphertextElectionContext,\n    scheduler: Optional[Scheduler] = None,\n) -> Optional[CompensatedDecryptionShare]:\n    \"\"\"\n    Compute the compensated decryption for all of the contests in the Ciphertext Tally\n\n    :param guardian_key: Guardian's election public key\n    :param missing_guardian_key: Missing guardian's election public key\n    :param missing_guardian_backup: Missing guardian's election partial key backup\n    :param tally: Encrypted tally to get decryption share of\n    :param context: Election context\n    :param scheduler: Scheduler\n    :return: Return a guardian's compensated decryption share of tally for the missing guardian\n        or None if error\n    \"\"\"\n\n    contests: Dict[ContestId, CiphertextCompensatedDecryptionContest] = {}\n\n    for contest in tally.contests.values():\n        contest_share = compute_compensated_decryption_share_for_contest(\n            missing_guardian_coordinate,\n            present_guardian_key,\n            missing_guardian_key,\n            CiphertextContest(\n                contest.object_id,\n                contest.sequence_order,\n                contest.description_hash,\n                list(contest.selections.values()),\n            ),\n            context,\n            scheduler,\n        )\n        if contest_share is None:\n            return None\n        contests[contest.object_id] = contest_share\n\n    return CompensatedDecryptionShare(\n        tally.object_id,\n        present_guardian_key.owner_id,\n        missing_guardian_key.owner_id,\n        present_guardian_key.key,\n        contests,\n    )\n\n\ndef compute_decryption_share_for_ballot(\n    key_pair: ElectionKeyPair,\n    ballot: SubmittedBallot,\n    context: CiphertextElectionContext,\n    scheduler: Optional[Scheduler] = None,\n) -> Optional[DecryptionShare]:\n    \"\"\"\n    Compute the decryption for a single ballot\n\n    :param guardian_keys: Guardian's election key pair\n    :param ballot: Ballot to be decrypted\n    :param context: The public election encryption context\n    :param scheduler: Scheduler\n    :return: Decryption share for ballot or `None` if there is an error\n    \"\"\"\n    contests: Dict[ContestId, CiphertextDecryptionContest] = {}\n\n    for contest in ballot.contests:\n        contest_share = compute_decryption_share_for_contest(\n            key_pair,\n            CiphertextContest(\n                contest.object_id,\n                contest.sequence_order,\n                contest.description_hash,\n                contest.ballot_selections,\n            ),\n            context,\n            scheduler,\n        )\n        if contest_share is None:\n            return None\n        contests[contest.object_id] = contest_share\n\n    return DecryptionShare(\n        ballot.object_id,\n        key_pair.owner_id,\n        key_pair.share().key,\n        contests,\n    )\n\n\ndef compute_compensated_decryption_share_for_ballot(\n    missing_guardian_coordinate: ElementModQ,\n    missing_guardian_key: ElectionPublicKey,\n    present_guardian_key: ElectionPublicKey,\n    ballot: SubmittedBallot,\n    context: CiphertextElectionContext,\n    scheduler: Optional[Scheduler] = None,\n) -> Optional[CompensatedDecryptionShare]:\n    \"\"\"\n    Compute the compensated decryption for a single ballot\n\n    :param missing_guardian_coordinate: Missing guardian's election partial key backup\n    :param missing_guardian_key: Missing guardian's election public key\n    :param present_guardian_key: Present guardian's election public key\n    :param ballot: Encrypted ballot to get decryption share of\n    :param context: Election context\n    :param scheduler: Scheduler\n    :return: Return a guardian's compensated decryption share of ballot for the missing guardian\n        or None if error\n    \"\"\"\n    contests: Dict[ContestId, CiphertextCompensatedDecryptionContest] = {}\n\n    for contest in ballot.contests:\n        contest_share = compute_compensated_decryption_share_for_contest(\n            missing_guardian_coordinate,\n            present_guardian_key,\n            missing_guardian_key,\n            CiphertextContest(\n                contest.object_id,\n                contest.sequence_order,\n                contest.description_hash,\n                contest.ballot_selections,\n            ),\n            context,\n            scheduler,\n        )\n        if contest_share is None:\n            return None\n        contests[contest.object_id] = contest_share\n\n    return CompensatedDecryptionShare(\n        ballot.object_id,\n        present_guardian_key.owner_id,\n        missing_guardian_key.owner_id,\n        present_guardian_key.key,\n        contests,\n    )\n\n\ndef compute_decryption_share_for_contest(\n    key_pair: ElectionKeyPair,\n    contest: CiphertextContest,\n    context: CiphertextElectionContext,\n    scheduler: Optional[Scheduler] = None,\n) -> Optional[CiphertextDecryptionContest]:\n    \"\"\"\n    Compute the decryption share for a single contest\n\n    :param guardian_keys: Guardian's election key pair\n    :param contest: Contest to be decrypted\n    :param context: The public election encryption context\n    :param scheduler: Scheduler\n    :return: Decryption share for contest or `None` if there is an error\n    \"\"\"\n    if not scheduler:\n        scheduler = Scheduler()\n\n    selections: Dict[SelectionId, CiphertextDecryptionSelection] = {}\n\n    decryptions: List[Optional[CiphertextDecryptionSelection]] = scheduler.schedule(\n        compute_decryption_share_for_selection,\n        [(key_pair, selection, context) for selection in contest.selections],\n        with_shared_resources=True,\n    )\n\n    for decryption in decryptions:\n        if decryption is None:\n            return None\n        selections[decryption.object_id] = decryption\n\n    return CiphertextDecryptionContest(\n        contest.object_id,\n        key_pair.owner_id,\n        contest.description_hash,\n        selections,\n    )\n\n\ndef compute_compensated_decryption_share_for_contest(\n    missing_guardian_coordinate: ElementModQ,\n    present_guardian_key: ElectionPublicKey,\n    missing_guardian_key: ElectionPublicKey,\n    contest: CiphertextContest,\n    context: CiphertextElectionContext,\n    scheduler: Optional[Scheduler] = None,\n) -> Optional[CiphertextCompensatedDecryptionContest]:\n    \"\"\"\n    Compute the compensated decryption share for a single contest\n\n    :param missing_guardian_coordinate: Election partial key backup of the missing guardian\n    :param guardian_key: The election public key of the available guardian that will partially decrypt the selection\n    :param missing_guardian_key: Election public key of the guardian that is missing\n    :param contest: The specific contest to decrypt\n    :param context: The public election encryption context\n    :return: a `CiphertextCompensatedDecryptionContest` or `None` if there is an error\n    \"\"\"\n    if not scheduler:\n        scheduler = Scheduler()\n\n    selections: Dict[SelectionId, CiphertextCompensatedDecryptionSelection] = {}\n\n    selection_decryptions: List[Optional[CiphertextCompensatedDecryptionSelection]] = (\n        scheduler.schedule(\n            compute_compensated_decryption_share_for_selection,\n            [\n                (\n                    missing_guardian_coordinate,\n                    present_guardian_key,\n                    missing_guardian_key,\n                    selection,\n                    context,\n                )\n                for selection in contest.selections\n            ],\n            with_shared_resources=True,\n        )\n    )\n\n    for decryption in selection_decryptions:\n        if decryption is None:\n            return None\n        selections[decryption.object_id] = decryption\n\n    return CiphertextCompensatedDecryptionContest(\n        contest.object_id,\n        present_guardian_key.owner_id,\n        missing_guardian_key.owner_id,\n        contest.description_hash,\n        selections,\n    )\n\n\ndef compute_decryption_share_for_selection(\n    key_pair: ElectionKeyPair,\n    selection: CiphertextSelection,\n    context: CiphertextElectionContext,\n) -> Optional[CiphertextDecryptionSelection]:\n    \"\"\"\n    Compute a partial decryption for a specific selection\n\n    :param guardian_keys: Election keys for the guardian who will partially decrypt the selection\n    :param selection: The specific selection to decrypt\n    :param context: The public election encryption context\n    :return: a `CiphertextDecryptionSelection` or `None` if there is an error\n    \"\"\"\n\n    (decryption, proof) = partially_decrypt(\n        key_pair, selection.ciphertext, context.crypto_extended_base_hash\n    )\n\n    if proof.is_valid(\n        selection.ciphertext,\n        key_pair.key_pair.public_key,\n        decryption,\n        context.crypto_extended_base_hash,\n    ):\n        return create_ciphertext_decryption_selection(\n            selection.object_id,\n            key_pair.owner_id,\n            decryption,\n            proof,\n        )\n    log_warning(\n        f\"compute decryption share proof failed for guardian {key_pair.owner_id}\"\n        f\"and {selection.object_id} with invalid proof\"\n    )\n    return None\n\n\ndef compute_compensated_decryption_share_for_selection(\n    missing_guardian_backup: ElementModQ,\n    available_guardian_key: ElectionPublicKey,\n    missing_guardian_key: ElectionPublicKey,\n    selection: CiphertextSelection,\n    context: CiphertextElectionContext,\n) -> Optional[CiphertextCompensatedDecryptionSelection]:\n    \"\"\"\n    Compute a compensated decryption share for a specific selection using the\n    available guardian's share of the missing guardian's private key polynomial\n\n    :param missing_guardian_backup: The coordinate aka backup of a missing guardian\n    :param available_guardian_key: Election public key of the guardian that is present\n    :param missing_guardian_key: Election public key of the guardian that is missing\n    :param selection: The specific selection to decrypt\n    :param context: The public election encryption context\n    :return: a `CiphertextCompensatedDecryptionSelection` or `None` if there is an error\n    \"\"\"\n\n    compensated = decrypt_with_threshold(\n        missing_guardian_backup,\n        selection.ciphertext,\n        context.crypto_extended_base_hash,\n    )\n\n    if compensated is None:\n        log_warning(\n            (\n                f\"compute compensated decryption share failed for {available_guardian_key.owner_id} \"\n                f\"missing: {missing_guardian_key.owner_id} {selection.object_id}\"\n            )\n        )\n        return None\n\n    (decryption, proof) = compensated\n\n    recovery_public_key = compute_recovery_public_key(\n        available_guardian_key, missing_guardian_key\n    )\n\n    if proof.is_valid(\n        selection.ciphertext,\n        recovery_public_key,\n        decryption,\n        context.crypto_extended_base_hash,\n    ):\n        share = CiphertextCompensatedDecryptionSelection(\n            selection.object_id,\n            available_guardian_key.owner_id,\n            missing_guardian_key.owner_id,\n            decryption,\n            recovery_public_key,\n            proof,\n        )\n        return share\n    log_warning(\n        (\n            f\"compute compensated decryption share proof failed for {available_guardian_key.owner_id} \"\n            f\"missing: {missing_guardian_key.owner_id} {selection.object_id}\"\n        )\n    )\n    return None\n\n\ndef partially_decrypt(\n    key_pair: ElectionKeyPair,\n    elgamal: ElGamalCiphertext,\n    extended_base_hash: ElementModQ,\n    nonce_seed: Optional[ElementModQ] = None,\n) -> Tuple[ElementModP, ChaumPedersenProof]:\n    \"\"\"\n    Compute a partial decryption of an elgamal encryption\n\n    :param elgamal: the `ElGamalCiphertext` that will be partially decrypted\n    :param extended_base_hash: the extended base hash of the election that\n                                was used to generate t he ElGamal Ciphertext\n    :param nonce_seed: an optional value used to generate the `ChaumPedersenProof`\n                        if no value is provided, a random number will be used.\n    :return: a `Tuple[ElementModP, ChaumPedersenProof]` of the decryption and its proof\n    \"\"\"\n    if nonce_seed is None:\n        nonce_seed = rand_q()\n\n    # TODO: ISSUE #47: Decrypt the election secret key\n\n    # 𝑀_i = 𝐴^𝑠𝑖 mod 𝑝\n    partial_decryption = elgamal.partial_decrypt(key_pair.key_pair.secret_key)\n\n    # 𝑀_i = 𝐴^𝑠𝑖 mod 𝑝 and 𝐾𝑖 = 𝑔^𝑠𝑖 mod 𝑝\n    proof = make_chaum_pedersen(\n        message=elgamal,\n        s=key_pair.key_pair.secret_key,\n        m=partial_decryption,\n        seed=nonce_seed,\n        hash_header=extended_base_hash,\n    )\n\n    return (partial_decryption, proof)\n\n\ndef decrypt_backup(\n    guardian_backup: ElectionPartialKeyBackup,\n    key_pair: ElectionKeyPair,\n) -> Optional[ElementModQ]:\n    \"\"\"\n    Decrypts a compensated partial decryption of an elgamal encryption\n    on behalf of a missing guardian\n\n    :param guardian_backup: Missing guardian's backup\n    :param key_pair: The present guardian's key pair that will be used to decrypt the backup\n    :return: a `Tuple[ElementModP, ChaumPedersenProof]` of the decryption and its proof\n    \"\"\"\n    encryption_seed = get_backup_seed(\n        key_pair.owner_id,\n        key_pair.sequence_order,\n    )\n    bytes_optional = guardian_backup.encrypted_coordinate.decrypt(\n        key_pair.key_pair.secret_key, encryption_seed\n    )\n    if bytes_optional is None:\n        return None\n    coordinate_data: CoordinateData = CoordinateData.from_bytes(\n        get_optional(bytes_optional)\n    )\n    return coordinate_data.coordinate\n\n\ndef decrypt_with_threshold(\n    coordinate: ElementModQ,\n    ciphertext: ElGamalCiphertext,\n    extended_base_hash: ElementModQ,\n    nonce_seed: Optional[ElementModQ] = None,\n) -> Optional[Tuple[ElementModP, ChaumPedersenProof]]:\n    \"\"\"\n    Compute a compensated partial decryption of an elgamal encryption\n    given a coordinate from a missing guardian.\n\n    :param coordinate: The coordinate aka backup provided to a present guardian from\n                        a missing guardian\n    :param ciphertext: the `ElGamalCiphertext` that will be partially decrypted\n    :param extended_base_hash: the extended base hash of the election that\n                                was used to generate the ElGamal Ciphertext\n    :param nonce_seed: an optional value used to generate the `ChaumPedersenProof`\n                        if no value is provided, a random number will be used.\n    :return: a `Tuple[ElementModP, ChaumPedersenProof]` of the decryption and its proof\n    \"\"\"\n    if nonce_seed is None:\n        nonce_seed = rand_q()\n\n    # 𝑀_{𝑖,l} = 𝐴^P𝑖_{l}\n    partial_decryption = ciphertext.partial_decrypt(coordinate)\n\n    # 𝑀_{𝑖,l} = 𝐴^𝑠𝑖 mod 𝑝 and 𝐾𝑖 = 𝑔^𝑠𝑖 mod 𝑝\n    proof = make_chaum_pedersen(\n        ciphertext,\n        coordinate,\n        partial_decryption,\n        nonce_seed,\n        extended_base_hash,\n    )\n\n    return (partial_decryption, proof)\n\n\ndef compute_recovery_public_key(\n    guardian_key: ElectionPublicKey,\n    missing_guardian_key: ElectionPublicKey,\n) -> RecoveryPublicKey:\n    \"\"\"\n    Compute the recovery public key,\n    corresponding to the secret share Pi(l)\n    K_ij^(l^j) for j in 0..k-1.  K_ij is coefficients[j].public_key\n    \"\"\"\n\n    pub_key = ONE_MOD_P\n    for index, commitment in enumerate(missing_guardian_key.coefficient_commitments):\n        exponent = pow_q(guardian_key.sequence_order, index)\n        pub_key = mult_p(pub_key, pow_p(commitment, exponent))\n    return pub_key\n\n\ndef reconstruct_decryption_share(\n    missing_guardian_key: ElectionPublicKey,\n    tally: CiphertextTally,\n    shares: Dict[GuardianId, CompensatedDecryptionShare],\n    lagrange_coefficients: Dict[GuardianId, ElementModQ],\n) -> DecryptionShare:\n    \"\"\"\n    Reconstruct the missing Decryption Share for a missing guardian\n    from the collection of compensated decryption shares\n\n    :param missing_guardian_id: The guardian id for the missing guardian\n    :param public_key: The public key of the guardian creating share\n    :param tally: The collection of `CiphertextTallyContest` that is cast\n    :shares: the collection of `CompensatedTallyDecryptionShare` for the missing guardian from available guardians\n    :lagrange_coefficients: the lagrange coefficients corresponding to the available guardians that provided shares\n    \"\"\"\n    contests: Dict[ContestId, CiphertextDecryptionContest] = {}\n\n    for contest in tally.contests.values():\n        contests[contest.object_id] = reconstruct_decryption_contest(\n            missing_guardian_key.owner_id,\n            CiphertextContest(\n                contest.object_id,\n                contest.sequence_order,\n                contest.description_hash,\n                list(contest.selections.values()),\n            ),\n            shares,\n            lagrange_coefficients,\n        )\n\n    return DecryptionShare(\n        tally.object_id,\n        missing_guardian_key.owner_id,\n        missing_guardian_key.key,\n        contests,\n    )\n\n\ndef reconstruct_decryption_share_for_ballot(\n    missing_guardian_key: ElectionPublicKey,\n    ballot: SubmittedBallot,\n    shares: Dict[GuardianId, CompensatedDecryptionShare],\n    lagrange_coefficients: Dict[GuardianId, ElementModQ],\n) -> DecryptionShare:\n    \"\"\"\n    Reconstruct a missing ballot Decryption share for a missing guardian\n    from the collection of compensated decryption shares\n\n    :param missing_guardian_id: The guardian id for the missing guardian\n    :param public_key: the public key for the missing guardian\n    :param ballot: The `SubmittedBallot` to reconstruct\n    :shares: the collection of `CompensatedBallotDecryptionShare` for\n        the missing guardian, each keyed by the ID of the guardian that produced it from available guardians\n    :lagrange_coefficients: the lagrange coefficients corresponding to the available guardians that provided shares\n    \"\"\"\n    contests: Dict[ContestId, CiphertextDecryptionContest] = {}\n\n    for contest in ballot.contests:\n        contests[contest.object_id] = reconstruct_decryption_contest(\n            missing_guardian_key.owner_id,\n            CiphertextContest(\n                contest.object_id,\n                contest.sequence_order,\n                contest.description_hash,\n                contest.ballot_selections,\n            ),\n            shares,\n            lagrange_coefficients,\n        )\n\n    return DecryptionShare(\n        ballot.object_id,\n        missing_guardian_key.owner_id,\n        missing_guardian_key.key,\n        contests,\n    )\n\n\ndef reconstruct_decryption_contest(\n    missing_guardian_id: GuardianId,\n    contest: CiphertextContest,\n    shares: Dict[GuardianId, CompensatedDecryptionShare],\n    lagrange_coefficients: Dict[GuardianId, ElementModQ],\n) -> CiphertextDecryptionContest:\n    \"\"\"\n    Reconstruct the missing Decryption Share for a missing guardian\n    from the collection of compensated decryption shares\n\n    :param missing_guardian_id: The guardian id for the missing guardian\n    :param contest: The CiphertextContest to decrypt\n    :shares: the collection of `CompensatedDecryptionShare` for the missing guardian from available guardians\n    :lagrange_coefficients: the lagrange coefficients corresponding to the available guardians that provided shares\n    \"\"\"\n\n    contest_shares: Dict[GuardianId, CiphertextCompensatedDecryptionContest] = {\n        available_guardian_id: compensated_share.contests[contest.object_id]\n        for available_guardian_id, compensated_share in shares.items()\n    }\n\n    selections: Dict[SelectionId, CiphertextDecryptionSelection] = {}\n    for selection in contest.selections:\n\n        # collect all of the shares generated for each selection\n        compensated_selection_shares: Dict[\n            GuardianId, CiphertextCompensatedDecryptionSelection\n        ] = {\n            available_guardian_id: compensated_contest.selections[selection.object_id]\n            for available_guardian_id, compensated_contest in contest_shares.items()\n        }\n\n        share_pow_p = []\n        for available_guardian_id, share in compensated_selection_shares.items():\n            share_pow_p.append(\n                pow_p(share.share, lagrange_coefficients[available_guardian_id])\n            )\n\n        reconstructed_share = mult_p(*share_pow_p)\n\n        selections[selection.object_id] = create_ciphertext_decryption_selection(\n            selection.object_id,\n            missing_guardian_id,\n            reconstructed_share,\n            compensated_selection_shares,\n        )\n    return CiphertextDecryptionContest(\n        contest.object_id,\n        missing_guardian_id,\n        contest.description_hash,\n        selections,\n    )\n\n\ndef compute_lagrange_coefficients_for_guardians(\n    available_guardians_keys: List[ElectionPublicKey],\n) -> Dict[GuardianId, ElementModQ]:\n    \"\"\"\n    Produce all Lagrange coefficients for a collection of available\n    Guardians, to be used when reconstructing a missing share.\n    \"\"\"\n    return {\n        guardian_keys.owner_id: compute_lagrange_coefficients_for_guardian(\n            guardian_keys, available_guardians_keys\n        )\n        for guardian_keys in available_guardians_keys\n    }\n\n\ndef compute_lagrange_coefficients_for_guardian(\n    guardian_key: ElectionPublicKey,\n    other_guardians_keys: List[ElectionPublicKey],\n) -> ElementModQ:\n    \"\"\"\n    Produce a Lagrange coefficient for a single Guardian, to be used when reconstructing a missing share.\n    \"\"\"\n    other_guardian_orders = [\n        g.sequence_order\n        for g in other_guardians_keys\n        if g.owner_id != guardian_key.owner_id\n    ]\n    return compute_lagrange_coefficient(\n        guardian_key.sequence_order,\n        *other_guardian_orders,\n    )\n"
  },
  {
    "path": "src/electionguard/decryption_mediator.py",
    "content": "from typing import Dict, List, Optional\n\n\nfrom .ballot import SubmittedBallot\nfrom .decryption import (\n    compute_lagrange_coefficients_for_guardians,\n    reconstruct_decryption_share,\n    reconstruct_decryption_share_for_ballot,\n)\nfrom .decryption_share import DecryptionShare, CompensatedDecryptionShare\nfrom .decrypt_with_shares import decrypt_ballot, decrypt_tally\nfrom .election import CiphertextElectionContext\nfrom .group import ElementModQ\nfrom .key_ceremony import ElectionPublicKey\nfrom .key_ceremony_mediator import GuardianPair\nfrom .logs import log_info, log_warning\nfrom .manifest import Manifest\nfrom .tally import (\n    CiphertextTally,\n    PlaintextTally,\n)\nfrom .type import BallotId, GuardianId, MediatorId\n\n\nclass DecryptionMediator:\n    \"\"\"\n    The Decryption Mediator composes partial decryptions from each Guardian\n    to form a decrypted representation of an election tally.\n    \"\"\"\n\n    # pylint: disable=too-many-instance-attributes\n    id: MediatorId\n    _context: CiphertextElectionContext\n\n    # Guardians\n    _available_guardians: Dict[GuardianId, ElectionPublicKey]\n    _missing_guardians: Dict[GuardianId, ElectionPublicKey]\n\n    # Decryption Shares\n    _tally_shares: Dict[GuardianId, DecryptionShare]\n    _ballot_shares: Dict[BallotId, Dict[GuardianId, DecryptionShare]]\n\n    # Compensated Shares\n    _compensated_tally_shares: Dict[GuardianPair, CompensatedDecryptionShare]\n    _compensated_ballot_shares: Dict[\n        BallotId, Dict[GuardianPair, CompensatedDecryptionShare]\n    ]\n\n    def __init__(self, id: MediatorId, context: CiphertextElectionContext):\n        \"\"\"Initialize the decryption mediator.\"\"\"\n        self.id = id\n        self._context = context\n\n        self._available_guardians = {}\n        self._missing_guardians = {}\n\n        self._tally_shares = {}\n        self._ballot_shares = {}\n\n        self._compensated_tally_shares = {}\n        self._compensated_ballot_shares = {}\n\n    def announce(\n        self,\n        guardian_key: ElectionPublicKey,\n        tally_share: DecryptionShare,\n        ballot_shares: Optional[Dict[BallotId, Optional[DecryptionShare]]] = None,\n    ) -> None:\n        \"\"\"\n        Announce that a Guardian is present and participating in the decryption.\n\n        A guardian announces by presenting their id and their shares of the decryption\n\n        :param guardian_key: The election public key of the guardian who will participate in the decryption.\n        :param tally_share: Guardian's decryption share of the tally\n        :param ballot_shares: Guardian's decryption shares of the ballots\n        \"\"\"\n        guardian_id = guardian_key.owner_id\n\n        # Only allow a guardian to announce once\n        if guardian_id in self._available_guardians:\n            log_info(f\"guardian {guardian_id} already announced\")\n            return\n\n        self._save_tally_share(guardian_id, tally_share)\n\n        if ballot_shares is not None:\n            self._save_ballot_shares(guardian_id, ballot_shares)\n\n        self._mark_available(guardian_key)\n\n    def announce_missing(self, missing_guardian_key: ElectionPublicKey) -> None:\n        \"\"\"\n        Announce that a Guardian is missing and not participating in the decryption.\n\n        :param missing_guardian_key: The election public key of the missing guardian\n        \"\"\"\n        missing_guardian_id = missing_guardian_key.owner_id\n\n        # If guardian is available, can't be marked missing\n        if missing_guardian_id in self._available_guardians:\n            log_info(f\"guardian {missing_guardian_id} already announced\")\n            return\n\n        self._mark_missing(missing_guardian_key)\n\n    def validate_missing_guardians(\n        self, guardian_keys: List[ElectionPublicKey]\n    ) -> bool:\n        \"\"\"Check the guardian's collections of keys and ensure the public keys match for the guardians.\"\"\"\n        # Check this guardian's collection of public keys\n        # for other guardians that have not announced\n        missing_guardians: Dict[GuardianId, ElectionPublicKey] = {\n            guardian_key.owner_id: guardian_key\n            for guardian_key in guardian_keys\n            if guardian_key.owner_id not in self._available_guardians\n        }\n\n        # Check that the public keys match for any missing guardians already reported\n        # note this check naively assumes that the first guardian to annouce is telling the truth\n        # but for this implementation it is simply a sanity check on the input data.\n        # a consuming application should implement better validation of the guardian state\n        # before announcing a guardian is available for decryption.\n        for guardian_id, public_key in missing_guardians.items():\n            if guardian_id in self._missing_guardians:\n                if self._missing_guardians[guardian_id] != public_key:\n                    log_warning(\n                        (\n                            f\"announce guardian: {guardian_id} \"\n                            f\"expected public key mismatch for missing {guardian_id}\"\n                        )\n                    )\n                    return False\n            else:\n                self._missing_guardians[guardian_id] = missing_guardians[guardian_id]\n        return True\n\n    def announcement_complete(self) -> bool:\n        \"\"\"\n        Determine if the announcement phase is complete\n        :return: True if announcement complete\n        \"\"\"\n        # If a quorum not announced, not ready\n        if len(self._available_guardians) < self._context.quorum:\n            log_warning(\"cannot decrypt with fewer than quorum available guardians\")\n            return False\n\n        # If guardians missing or available not accounted for, not ready\n        if (\n            len(self._available_guardians) + len(self._missing_guardians)\n            != self._context.number_of_guardians\n        ):\n            log_warning(\n                \"cannot decrypt without accounting for all guardians missing or present\"\n            )\n            return False\n        return True\n\n    def get_available_guardians(self) -> List[ElectionPublicKey]:\n        \"\"\"\n        Get all available guardian keys\n        :return: All available guardians election public keys\n        \"\"\"\n        return list(self._available_guardians.values())\n\n    def get_missing_guardians(self) -> List[ElectionPublicKey]:\n        \"\"\"\n        Get all missing guardian keys\n        :return: All missing guardians election public keys\n        \"\"\"\n        return list(self._missing_guardians.values())\n\n    def receive_tally_compensation_share(\n        self, tally_compensation_share: CompensatedDecryptionShare\n    ) -> None:\n        self._compensated_tally_shares[\n            GuardianPair(\n                tally_compensation_share.guardian_id,\n                tally_compensation_share.missing_guardian_id,\n            )\n        ] = tally_compensation_share\n\n    def receive_ballot_compensation_shares(\n        self, ballot_compensation_shares: Dict[BallotId, CompensatedDecryptionShare]\n    ) -> None:\n        for ballot_id, share in ballot_compensation_shares.items():\n            ballot_shares = self._compensated_ballot_shares.get(ballot_id)\n            if not ballot_shares:\n                ballot_shares = {}\n            ballot_shares[\n                GuardianPair(share.guardian_id, share.missing_guardian_id)\n            ] = share\n            self._compensated_ballot_shares[ballot_id] = ballot_shares\n\n    def get_lagrange_coefficients(self) -> Dict[GuardianId, ElementModQ]:\n        return compute_lagrange_coefficients_for_guardians(\n            list(self._available_guardians.values())\n        )\n\n    def reconstruct_shares_for_tally(self, ciphertext_tally: CiphertextTally) -> None:\n        lagrange_coefficients = self.get_lagrange_coefficients()\n        for (\n            missing_guardian_id,\n            missing_guardian_key,\n        ) in self._missing_guardians.items():\n            # Share already reconstructed\n            if missing_guardian_id in self._tally_shares:\n                continue\n\n            compensated_shares = _filter_by_missing_guardian(\n                missing_guardian_id, self._compensated_tally_shares\n            )\n\n            reconstructed_share = reconstruct_decryption_share(\n                missing_guardian_key,\n                ciphertext_tally,\n                compensated_shares,\n                lagrange_coefficients,\n            )\n\n            # Add reconstructed share into tally shares\n            self._tally_shares[missing_guardian_id] = reconstructed_share\n\n    def reconstruct_shares_for_ballots(\n        self, ciphertext_ballots: List[SubmittedBallot]\n    ) -> None:\n        lagrange_coefficients = compute_lagrange_coefficients_for_guardians(\n            list(self._available_guardians.values())\n        )\n        for ciphertext_ballot in ciphertext_ballots:\n            ballot_id = ciphertext_ballot.object_id\n            ballot_shares = self._ballot_shares[ballot_id]\n\n            for (\n                missing_guardian_id,\n                missing_guardian_key,\n            ) in self._missing_guardians.items():\n                # Share already reconstructed\n                if missing_guardian_id in ballot_shares:\n                    continue\n\n                compensated_shares = _filter_by_missing_guardian(\n                    missing_guardian_id, self._compensated_ballot_shares[ballot_id]\n                )\n\n                reconstructed_share = reconstruct_decryption_share_for_ballot(\n                    missing_guardian_key,\n                    ciphertext_ballot,\n                    compensated_shares,\n                    lagrange_coefficients,\n                )\n\n                ballot_shares[missing_guardian_id] = reconstructed_share\n\n            # Add shares into ballot shares\n            self._ballot_shares[ballot_id] = ballot_shares\n\n    def get_plaintext_tally(\n        self, ciphertext_tally: CiphertextTally, manifest: Manifest\n    ) -> Optional[PlaintextTally]:\n        \"\"\"\n        Get the plaintext tally for the election by composing each Guardian's\n        decrypted representation of each selection into a decrypted representation\n\n        :return: a `PlaintextTally` or `None`\n        \"\"\"\n\n        if not self.announcement_complete() or not self._ready_to_decrypt(\n            self._tally_shares\n        ):\n            return None\n\n        return decrypt_tally(\n            ciphertext_tally,\n            self._tally_shares,\n            self._context.crypto_extended_base_hash,\n            manifest,\n        )\n\n    def get_plaintext_ballots(\n        self, ciphertext_ballots: List[SubmittedBallot], manifest: Manifest\n    ) -> Optional[Dict[BallotId, PlaintextTally]]:\n        \"\"\"\n        Get the plaintext ballots for the election by composing each Guardian's\n        decrypted representation of each selection into a decrypted representation\n        This is typically used in the spoiled ballot use case.\n\n        :return: a Plaintext Ballots or `None`\n        \"\"\"\n\n        if not self.announcement_complete():\n            return None\n\n        ballots = {}\n\n        for ciphertext_ballot in ciphertext_ballots:\n            ballot_shares = self._ballot_shares.get(ciphertext_ballot.object_id)\n            if not ballot_shares or not self._ready_to_decrypt(ballot_shares):\n                # Skip ballot if not ready to decrypt\n                continue\n            ballot = decrypt_ballot(\n                ciphertext_ballot,\n                ballot_shares,\n                self._context.crypto_extended_base_hash,\n                manifest,\n            )\n\n            if ballot:\n                ballots[ballot.object_id] = ballot\n\n        return ballots\n\n    def _save_tally_share(\n        self, guardian_id: GuardianId, guardians_tally_share: DecryptionShare\n    ) -> None:\n        \"\"\"Save a guardians tally share.\"\"\"\n        self._tally_shares[guardian_id] = guardians_tally_share\n\n    def _save_ballot_shares(\n        self,\n        guardian_id: GuardianId,\n        guardians_ballot_shares: Dict[BallotId, Optional[DecryptionShare]],\n    ) -> None:\n        \"\"\"Save a guardian's set of ballot shares.\"\"\"\n        for ballot_id, guardian_ballot_share in guardians_ballot_shares.items():\n            shares = self._ballot_shares.get(ballot_id)\n            if shares is None:\n                shares = {}\n            if guardian_ballot_share is not None:\n                shares[guardian_id] = guardian_ballot_share\n            self._ballot_shares[ballot_id] = shares\n\n    def _mark_available(self, guardian_key: ElectionPublicKey) -> None:\n        \"\"\"\n        This guardian removes itself from the missing list since it generated a valid share.\n        \"\"\"\n        guardian_id = guardian_key.owner_id\n        self._available_guardians[guardian_id] = guardian_key\n        if guardian_id in self._missing_guardians:\n            self._missing_guardians.pop(guardian_id)\n\n    def _mark_missing(self, guardian_key: ElectionPublicKey) -> None:\n        \"\"\"\"\"\"\n        self._missing_guardians[guardian_key.owner_id] = guardian_key\n\n    def _ready_to_decrypt(self, shares: Dict[GuardianId, DecryptionShare]) -> bool:\n        \"\"\"Shares are ready to decrypt.\"\"\"\n        # If all guardian shares are represented including if necessary\n        # the missing guardians reconstructed shares, the decryption can be made\n        return len(shares) == self._context.number_of_guardians\n\n\ndef _filter_by_missing_guardian(\n    missing_guardian_id: GuardianId,\n    shares: Dict[GuardianPair, CompensatedDecryptionShare],\n) -> Dict[GuardianId, CompensatedDecryptionShare]:\n    \"\"\"\n    Filter a guardian pair and compensated share dictionary by missing guardian.\n    \"\"\"\n    missing_guardian_shares = {}\n    for pair, share in shares.items():\n        if pair.designated_id == missing_guardian_id:\n            missing_guardian_shares[pair.owner_id] = share\n    return missing_guardian_shares\n"
  },
  {
    "path": "src/electionguard/decryption_share.py",
    "content": "from dataclasses import dataclass, field\nfrom typing import Dict, Optional, Tuple, Union\n\nfrom .chaum_pedersen import ChaumPedersenProof\nfrom .election_object_base import ElectionObjectBase\nfrom .elgamal import ElGamalCiphertext, ElGamalPublicKey\n\nfrom .group import ElementModP, ElementModQ\n\nfrom .logs import log_warning\n\nfrom .type import ContestId, GuardianId, SelectionId\n\n\n@dataclass\nclass CiphertextCompensatedDecryptionSelection(ElectionObjectBase):\n    \"\"\"\n    A compensated fragment of a Guardian's Partial Decryption of a selection generated by an available guardian\n    \"\"\"\n\n    guardian_id: GuardianId\n    \"\"\"\n    The Available Guardian that this share belongs to\n    \"\"\"\n\n    missing_guardian_id: GuardianId\n    \"\"\"\n    The Missing Guardian for whom this share is calculated on behalf of\n    \"\"\"\n\n    share: ElementModP\n    \"\"\"\n    The Share of the decryption of a selection. `M_{i,l} in the spec`\n    \"\"\"\n\n    recovery_key: ElementModP\n    \"\"\"\n    The Recovery Public Key for the missing_guardian that corresponds to the available guardian's share of the secret\n    \"\"\"\n\n    proof: ChaumPedersenProof\n    \"\"\"\n    The Proof that the share was decrypted correctly\n    \"\"\"\n\n\nProofOrRecovery = Union[\n    ChaumPedersenProof, Dict[GuardianId, CiphertextCompensatedDecryptionSelection]\n]\n\n\n@dataclass\nclass CiphertextDecryptionSelection(ElectionObjectBase):\n    \"\"\"\n    A Guardian's Partial Decryption of a selection.  A CiphertextDecryptionSelection\n    can be generated by a guardian directly, or it can be compensated for by a quoprum of guardians\n\n    When the guardian generates this share directly, the `proof` field is populated with\n    a `chaumPedersen` proof that the decryption share was generated correctly.\n\n    When the share is generated on behalf of this guardian by other guardians, the `recovered_parts`\n    collection is populated with the `CiphertextCompensatedDecryptionSelection` objects generated\n    by each available guardian.\n    \"\"\"\n\n    guardian_id: GuardianId\n    \"\"\"\n    The Available Guardian that this share belongs to\n    \"\"\"\n\n    share: ElementModP\n    \"\"\"\n    The Share of the decryption of a selection. `M_i` in the spec\n    \"\"\"\n\n    proof: Optional[ChaumPedersenProof] = field(init=True, default=None)\n    \"\"\"\n    The Proof that the share was decrypted correctly, if the guardian\n    was available for decryption\n    \"\"\"\n\n    recovered_parts: Optional[\n        Dict[GuardianId, CiphertextCompensatedDecryptionSelection]\n    ] = field(init=True, default=None)\n    \"\"\"\n    the recovered parts of the decryption provided by available guardians,\n    if the guardian was missing from decryption\n    \"\"\"\n\n    def is_valid(\n        self,\n        message: ElGamalCiphertext,\n        election_public_key: ElGamalPublicKey,\n        extended_base_hash: ElementModQ,\n    ) -> bool:\n        \"\"\"\n        Verify that this CiphertextDecryptionSelection is valid for a\n        specific ElGamal key pair, public key, and election context.\n\n        :param message: the `ElGamalCiphertext` to compare\n        :param election_public_key: the `ElementModP Election Public Key for the Guardian\n        :param extended_base_hash: The `ElementModQ` election extended base hash.\n        \"\"\"\n        # verify we have a proof or recovered parts\n        if self.proof is None and self.recovered_parts is None:\n            log_warning(\n                (\n                    f\"CiphertextDecryptionSelection is_valid failed for guardian: {self.guardian_id} \"\n                    f\"selection: {self.object_id} with missing data\"\n                )\n            )\n            return False\n\n        if self.proof is not None and self.recovered_parts is not None:\n            log_warning(\n                (\n                    f\"CiphertextDecryptionSelection is_valid failed for guardian: {self.guardian_id} \"\n                    f\"selection: {self.object_id} cannot have proof and recovery\"\n                )\n            )\n            return False\n\n        if self.proof is not None and not self.proof.is_valid(\n            message,\n            election_public_key,\n            self.share,\n            extended_base_hash,\n        ):\n            log_warning(\n                (\n                    f\"CiphertextDecryptionSelection is_valid failed for guardian: {self.guardian_id} \"\n                    f\"selection: {self.object_id} with invalid proof\"\n                )\n            )\n            return False\n\n        if self.recovered_parts is not None:\n            for (\n                _compensating_guardian_id,\n                part,\n            ) in self.recovered_parts.items():\n                if not part.proof.is_valid(\n                    message,\n                    part.recovery_key,\n                    part.share,\n                    extended_base_hash,\n                ):\n\n                    log_warning(\n                        (\n                            f\"CiphertextDecryptionSelection is_valid failed for guardian: {self.guardian_id} \"\n                            f\"selection: {self.object_id} with invalid partial proof\"\n                        )\n                    )\n                    return False\n\n        return True\n\n\ndef create_ciphertext_decryption_selection(\n    object_id: str,\n    guardian_id: GuardianId,\n    share: ElementModP,\n    proof_or_recovery: ProofOrRecovery,\n) -> CiphertextDecryptionSelection:\n    \"\"\"\n    Create a ciphertext decryption selection\n\n    :param object_id: Object id\n    :param guardian_id: Guardian id\n    :param description_hash: Description hash\n    :param share: Share\n    :param proof_or_recovery: Proof or recovery\n    \"\"\"\n    if isinstance(proof_or_recovery, ChaumPedersenProof):\n        return CiphertextDecryptionSelection(\n            object_id, guardian_id, share, proof=proof_or_recovery\n        )\n    if isinstance(proof_or_recovery, dict):\n        return CiphertextDecryptionSelection(\n            object_id,\n            guardian_id,\n            share,\n            recovered_parts=proof_or_recovery,\n        )\n    log_warning(f\"decryption share cannot assign {proof_or_recovery}\")\n    return CiphertextDecryptionSelection(\n        object_id,\n        guardian_id,\n        share,\n    )\n\n\n@dataclass\nclass CiphertextDecryptionContest(ElectionObjectBase):\n    \"\"\"\n    A Guardian's Partial Decryption of a contest\n    \"\"\"\n\n    guardian_id: GuardianId\n    \"\"\"\n    The Available Guardian that this share belongs to\n    \"\"\"\n\n    description_hash: ElementModQ\n    \"\"\"\n    The ContestDescription Hash\n    \"\"\"\n\n    selections: Dict[SelectionId, CiphertextDecryptionSelection]\n    \"\"\"\n    the collection of decryption shares for this contest's selections\n    \"\"\"\n\n\n@dataclass\nclass CiphertextCompensatedDecryptionContest(ElectionObjectBase):\n    \"\"\"\n    A Guardian's Partial Decryption of a contest\n    \"\"\"\n\n    guardian_id: GuardianId\n    \"\"\"\n    The Available Guardian that this share belongs to\n    \"\"\"\n\n    missing_guardian_id: GuardianId\n    \"\"\"\n    The Missing Guardian for whom this share is calculated on behalf of\n    \"\"\"\n\n    description_hash: ElementModQ\n    \"\"\"\n    The ContestDescription Hash\n    \"\"\"\n\n    selections: Dict[SelectionId, CiphertextCompensatedDecryptionSelection]\n    \"\"\"\n    the collection of decryption shares for this contest's selections\n    \"\"\"\n\n\n@dataclass\nclass DecryptionShare(ElectionObjectBase):\n    \"\"\"\n    A Guardian's Partial Decryption Share of a specific set of contests (Tally or Ballot)\n    \"\"\"\n\n    guardian_id: GuardianId\n    \"\"\"\n    The Available Guardian that this share belongs to\n    \"\"\"\n\n    public_key: ElGamalPublicKey\n    \"\"\"\n    The election public key for the guardian\n    \"\"\"\n\n    contests: Dict[ContestId, CiphertextDecryptionContest]\n    \"\"\"\n    The collection of all contests in the ballot\n    \"\"\"\n\n\n@dataclass\nclass CompensatedDecryptionShare(ElectionObjectBase):\n    \"\"\"\n    A Compensated Partial Decryption Share generated by\n    an available guardian on behalf of a missing guardian\n    \"\"\"\n\n    guardian_id: GuardianId\n    \"\"\"\n    The Available Guardian that this share belongs to\n    \"\"\"\n\n    missing_guardian_id: GuardianId\n    \"\"\"\n    The Missing Guardian for whom this share is calculated on behalf of\n    \"\"\"\n\n    public_key: ElGamalPublicKey\n    \"\"\"\n    The election public key for the guardian\n    \"\"\"\n\n    contests: Dict[ContestId, CiphertextCompensatedDecryptionContest]\n    \"\"\"\n    The collection of all contests in the ballot\n    \"\"\"\n\n\ndef get_shares_for_selection(\n    selection_id: str,\n    shares: Dict[GuardianId, DecryptionShare],\n) -> Dict[GuardianId, Tuple[ElementModP, CiphertextDecryptionSelection]]:\n    \"\"\"\n    Get all of the cast shares for a specific selection\n    \"\"\"\n    selections: Dict[GuardianId, Tuple[ElementModP, CiphertextDecryptionSelection]] = {}\n    for share in shares.values():\n        for contest in share.contests.values():\n            for selection in contest.selections.values():\n                if selection.object_id == selection_id:\n                    selections[share.guardian_id] = (share.public_key, selection)\n\n    return selections\n"
  },
  {
    "path": "src/electionguard/discrete_log.py",
    "content": "# pylint: disable=global-statement\n# support for computing discrete logs, with a cache so they're never recomputed\n\nimport asyncio\nfrom typing import Dict, Tuple, Optional\n\nfrom .constants import get_generator\nfrom .singleton import Singleton\nfrom .group import BaseElement, ElementModP, ONE_MOD_P, mult_p\n\nDiscreteLogCache = Dict[ElementModP, int]\n\n_DLOG_MAX_EXPONENT = 100_000_000\n\"\"\"The max exponent to calculate.  This value is used to stop a race condition.\"\"\"\n\n_INITIAL_CACHE = {ONE_MOD_P: 0}\n\n\nclass DiscreteLogExponentError(ValueError):\n    \"\"\"Raised when the max exponent is larger than the system allows.\"\"\"\n\n    def __init__(self, exponent: int, max_exponent: int = _DLOG_MAX_EXPONENT) -> None:\n        super().__init__(\n            f\"Discrete log exponent of {exponent} exceeds maximum of {max_exponent}.\"\n        )\n\n\nclass DiscreteLogNotFoundError(ValueError):\n    \"\"\"Raised when the discrete value could not be found in cache.\"\"\"\n\n    def __init__(self, element: BaseElement) -> None:\n        super().__init__(f\"Discrete log of {element} could not be found in cache.\")\n\n\ndef compute_discrete_log(\n    element: ElementModP,\n    cache: DiscreteLogCache,\n    max_exponent: int = _DLOG_MAX_EXPONENT,\n    lazy_evaluation: bool = True,\n) -> Tuple[int, DiscreteLogCache]:\n    \"\"\"\n    Computes the discrete log (base g, mod p) of the given element,\n    with internal caching of results. Should run efficiently when called\n    multiple times when the exponent is at most in the single-digit millions.\n    Performance will degrade if it's much larger.\n\n    For the best possible performance,\n    pre-compute the discrete log of a number you expect to have the biggest\n    exponent you'll ever see. After that, the cache will be fully loaded,\n    and every call will be nothing more than a dictionary lookup.\n    \"\"\"\n\n    if element in cache:\n        return (cache[element], cache)\n    if not lazy_evaluation:\n        raise DiscreteLogNotFoundError(element)\n\n    _cache = compute_discrete_log_cache(element, cache, max_exponent)\n    return (_cache[element], _cache)\n\n\nasync def compute_discrete_log_async(\n    element: ElementModP,\n    cache: DiscreteLogCache,\n    mutex: asyncio.Lock = asyncio.Lock(),\n    max_exponent: int = _DLOG_MAX_EXPONENT,\n    lazy_evaluation: bool = True,\n) -> Tuple[int, DiscreteLogCache]:\n    \"\"\"\n    Computes the discrete log (base g, mod p) of the given element,\n    with internal caching of results. Should run efficiently when called\n    multiple times when the exponent is at most in the single-digit millions.\n    Performance will degrade if it's much larger.\n\n    Note: *this function is thread-safe*. For the best possible performance,\n    pre-compute the discrete log of a number you expect to have the biggest\n    exponent you'll ever see. After that, the cache will be fully loaded,\n    and every call will be nothing more than a dictionary lookup.\n    \"\"\"\n    if element in cache:\n        return (cache[element], cache)\n\n    async with mutex:\n        if element in cache:\n            return (cache[element], cache)\n        if not lazy_evaluation:\n            raise DiscreteLogNotFoundError(element)\n\n        _cache = compute_discrete_log_cache(element, cache, max_exponent)\n        return (_cache[element], _cache)\n\n\ndef precompute_discrete_log_cache(\n    max_exponent: int, cache: Optional[DiscreteLogCache] = None\n) -> DiscreteLogCache:\n    \"\"\"\n    Precompute the discrete log by the max exponent.\n    \"\"\"\n\n    if max_exponent > _DLOG_MAX_EXPONENT:\n        raise DiscreteLogExponentError(max_exponent)\n\n    if not cache:\n        cache = _INITIAL_CACHE\n\n    current_element = list(cache)[-1]\n    prev_exponent = cache[current_element]\n\n    if prev_exponent >= max_exponent:\n        return cache\n\n    g = ElementModP(get_generator(), False)\n\n    for exponent in range(prev_exponent + 1, max_exponent + 1):\n        current_element = mult_p(g, current_element)\n        cache[current_element] = exponent\n\n    return cache\n\n\ndef compute_discrete_log_cache(\n    element: ElementModP,\n    cache: DiscreteLogCache,\n    max_exponent: int = _DLOG_MAX_EXPONENT,\n) -> DiscreteLogCache:\n    \"\"\"\n    Compute or lazy evaluation a discrete log cache up to the specified element.\n    \"\"\"\n\n    if max_exponent > _DLOG_MAX_EXPONENT:\n        raise DiscreteLogExponentError(max_exponent)\n\n    if not cache:\n        cache = _INITIAL_CACHE\n\n    max_element = list(cache)[-1]\n    exponent = cache[max_element]\n    if exponent > max_exponent:\n        raise DiscreteLogExponentError(exponent, max_exponent)\n\n    g = ElementModP(get_generator(), False)\n\n    while element != max_element:\n        exponent = exponent + 1\n        if exponent > max_exponent:\n            raise DiscreteLogExponentError(exponent, max_exponent)\n        max_element = mult_p(g, max_element)\n        cache[max_element] = exponent\n    return cache\n\n\nclass DiscreteLog(Singleton):\n    \"\"\"\n    A class instance of the discrete log that includes a cache.\n    \"\"\"\n\n    _cache: DiscreteLogCache = {ONE_MOD_P: 0}\n    _mutex = asyncio.Lock()\n    _max_exponent: int = _DLOG_MAX_EXPONENT\n    _lazy_evaluation: bool = True\n\n    def get_cache(self) -> DiscreteLogCache:\n        return self._cache\n\n    def set_max_exponent(self, max_exponent: int) -> None:\n        self._max_exponent = max_exponent\n\n    def set_lazy_evaluation(self, lazy_evaluation: bool) -> None:\n        self._lazy_evaluation = lazy_evaluation\n\n    def precompute_cache(self, exponent: int) -> None:\n        exponent = min(exponent, self._max_exponent)\n\n        precompute_discrete_log_cache(exponent, self._cache)\n\n    async def precompute_cache_async(self, exponent: int) -> None:\n        exponent = min(exponent, self._max_exponent)\n\n        async with self._mutex:\n            precompute_discrete_log_cache(exponent)\n\n    def discrete_log(self, element: ElementModP) -> int:\n        (result, _cache) = compute_discrete_log(\n            element, self._cache, self._max_exponent, self._lazy_evaluation\n        )\n        return result\n\n    async def discrete_log_async(self, element: ElementModP) -> int:\n        (result, _cache) = await compute_discrete_log_async(\n            element, self._cache, self._mutex, self._max_exponent, self._lazy_evaluation\n        )\n        return result\n"
  },
  {
    "path": "src/electionguard/election.py",
    "content": "\"\"\"Context for election encryption.\"\"\"\n\nfrom dataclasses import dataclass, field\nfrom typing import Dict, Optional\n\nfrom .constants import get_small_prime, get_large_prime, get_generator\nfrom .elgamal import ElGamalPublicKey\nfrom .group import (\n    ElementModQ,\n    ElementModP,\n)\nfrom .hash import hash_elems\n\n\n@dataclass\nclass Configuration:\n    \"\"\"Configuration of election to allow edge cases.\"\"\"\n\n    allow_overvotes: bool = field(default=True)\n    \"\"\"\n    Allow overvotes, votes exceeding selection limit, for the election.\n    \"\"\"\n\n    max_votes: int = field(default=1_000_000)\n    \"\"\"\n    Maximum votes, the maximum votes allowed on a selection for an aggregate ballot or tally.\n    This can also be seen as the maximum ballots where a selection on a ballot can only have one vote.\n    \"\"\"\n\n\n# pylint: disable=too-many-instance-attributes\n@dataclass(eq=True, unsafe_hash=True)\nclass CiphertextElectionContext:\n    \"\"\"`CiphertextElectionContext` is the ElectionGuard representation of a specific election.\n\n    Note: The ElectionGuard Data Spec deviates from the NIST model in that\n    this object includes fields that are populated in the course of encrypting an election\n    Specifically, `crypto_base_hash`, `crypto_extended_base_hash` and `elgamal_public_key`\n    are populated with election-specific information necessary for encrypting the election.\n    Refer to the specification for more information.\n\n    To make an instance of this class, don't construct it directly. Use\n    `make_ciphertext_election_context` instead.\n    \"\"\"\n\n    number_of_guardians: int\n    \"\"\"\n    The number of guardians necessary to generate the public key\n    \"\"\"\n    quorum: int\n    \"\"\"\n    The quorum of guardians necessary to decrypt an election.  Must be fewer than `number_of_guardians`\n    \"\"\"\n\n    elgamal_public_key: ElGamalPublicKey\n    \"\"\"the `joint public key (K)` in the specification\"\"\"\n\n    commitment_hash: ElementModQ\n    \"\"\"\n    the `commitment hash H(K 1,0 , K 2,0 ... , K n,0 )` of the public commitments\n    guardians make to each other in the specification\n    \"\"\"\n\n    manifest_hash: ElementModQ\n    \"\"\"The hash of the election metadata\"\"\"\n\n    crypto_base_hash: ElementModQ\n    \"\"\"The `base hash code (𝑄)` in the specification\"\"\"\n\n    crypto_extended_base_hash: ElementModQ\n    \"\"\"The `extended base hash code (𝑄')` in specification\"\"\"\n\n    extended_data: Optional[Dict[str, str]]\n    \"\"\"Data to allow extending the context for special cases.\"\"\"\n\n    configuration: Configuration = field(default_factory=Configuration)\n    \"\"\"Configuration for the election edge cases.\"\"\"\n\n    def get_extended_data_field(self, field_name: str) -> Optional[str]:\n        \"\"\"Returns the value for a field in the extended data or None if it isn't initialized.\"\"\"\n\n        if self.extended_data is None:\n            return None\n        return self.extended_data.get(field_name)\n\n\ndef make_ciphertext_election_context(\n    number_of_guardians: int,\n    quorum: int,\n    elgamal_public_key: ElGamalPublicKey,\n    commitment_hash: ElementModQ,\n    manifest_hash: ElementModQ,\n    extended_data: Optional[Dict[str, str]] = None,\n) -> CiphertextElectionContext:\n    \"\"\"\n    Makes a CiphertextElectionContext object.\n\n    :param number_of_guardians: The number of guardians necessary to generate the public key\n    :param quorum: The quorum of guardians necessary to decrypt an election.  Must be fewer than `number_of_guardians`\n    :param elgamal_public_key: the public key of the election\n    :param commitment_hash: the hash of the commitments the guardians make to each other\n    :param manifest_hash: the hash of the election metadata\n    \"\"\"\n\n    # What's a crypto_base_hash?\n    # The metadata of this object are hashed together with the\n    # - prime modulus (𝑝),\n    # - subgroup order (𝑞),\n    # - generator (𝑔),\n    # - number of guardians (𝑛),\n    # - decryption threshold value (𝑘),\n    # to form a base hash code (𝑄) which will be incorporated\n    # into every subsequent hash computation in the election.\n\n    # What's a crypto_extended_base_hash?\n    # Once the baseline parameters have been produced and confirmed,\n    # all of the public guardian commitments 𝐾𝑖,𝑗 are hashed together\n    # with the base hash 𝑄 to form an extended base hash 𝑄' that will\n    # form the basis of subsequent hash computations.\n\n    crypto_base_hash = hash_elems(\n        ElementModP(get_large_prime(), False),\n        ElementModQ(get_small_prime(), False),\n        ElementModP(get_generator(), False),\n        number_of_guardians,\n        quorum,\n        manifest_hash,\n    )\n    crypto_extended_base_hash = hash_elems(crypto_base_hash, commitment_hash)\n    return CiphertextElectionContext(\n        number_of_guardians,\n        quorum,\n        elgamal_public_key,\n        commitment_hash,\n        manifest_hash,\n        crypto_base_hash,\n        crypto_extended_base_hash,\n        extended_data,\n    )\n"
  },
  {
    "path": "src/electionguard/election_object_base.py",
    "content": "\"\"\"Base objects to derive other election objects.\"\"\"\n\nfrom dataclasses import dataclass\nfrom typing import List, Sequence, TypeVar\n\n\n@dataclass\nclass ElectionObjectBase:\n    \"\"\"A base object to derive other election objects identifiable by object_id.\"\"\"\n\n    object_id: str\n\n\n@dataclass\nclass OrderedObjectBase(ElectionObjectBase):\n    \"\"\"A ordered base object to derive other election objects.\"\"\"\n\n    sequence_order: int\n    \"\"\"\n    Used for ordering in a ballot to ensure various encryption primitives are deterministic.\n    The sequence order must be unique and should be representative of how the items are represented\n    on a template ballot in an external system.  The sequence order is not required to be in the order\n    in which they are displayed to a voter.  Any acceptable range of integer values may be provided.\n    \"\"\"\n\n\n_Orderable_T = TypeVar(\"_Orderable_T\", bound=\"OrderedObjectBase\")\n\n\ndef sequence_order_sort(unsorted: List[_Orderable_T]) -> List[_Orderable_T]:\n    \"\"\"Sort by sequence order.\"\"\"\n    return sorted(unsorted, key=lambda item: item.sequence_order)\n\n\ndef list_eq(\n    list1: Sequence[ElectionObjectBase], list2: Sequence[ElectionObjectBase]\n) -> bool:\n    \"\"\"\n    We want to compare lists of election objects as if they're sets. We fake this by first\n    sorting them on their object ids, then using regular list comparison.\n    \"\"\"\n    return sorted(list1, key=lambda x: x.object_id) == sorted(\n        list2, key=lambda x: x.object_id\n    )\n"
  },
  {
    "path": "src/electionguard/election_polynomial.py",
    "content": "from dataclasses import dataclass\nfrom typing import Dict, List, Optional\n\nfrom .elgamal import ElGamalKeyPair\nfrom .group import (\n    add_q,\n    ElementModP,\n    ElementModQ,\n    g_pow_p,\n    div_q,\n    mult_p,\n    mult_q,\n    ONE_MOD_P,\n    pow_p,\n    pow_q,\n    rand_q,\n    ZERO_MOD_Q,\n)\nfrom .schnorr import make_schnorr_proof, SchnorrProof\nfrom .type import GuardianId\n\nSecretCoefficient = ElementModQ  # Secret coefficient of election polynomial\nPublicCommitment = ElementModP  # Public commitment of election polynomial\n\n\n@dataclass\nclass Coefficient:\n    \"\"\"\n    A coefficient of an Election Polynomial\n    \"\"\"\n\n    value: SecretCoefficient\n    \"\"\"The secret coefficient `a_ij` \"\"\"\n\n    commitment: PublicCommitment\n    \"\"\"The public key `K_ij` generated from secret coefficient\"\"\"\n\n    proof: SchnorrProof\n    \"\"\"A proof of possession of the private key for the secret coefficient\"\"\"\n\n\n@dataclass\nclass ElectionPolynomial:\n    \"\"\"\n    A polynomial defined by coefficients\n\n    The 0-index coefficient is used for a secret key which can\n    be discovered by a quorum of n guardians corresponding to n coefficients.\n    \"\"\"\n\n    coefficients: List[Coefficient]\n    \"\"\"List of coefficient value, commitments and proofs\"\"\"\n\n    def get_commitments(self) -> List[PublicCommitment]:\n        \"\"\"Access the list of public keys generated from secret coefficient\"\"\"\n        return [coefficient.commitment for coefficient in self.coefficients]\n\n    def get_proofs(self) -> List[SchnorrProof]:\n        \"\"\"Access the list of proof of possesion of the private key for the secret coefficient\"\"\"\n        return [coefficient.proof for coefficient in self.coefficients]\n\n\ndef generate_polynomial(\n    number_of_coefficients: int, nonce: Optional[ElementModQ] = None\n) -> ElectionPolynomial:\n    \"\"\"\n    Generates a polynomial for sharing election keys\n\n    :param number_of_coefficients: Number of coefficients of polynomial\n    :param nonce: an optional nonce parameter that may be provided (useful for testing)\n    :return: Polynomial used to share election keys\n    \"\"\"\n    coefficients: List[Coefficient] = []\n\n    for i in range(number_of_coefficients):\n        # Note: the nonce value is not safe. it is designed for testing only.\n        # this method should be called without the nonce in production.\n        value = add_q(nonce, i) if nonce is not None else rand_q()\n        commitment = g_pow_p(value)\n        proof = make_schnorr_proof(\n            ElGamalKeyPair(value, commitment), rand_q()\n        )  # TODO Alternate schnoor proof method that doesn't need KeyPair\n        coefficient = Coefficient(value, commitment, proof)\n        coefficients.append(coefficient)\n    return ElectionPolynomial(coefficients)\n\n\ndef compute_polynomial_coordinate(\n    exponent_modifier: int, polynomial: ElectionPolynomial\n) -> ElementModQ:\n    \"\"\"\n    Computes a single coordinate value of the election polynomial used for sharing\n\n    :param exponent_modifier: Unique modifier (usually sequence order) for exponent\n    :param polynomial: Election polynomial\n    :return: Polynomial used to share election keys\n    \"\"\"\n\n    exponent_modifier_mod_q = ElementModQ(exponent_modifier)\n\n    computed_value = ZERO_MOD_Q\n    for i, coefficient in enumerate(polynomial.coefficients):\n        exponent = pow_q(exponent_modifier_mod_q, i)\n        factor = mult_q(coefficient.value, exponent)\n        computed_value = add_q(computed_value, factor)\n    return computed_value\n\n\n@dataclass\nclass LagrangeCoefficientsRecord:\n    \"\"\"\n    Record for lagrange coefficients for specific coordinates, usually the guardian sequence order\n    to be used in the public election record.\n    \"\"\"\n\n    coefficients: Dict[GuardianId, ElementModQ]\n\n\n# pylint: disable=unnecessary-comprehension\ndef compute_lagrange_coefficient(coordinate: int, *degrees: int) -> ElementModQ:\n    \"\"\"\n    Compute the lagrange coefficient for a specific coordinate against N degrees.\n    :param coordinate: the coordinate to plot, uisually a Guardian's Sequence Order\n    :param degrees: the degrees across which to plot, usually the collection of\n                    available Guardians' Sequence Orders\n    \"\"\"\n\n    numerator = mult_q(*[degree for degree in degrees])\n    denominator = mult_q(*[(degree - coordinate) for degree in degrees])\n    result = div_q((numerator), (denominator))\n    return result\n\n\ndef verify_polynomial_coordinate(\n    coordinate: ElementModQ,\n    exponent_modifier: int,\n    commitments: List[PublicCommitment],\n) -> bool:\n    \"\"\"\n    Verify a polynomial coordinate value is in fact on the polynomial's curve\n\n    :param coordinate: Value to be checked\n    :param exponent_modifier: Unique modifier (usually sequence order) for exponent\n    :param commitments: Public commitments for coefficients of polynomial\n    :return: True if verified on polynomial\n    \"\"\"\n\n    exponent_modifier_mod_q = ElementModQ(exponent_modifier)\n\n    commitment_output = ONE_MOD_P\n    for i, commitment in enumerate(commitments):\n        exponent = pow_p(exponent_modifier_mod_q, i)\n        factor = pow_p(commitment, exponent)\n        commitment_output = mult_p(commitment_output, factor)\n\n    value_output = g_pow_p(coordinate)\n    return value_output == commitment_output\n"
  },
  {
    "path": "src/electionguard/elgamal.py",
    "content": "from dataclasses import dataclass\nfrom typing import Any, Iterable, Optional, Union\n\n\nfrom .big_integer import bytes_to_hex\nfrom .byte_padding import to_padded_bytes\nfrom .discrete_log import DiscreteLog\nfrom .group import (\n    ElementModQ,\n    ElementModP,\n    g_pow_p,\n    mult_p,\n    mult_inv_p,\n    pow_p,\n    ZERO_MOD_Q,\n    TWO_MOD_Q,\n    rand_range_q,\n)\nfrom .hash import hash_elems\nfrom .hmac import get_hmac\nfrom .logs import log_info, log_error\nfrom .utils import get_optional\n\nElGamalSecretKey = ElementModQ\nElGamalPublicKey = ElementModP\n\n_BLOCK_SIZE = 32\n\n\n@dataclass\nclass ElGamalKeyPair:\n    \"\"\"A tuple of an ElGamal secret key and public key.\"\"\"\n\n    secret_key: ElGamalSecretKey\n    public_key: ElGamalPublicKey\n\n\n@dataclass\nclass ElGamalCiphertext:\n    \"\"\"\n    An \"exponential ElGamal ciphertext\" (i.e., with the plaintext in the exponent to allow for\n    homomorphic addition). Create one with `elgamal_encrypt`. Add them with `elgamal_add`.\n    Decrypt using one of the supplied instance methods.\n    \"\"\"\n\n    pad: ElementModP\n    \"\"\"pad or alpha\"\"\"\n\n    data: ElementModP\n    \"\"\"encrypted data or beta\"\"\"\n\n    def __eq__(self, other: Any) -> bool:\n        if isinstance(other, ElGamalCiphertext):\n            return self.pad == other.pad and self.data == other.data\n        return False\n\n    def decrypt_known_product(self, product: ElementModP) -> int:\n        \"\"\"\n        Decrypts an ElGamal ciphertext with a \"known product\" (the blinding factor used in the encryption).\n\n        :param product: The known product (blinding factor).\n        :return: An exponentially encoded plaintext message.\n        \"\"\"\n        return DiscreteLog().discrete_log(mult_p(self.data, mult_inv_p(product)))\n\n    def decrypt(self, secret_key: ElGamalSecretKey) -> int:\n        \"\"\"\n        Decrypt an ElGamal ciphertext using a known ElGamal secret key.\n\n        :param secret_key: The corresponding ElGamal secret key.\n        :return: An exponentially encoded plaintext message.\n        \"\"\"\n        return self.decrypt_known_product(pow_p(self.pad, secret_key))\n\n    def decrypt_known_nonce(\n        self, public_key: ElGamalPublicKey, nonce: ElementModQ\n    ) -> int:\n        \"\"\"\n        Decrypt an ElGamal ciphertext using a known nonce and the ElGamal public key.\n\n        :param public_key: The corresponding ElGamal public key.\n        :param nonce: The secret nonce used to create the ciphertext.\n        :return: An exponentially encoded plaintext message.\n        \"\"\"\n        return self.decrypt_known_product(pow_p(public_key, nonce))\n\n    def partial_decrypt(self, secret_key: ElGamalSecretKey) -> ElementModP:\n        \"\"\"\n        Partially Decrypts an ElGamal ciphertext with a known ElGamal secret key.\n\n        𝑀_i = 𝐴^𝑠𝑖 mod 𝑝 in the spec\n\n        :param secret_key: The corresponding ElGamal secret key.\n        :return: An exponentially encoded plaintext message.\n        \"\"\"\n        return pow_p(self.pad, secret_key)\n\n    def crypto_hash(self) -> ElementModQ:\n        \"\"\"\n        Computes a cryptographic hash of this ciphertext.\n        \"\"\"\n        return hash_elems(self.pad, self.data)\n\n\n@dataclass\nclass HashedElGamalCiphertext:\n    \"\"\"\n    A hashed version of ElGamal Ciphertext with less size restrictions.\n    Create one with `hashed_elgamal_encrypt`. Add them with `elgamal_add`.\n    Decrypt using one of the supplied instance methods.\n    \"\"\"\n\n    pad: ElementModP\n    \"\"\"pad or alpha\"\"\"\n\n    data: str\n    \"\"\"encrypted data or beta\"\"\"\n\n    mac: str\n    \"\"\"message authentication code for hmac\"\"\"\n\n    def decrypt(\n        self, secret_key: ElGamalSecretKey, encryption_seed: ElementModQ\n    ) -> Union[bytes, None]:\n        \"\"\"\n        Decrypt an ElGamal ciphertext using a known ElGamal secret key.\n\n        :param secret_key: The corresponding ElGamal secret key.\n        :param encryption_seed: Encryption seed (Q) for election.\n        :return: Decrypted plaintext message.\n        \"\"\"\n\n        session_key = hash_elems(self.pad, pow_p(self.pad, secret_key))\n        data_bytes = to_padded_bytes(self.data)\n\n        (ciphertext_chunks, bit_length) = _get_chunks(data_bytes)\n        mac_key = get_hmac(\n            session_key.to_hex_bytes(),\n            encryption_seed.to_hex_bytes(),\n            bit_length,\n        )\n        to_mac = self.pad.to_hex_bytes() + data_bytes\n        mac = bytes_to_hex(get_hmac(mac_key, to_mac))\n\n        if mac != self.mac:\n            log_error(\"MAC verification failed in decryption.\")\n            return None\n\n        data = b\"\"\n        for i, block in enumerate(ciphertext_chunks):\n            data_key = get_hmac(\n                session_key.to_hex_bytes(),\n                encryption_seed.to_hex_bytes(),\n                bit_length,\n                (i + 1),\n            )\n            data += bytes([a ^ b for (a, b) in zip(block, data_key)])\n        return data\n\n\ndef elgamal_keypair_from_secret(a: ElementModQ) -> Optional[ElGamalKeyPair]:\n    \"\"\"\n    Given an ElGamal secret key (typically, a random number in [2,Q)), returns\n    an ElGamal keypair, consisting of the given secret key a and public key g^a.\n    \"\"\"\n    secret_key_int = a\n    if secret_key_int < 2:\n        log_error(\"ElGamal secret key needs to be in [2,Q).\")\n        return None\n\n    return ElGamalKeyPair(a, g_pow_p(a))\n\n\ndef elgamal_keypair_random() -> ElGamalKeyPair:\n    \"\"\"\n    Create a random elgamal keypair\n\n    :return: random elgamal key pair\n    \"\"\"\n    return get_optional(elgamal_keypair_from_secret(rand_range_q(TWO_MOD_Q)))\n\n\ndef elgamal_combine_public_keys(keys: Iterable[ElGamalPublicKey]) -> ElGamalPublicKey:\n    \"\"\"\n    Combine multiple elgamal public keys into a joint key\n\n    :param keys: list of public elgamal keys\n    :return: joint key of elgamal keys\n    \"\"\"\n    return mult_p(*keys)\n\n\ndef elgamal_encrypt(\n    message: int, nonce: ElementModQ, public_key: ElGamalPublicKey\n) -> Optional[ElGamalCiphertext]:\n    \"\"\"\n    Encrypts a set length message with a given random nonce and an ElGamal public key.\n\n    :param message: Known length message (m) to elgamal_encrypt; must be an integer in [0,Q).\n    :param nonce: Randomly chosen nonce in [1,Q).\n    :param public_key: ElGamal public key.\n    :return: An `ElGamalCiphertext`.\n    \"\"\"\n    if nonce == ZERO_MOD_Q:\n        log_error(\"ElGamal encryption requires a non-zero nonce\")\n        return None\n\n    pad = g_pow_p(nonce)\n    gpowp_m = g_pow_p(message)\n    pubkey_pow_n = pow_p(public_key, nonce)\n    data = mult_p(gpowp_m, pubkey_pow_n)\n\n    log_info(f\": publicKey: {public_key.to_hex()}\")\n    log_info(f\": pad: {pad.to_hex()}\")\n    log_info(f\": data: {data.to_hex()}\")\n\n    return ElGamalCiphertext(pad, data)\n\n\ndef hashed_elgamal_encrypt(\n    message: bytes,\n    nonce: ElementModQ,\n    public_key: ElGamalPublicKey,\n    encryption_seed: ElementModQ,\n) -> HashedElGamalCiphertext:\n    \"\"\"\n    Encrypts a variable length byte message with a given random nonce and an ElGamal public key.\n\n    :param message: message (m) to encrypt; must be in bytes.\n    :param nonce: Randomly chosen nonce in [1, Q).\n    :param public_key: ElGamal public key.\n    :param encryption_seed: Encryption seed (Q) for election.\n    \"\"\"\n\n    pad = g_pow_p(nonce)\n    pubkey_pow_n = pow_p(public_key, nonce)\n\n    session_key = hash_elems(pad, pubkey_pow_n)\n\n    (message_chunks, bit_length) = _get_chunks(message)\n    data = b\"\"\n    for i, block in enumerate(message_chunks):\n        data_key = get_hmac(\n            session_key.to_hex_bytes(),\n            encryption_seed.to_hex_bytes(),\n            bit_length,\n            (i + 1),\n        )\n        data += bytes([a ^ b for (a, b) in zip(block, data_key)])\n\n    mac_key = get_hmac(\n        session_key.to_hex_bytes(), encryption_seed.to_hex_bytes(), bit_length\n    )\n    to_mac = pad.to_hex_bytes() + data\n    mac = get_hmac(mac_key, to_mac)\n\n    log_info(f\": publicKey: {public_key.to_hex()}\")\n    log_info(f\": pad: {pad.to_hex()}\")\n    log_info(f\": data: {data!r}\")\n    log_info(f\": mac: {bytes_to_hex(mac)}\")\n    log_info(f\"to_mac {to_mac!r}\")\n\n    return HashedElGamalCiphertext(pad, bytes_to_hex(data), bytes_to_hex(mac))\n\n\ndef _get_chunks(message: bytes) -> tuple[list[bytes], int]:\n    remainder = len(message) % _BLOCK_SIZE\n    if remainder:\n        message += bytes([0 for _n in range(_BLOCK_SIZE - remainder)])\n    number_of_blocks = int(len(message) / _BLOCK_SIZE)\n    return (\n        [\n            message[_BLOCK_SIZE * i : _BLOCK_SIZE * (i + 1)]\n            for i in range(number_of_blocks)\n        ],\n        len(message) * 8,\n    )\n\n\ndef elgamal_add(*ciphertexts: ElGamalCiphertext) -> ElGamalCiphertext:\n    \"\"\"\n    Homomorphically accumulates one or more ElGamal ciphertexts by pairwise multiplication. The exponents\n    of vote counters will add.\n    \"\"\"\n    assert len(ciphertexts) != 0, \"Must have one or more ciphertexts for elgamal_add\"\n\n    result = ciphertexts[0]\n    for c in ciphertexts[1:]:\n        result = ElGamalCiphertext(\n            mult_p(result.pad, c.pad), mult_p(result.data, c.data)\n        )\n\n    return result\n"
  },
  {
    "path": "src/electionguard/encrypt.py",
    "content": "from datetime import datetime, timezone\nfrom dataclasses import dataclass, field\nfrom typing import Dict, List, Optional, Type, TypeVar\nfrom uuid import getnode\n\nfrom .ballot import (\n    CiphertextBallot,\n    CiphertextBallotContest,\n    CiphertextBallotSelection,\n    PlaintextBallot,\n    PlaintextBallotContest,\n    PlaintextBallotSelection,\n    make_ciphertext_ballot_contest,\n    make_ciphertext_ballot_selection,\n    make_ciphertext_ballot,\n)\n\nfrom .ballot_code import get_hash_for_device\nfrom .election import CiphertextElectionContext\nfrom .elgamal import ElGamalPublicKey, elgamal_encrypt, hashed_elgamal_encrypt\nfrom .serialize import padded_decode, padded_encode\nfrom .group import ElementModQ, rand_q\nfrom .logs import log_info, log_warning\nfrom .manifest import (\n    InternalManifest,\n    ContestDescription,\n    ContestDescriptionWithPlaceholders,\n    SelectionDescription,\n)\nfrom .nonces import Nonces\nfrom .type import SelectionId\nfrom .utils import (\n    ContestException,\n    NullVoteException,\n    OverVoteException,\n    UnderVoteException,\n    get_optional,\n    get_or_else_optional_func,\n    ContestErrorType,\n)\n\n\n_T = TypeVar(\"_T\", bound=\"ContestData\")\n\n\n@dataclass\nclass ContestData:\n    \"\"\"Contests errors and extended data from the selections on the contest.\"\"\"\n\n    error: Optional[ContestErrorType] = field(default=None)\n    error_data: Optional[List[SelectionId]] = field(default=None)\n    write_ins: Optional[Dict[SelectionId, str]] = field(default=None)\n\n    @classmethod\n    def from_bytes(cls: Type[_T], data: bytes) -> _T:\n        return padded_decode(cls, data)\n\n    def to_bytes(self) -> bytes:\n        return padded_encode(self)\n\n\n@dataclass\nclass EncryptionDevice:\n    \"\"\"\n    Metadata for encryption device\n    \"\"\"\n\n    device_id: int\n    \"\"\"Unique identifier for device\"\"\"\n\n    session_id: int\n    \"\"\"Used to identify session and protect the timestamp\"\"\"\n\n    launch_code: int\n    \"\"\"Election initialization value\"\"\"\n\n    location: str\n    \"\"\"Arbitrary string to designate the location of device\"\"\"\n\n    def get_hash(self) -> ElementModQ:\n        \"\"\"\n        Get hash for encryption device\n        :return: Starting hash\n        \"\"\"\n        return get_hash_for_device(\n            self.device_id, self.session_id, self.launch_code, self.location\n        )\n\n    def get_timestamp(self) -> int:\n        \"\"\"\n        Get the current timestamp in utc\n        \"\"\"\n        return int(datetime.now(timezone.utc).timestamp())\n\n\nclass EncryptionMediator:\n    \"\"\"\n    An object for caching election and encryption state.\n\n    It composes Elections and Ballots.\n    \"\"\"\n\n    _internal_manifest: InternalManifest\n    _context: CiphertextElectionContext\n    _encryption_seed: ElementModQ\n\n    def __init__(\n        self,\n        internal_manifest: InternalManifest,\n        context: CiphertextElectionContext,\n        encryption_device: EncryptionDevice,\n    ):\n        self._internal_manifest = internal_manifest\n        self._context = context\n        self._encryption_seed = encryption_device.get_hash()\n\n    def encrypt(self, ballot: PlaintextBallot) -> Optional[CiphertextBallot]:\n        \"\"\"\n        Encrypt the specified ballot using the cached election context.\n        \"\"\"\n\n        log_info(f\" encrypt: objectId: {ballot.object_id}\")\n        encrypted_ballot = encrypt_ballot(\n            ballot, self._internal_manifest, self._context, self._encryption_seed\n        )\n        if encrypted_ballot is not None and encrypted_ballot.code is not None:\n            self._encryption_seed = encrypted_ballot.code\n        return encrypted_ballot\n\n\ndef generate_device_uuid() -> int:\n    \"\"\"\n    Get unique identifier for device\n    :return: Unique identifier\n    \"\"\"\n    return getnode()\n\n\ndef selection_from(\n    description: SelectionDescription,\n    is_placeholder: bool = False,\n    is_affirmative: bool = False,\n) -> PlaintextBallotSelection:\n    \"\"\"\n    Construct a `BallotSelection` from a specific `SelectionDescription`.\n    This function is useful for filling selections when a voter undervotes a ballot.\n    It is also used to create placeholder representations when generating the `ConstantChaumPedersenProof`\n\n    :param description: The `SelectionDescription` which provides the relevant `object_id`\n    :param is_placeholder: Mark this selection as a placeholder value\n    :param is_affirmative: Mark this selection as `yes`\n    :return: A BallotSelection\n    \"\"\"\n\n    return PlaintextBallotSelection(\n        description.object_id,\n        1 if is_affirmative else 0,\n        is_placeholder,\n    )\n\n\ndef contest_from(description: ContestDescription) -> PlaintextBallotContest:\n    \"\"\"\n    Construct a `BallotContest` from a specific `ContestDescription` with all false fields.\n    This function is useful for filling contests and selections when a voter undervotes a ballot.\n\n    :param description: The `ContestDescription` used to derive the well-formed `BallotContest`\n    :return: a `BallotContest`\n    \"\"\"\n\n    selections: List[PlaintextBallotSelection] = []\n\n    for selection_description in description.ballot_selections:\n        selections.append(selection_from(selection_description))\n\n    return PlaintextBallotContest(description.object_id, selections)\n\n\ndef encrypt_selection(\n    selection: PlaintextBallotSelection,\n    selection_description: SelectionDescription,\n    elgamal_public_key: ElGamalPublicKey,\n    crypto_extended_base_hash: ElementModQ,\n    nonce_seed: ElementModQ,\n    is_placeholder: bool = False,\n    should_verify_proofs: bool = False,\n) -> Optional[CiphertextBallotSelection]:\n    \"\"\"\n    Encrypt a specific `BallotSelection` in the context of a specific `BallotContest`\n\n    :param selection: the selection in the valid input form\n    :param selection_description: the `SelectionDescription` from the\n        `ContestDescription` which defines this selection's structure\n    :param elgamal_public_key: the public key (K) used to encrypt the ballot\n    :param crypto_extended_base_hash: the extended base hash of the election\n    :param nonce_seed: an `ElementModQ` used as a header to seed the `Nonce` generated for this selection.\n                 this value can be (or derived from) the BallotContest nonce, but no relationship is required\n    :param is_placeholder: specifies if this is a placeholder selection\n    :param should_verify_proofs: specify if the proofs should be verified prior to returning (default False)\n    \"\"\"\n\n    # Validate Input\n    if not selection.is_valid(selection_description.object_id):\n        log_warning(f\"malformed input selection: {selection}\")\n        return None\n\n    selection_description_hash = selection_description.crypto_hash()\n    nonce_sequence = Nonces(selection_description_hash, nonce_seed)\n    selection_nonce = nonce_sequence[selection_description.sequence_order]\n    disjunctive_chaum_pedersen_nonce = next(iter(nonce_sequence))\n\n    log_info(\n        f\": encrypt_selection: for {selection_description.object_id} hash: {selection_description_hash.to_hex()}\"\n    )\n\n    selection_representation = selection.vote\n\n    # Generate the encryption\n    elgamal_encryption = elgamal_encrypt(\n        selection_representation, selection_nonce, elgamal_public_key\n    )\n\n    if elgamal_encryption is None:\n        # will have logged about the failure earlier, so no need to log anything here\n        return None\n\n    # TODO: ISSUE #35: encrypt/decrypt: encrypt the extended_data field\n\n    # Create the return object\n    encrypted_selection = make_ciphertext_ballot_selection(\n        selection.object_id,\n        selection_description.sequence_order,\n        selection_description_hash,\n        get_optional(elgamal_encryption),\n        elgamal_public_key,\n        crypto_extended_base_hash,\n        disjunctive_chaum_pedersen_nonce,\n        selection_representation,\n        is_placeholder,\n        selection_nonce,\n    )\n\n    if encrypted_selection.proof is None:\n        return None  # log will have happened earlier\n\n    # optionally, skip the verification step\n    if not should_verify_proofs:\n        return encrypted_selection\n\n    # verify the selection.\n    if encrypted_selection.is_valid_encryption(\n        selection_description_hash, elgamal_public_key, crypto_extended_base_hash\n    ):\n        return encrypted_selection\n    log_warning(\n        f\"mismatching selection proof for selection {encrypted_selection.object_id}\"\n    )\n    return None\n\n\ndef encrypt_contest(\n    contest: PlaintextBallotContest,\n    contest_description: ContestDescriptionWithPlaceholders,\n    elgamal_public_key: ElGamalPublicKey,\n    crypto_extended_base_hash: ElementModQ,\n    nonce_seed: ElementModQ,\n    should_verify_proofs: bool = False,\n) -> Optional[CiphertextBallotContest]:\n    \"\"\"\n    Encrypt a specific `BallotContest` in the context of a specific `Ballot`.\n\n    This method accepts a contest representation that only includes `True` selections.\n    It will fill missing selections for a contest with `False` values, and generate `placeholder`\n    selections to represent the number of seats available for a given contest.  By adding `placeholder`\n    votes\n\n    :param contest: the contest in the valid input form\n    :param contest_description: the `ContestDescriptionWithPlaceholders`\n        from the `ContestDescription` which defines this contest's structure\n    :param elgamal_public_key: the public key (k) used to encrypt the ballot\n    :param crypto_extended_base_hash: the extended base hash of the election\n    :param nonce_seed: an `ElementModQ` used as a header to seed the `Nonce` generated for this contest.\n                 this value can be (or derived from) the Ballot nonce, but no relationship is required\n    :param should_verify_proofs: specify if the proofs should be verified prior to returning (default False)\n    \"\"\"\n    error: Optional[ContestErrorType] = None\n    error_data: Optional[List[SelectionId]] = None\n\n    # Validate Input\n    try:\n        contest.valid(contest_description)\n    except OverVoteException as ove:\n        error = ove.type\n        error_data = ove.overvoted_ids\n    except (NullVoteException, UnderVoteException) as nve:\n        error = nve.type\n    except ContestException as ce:\n        log_warning(str(ce))\n        return None\n\n    # account for sequence id\n    contest_description_hash = contest_description.crypto_hash()\n    nonce_sequence = Nonces(contest_description_hash, nonce_seed)\n    contest_nonce = nonce_sequence[contest_description.sequence_order]\n    chaum_pedersen_nonce = next(iter(nonce_sequence))\n\n    encrypted_selections: List[CiphertextBallotSelection] = []\n\n    selection_count = 0\n\n    # TODO: ISSUE #54 this code could be inefficient if we had a contest\n    # with a lot of choices, although the O(n^2) iteration here is small\n    # compared to the huge cost of doing the cryptography.\n\n    # Generate the encrypted selections\n    for description in contest_description.ballot_selections:\n        has_selection = False\n        encrypted_selection = None\n\n        # iterate over the actual selections for each contest description\n        # and apply the selected value if it exists.  If it does not, an explicit\n        # false is entered instead and the selection_count is not incremented\n        # this allows consumers to only pass in the relevant selections made by a voter\n        for selection in contest.ballot_selections:\n            # If overvote, no votes should be counted and instead placeholders should be used.\n            if (\n                selection.object_id == description.object_id\n                and error is not ContestErrorType.OverVote\n            ):\n                # track the selection count so we can append the\n                # appropriate number of true placeholder votes\n                has_selection = True\n                selection_count += selection.vote\n                encrypted_selection = encrypt_selection(\n                    selection,\n                    description,\n                    elgamal_public_key,\n                    crypto_extended_base_hash,\n                    contest_nonce,\n                    should_verify_proofs=should_verify_proofs,\n                )\n                break\n\n        if not has_selection:\n            # No selection was made for this possible value\n            # so we explicitly set it to false\n            encrypted_selection = encrypt_selection(\n                selection_from(description),\n                description,\n                elgamal_public_key,\n                crypto_extended_base_hash,\n                contest_nonce,\n                should_verify_proofs=should_verify_proofs,\n            )\n\n        if encrypted_selection is None:\n            return None  # log will have happened earlier\n        encrypted_selections.append(get_optional(encrypted_selection))\n\n    # Handle Placeholder selections\n    # After we loop through all of the real selections on the ballot,\n    # we loop through each placeholder value and determine if it should be filled in\n\n    # Add a placeholder selection for each possible seat in the contest\n    for placeholder in contest_description.placeholder_selections:\n        # for undervotes, select the placeholder value as true for each available seat\n        # note this pattern is used since DisjunctiveChaumPedersen expects a 0 or 1\n        # so each seat can only have a maximum value of 1 in the current implementation\n        select_placeholder = False\n        if selection_count < contest_description.number_elected:\n            select_placeholder = True\n            selection_count += 1\n\n        encrypted_selection = encrypt_selection(\n            selection=selection_from(\n                description=placeholder,\n                is_placeholder=True,\n                is_affirmative=select_placeholder,\n            ),\n            selection_description=placeholder,\n            elgamal_public_key=elgamal_public_key,\n            crypto_extended_base_hash=crypto_extended_base_hash,\n            nonce_seed=contest_nonce,\n            is_placeholder=True,\n            should_verify_proofs=should_verify_proofs,\n        )\n        if encrypted_selection is None:\n            return None  # log will have happened earlier\n        encrypted_selections.append(get_optional(encrypted_selection))\n\n    encrypted_contest_data = hashed_elgamal_encrypt(\n        ContestData(error, error_data, contest.write_ins).to_bytes(),\n        Nonces(contest_nonce, \"constant-extended-data\")[0],\n        elgamal_public_key,\n        crypto_extended_base_hash,\n    )\n\n    # Create the return object\n    encrypted_contest = make_ciphertext_ballot_contest(\n        contest.object_id,\n        contest_description.sequence_order,\n        contest_description_hash,\n        encrypted_selections,\n        elgamal_public_key,\n        crypto_extended_base_hash,\n        chaum_pedersen_nonce,\n        contest_description.number_elected,\n        nonce=contest_nonce,\n        extended_data=encrypted_contest_data,\n    )\n\n    if should_verify_proofs or not encrypted_contest.proof:\n        if encrypted_contest.is_valid_encryption(\n            contest_description_hash, elgamal_public_key, crypto_extended_base_hash\n        ):\n            return encrypted_contest\n        log_warning(\n            f\"mismatching contest proof for contest {encrypted_contest.object_id}\"\n        )\n        return None\n\n    return encrypted_contest\n\n\n# TODO: ISSUE #57: add the device hash to the function interface so it can be propagated with the ballot.\n# also propagate the seed so that the ballot codes can be regenerated\n# by traversing the collection of ballots encrypted by a specific device\n\n\ndef encrypt_ballot(\n    ballot: PlaintextBallot,\n    internal_manifest: InternalManifest,\n    context: CiphertextElectionContext,\n    encryption_seed: ElementModQ,\n    nonce: Optional[ElementModQ] = None,\n    should_verify_proofs: bool = False,\n) -> Optional[CiphertextBallot]:\n    \"\"\"\n    Encrypt a specific `Ballot` in the context of a specific `CiphertextElectionContext`.\n\n    This method accepts a ballot representation that only includes `True` selections.\n    It will fill missing selections for a contest with `False` values, and generate `placeholder`\n    selections to represent the number of seats available for a given contest.\n\n    This method also allows for ballots to exclude passing contests for which the voter made no selections.\n    It will fill missing contests with `False` selections and generate `placeholder` selections that are marked `True`.\n\n    :param ballot: the ballot in the valid input form\n    :param internal_manifest: the `InternalManifest` which defines this ballot's structure\n    :param context: all the cryptographic context for the election\n    :param encryption_seed: Hash from previous ballot or starting hash from device\n    :param nonce: an optional `int` used to seed the `Nonce` generated for this contest\n                 if this value is not provided, the secret generating mechanism of the OS provides its own\n    :param should_verify_proofs: specify if the proofs should be verified prior to returning (default False)\n    \"\"\"\n\n    # Determine the relevant range of contests for this ballot style\n    style = internal_manifest.get_ballot_style(ballot.style_id)\n\n    # Validate Input\n    if not ballot.is_valid(style.object_id):\n        log_warning(f\"malformed input ballot: {ballot}\")\n        return None\n\n    # Generate a random master nonce to use for the contest and selection nonce's on the ballot\n    random_master_nonce = get_or_else_optional_func(nonce, lambda: rand_q())\n\n    # Include a representation of the election and the external Id in the nonce's used\n    # to derive other nonce values on the ballot\n    nonce_seed = CiphertextBallot.nonce_seed(\n        internal_manifest.manifest_hash,\n        ballot.object_id,\n        random_master_nonce,\n    )\n\n    log_info(f\": manifest_hash : {internal_manifest.manifest_hash.to_hex()}\")\n    log_info(f\": encryption_seed : {encryption_seed.to_hex()}\")\n\n    encrypted_contests = encrypt_ballot_contests(\n        ballot,\n        internal_manifest,\n        context,\n        nonce_seed,\n        should_verify_proofs=should_verify_proofs,\n    )\n    if encrypted_contests is None:\n        return None\n\n    # Create the return object\n    encrypted_ballot = make_ciphertext_ballot(\n        ballot.object_id,\n        ballot.style_id,\n        internal_manifest.manifest_hash,\n        encryption_seed,\n        encrypted_contests,\n        random_master_nonce,\n    )\n\n    if not encrypted_ballot.code:\n        return None\n\n    if not should_verify_proofs:\n        return encrypted_ballot\n\n    # Verify the proofs\n    if encrypted_ballot.is_valid_encryption(\n        internal_manifest.manifest_hash,\n        context.elgamal_public_key,\n        context.crypto_extended_base_hash,\n    ):\n        return encrypted_ballot\n    return None  # log will have happened earlier\n\n\ndef encrypt_ballot_contests(\n    ballot: PlaintextBallot,\n    description: InternalManifest,\n    context: CiphertextElectionContext,\n    nonce_seed: ElementModQ,\n    should_verify_proofs: bool = False,\n) -> Optional[List[CiphertextBallotContest]]:\n    \"\"\"Encrypt contests from a plaintext ballot with a specific style\"\"\"\n    encrypted_contests: List[CiphertextBallotContest] = []\n\n    # Only iterate on contests for this specific ballot style\n    for ballot_style_contest in description.get_contests_for(ballot.style_id):\n        use_contest = None\n        for contest in ballot.contests:\n            if contest.object_id == ballot_style_contest.object_id:\n                use_contest = contest\n                break\n\n        # no selections provided for the contest, so create a placeholder contest\n        if not use_contest:\n            use_contest = contest_from(ballot_style_contest)\n\n        encrypted_contest = encrypt_contest(\n            use_contest,\n            ballot_style_contest,\n            context.elgamal_public_key,\n            context.crypto_extended_base_hash,\n            nonce_seed,\n            should_verify_proofs=should_verify_proofs,\n        )\n\n        if encrypted_contest is None:\n            return None\n        encrypted_contests.append(get_optional(encrypted_contest))\n    return encrypted_contests\n"
  },
  {
    "path": "src/electionguard/group.py",
    "content": "\"\"\"Basic modular math module.\n\nSupport for basic modular math in ElectionGuard. This code's primary purpose is to be \"correct\",\nin the sense that performance may be less than hand-optimized C code, and no guarantees are\nmade about timing or other side-channels.\n\"\"\"\n\nfrom abc import ABC\nfrom typing import Final, Optional, Union\nfrom secrets import randbelow\nfrom sys import maxsize\n\n# pylint: disable=no-name-in-module\nfrom gmpy2 import mpz, powmod, invert\n\nfrom .big_integer import BigInteger\nfrom .constants import get_large_prime, get_small_prime, get_generator\n\n\nclass BaseElement(BigInteger, ABC):\n    \"\"\"An element limited by mod T within [0, T) where T is determined by an upper_bound function.\"\"\"\n\n    def __new__(cls, data: Union[int, str], check_within_bounds: bool = True):  # type: ignore\n        \"\"\"Instantiate element mod T where element is an int or its hex representation.\"\"\"\n        element = super(BaseElement, cls).__new__(cls, data)\n        if check_within_bounds:\n            if not 0 <= element.value < cls.get_upper_bound():\n                raise OverflowError\n        return element\n\n    @classmethod\n    def get_upper_bound(cls) -> int:\n        \"\"\"Get the upper bound for the element.\"\"\"\n        return maxsize\n\n    def is_in_bounds(self) -> bool:\n        \"\"\"\n        Validate that the element is actually within the bounds of [0,Q).\n\n        Returns true if all is good, false if something's wrong.\n        \"\"\"\n        return 0 <= self.value < self.get_upper_bound()\n\n    def is_in_bounds_no_zero(self) -> bool:\n        \"\"\"\n        Validate that the element is actually within the bounds of [1,Q).\n\n        Returns true if all is good, false if something's wrong.\n        \"\"\"\n        return 1 <= self.value < self.get_upper_bound()\n\n\nclass ElementModQ(BaseElement):\n    \"\"\"An element of the smaller `mod q` space, i.e., in [0, Q), where Q is a 256-bit prime.\"\"\"\n\n    @classmethod\n    def get_upper_bound(cls) -> int:\n        \"\"\"Get the upper bound for the element.\"\"\"\n        return get_small_prime()\n\n\nclass ElementModP(BaseElement):\n    \"\"\"An element of the larger `mod p` space, i.e., in [0, P), where P is a 4096-bit prime.\"\"\"\n\n    @classmethod\n    def get_upper_bound(cls) -> int:\n        \"\"\"Get the upper bound for the element.\"\"\"\n        return get_large_prime()\n\n    def is_valid_residue(self) -> bool:\n        \"\"\"Validate that this element is in Z^r_p.\"\"\"\n        residue = pow_p(self, get_small_prime()) == ONE_MOD_P\n        return self.is_in_bounds() and residue\n\n\n# Common constants\nZERO_MOD_Q: Final[ElementModQ] = ElementModQ(0)\nONE_MOD_Q: Final[ElementModQ] = ElementModQ(1)\nTWO_MOD_Q: Final[ElementModQ] = ElementModQ(2)\n\nZERO_MOD_P: Final[ElementModP] = ElementModP(0)\nONE_MOD_P: Final[ElementModP] = ElementModP(1)\nTWO_MOD_P: Final[ElementModP] = ElementModP(2)\n\nElementModPOrQ = Union[ElementModP, ElementModQ]\nElementModPOrQorInt = Union[ElementModP, ElementModQ, int]\nElementModQorInt = Union[ElementModQ, int]\nElementModPorInt = Union[ElementModP, int]\n\n\ndef _get_mpz(input: Union[BaseElement, int]) -> mpz:\n    \"\"\"Get BaseElement or integer as mpz.\"\"\"\n    if isinstance(input, BaseElement):\n        return input.value\n    return mpz(input)\n\n\ndef hex_to_q(input: str) -> Optional[ElementModQ]:\n    \"\"\"\n    Given a hex string representing bytes, returns an ElementModQ.\n\n    Returns `None` if the number is out of the allowed [0,Q) range.\n    \"\"\"\n    try:\n        return ElementModQ(input)\n    except OverflowError:\n        return None\n\n\ndef int_to_q(input: int) -> Optional[ElementModQ]:\n    \"\"\"\n    Given a Python integer, returns an ElementModQ.\n\n    Returns `None` if the number is out of the allowed [0,Q) range.\n    \"\"\"\n    try:\n        return ElementModQ(input)\n    except OverflowError:\n        return None\n\n\ndef hex_to_p(input: str) -> Optional[ElementModP]:\n    \"\"\"\n    Given a hex string representing bytes, returns an ElementModP.\n\n    Returns `None` if the number is out of the allowed [0,Q) range.\n    \"\"\"\n    try:\n        return ElementModP(input)\n    except OverflowError:\n        return None\n\n\ndef int_to_p(input: int) -> Optional[ElementModP]:\n    \"\"\"\n    Given a Python integer, returns an ElementModP.\n\n    Returns `None` if the number is out of the allowed [0,P) range.\n    \"\"\"\n    try:\n        return ElementModP(input)\n    except OverflowError:\n        return None\n\n\ndef add_q(*elems: ElementModQorInt) -> ElementModQ:\n    \"\"\"Add together one or more elements in Q, returns the sum mod Q.\"\"\"\n    sum = _get_mpz(0)\n    for e in elems:\n        e = _get_mpz(e)\n        sum = (sum + e) % get_small_prime()\n    return ElementModQ(sum)\n\n\ndef a_minus_b_q(a: ElementModQorInt, b: ElementModQorInt) -> ElementModQ:\n    \"\"\"Compute (a-b) mod q.\"\"\"\n    a = _get_mpz(a)\n    b = _get_mpz(b)\n    return ElementModQ((a - b) % get_small_prime())\n\n\ndef div_p(a: ElementModPOrQorInt, b: ElementModPOrQorInt) -> ElementModP:\n    \"\"\"Compute a/b mod p.\"\"\"\n    b = _get_mpz(b)\n    inverse = invert(b, _get_mpz(get_large_prime()))\n    return mult_p(a, inverse)\n\n\ndef div_q(a: ElementModPOrQorInt, b: ElementModPOrQorInt) -> ElementModQ:\n    \"\"\"Compute a/b mod q.\"\"\"\n    b = _get_mpz(b)\n    inverse = invert(b, _get_mpz(get_small_prime()))\n    return mult_q(a, inverse)\n\n\ndef negate_q(a: ElementModQorInt) -> ElementModQ:\n    \"\"\"Compute (Q - a) mod q.\"\"\"\n    a = _get_mpz(a)\n    return ElementModQ(get_small_prime() - a)\n\n\ndef a_plus_bc_q(\n    a: ElementModQorInt, b: ElementModQorInt, c: ElementModQorInt\n) -> ElementModQ:\n    \"\"\"Compute (a + b * c) mod q.\"\"\"\n    a = _get_mpz(a)\n    b = _get_mpz(b)\n    c = _get_mpz(c)\n    return ElementModQ((a + b * c) % get_small_prime())\n\n\ndef mult_inv_p(e: ElementModPOrQorInt) -> ElementModP:\n    \"\"\"\n    Compute the multiplicative inverse mod p.\n\n    :param e:  An element in [1, P).\n    \"\"\"\n    e = _get_mpz(e)\n    assert e != 0, \"No multiplicative inverse for zero\"\n    return ElementModP(powmod(e, -1, get_large_prime()))\n\n\ndef pow_p(b: ElementModPOrQorInt, e: ElementModPOrQorInt) -> ElementModP:\n    \"\"\"\n    Compute b^e mod p.\n\n    :param b: An element in [0,P).\n    :param e: An element in [0,P).\n    \"\"\"\n    b = _get_mpz(b)\n    e = _get_mpz(e)\n    return ElementModP(powmod(b, e, get_large_prime()))\n\n\ndef pow_q(b: ElementModQorInt, e: ElementModQorInt) -> ElementModQ:\n    \"\"\"\n    Compute b^e mod q.\n\n    :param b: An element in [0,Q).\n    :param e: An element in [0,Q).\n    \"\"\"\n    b = _get_mpz(b)\n    e = _get_mpz(e)\n    return ElementModQ(powmod(b, e, get_small_prime()))\n\n\ndef mult_p(*elems: ElementModPOrQorInt) -> ElementModP:\n    \"\"\"\n    Compute the product, mod p, of all elements.\n\n    :param elems: Zero or more elements in [0,P).\n    \"\"\"\n    product = _get_mpz(1)\n    for x in elems:\n        x = _get_mpz(x)\n        product = (product * x) % get_large_prime()\n    return ElementModP(product)\n\n\ndef mult_q(*elems: ElementModPOrQorInt) -> ElementModQ:\n    \"\"\"\n    Compute the product, mod q, of all elements.\n\n    :param elems: Zero or more elements in [0,Q).\n    \"\"\"\n    product = _get_mpz(1)\n    for x in elems:\n        x = _get_mpz(x)\n        product = (product * x) % get_small_prime()\n    return ElementModQ(product)\n\n\ndef g_pow_p(e: ElementModPOrQorInt) -> ElementModP:\n    \"\"\"\n    Compute g^e mod p.\n\n    :param e: An element in [0,P).\n    \"\"\"\n    return pow_p(get_generator(), e)\n\n\ndef rand_q() -> ElementModQ:\n    \"\"\"\n    Generate random number between 0 and Q.\n\n    :return: Random value between 0 and Q\n    \"\"\"\n    return ElementModQ(randbelow(get_small_prime()))\n\n\ndef rand_range_q(start: ElementModQorInt) -> ElementModQ:\n    \"\"\"\n    Generate random number between start and Q.\n\n    :param start: Starting value of range\n    :return: Random value between start and Q\n    \"\"\"\n    start = _get_mpz(start)\n    random = 0\n    while random < start:\n        random = randbelow(get_small_prime())\n    return ElementModQ(random)\n"
  },
  {
    "path": "src/electionguard/guardian.py",
    "content": "# pylint: disable=too-many-public-methods\n\nfrom dataclasses import dataclass\nfrom typing import Dict, List, Optional, TypeVar\n\nfrom electionguard.utils import get_optional\n\nfrom .ballot import SubmittedBallot\nfrom .decryption import (\n    compute_compensated_decryption_share,\n    compute_compensated_decryption_share_for_ballot,\n    compute_decryption_share,\n    compute_decryption_share_for_ballot,\n    decrypt_backup,\n)\nfrom .decryption_share import CompensatedDecryptionShare, DecryptionShare\nfrom .election import CiphertextElectionContext\nfrom .election_polynomial import ElectionPolynomial, PublicCommitment\nfrom .elgamal import ElGamalKeyPair, ElGamalPublicKey, elgamal_combine_public_keys\nfrom .group import ElementModP, ElementModQ\nfrom .key_ceremony import (\n    CeremonyDetails,\n    ElectionKeyPair,\n    ElectionPartialKeyBackup,\n    ElectionPartialKeyChallenge,\n    ElectionPartialKeyVerification,\n    ElectionPublicKey,\n    generate_election_key_pair,\n    generate_election_partial_key_backup,\n    generate_election_partial_key_challenge,\n    verify_election_partial_key_backup,\n    verify_election_partial_key_challenge,\n)\nfrom .logs import log_warning\nfrom .schnorr import SchnorrProof\nfrom .tally import CiphertextTally\nfrom .type import BallotId, GuardianId\n\n\n@dataclass\nclass GuardianRecord:\n    \"\"\"\n    Published record containing all required information per Guardian\n    for Election record used in verification processes\n    \"\"\"\n\n    guardian_id: GuardianId\n    \"\"\"Unique identifier of the guardian\"\"\"\n\n    sequence_order: int\n    \"\"\"\n    Unique sequence order of the guardian indicating the order\n    in which the guardian should be processed\n    \"\"\"\n\n    election_public_key: ElGamalPublicKey\n    \"\"\"\n    Guardian's election public key for encrypting election objects.\n    \"\"\"\n\n    election_commitments: List[PublicCommitment]\n    \"\"\"\n    Commitment for each coeffficient of the guardians secret polynomial.\n    First commitment is and should be identical to election_public_key.\n    \"\"\"\n\n    election_proofs: List[SchnorrProof]\n    \"\"\"\n    Proofs for each commitment for each coeffficient of the guardians secret polynomial.\n    First proof is the proof for the election_public_key.\n    \"\"\"\n\n\ndef publish_guardian_record(election_public_key: ElectionPublicKey) -> GuardianRecord:\n    \"\"\"\n    Published record containing all required information per Guardian\n    for Election record used in verification processes\n\n    :param election_public_key: Guardian's election public key\n    :return: Guardian's record\n    \"\"\"\n    return GuardianRecord(\n        election_public_key.owner_id,\n        election_public_key.sequence_order,\n        election_public_key.key,\n        election_public_key.coefficient_commitments,\n        election_public_key.coefficient_proofs,\n    )\n\n\n@dataclass\nclass PrivateGuardianRecord:\n    \"\"\"Unpublishable private record containing information per Guardian.\"\"\"\n\n    guardian_id: GuardianId\n    \"\"\"Unique identifier of the guardian\"\"\"\n\n    election_keys: ElectionKeyPair\n    \"\"\"Private election Key pair of this guardian\"\"\"\n\n    backups_to_share: Dict[GuardianId, ElectionPartialKeyBackup]\n    \"\"\"This guardian's partial key backups that will be shared to other guardians\"\"\"\n\n    guardian_election_public_keys: Dict[GuardianId, ElectionPublicKey]\n    \"\"\"Received election public keys that are shared with this guardian\"\"\"\n\n    guardian_election_partial_key_backups: Dict[GuardianId, ElectionPartialKeyBackup]\n    \"\"\"Received partial key backups that are shared with this guardian\"\"\"\n\n    guardian_election_partial_key_verifications: Dict[\n        GuardianId, ElectionPartialKeyVerification\n    ]\n    \"\"\"Verifications of other guardian's backups\"\"\"\n\n\nclass Guardian:\n    \"\"\"\n    Guardian of election responsible for safeguarding information and decrypting results.\n\n    The first half of the guardian involves the key exchange known as the key ceremony.\n    The second half relates to the decryption process.\n    \"\"\"\n\n    _election_keys: ElectionKeyPair\n    ceremony_details: CeremonyDetails\n\n    _backups_to_share: Dict[GuardianId, ElectionPartialKeyBackup]\n    \"\"\"\n    The collection of this guardian's partial key backups that will be shared to other guardians\n    \"\"\"\n\n    # From Other Guardians\n    _guardian_election_public_keys: Dict[GuardianId, ElectionPublicKey]\n    \"\"\"\n    The collection of other guardians' election public keys that are shared with this guardian\n    \"\"\"\n\n    _guardian_election_partial_key_backups: Dict[GuardianId, ElectionPartialKeyBackup]\n    \"\"\"\n    The collection of other guardians' partial key backups that are shared with this guardian\n    \"\"\"\n\n    _guardian_election_partial_key_verifications: Dict[\n        GuardianId, ElectionPartialKeyVerification\n    ]\n    \"\"\"\n    The collection of other guardians' verifications that they shared their backups correctly\n    \"\"\"\n\n    def __init__(\n        self,\n        key_pair: ElectionKeyPair,\n        ceremony_details: CeremonyDetails,\n        election_public_keys: Optional[Dict[GuardianId, ElectionPublicKey]] = None,\n        partial_key_backups: Optional[\n            Dict[GuardianId, ElectionPartialKeyBackup]\n        ] = None,\n        backups_to_share: Optional[Dict[GuardianId, ElectionPartialKeyBackup]] = None,\n        guardian_election_partial_key_verifications: Optional[\n            Dict[GuardianId, ElectionPartialKeyVerification]\n        ] = None,\n    ) -> None:\n        \"\"\"\n        Initialize a guardian with the specified arguments.\n\n        :param key_pair The key pair the guardian generated during a key ceremony\n        :param ceremony_details The details of the key ceremony\n        :param election_public_keys the public keys the guardian generated during a key ceremony\n        :param partial_key_backups the partial key backups the guardian generated during a key ceremony\n        \"\"\"\n\n        self._election_keys = key_pair\n        self.ceremony_details = ceremony_details\n\n        # Reduce this ⬇️\n        self._backups_to_share = {} if backups_to_share is None else backups_to_share\n        self._guardian_election_public_keys = (\n            {} if election_public_keys is None else election_public_keys\n        )\n        self._guardian_election_partial_key_backups = (\n            {} if partial_key_backups is None else partial_key_backups\n        )\n        self._guardian_election_partial_key_verifications = (\n            {}\n            if guardian_election_partial_key_verifications is None\n            else guardian_election_partial_key_verifications\n        )\n\n        self.save_guardian_key(key_pair.share())\n\n    @property\n    def id(self) -> GuardianId:\n        return self._election_keys.owner_id\n\n    @property\n    def sequence_order(self) -> int:\n        return self._election_keys.sequence_order\n\n    @classmethod\n    def from_public_key(\n        cls,\n        number_of_guardians: int,\n        quorum: int,\n        public_key: ElectionPublicKey,\n    ) -> \"Guardian\":\n        el_gamal_key_pair = ElGamalKeyPair(ElementModQ(0), public_key.key)\n        election_key_pair = ElectionKeyPair(\n            public_key.owner_id,\n            public_key.sequence_order,\n            el_gamal_key_pair,\n            ElectionPolynomial([]),\n        )\n        ceremony_details = CeremonyDetails(number_of_guardians, quorum)\n        return cls(election_key_pair, ceremony_details)\n\n    @classmethod\n    def from_nonce(\n        cls,\n        id: str,\n        sequence_order: int,\n        number_of_guardians: int,\n        quorum: int,\n        nonce: Optional[ElementModQ] = None,\n    ) -> \"Guardian\":\n        \"\"\"Creates a guardian with an `ElementModQ` value that will be used to generate\n        the `ElectionKeyPair`. If no nonce provided, this will be generated automatically.\n        This method should generally only be used for testing.\"\"\"\n        key_pair = generate_election_key_pair(id, sequence_order, quorum, nonce)\n        ceremony_details = CeremonyDetails(number_of_guardians, quorum)\n        return cls(key_pair, ceremony_details)\n\n    @classmethod\n    def from_private_record(\n        cls,\n        private_guardian_record: PrivateGuardianRecord,\n        number_of_guardians: int,\n        quorum: int,\n    ) -> \"Guardian\":\n        guardian = cls(\n            private_guardian_record.election_keys,\n            CeremonyDetails(number_of_guardians, quorum),\n            private_guardian_record.guardian_election_public_keys,\n            private_guardian_record.guardian_election_partial_key_backups,\n            private_guardian_record.backups_to_share,\n            private_guardian_record.guardian_election_partial_key_verifications,\n        )\n\n        return guardian\n\n    def publish(self) -> GuardianRecord:\n        \"\"\"Publish record of guardian with all required information.\"\"\"\n        return publish_guardian_record(self._election_keys.share())\n\n    def export_private_data(self) -> PrivateGuardianRecord:\n        \"\"\"Export private data of guardian. Warning cannot be published.\"\"\"\n        return PrivateGuardianRecord(\n            self.id,\n            self._election_keys,\n            self._backups_to_share,\n            self._guardian_election_public_keys,\n            self._guardian_election_partial_key_backups,\n            self._guardian_election_partial_key_verifications,\n        )\n\n    def set_ceremony_details(self, number_of_guardians: int, quorum: int) -> None:\n        \"\"\"\n        Set ceremony details for election.\n\n        :param number_of_guardians: Number of guardians in election\n        :param quorum: Quorum of guardians required to decrypt\n        \"\"\"\n        self.ceremony_details = CeremonyDetails(number_of_guardians, quorum)\n\n    def decrypt_backup(self, backup: ElectionPartialKeyBackup) -> Optional[ElementModQ]:\n        \"\"\"\n        Decrypts a compensated partial decryption of an elgamal encryption\n        on behalf of a missing guardian.\n\n        :param backup: An encrypted backup from a missing guardian.\n        :return: A decrypted backup.\n        \"\"\"\n\n        return decrypt_backup(get_optional(backup), self._election_keys)\n\n    # Public Keys\n    def share_key(self) -> ElectionPublicKey:\n        \"\"\"\n        Share election public key with another guardian.\n\n        :return: Election public key\n        \"\"\"\n        return self._election_keys.share()\n\n    def save_guardian_key(self, key: ElectionPublicKey) -> None:\n        \"\"\"\n        Save public election keys for another guardian.\n\n        :param key: Election public key\n        \"\"\"\n        self._guardian_election_public_keys[key.owner_id] = key\n\n    def all_guardian_keys_received(self) -> bool:\n        \"\"\"\n        True if all keys have been received.\n\n        :return: All keys backups received\n        \"\"\"\n        return (\n            len(self._guardian_election_public_keys)\n            == self.ceremony_details.number_of_guardians\n        )\n\n    def generate_election_partial_key_backups(self) -> bool:\n        \"\"\"\n        Generate all election partial key backups based on existing public keys.\n        \"\"\"\n        for guardian_key in self._guardian_election_public_keys.values():\n            backup = generate_election_partial_key_backup(\n                self.id, self._election_keys.polynomial, guardian_key\n            )\n            if backup is None:\n                log_warning(\n                    f\"guardian; {self.id} could not generate election partial key backups: failed to encrypt\"\n                )\n                return False\n            self._backups_to_share[guardian_key.owner_id] = backup\n\n        return True\n\n    # Election Partial Key Backup\n    def share_election_partial_key_backup(\n        self, designated_id: GuardianId\n    ) -> Optional[ElectionPartialKeyBackup]:\n        \"\"\"\n        Share election partial key backup with another guardian.\n\n        :param designated_id: Designated guardian\n        :return: Election partial key backup or None\n        \"\"\"\n        return self._backups_to_share.get(designated_id)\n\n    def share_election_partial_key_backups(self) -> List[ElectionPartialKeyBackup]:\n        \"\"\"\n        Share all election partial key backups.\n\n        :return: Election partial key backup or None\n        \"\"\"\n        return list(self._backups_to_share.values())\n\n    def save_election_partial_key_backup(\n        self, backup: ElectionPartialKeyBackup\n    ) -> None:\n        \"\"\"\n        Save election partial key backup from another guardian.\n\n        :param backup: Election partial key backup\n        \"\"\"\n        self._guardian_election_partial_key_backups[backup.owner_id] = backup\n\n    def all_election_partial_key_backups_received(self) -> bool:\n        \"\"\"\n        True if all election partial key backups have been received.\n\n        :return: All election partial key backups received\n        \"\"\"\n        return (\n            len(self._guardian_election_partial_key_backups)\n            == self.ceremony_details.number_of_guardians - 1\n        )\n\n    # Verification\n    def verify_election_partial_key_backup(\n        self,\n        guardian_id: GuardianId,\n    ) -> Optional[ElectionPartialKeyVerification]:\n        \"\"\"\n        Verify election partial key backup value is in polynomial.\n\n        :param guardian_id: Owner of backup to verify\n        :param decrypt:\n        :return: Election partial key verification or None\n        \"\"\"\n        backup = self._guardian_election_partial_key_backups.get(guardian_id)\n        public_key = self._guardian_election_public_keys.get(guardian_id)\n        if backup is None:\n            raise ValueError(f\"No backup exists for {guardian_id}\")\n        if public_key is None:\n            raise ValueError(f\"No public key exists for {guardian_id}\")\n        return verify_election_partial_key_backup(\n            self.id, backup, public_key, self._election_keys\n        )\n\n    def publish_election_backup_challenge(\n        self, guardian_id: GuardianId\n    ) -> Optional[ElectionPartialKeyChallenge]:\n        \"\"\"\n        Publish election backup challenge of election partial key verification.\n\n        :param guardian_id: Owner of election key\n        :return: Election partial key challenge or None\n        \"\"\"\n        backup_in_question = self._backups_to_share.get(guardian_id)\n        if backup_in_question is None:\n            return None\n        return generate_election_partial_key_challenge(\n            backup_in_question, self._election_keys.polynomial\n        )\n\n    def verify_election_partial_key_challenge(\n        self, challenge: ElectionPartialKeyChallenge\n    ) -> ElectionPartialKeyVerification:\n        \"\"\"\n        Verify challenge of previous verification of election partial key.\n\n        :param challenge: Election partial key challenge\n        :return: Election partial key verification\n        \"\"\"\n        return verify_election_partial_key_challenge(self.id, challenge)\n\n    def save_election_partial_key_verification(\n        self, verification: ElectionPartialKeyVerification\n    ) -> None:\n        \"\"\"\n        Save election partial key verification from another guardian.\n\n        :param verification: Election partial key verification\n        \"\"\"\n        self._guardian_election_partial_key_verifications[\n            verification.designated_id\n        ] = verification\n\n    def all_election_partial_key_backups_verified(self) -> bool:\n        \"\"\"\n        True if all election partial key backups have been verified.\n\n        :return: All election partial key backups verified\n        \"\"\"\n        required = self.ceremony_details.number_of_guardians - 1\n        if len(self._guardian_election_partial_key_verifications) != required:\n            return False\n        for verification in self._guardian_election_partial_key_verifications.values():\n            if not verification.verified:\n                return False\n        return True\n\n    # Joint Key\n    def publish_joint_key(self) -> Optional[ElementModP]:\n        \"\"\"\n        Create the joint election key from the public keys of all guardians.\n\n        :return: Optional joint key for election\n        \"\"\"\n        if not self.all_guardian_keys_received():\n            return None\n        if not self.all_election_partial_key_backups_verified():\n            return None\n\n        public_keys = map(\n            lambda public_key: public_key.key,\n            self._guardian_election_public_keys.values(),\n        )\n        return elgamal_combine_public_keys(public_keys)\n\n    def share_other_guardian_key(\n        self, guardian_id: GuardianId\n    ) -> Optional[ElectionPublicKey]:\n        \"\"\"Share other guardians keys shared during key ceremony\"\"\"\n        return self._guardian_election_public_keys.get(guardian_id)\n\n    def compute_tally_share(\n        self, tally: CiphertextTally, context: CiphertextElectionContext\n    ) -> Optional[DecryptionShare]:\n        \"\"\"\n        Compute the decryption share of tally.\n\n        :param tally: Ciphertext tally to get share of\n        :param context: Election context\n        :return: Decryption share of tally or None if failure\n        \"\"\"\n        return compute_decryption_share(\n            self._election_keys,\n            tally,\n            context,\n        )\n\n    def compute_ballot_shares(\n        self, ballots: List[SubmittedBallot], context: CiphertextElectionContext\n    ) -> Dict[BallotId, Optional[DecryptionShare]]:\n        \"\"\"\n        Compute the decryption shares of ballots.\n\n        :param ballots: List of ciphertext ballots to get shares of\n        :param context: Election context\n        :return: Decryption shares of ballots or None if failure\n        \"\"\"\n        shares = {}\n        for ballot in ballots:\n            share = compute_decryption_share_for_ballot(\n                self._election_keys,\n                ballot,\n                context,\n            )\n            shares[ballot.object_id] = share\n        return shares\n\n    def compute_compensated_tally_share(\n        self,\n        missing_guardian_id: GuardianId,\n        tally: CiphertextTally,\n        context: CiphertextElectionContext,\n    ) -> Optional[CompensatedDecryptionShare]:\n        \"\"\"\n        Compute the compensated decryption share of a tally for a missing guardian.\n\n        :param missing_guardian_id: Missing guardians id\n        :param tally: Ciphertext tally to get share of\n        :param context: Election context\n        :return: Compensated decryption share of tally or None if failure\n        \"\"\"\n        # Ensure missing guardian information available\n        missing_guardian_key = self._guardian_election_public_keys.get(\n            missing_guardian_id\n        )\n        missing_guardian_backup = self._guardian_election_partial_key_backups.get(\n            missing_guardian_id\n        )\n        if missing_guardian_key is None or missing_guardian_backup is None:\n            return None\n        missing_guardian_coordinate = self.decrypt_backup(missing_guardian_backup)\n        return compute_compensated_decryption_share(\n            get_optional(missing_guardian_coordinate),\n            self.share_key(),\n            missing_guardian_key,\n            tally,\n            context,\n        )\n\n    def compute_compensated_ballot_shares(\n        self,\n        missing_guardian_id: GuardianId,\n        ballots: List[SubmittedBallot],\n        context: CiphertextElectionContext,\n    ) -> Dict[BallotId, Optional[CompensatedDecryptionShare]]:\n        \"\"\"\n        Compute the compensated decryption share of each ballots for a missing guardian.\n\n        :param missing_guardian_id: Missing guardians id\n        :param ballots: List of ciphertext ballots to get shares of\n        :param context: Election context\n        :return: Compensated decryption shares of ballots or None if failure\n        \"\"\"\n        shares: Dict[BallotId, Optional[CompensatedDecryptionShare]] = {\n            ballot.object_id: None for ballot in ballots\n        }\n        # Ensure missing guardian information available\n        missing_guardian_key = self._guardian_election_public_keys.get(\n            missing_guardian_id\n        )\n        missing_guardian_backup = self._guardian_election_partial_key_backups.get(\n            missing_guardian_id\n        )\n        if missing_guardian_key is None or missing_guardian_backup is None:\n            return shares\n\n        missing_guardian_coordinate = self.decrypt_backup(missing_guardian_backup)\n        for ballot in ballots:\n            share = compute_compensated_decryption_share_for_ballot(\n                get_optional(missing_guardian_coordinate),\n                missing_guardian_key,\n                self.share_key(),\n                ballot,\n                context,\n            )\n            shares[ballot.object_id] = share\n        return shares\n\n\n_SHARE = TypeVar(\"_SHARE\")\n\n\ndef get_valid_ballot_shares(\n    ballot_shares: Dict[BallotId, Optional[_SHARE]],\n) -> Dict[BallotId, _SHARE]:\n    \"\"\"Get valid ballot shares.\"\"\"\n    filtered_shares = {}\n    for ballot_id, ballot_share in ballot_shares.items():\n        if ballot_share is not None:\n            filtered_shares[ballot_id] = ballot_share\n    return filtered_shares\n"
  },
  {
    "path": "src/electionguard/hash.py",
    "content": "# pylint: disable=isinstance-second-argument-not-valid-type\nfrom abc import abstractmethod\nfrom collections.abc import Sequence\nfrom hashlib import sha256\nfrom typing import (\n    Iterable,\n    List,\n    Union,\n    Protocol,\n    runtime_checkable,\n    Sequence as TypedSequence,\n)\n\nfrom .constants import get_small_prime\nfrom .utils import BYTE_ENCODING, BYTE_ORDER\nfrom .group import (\n    ElementModPOrQ,\n    ElementModQ,\n    ElementModP,\n)\n\n\n@runtime_checkable\nclass CryptoHashable(Protocol):\n    \"\"\"\n    Denotes hashable\n    \"\"\"\n\n    @abstractmethod\n    def crypto_hash(self) -> ElementModQ:\n        \"\"\"\n        Generates a hash given the fields on the implementing instance.\n        \"\"\"\n\n\n@runtime_checkable\nclass CryptoHashCheckable(Protocol):\n    \"\"\"\n    Checkable version of crypto hash\n    \"\"\"\n\n    @abstractmethod\n    def crypto_hash_with(self, encryption_seed: ElementModQ) -> ElementModQ:\n        \"\"\"\n        Generates a hash with a given seed that can be checked later against the seed and class metadata.\n        \"\"\"\n\n\n# All the \"atomic\" types that we know how to hash.\nCryptoHashableT = Union[CryptoHashable, ElementModPOrQ, str, int, None]\n\n# \"Compound\" types that we know how to hash. Note that we're using Sequence, rather than List,\n# because Sequences are read-only, and thus safely covariant. All this really means is that\n# we promise never to mutate any list that you pass to hash_elems.\nCryptoHashableAll = Union[\n    TypedSequence[CryptoHashableT],\n    CryptoHashableT,\n]\n\n\ndef hash_elems(*a: CryptoHashableAll) -> ElementModQ:\n    \"\"\"\n    Given zero or more elements, calculate their cryptographic hash\n    using SHA256. Allowed element types are `ElementModP`, `ElementModQ`,\n    `str`, or `int`, anything implementing `CryptoHashable`, and lists\n    or optionals of any of those types.\n\n    :param a: Zero or more elements of any of the accepted types.\n    :return: A cryptographic hash of these elements, concatenated.\n    \"\"\"\n    h = sha256()\n    h.update(\"|\".encode(BYTE_ENCODING))\n    for x in a:\n        # We could just use str(x) for everything, but then we'd have a resulting string\n        # that's a bit Python-specific, and we'd rather make it easier for other languages\n        # to exactly match this hash function.\n\n        if isinstance(x, (ElementModP, ElementModQ)):\n            hash_me = x.to_hex()\n        elif isinstance(x, CryptoHashable):\n            hash_me = x.crypto_hash().to_hex()\n        elif isinstance(x, str):\n            # strings are iterable, so it's important to handle them before list-like types\n            hash_me = x\n        elif isinstance(x, int):\n            hash_me = str(x)\n        elif not x:\n            # This case captures empty lists and None, nicely guaranteeing that we don't\n            # need to do a recursive call if the list is empty. So we need a string to\n            # feed in for both of these cases. \"None\" would be a Python-specific thing,\n            # so we'll go with the more JSON-ish \"null\".\n            hash_me = \"null\"\n        elif isinstance(x, (Sequence, List, Iterable)):\n            # The simplest way to deal with lists, tuples, and such are to crunch them recursively.\n            hash_me = hash_elems(*x).to_hex()\n        else:\n            hash_me = str(x)\n\n        h.update((hash_me + \"|\").encode(BYTE_ENCODING))\n\n    return ElementModQ(\n        int.from_bytes(h.digest(), byteorder=BYTE_ORDER) % get_small_prime()\n    )\n"
  },
  {
    "path": "src/electionguard/hmac.py",
    "content": "\"\"\"Implementation of Hashing for Message Authentication Codes (HMAC)\"\"\"\n\nfrom hmac import digest\nfrom typing import Optional, Literal\n\n_BYTE_LENGTH = 4\n_BYTE_ORDER: Literal[\"little\", \"big\"] = \"little\"\n\n\ndef get_hmac(\n    key: bytes, message: bytes, length: Optional[int] = None, start: int = 0\n) -> bytes:\n    \"\"\"\n    Get a hash-based message authentication code(hmac) digest using\n    default hashing algorithm.\n\n    :param key: key (k) in bytes\n    :param message: message in bytes\n    :param length: length (L) of total message\n    :param start: starting byte position\n    :return: hmac digest in bytes\n    \"\"\"\n\n    if length:\n        message = _fix_message_length(message, length, start)\n\n    return digest(key, message, \"SHA256\")\n\n\ndef _fix_message_length(msg: bytes, length: int, start: int = 0) -> bytes:\n    \"\"\"\n    Fix the message length to a set byte length with starting and end bytes.\n\n    :param msg: message\n    :param length: length (L)\n    :param start: start of byte\n    \"\"\"\n\n    start_byte = start.to_bytes(_BYTE_LENGTH, _BYTE_ORDER)\n    end_byte = length.to_bytes(_BYTE_LENGTH, _BYTE_ORDER)\n    return start_byte + msg + end_byte\n"
  },
  {
    "path": "src/electionguard/key_ceremony.py",
    "content": "from dataclasses import dataclass\nfrom typing import List, Type, TypeVar, Optional\n\nfrom .serialize import padded_decode, padded_encode\nfrom .election_polynomial import (\n    PublicCommitment,\n    compute_polynomial_coordinate,\n    ElectionPolynomial,\n    generate_polynomial,\n    verify_polynomial_coordinate,\n)\nfrom .elgamal import (\n    ElGamalKeyPair,\n    ElGamalPublicKey,\n    HashedElGamalCiphertext,\n    elgamal_combine_public_keys,\n    hashed_elgamal_encrypt,\n)\nfrom .group import ElementModQ, rand_q\nfrom .hash import hash_elems\nfrom .schnorr import SchnorrProof\nfrom .type import (\n    GuardianId,\n    VerifierId,\n)\nfrom .utils import get_optional\n\n\n@dataclass\nclass ElectionPublicKey:\n    \"\"\"A tuple of election public key and owner information\"\"\"\n\n    owner_id: GuardianId\n    \"\"\"\n    The id of the owner guardian\n    \"\"\"\n\n    sequence_order: int\n    \"\"\"\n    The sequence order of the owner guardian\n    \"\"\"\n\n    key: ElGamalPublicKey\n    \"\"\"\n    The election public for the guardian\n    Note: This is the same as the first coefficient commitment\n    \"\"\"\n\n    coefficient_commitments: List[PublicCommitment]\n    \"\"\"\n    The commitments for the coefficients in the secret polynomial\n    \"\"\"\n\n    coefficient_proofs: List[SchnorrProof]\n    \"\"\"\n    The proofs for the coefficients in the secret polynomial\n    \"\"\"\n\n\n@dataclass\nclass ElectionKeyPair:\n    \"\"\"A tuple of election key pair, proof and polynomial\"\"\"\n\n    owner_id: GuardianId\n    \"\"\"\n    The id of the owner guardian\n    \"\"\"\n\n    sequence_order: int\n    \"\"\"\n    The sequence order of the owner guardian\n    \"\"\"\n\n    key_pair: ElGamalKeyPair\n    \"\"\"\n    The pair of public and private election keys for the guardian\n    \"\"\"\n\n    polynomial: ElectionPolynomial\n    \"\"\"\n    The secret polynomial for the guardian\n    \"\"\"\n\n    def share(self) -> ElectionPublicKey:\n        \"\"\"Share the election public key and associated data\"\"\"\n        return ElectionPublicKey(\n            self.owner_id,\n            self.sequence_order,\n            self.key_pair.public_key,\n            self.polynomial.get_commitments(),\n            self.polynomial.get_proofs(),\n        )\n\n\n@dataclass\nclass ElectionJointKey:\n    \"\"\"\n    The Election joint key\n    \"\"\"\n\n    joint_public_key: ElGamalPublicKey\n    \"\"\"\n    The product of the guardian public keys\n    K = ∏ ni=1 Ki mod p.\n    \"\"\"\n    commitment_hash: ElementModQ\n    \"\"\"\n    The hash of the commitments that the guardians make to each other\n    H = H(K 1,0 , K 2,0 ... , K n,0 )\n    \"\"\"\n\n\n@dataclass\nclass ElectionPartialKeyBackup:\n    \"\"\"Election partial key backup used for key sharing\"\"\"\n\n    owner_id: GuardianId\n    \"\"\"\n    The Id of the guardian that generated this backup\n    \"\"\"\n\n    designated_id: GuardianId\n    \"\"\"\n    The Id of the guardian to receive this backup\n    \"\"\"\n\n    designated_sequence_order: int\n    \"\"\"\n    The sequence order of the designated guardian\n    \"\"\"\n\n    encrypted_coordinate: HashedElGamalCiphertext\n    \"\"\"\n    The coordinate corresponding to a secret election polynomial\n    \"\"\"\n\n\n@dataclass\nclass CeremonyDetails:\n    \"\"\"Details of key ceremony\"\"\"\n\n    number_of_guardians: int\n    quorum: int\n\n\n@dataclass\nclass ElectionPartialKeyVerification:\n    \"\"\"Verification of election partial key used in key sharing\"\"\"\n\n    owner_id: GuardianId\n    designated_id: GuardianId\n    verifier_id: GuardianId\n    verified: bool\n\n\n@dataclass\nclass ElectionPartialKeyChallenge:\n    \"\"\"Challenge of election partial key used in key sharing\"\"\"\n\n    owner_id: GuardianId\n    designated_id: GuardianId\n    designated_sequence_order: int\n\n    value: ElementModQ\n    coefficient_commitments: List[PublicCommitment]\n    coefficient_proofs: List[SchnorrProof]\n\n\n_T = TypeVar(\"_T\", bound=\"CoordinateData\")\n\n\n@dataclass\nclass CoordinateData:\n    \"\"\"A coordinate from a PartialKeyBackup that can be serialized and deserialized for encryption/decryption\"\"\"\n\n    coordinate: ElementModQ\n\n    @classmethod\n    def from_bytes(cls: Type[_T], data: bytes) -> _T:\n        return padded_decode(cls, data)\n\n    def to_bytes(self) -> bytes:\n        return padded_encode(self)\n\n\ndef generate_election_key_pair(\n    guardian_id: str,\n    sequence_order: int,\n    quorum: int,\n    nonce: Optional[ElementModQ] = None,\n) -> ElectionKeyPair:\n    \"\"\"\n    Generate election key pair, proof, and polynomial\n    :param quorum: Quorum of guardians needed to decrypt\n    :return: Election key pair\n    \"\"\"\n    polynomial = generate_polynomial(quorum, nonce)\n    key_pair = ElGamalKeyPair(\n        polynomial.coefficients[0].value, polynomial.coefficients[0].commitment\n    )\n    return ElectionKeyPair(guardian_id, sequence_order, key_pair, polynomial)\n\n\ndef generate_election_partial_key_backup(\n    sender_guardian_id: GuardianId,\n    sender_guardian_polynomial: ElectionPolynomial,\n    receiver_guardian_public_key: ElectionPublicKey,\n) -> ElectionPartialKeyBackup:\n    \"\"\"\n    Generate election partial key backup for sharing\n    :param sender_guardian_id: Owner of election key\n    :param sender_guardian_polynomial: The owner's Election polynomial\n    :param receiver_guardian_public_key: The receiving guardian's public key\n    :return: Election partial key backup\n    \"\"\"\n    coordinate = compute_polynomial_coordinate(\n        receiver_guardian_public_key.sequence_order, sender_guardian_polynomial\n    )\n    coordinate_data = CoordinateData(coordinate)\n    nonce = rand_q()\n    seed = get_backup_seed(\n        receiver_guardian_public_key.owner_id,\n        receiver_guardian_public_key.sequence_order,\n    )\n    encrypted_coordinate = hashed_elgamal_encrypt(\n        coordinate_data.to_bytes(),\n        nonce,\n        receiver_guardian_public_key.key,\n        seed,\n    )\n    return ElectionPartialKeyBackup(\n        sender_guardian_id,\n        receiver_guardian_public_key.owner_id,\n        receiver_guardian_public_key.sequence_order,\n        encrypted_coordinate,\n    )\n\n\ndef get_backup_seed(receiver_guardian_id: str, sequence_order: int) -> ElementModQ:\n    return hash_elems(receiver_guardian_id, sequence_order)\n\n\ndef verify_election_partial_key_backup(\n    receiver_guardian_id: str,\n    sender_guardian_backup: ElectionPartialKeyBackup,\n    sender_guardian_public_key: ElectionPublicKey,\n    receiver_guardian_keys: ElectionKeyPair,\n) -> ElectionPartialKeyVerification:\n    \"\"\"\n    Verify election partial key backup contain point on owners polynomial\n    :param receiver_guardian_id: Receiving guardian's identifier\n    :param sender_guardian_backup: Sender guardian's election partial key backup\n    :param sender_guardian_public_key: Sender guardian's election public key\n    :param receiver_guardian_keys: Receiving guardian's key pair\n    \"\"\"\n\n    encryption_seed = get_backup_seed(\n        receiver_guardian_id,\n        sender_guardian_backup.designated_sequence_order,\n    )\n\n    secret_key = receiver_guardian_keys.key_pair.secret_key\n    bytes_optional = sender_guardian_backup.encrypted_coordinate.decrypt(\n        secret_key, encryption_seed\n    )\n    coordinate_data: CoordinateData = CoordinateData.from_bytes(\n        get_optional(bytes_optional)\n    )\n    verified = verify_polynomial_coordinate(\n        coordinate_data.coordinate,\n        sender_guardian_backup.designated_sequence_order,\n        sender_guardian_public_key.coefficient_commitments,\n    )\n    return ElectionPartialKeyVerification(\n        sender_guardian_backup.owner_id,\n        sender_guardian_backup.designated_id,\n        receiver_guardian_id,\n        verified,\n    )\n\n\ndef generate_election_partial_key_challenge(\n    backup: ElectionPartialKeyBackup,\n    polynomial: ElectionPolynomial,\n) -> ElectionPartialKeyChallenge:\n    \"\"\"\n    Generate challenge to a previous verification of a partial key backup\n    :param backup: Election partial key backup in question\n    :param polynomial: Polynomial to regenerate point\n    :return: Election partial key verification\n    \"\"\"\n    return ElectionPartialKeyChallenge(\n        backup.owner_id,\n        backup.designated_id,\n        backup.designated_sequence_order,\n        compute_polynomial_coordinate(backup.designated_sequence_order, polynomial),\n        polynomial.get_commitments(),\n        polynomial.get_proofs(),\n    )\n\n\ndef verify_election_partial_key_challenge(\n    verifier_id: VerifierId, challenge: ElectionPartialKeyChallenge\n) -> ElectionPartialKeyVerification:\n    \"\"\"\n    Verify a challenge to a previous verification of a partial key backup\n    :param verifier_id: Verifier of the challenge\n    :param challenge: Election partial key challenge\n    :return: Election partial key verification\n    \"\"\"\n    return ElectionPartialKeyVerification(\n        challenge.owner_id,\n        challenge.designated_id,\n        verifier_id,\n        verify_polynomial_coordinate(\n            challenge.value,\n            challenge.designated_sequence_order,\n            challenge.coefficient_commitments,\n        ),\n    )\n\n\ndef combine_election_public_keys(\n    election_public_keys: List[ElectionPublicKey],\n) -> ElectionJointKey:\n    \"\"\"\n    Creates a joint election key from the public keys of all guardians\n    :param election_public_keys: all public keys of the guardians\n    :return: ElectionJointKey for election\n    \"\"\"\n    public_keys = [set.key for set in election_public_keys]\n    commitments = [\n        commitment\n        for set in election_public_keys\n        for commitment in set.coefficient_commitments\n    ]\n\n    return ElectionJointKey(\n        joint_public_key=elgamal_combine_public_keys(public_keys),\n        commitment_hash=get_optional(\n            hash_elems(commitments)\n        ),  # H(K 1,0 , K 2,0 ... , K n,0 )\n    )\n"
  },
  {
    "path": "src/electionguard/key_ceremony_mediator.py",
    "content": "from dataclasses import dataclass, field\nfrom typing import Dict, Iterable, List, Optional\nfrom .key_ceremony import (\n    CeremonyDetails,\n    ElectionJointKey,\n    ElectionPartialKeyBackup,\n    ElectionPartialKeyChallenge,\n    ElectionPartialKeyVerification,\n    ElectionPublicKey,\n    combine_election_public_keys,\n    verify_election_partial_key_challenge,\n)\nfrom .type import GuardianId, MediatorId\n\n\n@dataclass(unsafe_hash=True)\nclass GuardianPair:\n    \"\"\"Pair of guardians involved in sharing\"\"\"\n\n    owner_id: GuardianId\n    designated_id: GuardianId\n\n\n@dataclass\nclass BackupVerificationState:\n    \"\"\"The state of the verifications of all guardian election partial key backups\"\"\"\n\n    all_sent: bool = field(default=False)\n    all_verified: bool = field(default=False)\n    failed_verifications: List[GuardianPair] = field(default_factory=list)\n\n\nclass KeyCeremonyMediator:\n    \"\"\"\n    KeyCeremonyMediator for assisting communication between guardians\n    \"\"\"\n\n    id: MediatorId\n    ceremony_details: CeremonyDetails\n\n    # From Guardians\n    # Round 1\n    _election_public_keys: Dict[GuardianId, ElectionPublicKey]\n\n    # Round 2\n    _election_partial_key_backups: Dict[GuardianPair, ElectionPartialKeyBackup]\n\n    # Round 3\n    _election_partial_key_verifications: Dict[\n        GuardianPair, ElectionPartialKeyVerification\n    ]\n\n    def __init__(self, id: MediatorId, ceremony_details: CeremonyDetails):\n        self.id = id\n        self.ceremony_details = ceremony_details\n        self._election_public_keys: Dict[GuardianId, ElectionPublicKey] = {}\n        self._election_partial_key_backups: Dict[\n            GuardianPair, ElectionPartialKeyBackup\n        ] = {}\n        self._election_partial_key_verifications: Dict[\n            GuardianPair, ElectionPartialKeyVerification\n        ] = {}\n        self._election_partial_key_challenges: Dict[\n            GuardianPair, ElectionPartialKeyChallenge\n        ] = {}\n\n    # ROUND 1: Announce guardians with public keys\n    def announce(self, key: ElectionPublicKey) -> None:\n        \"\"\"\n        Announce the guardian as present and participating the Key Ceremony\n        :param key: Guardian's election public key\n        \"\"\"\n        self._receive_election_public_key(key)\n\n    def all_guardians_announced(self) -> bool:\n        \"\"\"\n        Check the annoucement of all the guardians expected\n        :return: True if all guardians in attendance are announced\n        \"\"\"\n        return (\n            len(self._election_public_keys) == self.ceremony_details.number_of_guardians\n        )\n\n    def share_announced(\n        self, requesting_guardian_id: Optional[GuardianId] = None\n    ) -> Optional[List[ElectionPublicKey]]:\n        \"\"\"\n        When all guardians have announced, share their public keys indicating their announcement\n        \"\"\"\n        if not self.all_guardians_announced():\n            return None\n\n        guardian_keys: List[ElectionPublicKey] = []\n        for guardian_id in self._get_announced_guardians():\n            if guardian_id != requesting_guardian_id:\n                guardian_keys.append(self._election_public_keys[guardian_id])\n        return guardian_keys\n\n    # ROUND 2: Share Election Partial Key Backups for compensating\n    def receive_backups(self, backups: List[ElectionPartialKeyBackup]) -> None:\n        \"\"\"\n        Receive all the election partial key backups generated by a guardian\n        \"\"\"\n        if not self.all_guardians_announced():\n            return\n        for backup in backups:\n            self._receive_election_partial_key_backup(backup)\n\n    def all_backups_available(self) -> bool:\n        \"\"\"\n        Check the availability of all the guardians backups\n        :return: True if all guardians have sent backups\n        \"\"\"\n        return (\n            self.all_guardians_announced()\n            and self._all_election_partial_key_backups_available()\n        )\n\n    def share_backups(\n        self, requesting_guardian_id: Optional[GuardianId] = None\n    ) -> Optional[List[ElectionPartialKeyBackup]]:\n        \"\"\"\n        Share all backups designated for a specific guardian\n        \"\"\"\n        if not self.all_guardians_announced() or not self.all_backups_available():\n            return None\n        if not requesting_guardian_id:\n            return list(self._election_partial_key_backups.values())\n        return self._share_election_partial_key_backups_to_guardian(\n            requesting_guardian_id\n        )\n\n    # ROUND 3: Share verifications of backups\n    def receive_backup_verifications(\n        self, verifications: List[ElectionPartialKeyVerification]\n    ) -> None:\n        \"\"\"\n        Receive all the election partial key verifications performed by a guardian\n        \"\"\"\n        if not self.all_backups_available():\n            return\n        for verification in verifications:\n            self._receive_election_partial_key_verification(verification)\n\n    def get_verification_state(self) -> BackupVerificationState:\n        if (\n            not self.all_backups_available()\n            or not self._all_election_partial_key_verifications_received()\n        ):\n            return BackupVerificationState()\n        return self._check_verification_of_election_partial_key_backups()\n\n    def all_backups_verified(self) -> bool:\n        return self.get_verification_state().all_verified\n\n    # ROUND 4 (Optional): If a verification fails, guardian must issue challenge\n    def verify_challenge(\n        self, challenge: ElectionPartialKeyChallenge\n    ) -> ElectionPartialKeyVerification:\n        \"\"\"\n        Mediator receives challenge and will act to mediate and verify\n        \"\"\"\n        verification = verify_election_partial_key_challenge(self.id, challenge)\n        if verification.verified:\n            self._receive_election_partial_key_verification(verification)\n        return verification\n\n    # FINAL: Publish joint public election key\n    def publish_joint_key(self) -> Optional[ElectionJointKey]:\n        \"\"\"\n        Publish joint election key from the public keys of all guardians\n        :return: Joint key for election\n        \"\"\"\n        if not self.all_backups_verified():\n            return None\n\n        return combine_election_public_keys(list(self._election_public_keys.values()))\n\n    def reset(self, ceremony_details: CeremonyDetails) -> None:\n        \"\"\"\n        Reset mediator to initial state\n        :param ceremony_details: Ceremony details of election\n        \"\"\"\n        self.ceremony_details = ceremony_details\n        self._election_public_keys = {}\n        self._election_partial_key_backups = {}\n        self._election_partial_key_challenges = {}\n        self._election_partial_key_verifications = {}\n\n    # Election Public Keys\n    def _receive_election_public_key(self, public_key: ElectionPublicKey) -> None:\n        \"\"\"\n        Receive election public key from guardian\n        :param public_key: election public key\n        \"\"\"\n        self._election_public_keys[public_key.owner_id] = public_key\n\n    def _get_announced_guardians(self) -> Iterable[GuardianId]:\n        return self._election_public_keys.keys()\n\n    # Election Partial Key Backups\n    def _receive_election_partial_key_backup(\n        self, backup: ElectionPartialKeyBackup\n    ) -> None:\n        \"\"\"\n        Receive election partial key backup from guardian\n        :param backup: Election partial key backup\n        :return: boolean indicating success or failure\n        \"\"\"\n        if backup.owner_id == backup.designated_id:\n            return\n        self._election_partial_key_backups[\n            GuardianPair(backup.owner_id, backup.designated_id)\n        ] = backup\n\n    def _all_election_partial_key_backups_available(self) -> bool:\n        \"\"\"\n        True if all election partial key backups for all guardians available\n        :return: All election partial key backups for all guardians available\n        \"\"\"\n        required_backups_per_guardian = self.ceremony_details.number_of_guardians - 1\n        return (\n            len(self._election_partial_key_backups)\n            == required_backups_per_guardian * self.ceremony_details.number_of_guardians\n        )\n\n    def _share_election_partial_key_backups_to_guardian(\n        self, guardian_id: GuardianId\n    ) -> List[ElectionPartialKeyBackup]:\n        \"\"\"\n        Share all election partial key backups for designated guardian\n        :param guardian_id: Recipients guardian id\n        :return: List of guardians designated backups\n        \"\"\"\n        backups: List[ElectionPartialKeyBackup] = []\n        for current_guardian_id in self._get_announced_guardians():\n            if guardian_id != current_guardian_id:\n                backup = self._election_partial_key_backups[\n                    GuardianPair(current_guardian_id, guardian_id)\n                ]\n                if backup is not None:\n                    backups.append(backup)\n        return backups\n\n    # Partial Key Verifications\n    def _receive_election_partial_key_verification(\n        self, verification: ElectionPartialKeyVerification\n    ) -> None:\n        \"\"\"\n        Receive election partial key verification from guardian\n        :param verification: Election partial key verification\n        \"\"\"\n        if verification.owner_id == verification.designated_id:\n            return\n        self._election_partial_key_verifications[\n            GuardianPair(verification.owner_id, verification.designated_id)\n        ] = verification\n\n    def _all_election_partial_key_verifications_received(self) -> bool:\n        \"\"\"\n        True if all election partial key verifications recieved\n        :return: All election partial key verifications received\n        \"\"\"\n        required_verifications_per_guardian = (\n            self.ceremony_details.number_of_guardians - 1\n        )\n        return (\n            len(self._election_partial_key_verifications)\n            == required_verifications_per_guardian\n            * self.ceremony_details.number_of_guardians\n        )\n\n    def _check_verification_of_election_partial_key_backups(\n        self,\n    ) -> BackupVerificationState:\n        \"\"\"\n        True if all election partial key backups verified\n        :return: All election partial key backups verified\n        \"\"\"\n        if not self._all_election_partial_key_verifications_received():\n            return BackupVerificationState()\n        failed_verifications: List[GuardianPair] = []\n        for verification in self._election_partial_key_verifications.values():\n            if not verification.verified:\n                failed_verifications.append(\n                    GuardianPair(verification.owner_id, verification.designated_id)\n                )\n\n        return BackupVerificationState(\n            True, len(failed_verifications) == 0, failed_verifications\n        )\n"
  },
  {
    "path": "src/electionguard/logs.py",
    "content": "import inspect\nimport logging\nimport os.path\nimport sys\nfrom typing import Any, List, Tuple\nfrom logging.handlers import RotatingFileHandler\n\nfrom .singleton import Singleton\n\nFORMAT = \"[%(process)d:%(asctime)s]:%(levelname)s:%(message)s\"\n\n\nclass ElectionGuardLog(Singleton):\n    \"\"\"\n    A singleton log for the library\n    \"\"\"\n\n    __logger: logging.Logger\n    __stream_handler: logging.StreamHandler\n\n    def __init__(self) -> None:\n        super().__init__()\n\n        self.__logger = logging.getLogger(\"electionguard\")\n        # Pythong's logger will use the most restrictive of the logger level and the handler level,\n        #   so set the logger to the lowest level the handler ever might log at\n        self.__logger.setLevel(logging.DEBUG)\n        self.__stream_handler = get_stream_handler(logging.INFO)\n        self.__logger.addHandler(self.__stream_handler)\n\n    @staticmethod\n    def __get_call_info() -> Tuple[str, str, int]:\n        stack = inspect.stack()\n\n        # stack[0]: __get_call_info\n        # stack[1]: __formatted_message\n        # stack[2]: (log method, e.g. \"warn\")\n        # stack[3]: Singleton\n        # stack[4]: caller <-- we want this\n\n        filename = stack[4][1]\n        line = stack[4][2]\n        funcname = stack[4][3]\n\n        return filename, funcname, line\n\n    def __formatted_message(self, message: str) -> str:\n        filename, funcname, line = self.__get_call_info()\n        message = f\"{os.path.basename(filename)}.{funcname}:#L{line}: {message}\"\n        return message\n\n    def set_stream_log_level(self, Level: int) -> None:\n        \"\"\"\n        Sets the stream log level\n        \"\"\"\n        self.remove_handler(self.__stream_handler)\n        self.add_handler(get_stream_handler(Level))\n\n    def add_handler(self, handler: logging.Handler) -> None:\n        \"\"\"\n        Adds a logger handler\n        \"\"\"\n        self.__logger.addHandler(handler)\n\n    def remove_handler(self, handler: logging.Handler) -> None:\n        \"\"\"\n        Removes a logger handler\n        \"\"\"\n        self.__logger.removeHandler(handler)\n\n    def handlers(self) -> List[logging.Handler]:\n        \"\"\"\n        Returns all logging handlers\n        \"\"\"\n        return self.__logger.handlers\n\n    def debug(self, message: str, *args: Any, **kwargs: Any) -> None:\n        \"\"\"\n        Logs a debug message\n        \"\"\"\n        self.__logger.debug(self.__formatted_message(message), *args, **kwargs)\n\n    def info(self, message: str, *args: Any, **kwargs: Any) -> None:\n        \"\"\"\n        Logs a info message\n        \"\"\"\n        self.__logger.info(self.__formatted_message(message), *args, **kwargs)\n\n    def warn(self, message: str, *args: Any, **kwargs: Any) -> None:\n        \"\"\"\n        Logs a warning message\n        \"\"\"\n        self.__logger.warning(self.__formatted_message(message), *args, **kwargs)\n\n    def error(self, message: str, *args: Any, **kwargs: Any) -> None:\n        \"\"\"\n        Logs a error message\n        \"\"\"\n        self.__logger.error(self.__formatted_message(message), *args, **kwargs)\n\n    def critical(self, message: str, *args: Any, **kwargs: Any) -> None:\n        \"\"\"\n        Logs a critical message\n        \"\"\"\n        self.__logger.critical(self.__formatted_message(message), *args, **kwargs)\n\n\ndef get_stream_handler(log_level: int) -> logging.StreamHandler:\n    \"\"\"\n    Get a Stream Handler, sends only warnings and errors to stdout.\n    \"\"\"\n    stream_handler = logging.StreamHandler(sys.stdout)\n    stream_handler.setLevel(log_level)\n    stream_handler.setFormatter(logging.Formatter(FORMAT))\n    return stream_handler\n\n\ndef get_file_handler(log_level: int, filename: str) -> logging.FileHandler:\n    \"\"\"\n    Get a File System Handler, sends verbose logging to a file, `electionguard.log`.\n    When that file gets too large, the logs will rotate, creating files with names\n    like `electionguard.log.1`.\n    \"\"\"\n\n    # TODO: add file compression, save a bunch of space.\n    #   https://medium.com/@rahulraghu94/overriding-pythons-timedrotatingfilehandler-to-compress-your-log-files-iot-c766a4ace240 # pylint: disable=line-too-long\n    file_handler = RotatingFileHandler(\n        filename, \"a\", maxBytes=10_000_000, backupCount=10\n    )\n    file_handler.setLevel(log_level)\n    file_handler.setFormatter(logging.Formatter(FORMAT))\n    return file_handler\n\n\nLOG = ElectionGuardLog()\n\n\ndef log_add_handler(handler: logging.Handler) -> None:\n    \"\"\"\n    Adds a handler to the logger\n    \"\"\"\n    LOG.add_handler(handler)\n\n\ndef log_remove_handler(handler: logging.Handler) -> None:\n    \"\"\"\n    Removes a handler from the logger\n    \"\"\"\n    LOG.remove_handler(handler)\n\n\ndef log_handlers() -> List[logging.Handler]:\n    \"\"\"\n    Returns all logger handlers\n    \"\"\"\n    return LOG.handlers()\n\n\ndef log_debug(msg: str, *args: Any, **kwargs: Any) -> None:\n    \"\"\"\n    Logs a debug message to the console and the file log.\n    \"\"\"\n    LOG.debug(msg, *args, **kwargs)\n\n\ndef log_info(msg: str, *args: Any, **kwargs: Any) -> None:\n    \"\"\"\n    Logs an information message to the console and the file log.\n    \"\"\"\n    LOG.info(msg, *args, **kwargs)\n\n\ndef log_warning(msg: str, *args: Any, **kwargs: Any) -> None:\n    \"\"\"\n    Logs a warning message to the console and the file log.\n    \"\"\"\n    LOG.warn(msg, *args, **kwargs)\n\n\ndef log_error(msg: str, *args: Any, **kwargs: Any) -> None:\n    \"\"\"\n    Logs an error message to the console and the file log.\n    \"\"\"\n    LOG.error(msg, *args, **kwargs)\n\n\ndef log_critical(msg: str, *args: Any, **kwargs: Any) -> None:\n    \"\"\"\n    Logs a critical message to the console and the file log.\n    \"\"\"\n    LOG.critical(msg, *args, **kwargs)\n"
  },
  {
    "path": "src/electionguard/manifest.py",
    "content": "from dataclasses import dataclass, field, InitVar\nfrom datetime import datetime\nfrom enum import Enum, unique\nfrom typing import Dict, cast, List, Optional, Set, Any\n\nfrom .election_object_base import ElectionObjectBase, OrderedObjectBase, list_eq\nfrom .group import ElementModQ\nfrom .hash import CryptoHashable, hash_elems\nfrom .logs import log_warning\nfrom .utils import get_optional, to_iso_date_string\n\n\n@unique\nclass ElectionType(Enum):\n    \"\"\"\n    enumerations for the `ElectionReport` entity\n    see: https://developers.google.com/elections-data/reference/election-type\n    \"\"\"\n\n    unknown = \"unknown\"\n    general = \"general\"\n    partisan_primary_closed = \"partisan_primary_closed\"\n    partisan_primary_open = \"partisan_primary_open\"\n    primary = \"primary\"\n    runoff = \"runoff\"\n    special = \"special\"\n    other = \"other\"\n\n\n@unique\nclass ReportingUnitType(Enum):\n    \"\"\"\n    Enumeration for the type of geopolitical unit\n    see: https://developers.google.com/elections-data/reference/reporting-unit-type\n    \"\"\"\n\n    unknown = \"unknown\"\n    ballot_batch = \"ballot_batch\"\n    ballot_style_area = \"ballot_style_area\"\n    borough = \"borough\"\n    city = \"city\"\n    city_council = \"city_council\"\n    combined_precinct = \"combined_precinct\"\n    congressional = \"congressional\"\n    country = \"country\"\n    county = \"county\"\n    county_council = \"county_council\"\n    drop_box = \"drop_box\"\n    judicial = \"judicial\"\n    municipality = \"municipality\"\n    polling_place = \"polling_place\"\n    precinct = \"precinct\"\n    school = \"school\"\n    special = \"special\"\n    split_precinct = \"split_precinct\"\n    state = \"state\"\n    state_house = \"state_house\"\n    state_senate = \"state_senate\"\n    township = \"township\"\n    utility = \"utility\"\n    village = \"village\"\n    vote_center = \"vote_center\"\n    ward = \"ward\"\n    water = \"water\"\n    other = \"other\"\n\n\n@unique\nclass VoteVariationType(Enum):\n    \"\"\"\n    Enumeration for contest algorithm or rules in the `Contest` entity\n    see: https://developers.google.com/elections-data/reference/vote-variation\n    \"\"\"\n\n    one_of_m = \"one_of_m\"\n    approval = \"approval\"\n    borda = \"borda\"\n    cumulative = \"cumulative\"\n    majority = \"majority\"\n    n_of_m = \"n_of_m\"\n    plurality = \"plurality\"\n    proportional = \"proportional\"\n    range = \"range\"\n    rcv = \"rcv\"\n    super_majority = \"super_majority\"\n    other = \"other\"\n\n\nSUPPORTED_VOTE_VARIATIONS = [\n    VoteVariationType.one_of_m,\n    VoteVariationType.approval,\n    VoteVariationType.majority,\n    VoteVariationType.n_of_m,\n    VoteVariationType.plurality,\n    VoteVariationType.super_majority,\n]\n\n\n# pylint: disable=super-init-not-called\n@dataclass(eq=True, unsafe_hash=True)\nclass AnnotatedString(CryptoHashable):\n    \"\"\"\n    Use this as a type for character strings.\n    See: https://developers.google.com/elections-data/reference/annotated-string\n    \"\"\"\n\n    annotation: str = field(default=\"\")\n    value: str = field(default=\"\")\n\n    # explicit `__init__` added as workaround for https://bugs.python.org/issue45081\n    # can potentially be removed with future python version >3.9.7\n    def __init__(\n        self,\n        annotation: str = \"\",\n        value: str = \"\",\n    ):\n        self.annotation = annotation\n        self.value = value\n\n    def crypto_hash(self) -> ElementModQ:\n        \"\"\"\n        A hash representation of the object\n        \"\"\"\n        hash = hash_elems(self.annotation, self.value)\n        return hash\n\n\n# pylint: disable=super-init-not-called\n@dataclass(eq=True, unsafe_hash=True)\nclass Language(CryptoHashable):\n    \"\"\"\n    The ISO-639 language\n    see: https://en.wikipedia.org/wiki/ISO_639\n    \"\"\"\n\n    value: str\n    language: str = field(default=\"en\")\n\n    # explicit `__init__` added as workaround for https://bugs.python.org/issue45081\n    # can potentially be removed with future python version >3.9.7\n    def __init__(\n        self,\n        value: str,\n        language: str = \"en\",\n    ):\n        self.value = value\n        self.language = language\n\n    def crypto_hash(self) -> ElementModQ:\n        \"\"\"\n        A hash representation of the object\n        \"\"\"\n        hash = hash_elems(self.value, self.language)\n        return hash\n\n\n# pylint: disable=super-init-not-called\n@dataclass(eq=True, unsafe_hash=True)\nclass InternationalizedText(CryptoHashable):\n    \"\"\"\n    Data entity used to represent multi-national text. Use when text on a ballot contains multi-national text.\n    See: https://developers.google.com/elections-data/reference/internationalized-text\n    \"\"\"\n\n    text: List[Language] = field(default_factory=lambda: [])\n\n    # explicit `__init__` added as workaround for https://bugs.python.org/issue45081\n    # can potentially be removed with future python version >3.9.7\n    def __init__(\n        self,\n        text: Optional[List[Language]] = None,\n    ):\n        self.text = text if text else []\n\n    def crypto_hash(self) -> ElementModQ:\n        \"\"\"\n        A hash representation of the object\n        \"\"\"\n        hash = hash_elems(self.text)\n        return hash\n\n\n# pylint: disable=super-init-not-called\n@dataclass(eq=True, unsafe_hash=True)\nclass ContactInformation(CryptoHashable):\n    \"\"\"\n    For defining contact information about objects such as persons, boards of authorities, and organizations.\n    See: https://developers.google.com/elections-data/reference/contact-information\n    \"\"\"\n\n    address_line: Optional[List[str]] = field(default=None)\n    email: Optional[List[AnnotatedString]] = field(default=None)\n    phone: Optional[List[AnnotatedString]] = field(default=None)\n    name: Optional[str] = field(default=None)\n\n    # explicit `__init__` added as workaround for https://bugs.python.org/issue45081\n    # can potentially be removed with future python version >3.9.7\n    def __init__(\n        self,\n        address_line: Optional[List[str]] = None,\n        email: Optional[List[AnnotatedString]] = None,\n        phone: Optional[List[AnnotatedString]] = None,\n        name: Optional[str] = None,\n    ):\n        self.address_line = address_line\n        self.email = email\n        self.phone = phone\n        self.name = name\n\n    def crypto_hash(self) -> ElementModQ:\n        \"\"\"\n        A hash representation of the object\n        \"\"\"\n        hash = hash_elems(self.name, self.address_line, self.email, self.phone)\n        return hash\n\n\n@dataclass(eq=True, unsafe_hash=True)\nclass GeopoliticalUnit(ElectionObjectBase, CryptoHashable):\n    \"\"\"\n    Use this entity for defining geopolitical units such as cities, districts, jurisdictions, or precincts,\n    for the purpose of associating contests, offices, vote counts, or other information with the geographies.\n    See: https://developers.google.com/elections-data/reference/gp-unit\n    \"\"\"\n\n    name: str\n    type: ReportingUnitType\n    contact_information: Optional[ContactInformation] = field(default=None)\n\n    def crypto_hash(self) -> ElementModQ:\n        \"\"\"\n        A hash representation of the object\n        \"\"\"\n        hash = hash_elems(\n            self.object_id, self.name, str(self.type.name), self.contact_information\n        )\n        return hash\n\n\n@dataclass(eq=True, unsafe_hash=True)\nclass BallotStyle(ElectionObjectBase, CryptoHashable):\n    \"\"\"\n    A BallotStyle works as a key to uniquely specify a set of contests. See also `ContestDescription`.\n    \"\"\"\n\n    geopolitical_unit_ids: Optional[List[str]] = field(default=None)\n    party_ids: Optional[List[str]] = field(default=None)\n    image_uri: Optional[str] = field(default=None)\n\n    def crypto_hash(self) -> ElementModQ:\n        \"\"\"\n        A hash representation of the object\n        \"\"\"\n        hash = hash_elems(\n            self.object_id, self.geopolitical_unit_ids, self.party_ids, self.image_uri\n        )\n        return hash\n\n\n@dataclass(eq=True, unsafe_hash=True)\nclass Party(ElectionObjectBase, CryptoHashable):\n    \"\"\"\n    Use this entity to describe a political party that can then be referenced from other entities.\n    See: https://developers.google.com/elections-data/reference/party\n    \"\"\"\n\n    name: InternationalizedText = field(default=InternationalizedText())\n    abbreviation: Optional[str] = field(default=None)\n    color: Optional[str] = field(default=None)\n    logo_uri: Optional[str] = field(default=None)\n\n    def get_party_id(self) -> str:\n        \"\"\"\n        Returns the object identifier associated with the Party.\n        \"\"\"\n        return self.object_id\n\n    def crypto_hash(self) -> ElementModQ:\n        \"\"\"\n        A hash representation of the object\n        \"\"\"\n        hash = hash_elems(\n            self.object_id,\n            self.name,\n            self.abbreviation,\n            self.color,\n            self.logo_uri,\n        )\n        return hash\n\n\n@dataclass(eq=True, unsafe_hash=True)\nclass Candidate(ElectionObjectBase, CryptoHashable):\n    \"\"\"\n    Entity describing information about a candidate in a contest.\n    See: https://developers.google.com/elections-data/reference/candidate\n    Note: The ElectionGuard Data Spec deviates from the NIST model in that\n    selections for any contest type are considered a \"candidate\".\n    for instance, on a yes-no referendum contest, two `candidate` objects\n    would be included in the model to represent the `affirmative` and `negative`\n    selections for the contest.  See the wiki, readme's, and tests in this repo for more info\n    \"\"\"\n\n    name: InternationalizedText = field(default=InternationalizedText())\n    party_id: Optional[str] = field(default=None)\n    image_uri: Optional[str] = field(default=None)\n    is_write_in: Optional[bool] = field(default=None)\n\n    def get_candidate_id(self) -> str:\n        \"\"\"\n        Given a `Candidate`, returns a \"candidate ID\", which is used in other ElectionGuard structures.\n        \"\"\"\n        return self.object_id\n\n    def crypto_hash(self) -> ElementModQ:\n        \"\"\"\n        A hash representation of the object\n        \"\"\"\n        hash = hash_elems(self.object_id, self.name, self.party_id, self.image_uri)\n        return hash\n\n\n@dataclass(eq=True, unsafe_hash=True)\nclass SelectionDescription(OrderedObjectBase, CryptoHashable):\n    \"\"\"\n    Data entity for the ballot selections in a contest,\n    for example linking candidates and parties to their vote counts.\n    See: https://developers.google.com/elections-data/reference/ballot-selection\n    Note: The ElectionGuard Data Spec deviates from the NIST model in that\n    there is no difference for different types of selections.\n    The ElectionGuard Data Spec deviates from the NIST model in that\n    `sequence_order` is a required field since it is used for ordering selections\n    in a contest to ensure various encryption primitives are deterministic.\n    For a given election, the sequence of selections displayed to a user may be different\n    however that information is not captured by default when encrypting a specific ballot.\n    \"\"\"\n\n    candidate_id: str\n\n    def crypto_hash(self) -> ElementModQ:\n        \"\"\"\n        A hash representation of the object\n        \"\"\"\n        hash = hash_elems(self.object_id, self.sequence_order, self.candidate_id)\n        return hash\n\n\n# pylint: disable=too-many-instance-attributes\n@dataclass(unsafe_hash=True)\nclass ContestDescription(OrderedObjectBase, CryptoHashable):\n    \"\"\"\n    Use this data entity for describing a contest and linking the contest\n    to the associated candidates and parties.\n    See: https://developers.google.com/elections-data/reference/contest\n    Note: The ElectionGuard Data Spec deviates from the NIST model in that\n    `sequence_order` is a required field since it is used for ordering selections\n    in a contest to ensure various encryption primitives are deterministic.\n    For a given election, the sequence of contests displayed to a user may be different\n    however that information is not captured by default when encrypting a specific ballot.\n    \"\"\"\n\n    electoral_district_id: str\n\n    vote_variation: VoteVariationType\n\n    # Number of candidates that are elected in the contest (\"n\" of n-of-m).\n    # Note: a referendum is considered a specific case of 1-of-m in ElectionGuard\n    number_elected: int\n\n    # Maximum number of votes/write-ins per voter in this contest. Used in cumulative voting\n    # to indicate how many total votes a voter can spread around. In n-of-m elections, this will\n    # be None.\n    votes_allowed: Optional[int]\n\n    # Name of the contest, not necessarily as it appears on the ballot.\n    name: str\n\n    # For associating a ballot selection for the contest, i.e., a candidate, a ballot measure.\n    ballot_selections: List[SelectionDescription] = field(default_factory=lambda: [])\n\n    # Title of the contest as it appears on the ballot.\n    ballot_title: Optional[InternationalizedText] = field(default=None)\n\n    # Subtitle of the contest as it appears on the ballot.\n    ballot_subtitle: Optional[InternationalizedText] = field(default=None)\n\n    def __eq__(self, other: Any) -> bool:\n        return (\n            isinstance(other, ContestDescription)\n            and self.electoral_district_id == other.electoral_district_id\n            and self.sequence_order == other.sequence_order\n            and self.vote_variation == other.vote_variation\n            and self.number_elected == other.number_elected\n            and self.votes_allowed == other.votes_allowed\n            and self.name == other.name\n            and list_eq(self.ballot_selections, other.ballot_selections)\n            and self.ballot_title == other.ballot_title\n            and self.ballot_subtitle == other.ballot_subtitle\n        )\n\n    def crypto_hash(self) -> ElementModQ:\n        \"\"\"\n        Given a ContestDescription, deterministically derives a \"hash\" of that contest,\n        suitable for use in ElectionGuard's \"base hash\" values, and for validating that\n        either a plaintext or encrypted voted context and its corresponding contest\n        description match up.\n        \"\"\"\n        # remove any placeholders from the hash mechanism\n        hash = hash_elems(\n            self.object_id,\n            self.sequence_order,\n            self.electoral_district_id,\n            str(self.vote_variation.name),\n            self.ballot_title,\n            self.ballot_subtitle,\n            self.name,\n            self.number_elected,\n            self.votes_allowed,\n            self.ballot_selections,\n        )\n        return hash\n\n    def is_valid(self) -> bool:\n        \"\"\"\n        Check the validity of the contest object by verifying its data\n        \"\"\"\n        contest_has_valid_number_elected = self.number_elected <= len(\n            self.ballot_selections\n        )\n        contest_has_valid_votes_allowed = (\n            self.votes_allowed is None or self.number_elected <= self.votes_allowed\n        )\n\n        # verify the candidate_ids, selection object_ids, and sequence_ids are unique\n        candidate_ids: Set[str] = set()\n        selection_ids: Set[str] = set()\n        sequence_ids: Set[int] = set()\n        selection_count = 0\n        expected_selection_count = len(self.ballot_selections)\n\n        for selection in self.ballot_selections:\n            selection_count += 1\n            # validate the object_id\n            if selection.object_id not in selection_ids:\n                selection_ids.add(selection.object_id)\n            # validate the sequence_order\n            if selection.sequence_order not in sequence_ids:\n                sequence_ids.add(selection.sequence_order)\n            # validate the candidate id\n            if selection.candidate_id not in candidate_ids:\n                candidate_ids.add(selection.candidate_id)\n\n        selections_have_valid_candidate_ids = (\n            len(candidate_ids) == expected_selection_count\n        )\n        selections_have_valid_selection_ids = (\n            len(selection_ids) == expected_selection_count\n        )\n        selections_have_valid_sequence_ids = (\n            len(sequence_ids) == expected_selection_count\n        )\n\n        contest_has_supported_vote_variation = (\n            self.vote_variation in SUPPORTED_VOTE_VARIATIONS\n        )\n\n        success = (\n            contest_has_valid_number_elected\n            and contest_has_valid_votes_allowed\n            and selections_have_valid_candidate_ids\n            and selections_have_valid_selection_ids\n            and selections_have_valid_sequence_ids\n            and contest_has_supported_vote_variation\n        )\n\n        if not success:\n            log_warning(\n                \"Contest %s failed validation check: %s\",\n                self.object_id,\n                str(\n                    {\n                        \"contest_has_valid_number_elected\": contest_has_valid_number_elected,\n                        \"contest_has_valid_votes_allowed\": contest_has_valid_votes_allowed,\n                        \"selections_have_valid_candidate_ids\": selections_have_valid_candidate_ids,\n                        \"selections_have_valid_selection_ids\": selections_have_valid_selection_ids,\n                        \"selections_have_valid_sequence_ids\": selections_have_valid_sequence_ids,\n                        \"contest_has_supported_vote_variation\": contest_has_supported_vote_variation,\n                    }\n                ),\n            )\n\n        return success\n\n\n@dataclass(eq=True, unsafe_hash=True)\nclass CandidateContestDescription(ContestDescription):\n    \"\"\"\n    Use this entity to describe a contest that involves selecting one or more candidates.\n    See: https://developers.google.com/elections-data/reference/contest\n    Note: The ElectionGuard Data Spec deviates from the NIST model in that\n    this subclass is used purely for convenience\n    \"\"\"\n\n    primary_party_ids: List[str] = field(default_factory=lambda: [])\n\n\n@dataclass(eq=True, unsafe_hash=True)\nclass ReferendumContestDescription(ContestDescription):\n    \"\"\"\n    Use this entity to describe a contest that involves selecting exactly one 'candidate'.\n    See: https://developers.google.com/elections-data/reference/contest\n    Note: The ElectionGuard Data Spec deviates from the NIST model in that\n    this subclass is used purely for convenience\n    \"\"\"\n\n\n@dataclass(eq=True, unsafe_hash=True)\nclass ContestDescriptionWithPlaceholders(ContestDescription):\n    \"\"\"\n    ContestDescriptionWithPlaceholders is a `ContestDescription` with ElectionGuard `placeholder_selections`.\n    (The ElectionGuard spec requires for n-of-m elections that there be *exactly* n counters that are one\n    with the rest zero, so if a voter deliberately undervotes, one or more of the placeholder counters will\n    become one. This allows the `ConstantChaumPedersenProof` to verify correctly for undervoted contests.)\n    \"\"\"\n\n    placeholder_selections: List[SelectionDescription] = field(\n        default_factory=lambda: []\n    )\n\n    def is_valid(self) -> bool:\n        \"\"\"\n        Checks is contest description is valid\n        :return: true if valid\n        \"\"\"\n        contest_description_validates = super().is_valid()\n        return (\n            contest_description_validates\n            and len(self.placeholder_selections) == self.number_elected\n        )\n\n    def is_placeholder(self, selection: SelectionDescription) -> bool:\n        \"\"\"\n        Checks is contest description is placeholder\n        :return: true if placeholder\n        \"\"\"\n        return selection in self.placeholder_selections\n\n    def selection_for(self, selection_id: str) -> Optional[SelectionDescription]:\n        \"\"\"\n        Gets the description for a particular id\n        :param selection_id: Id of Selection\n        :return: description\n        \"\"\"\n        matching_selections = list(\n            filter(lambda i: i.object_id == selection_id, self.ballot_selections)\n        )\n\n        if any(matching_selections):\n            return matching_selections[0]\n\n        matching_placeholders = list(\n            filter(lambda i: i.object_id == selection_id, self.placeholder_selections)\n        )\n\n        if any(matching_placeholders):\n            return matching_placeholders[0]\n        return None\n\n\nclass SpecVersion(Enum):\n    \"\"\"Specify ElectionGuard Versions\"\"\"\n\n    EG0_95 = \"v0.95\"\n    EG1_0 = \"1.0\"\n\n\n# pylint: disable=too-many-instance-attributes,super-init-not-called\n@dataclass(unsafe_hash=True)\nclass Manifest(CryptoHashable):\n    \"\"\"\n    Use this entity for defining the structure of the election and associated\n    information such as candidates, contests, and vote counts.  This class is\n    based on the NIST Election Common Standard Data Specification.  Some deviations\n    from the standard exist.\n\n    This structure is considered an immutable input object and should not be changed\n    through the course of an election, as it's hash representation is the basis for all\n    other hash representations within an ElectionGuard election context.\n\n    See: https://developers.google.com/elections-data/reference/election\n    \"\"\"\n\n    election_scope_id: str\n    spec_version: SpecVersion\n    type: ElectionType\n    start_date: datetime\n    end_date: datetime\n    geopolitical_units: List[GeopoliticalUnit]\n    parties: List[Party]\n    candidates: List[Candidate]\n    contests: List[ContestDescription]\n    ballot_styles: List[BallotStyle]\n    name: Optional[InternationalizedText] = field(default=None)\n    contact_information: Optional[ContactInformation] = field(default=None)\n\n    # explicit `__init__` added as workaround for https://bugs.python.org/issue45081\n    # can potentially be removed with future python version >3.9.7\n    def __init__(\n        self,\n        election_scope_id: str,\n        spec_version: SpecVersion,\n        type: ElectionType,\n        start_date: datetime,\n        end_date: datetime,\n        geopolitical_units: List[GeopoliticalUnit],\n        parties: List[Party],\n        candidates: List[Candidate],\n        contests: List[ContestDescription],\n        ballot_styles: List[BallotStyle],\n        name: Optional[InternationalizedText] = None,\n        contact_information: Optional[ContactInformation] = None,\n    ):\n        self.election_scope_id = election_scope_id\n        self.spec_version = spec_version\n        self.type = type\n        self.start_date = start_date\n        self.end_date = end_date\n        self.geopolitical_units = geopolitical_units\n        self.parties = parties\n        self.candidates = candidates\n        self.contests = contests\n        self.ballot_styles = ballot_styles\n        self.name = name\n        self.contact_information = contact_information\n\n    def __eq__(self, other: Any) -> bool:\n        return (\n            isinstance(other, Manifest)\n            and self.election_scope_id == other.election_scope_id\n            and self.type == other.type\n            and self.start_date == other.start_date\n            and self.end_date == other.end_date\n            and list_eq(self.geopolitical_units, other.geopolitical_units)\n            and list_eq(self.parties, other.parties)\n            and list_eq(self.candidates, other.candidates)\n            and list_eq(self.contests, other.contests)\n            and list_eq(self.ballot_styles, other.ballot_styles)\n            and self.name == other.name\n            and self.contact_information == other.contact_information\n        )\n\n    def crypto_hash(self) -> ElementModQ:\n        \"\"\"\n        Returns a hash of the metadata components of the election\n        \"\"\"\n        hash = hash_elems(\n            self.election_scope_id,\n            str(self.type.name),\n            to_iso_date_string(self.start_date),\n            to_iso_date_string(self.end_date),\n            self.name,\n            self.contact_information,\n            self.geopolitical_units,\n            self.parties,\n            self.contests,\n            self.ballot_styles,\n        )\n        return hash\n\n    def is_valid(self) -> bool:\n        \"\"\"\n        Verifies the dataset to ensure it is well-formed\n        \"\"\"\n        gp_unit_ids: Set[str] = set()\n        ballot_style_ids: Set[str] = set()\n        party_ids: Set[str] = set()\n        candidate_ids: Set[str] = set()\n        contest_ids: Set[str] = set()\n\n        # Validate GP Units\n        for gp_unit in self.geopolitical_units:\n            if gp_unit.object_id not in gp_unit_ids:\n                gp_unit_ids.add(gp_unit.object_id)\n\n        # fail if there are duplicates\n        geopolitical_units_valid = len(gp_unit_ids) == len(self.geopolitical_units)\n\n        # Validate Ballot Styles\n        ballot_styles_have_valid_gp_unit_ids = True\n\n        for style in self.ballot_styles:\n            if style.object_id not in ballot_style_ids:\n                ballot_style_ids.add(style.object_id)\n            if style.geopolitical_unit_ids is None:\n                ballot_styles_have_valid_gp_unit_ids = False\n                break\n            # validate associated gp unit ids\n            for gp_unit_id in style.geopolitical_unit_ids:\n                ballot_styles_have_valid_gp_unit_ids = (\n                    ballot_styles_have_valid_gp_unit_ids and gp_unit_id in gp_unit_ids\n                )\n\n        ballot_styles_valid = (\n            len(ballot_style_ids) == len(self.ballot_styles)\n            and ballot_styles_have_valid_gp_unit_ids\n        )\n\n        # Validate Parties\n        for party in self.parties:\n            if party.object_id not in party_ids:\n                party_ids.add(party.object_id)\n\n        parties_valid = len(party_ids) == len(self.parties)\n\n        # Validate Candidates\n        candidates_have_valid_party_ids = True\n\n        for candidate in self.candidates:\n            if candidate.object_id not in candidate_ids:\n                candidate_ids.add(candidate.object_id)\n            # validate the associated party id\n            candidates_have_valid_party_ids = candidates_have_valid_party_ids and (\n                candidate.party_id is None or candidate.party_id in party_ids\n            )\n\n        candidates_have_valid_length = len(candidate_ids) == len(self.candidates)\n        candidates_valid = (\n            candidates_have_valid_length and candidates_have_valid_party_ids\n        )\n\n        # Validate Contests\n        contests_validate_their_properties = True\n        contests_have_valid_electoral_district_id = True\n        candidate_contests_have_valid_party_ids = True\n\n        contest_sequence_ids: Set[int] = set()\n\n        for contest in self.contests:\n\n            contests_validate_their_properties = (\n                contests_validate_their_properties and contest.is_valid()\n            )\n\n            if contest.object_id not in contest_ids:\n                contest_ids.add(contest.object_id)\n\n            # validate the sequence order\n            if contest.sequence_order not in contest_sequence_ids:\n                contest_sequence_ids.add(contest.sequence_order)\n\n            # validate the associated gp unit id\n            contests_have_valid_electoral_district_id = (\n                contests_have_valid_electoral_district_id\n                and contest.electoral_district_id in gp_unit_ids\n            )\n\n            if isinstance(contest, CandidateContestDescription):\n                candidate_contest = cast(CandidateContestDescription, contest)\n                if candidate_contest.primary_party_ids is not None:\n                    for primary_party_id in candidate_contest.primary_party_ids:\n                        # validate the party ids\n                        candidate_contests_have_valid_party_ids = (\n                            candidate_contests_have_valid_party_ids\n                            and primary_party_id in party_ids\n                        )\n\n        # TODO: ISSUE #55: verify that the contest sequence order set is in the proper order\n\n        contests_have_valid_object_ids = len(contest_ids) == len(self.contests)\n        contests_have_valid_sequence_ids = len(contest_sequence_ids) == len(\n            self.contests\n        )\n        contests_valid = (\n            contests_have_valid_object_ids\n            and contests_have_valid_sequence_ids\n            and contests_validate_their_properties\n            and contests_have_valid_electoral_district_id\n            and candidate_contests_have_valid_party_ids\n        )\n\n        success = (\n            geopolitical_units_valid\n            and ballot_styles_valid\n            and parties_valid\n            and candidates_valid\n            and contests_valid\n        )\n\n        if not success:\n            log_warning(\n                \"Election failed validation check: is_valid: %s\",\n                str(\n                    {\n                        \"geopolitical_units_valid\": geopolitical_units_valid,\n                        \"ballot_styles_valid\": ballot_styles_valid,\n                        \"ballot_styles_have_valid_gp_unit_ids\": ballot_styles_have_valid_gp_unit_ids,\n                        \"parties_valid\": parties_valid,\n                        \"candidates_valid\": candidates_valid,\n                        \"candidates_have_valid_length\": candidates_have_valid_length,\n                        \"candidates_have_valid_party_ids\": candidates_have_valid_party_ids,\n                        \"contests_valid\": contests_valid,\n                        \"contests_have_valid_object_ids\": contests_have_valid_object_ids,\n                        \"contests_have_valid_sequence_ids\": contests_have_valid_sequence_ids,\n                        \"contests_validate_their_properties\": contests_validate_their_properties,\n                        \"contests_have_valid_electoral_district_id\": contests_have_valid_electoral_district_id,\n                        \"candidate_contests_have_valid_party_ids\": candidate_contests_have_valid_party_ids,\n                    }\n                ),\n            )\n        return success\n\n    def _get_candidate_name(self, candidate: Candidate, lang: str) -> str:\n        if candidate.is_write_in:\n            return \"Write-In\"\n        return get_i8n_value(candidate.name, lang, candidate.object_id)\n\n    def _get_candidate_names(self, lang: str) -> Dict[str, str]:\n        return {\n            candidate.object_id: self._get_candidate_name(candidate, lang)\n            for candidate in self.candidates\n        }\n\n    def _get_selections_with_candidate_id(self) -> Dict[str, str]:\n        selections: Dict[str, str] = {}\n        for contest in self.contests:\n            for selection in contest.ballot_selections:\n                selections.update({selection.object_id: selection.candidate_id})\n        return selections\n\n    def _replace_candidate_ids_with_names(\n        self, selections: Dict[str, str], candidates: Dict[str, str]\n    ) -> None:\n        for selection_id, candidate_id in selections.items():\n            candidate_name = candidates.get(candidate_id)\n            if candidate_name is not None:\n                selections.update({selection_id: candidate_name})\n\n    def get_selection_names(self, lang: str) -> Dict[str, str]:\n        \"\"\"\n        Retrieves a dictionary whose keys are all selection id's and whose values are\n        those selection's candidate names in the supplied language if available\n        \"\"\"\n        candidates = self._get_candidate_names(lang)\n        selections = self._get_selections_with_candidate_id()\n        self._replace_candidate_ids_with_names(selections, candidates)\n        return selections\n\n    def get_contest_names(self) -> Dict[str, str]:\n        \"\"\"\n        Retrieves a dictionary whose keys are all contest id's and whose values are\n        those contest's names\n        \"\"\"\n\n        return {contest.object_id: contest.name for contest in self.contests}\n\n    def get_name(self) -> str:\n        def get_first_value(text: Optional[InternationalizedText]) -> str:\n            return \"\" if text is None else text.text[0].value\n\n        return get_first_value(self.name)\n\n\n@dataclass(eq=True, unsafe_hash=True)\nclass InternalManifest:\n    \"\"\"\n    `InternalManifest` is a subset of the `Manifest` structure that specifies\n    the components that ElectionGuard uses for conducting an election.  The key component is the\n    `contests` collection, which applies placeholder selections to the `Manifest` contests\n    \"\"\"\n\n    manifest: InitVar[Manifest] = None\n\n    geopolitical_units: List[GeopoliticalUnit] = field(init=False)\n\n    contests: List[ContestDescriptionWithPlaceholders] = field(init=False)\n\n    ballot_styles: List[BallotStyle] = field(init=False)\n\n    manifest_hash: ElementModQ = field(init=False)\n\n    def __post_init__(self, manifest: Manifest) -> None:\n        object.__setattr__(self, \"manifest_hash\", manifest.crypto_hash())\n        object.__setattr__(self, \"geopolitical_units\", manifest.geopolitical_units)\n        object.__setattr__(self, \"ballot_styles\", manifest.ballot_styles)\n        object.__setattr__(\n            self, \"contests\", self._generate_contests_with_placeholders(manifest)\n        )\n\n    def contest_for(\n        self, contest_id: str\n    ) -> Optional[ContestDescriptionWithPlaceholders]:\n        \"\"\"\n        Get contest by id\n        :param contest_id: Contest id\n        :return: Contest description or none\n        \"\"\"\n        matching_contests = list(\n            filter(lambda i: i.object_id == contest_id, self.contests)\n        )\n\n        if any(matching_contests):\n            return matching_contests[0]\n        return None\n\n    def get_ballot_style(self, ballot_style_id: str) -> BallotStyle:\n        \"\"\"\n        Get a ballot style for a specified ballot_style_id\n        \"\"\"\n        style = list(\n            filter(lambda i: i.object_id == ballot_style_id, self.ballot_styles)\n        )[0]\n        return style\n\n    def get_contests_for(\n        self, ballot_style_id: str\n    ) -> List[ContestDescriptionWithPlaceholders]:\n        \"\"\"\n        Get contests for a ballot style\n        :param ballot_style_id: ballot style id\n        :return: contest descriptions\n        \"\"\"\n        style = self.get_ballot_style(ballot_style_id)\n        if style.geopolitical_unit_ids is None:\n            return []\n        # pylint: disable=unnecessary-comprehension\n        gp_unit_ids = [gp_unit_id for gp_unit_id in style.geopolitical_unit_ids]\n        contests = list(\n            filter(lambda i: i.electoral_district_id in gp_unit_ids, self.contests)\n        )\n        return contests\n\n    @staticmethod\n    def _generate_contests_with_placeholders(\n        manifest: Manifest,\n    ) -> List[ContestDescriptionWithPlaceholders]:\n        \"\"\"\n        For each contest, append the `number_elected` number\n        of placeholder selections to the end of the contest collection\n        \"\"\"\n        contests: List[ContestDescriptionWithPlaceholders] = []\n        for contest in manifest.contests:\n            placeholder_selections = generate_placeholder_selections_from(\n                contest, contest.number_elected\n            )\n            contests.append(\n                contest_description_with_placeholders_from(\n                    contest, placeholder_selections\n                )\n            )\n\n        return contests\n\n\ndef contest_description_with_placeholders_from(\n    description: ContestDescription, placeholders: List[SelectionDescription]\n) -> ContestDescriptionWithPlaceholders:\n    \"\"\"\n    Generates a placeholder selection description\n    :param description: contest description\n    :param placeholders: list of placeholder descriptions of selections\n    :return: a SelectionDescription or None\n    \"\"\"\n    return ContestDescriptionWithPlaceholders(\n        object_id=description.object_id,\n        electoral_district_id=description.electoral_district_id,\n        sequence_order=description.sequence_order,\n        vote_variation=description.vote_variation,\n        number_elected=description.number_elected,\n        votes_allowed=description.votes_allowed,\n        name=description.name,\n        ballot_selections=description.ballot_selections,\n        ballot_title=description.ballot_title,\n        ballot_subtitle=description.ballot_subtitle,\n        placeholder_selections=placeholders,\n    )\n\n\ndef generate_placeholder_selection_from(\n    contest: ContestDescription, use_sequence_id: Optional[int] = None\n) -> Optional[SelectionDescription]:\n    \"\"\"\n    Generates a placeholder selection description that is unique so it can be hashed\n\n    :param use_sequence_id: an optional integer unique to the contest identifying this selection's place in the contest\n    :return: a SelectionDescription or None\n    \"\"\"\n    sequence_ids = [selection.sequence_order for selection in contest.ballot_selections]\n    if use_sequence_id is None:\n        # if no sequence order is specified, take the max\n        use_sequence_id = max(*sequence_ids) + 1\n    elif use_sequence_id in sequence_ids:\n        log_warning(\n            f\"mismatched placeholder selection {use_sequence_id} already exists\"\n        )\n        return None\n\n    placeholder_object_id = f\"{contest.object_id}-{use_sequence_id}\"\n    return SelectionDescription(\n        f\"{placeholder_object_id}-placeholder\",\n        use_sequence_id,\n        f\"{placeholder_object_id}-candidate\",\n    )\n\n\ndef generate_placeholder_selections_from(\n    contest: ContestDescription, count: int\n) -> List[SelectionDescription]:\n    \"\"\"\n    Generates the specified number of placeholder selections in\n    ascending sequence order from the max selection sequence orderf\n\n    :param contest: ContestDescription for input\n    :param count: optionally specify a number of placeholders to generate\n    :return: a collection of `SelectionDescription` objects, which may be empty\n    \"\"\"\n    max_sequence_order = max(\n        selection.sequence_order for selection in contest.ballot_selections\n    )\n    selections: List[SelectionDescription] = []\n    for i in range(count):\n        sequence_order = max_sequence_order + 1 + i\n        selections.append(\n            get_optional(generate_placeholder_selection_from(contest, sequence_order))\n        )\n    return selections\n\n\ndef get_i8n_value(name: InternationalizedText, lang: str, default_val: str) -> str:\n    query = (t.value for t in name.text if t.language == lang)\n    result = next(query, \"\")\n    return default_val if result == \"\" else result\n"
  },
  {
    "path": "src/electionguard/nonces.py",
    "content": "# pylint: disable=too-many-ancestors\nfrom typing import Union, Sequence, List, overload\n\nfrom electionguard.group import ElementModQ, ElementModPOrQ\nfrom electionguard.hash import hash_elems\n\n\nclass Nonces(Sequence[ElementModQ]):\n    \"\"\"\n    Creates a sequence of random elements in [0,Q), seeded from an initial element in [0,Q).\n    If you start with the same seed, you'll get exactly the same sequence. Optional string\n    or ElementModPOrQ \"headers\" can be included alongside the seed both at construction time\n    and when asking for the next nonce. This is useful when specifying what a nonce is\n    being used for, to avoid various kinds of subtle cryptographic attacks.\n\n    The Nonces class is a Sequence. It can be iterated, or it can be treated as an array\n    and indexed. Asking for a nonce is constant time, regardless of the index.\n    \"\"\"\n\n    def __init__(self, seed: ElementModQ, *headers: Union[str, ElementModPOrQ]) -> None:\n        if len(headers) > 0:\n            self.__seed: ElementModQ = hash_elems(seed, *headers)\n        else:\n            self.__seed = seed\n\n    # https://github.com/python/mypy/issues/4108\n    @overload\n    def __getitem__(self, index: int) -> ElementModQ:\n        pass\n\n    @overload\n    def __getitem__(self, index: slice) -> List[ElementModQ]:\n        pass\n\n    def __getitem__(\n        self, index: Union[slice, int]\n    ) -> Union[ElementModQ, List[ElementModQ]]:\n        if isinstance(index, int):\n            return self.get_with_headers(index)\n        if isinstance(index.stop, int):\n            # Handling slices is a pain: https://stackoverflow.com/a/42731787\n            indices = range(index.start or 0, index.stop, index.step or 1)\n            return [self[i] for i in indices]\n        raise TypeError(\"Cannot take unbounded slice of Nonces\")\n\n    def __len__(self) -> int:\n        raise TypeError(\"Nonces does not have finite length\")\n\n    def get_with_headers(self, item: int, *headers: str) -> ElementModQ:\n        \"\"\"\n        Gets an item from the sequence at any offset. Headers can be included\n        to optionally help specify what a nonce is being used for.\n\n        :param item: Index into the nonces.\n        :param headers:  Optional string headers.\n        \"\"\"\n        if item < 0:\n            raise TypeError(\"Nonces do not support negative indices.\")\n        return hash_elems(self.__seed, item, *headers)\n"
  },
  {
    "path": "src/electionguard/proof.py",
    "content": "from enum import Enum\n\nfrom .utils import space_between_capitals\n\n\nclass ProofUsage(Enum):\n    \"\"\"Usage case for proof\"\"\"\n\n    Unknown = \"Unknown\"\n    SecretValue = \"Prove knowledge of secret value\"\n    SelectionLimit = \"Prove value within selection's limit\"\n    SelectionValue = \"Prove selection's value (0 or 1)\"\n\n\nclass Proof:\n    \"\"\"Base class for proofs with name and usage case\"\"\"\n\n    name: str = \"Proof\"\n    usage: ProofUsage = ProofUsage.Unknown\n\n    def __init__(self) -> None:\n        object.__setattr__(\n            self, \"name\", space_between_capitals(self.__class__.__name__)\n        )\n"
  },
  {
    "path": "src/electionguard/py.typed",
    "content": ""
  },
  {
    "path": "src/electionguard/scheduler.py",
    "content": "# pylint: disable=consider-using-with\nfrom __future__ import annotations\nimport traceback\nfrom typing import Any, Callable, Iterable, List, TypeVar\nfrom contextlib import AbstractContextManager\nfrom multiprocessing import Pool as ProcessPool\nfrom multiprocessing.dummy import Pool as ThreadPool\nfrom multiprocessing.pool import Pool\nfrom psutil import cpu_count\n\nfrom .logs import log_warning\nfrom .singleton import Singleton\n\n_T = TypeVar(\"_T\")\n\n\nclass Scheduler(Singleton, AbstractContextManager):\n    \"\"\"\n    Worker that wraps Multprocessing and allows\n    for shared context or spawning processes.\n    Implemented as a singleton to guarantee there is only one set\n    of tread and process pools in use throughout the library.\n    Also implements the [Context Manager Protocol](https://docs.python.org/3.8/library/stdtypes.html#typecontextmanager)\n    \"\"\"\n\n    __process_pool: Pool\n    __thread_pool: Pool\n\n    def __init__(self) -> None:\n        super().__init__()\n        self._open()\n\n    def __enter__(self) -> Scheduler:\n        self._open()\n        return self\n\n    def __exit__(self, exc_type: Any, exc_value: Any, exc_traceback: Any) -> None:\n        self.close()\n\n    def _open(self) -> None:\n        \"\"\"Open pools\"\"\"\n        max_processes = cpu_count(logical=False)\n        # Reserve one CPU for I/O bound tasks\n        if max_processes > 2:\n            max_processes = max_processes - 1\n        self.__process_pool = ProcessPool(max_processes)\n        self.__thread_pool = ThreadPool(max_processes)\n\n    def close(self) -> None:\n        \"\"\"Close pools\"\"\"\n        self.__process_pool.close()\n        self.__thread_pool.close()\n\n    @staticmethod\n    def cpu_count() -> int:\n        \"\"\"Get CPU count\"\"\"\n        return int(cpu_count(logical=False))\n\n    def schedule(\n        self,\n        task: Callable,\n        arguments: Iterable[Iterable[Any]],\n        with_shared_resources: bool = False,\n    ) -> List[_T]:\n        \"\"\"\n        Schedule tasks with list of arguments\n        :param task: the callable task to execute\n        :param arguments: the list of lists passed to the task using starmap\n        :param with_shared_resources: flag to use threads instead of processes\n            allowing resources to be shared.  note\n            when using the threadpool, execution is bound\n            by default to the [global interpreter lock]\n            (https://docs.python.org/3.8/glossary.html#term-global-interpreter-lock)\n        \"\"\"\n        if with_shared_resources:\n            return self.safe_starmap(self.__thread_pool, task, arguments)\n        return self.safe_starmap(self.__process_pool, task, arguments)\n\n    @staticmethod\n    def safe_starmap(\n        pool: Pool, task: Callable, arguments: Iterable[Iterable[Any]]\n    ) -> List[_T]:\n        \"\"\"Safe wrapper around starmap to ensure pool is open\"\"\"\n        try:\n            return pool.starmap(task, arguments)\n        except ValueError as e:\n            log_warning(\n                f\"safe_starmap({task}, {arguments}) exception ValueError({str(e)})\"\n            )\n            return []\n        except Exception:  # pylint: disable=broad-except\n            log_warning(\n                f\"safe_starmap({task}, {arguments}) failed with \\n {traceback.format_exc()}\"\n            )\n            return []\n\n    @staticmethod\n    def safe_map(pool: Pool, task: Callable, arguments: Iterable[Any]) -> List[_T]:\n        \"\"\"Safe wrapper around starmap to ensure pool is open\"\"\"\n        try:\n            return pool.map(task, arguments)\n        except ValueError as e:\n            log_warning(f\"safe_map({task}, {arguments}) exception ValueError({str(e)})\")\n            return []\n        except Exception:  # pylint: disable=broad-except\n            log_warning(\n                f\"safe_starmap({task}, {arguments}) failed with \\n {traceback.format_exc()}\"\n            )\n            return []\n"
  },
  {
    "path": "src/electionguard/schnorr.py",
    "content": "from dataclasses import dataclass\n\nfrom .elgamal import ElGamalKeyPair, ElGamalPublicKey\nfrom .group import (\n    ElementModQ,\n    ElementModP,\n    g_pow_p,\n    mult_p,\n    pow_p,\n    a_plus_bc_q,\n)\nfrom .hash import hash_elems\nfrom .logs import log_warning\nfrom .proof import Proof, ProofUsage\n\n\n@dataclass\nclass SchnorrProof(Proof):\n    \"\"\"\n    Representation of a Schnorr proof\n    \"\"\"\n\n    public_key: ElGamalPublicKey\n    \"\"\"k in the spec\"\"\"\n    commitment: ElementModP\n    \"\"\"h in the spec\"\"\"\n    challenge: ElementModQ\n    \"\"\"c in the spec\"\"\"\n    response: ElementModQ\n    \"\"\"u in the spec\"\"\"\n    usage: ProofUsage = ProofUsage.SecretValue\n\n    def __post_init__(self) -> None:\n        super().__init__()\n\n    def is_valid(self) -> bool:\n        \"\"\"\n        Check validity of the `proof` for proving possession of the private key corresponding\n        to `public_key`.\n\n        :return: true if the transcript is valid, false if anything is wrong\n        \"\"\"\n        k = self.public_key\n        h = self.commitment\n        u = self.response\n        valid_public_key = k.is_valid_residue()\n        in_bounds_h = h.is_in_bounds()\n        in_bounds_u = u.is_in_bounds()\n\n        c = hash_elems(k, h)\n        valid_challenge = c == self.challenge\n\n        valid_proof = g_pow_p(u) == mult_p(h, pow_p(k, c))\n\n        success = (\n            valid_public_key\n            and in_bounds_h\n            and in_bounds_u\n            and valid_challenge\n            and valid_proof\n        )\n        if not success:\n            log_warning(\n                \"found an invalid Schnorr proof: %s\",\n                str(\n                    {\n                        \"in_bounds_h\": in_bounds_h,\n                        \"in_bounds_u\": in_bounds_u,\n                        \"valid_public_key\": valid_public_key,\n                        \"valid_challenge\": valid_challenge,\n                        \"valid_proof\": valid_proof,\n                        \"proof\": self,\n                    }\n                ),\n            )\n        return success\n\n\ndef make_schnorr_proof(keypair: ElGamalKeyPair, r: ElementModQ) -> SchnorrProof:\n    \"\"\"\n    Given an ElGamal keypair and a nonce, generates a proof that the prover knows the secret key without revealing it.\n\n    :param keypair: An ElGamal keypair.\n    :param r: A random element in [0,Q).\n    \"\"\"\n\n    k = keypair.public_key\n    h = g_pow_p(r)\n    c = hash_elems(k, h)\n    u = a_plus_bc_q(r, keypair.secret_key, c)\n\n    return SchnorrProof(k, h, c, u)\n"
  },
  {
    "path": "src/electionguard/serialize.py",
    "content": "from datetime import datetime\nfrom io import TextIOWrapper\nimport json\nimport os\nfrom pathlib import Path\nfrom typing import Any, List, Type, TypeVar, Union\n\n\nfrom dacite import Config, from_dict\nfrom dateutil import parser\nfrom pydantic_core import to_jsonable_python\nfrom pydantic.v1.tools import schema_json_of\n\n\nfrom .big_integer import BigInteger\nfrom .ballot_box import BallotBoxState\nfrom .byte_padding import add_padding, remove_padding, DataSize\nfrom .group import ElementModP, ElementModQ\nfrom .manifest import ElectionType, ReportingUnitType, VoteVariationType, SpecVersion\nfrom .proof import ProofUsage\nfrom .utils import BYTE_ENCODING, ContestErrorType\n\n\n_T = TypeVar(\"_T\")\n\n_file_extension = \"json\"\n\n_config = Config(\n    cast=[\n        BigInteger,\n        ContestErrorType,\n        ElementModP,\n        ElementModQ,\n        ElectionType,\n        BallotBoxState,\n        ElectionType,\n        ReportingUnitType,\n        SpecVersion,\n        VoteVariationType,\n        ProofUsage,\n    ],\n    type_hooks={datetime: parser.parse},\n)\n\n\ndef padded_encode(data: Any, size: DataSize = DataSize.Bytes_512) -> bytes:\n    return add_padding(to_raw(data).encode(BYTE_ENCODING), size)\n\n\ndef padded_decode(\n    type_: Type[_T], padded_data: bytes, size: DataSize = DataSize.Bytes_512\n) -> _T:\n    return from_raw(type_, remove_padding(padded_data, size))\n\n\ndef construct_path(\n    target_file_name: str,\n    target_path: str = \"\",\n    target_file_extension: str = _file_extension,\n) -> str:\n    \"\"\"Construct path from file name, path, and extension.\"\"\"\n\n    target_file = f\"{target_file_name}.{target_file_extension}\"\n    return os.path.join(target_path, target_file)\n\n\ndef from_raw(type_: Type[_T], raw: Union[str, bytes]) -> _T:\n    \"\"\"Deserialize raw json string as type.\"\"\"\n\n    return from_dict(type_, json.loads(raw), _config)\n\n\ndef from_list_raw(type_: Type[_T], raw: Union[str, bytes]) -> List[_T]:\n    \"\"\"Deserialize raw json string as type.\"\"\"\n\n    data = json.loads(raw)\n    ls: List[_T] = []\n    for item in data:\n        ls.append(from_dict(type_, item, _config))\n    return ls\n\n\ndef to_raw(data: Any) -> str:\n    \"\"\"Serialize data to raw json format.\"\"\"\n\n    return json.dumps(to_jsonable_python(data))\n\n\ndef from_file_wrapper(type_: Type[_T], file: TextIOWrapper) -> _T:\n    \"\"\"Deserialize json file as type.\"\"\"\n\n    data = json.load(file)\n    return from_dict(type_, data, _config)\n\n\ndef from_file(type_: Type[_T], path: Union[str, Path]) -> _T:\n    \"\"\"Deserialize json file as type.\"\"\"\n\n    with open(path, \"r\", encoding=BYTE_ENCODING) as json_file:\n        data = json.load(json_file)\n    return from_dict(type_, data, _config)\n\n\ndef from_list_in_file(type_: Type[_T], path: Union[str, Path]) -> List[_T]:\n    \"\"\"Deserialize json file that has an array of certain type.\"\"\"\n\n    with open(path, \"r\", encoding=BYTE_ENCODING) as json_file:\n        data = json.load(json_file)\n        ls: List[_T] = []\n        for item in data:\n            ls.append(from_dict(type_, item, _config))\n    return ls\n\n\ndef from_list_in_file_wrapper(type_: Type[_T], file: TextIOWrapper) -> List[_T]:\n    \"\"\"Deserialize json file that has an array of certain type.\"\"\"\n\n    data = json.load(file)\n    ls: List[_T] = []\n    for item in data:\n        ls.append(from_dict(type_, item, _config))\n    return ls\n\n\ndef to_file(\n    data: Any,\n    target_file_name: str,\n    target_path: str = \"\",\n) -> str:\n    \"\"\"Serialize object to JSON\"\"\"\n\n    if not os.path.exists(target_path):\n        os.makedirs(target_path)\n\n    path = construct_path(target_file_name, target_path)\n    with open(\n        path,\n        \"w\",\n        encoding=BYTE_ENCODING,\n    ) as outfile:\n        json.dump(to_jsonable_python(data), outfile)\n        return path\n\n\ndef get_schema(_type: Any) -> str:\n    \"\"\"Get JSON Schema for type\"\"\"\n\n    return schema_json_of(_type)\n"
  },
  {
    "path": "src/electionguard/singleton.py",
    "content": "from typing import Any\n\n\nclass Singleton:\n    \"\"\"A Singleton Class\"\"\"\n\n    __instance = None\n\n    @staticmethod\n    def get_instance() -> Any:\n        \"\"\"Get a static instance of the derived class.\"\"\"\n        if Singleton.__instance is None:\n            Singleton()\n        return Singleton.__instance\n\n    def __init__(self) -> None:\n        if Singleton.__instance is None:\n            Singleton.__instance = self  # pylint: disable=unused-private-member\n"
  },
  {
    "path": "src/electionguard/tally.py",
    "content": "# pylint: disable=unnecessary-comprehension\nfrom dataclasses import dataclass, field\nfrom typing import Iterable, Optional, List, Dict, Set, Tuple, Any\nfrom collections.abc import Container, Sized\n\nfrom .ballot import (\n    BallotBoxState,\n    CiphertextBallotSelection,\n    SubmittedBallot,\n    CiphertextSelection,\n)\nfrom .data_store import DataStore\nfrom .ballot_validator import ballot_is_valid_for_election\nfrom .decryption_share import CiphertextDecryptionSelection\nfrom .election import CiphertextElectionContext\nfrom .election_object_base import ElectionObjectBase, OrderedObjectBase\nfrom .elgamal import ElGamalCiphertext, elgamal_add\nfrom .group import ElementModQ, ONE_MOD_P, ElementModP\nfrom .logs import log_warning\nfrom .manifest import InternalManifest\nfrom .scheduler import Scheduler\nfrom .type import BallotId, ContestId, SelectionId\n\n\n@dataclass\nclass PlaintextTallySelection(ElectionObjectBase):\n    \"\"\"\n    A plaintext Tally Selection is a decrypted selection of a contest\n    \"\"\"\n\n    tally: int\n    # g^tally or M in the spec\n    value: ElementModP\n\n    message: ElGamalCiphertext\n\n    shares: List[CiphertextDecryptionSelection]\n\n\n@dataclass\nclass CiphertextTallySelection(ElectionObjectBase, CiphertextSelection):\n    \"\"\"\n    a CiphertextTallySelection is a homomorphic accumulation of all of the\n    CiphertextBallotSelection instances for a specific selection in an election.\n    \"\"\"\n\n    sequence_order: int\n    \"\"\"Order of the selection.\"\"\"\n\n    description_hash: ElementModQ\n    \"\"\"\n    The SelectionDescription hash\n    \"\"\"\n\n    ciphertext: ElGamalCiphertext = field(\n        default_factory=lambda: ElGamalCiphertext(ONE_MOD_P, ONE_MOD_P)\n    )\n    \"\"\"\n    The encrypted representation of the total of all ballots for this selection\n    \"\"\"\n\n    def elgamal_accumulate(\n        self, elgamal_ciphertext: ElGamalCiphertext\n    ) -> ElGamalCiphertext:\n        \"\"\"\n        Homomorphically add the specified value to the message\n        \"\"\"\n        new_value = elgamal_add(self.ciphertext, elgamal_ciphertext)\n        self.ciphertext = new_value\n        return self.ciphertext\n\n\n@dataclass\nclass PlaintextTallyContest(ElectionObjectBase):\n    \"\"\"\n    A plaintext Tally Contest is a collection of plaintext selections\n    \"\"\"\n\n    selections: Dict[SelectionId, PlaintextTallySelection]\n\n\n@dataclass\nclass CiphertextTallyContest(OrderedObjectBase):\n    \"\"\"\n    A CiphertextTallyContest is a container for associating a collection of CiphertextTallySelection\n    to a specific ContestDescription\n    \"\"\"\n\n    description_hash: ElementModQ\n    \"\"\"\n    The ContestDescription hash\n    \"\"\"\n\n    selections: Dict[SelectionId, CiphertextTallySelection]\n    \"\"\"\n    A collection of CiphertextTallySelection mapped by SelectionDescription.object_id\n    \"\"\"\n\n    def accumulate_contest(\n        self,\n        contest_selections: List[CiphertextBallotSelection],\n        scheduler: Optional[Scheduler] = None,\n    ) -> bool:\n        \"\"\"\n        Accumulate the contest selections of an individual ballot into this tally\n        \"\"\"\n\n        if len(contest_selections) == 0:\n            log_warning(\n                f\"accumulate cannot add missing selections for contest {self.object_id}\"\n            )\n            return False\n\n        # Validate the input data by comparing the selection id's provided\n        # to the valid selection id's for this tally contest\n        selection_ids = {\n            selection.object_id\n            for selection in contest_selections\n            if not selection.is_placeholder_selection\n        }\n\n        if any(set(self.selections).difference(selection_ids)):\n            log_warning(\n                f\"accumulate cannot add mismatched selections for contest {self.object_id}\"\n            )\n            return False\n\n        if scheduler is None:\n            scheduler = Scheduler()\n\n        # iterate through the tally selections and add the new value to the total\n        results: List[Tuple[SelectionId, Optional[ElGamalCiphertext]]] = (\n            scheduler.schedule(\n                self._accumulate_selections,\n                [\n                    (key, selection_tally, contest_selections)\n                    for (key, selection_tally) in self.selections.items()\n                ],\n            )\n        )\n\n        for key, ciphertext in results:\n            if ciphertext is None:\n                return False\n            self.selections[key].ciphertext = ciphertext\n\n        return True\n\n    @staticmethod\n    def _accumulate_selections(\n        key: SelectionId,\n        selection_tally: CiphertextTallySelection,\n        contest_selections: List[CiphertextBallotSelection],\n    ) -> Tuple[SelectionId, Optional[ElGamalCiphertext]]:\n        use_selection = None\n        for selection in contest_selections:\n            if key == selection.object_id:\n                use_selection = selection\n                break\n\n        # a selection on the ballot that is required was not found\n        # this should never happen when using the `CiphertextTally`\n        # but sanity check anyway\n        if not use_selection:\n            log_warning(f\"add cannot accumulate for missing selection {key}\")\n            return key, None\n\n        return key, selection_tally.elgamal_accumulate(use_selection.ciphertext)\n\n\n@dataclass\nclass PlaintextTally(ElectionObjectBase):\n    \"\"\"\n    The plaintext representation of all contests in the election\n    \"\"\"\n\n    contests: Dict[ContestId, PlaintextTallyContest]\n\n\n@dataclass\nclass PublishedCiphertextTally(ElectionObjectBase):\n    \"\"\"\n    A published version of the ciphertext tally\n    \"\"\"\n\n    contests: Dict[ContestId, CiphertextTallyContest]\n\n\n@dataclass\nclass CiphertextTally(ElectionObjectBase, Container, Sized):\n    \"\"\"\n    A `CiphertextTally` accepts cast and spoiled ballots and accumulates a tally on the cast ballots\n    \"\"\"\n\n    _internal_manifest: InternalManifest\n    _encryption: CiphertextElectionContext\n\n    cast_ballot_ids: Set[BallotId] = field(default_factory=lambda: set())\n    \"\"\"A local cache of ballots id's that have already been cast\"\"\"\n    spoiled_ballot_ids: Set[BallotId] = field(default_factory=lambda: set())\n\n    contests: Dict[ContestId, CiphertextTallyContest] = field(init=False)\n    \"\"\"\n    A collection of each contest and selection in an election.\n    Retains an encrypted representation of a tally for each selection\n    \"\"\"\n\n    def __post_init__(self) -> None:\n        object.__setattr__(\n            self, \"contests\", self._build_tally_collection(self._internal_manifest)\n        )\n\n    def __len__(self) -> int:\n        return len(self.cast_ballot_ids) + len(self.spoiled_ballot_ids)\n\n    def __contains__(self, item: object) -> bool:\n        if not isinstance(item, SubmittedBallot):\n            return False\n\n        if (\n            item.object_id in self.cast_ballot_ids\n            or item.object_id in self.spoiled_ballot_ids\n        ):\n            return True\n\n        return False\n\n    def append(\n        self,\n        ballot: SubmittedBallot,\n        should_validate: bool,\n        scheduler: Optional[Scheduler] = None,\n    ) -> bool:\n        \"\"\"\n        Append a ballot to the tally and recalculate the tally.\n        \"\"\"\n        if ballot.state == BallotBoxState.UNKNOWN:\n            log_warning(f\"append cannot add {ballot.object_id} with invalid state\")\n            return False\n\n        if ballot in self:\n            log_warning(f\"append cannot add {ballot.object_id} that is already tallied\")\n            return False\n\n        if not ballot_is_valid_for_election(\n            ballot, self._internal_manifest, self._encryption, should_validate\n        ):\n            return False\n\n        if ballot.state == BallotBoxState.CAST:\n            return self._add_cast(ballot, scheduler)\n\n        if ballot.state == BallotBoxState.SPOILED:\n            return self._add_spoiled(ballot)\n\n        log_warning(f\"append cannot add {ballot.object_id}\")\n        return False\n\n    def batch_append(\n        self,\n        ballots: Iterable[Tuple[Any, SubmittedBallot]],\n        should_validate: bool,\n        scheduler: Optional[Scheduler] = None,\n    ) -> bool:\n        \"\"\"\n        Append a collection of Ballots to the tally and recalculate\n        \"\"\"\n        cast_ballot_selections: Dict[SelectionId, Dict[BallotId, ElGamalCiphertext]] = (\n            {}\n        )\n        for ballot in ballots:\n            # get the value of the dict\n            ballot_value = ballot[1]\n            if ballot_value not in self and ballot_is_valid_for_election(\n                ballot_value, self._internal_manifest, self._encryption, should_validate\n            ):\n                if ballot_value.state == BallotBoxState.CAST:\n\n                    # collect the selections so they can can be accumulated in parallel\n                    for contest in ballot_value.contests:\n                        for selection in contest.ballot_selections:\n                            if selection.object_id not in cast_ballot_selections:\n                                cast_ballot_selections[selection.object_id] = {}\n\n                            cast_ballot_selections[selection.object_id][\n                                ballot_value.object_id\n                            ] = selection.ciphertext\n\n                # just append the spoiled ballots\n                elif ballot_value.state == BallotBoxState.SPOILED:\n                    self._add_spoiled(ballot_value)\n\n        # cache the cast ballot id's so they are not double counted\n        if self._execute_accumulate(cast_ballot_selections, scheduler):\n            for ballot in ballots:\n                # get the value of the dict\n                ballot_value = ballot[1]\n                if ballot_value.state == BallotBoxState.CAST:\n                    self.cast_ballot_ids.add(ballot_value.object_id)\n            return True\n\n        return False\n\n    def cast(self) -> int:\n        \"\"\"\n        Get a count of the cast ballots\n        \"\"\"\n        return len(self.cast_ballot_ids)\n\n    def spoiled(self) -> int:\n        \"\"\"\n        Get a count of the spoiled ballots\n        \"\"\"\n        return len(self.spoiled_ballot_ids)\n\n    def publish(self) -> PublishedCiphertextTally:\n        return PublishedCiphertextTally(self.object_id, self.contests)\n\n    @staticmethod\n    def _accumulate(\n        id: str, ballot_selections: Dict[BallotId, ElGamalCiphertext]\n    ) -> Tuple[str, ElGamalCiphertext]:\n        return (\n            id,\n            elgamal_add(*[ciphertext for ciphertext in ballot_selections.values()]),\n        )\n\n    def _add_cast(\n        self, ballot: SubmittedBallot, scheduler: Optional[Scheduler] = None\n    ) -> bool:\n        \"\"\"\n        Add a cast ballot to the tally, synchronously\n        \"\"\"\n\n        # iterate through the contests and elgamal add\n        for contest in ballot.contests:\n            # This should never happen since the ballot is validated against the election metadata\n            # but it's possible the local dictionary was modified so we double check.\n            if not contest.object_id in self.contests:\n                log_warning(\n                    f\"add cast missing contest in valid set {contest.object_id}\"\n                )\n                return False\n\n            use_contest = self.contests[contest.object_id]\n            if not use_contest.accumulate_contest(contest.ballot_selections, scheduler):\n                return False\n\n            self.contests[contest.object_id] = use_contest\n        self.cast_ballot_ids.add(ballot.object_id)\n        return True\n\n    def _add_spoiled(self, ballot: SubmittedBallot) -> bool:\n        \"\"\"\n        Add a spoiled ballot\n        \"\"\"\n\n        self.spoiled_ballot_ids.add(ballot.object_id)\n        return True\n\n    @staticmethod\n    def _build_tally_collection(\n        internal_manifest: InternalManifest,\n    ) -> Dict[ContestId, CiphertextTallyContest]:\n        \"\"\"\n        Build the object graph for the tally from the InternalManifest\n        \"\"\"\n\n        cast_collection: Dict[str, CiphertextTallyContest] = {}\n        for contest in internal_manifest.contests:\n            # build a collection of valid selections for the contest description\n            # note: we explicitly ignore the Placeholder Selections.\n            contest_selections: Dict[str, CiphertextTallySelection] = {}\n            for selection in contest.ballot_selections:\n                contest_selections[selection.object_id] = CiphertextTallySelection(\n                    selection.object_id,\n                    selection.sequence_order,\n                    selection.crypto_hash(),\n                )\n\n            cast_collection[contest.object_id] = CiphertextTallyContest(\n                contest.object_id,\n                contest.sequence_order,\n                contest.crypto_hash(),\n                contest_selections,\n            )\n\n        return cast_collection\n\n    def _execute_accumulate(\n        self,\n        ciphertext_selections_by_selection_id: Dict[\n            str, Dict[BallotId, ElGamalCiphertext]\n        ],\n        scheduler: Optional[Scheduler] = None,\n    ) -> bool:\n\n        result_set: List[Tuple[SelectionId, ElGamalCiphertext]]\n        if not scheduler:\n            scheduler = Scheduler()\n        result_set = scheduler.schedule(\n            self._accumulate,\n            [\n                (selection_id, selections)\n                for (\n                    selection_id,\n                    selections,\n                ) in ciphertext_selections_by_selection_id.items()\n            ],\n        )\n\n        result_dict = {\n            selection_id: ciphertext for (selection_id, ciphertext) in result_set\n        }\n\n        for contest in self.contests.values():\n            for selection_id, selection in contest.selections.items():\n                if selection_id in result_dict:\n                    selection.elgamal_accumulate(result_dict[selection_id])\n\n        return True\n\n\ndef tally_ballot(\n    ballot: SubmittedBallot, tally: CiphertextTally\n) -> Optional[CiphertextTally]:\n    \"\"\"\n    Tally a ballot that is either Cast or Spoiled\n    :return: The mutated CiphertextTally or None if there is an error\n    \"\"\"\n\n    if ballot.state == BallotBoxState.UNKNOWN:\n        log_warning(\n            f\"tally ballots error tallying unknown state for ballot {ballot.object_id}\"\n        )\n        return None\n\n    if tally.append(ballot, True):\n        return tally\n\n    return None\n\n\ndef tally_ballots(\n    store: DataStore,\n    internal_manifest: InternalManifest,\n    context: CiphertextElectionContext,\n) -> Optional[CiphertextTally]:\n    \"\"\"\n    Tally all of the ballots in the ballot store.\n    :return: a CiphertextTally or None if there is an error\n    \"\"\"\n    # TODO: ISSUE #14: unique Id for the tally\n    tally: CiphertextTally = CiphertextTally(\n        \"election-results\", internal_manifest, context\n    )\n    if tally.batch_append(store, True):\n        return tally\n    return None\n"
  },
  {
    "path": "src/electionguard/type.py",
    "content": "BallotId = str\nContestId = str\nGuardianId = str\nMediatorId = str\nVerifierId = str\nSelectionId = str\n"
  },
  {
    "path": "src/electionguard/utils.py",
    "content": "from datetime import datetime, timezone\nfrom enum import Enum\nfrom re import sub\nfrom typing import Callable, List, Optional, TypeVar, Literal\nfrom base64 import b16decode\n\nfrom .type import ContestId, SelectionId\n\n_T = TypeVar(\"_T\")\n_U = TypeVar(\"_U\")\n\nBYTE_ORDER: Literal[\"little\", \"big\"] = \"big\"\nBYTE_ENCODING = \"utf-8\"\n\n\nclass ContestErrorType(Enum):\n    \"\"\"Various errors that can occur on ballots contest after voting.\"\"\"\n\n    Default = \"default\"\n    NullVote = \"nullvote\"\n    UnderVote = \"undervote\"\n    OverVote = \"overvote\"\n\n\nclass ContestException(Exception):\n    \"\"\"Generic contest error\"\"\"\n\n    type: ContestErrorType\n\n    def __init__(\n        self,\n        contest_id: ContestId,\n        type: ContestErrorType = ContestErrorType.Default,\n        override_message: Optional[str] = None,\n    ):\n        self.type = type\n        super().__init__(\n            override_message or f\"{type} error has occurred on contest {contest_id}.\"\n        )\n\n\nclass OverVoteException(ContestException):\n    \"\"\"Over vote on contest error.\"\"\"\n\n    overvoted_ids: List[SelectionId]\n\n    def __init__(self, contest_id: ContestId, overvoted_ids: List[SelectionId]):\n        self.overvoted_ids = overvoted_ids\n        super().__init__(contest_id, ContestErrorType.OverVote)\n\n\nclass UnderVoteException(ContestException):\n    \"\"\"Under vote on contest error.\"\"\"\n\n    def __init__(self, contest_id: ContestId):\n        super().__init__(contest_id, ContestErrorType.UnderVote)\n\n\nclass NullVoteException(ContestException):\n    \"\"\"Null vote on contest error.\"\"\"\n\n    def __init__(self, contest_id: ContestId):\n        super().__init__(contest_id, ContestErrorType.NullVote)\n\n\ndef get_optional(optional: Optional[_T]) -> _T:\n    \"\"\"\n    General-purpose unwrapping function to handle `Optional`.\n    Raises an exception if it's actually `None`, otherwise\n    returns the internal type.\n    \"\"\"\n    assert optional is not None, \"Unwrap called on None\"\n    return optional\n\n\ndef match_optional(\n    optional: Optional[_T], none_func: Callable[[], _U], some_func: Callable[[_T], _U]\n) -> _U:\n    \"\"\"\n    General-purpose pattern-matching function to handle `Optional`.\n    If it's actually `None`, the `none_func` lambda is called.\n    Otherwise, the `some_func` lambda is called with the value.\n    \"\"\"\n    if optional is None:\n        return none_func()\n    return some_func(optional)\n\n\ndef get_or_else_optional(optional: Optional[_T], alt_value: _T) -> _T:\n    \"\"\"\n    General-purpose getter for `Optional`. If it's `None`, returns the `alt_value`.\n    Otherwise, returns the contents of `optional`.\n    \"\"\"\n    if optional is None:\n        return alt_value\n    return optional\n\n\ndef get_or_else_optional_func(optional: Optional[_T], func: Callable[[], _T]) -> _T:\n    \"\"\"\n    General-purpose getter for `Optional`. If it's `None`, calls the lambda `func`\n    and returns its value. Otherwise, returns the contents of `optional`.\n    \"\"\"\n    if optional is None:\n        return func()\n    return optional\n\n\ndef flatmap_optional(\n    optional: Optional[_T], mapper: Callable[[_T], _U]\n) -> Optional[_U]:\n    \"\"\"\n    General-purpose flatmapping on `Optional`. If it's `None`, returns `None` as well,\n    otherwise returns the lambda applied to the contents.\n    \"\"\"\n    if optional is None:\n        return None\n    return mapper(optional)\n\n\ndef to_hex_bytes(data: bytes) -> bytes:\n    \"\"\"\n    Convert from the element to the representation of bytes by first going through hex.\n    \"\"\"\n\n    return b16decode(data)\n\n\ndef to_ticks(date_time: datetime) -> int:\n    \"\"\"\n    Return the number of ticks for a date time.\n    Ticks are defined here as number of seconds since the unix epoch (00:00:00 UTC on 1 January 1970)\n    :param date_time: Date time to convert\n    :return: number of ticks\n    \"\"\"\n\n    ticks = (\n        date_time.timestamp()\n        if date_time.tzinfo\n        else date_time.astimezone(timezone.utc).timestamp()\n    )\n    return int(ticks)\n\n\ndef to_iso_date_string(date_time: datetime) -> str:\n    \"\"\"\n    Return the number of ticks for a date time.\n    Ticks are defined here as number of seconds since the unix epoch (00:00:00 UTC on 1 January 1970)\n    :param date_time: Date time to convert\n    :return: number of ticks\n    \"\"\"\n    utc_datetime = (\n        date_time.astimezone(timezone.utc).replace(microsecond=0)\n        if date_time.tzinfo\n        else date_time.replace(microsecond=0)\n    )\n    return utc_datetime.strftime(\"%Y-%m-%dT%H:%M:%SZ\")\n\n\ndef space_between_capitals(base: str) -> str:\n    \"\"\"\n    Return a modified string with spaces between capital letters\n    :param base: base string\n    :return: modified string\n    \"\"\"\n    return sub(r\"(\\w)([A-Z])\", r\"\\1 \\2\", base)\n"
  },
  {
    "path": "src/electionguard_cli/__init__.py",
    "content": "from electionguard_cli import cli_models\nfrom electionguard_cli import cli_steps\nfrom electionguard_cli import e2e\nfrom electionguard_cli import encrypt_ballots\nfrom electionguard_cli import import_ballots\nfrom electionguard_cli import mark_ballots\nfrom electionguard_cli import setup_election\nfrom electionguard_cli import start\nfrom electionguard_cli import submit_ballots\n\nfrom electionguard_cli.cli_models import (\n    BuildElectionResults,\n    CliDecryptResults,\n    CliElectionInputsBase,\n    EncryptResults,\n    MarkResults,\n    SubmitResults,\n    cli_decrypt_results,\n    cli_election_inputs_base,\n    e2e_build_election_results,\n    encrypt_results,\n    mark_results,\n    submit_results,\n)\nfrom electionguard_cli.cli_steps import (\n    CliStepBase,\n    DecryptStep,\n    ElectionBuilderStep,\n    EncryptVotesStep,\n    InputRetrievalStepBase,\n    KeyCeremonyStep,\n    MarkBallotsStep,\n    OutputStepBase,\n    PrintResultsStep,\n    SubmitBallotsStep,\n    TallyStep,\n    cli_step_base,\n    decrypt_step,\n    election_builder_step,\n    encrypt_votes_step,\n    input_retrieval_step_base,\n    key_ceremony_step,\n    mark_ballots_step,\n    output_step_base,\n    print_results_step,\n    submit_ballots_step,\n    tally_step,\n)\nfrom electionguard_cli.e2e import (\n    E2eCommand,\n    E2eElectionBuilderStep,\n    E2eInputRetrievalStep,\n    E2eInputs,\n    E2ePublishStep,\n    SubmitVotesStep,\n    e2e_command,\n    e2e_election_builder_step,\n    e2e_input_retrieval_step,\n    e2e_inputs,\n    e2e_publish_step,\n    submit_votes_step,\n)\nfrom electionguard_cli.encrypt_ballots import (\n    EncryptBallotInputs,\n    EncryptBallotsCommand,\n    EncryptBallotsElectionBuilderStep,\n    EncryptBallotsInputRetrievalStep,\n    EncryptBallotsPublishStep,\n    encrypt_ballot_inputs,\n    encrypt_ballots_election_builder_step,\n    encrypt_ballots_input_retrieval_step,\n    encrypt_ballots_publish_step,\n    encrypt_command,\n)\nfrom electionguard_cli.import_ballots import (\n    ImportBallotInputs,\n    ImportBallotsCommand,\n    ImportBallotsElectionBuilderStep,\n    ImportBallotsInputRetrievalStep,\n    ImportBallotsPublishStep,\n    import_ballot_inputs,\n    import_ballots_command,\n    import_ballots_election_builder_step,\n    import_ballots_input_retrieval_step,\n    import_ballots_publish_step,\n)\nfrom electionguard_cli.mark_ballots import (\n    MarkBallotInputs,\n    MarkBallotsCommand,\n    MarkBallotsElectionBuilderStep,\n    MarkBallotsInputRetrievalStep,\n    MarkBallotsPublishStep,\n    mark_ballot_inputs,\n    mark_ballots_election_builder_step,\n    mark_ballots_input_retrieval_step,\n    mark_ballots_publish_step,\n    mark_command,\n)\nfrom electionguard_cli.setup_election import (\n    OutputSetupFilesStep,\n    SetupElectionBuilderStep,\n    SetupElectionCommand,\n    SetupInputRetrievalStep,\n    SetupInputs,\n    output_setup_files_step,\n    setup_election_builder_step,\n    setup_election_command,\n    setup_input_retrieval_step,\n    setup_inputs,\n)\nfrom electionguard_cli.start import (\n    cli,\n)\nfrom electionguard_cli.submit_ballots import (\n    SubmitBallotInputs,\n    SubmitBallotsCommand,\n    SubmitBallotsElectionBuilderStep,\n    SubmitBallotsInputRetrievalStep,\n    SubmitBallotsPublishStep,\n    submit_ballot_inputs,\n    submit_ballots_election_builder_step,\n    submit_ballots_input_retrieval_step,\n    submit_ballots_publish_step,\n    submit_command,\n)\n\n__all__ = [\n    \"BuildElectionResults\",\n    \"CliDecryptResults\",\n    \"CliElectionInputsBase\",\n    \"CliStepBase\",\n    \"DecryptStep\",\n    \"E2eCommand\",\n    \"E2eElectionBuilderStep\",\n    \"E2eInputRetrievalStep\",\n    \"E2eInputs\",\n    \"E2ePublishStep\",\n    \"ElectionBuilderStep\",\n    \"EncryptBallotInputs\",\n    \"EncryptBallotsCommand\",\n    \"EncryptBallotsElectionBuilderStep\",\n    \"EncryptBallotsInputRetrievalStep\",\n    \"EncryptBallotsPublishStep\",\n    \"EncryptResults\",\n    \"EncryptVotesStep\",\n    \"ImportBallotInputs\",\n    \"ImportBallotsCommand\",\n    \"ImportBallotsElectionBuilderStep\",\n    \"ImportBallotsInputRetrievalStep\",\n    \"ImportBallotsPublishStep\",\n    \"InputRetrievalStepBase\",\n    \"KeyCeremonyStep\",\n    \"MarkBallotInputs\",\n    \"MarkBallotsCommand\",\n    \"MarkBallotsElectionBuilderStep\",\n    \"MarkBallotsInputRetrievalStep\",\n    \"MarkBallotsPublishStep\",\n    \"MarkBallotsStep\",\n    \"MarkResults\",\n    \"OutputSetupFilesStep\",\n    \"OutputStepBase\",\n    \"PrintResultsStep\",\n    \"SetupElectionBuilderStep\",\n    \"SetupElectionCommand\",\n    \"SetupInputRetrievalStep\",\n    \"SetupInputs\",\n    \"SubmitBallotInputs\",\n    \"SubmitBallotsCommand\",\n    \"SubmitBallotsElectionBuilderStep\",\n    \"SubmitBallotsInputRetrievalStep\",\n    \"SubmitBallotsPublishStep\",\n    \"SubmitBallotsStep\",\n    \"SubmitResults\",\n    \"SubmitVotesStep\",\n    \"TallyStep\",\n    \"cli\",\n    \"cli_decrypt_results\",\n    \"cli_election_inputs_base\",\n    \"cli_models\",\n    \"cli_step_base\",\n    \"cli_steps\",\n    \"decrypt_step\",\n    \"e2e\",\n    \"e2e_build_election_results\",\n    \"e2e_command\",\n    \"e2e_election_builder_step\",\n    \"e2e_input_retrieval_step\",\n    \"e2e_inputs\",\n    \"e2e_publish_step\",\n    \"election_builder_step\",\n    \"encrypt_ballot_inputs\",\n    \"encrypt_ballots\",\n    \"encrypt_ballots_election_builder_step\",\n    \"encrypt_ballots_input_retrieval_step\",\n    \"encrypt_ballots_publish_step\",\n    \"encrypt_command\",\n    \"encrypt_results\",\n    \"encrypt_votes_step\",\n    \"import_ballot_inputs\",\n    \"import_ballots\",\n    \"import_ballots_command\",\n    \"import_ballots_election_builder_step\",\n    \"import_ballots_input_retrieval_step\",\n    \"import_ballots_publish_step\",\n    \"input_retrieval_step_base\",\n    \"key_ceremony_step\",\n    \"mark_ballot_inputs\",\n    \"mark_ballots\",\n    \"mark_ballots_election_builder_step\",\n    \"mark_ballots_input_retrieval_step\",\n    \"mark_ballots_publish_step\",\n    \"mark_ballots_step\",\n    \"mark_command\",\n    \"mark_results\",\n    \"output_setup_files_step\",\n    \"output_step_base\",\n    \"print_results_step\",\n    \"setup_election\",\n    \"setup_election_builder_step\",\n    \"setup_election_command\",\n    \"setup_input_retrieval_step\",\n    \"setup_inputs\",\n    \"start\",\n    \"submit_ballot_inputs\",\n    \"submit_ballots\",\n    \"submit_ballots_election_builder_step\",\n    \"submit_ballots_input_retrieval_step\",\n    \"submit_ballots_publish_step\",\n    \"submit_ballots_step\",\n    \"submit_command\",\n    \"submit_results\",\n    \"submit_votes_step\",\n    \"tally_step\",\n]\n"
  },
  {
    "path": "src/electionguard_cli/cli_models/__init__.py",
    "content": "from electionguard_cli.cli_models import cli_decrypt_results\nfrom electionguard_cli.cli_models import cli_election_inputs_base\nfrom electionguard_cli.cli_models import e2e_build_election_results\nfrom electionguard_cli.cli_models import encrypt_results\nfrom electionguard_cli.cli_models import mark_results\nfrom electionguard_cli.cli_models import submit_results\n\nfrom electionguard_cli.cli_models.cli_decrypt_results import (\n    CliDecryptResults,\n)\nfrom electionguard_cli.cli_models.cli_election_inputs_base import (\n    CliElectionInputsBase,\n)\nfrom electionguard_cli.cli_models.e2e_build_election_results import (\n    BuildElectionResults,\n)\nfrom electionguard_cli.cli_models.encrypt_results import (\n    EncryptResults,\n)\nfrom electionguard_cli.cli_models.mark_results import (\n    MarkResults,\n)\nfrom electionguard_cli.cli_models.submit_results import (\n    SubmitResults,\n)\n\n__all__ = [\n    \"BuildElectionResults\",\n    \"CliDecryptResults\",\n    \"CliElectionInputsBase\",\n    \"EncryptResults\",\n    \"MarkResults\",\n    \"SubmitResults\",\n    \"cli_decrypt_results\",\n    \"cli_election_inputs_base\",\n    \"e2e_build_election_results\",\n    \"encrypt_results\",\n    \"mark_results\",\n    \"submit_results\",\n]\n"
  },
  {
    "path": "src/electionguard_cli/cli_models/cli_decrypt_results.py",
    "content": "from dataclasses import dataclass\nfrom typing import Dict\nfrom electionguard.tally import CiphertextTally, PlaintextTally\nfrom electionguard.type import BallotId\nfrom electionguard.election_polynomial import LagrangeCoefficientsRecord\n\n\n@dataclass\nclass CliDecryptResults:\n    \"\"\"Responsible for holding the results of decrypting an election.\"\"\"\n\n    plaintext_tally: PlaintextTally\n    plaintext_spoiled_ballots: Dict[BallotId, PlaintextTally]\n    ciphertext_tally: CiphertextTally\n    lagrange_coefficients: LagrangeCoefficientsRecord\n"
  },
  {
    "path": "src/electionguard_cli/cli_models/cli_election_inputs_base.py",
    "content": "from abc import ABC\nfrom typing import List\nfrom electionguard.guardian import Guardian\nfrom electionguard.manifest import Manifest\n\n\nclass CliElectionInputsBase(ABC):\n    \"\"\"Responsible for holding inputs common to all CLI election commands\"\"\"\n\n    guardian_count: int\n    quorum: int\n    manifest: Manifest\n    guardians: List[Guardian]\n"
  },
  {
    "path": "src/electionguard_cli/cli_models/e2e_build_election_results.py",
    "content": "from dataclasses import dataclass\n\nfrom electionguard.election import CiphertextElectionContext\nfrom electionguard.manifest import InternalManifest\n\n\n@dataclass\nclass BuildElectionResults:\n    \"\"\"The results of building an election, more specifically an internal manifest and context.\"\"\"\n\n    internal_manifest: InternalManifest\n    context: CiphertextElectionContext\n"
  },
  {
    "path": "src/electionguard_cli/cli_models/encrypt_results.py",
    "content": "from dataclasses import dataclass\nfrom typing import List\nfrom electionguard.ballot import CiphertextBallot\nfrom electionguard.encrypt import EncryptionDevice\n\n\n@dataclass\nclass EncryptResults:\n    \"\"\"Responsible for holding the results of encrypting votes in an election.\"\"\"\n\n    device: EncryptionDevice\n    ciphertext_ballots: List[CiphertextBallot]\n"
  },
  {
    "path": "src/electionguard_cli/cli_models/mark_results.py",
    "content": "from typing import List\nfrom electionguard.ballot import PlaintextBallot\n\n\nclass MarkResults:\n    \"\"\"Responsible for holding the results of marking ballots in an election.\"\"\"\n\n    def __init__(\n        self,\n        plaintext_ballots: List[PlaintextBallot],\n    ):\n        self.plaintext_ballots = plaintext_ballots\n\n    plaintext_ballots: List[PlaintextBallot]\n"
  },
  {
    "path": "src/electionguard_cli/cli_models/submit_results.py",
    "content": "from typing import List\nfrom electionguard.ballot import SubmittedBallot\n\n\nclass SubmitResults:\n    \"\"\"Responsible for holding the results of submitting ballots in an election.\"\"\"\n\n    def __init__(\n        self,\n        submitted_ballots: List[SubmittedBallot],\n    ):\n        self.submitted_ballots = submitted_ballots\n\n    submitted_ballots: List[SubmittedBallot]\n"
  },
  {
    "path": "src/electionguard_cli/cli_steps/__init__.py",
    "content": "from electionguard_cli.cli_steps import cli_step_base\nfrom electionguard_cli.cli_steps import decrypt_step\nfrom electionguard_cli.cli_steps import election_builder_step\nfrom electionguard_cli.cli_steps import encrypt_votes_step\nfrom electionguard_cli.cli_steps import input_retrieval_step_base\nfrom electionguard_cli.cli_steps import key_ceremony_step\nfrom electionguard_cli.cli_steps import mark_ballots_step\nfrom electionguard_cli.cli_steps import output_step_base\nfrom electionguard_cli.cli_steps import print_results_step\nfrom electionguard_cli.cli_steps import submit_ballots_step\nfrom electionguard_cli.cli_steps import tally_step\n\nfrom electionguard_cli.cli_steps.cli_step_base import (\n    CliStepBase,\n)\nfrom electionguard_cli.cli_steps.decrypt_step import (\n    DecryptStep,\n)\nfrom electionguard_cli.cli_steps.election_builder_step import (\n    ElectionBuilderStep,\n)\nfrom electionguard_cli.cli_steps.encrypt_votes_step import (\n    EncryptVotesStep,\n)\nfrom electionguard_cli.cli_steps.input_retrieval_step_base import (\n    InputRetrievalStepBase,\n)\nfrom electionguard_cli.cli_steps.key_ceremony_step import (\n    KeyCeremonyStep,\n)\nfrom electionguard_cli.cli_steps.mark_ballots_step import (\n    MarkBallotsStep,\n)\nfrom electionguard_cli.cli_steps.output_step_base import (\n    OutputStepBase,\n)\nfrom electionguard_cli.cli_steps.print_results_step import (\n    PrintResultsStep,\n)\nfrom electionguard_cli.cli_steps.submit_ballots_step import (\n    SubmitBallotsStep,\n)\nfrom electionguard_cli.cli_steps.tally_step import (\n    TallyStep,\n)\n\n__all__ = [\n    \"CliStepBase\",\n    \"DecryptStep\",\n    \"ElectionBuilderStep\",\n    \"EncryptVotesStep\",\n    \"InputRetrievalStepBase\",\n    \"KeyCeremonyStep\",\n    \"MarkBallotsStep\",\n    \"OutputStepBase\",\n    \"PrintResultsStep\",\n    \"SubmitBallotsStep\",\n    \"TallyStep\",\n    \"cli_step_base\",\n    \"decrypt_step\",\n    \"election_builder_step\",\n    \"encrypt_votes_step\",\n    \"input_retrieval_step_base\",\n    \"key_ceremony_step\",\n    \"mark_ballots_step\",\n    \"output_step_base\",\n    \"print_results_step\",\n    \"submit_ballots_step\",\n    \"tally_step\",\n]\n"
  },
  {
    "path": "src/electionguard_cli/cli_steps/cli_step_base.py",
    "content": "from typing import Any, Optional\nimport click\n\n\nclass CliStepBase:\n    \"\"\"\n    Responsible for providing common functionality to the individual steps within an end-to-end election command\n    from the CLI.\n    \"\"\"\n\n    header_color = \"green\"\n    value_color = \"yellow\"\n    warning_color = \"bright_red\"\n    section_color = \"bright_white\"\n    VERIFICATION_URL_NAME = \"verification_url\"\n\n    def print_header(self, s: str) -> None:\n        click.echo(\"\")\n        click.secho(f\"{'-'*40}\", fg=self.header_color)\n        click.secho(s, fg=self.header_color)\n        click.secho(f\"{'-'*40}\", fg=self.header_color)\n\n    def print_section(self, s: Optional[str]) -> None:\n        click.echo(\"\")\n        click.secho(s, fg=self.section_color, bold=True)\n\n    def print_value(self, name: str, value: Any) -> None:\n        click.echo(click.style(name + \": \") + click.style(value, fg=self.value_color))\n\n    def print_warning(self, s: str) -> None:\n        click.secho(f\"WARNING: {s}\", fg=self.warning_color)\n"
  },
  {
    "path": "src/electionguard_cli/cli_steps/decrypt_step.py",
    "content": "from typing import List\nimport click\nfrom electionguard.guardian import Guardian\nfrom electionguard.utils import get_optional\nfrom electionguard.ballot import SubmittedBallot\nfrom electionguard.manifest import Manifest\nfrom electionguard.tally import CiphertextTally\nfrom electionguard.decryption_mediator import DecryptionMediator\nfrom electionguard.election_polynomial import LagrangeCoefficientsRecord\n\nfrom ..cli_models import BuildElectionResults, CliDecryptResults\nfrom .cli_step_base import CliStepBase\n\n\nclass DecryptStep(CliStepBase):\n    \"\"\"Responsible for decrypting a tally and/or cast ballots\"\"\"\n\n    def _get_lagrange_coefficients(\n        self, decryption_mediator: DecryptionMediator\n    ) -> LagrangeCoefficientsRecord:\n        lagrange_coefficients = LagrangeCoefficientsRecord(\n            decryption_mediator.get_lagrange_coefficients()\n        )\n        coefficient_count = len(lagrange_coefficients.coefficients)\n        self.print_value(\"Lagrange coefficients retrieved\", coefficient_count)\n        return lagrange_coefficients\n\n    def decrypt(\n        self,\n        ciphertext_tally: CiphertextTally,\n        spoiled_ballots: List[SubmittedBallot],\n        guardians: List[Guardian],\n        build_election_results: BuildElectionResults,\n        manifest: Manifest,\n    ) -> CliDecryptResults:\n        self.print_header(\"Decrypting tally\")\n\n        decryption_mediator = DecryptionMediator(\n            \"decryption-mediator\",\n            build_election_results.context,\n        )\n        context = build_election_results.context\n\n        self.print_value(\"Cast ballots\", ciphertext_tally.cast())\n        self.print_value(\"Spoiled ballots\", ciphertext_tally.spoiled())\n        self.print_value(\"Total ballots\", len(ciphertext_tally))\n\n        count = 0\n        for guardian in guardians:\n            guardian_key = guardian.share_key()\n            tally_share = get_optional(\n                guardian.compute_tally_share(ciphertext_tally, context)\n            )\n            ballot_shares = guardian.compute_ballot_shares(spoiled_ballots, context)\n            decryption_mediator.announce(guardian_key, tally_share, ballot_shares)\n            count += 1\n            click.echo(f\"Guardian Present: {guardian.id}\")\n\n        lagrange_coefficients = self._get_lagrange_coefficients(decryption_mediator)\n\n        plaintext_tally = get_optional(\n            decryption_mediator.get_plaintext_tally(ciphertext_tally, manifest)\n        )\n\n        plaintext_spoiled_ballots = get_optional(\n            decryption_mediator.get_plaintext_ballots(spoiled_ballots, manifest)\n        )\n\n        return CliDecryptResults(\n            plaintext_tally,\n            plaintext_spoiled_ballots,\n            ciphertext_tally,\n            lagrange_coefficients,\n        )\n"
  },
  {
    "path": "src/electionguard_cli/cli_steps/election_builder_step.py",
    "content": "from typing import Optional\nimport click\nfrom electionguard.elgamal import ElGamalPublicKey\nfrom electionguard.group import ElementModQ\nfrom electionguard.utils import get_optional\nfrom electionguard_tools.helpers.election_builder import ElectionBuilder\n\nfrom ..cli_models import CliElectionInputsBase, BuildElectionResults\nfrom .cli_step_base import CliStepBase\n\n\nclass ElectionBuilderStep(CliStepBase):\n    \"\"\"Responsible for creating a manifest and context for use in an election.\"\"\"\n\n    def _build_election(\n        self,\n        election_inputs: CliElectionInputsBase,\n        joint_public_key: ElGamalPublicKey,\n        committment_hash: ElementModQ,\n        verification_url: Optional[str],\n    ) -> BuildElectionResults:\n        self.print_header(\"Building election\")\n\n        click.echo(\"Initializing public key and commitment hash\")\n        election_builder = ElectionBuilder(\n            election_inputs.guardian_count,\n            election_inputs.quorum,\n            election_inputs.manifest,\n        )\n        election_builder.set_public_key(joint_public_key)\n        election_builder.set_commitment_hash(committment_hash)\n        if verification_url is not None:\n            election_builder.add_extended_data_field(\n                self.VERIFICATION_URL_NAME, verification_url\n            )\n        click.echo(\"Creating context and internal manifest\")\n        build_result = election_builder.build()\n        internal_manifest, context = get_optional(build_result)\n        return BuildElectionResults(internal_manifest, context)\n"
  },
  {
    "path": "src/electionguard_cli/cli_steps/encrypt_votes_step.py",
    "content": "from typing import List, Tuple\nimport click\n\nfrom electionguard.encrypt import EncryptionDevice, EncryptionMediator\nfrom electionguard.election import CiphertextElectionContext\nfrom electionguard.manifest import InternalManifest\nfrom electionguard.utils import get_optional\nfrom electionguard.ballot import (\n    CiphertextBallot,\n    PlaintextBallot,\n)\nfrom electionguard_tools.factories import (\n    ElectionFactory,\n)\n\nfrom .cli_step_base import CliStepBase\nfrom ..cli_models import BuildElectionResults, EncryptResults\n\n\nclass EncryptVotesStep(CliStepBase):\n    \"\"\"Responsible for encrypting votes and storing them in a ballot store.\"\"\"\n\n    def encrypt(\n        self,\n        ballots: List[PlaintextBallot],\n        build_election_results: BuildElectionResults,\n    ) -> EncryptResults:\n        self.print_header(\"Encrypting Ballots\")\n        internal_manifest = build_election_results.internal_manifest\n        context = build_election_results.context\n        (ciphertext_ballots, device) = self._encrypt_votes(\n            ballots, internal_manifest, context\n        )\n        return EncryptResults(device, ciphertext_ballots)\n\n    def _get_encrypter(\n        self,\n        internal_manifest: InternalManifest,\n        context: CiphertextElectionContext,\n    ) -> Tuple[EncryptionMediator, EncryptionDevice]:\n        device = ElectionFactory.get_encryption_device()\n        self.print_value(\"Device location\", device.location)\n        encrypter = EncryptionMediator(internal_manifest, context, device)\n        return (encrypter, device)\n\n    def _encrypt_votes(\n        self,\n        plaintext_ballots: List[PlaintextBallot],\n        internal_manifest: InternalManifest,\n        context: CiphertextElectionContext,\n    ) -> Tuple[List[CiphertextBallot], EncryptionDevice]:\n        self.print_value(\"Ballots to encrypt\", len(plaintext_ballots))\n        (encrypter, device) = self._get_encrypter(internal_manifest, context)\n        encrypted_ballots = EncryptVotesStep._encrypt_ballots(\n            plaintext_ballots, encrypter\n        )\n        return (encrypted_ballots, device)\n\n    @staticmethod\n    def _encrypt_ballots(\n        plaintext_ballots: List[PlaintextBallot], encrypter: EncryptionMediator\n    ) -> List[CiphertextBallot]:\n        ciphertext_ballots: List[CiphertextBallot] = []\n        for plaintext_ballot in plaintext_ballots:\n            click.echo(f\"Encrypting ballot: {plaintext_ballot.object_id}\")\n            encrypted_ballot = encrypter.encrypt(plaintext_ballot)\n            ciphertext_ballots.append(get_optional(encrypted_ballot))\n        return ciphertext_ballots\n"
  },
  {
    "path": "src/electionguard_cli/cli_steps/input_retrieval_step_base.py",
    "content": "from typing import List, Type, TypeVar\nfrom os.path import isfile, isdir, join\nfrom os import listdir\nfrom io import TextIOWrapper\nfrom click import echo\n\nfrom electionguard.election import CiphertextElectionContext\nfrom electionguard.manifest import Manifest\nfrom electionguard.serialize import from_list_in_file, from_file, from_raw\nfrom electionguard.serialize import (\n    from_file_wrapper,\n)\n\nfrom .cli_step_base import CliStepBase\n\n_T = TypeVar(\"_T\")\n\n\nclass InputRetrievalStepBase(CliStepBase):\n    \"\"\"A common base class for all CLI commands that accept user input\"\"\"\n\n    def _get_manifest(self, manifest_file: TextIOWrapper) -> Manifest:\n        manifest: Manifest = from_file_wrapper(Manifest, manifest_file)\n        if not manifest.is_valid():\n            raise ValueError(\"manifest file is invalid\")\n        self.__print_manifest(manifest)\n        return manifest\n\n    def _get_manifest_raw(self, manifest_raw: str) -> Manifest:\n        manifest: Manifest = from_raw(Manifest, manifest_raw)\n        if not manifest.is_valid():\n            raise ValueError(\"manifest file is invalid\")\n        self.__print_manifest(manifest)\n        return manifest\n\n    def __print_manifest(self, manifest: Manifest) -> None:\n        self.print_value(\"Name\", manifest.get_name())\n        self.print_value(\"Scope\", manifest.election_scope_id)\n        self.print_value(\"Geopolitical Units\", len(manifest.geopolitical_units))\n        self.print_value(\"Parties\", len(manifest.parties))\n        self.print_value(\"Candidates\", len(manifest.candidates))\n        self.print_value(\"Contests\", len(manifest.contests))\n        self.print_value(\"Ballot Styles\", len(manifest.ballot_styles))\n\n    @staticmethod\n    def _get_context(context_file: TextIOWrapper) -> CiphertextElectionContext:\n        return from_file_wrapper(CiphertextElectionContext, context_file)\n\n    @staticmethod\n    def _get_ballots(ballots_path: str, ballot_type: Type[_T]) -> List[_T]:\n        if isfile(ballots_path):\n            return from_list_in_file(ballot_type, ballots_path)\n        if isdir(ballots_path):\n            files = listdir(ballots_path)\n            return [\n                InputRetrievalStepBase._get_ballot(ballots_path, f, ballot_type)\n                for f in files\n            ]\n        raise ValueError(\n            f\"{ballots_path} is neither a valid file nor a valid directory\"\n        )\n\n    @staticmethod\n    def _get_ballot(ballots_dir: str, filename: str, ballot_type: Type[_T]) -> _T:\n        full_file = join(ballots_dir, filename)\n        echo(f\"Importing {filename}\")\n        return from_file(ballot_type, full_file)\n"
  },
  {
    "path": "src/electionguard_cli/cli_steps/key_ceremony_step.py",
    "content": "from typing import List\nfrom electionguard.guardian import Guardian\nfrom electionguard.key_ceremony import (\n    ElectionJointKey,\n)\nfrom electionguard.key_ceremony_mediator import KeyCeremonyMediator\nfrom electionguard.utils import get_optional\nfrom electionguard_tools.helpers.key_ceremony_orchestrator import (\n    KeyCeremonyOrchestrator,\n)\n\nfrom .cli_step_base import CliStepBase\n\n\nclass KeyCeremonyStep(CliStepBase):\n    \"\"\"Responsible for running a key ceremony and producing an elgamal public key given a list of guardians.\"\"\"\n\n    def run_key_ceremony(self, guardians: List[Guardian]) -> ElectionJointKey:\n        self.print_header(\"Performing key ceremony\")\n\n        mediator: KeyCeremonyMediator = KeyCeremonyMediator(\n            \"mediator_1\", guardians[0].ceremony_details\n        )\n        KeyCeremonyOrchestrator.perform_full_ceremony(guardians, mediator)\n        joint_key = mediator.publish_joint_key()\n\n        self.print_value(\"Joint Key\", get_optional(joint_key).joint_public_key)\n        return get_optional(joint_key)\n"
  },
  {
    "path": "src/electionguard_cli/cli_steps/mark_ballots_step.py",
    "content": "from typing import Optional\nfrom electionguard_tools.factories import BallotFactory\n\nfrom .cli_step_base import CliStepBase\nfrom ..cli_models import BuildElectionResults, MarkResults\n\n\nclass MarkBallotsStep(CliStepBase):\n    \"\"\"Responsible for marking ballots.\"\"\"\n\n    def mark(\n        self,\n        build_election_results: BuildElectionResults,\n        num_ballots: int,\n        ballot_style_id: Optional[str],\n    ) -> MarkResults:\n        self.print_header(\"Marking Ballots\")\n        internal_manifest = build_election_results.internal_manifest\n        ballot_factory = BallotFactory()\n        plaintext_ballots = ballot_factory.generate_fake_plaintext_ballots_for_election(\n            internal_manifest,\n            num_ballots,\n            ballot_style_id,\n            allow_null_votes=False,\n            allow_under_votes=False,\n        )\n        return MarkResults(plaintext_ballots)\n"
  },
  {
    "path": "src/electionguard_cli/cli_steps/output_step_base.py",
    "content": "from typing import List, Any\n\nfrom electionguard import to_file\nfrom electionguard.guardian import Guardian, GuardianRecord\nfrom electionguard_tools.helpers.export import GUARDIAN_PREFIX\n\nfrom .cli_step_base import CliStepBase\nfrom ..cli_models import CliElectionInputsBase\n\n\nclass OutputStepBase(CliStepBase):\n    \"\"\"Responsible for common functionality across all CLI commands related to outputting results.\"\"\"\n\n    _COMPRESSION_FORMAT = \"zip\"\n\n    def _export_private_keys(self, output_keys: str, guardians: List[Guardian]) -> None:\n        if output_keys is None:\n            return\n\n        private_guardian_records = [\n            guardian.export_private_data() for guardian in guardians\n        ]\n        file_path = output_keys\n        for private_guardian_record in private_guardian_records:\n            file_name = GUARDIAN_PREFIX + private_guardian_record.guardian_id\n            to_file(private_guardian_record, file_name, file_path)\n        self.print_value(\"Guardian private keys\", output_keys)\n        self.print_warning(\n            f\"The files in {file_path} are secret and should be protected securely and not shared.\"\n        )\n\n    @staticmethod\n    def _get_guardian_records(\n        election_inputs: CliElectionInputsBase,\n    ) -> List[GuardianRecord]:\n        return [guardian.publish() for guardian in election_inputs.guardians]\n\n    def _export_file(\n        self,\n        title: str,\n        content: Any,\n        file_dir: str,\n        file_name: str,\n    ) -> str:\n        location = to_file(content, file_name, file_dir)\n        self.print_value(title, location)\n        return location\n"
  },
  {
    "path": "src/electionguard_cli/cli_steps/print_results_step.py",
    "content": "from typing import Dict\nfrom electionguard.manifest import Manifest\nfrom electionguard.type import BallotId\nfrom electionguard.tally import (\n    PlaintextTally,\n)\n\nfrom ..cli_models import CliDecryptResults\nfrom .cli_step_base import CliStepBase\n\n\nclass PrintResultsStep(CliStepBase):\n    \"\"\"Responsible for printing the results of an end-to-end election.\"\"\"\n\n    def _print_tally(\n        self,\n        plaintext_tally: PlaintextTally,\n        contest_names: Dict[str, str],\n        selection_names: Dict[str, str],\n    ) -> None:\n        self.print_header(\"Decrypted tally\")\n        for tally_contest in plaintext_tally.contests.values():\n            contest_name = contest_names.get(tally_contest.object_id)\n\n            self.print_section(contest_name)\n            for selection in tally_contest.selections.values():\n                name = selection_names[selection.object_id]\n                self.print_value(f\"  {name}\", selection.tally)\n\n    def _print_spoiled_ballots(\n        self,\n        plaintext_spoiled_ballots: Dict[BallotId, PlaintextTally],\n        contest_names: Dict[str, str],\n        selection_names: Dict[str, str],\n    ) -> None:\n        ballot_ids = plaintext_spoiled_ballots.keys()\n        for ballot_id in ballot_ids:\n            self.print_header(f\"Spoiled ballot '{ballot_id}'\")\n            spoiled_ballot = plaintext_spoiled_ballots[ballot_id]\n            for contest in spoiled_ballot.contests.values():\n                contest_name = contest_names.get(contest.object_id)\n                self.print_section(contest_name)\n                for selection in contest.selections.values():\n                    name = selection_names[selection.object_id]\n                    self.print_value(f\"  {name}\", selection.tally)\n\n    def print_election_results(\n        self, decrypt_results: CliDecryptResults, manifest: Manifest\n    ) -> None:\n        selection_names = manifest.get_selection_names(\"en\")\n        contest_names = manifest.get_contest_names()\n        self._print_tally(\n            decrypt_results.plaintext_tally, contest_names, selection_names\n        )\n        self._print_spoiled_ballots(\n            decrypt_results.plaintext_spoiled_ballots, contest_names, selection_names\n        )\n"
  },
  {
    "path": "src/electionguard_cli/cli_steps/submit_ballots_step.py",
    "content": "from typing import List\nimport click\n\nfrom electionguard.data_store import DataStore\nfrom electionguard.ballot_box import BallotBox\nfrom electionguard.ballot import CiphertextBallot\n\nfrom .cli_step_base import CliStepBase\nfrom ..cli_models import BuildElectionResults, SubmitResults\n\n\nclass SubmitBallotsStep(CliStepBase):\n    \"\"\"Responsible for submitting ballots.\"\"\"\n\n    def submit(\n        self,\n        build_election_results: BuildElectionResults,\n        cast_ballots: List[CiphertextBallot],\n        spoil_ballots: List[CiphertextBallot],\n    ) -> SubmitResults:\n        self.print_header(\"Submitting Ballots\")\n        internal_manifest = build_election_results.internal_manifest\n        context = build_election_results.context\n\n        ballot_store: DataStore = DataStore()\n        ballot_box = BallotBox(internal_manifest, context, ballot_store)\n\n        for ballot in cast_ballots:\n            ballot_box.cast(ballot)\n            click.echo(f\"Cast Ballot Id: {ballot.object_id}\")\n\n        for ballot in spoil_ballots:\n            ballot_box.spoil(ballot)\n            click.echo(f\"Spoilt Ballot Id: {ballot.object_id}\")\n\n        return SubmitResults(ballot_store.all())\n"
  },
  {
    "path": "src/electionguard_cli/cli_steps/tally_step.py",
    "content": "from typing import Iterable, List, Tuple\nfrom electionguard.scheduler import Scheduler\nfrom electionguard.data_store import DataStore\nfrom electionguard.tally import CiphertextTally\nfrom electionguard.ballot_box import get_ballots\nfrom electionguard.ballot import BallotBoxState, SubmittedBallot\n\nfrom ..cli_models import BuildElectionResults\nfrom .cli_step_base import CliStepBase\n\n\nclass TallyStep(CliStepBase):\n    \"\"\"Responsible for creating a tally and retrieving spoiled ballots.\"\"\"\n\n    def get_from_ballots(\n        self,\n        build_election_results: BuildElectionResults,\n        ballots: List[SubmittedBallot],\n    ) -> Tuple[CiphertextTally, List[SubmittedBallot]]:\n        self.print_header(\"Creating Tally\")\n        tuble_ballots = [(None, b) for b in ballots]\n        tally = self._get_tally(build_election_results, tuble_ballots)\n\n        spoiled_ballots = [b for b in ballots if b.state == BallotBoxState.SPOILED]\n\n        return (tally, spoiled_ballots)\n\n    def get_from_ballot_store(\n        self, build_election_results: BuildElectionResults, ballot_store: DataStore\n    ) -> Tuple[CiphertextTally, List[SubmittedBallot]]:\n        self.print_header(\"Creating Tally\")\n        ciphertext_tally = self._get_tally(build_election_results, ballot_store.items())\n        spoiled_ballots = self._get_spoiled_ballots(ballot_store)\n        return (ciphertext_tally, spoiled_ballots)\n\n    def _get_tally(\n        self,\n        build_election_results: BuildElectionResults,\n        ballots: Iterable[Tuple[None, SubmittedBallot]],\n    ) -> CiphertextTally:\n        tally = CiphertextTally(\n            \"election-results\",\n            build_election_results.internal_manifest,\n            build_election_results.context,\n        )\n        with Scheduler() as scheduler:\n            tally.batch_append(ballots, True, scheduler)\n        self.print_value(\"Ballots in tally\", len(tally))\n        return tally\n\n    def _get_spoiled_ballots(self, ballot_store: DataStore) -> List[SubmittedBallot]:\n        submitted_ballots = get_ballots(ballot_store, BallotBoxState.SPOILED)\n        spoiled_ballots_list = list(submitted_ballots.values())\n        self.print_value(\"Spoiled ballots\", len(spoiled_ballots_list))\n        return spoiled_ballots_list\n"
  },
  {
    "path": "src/electionguard_cli/e2e/__init__.py",
    "content": "from electionguard_cli.e2e import e2e_command\nfrom electionguard_cli.e2e import e2e_election_builder_step\nfrom electionguard_cli.e2e import e2e_input_retrieval_step\nfrom electionguard_cli.e2e import e2e_inputs\nfrom electionguard_cli.e2e import e2e_publish_step\nfrom electionguard_cli.e2e import submit_votes_step\n\nfrom electionguard_cli.e2e.e2e_command import (\n    E2eCommand,\n)\nfrom electionguard_cli.e2e.e2e_election_builder_step import (\n    E2eElectionBuilderStep,\n)\nfrom electionguard_cli.e2e.e2e_input_retrieval_step import (\n    E2eInputRetrievalStep,\n)\nfrom electionguard_cli.e2e.e2e_inputs import (\n    E2eInputs,\n)\nfrom electionguard_cli.e2e.e2e_publish_step import (\n    E2ePublishStep,\n)\nfrom electionguard_cli.e2e.submit_votes_step import (\n    SubmitVotesStep,\n)\n\n__all__ = [\n    \"E2eCommand\",\n    \"E2eElectionBuilderStep\",\n    \"E2eInputRetrievalStep\",\n    \"E2eInputs\",\n    \"E2ePublishStep\",\n    \"SubmitVotesStep\",\n    \"e2e_command\",\n    \"e2e_election_builder_step\",\n    \"e2e_input_retrieval_step\",\n    \"e2e_inputs\",\n    \"e2e_publish_step\",\n    \"submit_votes_step\",\n]\n"
  },
  {
    "path": "src/electionguard_cli/e2e/e2e_command.py",
    "content": "from io import TextIOWrapper\nimport click\n\n\nfrom ..cli_steps import (\n    DecryptStep,\n    PrintResultsStep,\n    TallyStep,\n    KeyCeremonyStep,\n    EncryptVotesStep,\n)\nfrom .e2e_input_retrieval_step import E2eInputRetrievalStep\nfrom .submit_votes_step import SubmitVotesStep\nfrom .e2e_publish_step import E2ePublishStep\nfrom .e2e_election_builder_step import E2eElectionBuilderStep\n\n\n@click.command(\"e2e\")\n@click.option(\n    \"--guardian-count\",\n    prompt=\"Number of guardians\",\n    help=\"The number of guardians that will participate in the key ceremony and tally.\",\n    type=click.INT,\n)\n@click.option(\n    \"--quorum\",\n    prompt=\"Quorum\",\n    help=\"The minimum number of guardians required to show up to the tally.\",\n    type=click.INT,\n)\n@click.option(\n    \"--manifest\",\n    prompt=\"Manifest file\",\n    help=\"The location of an election manifest.\",\n    type=click.File(),\n)\n@click.option(\n    \"--ballots\",\n    prompt=\"Ballots file or directory\",\n    help=\"The location of a file or directory that contains plaintext ballots.\",\n    type=click.Path(exists=True, dir_okay=True, file_okay=True),\n)\n@click.option(\n    \"--spoil-id\",\n    prompt=\"Object-id of ballot to spoil\",\n    help=\"The object-id of a ballot within the ballots file to spoil.\",\n    type=click.STRING,\n    default=None,\n    prompt_required=False,\n)\n@click.option(\n    \"--url\",\n    help=\"An optional verification url for the election.\",\n    required=False,\n    type=click.STRING,\n    default=None,\n    prompt=False,\n)\n@click.option(\n    \"--output-record\",\n    help=\"A file name for saving an output election record (e.g. './election.zip').\"\n    + \" If no value provided then an election record will not be generated.\",\n    type=click.Path(\n        exists=False,\n        dir_okay=False,\n        file_okay=True,\n    ),\n    default=None,\n)\n@click.option(\n    \"--output-keys\",\n    help=\"A directory for saving the private and public guardian keys (e.g. './guardian-keys').\"\n    + \" If no value provided then no keys will be output.\",\n    type=click.Path(exists=False, dir_okay=True, file_okay=False, resolve_path=True),\n    default=None,\n)\ndef E2eCommand(\n    guardian_count: int,\n    quorum: int,\n    manifest: TextIOWrapper,\n    ballots: str,\n    spoil_id: str,\n    url: str,\n    output_record: str,\n    output_keys: str,\n) -> None:\n    \"\"\"Runs through an end-to-end election.\"\"\"\n\n    # get user inputs\n    election_inputs = E2eInputRetrievalStep().get_inputs(\n        guardian_count,\n        quorum,\n        manifest,\n        ballots,\n        spoil_id,\n        output_record,\n        output_keys,\n        url,\n    )\n\n    # perform election\n    joint_key = KeyCeremonyStep().run_key_ceremony(election_inputs.guardians)\n    build_election_results = E2eElectionBuilderStep().build_election_with_key(\n        election_inputs, joint_key\n    )\n    encrypt_results = EncryptVotesStep().encrypt(\n        election_inputs.ballots, build_election_results\n    )\n    data_store = SubmitVotesStep().submit(\n        election_inputs, build_election_results, encrypt_results\n    )\n    (ciphertext_tally, spoiled_ballots) = TallyStep().get_from_ballot_store(\n        build_election_results, data_store\n    )\n    decrypt_results = DecryptStep().decrypt(\n        ciphertext_tally,\n        spoiled_ballots,\n        election_inputs.guardians,\n        build_election_results,\n        election_inputs.manifest,\n    )\n\n    # print results\n    PrintResultsStep().print_election_results(decrypt_results, election_inputs.manifest)\n\n    # publish election record\n    E2ePublishStep().export(\n        election_inputs,\n        build_election_results,\n        encrypt_results,\n        decrypt_results,\n        data_store,\n    )\n"
  },
  {
    "path": "src/electionguard_cli/e2e/e2e_election_builder_step.py",
    "content": "from electionguard.key_ceremony import ElectionJointKey\nfrom .e2e_inputs import E2eInputs\nfrom ..cli_models import BuildElectionResults\nfrom ..cli_steps import ElectionBuilderStep\n\n\nclass E2eElectionBuilderStep(ElectionBuilderStep):\n    \"\"\"Responsible for creating a manifest and context for use in an election\"\"\"\n\n    def build_election_with_key(\n        self, election_inputs: E2eInputs, joint_key: ElectionJointKey\n    ) -> BuildElectionResults:\n        return self._build_election(\n            election_inputs,\n            joint_key.joint_public_key,\n            joint_key.commitment_hash,\n            election_inputs.verification_url,\n        )\n"
  },
  {
    "path": "src/electionguard_cli/e2e/e2e_input_retrieval_step.py",
    "content": "from io import TextIOWrapper\nfrom typing import Optional\nfrom electionguard.ballot import PlaintextBallot\n\nfrom electionguard.key_ceremony import CeremonyDetails\nfrom electionguard.manifest import Manifest\nfrom electionguard_tools.helpers.key_ceremony_orchestrator import (\n    KeyCeremonyOrchestrator,\n)\n\nfrom ..cli_steps import (\n    InputRetrievalStepBase,\n)\nfrom .e2e_inputs import E2eInputs\n\n\nclass E2eInputRetrievalStep(InputRetrievalStepBase):\n    \"\"\"Responsible for retrieving and parsing user provided inputs for the CLI's e2e command.\"\"\"\n\n    def get_inputs(\n        self,\n        guardian_count: int,\n        quorum: int,\n        manifest_file: TextIOWrapper,\n        ballots_path: str,\n        spoil_id: str,\n        output_record: str,\n        output_keys: str,\n        verification_url: Optional[str],\n    ) -> E2eInputs:\n        self.print_header(\"Retrieving Inputs\")\n        guardians = KeyCeremonyOrchestrator.create_guardians(\n            CeremonyDetails(guardian_count, quorum)\n        )\n        manifest: Manifest = self._get_manifest(manifest_file)\n        ballots = E2eInputRetrievalStep._get_ballots(ballots_path, PlaintextBallot)\n        self.print_value(\"Guardians\", guardian_count)\n        self.print_value(\"Quorum\", quorum)\n        return E2eInputs(\n            guardian_count,\n            quorum,\n            guardians,\n            manifest,\n            ballots,\n            spoil_id,\n            output_record,\n            output_keys,\n            verification_url,\n        )\n"
  },
  {
    "path": "src/electionguard_cli/e2e/e2e_inputs.py",
    "content": "from typing import List, Optional\nfrom electionguard.ballot import PlaintextBallot\nfrom electionguard.guardian import Guardian\nfrom electionguard.manifest import Manifest\nfrom ..cli_models import (\n    CliElectionInputsBase,\n)\n\n\n# pylint: disable=too-many-instance-attributes\nclass E2eInputs(CliElectionInputsBase):\n    \"\"\"Responsible for holding the inputs for the CLI's e2e command.\"\"\"\n\n    def __init__(\n        self,\n        guardian_count: int,\n        quorum: int,\n        guardians: List[Guardian],\n        manifest: Manifest,\n        ballots: List[PlaintextBallot],\n        spoil_id: str,\n        output_record: str,\n        output_keys: str,\n        verification_url: Optional[str],\n    ):\n        self.guardian_count = guardian_count\n        self.quorum = quorum\n        self.guardians = guardians\n        self.manifest = manifest\n        self.ballots = ballots\n        self.spoil_id = spoil_id\n        self.output_record = output_record\n        self.output_keys = output_keys\n        self.verification_url = verification_url\n\n    ballots: List[PlaintextBallot]\n    spoil_id: str\n    output_record: str\n    output_keys: str\n    verification_url: Optional[str]\n"
  },
  {
    "path": "src/electionguard_cli/e2e/e2e_publish_step.py",
    "content": "from shutil import make_archive\nfrom os.path import splitext\nfrom tempfile import TemporaryDirectory\nfrom click import echo\nfrom electionguard.constants import get_constants\nfrom electionguard.data_store import DataStore\n\nfrom electionguard_tools.helpers.export import export_record\n\nfrom .e2e_inputs import E2eInputs\nfrom ..cli_models import BuildElectionResults, CliDecryptResults, EncryptResults\nfrom ..cli_steps import OutputStepBase\n\n\nclass E2ePublishStep(OutputStepBase):\n    \"\"\"Responsible for publishing an election record after an election has completed.\"\"\"\n\n    def export(\n        self,\n        election_inputs: E2eInputs,\n        build_election_results: BuildElectionResults,\n        submit_results: EncryptResults,\n        decrypt_results: CliDecryptResults,\n        data_store: DataStore,\n    ) -> None:\n\n        self.print_header(\"Election Record\")\n\n        self._export_election_record(\n            election_inputs,\n            build_election_results,\n            submit_results,\n            decrypt_results,\n            data_store,\n        )\n        self._export_private_keys_e2e(election_inputs)\n\n    def _export_election_record(\n        self,\n        election_inputs: E2eInputs,\n        build_election_results: BuildElectionResults,\n        encrypt_results: EncryptResults,\n        decrypt_results: CliDecryptResults,\n        data_store: DataStore,\n    ) -> None:\n        guardian_records = OutputStepBase._get_guardian_records(election_inputs)\n        constants = get_constants()\n        with TemporaryDirectory() as temp_dir:\n            export_record(\n                election_inputs.manifest,\n                build_election_results.context,\n                constants,\n                [encrypt_results.device],\n                data_store.all(),\n                decrypt_results.plaintext_spoiled_ballots.values(),\n                decrypt_results.ciphertext_tally.publish(),\n                decrypt_results.plaintext_tally,\n                guardian_records,\n                decrypt_results.lagrange_coefficients,\n                election_record_directory=temp_dir,\n            )\n            file_name = splitext(election_inputs.output_record)[0]\n            make_archive(file_name, self._COMPRESSION_FORMAT, temp_dir)\n            echo(f\"Exported election record to '{election_inputs.output_record}'\")\n\n    def _export_private_keys_e2e(self, election_inputs: E2eInputs) -> None:\n        self._export_private_keys(\n            election_inputs.output_keys, election_inputs.guardians\n        )\n"
  },
  {
    "path": "src/electionguard_cli/e2e/submit_votes_step.py",
    "content": "from typing import List\nimport click\n\nfrom electionguard.data_store import DataStore\nfrom electionguard.ballot_box import BallotBox\nfrom electionguard.election import CiphertextElectionContext\nfrom electionguard.manifest import InternalManifest\nfrom electionguard.utils import get_optional\nfrom electionguard.ballot import (\n    CiphertextBallot,\n)\n\nfrom ..cli_models import BuildElectionResults, EncryptResults\nfrom ..cli_steps import CliStepBase\nfrom .e2e_inputs import E2eInputs\n\n\nclass SubmitVotesStep(CliStepBase):\n    \"\"\"Responsible for submitting ballots into a ballot store.\"\"\"\n\n    def submit(\n        self,\n        e2e_inputs: E2eInputs,\n        build_election_results: BuildElectionResults,\n        e2e_encrypt_results: EncryptResults,\n    ) -> DataStore:\n        self.print_header(\"Submitting Ballots\")\n        internal_manifest = build_election_results.internal_manifest\n        context = build_election_results.context\n        ballot_store = SubmitVotesStep._cast_and_spoil(\n            internal_manifest,\n            context,\n            e2e_encrypt_results.ciphertext_ballots,\n            e2e_inputs,\n        )\n        return ballot_store\n\n    @staticmethod\n    def _cast_and_spoil(\n        internal_manifest: InternalManifest,\n        context: CiphertextElectionContext,\n        ciphertext_ballots: List[CiphertextBallot],\n        e2e_inputs: E2eInputs,\n    ) -> DataStore:\n        ballot_store: DataStore = DataStore()\n        ballot_box = BallotBox(internal_manifest, context, ballot_store)\n\n        for ballot in ciphertext_ballots:\n            spoil = ballot.object_id == e2e_inputs.spoil_id\n            if spoil:\n                submitted_ballot = ballot_box.spoil(ballot)\n            else:\n                submitted_ballot = ballot_box.cast(ballot)\n\n            click.echo(\n                f\"Submitted Ballot Id: {ballot.object_id} state: {get_optional(submitted_ballot).state}\"\n            )\n        return ballot_store\n"
  },
  {
    "path": "src/electionguard_cli/encrypt_ballots/__init__.py",
    "content": "from electionguard_cli.encrypt_ballots import encrypt_ballot_inputs\nfrom electionguard_cli.encrypt_ballots import encrypt_ballots_election_builder_step\nfrom electionguard_cli.encrypt_ballots import encrypt_ballots_input_retrieval_step\nfrom electionguard_cli.encrypt_ballots import encrypt_ballots_publish_step\nfrom electionguard_cli.encrypt_ballots import encrypt_command\n\nfrom electionguard_cli.encrypt_ballots.encrypt_ballot_inputs import (\n    EncryptBallotInputs,\n)\nfrom electionguard_cli.encrypt_ballots.encrypt_ballots_election_builder_step import (\n    EncryptBallotsElectionBuilderStep,\n)\nfrom electionguard_cli.encrypt_ballots.encrypt_ballots_input_retrieval_step import (\n    EncryptBallotsInputRetrievalStep,\n)\nfrom electionguard_cli.encrypt_ballots.encrypt_ballots_publish_step import (\n    EncryptBallotsPublishStep,\n)\nfrom electionguard_cli.encrypt_ballots.encrypt_command import (\n    EncryptBallotsCommand,\n)\n\n__all__ = [\n    \"EncryptBallotInputs\",\n    \"EncryptBallotsCommand\",\n    \"EncryptBallotsElectionBuilderStep\",\n    \"EncryptBallotsInputRetrievalStep\",\n    \"EncryptBallotsPublishStep\",\n    \"encrypt_ballot_inputs\",\n    \"encrypt_ballots_election_builder_step\",\n    \"encrypt_ballots_input_retrieval_step\",\n    \"encrypt_ballots_publish_step\",\n    \"encrypt_command\",\n]\n"
  },
  {
    "path": "src/electionguard_cli/encrypt_ballots/encrypt_ballot_inputs.py",
    "content": "from typing import List\nfrom electionguard.ballot import PlaintextBallot\nfrom electionguard.election import CiphertextElectionContext\nfrom electionguard.encrypt import EncryptionDevice\nfrom electionguard.manifest import Manifest\n\nfrom ..cli_models import (\n    CliElectionInputsBase,\n)\n\n\nclass EncryptBallotInputs(CliElectionInputsBase):\n    \"\"\"Responsible for holding the inputs for the CLI's encrypt ballots command\"\"\"\n\n    def __init__(\n        self,\n        manifest: Manifest,\n        context: CiphertextElectionContext,\n        plaintext_ballots: List[PlaintextBallot],\n    ):\n        self.guardian_count = context.number_of_guardians\n        self.quorum = context.quorum\n        self.manifest = manifest\n        self.plaintext_ballots = plaintext_ballots\n        self.context = context\n\n    plaintext_ballots: List[PlaintextBallot]\n    context: CiphertextElectionContext\n    encryption_devices: List[EncryptionDevice]\n    output_record: str\n"
  },
  {
    "path": "src/electionguard_cli/encrypt_ballots/encrypt_ballots_election_builder_step.py",
    "content": "from ..cli_models import BuildElectionResults\nfrom ..cli_steps import ElectionBuilderStep\nfrom .encrypt_ballot_inputs import EncryptBallotInputs\n\n\nclass EncryptBallotsElectionBuilderStep(ElectionBuilderStep):\n    \"\"\"Responsible for creating a manifest and context for use in an election\n    specifically for the encrypt ballots command\"\"\"\n\n    def build_election_with_context(\n        self, election_inputs: EncryptBallotInputs\n    ) -> BuildElectionResults:\n        verification_url = election_inputs.context.get_extended_data_field(\n            self.VERIFICATION_URL_NAME\n        )\n        return self._build_election(\n            election_inputs,\n            election_inputs.context.elgamal_public_key,\n            election_inputs.context.commitment_hash,\n            verification_url,\n        )\n"
  },
  {
    "path": "src/electionguard_cli/encrypt_ballots/encrypt_ballots_input_retrieval_step.py",
    "content": "from io import TextIOWrapper\nfrom electionguard.ballot import PlaintextBallot\n\nfrom electionguard.manifest import Manifest\n\nfrom .encrypt_ballot_inputs import EncryptBallotInputs\nfrom ..cli_steps import (\n    InputRetrievalStepBase,\n)\n\n\nclass EncryptBallotsInputRetrievalStep(InputRetrievalStepBase):\n    \"\"\"Responsible for retrieving and parsing user provided inputs for the CLI's encrypt ballots command.\"\"\"\n\n    def get_inputs(\n        self,\n        manifest_file: TextIOWrapper,\n        context_file: TextIOWrapper,\n        ballots_dir: str,\n    ) -> EncryptBallotInputs:\n\n        self.print_header(\"Retrieving Inputs\")\n        manifest: Manifest = self._get_manifest(manifest_file)\n        context = InputRetrievalStepBase._get_context(context_file)\n        plaintext_ballots = InputRetrievalStepBase._get_ballots(\n            ballots_dir, PlaintextBallot\n        )\n\n        return EncryptBallotInputs(\n            manifest,\n            context,\n            plaintext_ballots,\n        )\n"
  },
  {
    "path": "src/electionguard_cli/encrypt_ballots/encrypt_ballots_publish_step.py",
    "content": "from click import echo\n\nfrom electionguard import to_file\n\nfrom ..cli_models import EncryptResults\nfrom ..cli_steps import OutputStepBase\n\n\nclass EncryptBallotsPublishStep(OutputStepBase):\n    \"\"\"Responsible for writing the results of the encrypt ballots command.\"\"\"\n\n    def publish(self, encrypt_results: EncryptResults, out_dir: str) -> None:\n        if out_dir is None:\n            return\n        self.print_header(\"Writing Encrypted Ballots\")\n        device_file = to_file(encrypt_results.device, \"device\", out_dir)\n        self.print_value(\"Device file\", device_file + \".json\")\n        for ballot in encrypt_results.ciphertext_ballots:\n            ballot_file = to_file(ballot, ballot.object_id, out_dir)\n            echo(f\"Writing {ballot_file}\")\n        self.print_value(\"Encrypted ballots\", len(encrypt_results.ciphertext_ballots))\n"
  },
  {
    "path": "src/electionguard_cli/encrypt_ballots/encrypt_command.py",
    "content": "from io import TextIOWrapper\nimport click\n\n\nfrom .encrypt_ballots_election_builder_step import EncryptBallotsElectionBuilderStep\nfrom .encrypt_ballots_input_retrieval_step import EncryptBallotsInputRetrievalStep\nfrom .encrypt_ballots_publish_step import EncryptBallotsPublishStep\nfrom ..cli_steps import EncryptVotesStep\n\n\n@click.command(\"encrypt-ballots\")\n@click.option(\n    \"--manifest\",\n    prompt=\"Manifest file\",\n    help=\"The location of an election manifest.\",\n    type=click.File(),\n)\n@click.option(\n    \"--context\",\n    prompt=\"Context file\",\n    help=\"The location of an election context.\",\n    type=click.File(),\n)\n@click.option(\n    \"--ballots-dir\",\n    prompt=\"Ballots file\",\n    help=\"The location of a file that contains plaintext ballots.\",\n    type=click.Path(exists=True, dir_okay=True, file_okay=False, resolve_path=True),\n)\n@click.option(\n    \"--out-dir\",\n    help=\"A directory for saving encrypted ballots and encryption device to.\",\n    type=click.Path(exists=False, dir_okay=True, file_okay=False, resolve_path=True),\n)\ndef EncryptBallotsCommand(\n    manifest: TextIOWrapper, context: TextIOWrapper, ballots_dir: str, out_dir: str\n) -> None:\n    \"\"\"\n    Encrypt ballots, but does not submit them\n    \"\"\"\n\n    election_inputs = EncryptBallotsInputRetrievalStep().get_inputs(\n        manifest, context, ballots_dir\n    )\n    build_election_results = (\n        EncryptBallotsElectionBuilderStep().build_election_with_context(election_inputs)\n    )\n    encrypt_results = EncryptVotesStep().encrypt(\n        election_inputs.plaintext_ballots, build_election_results\n    )\n    EncryptBallotsPublishStep().publish(encrypt_results, out_dir)\n"
  },
  {
    "path": "src/electionguard_cli/import_ballots/__init__.py",
    "content": "from electionguard_cli.import_ballots import import_ballot_inputs\nfrom electionguard_cli.import_ballots import import_ballots_command\nfrom electionguard_cli.import_ballots import import_ballots_election_builder_step\nfrom electionguard_cli.import_ballots import import_ballots_input_retrieval_step\nfrom electionguard_cli.import_ballots import import_ballots_publish_step\n\nfrom electionguard_cli.import_ballots.import_ballot_inputs import (\n    ImportBallotInputs,\n)\nfrom electionguard_cli.import_ballots.import_ballots_command import (\n    ImportBallotsCommand,\n)\nfrom electionguard_cli.import_ballots.import_ballots_election_builder_step import (\n    ImportBallotsElectionBuilderStep,\n)\nfrom electionguard_cli.import_ballots.import_ballots_input_retrieval_step import (\n    ImportBallotsInputRetrievalStep,\n)\nfrom electionguard_cli.import_ballots.import_ballots_publish_step import (\n    ImportBallotsPublishStep,\n)\n\n__all__ = [\n    \"ImportBallotInputs\",\n    \"ImportBallotsCommand\",\n    \"ImportBallotsElectionBuilderStep\",\n    \"ImportBallotsInputRetrievalStep\",\n    \"ImportBallotsPublishStep\",\n    \"import_ballot_inputs\",\n    \"import_ballots_command\",\n    \"import_ballots_election_builder_step\",\n    \"import_ballots_input_retrieval_step\",\n    \"import_ballots_publish_step\",\n]\n"
  },
  {
    "path": "src/electionguard_cli/import_ballots/import_ballot_inputs.py",
    "content": "from typing import List\nfrom electionguard.ballot import SubmittedBallot\nfrom electionguard.election import CiphertextElectionContext\nfrom electionguard.encrypt import EncryptionDevice\nfrom electionguard.guardian import Guardian\nfrom electionguard.manifest import Manifest\n\nfrom ..cli_models import (\n    CliElectionInputsBase,\n)\n\n\n# pylint: disable=too-many-instance-attributes\nclass ImportBallotInputs(CliElectionInputsBase):\n    \"\"\"Responsible for holding the inputs for the CLI's import ballots command\"\"\"\n\n    def __init__(\n        self,\n        guardians: List[Guardian],\n        manifest: Manifest,\n        submitted_ballots: List[SubmittedBallot],\n        context: CiphertextElectionContext,\n        encryption_device: List[EncryptionDevice],\n        output_record: str,\n    ):\n        self.guardian_count = context.number_of_guardians\n        self.quorum = context.quorum\n        self.guardians = guardians\n        self.manifest = manifest\n        self.submitted_ballots = submitted_ballots\n        self.context = context\n        self.encryption_devices = encryption_device\n        self.output_record = output_record\n\n    submitted_ballots: List[SubmittedBallot]\n    context: CiphertextElectionContext\n    encryption_devices: List[EncryptionDevice]\n    output_record: str\n"
  },
  {
    "path": "src/electionguard_cli/import_ballots/import_ballots_command.py",
    "content": "from io import TextIOWrapper\nimport click\n\nfrom .import_ballots_publish_step import ImportBallotsPublishStep\nfrom .import_ballots_input_retrieval_step import ImportBallotsInputRetrievalStep\nfrom .import_ballots_election_builder_step import ImportBallotsElectionBuilderStep\nfrom ..cli_steps.decrypt_step import DecryptStep\nfrom ..cli_steps.print_results_step import PrintResultsStep\nfrom ..cli_steps.tally_step import TallyStep\n\n\n@click.command(\"import-ballots\")\n@click.option(\n    \"--manifest\",\n    prompt=\"Manifest file\",\n    help=\"The location of an election manifest.\",\n    type=click.File(),\n)\n@click.option(\n    \"--context\",\n    prompt=\"Context file\",\n    help=\"The location of an election context.\",\n    type=click.File(),\n)\n@click.option(\n    \"--ballots-dir\",\n    prompt=\"Ballots file\",\n    help=\"The location of a file that contains submitted ballots.\",\n    type=click.Path(exists=True, dir_okay=True, file_okay=False, resolve_path=True),\n)\n@click.option(\n    \"--guardian-keys\",\n    prompt=\"Guardian keys file\",\n    help=\"The location of a json file with all guardians's private key data. \"\n    + \"This corresponds to the output-keys parameter of the e2e command.\",\n    type=click.Path(exists=True, dir_okay=True, file_okay=False, resolve_path=True),\n)\n@click.option(\n    \"--encryption-device\",\n    prompt=\"Encryption device file\",\n    help=\"An optional file containing an encryption device used for the ballots.  This data will \"\n    + \"be exported in the election record.\",\n    type=click.Path(exists=True, dir_okay=False, file_okay=True, resolve_path=True),\n    default=None,\n)\n@click.option(\n    \"--output-record\",\n    help=\"A file name for saving an output election record (e.g. './election.zip').\"\n    + \" If no value provided then an election record will not be generated.\",\n    type=click.Path(\n        exists=False,\n        dir_okay=False,\n        file_okay=True,\n    ),\n    default=None,\n)\ndef ImportBallotsCommand(\n    manifest: TextIOWrapper,\n    context: TextIOWrapper,\n    ballots_dir: str,\n    guardian_keys: str,\n    encryption_device: str,\n    output_record: str,\n) -> None:\n    \"\"\"\n    Imports ballots\n    \"\"\"\n\n    # get user inputs\n    election_inputs = ImportBallotsInputRetrievalStep().get_inputs(\n        manifest, context, ballots_dir, guardian_keys, encryption_device, output_record\n    )\n\n    # perform election\n    build_election_results = (\n        ImportBallotsElectionBuilderStep().build_election_with_context(election_inputs)\n    )\n    (ciphertext_tally, spoiled_ballots) = TallyStep().get_from_ballots(\n        build_election_results, election_inputs.submitted_ballots\n    )\n    decrypt_results = DecryptStep().decrypt(\n        ciphertext_tally,\n        spoiled_ballots,\n        election_inputs.guardians,\n        build_election_results,\n        election_inputs.manifest,\n    )\n\n    # print results\n    PrintResultsStep().print_election_results(decrypt_results, election_inputs.manifest)\n\n    # publish election record\n    ImportBallotsPublishStep().publish(\n        election_inputs, build_election_results, decrypt_results\n    )\n"
  },
  {
    "path": "src/electionguard_cli/import_ballots/import_ballots_election_builder_step.py",
    "content": "from ..cli_models import BuildElectionResults\nfrom ..cli_steps import ElectionBuilderStep\nfrom .import_ballot_inputs import ImportBallotInputs\n\n\nclass ImportBallotsElectionBuilderStep(ElectionBuilderStep):\n    \"\"\"Responsible for creating a manifest and context for use in an election\n    specifically for the import ballots command\"\"\"\n\n    def build_election_with_context(\n        self, election_inputs: ImportBallotInputs\n    ) -> BuildElectionResults:\n        verification_url = election_inputs.context.get_extended_data_field(\n            self.VERIFICATION_URL_NAME\n        )\n        return self._build_election(\n            election_inputs,\n            election_inputs.context.elgamal_public_key,\n            election_inputs.context.commitment_hash,\n            verification_url,\n        )\n"
  },
  {
    "path": "src/electionguard_cli/import_ballots/import_ballots_input_retrieval_step.py",
    "content": "from typing import List\nfrom io import TextIOWrapper\nfrom os import listdir\nfrom os.path import join\n\nfrom electionguard import CiphertextElectionContext\nfrom electionguard.ballot import SubmittedBallot\nfrom electionguard.encrypt import EncryptionDevice\nfrom electionguard.guardian import Guardian, PrivateGuardianRecord\nfrom electionguard.manifest import Manifest\nfrom electionguard.serialize import from_file\n\nfrom .import_ballot_inputs import (\n    ImportBallotInputs,\n)\nfrom ..cli_steps import (\n    InputRetrievalStepBase,\n)\n\n\nclass ImportBallotsInputRetrievalStep(InputRetrievalStepBase):\n    \"\"\"Responsible for retrieving and parsing user provided inputs for the CLI's import ballots command.\"\"\"\n\n    def get_inputs(\n        self,\n        manifest_file: TextIOWrapper,\n        context_file: TextIOWrapper,\n        ballots_dir: str,\n        guardian_keys: str,\n        encryption_device_file: str,\n        output_record: str,\n    ) -> ImportBallotInputs:\n\n        self.print_header(\"Retrieving Inputs\")\n        manifest: Manifest = self._get_manifest(manifest_file)\n        context = InputRetrievalStepBase._get_context(context_file)\n        guardians = ImportBallotsInputRetrievalStep._get_guardians_from_keys(\n            guardian_keys, context\n        )\n        encryption_devices = self._get_encryption_devices(encryption_device_file)\n        submitted_ballots = ImportBallotsInputRetrievalStep._get_ballots(\n            ballots_dir, SubmittedBallot\n        )\n\n        self.print_value(\"Ballots Dir\", ballots_dir)\n\n        return ImportBallotInputs(\n            guardians,\n            manifest,\n            submitted_ballots,\n            context,\n            encryption_devices,\n            output_record,\n        )\n\n    def _get_encryption_devices(\n        self, encryption_device_file: str\n    ) -> List[EncryptionDevice]:\n        if encryption_device_file is None:\n            return []\n        encryption_device = from_file(EncryptionDevice, encryption_device_file)\n        self.print_value(\"Encryption device id\", encryption_device.device_id)\n        self.print_value(\"Encryption device location\", encryption_device.location)\n        return [encryption_device]\n\n    @staticmethod\n    def _get_guardians_from_keys(\n        guardian_keys_dir: str, context: CiphertextElectionContext\n    ) -> List[Guardian]:\n\n        files = listdir(guardian_keys_dir)\n        private_records = [\n            ImportBallotsInputRetrievalStep._load_private_record(guardian_keys_dir, f)\n            for f in files\n        ]\n        return list(\n            map(\n                lambda record: ImportBallotsInputRetrievalStep._get_guardian(\n                    record, context\n                ),\n                private_records,\n            )\n        )\n\n    @staticmethod\n    def _load_private_record(guardian_dir: str, filename: str) -> PrivateGuardianRecord:\n        full_file = join(guardian_dir, filename)\n        return from_file(PrivateGuardianRecord, full_file)\n\n    @staticmethod\n    def _get_guardian(\n        private_record: PrivateGuardianRecord, context: CiphertextElectionContext\n    ) -> Guardian:\n        return Guardian.from_private_record(\n            private_record, context.number_of_guardians, context.quorum\n        )\n"
  },
  {
    "path": "src/electionguard_cli/import_ballots/import_ballots_publish_step.py",
    "content": "from typing import List\nfrom shutil import make_archive\nfrom os.path import splitext\nfrom tempfile import TemporaryDirectory\nfrom click import echo\nfrom electionguard.encrypt import EncryptionDevice\n\nfrom electionguard.constants import get_constants\nfrom electionguard_tools.helpers.export import export_record\n\nfrom .import_ballot_inputs import ImportBallotInputs\nfrom ..cli_models import CliDecryptResults, BuildElectionResults\nfrom ..cli_steps import OutputStepBase\n\n\nclass ImportBallotsPublishStep(OutputStepBase):\n    \"\"\"Responsible for publishing an election record during an import ballots command\"\"\"\n\n    def publish(\n        self,\n        election_inputs: ImportBallotInputs,\n        build_election_results: BuildElectionResults,\n        decrypt_results: CliDecryptResults,\n    ) -> None:\n\n        if election_inputs.output_record is None:\n            return\n\n        self.print_header(\"Publishing Results\")\n\n        guardian_records = OutputStepBase._get_guardian_records(election_inputs)\n        constants = get_constants()\n\n        encryption_devices: List[EncryptionDevice] = election_inputs.encryption_devices\n\n        with TemporaryDirectory() as temp_dir:\n            export_record(\n                election_inputs.manifest,\n                build_election_results.context,\n                constants,\n                encryption_devices,\n                election_inputs.submitted_ballots,\n                decrypt_results.plaintext_spoiled_ballots.values(),\n                decrypt_results.ciphertext_tally.publish(),\n                decrypt_results.plaintext_tally,\n                guardian_records,\n                decrypt_results.lagrange_coefficients,\n                election_record_directory=temp_dir,\n            )\n            file_name = splitext(election_inputs.output_record)[0]\n            make_archive(file_name, self._COMPRESSION_FORMAT, temp_dir)\n            echo(f\"Exported election record to '{election_inputs.output_record}'\")\n"
  },
  {
    "path": "src/electionguard_cli/mark_ballots/__init__.py",
    "content": "from electionguard_cli.mark_ballots import mark_ballot_inputs\nfrom electionguard_cli.mark_ballots import mark_ballots_election_builder_step\nfrom electionguard_cli.mark_ballots import mark_ballots_input_retrieval_step\nfrom electionguard_cli.mark_ballots import mark_ballots_publish_step\nfrom electionguard_cli.mark_ballots import mark_command\n\nfrom electionguard_cli.mark_ballots.mark_ballot_inputs import (\n    MarkBallotInputs,\n)\nfrom electionguard_cli.mark_ballots.mark_ballots_election_builder_step import (\n    MarkBallotsElectionBuilderStep,\n)\nfrom electionguard_cli.mark_ballots.mark_ballots_input_retrieval_step import (\n    MarkBallotsInputRetrievalStep,\n)\nfrom electionguard_cli.mark_ballots.mark_ballots_publish_step import (\n    MarkBallotsPublishStep,\n)\nfrom electionguard_cli.mark_ballots.mark_command import (\n    MarkBallotsCommand,\n)\n\n__all__ = [\n    \"MarkBallotInputs\",\n    \"MarkBallotsCommand\",\n    \"MarkBallotsElectionBuilderStep\",\n    \"MarkBallotsInputRetrievalStep\",\n    \"MarkBallotsPublishStep\",\n    \"mark_ballot_inputs\",\n    \"mark_ballots_election_builder_step\",\n    \"mark_ballots_input_retrieval_step\",\n    \"mark_ballots_publish_step\",\n    \"mark_command\",\n]\n"
  },
  {
    "path": "src/electionguard_cli/mark_ballots/mark_ballot_inputs.py",
    "content": "from electionguard.election import CiphertextElectionContext\nfrom electionguard.manifest import Manifest\n\nfrom ..cli_models import (\n    CliElectionInputsBase,\n)\n\n\nclass MarkBallotInputs(CliElectionInputsBase):\n    \"\"\"Responsible for holding the inputs for the CLI's mark ballots command\"\"\"\n\n    def __init__(\n        self,\n        manifest: Manifest,\n        context: CiphertextElectionContext,\n    ):\n        self.guardian_count = context.number_of_guardians\n        self.quorum = context.quorum\n        self.manifest = manifest\n        self.context = context\n\n    context: CiphertextElectionContext\n"
  },
  {
    "path": "src/electionguard_cli/mark_ballots/mark_ballots_election_builder_step.py",
    "content": "from ..cli_models import BuildElectionResults\nfrom ..cli_steps import ElectionBuilderStep\nfrom .mark_ballot_inputs import MarkBallotInputs\n\n\nclass MarkBallotsElectionBuilderStep(ElectionBuilderStep):\n    \"\"\"Responsible for creating a manifest and context for use in an election\n    specifically for the mark ballots command\"\"\"\n\n    def build_election_with_context(\n        self, election_inputs: MarkBallotInputs\n    ) -> BuildElectionResults:\n        verification_url = election_inputs.context.get_extended_data_field(\n            self.VERIFICATION_URL_NAME\n        )\n        return self._build_election(\n            election_inputs,\n            election_inputs.context.elgamal_public_key,\n            election_inputs.context.commitment_hash,\n            verification_url,\n        )\n"
  },
  {
    "path": "src/electionguard_cli/mark_ballots/mark_ballots_input_retrieval_step.py",
    "content": "from io import TextIOWrapper\n\nfrom electionguard.manifest import Manifest\n\nfrom .mark_ballot_inputs import MarkBallotInputs\nfrom ..cli_steps import (\n    InputRetrievalStepBase,\n)\n\n\nclass MarkBallotsInputRetrievalStep(InputRetrievalStepBase):\n    \"\"\"Responsible for retrieving and parsing user provided inputs for the CLI's mark ballots command.\"\"\"\n\n    def get_inputs(\n        self,\n        manifest_file: TextIOWrapper,\n        context_file: TextIOWrapper,\n    ) -> MarkBallotInputs:\n\n        self.print_header(\"Retrieving Inputs\")\n        manifest: Manifest = self._get_manifest(manifest_file)\n        context = InputRetrievalStepBase._get_context(context_file)\n\n        return MarkBallotInputs(\n            manifest,\n            context,\n        )\n"
  },
  {
    "path": "src/electionguard_cli/mark_ballots/mark_ballots_publish_step.py",
    "content": "from click import echo\n\nfrom electionguard import to_file\n\nfrom ..cli_models import MarkResults\nfrom ..cli_steps import OutputStepBase\n\n\nclass MarkBallotsPublishStep(OutputStepBase):\n    \"\"\"Responsible for writing the results of the mark ballots command.\"\"\"\n\n    def publish(self, marked_ballots: MarkResults, out_dir: str) -> None:\n        if out_dir is None:\n            return\n        self.print_header(\"Writing Marked Ballots\")\n        for ballot in marked_ballots.plaintext_ballots:\n            ballot_file = to_file(ballot, ballot.object_id, out_dir)\n            echo(f\"Writing {ballot_file}\")\n        self.print_value(\"Marked ballots\", len(marked_ballots.plaintext_ballots))\n"
  },
  {
    "path": "src/electionguard_cli/mark_ballots/mark_command.py",
    "content": "from io import TextIOWrapper\nimport click\n\n\nfrom .mark_ballots_election_builder_step import MarkBallotsElectionBuilderStep\nfrom .mark_ballots_input_retrieval_step import MarkBallotsInputRetrievalStep\nfrom .mark_ballots_publish_step import MarkBallotsPublishStep\nfrom ..cli_steps import MarkBallotsStep\n\n\n@click.command(\"mark-ballots\")\n@click.argument(\"num_ballots\", type=click.INT)\n@click.argument(\"ballot_style_id\", type=click.STRING, required=False)\n@click.option(\n    \"--manifest\",\n    prompt=\"Manifest file\",\n    help=\"The location of an election manifest.\",\n    type=click.File(),\n)\n@click.option(\n    \"--context\",\n    prompt=\"Context file\",\n    help=\"The location of an election context.\",\n    type=click.File(),\n)\n@click.option(\n    \"--out-dir\",\n    help=\"A directory for saving plaintext ballots to.\",\n    type=click.Path(exists=False, dir_okay=True, file_okay=False, resolve_path=True),\n)\ndef MarkBallotsCommand(\n    num_ballots: int,\n    ballot_style_id: str,\n    manifest: TextIOWrapper,\n    context: TextIOWrapper,\n    out_dir: str,\n) -> None:\n    \"\"\"\n    Marks ballots\n    \"\"\"\n\n    election_inputs = MarkBallotsInputRetrievalStep().get_inputs(manifest, context)\n    build_election_results = (\n        MarkBallotsElectionBuilderStep().build_election_with_context(election_inputs)\n    )\n    marked_ballots = MarkBallotsStep().mark(\n        build_election_results, num_ballots, ballot_style_id\n    )\n    MarkBallotsPublishStep().publish(marked_ballots, out_dir)\n"
  },
  {
    "path": "src/electionguard_cli/setup_election/__init__.py",
    "content": "from electionguard_cli.setup_election import output_setup_files_step\nfrom electionguard_cli.setup_election import setup_election_builder_step\nfrom electionguard_cli.setup_election import setup_election_command\nfrom electionguard_cli.setup_election import setup_input_retrieval_step\nfrom electionguard_cli.setup_election import setup_inputs\n\nfrom electionguard_cli.setup_election.output_setup_files_step import (\n    OutputSetupFilesStep,\n)\nfrom electionguard_cli.setup_election.setup_election_builder_step import (\n    SetupElectionBuilderStep,\n)\nfrom electionguard_cli.setup_election.setup_election_command import (\n    SetupElectionCommand,\n)\nfrom electionguard_cli.setup_election.setup_input_retrieval_step import (\n    SetupInputRetrievalStep,\n)\nfrom electionguard_cli.setup_election.setup_inputs import (\n    SetupInputs,\n)\n\n__all__ = [\n    \"OutputSetupFilesStep\",\n    \"SetupElectionBuilderStep\",\n    \"SetupElectionCommand\",\n    \"SetupInputRetrievalStep\",\n    \"SetupInputs\",\n    \"output_setup_files_step\",\n    \"setup_election_builder_step\",\n    \"setup_election_command\",\n    \"setup_input_retrieval_step\",\n    \"setup_inputs\",\n]\n"
  },
  {
    "path": "src/electionguard_cli/setup_election/output_setup_files_step.py",
    "content": "from os import path\nfrom os.path import join\nfrom typing import Optional\n\nimport click\nfrom electionguard.election import CiphertextElectionContext\nfrom electionguard.serialize import to_file\nfrom electionguard.constants import get_constants\nfrom electionguard_tools.helpers.export import (\n    CONSTANTS_FILE_NAME,\n    CONTEXT_FILE_NAME,\n    GUARDIAN_PREFIX,\n    MANIFEST_FILE_NAME,\n)\n\nfrom .setup_inputs import SetupInputs\nfrom ..cli_models.e2e_build_election_results import BuildElectionResults\nfrom ..cli_steps import OutputStepBase\n\n\nclass OutputSetupFilesStep(OutputStepBase):\n    \"\"\"Responsible for outputting the files necessary to setup an election\"\"\"\n\n    def output(\n        self,\n        setup_inputs: SetupInputs,\n        build_election_results: BuildElectionResults,\n        package_dir: str,\n        keys_dir: Optional[str],\n    ) -> None:\n        self.print_header(\"Generating Output\")\n        self._export_context(build_election_results.context, package_dir)\n        self._export_constants(package_dir)\n        self._export_manifest(setup_inputs, package_dir)\n        self._export_guardian_records(setup_inputs, package_dir)\n        if keys_dir is not None:\n            self._export_guardian_private_keys(setup_inputs, keys_dir)\n\n    def _export_context(\n        self,\n        context: CiphertextElectionContext,\n        out_dir: str,\n    ) -> str:\n        return self._export_file(\"Context\", context, out_dir, CONTEXT_FILE_NAME)\n\n    def _export_constants(self, out_dir: str) -> str:\n        constants = get_constants()\n        return self._export_file(\"Constants\", constants, out_dir, CONSTANTS_FILE_NAME)\n\n    def _export_manifest(self, setup_inputs: SetupInputs, out_dir: str) -> None:\n        self._export_file(\n            \"Manifest\",\n            setup_inputs.manifest,\n            out_dir,\n            MANIFEST_FILE_NAME,\n        )\n\n    def _export_guardian_records(self, setup_inputs: SetupInputs, out_dir: str) -> None:\n        guardian_records_dir = join(out_dir, \"guardians\")\n        guardian_records = OutputStepBase._get_guardian_records(setup_inputs)\n        for guardian_record in guardian_records:\n            to_file(\n                guardian_record,\n                GUARDIAN_PREFIX + guardian_record.guardian_id,\n                guardian_records_dir,\n            )\n        self.print_value(\"Guardian records\", guardian_records_dir)\n\n    def _export_guardian_private_keys(self, inputs: SetupInputs, keys_dir: str) -> None:\n        if path.exists(keys_dir) and not inputs.force:\n            confirm = click.confirm(\n                \"Existing guardian keys found, are you sure you want to overwrite them?\",\n                default=True,\n            )\n            if not confirm:\n                return\n        self._export_private_keys(keys_dir, inputs.guardians)\n"
  },
  {
    "path": "src/electionguard_cli/setup_election/setup_election_builder_step.py",
    "content": "from electionguard import ElectionJointKey\n\nfrom .setup_inputs import SetupInputs\nfrom ..cli_models import BuildElectionResults\nfrom ..cli_steps import ElectionBuilderStep\n\n\nclass SetupElectionBuilderStep(ElectionBuilderStep):\n    \"\"\"Responsible for creating a manifest and context for use in an election\n    specifically for the import ballots command\"\"\"\n\n    def build_election_for_setup(\n        self, election_inputs: SetupInputs, joint_key: ElectionJointKey\n    ) -> BuildElectionResults:\n        return self._build_election(\n            election_inputs,\n            joint_key.joint_public_key,\n            joint_key.commitment_hash,\n            election_inputs.verification_url,\n        )\n"
  },
  {
    "path": "src/electionguard_cli/setup_election/setup_election_command.py",
    "content": "from io import TextIOWrapper\nimport click\n\nfrom .setup_election_builder_step import SetupElectionBuilderStep\nfrom .output_setup_files_step import OutputSetupFilesStep\nfrom ..cli_steps import KeyCeremonyStep\nfrom .setup_input_retrieval_step import SetupInputRetrievalStep\n\n\n@click.command(\"setup\")\n@click.option(\n    \"--guardian-count\",\n    prompt=\"Number of guardians\",\n    help=\"The number of guardians that will participate in the key ceremony and tally.\",\n    type=click.INT,\n)\n@click.option(\n    \"--quorum\",\n    prompt=\"Quorum\",\n    help=\"The minimum number of guardians required to show up to the tally.\",\n    type=click.INT,\n)\n@click.option(\n    \"--manifest\",\n    prompt=\"Manifest file\",\n    help=\"The location of an election manifest.\",\n    type=click.File(),\n)\n@click.option(\n    \"--url\",\n    help=\"An optional verification url for the election.\",\n    required=False,\n    type=click.STRING,\n    default=None,\n    prompt=False,\n)\n@click.option(\n    \"--package-dir\",\n    prompt=\"Election Package Output Directory\",\n    help=\"The location of a directory into which will be placed the output files such as \"\n    + \"context, constants, and guardian keys. Existing files will be overwritten.\",\n    type=click.Path(exists=False, dir_okay=True, file_okay=False, resolve_path=True),\n)\n@click.option(\n    \"--keys-dir\",\n    prompt=\"Private guardian keys directory\",\n    help=\"The location of a directory into which will be placed the guardian's private keys \"\n    + \"This folder should be protected. Existing files will be overwritten.\",\n    type=click.Path(exists=False, dir_okay=True, file_okay=False, resolve_path=True),\n)\ndef SetupElectionCommand(\n    guardian_count: int,\n    quorum: int,\n    manifest: TextIOWrapper,\n    url: str,\n    package_dir: str,\n    keys_dir: str,\n) -> None:\n    \"\"\"\n    This command runs an automated key ceremony and produces the files\n    necessary to encrypt ballots, decrypt an election, and produce an election record.\n    \"\"\"\n\n    setup_inputs = SetupInputRetrievalStep().get_inputs(\n        guardian_count, quorum, manifest, url\n    )\n    joint_key = KeyCeremonyStep().run_key_ceremony(setup_inputs.guardians)\n    build_election_results = SetupElectionBuilderStep().build_election_for_setup(\n        setup_inputs, joint_key\n    )\n    OutputSetupFilesStep().output(\n        setup_inputs, build_election_results, package_dir, keys_dir\n    )\n"
  },
  {
    "path": "src/electionguard_cli/setup_election/setup_input_retrieval_step.py",
    "content": "from io import TextIOWrapper\nfrom typing import Optional\nfrom electionguard.key_ceremony import CeremonyDetails\nfrom electionguard.manifest import Manifest\nfrom electionguard_tools.helpers.key_ceremony_orchestrator import (\n    KeyCeremonyOrchestrator,\n)\n\nfrom .setup_inputs import SetupInputs\nfrom ..cli_steps import InputRetrievalStepBase\n\n\nclass SetupInputRetrievalStep(InputRetrievalStepBase):\n    \"\"\"Responsible for retrieving and parsing user provided inputs for the CLI's setup election command.\"\"\"\n\n    def get_inputs(\n        self,\n        guardian_count: int,\n        quorum: int,\n        manifest_file: TextIOWrapper,\n        verification_url: Optional[str],\n    ) -> SetupInputs:\n\n        self.print_header(\"Retrieving Inputs\")\n        guardians = KeyCeremonyOrchestrator.create_guardians(\n            CeremonyDetails(guardian_count, quorum)\n        )\n        manifest: Manifest = self._get_manifest(manifest_file)\n\n        return SetupInputs(\n            guardian_count, quorum, guardians, manifest, verification_url\n        )\n"
  },
  {
    "path": "src/electionguard_cli/setup_election/setup_inputs.py",
    "content": "from typing import List, Optional\n\nfrom electionguard.guardian import Guardian\nfrom electionguard.manifest import Manifest\n\nfrom ..cli_models import CliElectionInputsBase\n\n\nclass SetupInputs(CliElectionInputsBase):\n    \"\"\"Responsible for holding the inputs for the CLI's setup election command.\"\"\"\n\n    verification_url: Optional[str]\n    force: bool\n\n    def __init__(\n        self,\n        guardian_count: int,\n        quorum: int,\n        guardians: List[Guardian],\n        manifest: Manifest,\n        verification_url: Optional[str],\n        force: bool = False,\n    ):\n        self.guardian_count = guardian_count\n        self.quorum = quorum\n        self.guardians = guardians\n        self.manifest = manifest\n        self.verification_url = verification_url\n        self.force = force\n"
  },
  {
    "path": "src/electionguard_cli/start.py",
    "content": "import click\n\nfrom .setup_election.setup_election_command import SetupElectionCommand\nfrom .e2e.e2e_command import E2eCommand\nfrom .import_ballots.import_ballots_command import ImportBallotsCommand\nfrom .encrypt_ballots.encrypt_command import EncryptBallotsCommand\nfrom .mark_ballots.mark_command import MarkBallotsCommand\nfrom .submit_ballots.submit_command import SubmitBallotsCommand\n\n\n@click.group()\ndef cli() -> None:\n    pass\n\n\ncli.add_command(E2eCommand)\ncli.add_command(SetupElectionCommand)\ncli.add_command(MarkBallotsCommand)\ncli.add_command(EncryptBallotsCommand)\ncli.add_command(SubmitBallotsCommand)\ncli.add_command(ImportBallotsCommand)\n"
  },
  {
    "path": "src/electionguard_cli/submit_ballots/__init__.py",
    "content": "from electionguard_cli.submit_ballots import submit_ballot_inputs\nfrom electionguard_cli.submit_ballots import submit_ballots_election_builder_step\nfrom electionguard_cli.submit_ballots import submit_ballots_input_retrieval_step\nfrom electionguard_cli.submit_ballots import submit_ballots_publish_step\nfrom electionguard_cli.submit_ballots import submit_command\n\nfrom electionguard_cli.submit_ballots.submit_ballot_inputs import (\n    SubmitBallotInputs,\n)\nfrom electionguard_cli.submit_ballots.submit_ballots_election_builder_step import (\n    SubmitBallotsElectionBuilderStep,\n)\nfrom electionguard_cli.submit_ballots.submit_ballots_input_retrieval_step import (\n    SubmitBallotsInputRetrievalStep,\n)\nfrom electionguard_cli.submit_ballots.submit_ballots_publish_step import (\n    SubmitBallotsPublishStep,\n)\nfrom electionguard_cli.submit_ballots.submit_command import (\n    SubmitBallotsCommand,\n)\n\n__all__ = [\n    \"SubmitBallotInputs\",\n    \"SubmitBallotsCommand\",\n    \"SubmitBallotsElectionBuilderStep\",\n    \"SubmitBallotsInputRetrievalStep\",\n    \"SubmitBallotsPublishStep\",\n    \"submit_ballot_inputs\",\n    \"submit_ballots_election_builder_step\",\n    \"submit_ballots_input_retrieval_step\",\n    \"submit_ballots_publish_step\",\n    \"submit_command\",\n]\n"
  },
  {
    "path": "src/electionguard_cli/submit_ballots/submit_ballot_inputs.py",
    "content": "from typing import List\n\nfrom electionguard.election import CiphertextElectionContext\nfrom electionguard.manifest import Manifest\nfrom electionguard.ballot import CiphertextBallot\n\nfrom ..cli_models import (\n    CliElectionInputsBase,\n)\n\n\nclass SubmitBallotInputs(CliElectionInputsBase):\n    \"\"\"Responsible for holding the inputs for the CLI's submit ballots command\"\"\"\n\n    def __init__(\n        self,\n        manifest: Manifest,\n        context: CiphertextElectionContext,\n        cast_ballots: List[CiphertextBallot],\n        spoil_ballots: List[CiphertextBallot],\n    ):\n        self.guardian_count = context.number_of_guardians\n        self.quorum = context.quorum\n        self.manifest = manifest\n        self.context = context\n        self.cast_ballots = cast_ballots\n        self.spoil_ballots = spoil_ballots\n\n    context: CiphertextElectionContext\n"
  },
  {
    "path": "src/electionguard_cli/submit_ballots/submit_ballots_election_builder_step.py",
    "content": "from ..cli_models import BuildElectionResults\nfrom ..cli_steps import ElectionBuilderStep\nfrom .submit_ballot_inputs import SubmitBallotInputs\n\n\nclass SubmitBallotsElectionBuilderStep(ElectionBuilderStep):\n    \"\"\"Responsible for creating a manifest and context for use in an election\n    specifically for the submit ballots command\"\"\"\n\n    def build_election_with_context(\n        self, election_inputs: SubmitBallotInputs\n    ) -> BuildElectionResults:\n        verification_url = election_inputs.context.get_extended_data_field(\n            self.VERIFICATION_URL_NAME\n        )\n        return self._build_election(\n            election_inputs,\n            election_inputs.context.elgamal_public_key,\n            election_inputs.context.commitment_hash,\n            verification_url,\n        )\n"
  },
  {
    "path": "src/electionguard_cli/submit_ballots/submit_ballots_input_retrieval_step.py",
    "content": "from io import TextIOWrapper\n\nfrom electionguard.manifest import Manifest\nfrom electionguard.ballot import CiphertextBallot\n\nfrom .submit_ballot_inputs import SubmitBallotInputs\nfrom ..cli_steps import (\n    InputRetrievalStepBase,\n)\n\n\nclass SubmitBallotsInputRetrievalStep(InputRetrievalStepBase):\n    \"\"\"Responsible for retrieving and parsing user provided inputs for the CLI's submit ballots command.\"\"\"\n\n    def get_inputs(\n        self,\n        manifest_file: TextIOWrapper,\n        context_file: TextIOWrapper,\n        cast_ballots_dir: str,\n        spoil_ballots_dir: str,\n    ) -> SubmitBallotInputs:\n\n        self.print_header(\"Retrieving Inputs\")\n        manifest: Manifest = self._get_manifest(manifest_file)\n        context = InputRetrievalStepBase._get_context(context_file)\n        cast_ballots = self._get_ballots(cast_ballots_dir, CiphertextBallot)\n\n        if spoil_ballots_dir is not None:\n            spoil_ballots = self._get_ballots(spoil_ballots_dir, CiphertextBallot)\n        else:\n            spoil_ballots = []\n\n        return SubmitBallotInputs(\n            manifest,\n            context,\n            cast_ballots,\n            spoil_ballots,\n        )\n"
  },
  {
    "path": "src/electionguard_cli/submit_ballots/submit_ballots_publish_step.py",
    "content": "from click import echo\n\nfrom electionguard import to_file\n\nfrom ..cli_models import SubmitResults\nfrom ..cli_steps import OutputStepBase\n\n\nclass SubmitBallotsPublishStep(OutputStepBase):\n    \"\"\"Responsible for writing the results of the submit ballots command.\"\"\"\n\n    def publish(self, submit_results: SubmitResults, out_dir: str) -> None:\n        if out_dir is None:\n            return\n        self.print_header(\"Writing Submitted Ballots\")\n        for ballot in submit_results.submitted_ballots:\n            ballot_file = to_file(ballot, ballot.object_id, out_dir)\n            echo(f\"Writing {ballot_file}\")\n        self.print_value(\"Submitted ballots\", len(submit_results.submitted_ballots))\n"
  },
  {
    "path": "src/electionguard_cli/submit_ballots/submit_command.py",
    "content": "from io import TextIOWrapper\nimport click\n\n\nfrom .submit_ballots_election_builder_step import SubmitBallotsElectionBuilderStep\nfrom .submit_ballots_input_retrieval_step import SubmitBallotsInputRetrievalStep\nfrom .submit_ballots_publish_step import SubmitBallotsPublishStep\nfrom ..cli_steps import SubmitBallotsStep\n\n\n@click.command(\"submit-ballots\")\n@click.argument(\n    \"cast_ballots_dir\",\n    type=click.Path(exists=False, dir_okay=True, file_okay=False, resolve_path=True),\n)\n@click.argument(\n    \"spoil_ballots_dir\",\n    type=click.Path(exists=False, dir_okay=True, file_okay=False, resolve_path=True),\n    required=False,\n)\n@click.option(\n    \"--manifest\",\n    prompt=\"Manifest file\",\n    help=\"The location of an election manifest.\",\n    type=click.File(),\n)\n@click.option(\n    \"--context\",\n    prompt=\"Context file\",\n    help=\"The location of an election context.\",\n    type=click.File(),\n)\n@click.option(\n    \"--out-dir\",\n    help=\"A directory for saving plaintext ballots to.\",\n    type=click.Path(exists=False, dir_okay=True, file_okay=False, resolve_path=True),\n)\ndef SubmitBallotsCommand(\n    cast_ballots_dir: str,\n    spoil_ballots_dir: str,\n    manifest: TextIOWrapper,\n    context: TextIOWrapper,\n    out_dir: str,\n) -> None:\n    \"\"\"\n    Submits ballots\n    \"\"\"\n\n    election_inputs = SubmitBallotsInputRetrievalStep().get_inputs(\n        manifest, context, cast_ballots_dir, spoil_ballots_dir\n    )\n    build_election_results = (\n        SubmitBallotsElectionBuilderStep().build_election_with_context(election_inputs)\n    )\n    submitted_ballots = SubmitBallotsStep().submit(\n        build_election_results,\n        election_inputs.cast_ballots,\n        election_inputs.spoil_ballots,\n    )\n    SubmitBallotsPublishStep().publish(submitted_ballots, out_dir)\n"
  },
  {
    "path": "src/electionguard_db/docker-compose.db.yml",
    "content": "version: \"3.8\"\nservices:\n  mongo:\n    image: mongo:4.4\n    container_name: \"electionguard-db\"\n    restart: always\n    ports:\n      - 27017:27017\n    environment:\n      MONGO_INITDB_ROOT_USERNAME: root\n      MONGO_INITDB_ROOT_PASSWORD: ${EG_DB_PASSWORD}\n      MONGO_INITDB_DATABASE: ElectionGuardDb\n    volumes:\n      - ./mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro\n      - ${EG_DB_DIR}:/data/db\n\n  mongo-express:\n    image: mongo-express:1.0.0-alpha.4\n    restart: always\n    ports:\n      - 8181:8081\n    environment:\n      ME_CONFIG_MONGODB_SERVER: electionguard-db\n      ME_CONFIG_MONGODB_ADMINUSERNAME: root\n      ME_CONFIG_MONGODB_ADMINPASSWORD: ${EG_DB_PASSWORD}\n    depends_on:\n      - mongo\n    volumes:\n      - ${EG_DB_DIR}:/data/db\n"
  },
  {
    "path": "src/electionguard_db/mongo-init.js",
    "content": "db.createCollection(\"guardians\");\ndb.createCollection(\"key_ceremonies\");\ndb.createCollection(\"elections\");\ndb.createCollection(\"ballot_uploads\");\ndb.createCollection(\"decryptions\");\ndb.createCollection(\"db_deltas\", { capped: true, size: 100000 });\ndb.db_deltas.insert({ type: \"init\" });\ndb.ballot_uploads.createIndex({ election_id: 1 });\ndb.ballot_uploads.createIndex({ election_id: 1, object_id: 1 });\ndb.decryptions.createIndex({ decryption_name: 1 });\ndb.decryptions.createIndex({ election_id: 1 });\ndb.decryptions.createIndex({ completed_at: 1 });\ndb.key_ceremonies.createIndex({ completed_at: 1 });\ndb.key_ceremonies.createIndex({ key_ceremony_name: 1 });\n"
  },
  {
    "path": "src/electionguard_gui/.dockerignore",
    "content": "docker-compose*.yml\nDockerfile\n"
  },
  {
    "path": "src/electionguard_gui/Dockerfile",
    "content": "FROM ubuntu:22.04\n\n##################################################################################\n# Install pyenv (https://github.com/pyenv/pyenv/wiki#suggested-build-environment)\n##################################################################################\n\nRUN echo \"installing pyenv\"\nRUN apt-get update\n# install tzdata to remove \"Please select the geographic area in which you live\" when installing pyenv dependencies\nRUN apt-get install -y tzdata && \\\n    apt-get install -y make build-essential libssl-dev zlib1g-dev \\\n        libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm \\\n        libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev\n\nRUN apt-get install -y git apt-utils\n\n# install pyenv\nRUN curl https://pyenv.run | bash\n\n# add pyenv to path\nRUN echo 'export PYENV_ROOT=\"$HOME/.pyenv\"' >> ~/.bashrc && \\\n    echo 'command -v pyenv >/dev/null || export PATH=\"$PYENV_ROOT/bin:$PATH\"' >> ~/.bashrc && \\\n    echo 'eval \"$(pyenv init -)\"' >> ~/.bashrc && \\\n    echo 'export PYENV_ROOT=\"$HOME/.pyenv\"' >> ~/.profile && \\\n    echo 'command -v pyenv >/dev/null || export PATH=\"$PYENV_ROOT/bin:$PATH\"' >> ~/.profile && \\\n    echo 'eval \"$(pyenv init -)\"' >> ~/.profile\n\nENV PYENV_ROOT=\"/root/.pyenv\"\nENV PATH=\"$PYENV_ROOT/bin:$PYENV_ROOT/shims:${PATH}\"\nRUN echo $PATH\n\n##################################################################################\n# Install python\n##################################################################################\n\nRUN pyenv install 3.9.9 && \\\n    pyenv global 3.9.9\n\n##################################################################################\n# Install EG prerequisites\n##################################################################################\n\nRUN apt-get install -y libgmp-dev libmpfr-dev libmpc-dev\nRUN pip install 'poetry==2.2.1'\n\n##################################################################################\n# Poetry Install\n##################################################################################\n\nRUN mkdir /app\nWORKDIR /app\n# --no-root allows us to copy the minimum to just get dependencies to give\n#       Docker very few reasons to invalidate the poetry install layer\nCOPY pyproject.toml README.md ./\nRUN poetry config virtualenvs.in-project true && \\\n\tpoetry install --no-root\n\n##################################################################################\n# Get Source\n##################################################################################\n\n# cleanup first, the next layer will get invalidated easiliy\nRUN apt-get clean && \\\n    rm -rf /var/lib/apt/lists/*\n\nCOPY ./src ./src\nRUN rm src/electionguard_gui/__init__.py\n\n##################################################################################\n# Run EGUI\n##################################################################################\n\n# the final poetry install runs fast, it activates the virtualenv and initializes the modules\nRUN poetry install\n\nENTRYPOINT [\"poetry\", \"run\", \"egui\"]\n# alternately, for testing:\n# ENTRYPOINT [\"tail\", \"-f\", \"/dev/null\"]\n"
  },
  {
    "path": "src/electionguard_gui/__init__.py",
    "content": "from electionguard_gui import components\nfrom electionguard_gui import containers\nfrom electionguard_gui import eel_utils\nfrom electionguard_gui import main_app\nfrom electionguard_gui import models\nfrom electionguard_gui import services\nfrom electionguard_gui import start\n\nfrom electionguard_gui.components import (\n    ComponentBase,\n    CreateDecryptionComponent,\n    CreateElectionComponent,\n    CreateKeyCeremonyComponent,\n    ElectionListComponent,\n    ExportElectionRecordComponent,\n    ExportEncryptionPackageComponent,\n    GuardianHomeComponent,\n    KeyCeremonyDetailsComponent,\n    UploadBallotsComponent,\n    ViewDecryptionComponent,\n    ViewElectionComponent,\n    ViewSpoiledBallotComponent,\n    ViewTallyComponent,\n    component_base,\n    create_decryption_component,\n    create_election_component,\n    create_key_ceremony_component,\n    election_list_component,\n    export_election_record_component,\n    export_encryption_package_component,\n    get_spoiled_ballot_by_id,\n    guardian_home_component,\n    key_ceremony_details_component,\n    notify_ui_db_changed,\n    refresh_decryption,\n    update_upload_status,\n    upload_ballots_component,\n    view_decryption_component,\n    view_election_component,\n    view_spoiled_ballot_component,\n    view_tally_component,\n)\nfrom electionguard_gui.containers import (\n    Container,\n)\nfrom electionguard_gui.eel_utils import (\n    convert_utc_to_local,\n    eel_fail,\n    eel_success,\n    utc_to_str,\n)\nfrom electionguard_gui.main_app import (\n    MainApp,\n)\nfrom electionguard_gui.models import (\n    DecryptionDto,\n    ElectionDto,\n    GuardianDecryptionShare,\n    KeyCeremonyDto,\n    KeyCeremonyStates,\n    decryption_dto,\n    election_dto,\n    key_ceremony_dto,\n    key_ceremony_states,\n)\nfrom electionguard_gui.services import (\n    AuthorizationService,\n    BallotUploadService,\n    ConfigurationService,\n    DB_HOST_KEY,\n    DB_PASSWORD_KEY,\n    DOCKER_MOUNT_DIR,\n    DbService,\n    DbWatcherService,\n    DecryptionS1JoinService,\n    DecryptionS2AnnounceService,\n    DecryptionService,\n    DecryptionStageBase,\n    EelLogService,\n    ElectionService,\n    GuardianService,\n    GuiSetupInputRetrievalStep,\n    HOST_KEY,\n    IS_ADMIN_KEY,\n    KeyCeremonyS1JoinService,\n    KeyCeremonyS2AnnounceService,\n    KeyCeremonyS3MakeBackupService,\n    KeyCeremonyS4ShareBackupService,\n    KeyCeremonyS5VerifyBackupService,\n    KeyCeremonyS6PublishKeyService,\n    KeyCeremonyService,\n    KeyCeremonyStageBase,\n    KeyCeremonyStateService,\n    MODE_KEY,\n    PORT_KEY,\n    RetryException,\n    ServiceBase,\n    VersionService,\n    announce_guardians,\n    authorization_service,\n    backup_to_dict,\n    ballot_upload_service,\n    configuration_service,\n    db_serialization_service,\n    db_service,\n    db_watcher_service,\n    decryption_s1_join_service,\n    decryption_s2_announce_service,\n    decryption_service,\n    decryption_stage_base,\n    decryption_stages,\n    directory_service,\n    eel_log_service,\n    election_service,\n    export_service,\n    get_data_dir,\n    get_export_dir,\n    get_export_locations,\n    get_guardian_number,\n    get_key_ceremony_status,\n    get_plaintext_ballot_report,\n    get_removable_drives,\n    get_tally,\n    guardian_service,\n    gui_setup_input_retrieval_step,\n    joint_key_to_dict,\n    key_ceremony_s1_join_service,\n    key_ceremony_s2_announce_service,\n    key_ceremony_s3_make_backup_service,\n    key_ceremony_s4_share_backup_service,\n    key_ceremony_s5_verify_backup_service,\n    key_ceremony_s6_publish_key_service,\n    key_ceremony_service,\n    key_ceremony_stage_base,\n    key_ceremony_stages,\n    key_ceremony_state_service,\n    make_guardian,\n    make_mediator,\n    plaintext_ballot_service,\n    public_key_to_dict,\n    service_base,\n    status_descriptions,\n    to_ballot_share_raw,\n    verification_to_dict,\n    version_service,\n)\nfrom electionguard_gui.start import (\n    run,\n)\n\n__all__ = [\n    \"AuthorizationService\",\n    \"BallotUploadService\",\n    \"ComponentBase\",\n    \"ConfigurationService\",\n    \"Container\",\n    \"CreateDecryptionComponent\",\n    \"CreateElectionComponent\",\n    \"CreateKeyCeremonyComponent\",\n    \"DB_HOST_KEY\",\n    \"DB_PASSWORD_KEY\",\n    \"DOCKER_MOUNT_DIR\",\n    \"DbService\",\n    \"DbWatcherService\",\n    \"DecryptionDto\",\n    \"DecryptionS1JoinService\",\n    \"DecryptionS2AnnounceService\",\n    \"DecryptionService\",\n    \"DecryptionStageBase\",\n    \"EelLogService\",\n    \"ElectionDto\",\n    \"ElectionListComponent\",\n    \"ElectionService\",\n    \"ExportElectionRecordComponent\",\n    \"ExportEncryptionPackageComponent\",\n    \"GuardianDecryptionShare\",\n    \"GuardianHomeComponent\",\n    \"GuardianService\",\n    \"GuiSetupInputRetrievalStep\",\n    \"HOST_KEY\",\n    \"IS_ADMIN_KEY\",\n    \"KeyCeremonyDetailsComponent\",\n    \"KeyCeremonyDto\",\n    \"KeyCeremonyS1JoinService\",\n    \"KeyCeremonyS2AnnounceService\",\n    \"KeyCeremonyS3MakeBackupService\",\n    \"KeyCeremonyS4ShareBackupService\",\n    \"KeyCeremonyS5VerifyBackupService\",\n    \"KeyCeremonyS6PublishKeyService\",\n    \"KeyCeremonyService\",\n    \"KeyCeremonyStageBase\",\n    \"KeyCeremonyStateService\",\n    \"KeyCeremonyStates\",\n    \"MODE_KEY\",\n    \"MainApp\",\n    \"PORT_KEY\",\n    \"RetryException\",\n    \"ServiceBase\",\n    \"UploadBallotsComponent\",\n    \"VersionService\",\n    \"ViewDecryptionComponent\",\n    \"ViewElectionComponent\",\n    \"ViewSpoiledBallotComponent\",\n    \"ViewTallyComponent\",\n    \"announce_guardians\",\n    \"authorization_service\",\n    \"backup_to_dict\",\n    \"ballot_upload_service\",\n    \"component_base\",\n    \"components\",\n    \"configuration_service\",\n    \"containers\",\n    \"convert_utc_to_local\",\n    \"create_decryption_component\",\n    \"create_election_component\",\n    \"create_key_ceremony_component\",\n    \"db_serialization_service\",\n    \"db_service\",\n    \"db_watcher_service\",\n    \"decryption_dto\",\n    \"decryption_s1_join_service\",\n    \"decryption_s2_announce_service\",\n    \"decryption_service\",\n    \"decryption_stage_base\",\n    \"decryption_stages\",\n    \"directory_service\",\n    \"eel_fail\",\n    \"eel_log_service\",\n    \"eel_success\",\n    \"eel_utils\",\n    \"election_dto\",\n    \"election_list_component\",\n    \"election_service\",\n    \"export_election_record_component\",\n    \"export_encryption_package_component\",\n    \"export_service\",\n    \"get_data_dir\",\n    \"get_export_dir\",\n    \"get_export_locations\",\n    \"get_guardian_number\",\n    \"get_key_ceremony_status\",\n    \"get_plaintext_ballot_report\",\n    \"get_removable_drives\",\n    \"get_spoiled_ballot_by_id\",\n    \"get_tally\",\n    \"guardian_home_component\",\n    \"guardian_service\",\n    \"gui_setup_input_retrieval_step\",\n    \"joint_key_to_dict\",\n    \"key_ceremony_details_component\",\n    \"key_ceremony_dto\",\n    \"key_ceremony_s1_join_service\",\n    \"key_ceremony_s2_announce_service\",\n    \"key_ceremony_s3_make_backup_service\",\n    \"key_ceremony_s4_share_backup_service\",\n    \"key_ceremony_s5_verify_backup_service\",\n    \"key_ceremony_s6_publish_key_service\",\n    \"key_ceremony_service\",\n    \"key_ceremony_stage_base\",\n    \"key_ceremony_stages\",\n    \"key_ceremony_state_service\",\n    \"key_ceremony_states\",\n    \"main_app\",\n    \"make_guardian\",\n    \"make_mediator\",\n    \"models\",\n    \"notify_ui_db_changed\",\n    \"plaintext_ballot_service\",\n    \"public_key_to_dict\",\n    \"refresh_decryption\",\n    \"run\",\n    \"service_base\",\n    \"services\",\n    \"start\",\n    \"status_descriptions\",\n    \"to_ballot_share_raw\",\n    \"update_upload_status\",\n    \"upload_ballots_component\",\n    \"utc_to_str\",\n    \"verification_to_dict\",\n    \"version_service\",\n    \"view_decryption_component\",\n    \"view_election_component\",\n    \"view_spoiled_ballot_component\",\n    \"view_tally_component\",\n]\n"
  },
  {
    "path": "src/electionguard_gui/components/__init__.py",
    "content": "from electionguard_gui.components import component_base\nfrom electionguard_gui.components import create_decryption_component\nfrom electionguard_gui.components import create_election_component\nfrom electionguard_gui.components import create_key_ceremony_component\nfrom electionguard_gui.components import election_list_component\nfrom electionguard_gui.components import export_election_record_component\nfrom electionguard_gui.components import export_encryption_package_component\nfrom electionguard_gui.components import guardian_home_component\nfrom electionguard_gui.components import key_ceremony_details_component\nfrom electionguard_gui.components import upload_ballots_component\nfrom electionguard_gui.components import view_decryption_component\nfrom electionguard_gui.components import view_election_component\nfrom electionguard_gui.components import view_spoiled_ballot_component\nfrom electionguard_gui.components import view_tally_component\n\nfrom electionguard_gui.components.component_base import (\n    ComponentBase,\n)\nfrom electionguard_gui.components.create_decryption_component import (\n    CreateDecryptionComponent,\n)\nfrom electionguard_gui.components.create_election_component import (\n    CreateElectionComponent,\n)\nfrom electionguard_gui.components.create_key_ceremony_component import (\n    CreateKeyCeremonyComponent,\n)\nfrom electionguard_gui.components.election_list_component import (\n    ElectionListComponent,\n)\nfrom electionguard_gui.components.export_election_record_component import (\n    ExportElectionRecordComponent,\n)\nfrom electionguard_gui.components.export_encryption_package_component import (\n    ExportEncryptionPackageComponent,\n)\nfrom electionguard_gui.components.guardian_home_component import (\n    GuardianHomeComponent,\n    notify_ui_db_changed,\n)\nfrom electionguard_gui.components.key_ceremony_details_component import (\n    KeyCeremonyDetailsComponent,\n)\nfrom electionguard_gui.components.upload_ballots_component import (\n    UploadBallotsComponent,\n    update_upload_status,\n)\nfrom electionguard_gui.components.view_decryption_component import (\n    ViewDecryptionComponent,\n    refresh_decryption,\n)\nfrom electionguard_gui.components.view_election_component import (\n    ViewElectionComponent,\n)\nfrom electionguard_gui.components.view_spoiled_ballot_component import (\n    ViewSpoiledBallotComponent,\n    get_spoiled_ballot_by_id,\n)\nfrom electionguard_gui.components.view_tally_component import (\n    ViewTallyComponent,\n)\n\n__all__ = [\n    \"ComponentBase\",\n    \"CreateDecryptionComponent\",\n    \"CreateElectionComponent\",\n    \"CreateKeyCeremonyComponent\",\n    \"ElectionListComponent\",\n    \"ExportElectionRecordComponent\",\n    \"ExportEncryptionPackageComponent\",\n    \"GuardianHomeComponent\",\n    \"KeyCeremonyDetailsComponent\",\n    \"UploadBallotsComponent\",\n    \"ViewDecryptionComponent\",\n    \"ViewElectionComponent\",\n    \"ViewSpoiledBallotComponent\",\n    \"ViewTallyComponent\",\n    \"component_base\",\n    \"create_decryption_component\",\n    \"create_election_component\",\n    \"create_key_ceremony_component\",\n    \"election_list_component\",\n    \"export_election_record_component\",\n    \"export_encryption_package_component\",\n    \"get_spoiled_ballot_by_id\",\n    \"guardian_home_component\",\n    \"key_ceremony_details_component\",\n    \"notify_ui_db_changed\",\n    \"refresh_decryption\",\n    \"update_upload_status\",\n    \"upload_ballots_component\",\n    \"view_decryption_component\",\n    \"view_election_component\",\n    \"view_spoiled_ballot_component\",\n    \"view_tally_component\",\n]\n"
  },
  {
    "path": "src/electionguard_gui/components/component_base.py",
    "content": "from abc import ABC\nimport traceback\nfrom typing import Any\nfrom electionguard_gui.eel_utils import eel_fail\n\nfrom electionguard_gui.services.db_service import DbService\nfrom electionguard_gui.services.eel_log_service import EelLogService\n\n\nclass ComponentBase(ABC):\n    \"\"\"Responsible for common functionality among ell components\"\"\"\n\n    _db_service: DbService\n    _log: EelLogService\n\n    def init(\n        self,\n        db_service: DbService,\n        log_service: EelLogService,\n    ) -> None:\n        self._db_service = db_service\n        self._log = log_service\n        self.expose()\n\n    def expose(self) -> None:\n        \"\"\"Override to expose the component's methods to JavaScript. This technique hides the\n        fact that method names exposed must be globally unique.\"\"\"\n\n    def handle_error(self, error: Exception) -> dict[str, Any]:\n        self._log.error(\"error in component_base\", error)\n        traceback.print_exc()\n        return eel_fail(str(error))\n"
  },
  {
    "path": "src/electionguard_gui/components/create_decryption_component.py",
    "content": "from typing import Any\nimport eel\nfrom electionguard_gui.eel_utils import eel_fail, eel_success\nfrom electionguard_gui.components.component_base import ComponentBase\nfrom electionguard_gui.models.election_dto import ElectionDto\nfrom electionguard_gui.services import ElectionService, DecryptionService\n\n\nclass CreateDecryptionComponent(ComponentBase):\n    \"\"\"Responsible for functionality related to creating decryptions for an election\"\"\"\n\n    _decryption_service: DecryptionService\n    _election_service: ElectionService\n\n    def __init__(\n        self,\n        decryption_service: DecryptionService,\n        election_service: ElectionService,\n    ) -> None:\n        self._decryption_service = decryption_service\n        self._election_service = election_service\n\n    def expose(self) -> None:\n        eel.expose(self.create_decryption)\n        eel.expose(self.get_suggested_decryption_name)\n\n    def get_suggested_decryption_name(self, election_id: str) -> dict[str, Any]:\n        db = self._db_service.get_db()\n        election: ElectionDto = self._election_service.get(db, election_id)\n        existing_decryptions = self._decryption_service.get_decryption_count(\n            db, election_id\n        )\n        return eel_success(\n            f\"{election.election_name} Tally #{existing_decryptions + 1}\"\n        )\n\n    def create_decryption(\n        self, election_id: str, decryption_name: str\n    ) -> dict[str, Any]:\n        try:\n            self._log.debug(\n                f\"Creating decryption for election: {election_id} with name: {decryption_name}\"\n            )\n            db = self._db_service.get_db()\n            election = self._election_service.get(db, election_id)\n            if election is None:\n                return eel_fail(f\"Election {election_id} not found\")\n            name_exists = self._decryption_service.name_exists(db, decryption_name)\n            if name_exists:\n                return eel_fail(f\"Decryption '{decryption_name}' already exists\")\n            decryption_id = self._decryption_service.create(\n                db, election, decryption_name\n            )\n            self._election_service.append_decryption(\n                db, election_id, decryption_id, decryption_name\n            )\n            return eel_success(decryption_id)\n        # pylint: disable=broad-except\n        except Exception as e:\n            return self.handle_error(e)\n"
  },
  {
    "path": "src/electionguard_gui/components/create_election_component.py",
    "content": "from os import path\nfrom shutil import make_archive, rmtree\nfrom typing import Any\nimport eel\nfrom electionguard.constants import get_constants\nfrom electionguard.guardian import Guardian\nfrom electionguard_cli.setup_election.output_setup_files_step import (\n    OutputSetupFilesStep,\n)\nfrom electionguard_cli.setup_election.setup_election_builder_step import (\n    SetupElectionBuilderStep,\n)\nfrom electionguard_gui.eel_utils import eel_fail, eel_success\nfrom electionguard_gui.components.component_base import ComponentBase\nfrom electionguard_gui.services import (\n    KeyCeremonyService,\n    GuiSetupInputRetrievalStep,\n    ElectionService,\n    GuardianService,\n)\nfrom electionguard_gui.services.directory_service import get_data_dir\n\n\nclass CreateElectionComponent(ComponentBase):\n    \"\"\"Responsible for functionality related to creating encryption packages for elections\"\"\"\n\n    _COMPRESSION_FORMAT = \"zip\"\n\n    _key_ceremony_service: KeyCeremonyService\n    _setup_input_retrieval_step: GuiSetupInputRetrievalStep\n    _setup_election_builder_step: SetupElectionBuilderStep\n    _election_service: ElectionService\n    _output_setup_files_step: OutputSetupFilesStep\n    _guardian_service: GuardianService\n\n    def __init__(\n        self,\n        key_ceremony_service: KeyCeremonyService,\n        election_service: ElectionService,\n        setup_input_retrieval_step: GuiSetupInputRetrievalStep,\n        setup_election_builder_step: SetupElectionBuilderStep,\n        output_setup_files_step: OutputSetupFilesStep,\n        guardian_service: GuardianService,\n    ) -> None:\n        self._key_ceremony_service = key_ceremony_service\n        self._setup_input_retrieval_step = setup_input_retrieval_step\n        self._setup_election_builder_step = setup_election_builder_step\n        self._election_service = election_service\n        self._output_setup_files_step = output_setup_files_step\n        self._guardian_service = guardian_service\n\n    def expose(self) -> None:\n        eel.expose(self.get_keys)\n        eel.expose(self.create_election)\n\n    def get_keys(self) -> dict[str, Any]:\n        self._log.debug(\"Getting keys\")\n        db = self._db_service.get_db()\n        completed_key_ceremonies = self._key_ceremony_service.get_completed(db)\n        keys = [\n            key_ceremony.to_id_name_dict() for key_ceremony in completed_key_ceremonies\n        ]\n        return eel_success(keys)\n\n    def create_election(\n        self, key_ceremony_id: str, election_name: str, manifest_raw: str, url: str\n    ) -> dict[str, Any]:\n        try:\n            self._log.debug(\n                f\"Creating election key_ceremony_id: {key_ceremony_id}, \"\n                + f\"election_name: {election_name}, \"\n                + f\"url: {url}\"\n            )\n            db = self._db_service.get_db()\n            existing_elections = db.elections.find_one({\"election_name\": election_name})\n            if existing_elections:\n                fail_result: dict[str, Any] = eel_fail(\"Election already exists\")\n                return fail_result\n\n            key_ceremony = self._key_ceremony_service.get(db, key_ceremony_id)\n\n            guardians = [\n                Guardian.from_public_key(\n                    key_ceremony.guardian_count, key_ceremony.quorum, key\n                )\n                for key in key_ceremony.keys\n            ]\n            election_inputs = self._setup_input_retrieval_step.get_gui_inputs(\n                key_ceremony.guardian_count,\n                key_ceremony.quorum,\n                guardians,\n                url,\n                manifest_raw,\n            )\n            joint_key = key_ceremony.get_joint_key()\n            build_election_results = (\n                self._setup_election_builder_step.build_election_for_setup(\n                    election_inputs, joint_key\n                )\n            )\n\n            temp_out_dir = path.join(get_data_dir(), \"election_setup\")\n            self._output_setup_files_step.output(\n                election_inputs, build_election_results, temp_out_dir, None\n            )\n            zip_file = path.join(\n                get_data_dir(),\n                \"encryption_packages\",\n                key_ceremony_id,\n                \"public_encryption_package\",\n            )\n            encryption_package_file = self._zip(temp_out_dir, zip_file)\n            guardian_records = [\n                guardian.publish() for guardian in election_inputs.guardians\n            ]\n            constants = get_constants()\n            election_id = self._election_service.create_election(\n                db,\n                election_name,\n                key_ceremony,\n                election_inputs.manifest,\n                build_election_results.context,\n                constants,\n                guardian_records,\n                encryption_package_file,\n                url,\n            )\n            return eel_success(election_id)\n        # pylint: disable=broad-except\n        except Exception as e:\n            return self.handle_error(e)\n\n    def _zip(self, dir_to_zip: str, zip_file_to_make: str) -> str:\n        make_archive(zip_file_to_make, self._COMPRESSION_FORMAT, dir_to_zip)\n        rmtree(dir_to_zip)\n        self._log.debug(f\"Temp zip file: {zip_file_to_make}.{self._COMPRESSION_FORMAT}\")\n        return f\"{zip_file_to_make}.{self._COMPRESSION_FORMAT}\"\n"
  },
  {
    "path": "src/electionguard_gui/components/create_key_ceremony_component.py",
    "content": "from typing import Any\nimport eel\nfrom electionguard_gui.components.component_base import ComponentBase\n\nfrom electionguard_gui.eel_utils import eel_fail, eel_success\nfrom electionguard_gui.services.authorization_service import AuthorizationService\nfrom electionguard_gui.services.key_ceremony_service import KeyCeremonyService\n\n\nclass CreateKeyCeremonyComponent(ComponentBase):\n    \"\"\"Responsible for functionality related to creating key ceremonies\"\"\"\n\n    _key_ceremony_service: KeyCeremonyService\n    _auth_service: AuthorizationService\n\n    def __init__(\n        self,\n        key_ceremony_service: KeyCeremonyService,\n        auth_service: AuthorizationService,\n    ) -> None:\n        super().__init__()\n        self._key_ceremony_service = key_ceremony_service\n        self._auth_service = auth_service\n\n    def expose(self) -> None:\n        eel.expose(self.create_key_ceremony)\n\n    def create_key_ceremony(\n        self, key_ceremony_name: str, guardian_count: int, quorum: int\n    ) -> dict[str, Any]:\n        if guardian_count < quorum:\n            result: dict[str, Any] = eel_fail(\n                \"Guardian count must be greater than or equal to quorum\"\n            )\n            return result\n\n        self._log.debug(\n            \"Starting ceremony: \"\n            + f\"key_ceremony_name: {key_ceremony_name}, \"\n            + f\"guardian_count: {guardian_count}, \"\n            + f\"quorum: {quorum}\"\n        )\n        db = self._db_service.get_db()\n        existing_key_ceremonies = self._key_ceremony_service.exists(\n            db, key_ceremony_name\n        )\n        if existing_key_ceremonies:\n            self._log.debug(f\"record '{key_ceremony_name}' already exists\")\n            fail_result: dict[str, Any] = eel_fail(\"Key ceremony name already exists\")\n            return fail_result\n        inserted_id = self._key_ceremony_service.create(\n            db, key_ceremony_name, guardian_count, quorum\n        )\n        self._key_ceremony_service.notify_changed(db, inserted_id)\n        result = eel_success(str(inserted_id))\n        return result\n"
  },
  {
    "path": "src/electionguard_gui/components/election_list_component.py",
    "content": "from typing import Any\nimport eel\nfrom electionguard_gui.components.component_base import ComponentBase\nfrom electionguard_gui.eel_utils import eel_success\nfrom electionguard_gui.services import ElectionService\n\n\nclass ElectionListComponent(ComponentBase):\n    \"\"\"Responsible for displaying multiple elections\"\"\"\n\n    _election_service: ElectionService\n\n    def __init__(self, election_service: ElectionService) -> None:\n        self._election_service = election_service\n\n    def expose(self) -> None:\n        eel.expose(self.get_elections)\n\n    def get_elections(self) -> dict[str, Any]:\n        db = self._db_service.get_db()\n        elections = self._election_service.get_all(db)\n        elections_list = [election.to_id_name_dict() for election in elections]\n        return eel_success(elections_list)\n"
  },
  {
    "path": "src/electionguard_gui/components/export_election_record_component.py",
    "content": "import os\nfrom tempfile import TemporaryDirectory\nfrom os.path import splitext\nfrom typing import Any\nfrom shutil import make_archive\nimport eel\nfrom electionguard.constants import get_constants\nfrom electionguard_gui.eel_utils import eel_success\nfrom electionguard_gui.components.component_base import ComponentBase\nfrom electionguard_gui.services import (\n    ElectionService,\n    DecryptionService,\n    BallotUploadService,\n)\nfrom electionguard_gui.services.export_service import get_export_locations\nfrom electionguard_tools.helpers.export import export_record\n\n\nclass ExportElectionRecordComponent(ComponentBase):\n    \"\"\"Responsible for exporting an election record for an election\"\"\"\n\n    _COMPRESSION_FORMAT = \"zip\"\n\n    _election_service: ElectionService\n    _decryption_service: DecryptionService\n    _ballot_upload_service: BallotUploadService\n\n    def __init__(\n        self,\n        election_service: ElectionService,\n        decryption_service: DecryptionService,\n        ballot_upload_service: BallotUploadService,\n    ) -> None:\n        self._election_service = election_service\n        self._decryption_service = decryption_service\n        self._ballot_upload_service = ballot_upload_service\n\n    def expose(self) -> None:\n        eel.expose(self.export_election_record)\n        eel.expose(self.get_election_record_export_locations)\n\n    def get_election_record_export_locations(self) -> dict[str, Any]:\n        self._log.trace(\"getting export locations\")\n        export_locations = get_export_locations()\n        locations = [\n            os.path.join(location, \"publish-election.zip\")\n            for location in export_locations\n        ]\n        return eel_success(locations)\n\n    def export_election_record(\n        self, decryption_id: str, location: str\n    ) -> dict[str, Any]:\n        db = self._db_service.get_db()\n        self._log.debug(f\"exporting election record {decryption_id} to {location}\")\n        decryption = self._decryption_service.get(db, decryption_id)\n        election = self._election_service.get(db, decryption.election_id)\n        context = election.get_context()\n        manifest = election.get_manifest()\n        constants = get_constants()\n        encryption_devices = election.get_encryption_devices()\n        submitted_ballots = self._ballot_upload_service.get_ballots(\n            db, election.id, lambda x: None\n        )\n        plaintext_tally = decryption.get_plaintext_tally()\n        spoiled_ballots = decryption.get_plaintext_spoiled_ballots()\n        lagrange_coefficients = decryption.get_lagrange_coefficients()\n        ciphertext_tally = decryption.get_ciphertext_tally()\n        guardian_records = election.get_guardian_records()\n        with TemporaryDirectory() as temp_dir:\n            export_record(\n                manifest,\n                context,\n                constants,\n                encryption_devices,\n                submitted_ballots,\n                spoiled_ballots,\n                ciphertext_tally,\n                plaintext_tally,\n                guardian_records,\n                lagrange_coefficients,\n                election_record_directory=temp_dir,\n            )\n            file_name = splitext(location)[0]\n            make_archive(file_name, self._COMPRESSION_FORMAT, temp_dir)\n\n        return eel_success()\n"
  },
  {
    "path": "src/electionguard_gui/components/export_encryption_package_component.py",
    "content": "import os\nfrom typing import Any\nfrom shutil import unpack_archive\nimport eel\nfrom electionguard_gui.eel_utils import eel_success\nfrom electionguard_gui.components.component_base import ComponentBase\nfrom electionguard_gui.services import ElectionService\nfrom electionguard_gui.services.export_service import get_export_locations\n\n\nclass ExportEncryptionPackageComponent(ComponentBase):\n    \"\"\"Responsible for exporting an encryption package for an election\"\"\"\n\n    _election_service: ElectionService\n\n    def __init__(self, election_service: ElectionService) -> None:\n        self._election_service = election_service\n\n    def expose(self) -> None:\n        eel.expose(self.get_encryption_package_export_locations)\n        eel.expose(self.export_encryption_package)\n\n    def get_encryption_package_export_locations(self) -> dict[str, Any]:\n        self._log.trace(\"getting export locations\")\n        export_locations = get_export_locations()\n        artifacts_locations = [\n            os.path.join(location, \"artifacts\") for location in export_locations\n        ]\n        return eel_success(artifacts_locations)\n\n    def export_encryption_package(\n        self, election_id: str, location: str\n    ) -> dict[str, Any]:\n        db = self._db_service.get_db()\n        election = self._election_service.get(db, election_id)\n        if not election.encryption_package_file:\n            raise Exception(\"No encryption package file\")\n        self._log.debug(f\"unzipping: {election.encryption_package_file} to {location}\")\n        unpack_archive(election.encryption_package_file, location)\n        return eel_success()\n"
  },
  {
    "path": "src/electionguard_gui/components/guardian_home_component.py",
    "content": "from typing import Any\nimport eel  # type: ignore[import-untyped]\nfrom electionguard_gui.eel_utils import eel_success\n\nfrom electionguard_gui.components.component_base import ComponentBase\nfrom electionguard_gui.services import (\n    KeyCeremonyService,\n    DecryptionService,\n    DbWatcherService,\n)\n\n\nclass GuardianHomeComponent(ComponentBase):\n    \"\"\"Responsible for functionality related to the guardian home page\"\"\"\n\n    _key_ceremony_service: KeyCeremonyService\n    _decryption_service: DecryptionService\n    _db_watcher_service: DbWatcherService\n\n    def __init__(\n        self,\n        key_ceremony_service: KeyCeremonyService,\n        decryption_service: DecryptionService,\n        db_watcher_service: DbWatcherService,\n    ) -> None:\n        super().__init__()\n        self._key_ceremony_service = key_ceremony_service\n        self._decryption_service = decryption_service\n        self._db_watcher_service = db_watcher_service\n\n    def expose(self) -> None:\n        eel.expose(self.get_decryptions)\n        eel.expose(self.get_key_ceremonies)\n        eel.expose(self.watch_db_collections)\n        eel.expose(self.stop_watching_db_collections)\n\n    def get_decryptions(self) -> dict[str, Any]:\n        db = self._db_service.get_db()\n        decryptions = self._decryption_service.get_active(db)\n        decryptions_json = [decryption.to_id_name_dict() for decryption in decryptions]\n        return eel_success(decryptions_json)\n\n    def get_key_ceremonies(self) -> dict[str, Any]:\n        db = self._db_service.get_db()\n        key_ceremonies = self._key_ceremony_service.get_active(db)\n        js_key_ceremonies = [\n            key_ceremony.to_id_name_dict() for key_ceremony in key_ceremonies\n        ]\n        return eel_success(js_key_ceremonies)\n\n    def watch_db_collections(self) -> None:\n        try:\n            self._log.debug(\"Watching database\")\n            db = self._db_service.get_db()\n            self._db_watcher_service.watch_database(db, None, notify_ui_db_changed)\n            self._log.debug(\"exited watching database\")\n        except KeyboardInterrupt:\n            self._log.debug(\"Keyboard interrupt, exiting watch database\")\n            self._db_watcher_service.stop_watching()\n        except Exception as e:  # pylint: disable=broad-except\n            self.handle_error(e)\n            self._db_watcher_service.stop_watching()\n            # no need to raise exception or return anything, we're in fire-and-forget mode here\n\n    def stop_watching_db_collections(self) -> None:\n        self._log.debug(\"Stopping watch database\")\n        self._db_watcher_service.stop_watching()\n\n\ndef notify_ui_db_changed(collection: str, _: str) -> None:\n    # pylint: disable=no-member\n    if collection == \"key_ceremonies\":\n        eel.key_ceremonies_changed()  # type: ignore[attr-defined]\n    if collection == \"decryptions\":\n        eel.decryptions_changed()  # type: ignore[attr-defined]\n"
  },
  {
    "path": "src/electionguard_gui/components/key_ceremony_details_component.py",
    "content": "import traceback\nfrom typing import List\nimport eel  # type: ignore[import-untyped]\nfrom pymongo.database import Database\nfrom electionguard_gui.eel_utils import eel_fail, eel_success\n\n\nfrom electionguard_gui.services.key_ceremony_stages import (\n    KeyCeremonyStageBase,\n    KeyCeremonyS1JoinService,\n    KeyCeremonyS2AnnounceService,\n    KeyCeremonyS3MakeBackupService,\n    KeyCeremonyS4ShareBackupService,\n    KeyCeremonyS5VerifyBackupService,\n    KeyCeremonyS6PublishKeyService,\n)\n\nfrom electionguard_gui.models.key_ceremony_dto import KeyCeremonyDto\nfrom electionguard_gui.services.key_ceremony_state_service import (\n    KeyCeremonyStateService,\n    get_key_ceremony_status,\n)\nfrom electionguard_gui.services import (\n    AuthorizationService,\n    DbWatcherService,\n    KeyCeremonyService,\n)\nfrom electionguard_gui.components.component_base import ComponentBase\n\n\nclass KeyCeremonyDetailsComponent(ComponentBase):\n    \"\"\"Responsible for retrieving key ceremony details\"\"\"\n\n    _auth_service: AuthorizationService\n    _ceremony_state_service: KeyCeremonyStateService\n    _db_watcher_service: DbWatcherService\n    _key_ceremony_s1_join_service: KeyCeremonyS1JoinService\n    key_ceremony_watch_stages: List[KeyCeremonyStageBase]\n\n    def __init__(\n        self,\n        key_ceremony_service: KeyCeremonyService,\n        auth_service: AuthorizationService,\n        db_watcher_service: DbWatcherService,\n        key_ceremony_state_service: KeyCeremonyStateService,\n        key_ceremony_s1_join_service: KeyCeremonyS1JoinService,\n        key_ceremony_s2_announce_service: KeyCeremonyS2AnnounceService,\n        key_ceremony_s3_make_backup_service: KeyCeremonyS3MakeBackupService,\n        key_ceremony_s4_share_backup_service: KeyCeremonyS4ShareBackupService,\n        key_ceremony_s5_verification_service: KeyCeremonyS5VerifyBackupService,\n        key_ceremony_s6_publish_key_service: KeyCeremonyS6PublishKeyService,\n    ) -> None:\n        super().__init__()\n        self._key_ceremony_service = key_ceremony_service\n        self._ceremony_state_service = key_ceremony_state_service\n        self._auth_service = auth_service\n        self._db_watcher_service = db_watcher_service\n        self._key_ceremony_s1_join_service = key_ceremony_s1_join_service\n        self.key_ceremony_watch_stages = [\n            key_ceremony_s2_announce_service,\n            key_ceremony_s3_make_backup_service,\n            key_ceremony_s4_share_backup_service,\n            key_ceremony_s5_verification_service,\n            key_ceremony_s6_publish_key_service,\n        ]\n\n    def expose(self) -> None:\n        eel.expose(self.join_key_ceremony)\n        eel.expose(self.watch_key_ceremony)\n        eel.expose(self.stop_watching_key_ceremony)\n\n    def watch_key_ceremony(self, key_ceremony_id: str) -> None:\n        try:\n            db = self._db_service.get_db()\n            # retrieve and send the key ceremony to the client\n            self.on_key_ceremony_changed(\"key_ceremonies\", key_ceremony_id)\n            self._log.debug(f\"watching key ceremony '{key_ceremony_id}'\")\n            # start watching for key ceremony changes from guardians\n            self._db_watcher_service.watch_database(\n                db, key_ceremony_id, self.on_key_ceremony_changed\n            )\n        except KeyboardInterrupt:\n            self._log.debug(\"Keyboard interrupt, exiting watch database\")\n            self._db_watcher_service.stop_watching()\n        except Exception as e:  # pylint: disable=broad-except\n            self.handle_error(e)\n            self._db_watcher_service.stop_watching()\n            # we're in a fire-and-forget scenario, so no need to raise an exception or return anything\n\n    def stop_watching_key_ceremony(self) -> None:\n        self._db_watcher_service.stop_watching()\n\n    def on_key_ceremony_changed(self, _: str, key_ceremony_id: str) -> None:\n        try:\n            self._log.debug(\n                f\"on_key_ceremony_changed key_ceremony_id: '{key_ceremony_id}'\"\n            )\n            db = self._db_service.get_db()\n            key_ceremony = self.get_ceremony(db, key_ceremony_id)\n            state = self._ceremony_state_service.get_key_ceremony_state(key_ceremony)\n            self._log.debug(f\"{key_ceremony_id} state = '{state}'\")\n\n            for stage in self.key_ceremony_watch_stages:\n                if stage.should_run(key_ceremony, state):\n                    stage.run(db, key_ceremony)\n                    break\n\n            key_ceremony = self.get_ceremony(db, key_ceremony_id)\n            new_state = self._ceremony_state_service.get_key_ceremony_state(\n                key_ceremony\n            )\n            if state != new_state:\n                self._log.debug(f\"state changed from {state} to {new_state}\")\n            key_ceremony.status = get_key_ceremony_status(new_state)\n            result = key_ceremony.to_dict()\n            # pylint: disable=no-member\n            eel.refresh_key_ceremony(eel_success(result))  # type: ignore[attr-defined]\n        # pylint: disable=broad-except\n        except Exception as e:\n            self._log.error(\"error on key ceremony changed\", e)\n            traceback.print_exc()\n            # pylint: disable=no-member\n            eel.refresh_key_ceremony(eel_fail(str(e)))  # type: ignore[attr-defined]\n\n    def join_key_ceremony(self, key_ceremony_id: str) -> None:\n        try:\n            db = self._db_service.get_db()\n            key_ceremony = self.get_ceremony(db, key_ceremony_id)\n            self._key_ceremony_s1_join_service.run(db, key_ceremony)\n        # pylint: disable=broad-except\n        except Exception as e:\n            self.handle_error(e)\n\n    def get_ceremony(self, db: Database, id: str) -> KeyCeremonyDto:\n        key_ceremony = self._key_ceremony_service.get(db, id)\n        return key_ceremony\n"
  },
  {
    "path": "src/electionguard_gui/components/upload_ballots_component.py",
    "content": "import os\nfrom typing import Any\nfrom datetime import datetime, timezone\nimport eel  # type: ignore[import-untyped]\nfrom electionguard.encrypt import EncryptionDevice\nfrom electionguard.serialize import from_file, from_raw\nfrom electionguard.ballot import SubmittedBallot\nfrom electionguard_gui.components.component_base import ComponentBase\nfrom electionguard_gui.eel_utils import eel_fail, eel_success\nfrom electionguard_gui.services import ElectionService, BallotUploadService\nfrom electionguard_gui.services.export_service import get_removable_drives\n\n\nclass UploadBallotsComponent(ComponentBase):\n    \"\"\"Responsible for uploading ballots to an election via the GUI\"\"\"\n\n    _election_service: ElectionService\n    _ballot_upload_service: BallotUploadService\n\n    def __init__(\n        self,\n        election_service: ElectionService,\n        ballot_upload_service: BallotUploadService,\n    ) -> None:\n        self._election_service = election_service\n        self._ballot_upload_service = ballot_upload_service\n\n    def expose(self) -> None:\n        eel.expose(self.create_ballot_upload)\n        eel.expose(self.upload_ballot)\n        eel.expose(self.is_wizard_supported)\n        eel.expose(self.scan_drives)\n        eel.expose(self.upload_ballots)\n\n    def create_ballot_upload(\n        self,\n        election_id: str,\n        device_file_name: str,\n        device_file_contents: str,\n    ) -> dict[str, Any]:\n        try:\n            db = self._db_service.get_db()\n            self._log.debug(f\"creating upload for {election_id}\")\n            election = self._election_service.get(db, election_id)\n            if election is None:\n                return eel_fail(f\"Election {election_id} not found\")\n            created_at = datetime.now(timezone.utc)\n            ballot_upload_id = self._ballot_upload_service.create(\n                db,\n                election_id,\n                device_file_name,\n                device_file_contents,\n                created_at,\n            )\n            self._election_service.append_ballot_upload(\n                db,\n                election_id,\n                ballot_upload_id,\n                device_file_contents,\n                created_at,\n            )\n            return eel_success(ballot_upload_id)\n        # pylint: disable=broad-except\n        except Exception as e:\n            return self.handle_error(e)\n\n    def upload_ballot(\n        self,\n        ballot_upload_id: str,\n        election_id: str,\n        file_name: str,\n        file_contents: str,\n    ) -> dict[str, Any]:\n        try:\n            db = self._db_service.get_db()\n            self._log.trace(f\"adding ballot {file_name} to {ballot_upload_id}\")\n            ballot = from_raw(SubmittedBallot, file_contents)\n            election = self._election_service.get(db, election_id)\n            context = election.get_context()\n            if context.manifest_hash != ballot.manifest_hash:\n                self._log.warn(\n                    f\"ballot '{ballot.object_id}' had a mismatched manifest hash. \"\n                    + f\"Expected {context.manifest_hash}, got {ballot.manifest_hash}.\"\n                )\n                return eel_fail(\n                    \"The uploaded ballot didn't match the encryption package for this election. \"\n                    + \"Please try a different ballot.\"\n                )\n            is_duplicate = self._ballot_upload_service.any_ballot_exists(\n                db, election_id, ballot.object_id\n            )\n            if is_duplicate:\n                self._log.warn(\n                    \"ballot '{ballot.object_id}' already exists in election '{election_id}'\"\n                )\n                return eel_success({\"is_duplicate\": True})\n\n            success = self._ballot_upload_service.add_ballot(\n                db,\n                ballot_upload_id,\n                election_id,\n                file_name,\n                file_contents,\n                ballot.object_id,\n            )\n            if success:\n                self._ballot_upload_service.increment_ballot_count(db, ballot_upload_id)\n                self._election_service.increment_ballot_upload_ballot_count(\n                    db, election_id, ballot_upload_id\n                )\n            return eel_success({\"is_duplicate\": False})\n        # pylint: disable=broad-except\n        except Exception as e:\n            return self.handle_error(e)\n\n    def is_wizard_supported(self) -> bool:\n        on_windows = os.name == \"nt\"\n        return on_windows\n\n    def scan_drives(self) -> dict[str, Any]:\n        try:\n            removable_drives = get_removable_drives()\n            self._log.trace(f\"found {len(removable_drives)} removable drives\")\n            candidate_drives = [\n                self.parse_drive(drive)\n                for drive in removable_drives\n                if os.path.exists(os.path.join(drive, \"artifacts\", \"encrypted_ballots\"))\n                and os.path.exists(os.path.join(drive, \"artifacts\", \"devices\"))\n            ]\n            first_candidate = next(iter(candidate_drives), None)\n            return eel_success(first_candidate)\n        # pylint: disable=broad-except\n        except Exception as e:\n            return self.handle_error(e)\n\n    def parse_drive(self, drive: str) -> dict[str, Any]:\n        ballots_dir = os.path.join(drive, \"artifacts\", \"encrypted_ballots\")\n        devices_dir = os.path.join(drive, \"artifacts\", \"devices\")\n        device_files = os.listdir(devices_dir)\n        device_file_name = next(iter(os.listdir(devices_dir)))\n        device_file_path = os.path.join(devices_dir, device_file_name)\n        if len(device_files) > 1:\n            self._log.warn(\n                \"found multiple device files in drive, using \" + device_file_name\n            )\n        device_file_json = from_file(EncryptionDevice, device_file_path)\n        location = device_file_json.location\n        ballot_count = len(os.listdir(ballots_dir))\n        return {\n            \"drive\": drive,\n            \"ballots\": ballot_count,\n            \"location\": location,\n            \"device_file_name\": device_file_name,\n            \"device_file_path\": device_file_path,\n            \"ballots_dir\": ballots_dir,\n        }\n\n    def upload_ballots(self, election_id: str) -> dict[str, Any]:\n        try:\n            update_upload_status(\"Scanning drives\")\n            drive_info = self.scan_drives()\n            device_file_name = drive_info[\"result\"][\"device_file_name\"]\n            device_file_path = drive_info[\"result\"][\"device_file_path\"]\n            self._log.debug(\n                f\"uploading ballots for {election_id} from {device_file_path} device {device_file_name}\"\n            )\n            update_upload_status(\"Uploading device file\")\n            ballot_upload_result = self.create_ballot_upload_from_file(\n                election_id,\n                device_file_name,\n                device_file_path,\n            )\n            if not ballot_upload_result[\"success\"]:\n                return ballot_upload_result\n\n            ballots_dir: str = drive_info[\"result\"][\"ballots_dir\"]\n            ballot_files = os.listdir(ballots_dir)\n            ballot_upload_id: str = ballot_upload_result[\"result\"]\n            ballot_num = 1\n            duplicate_count = 0\n            ballot_count = len(ballot_files)\n            for ballot_file in ballot_files:\n                self._log.debug(\"uploading ballot \" + ballot_file)\n                update_upload_status(f\"Uploading ballot {ballot_num}/{ballot_count}\")\n                result = self.create_ballot_from_file(\n                    election_id, ballot_file, ballot_upload_id, ballots_dir\n                )\n                if not result[\"success\"]:\n                    return result\n                if result[\"result\"][\"is_duplicate\"]:\n                    duplicate_count += 1\n                ballot_num += 1\n            return eel_success(\n                {\"ballot_count\": ballot_count, \"duplicate_count\": duplicate_count}\n            )\n        # pylint: disable=broad-except\n        except Exception as e:\n            return self.handle_error(e)\n\n    def create_ballot_from_file(\n        self,\n        election_id: str,\n        ballot_file_name: str,\n        ballot_upload_id: str,\n        ballots_dir: str,\n    ) -> dict[str, Any]:\n        ballot_file_path = os.path.join(ballots_dir, ballot_file_name)\n        with open(ballot_file_path, \"r\", encoding=\"utf-8\") as ballot_file:\n            ballot_contents = ballot_file.read()\n            return self.upload_ballot(\n                ballot_upload_id, election_id, ballot_file_name, ballot_contents\n            )\n\n    def create_ballot_upload_from_file(\n        self, election_id: str, device_file_name: str, device_file_path: str\n    ) -> dict[str, Any]:\n        with open(device_file_path, \"r\", encoding=\"utf-8\") as device_file:\n            ballot_upload = self.create_ballot_upload(\n                election_id, device_file_name, device_file.read()\n            )\n            return ballot_upload\n\n\ndef update_upload_status(status: str) -> None:\n    # pylint: disable=no-member\n    eel.update_upload_status(status)  # type: ignore[attr-defined]\n"
  },
  {
    "path": "src/electionguard_gui/components/view_decryption_component.py",
    "content": "import traceback\nfrom typing import Any\nimport eel  # type: ignore[import-untyped]\nfrom pymongo.database import Database\nfrom electionguard_gui.eel_utils import eel_fail, eel_success\nfrom electionguard_gui.components.component_base import ComponentBase\nfrom electionguard_gui.models.decryption_dto import DecryptionDto\nfrom electionguard_gui.services import (\n    ElectionService,\n    DecryptionService,\n    DbWatcherService,\n)\nfrom electionguard_gui.services.decryption_stages import (\n    DecryptionS1JoinService,\n    DecryptionS2AnnounceService,\n)\n\n\nclass ViewDecryptionComponent(ComponentBase):\n    \"\"\"Responsible for functionality related to creating decryptions for an election\"\"\"\n\n    _decryption_service: DecryptionService\n    _election_service: ElectionService\n    _decryption_s1_join_service: DecryptionS1JoinService\n    _decryption_s2_announce_service: DecryptionS2AnnounceService\n    _db_watcher_service: DbWatcherService\n\n    def __init__(\n        self,\n        decryption_service: DecryptionService,\n        election_service: ElectionService,\n        decryption_s1_join_service: DecryptionS1JoinService,\n        decryption_s2_announce_service: DecryptionS2AnnounceService,\n        db_watcher_service: DbWatcherService,\n    ) -> None:\n        self._decryption_service = decryption_service\n        self._election_service = election_service\n        self._decryption_s1_join_service = decryption_s1_join_service\n        self._decryption_s2_announce_service = decryption_s2_announce_service\n        self._db_watcher_service = db_watcher_service\n\n    def expose(self) -> None:\n        eel.expose(self.get_decryption)\n        eel.expose(self.watch_decryption)\n        eel.expose(self.stop_watching_decryption)\n        eel.expose(self.join_decryption)\n\n    def watch_decryption(self, decryption_id: str) -> None:\n        try:\n            db = self._db_service.get_db()\n            self._log.debug(f\"watching decryption '{decryption_id}'\")\n            self._db_watcher_service.watch_database(\n                db, decryption_id, self.on_decryption_changed\n            )\n        except Exception as e:  # pylint: disable=broad-except\n            self.handle_error(e)\n            self._db_watcher_service.stop_watching()\n            # no need to raise exception or return anything, we're in fire-and-forget mode here\n\n    def stop_watching_decryption(self) -> None:\n        self._db_watcher_service.stop_watching()\n\n    def on_decryption_changed(self, _: str, decryption_id: str) -> None:\n        try:\n            self._log.debug(f\"on_key_ceremony_changed decryption_id: '{decryption_id}'\")\n\n            db = self._db_service.get_db()\n            decryption = self._decryption_service.get(db, decryption_id)\n            self.try_run_stage_2(db, decryption)\n            refresh_decryption(eel_success())\n        # pylint: disable=broad-except\n        except Exception as e:\n            self._log.error(\"error in on decryption changed\", e)\n            traceback.print_exc()\n            refresh_decryption(eel_fail(str(e)))\n\n    def try_run_stage_2(self, db: Database, decryption: DecryptionDto) -> bool:\n        if self._decryption_s2_announce_service.should_run(db, decryption):\n            refresh_decryption(eel_success())\n            # give the UI a chance to update\n            eel.sleep(0.5)\n            self._decryption_s2_announce_service.run(db, decryption)\n            return True\n        return False\n\n    def get_decryption(self, decryption_id: str, is_refresh: bool) -> dict[str, Any]:\n        try:\n            db = self._db_service.get_db()\n            decryption = self._decryption_service.get(db, decryption_id)\n            if not is_refresh:\n                did_run = self.try_run_stage_2(db, decryption)\n                if did_run:\n                    decryption = self._decryption_service.get(db, decryption_id)\n            return eel_success(decryption.to_dict())\n        # pylint: disable=broad-except\n        except Exception as e:\n            return self.handle_error(e)\n\n    def join_decryption(self, decryption_id: str) -> dict[str, Any]:\n        try:\n            db = self._db_service.get_db()\n            decryption = self._decryption_service.get(db, decryption_id)\n            self._decryption_s1_join_service.run(db, decryption)\n            return eel_success(decryption.to_dict())\n        # pylint: disable=broad-except\n        except Exception as e:\n            return self.handle_error(e)\n\n\ndef refresh_decryption(result: dict[str, Any]) -> None:\n    # pylint: disable=no-member\n    eel.refresh_decryption(result)  # type: ignore[attr-defined]\n"
  },
  {
    "path": "src/electionguard_gui/components/view_election_component.py",
    "content": "from typing import Any\nimport eel\nfrom electionguard_gui.components.component_base import ComponentBase\nfrom electionguard_gui.eel_utils import eel_success\nfrom electionguard_gui.services import ElectionService\n\n\nclass ViewElectionComponent(ComponentBase):\n    \"\"\"Responsible for viewing election details\"\"\"\n\n    _election_service: ElectionService\n\n    def __init__(self, election_service: ElectionService) -> None:\n        self._election_service = election_service\n\n    def expose(self) -> None:\n        eel.expose(self.get_election)\n\n    def get_election(self, election_id: str) -> dict[str, Any]:\n        db = self._db_service.get_db()\n        election = self._election_service.get(db, election_id)\n        return eel_success(election.to_dict())\n"
  },
  {
    "path": "src/electionguard_gui/components/view_spoiled_ballot_component.py",
    "content": "from typing import Any\nimport eel\nfrom electionguard.tally import PlaintextTally\nfrom electionguard_gui.eel_utils import eel_success\nfrom electionguard_gui.components.component_base import ComponentBase\nfrom electionguard_gui.services import (\n    DecryptionService,\n    ElectionService,\n)\nfrom electionguard_gui.services.plaintext_ballot_service import (\n    get_plaintext_ballot_report,\n)\n\n\nclass ViewSpoiledBallotComponent(ComponentBase):\n    \"\"\"Responsible for functionality related to viewing a tally\"\"\"\n\n    _decryption_service: DecryptionService\n    _election_service: ElectionService\n\n    def __init__(\n        self, decryption_service: DecryptionService, election_service: ElectionService\n    ) -> None:\n        self._decryption_service = decryption_service\n        self._election_service = election_service\n\n    def expose(self) -> None:\n        eel.expose(self.get_spoiled_ballot)\n\n    def get_spoiled_ballot(\n        self, decryption_id: str, spoiled_ballot_id: str\n    ) -> dict[str, Any]:\n        try:\n            db = self._db_service.get_db()\n            self._log.debug(\n                f\"retrieving spoiled ballot '{decryption_id}'.{spoiled_ballot_id}\"\n            )\n            decryption = self._decryption_service.get(db, decryption_id)\n            election = self._election_service.get(db, decryption.election_id)\n            spoiled_ballots = decryption.get_plaintext_spoiled_ballots()\n            plaintext_tally = get_spoiled_ballot_by_id(\n                spoiled_ballots, spoiled_ballot_id\n            )\n            tally_report = get_plaintext_ballot_report(election, plaintext_tally)\n            result = {\n                \"election_id\": election.id,\n                \"election_name\": election.election_name,\n                \"decryption_name\": decryption.decryption_name,\n                \"report\": tally_report,\n            }\n            return eel_success(result)\n        # pylint: disable=broad-except\n        except Exception as e:\n            return self.handle_error(e)\n\n\ndef get_spoiled_ballot_by_id(\n    spoiled_ballots: list[PlaintextTally], spoiled_ballot_id: str\n) -> PlaintextTally:\n    matches: list[PlaintextTally] = [\n        ballot for ballot in spoiled_ballots if ballot.object_id == spoiled_ballot_id\n    ]\n    return next(iter(matches))\n"
  },
  {
    "path": "src/electionguard_gui/components/view_tally_component.py",
    "content": "from typing import Any\nimport eel\nfrom electionguard_gui.eel_utils import eel_success\nfrom electionguard_gui.components.component_base import ComponentBase\nfrom electionguard_gui.services import (\n    DecryptionService,\n    ElectionService,\n)\nfrom electionguard_gui.services.plaintext_ballot_service import (\n    get_plaintext_ballot_report,\n)\n\n\nclass ViewTallyComponent(ComponentBase):\n    \"\"\"Responsible for functionality related to viewing a tally\"\"\"\n\n    _decryption_service: DecryptionService\n    _election_service: ElectionService\n\n    def __init__(\n        self, decryption_service: DecryptionService, election_service: ElectionService\n    ) -> None:\n        self._decryption_service = decryption_service\n        self._election_service = election_service\n\n    def expose(self) -> None:\n        eel.expose(self.get_tally)\n\n    def get_tally(self, decryption_id: str) -> dict[str, Any]:\n        try:\n            db = self._db_service.get_db()\n            self._log.debug(f\"retrieving decryption '{decryption_id}'\")\n            decryption = self._decryption_service.get(db, decryption_id)\n            election = self._election_service.get(db, decryption.election_id)\n            plaintext_tally = decryption.get_plaintext_tally()\n            tally_report = get_plaintext_ballot_report(election, plaintext_tally)\n            result = {\n                \"election_id\": election.id,\n                \"election_name\": election.election_name,\n                \"decryption_name\": decryption.decryption_name,\n                \"report\": tally_report,\n            }\n            return eel_success(result)\n        # pylint: disable=broad-except\n        except Exception as e:\n            return self.handle_error(e)\n"
  },
  {
    "path": "src/electionguard_gui/containers.py",
    "content": "from dependency_injector import containers, providers\nfrom dependency_injector.providers import Factory, Singleton\nfrom electionguard_cli.setup_election.output_setup_files_step import (\n    OutputSetupFilesStep,\n)\nfrom electionguard_cli.setup_election.setup_election_builder_step import (\n    SetupElectionBuilderStep,\n)\nfrom electionguard_gui.components import (\n    CreateElectionComponent,\n    ViewElectionComponent,\n    CreateKeyCeremonyComponent,\n    ElectionListComponent,\n    GuardianHomeComponent,\n    KeyCeremonyDetailsComponent,\n    ExportEncryptionPackageComponent,\n    UploadBallotsComponent,\n    CreateDecryptionComponent,\n    ViewDecryptionComponent,\n    ExportElectionRecordComponent,\n    ViewTallyComponent,\n    ViewSpoiledBallotComponent,\n)\nfrom electionguard_gui.main_app import MainApp\nfrom electionguard_gui.services import (\n    AuthorizationService,\n    DbService,\n    EelLogService,\n    ElectionService,\n    GuardianService,\n    KeyCeremonyService,\n    KeyCeremonyStateService,\n    GuiSetupInputRetrievalStep,\n    BallotUploadService,\n    DecryptionService,\n    DbWatcherService,\n    ConfigurationService,\n    VersionService,\n)\nfrom electionguard_gui.services.decryption_stages import (\n    DecryptionS1JoinService,\n    DecryptionS2AnnounceService,\n)\nfrom electionguard_gui.services.key_ceremony_stages import (\n    KeyCeremonyS1JoinService,\n    KeyCeremonyS2AnnounceService,\n    KeyCeremonyS3MakeBackupService,\n    KeyCeremonyS4ShareBackupService,\n    KeyCeremonyS5VerifyBackupService,\n    KeyCeremonyS6PublishKeyService,\n)\n\n\nclass Container(containers.DeclarativeContainer):\n    \"\"\"Responsible for dependency injection and how components are wired together\"\"\"\n\n    # services\n    log_service: Singleton[EelLogService] = providers.Singleton(EelLogService)\n    config_service: Factory[ConfigurationService] = providers.Factory(\n        ConfigurationService\n    )\n    version_service: Factory[VersionService] = providers.Factory(\n        VersionService, log_service=log_service\n    )\n    db_service: Singleton[DbService] = providers.Singleton(\n        DbService, log_service=log_service, config_service=config_service\n    )\n    authorization_service: Singleton[AuthorizationService] = providers.Singleton(\n        AuthorizationService, config_service=config_service\n    )\n    db_watcher_service: Factory[DbWatcherService] = providers.Factory(\n        DbWatcherService, log_service=log_service\n    )\n    key_ceremony_service: Factory[KeyCeremonyService] = providers.Factory(\n        KeyCeremonyService,\n        log_service=log_service,\n        auth_service=authorization_service,\n        db_watcher_service=db_watcher_service,\n    )\n    election_service: Factory[ElectionService] = providers.Factory(\n        ElectionService, log_service=log_service, auth_service=authorization_service\n    )\n    key_ceremony_state_service: Factory[KeyCeremonyStateService] = providers.Factory(\n        KeyCeremonyStateService, log_service=log_service\n    )\n    guardian_service: Factory[GuardianService] = providers.Factory(\n        GuardianService, log_service=log_service\n    )\n    setup_input_retrieval_step: Factory[GuiSetupInputRetrievalStep] = providers.Factory(\n        GuiSetupInputRetrievalStep\n    )\n    setup_election_builder_step: Factory[SetupElectionBuilderStep] = providers.Factory(\n        SetupElectionBuilderStep\n    )\n    output_setup_files_step: Factory[OutputSetupFilesStep] = providers.Factory(\n        OutputSetupFilesStep\n    )\n    ballot_upload_service: Factory[BallotUploadService] = providers.Factory(\n        BallotUploadService, log_service=log_service, auth_service=authorization_service\n    )\n    decryption_service: Factory[DecryptionService] = providers.Factory(\n        DecryptionService,\n        log_service=log_service,\n        auth_service=authorization_service,\n        db_watcher_service=db_watcher_service,\n    )\n\n    # decryption services\n    decryption_s1_join_service: Factory[DecryptionS1JoinService] = providers.Factory(\n        DecryptionS1JoinService,\n        log_service=log_service,\n        db_service=db_service,\n        decryption_service=decryption_service,\n        auth_service=authorization_service,\n        guardian_service=guardian_service,\n        ballot_upload_service=ballot_upload_service,\n        election_service=election_service,\n    )\n    decryption_s2_announce_service: Factory[DecryptionS2AnnounceService] = (\n        providers.Factory(\n            DecryptionS2AnnounceService,\n            log_service=log_service,\n            db_service=db_service,\n            decryption_service=decryption_service,\n            auth_service=authorization_service,\n            guardian_service=guardian_service,\n            ballot_upload_service=ballot_upload_service,\n            election_service=election_service,\n        )\n    )\n\n    # key ceremony services\n    key_ceremony_s1_join_service: Factory[KeyCeremonyS1JoinService] = providers.Factory(\n        KeyCeremonyS1JoinService,\n        log_service=log_service,\n        db_service=db_service,\n        key_ceremony_service=key_ceremony_service,\n        auth_service=authorization_service,\n        key_ceremony_state_service=key_ceremony_state_service,\n        guardian_service=guardian_service,\n    )\n    key_ceremony_s2_announce_service: Factory[KeyCeremonyS2AnnounceService] = (\n        providers.Factory(\n            KeyCeremonyS2AnnounceService,\n            log_service=log_service,\n            db_service=db_service,\n            key_ceremony_service=key_ceremony_service,\n            auth_service=authorization_service,\n            key_ceremony_state_service=key_ceremony_state_service,\n            guardian_service=guardian_service,\n        )\n    )\n    key_ceremony_s3_make_backup_service: Factory[KeyCeremonyS3MakeBackupService] = (\n        providers.Factory(\n            KeyCeremonyS3MakeBackupService,\n            log_service=log_service,\n            db_service=db_service,\n            key_ceremony_service=key_ceremony_service,\n            auth_service=authorization_service,\n            key_ceremony_state_service=key_ceremony_state_service,\n            guardian_service=guardian_service,\n        )\n    )\n    key_ceremony_s4_share_backup_service: Factory[KeyCeremonyS4ShareBackupService] = (\n        providers.Factory(\n            KeyCeremonyS4ShareBackupService,\n            log_service=log_service,\n            db_service=db_service,\n            key_ceremony_service=key_ceremony_service,\n            auth_service=authorization_service,\n            key_ceremony_state_service=key_ceremony_state_service,\n            guardian_service=guardian_service,\n        )\n    )\n    key_ceremony_s5_verification_service: Factory[KeyCeremonyS5VerifyBackupService] = (\n        providers.Factory(\n            KeyCeremonyS5VerifyBackupService,\n            log_service=log_service,\n            db_service=db_service,\n            key_ceremony_service=key_ceremony_service,\n            auth_service=authorization_service,\n            key_ceremony_state_service=key_ceremony_state_service,\n            guardian_service=guardian_service,\n        )\n    )\n    key_ceremony_s6_publish_key_service: Factory[KeyCeremonyS6PublishKeyService] = (\n        providers.Factory(\n            KeyCeremonyS6PublishKeyService,\n            log_service=log_service,\n            db_service=db_service,\n            key_ceremony_service=key_ceremony_service,\n            auth_service=authorization_service,\n            key_ceremony_state_service=key_ceremony_state_service,\n            guardian_service=guardian_service,\n        )\n    )\n\n    # components\n    guardian_home_component: Factory[GuardianHomeComponent] = providers.Factory(\n        GuardianHomeComponent,\n        key_ceremony_service=key_ceremony_service,\n        decryption_service=decryption_service,\n        db_watcher_service=db_watcher_service,\n    )\n    create_election_component: Factory[CreateElectionComponent] = providers.Factory(\n        CreateElectionComponent,\n        key_ceremony_service=key_ceremony_service,\n        election_service=election_service,\n        setup_election_builder_step=setup_election_builder_step,\n        setup_input_retrieval_step=setup_input_retrieval_step,\n        output_setup_files_step=output_setup_files_step,\n        guardian_service=guardian_service,\n    )\n    create_key_ceremony_component: Factory[CreateKeyCeremonyComponent] = (\n        providers.Factory(\n            CreateKeyCeremonyComponent,\n            key_ceremony_service=key_ceremony_service,\n            auth_service=authorization_service,\n        )\n    )\n    election_list_component: Factory[ElectionListComponent] = providers.Factory(\n        ElectionListComponent,\n        election_service=election_service,\n    )\n    view_election_component: Factory[ViewElectionComponent] = providers.Factory(\n        ViewElectionComponent,\n        election_service=election_service,\n    )\n    key_ceremony_details_component: Factory[KeyCeremonyDetailsComponent] = (\n        providers.Factory(\n            KeyCeremonyDetailsComponent,\n            key_ceremony_service=key_ceremony_service,\n            auth_service=authorization_service,\n            db_watcher_service=db_watcher_service,\n            key_ceremony_state_service=key_ceremony_state_service,\n            key_ceremony_s1_join_service=key_ceremony_s1_join_service,\n            key_ceremony_s2_announce_service=key_ceremony_s2_announce_service,\n            key_ceremony_s3_make_backup_service=key_ceremony_s3_make_backup_service,\n            key_ceremony_s4_share_backup_service=key_ceremony_s4_share_backup_service,\n            key_ceremony_s5_verification_service=key_ceremony_s5_verification_service,\n            key_ceremony_s6_publish_key_service=key_ceremony_s6_publish_key_service,\n        )\n    )\n    export_encryption_package: Factory[ExportEncryptionPackageComponent] = (\n        providers.Factory(\n            ExportEncryptionPackageComponent,\n            election_service=election_service,\n        )\n    )\n    upload_ballots_component: Factory[UploadBallotsComponent] = providers.Factory(\n        UploadBallotsComponent,\n        election_service=election_service,\n        ballot_upload_service=ballot_upload_service,\n    )\n    create_decryption_component: Factory[CreateDecryptionComponent] = providers.Factory(\n        CreateDecryptionComponent,\n        election_service=election_service,\n        decryption_service=decryption_service,\n    )\n    view_decryption_component: Factory[ViewDecryptionComponent] = providers.Factory(\n        ViewDecryptionComponent,\n        election_service=election_service,\n        decryption_service=decryption_service,\n        decryption_s1_join_service=decryption_s1_join_service,\n        decryption_s2_announce_service=decryption_s2_announce_service,\n        db_watcher_service=db_watcher_service,\n    )\n    export_election_record_component: Factory[ExportElectionRecordComponent] = (\n        providers.Factory(\n            ExportElectionRecordComponent,\n            election_service=election_service,\n            decryption_service=decryption_service,\n            ballot_upload_service=ballot_upload_service,\n        )\n    )\n    view_tally_component: Factory[ViewTallyComponent] = providers.Factory(\n        ViewTallyComponent,\n        decryption_service=decryption_service,\n        election_service=election_service,\n    )\n    view_spoiled_ballot_component: Factory[ViewSpoiledBallotComponent] = (\n        providers.Factory(\n            ViewSpoiledBallotComponent,\n            decryption_service=decryption_service,\n            election_service=election_service,\n        )\n    )\n\n    # main\n    main_app: Factory[MainApp] = providers.Factory(\n        MainApp,\n        log_service=log_service,\n        config_service=config_service,\n        db_service=db_service,\n        guardian_home_component=guardian_home_component,\n        create_key_ceremony_component=create_key_ceremony_component,\n        key_ceremony_details_component=key_ceremony_details_component,\n        authorization_service=authorization_service,\n        create_election_component=create_election_component,\n        view_election_component=view_election_component,\n        election_list_component=election_list_component,\n        export_encryption_package=export_encryption_package,\n        upload_ballots_component=upload_ballots_component,\n        create_decryption_component=create_decryption_component,\n        view_decryption_component=view_decryption_component,\n        export_election_record_component=export_election_record_component,\n        view_tally_component=view_tally_component,\n        view_spoiled_ballot_component=view_spoiled_ballot_component,\n        version_service=version_service,\n    )\n"
  },
  {
    "path": "src/electionguard_gui/docker-compose.yml",
    "content": "version: \"3.8\"\nservices:\n  mongo:\n    image: mongo:4.4\n    container_name: \"electionguard-db\"\n    restart: always\n    ports:\n      - 27017:27017\n    environment:\n      MONGO_INITDB_ROOT_USERNAME: root\n      MONGO_INITDB_ROOT_PASSWORD: ${EG_DB_PASSWORD}\n      MONGO_INITDB_DATABASE: ElectionGuardDb\n    volumes:\n      - ../electionguard_db/mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro\n      - ${EG_DB_DIR}:/data/db\n\n  mongo-express:\n    image: mongo-express:1.0.0-alpha.4\n    restart: always\n    ports:\n      - 8181:8081\n    environment:\n      ME_CONFIG_MONGODB_SERVER: electionguard-db\n      ME_CONFIG_MONGODB_ADMINUSERNAME: root\n      ME_CONFIG_MONGODB_ADMINPASSWORD: ${EG_DB_PASSWORD}\n    depends_on:\n      - mongo\n    volumes:\n      - ${EG_DB_DIR}:/data/db\n\n  admin1:\n    image: egui:latest\n    container_name: \"admin1\"\n    restart: always\n    ports:\n      - 12801:12800\n    environment:\n      EG_DB_PASSWORD: ${EG_DB_PASSWORD}\n      EG_DB_HOST: mongo\n      EG_PORT: 12800\n      EG_MODE: none\n      EG_HOST: 0.0.0.0\n      EG_IS_ADMIN: True\n      # to get logs via `docker logs`, see also https://stackoverflow.com/a/51362214/40783\n      PYTHONUNBUFFERED: 1\n    depends_on:\n      - mongo\n    volumes:\n      # admin devices use /data to store encryption package after election creation. See also directory_service.py\n      - ../../egui_mnt/data:/egui_mnt/data\n      # egui_mnt/export is where admin devices export election records and encryption packages\n      - ../../egui_mnt/export/:/egui_mnt/export\n\n  guardian1:\n    image: egui:latest\n    container_name: \"guardian1\"\n    restart: always\n    ports:\n      - 12802:12800\n    environment:\n      EG_DB_PASSWORD: ${EG_DB_PASSWORD}\n      EG_DB_HOST: mongo\n      EG_PORT: 12800\n      EG_MODE: none\n      EG_HOST: 0.0.0.0\n      EG_IS_ADMIN: False\n      PYTHONUNBUFFERED: 1\n    depends_on:\n      - mongo\n    volumes:\n      # guardian devices store their private keys in /data so it is persists if the docker image is deleted\n      - ../../egui_mnt/data:/egui_mnt/data\n\n  guardian2:\n    image: egui:latest\n    container_name: \"guardian2\"\n    restart: always\n    ports:\n      - 12803:12800\n    environment:\n      EG_DB_PASSWORD: ${EG_DB_PASSWORD}\n      EG_DB_HOST: mongo\n      EG_PORT: 12800\n      EG_MODE: none\n      EG_HOST: 0.0.0.0\n      EG_IS_ADMIN: False\n      PYTHONUNBUFFERED: 1\n    depends_on:\n      - mongo\n    volumes:\n      # guardian devices store their private keys in /data so it is persists if the docker image is deleted\n      - ../../egui_mnt/data:/egui_mnt/data\n"
  },
  {
    "path": "src/electionguard_gui/eel_utils.py",
    "content": "from datetime import timezone, datetime\nimport os\nfrom typing import Any, Optional\n\n\ndef eel_fail(message: str) -> dict[str, Any]:\n    return {\"success\": False, \"message\": message}\n\n\ndef eel_success(result: Any = None) -> dict[str, Any]:\n    return {\"success\": True, \"result\": result}\n\n\ndef utc_to_str(utc_dt: Optional[datetime]) -> str:\n    if not utc_dt:\n        return \"\"\n    local = convert_utc_to_local(utc_dt)\n    if os.name == \"nt\":\n        return local.strftime(\"%b %#d, %Y %#I:%M %p\")\n    return local.strftime(\"%b %-d, %Y %-I:%M %p\")\n\n\ndef convert_utc_to_local(utc_dt: datetime) -> datetime:\n    return utc_dt.replace(tzinfo=timezone.utc).astimezone(None)\n"
  },
  {
    "path": "src/electionguard_gui/main_app.py",
    "content": "import traceback\nfrom typing import List\nimport eel\n\nfrom electionguard_gui.components import (\n    ViewElectionComponent,\n    ComponentBase,\n    CreateElectionComponent,\n    CreateKeyCeremonyComponent,\n    GuardianHomeComponent,\n    KeyCeremonyDetailsComponent,\n    ElectionListComponent,\n    ExportEncryptionPackageComponent,\n    UploadBallotsComponent,\n    CreateDecryptionComponent,\n    ViewDecryptionComponent,\n    ExportElectionRecordComponent,\n    ViewTallyComponent,\n    ViewSpoiledBallotComponent,\n)\n\nfrom electionguard_gui.services import (\n    AuthorizationService,\n    DbService,\n    EelLogService,\n    ServiceBase,\n    ConfigurationService,\n    VersionService,\n)\n\n\nclass MainApp:\n    \"\"\"Responsible for functionality related to the main app\"\"\"\n\n    log_service: EelLogService\n    db_service: DbService\n    components: List[ComponentBase]\n    services: List[ServiceBase]\n\n    def __init__(\n        self,\n        log_service: EelLogService,\n        config_service: ConfigurationService,\n        db_service: DbService,\n        guardian_home_component: GuardianHomeComponent,\n        create_key_ceremony_component: CreateKeyCeremonyComponent,\n        key_ceremony_details_component: KeyCeremonyDetailsComponent,\n        authorization_service: AuthorizationService,\n        create_election_component: CreateElectionComponent,\n        view_election_component: ViewElectionComponent,\n        election_list_component: ElectionListComponent,\n        export_encryption_package: ExportEncryptionPackageComponent,\n        upload_ballots_component: UploadBallotsComponent,\n        create_decryption_component: CreateDecryptionComponent,\n        view_decryption_component: ViewDecryptionComponent,\n        export_election_record_component: ExportElectionRecordComponent,\n        view_tally_component: ViewTallyComponent,\n        view_spoiled_ballot_component: ViewSpoiledBallotComponent,\n        version_service: VersionService,\n    ) -> None:\n        super().__init__()\n\n        self.log_service = log_service\n        self.db_service = db_service\n        self.config_service = config_service\n\n        self.components = [\n            guardian_home_component,\n            create_key_ceremony_component,\n            key_ceremony_details_component,\n            create_election_component,\n            view_election_component,\n            election_list_component,\n            export_encryption_package,\n            upload_ballots_component,\n            create_decryption_component,\n            view_decryption_component,\n            export_election_record_component,\n            view_tally_component,\n            view_spoiled_ballot_component,\n        ]\n\n        # services that need to expose methods to the UI\n        self.services = [\n            authorization_service,\n            db_service,\n            log_service,\n            version_service,\n        ]\n\n    def start(self) -> None:\n        try:\n            self.log_service.debug(\"Starting main app\")\n\n            for service in self.services:\n                service.init()\n\n            for component in self.components:\n                component.init(self.db_service, self.log_service)\n\n            self.db_service.verify_db_connection()\n            eel.init(\"src/electionguard_gui/web\")\n            mode = self.config_service.get_mode()\n            port = self.config_service.get_port()\n            host = self.config_service.get_host()\n            self.log_service.debug(f\"Starting eel port={port} mode={mode} host={host}\")\n            eel.start(\n                \"index.html\",\n                size=(1024, 768),\n                port=port,\n                mode=mode,\n                host=host,\n                close_callback=self.on_close,\n            )\n            self.log_service.info(\"Exiting main app normally\")\n        except Exception as e:\n            self.log_service.error(\"error in main app start\", e)\n            traceback.print_exc()\n            raise e\n\n    def on_close(self, _page: str, _open_sockets: list) -> None:\n        self.log_service.info(\n            \"To close the egui app ensure the browser tab is closed and hit Ctrl+C\"\n        )\n"
  },
  {
    "path": "src/electionguard_gui/models/__init__.py",
    "content": "from electionguard_gui.models import decryption_dto\nfrom electionguard_gui.models import election_dto\nfrom electionguard_gui.models import key_ceremony_dto\nfrom electionguard_gui.models import key_ceremony_states\n\nfrom electionguard_gui.models.decryption_dto import (\n    DecryptionDto,\n    GuardianDecryptionShare,\n)\nfrom electionguard_gui.models.election_dto import (\n    ElectionDto,\n)\nfrom electionguard_gui.models.key_ceremony_dto import (\n    KeyCeremonyDto,\n)\nfrom electionguard_gui.models.key_ceremony_states import (\n    KeyCeremonyStates,\n)\n\n__all__ = [\n    \"DecryptionDto\",\n    \"ElectionDto\",\n    \"GuardianDecryptionShare\",\n    \"KeyCeremonyDto\",\n    \"KeyCeremonyStates\",\n    \"decryption_dto\",\n    \"election_dto\",\n    \"key_ceremony_dto\",\n    \"key_ceremony_states\",\n]\n"
  },
  {
    "path": "src/electionguard_gui/models/decryption_dto.py",
    "content": "from typing import Any, Dict, Optional\nfrom datetime import datetime\nfrom electionguard.decryption_share import DecryptionShare\nfrom electionguard.election_polynomial import LagrangeCoefficientsRecord\nfrom electionguard.key_ceremony import ElectionPublicKey\nfrom electionguard.serialize import from_raw\nfrom electionguard.tally import PlaintextTally, PublishedCiphertextTally\nfrom electionguard.type import BallotId\nfrom electionguard_gui.eel_utils import utc_to_str\nfrom electionguard_gui.services.authorization_service import AuthorizationService\n\n\nclass GuardianDecryptionShare:\n    \"\"\"A guardian's contribution to a section of a tally and the spoiled ballots\"\"\"\n\n    def __init__(\n        self,\n        guardian_id: str,\n        decryption_share_json: str,\n        ballot_shares: dict[str, str],\n        guardian_key_json: str,\n    ):\n        self.guardian_id = guardian_id\n        self.guardian_key = from_raw(ElectionPublicKey, guardian_key_json)\n        self.tally_share = from_raw(DecryptionShare, decryption_share_json)\n        self.ballot_shares = {\n            ballot_id: from_raw(DecryptionShare, ballot_share)\n            for (ballot_id, ballot_share) in ballot_shares.items()\n        }\n\n    guardian_id: str\n    tally_share: DecryptionShare\n    ballot_shares: Dict[BallotId, Optional[DecryptionShare]]\n    guardian_key: ElectionPublicKey\n\n\n# pylint: disable=too-many-instance-attributes\nclass DecryptionDto:\n    \"\"\"Responsible for serializing to the front-end GUI and providing helper functions to Python.\"\"\"\n\n    decryption_id: str\n    election_id: str\n    election_name: Optional[str]\n    ballot_upload_count: int\n    ballot_count: int\n    guardians: int\n    quorum: int\n    decryption_name: Optional[str]\n    key_ceremony_id: Optional[str]\n    guardians_joined: list[str]\n    can_join: Optional[bool]\n    decryption_shares: list[Any]\n    plaintext_tally: Optional[str]\n    plaintext_spoiled_ballots: dict[str, str]\n    lagrange_coefficients: Optional[str]\n    ciphertext_tally: Optional[str]\n    completed_at_utc: Optional[datetime]\n    completed_at_str: str\n    created_by: Optional[str]\n    created_at_utc: Optional[datetime]\n    created_at_str: str\n\n    def __init__(self, decryption: dict[str, Any]):\n        self.decryption_id = str(decryption.get(\"_id\"))\n        self.election_id = str(decryption.get(\"election_id\"))\n        self.key_ceremony_id = decryption.get(\"key_ceremony_id\")\n        self.election_name = decryption.get(\"election_name\")\n        self.ballot_upload_count = _get_int(decryption, \"ballot_upload_count\", 0)\n        self.ballot_count = _get_int(decryption, \"ballot_count\", 0)\n        self.guardians = _get_int(decryption, \"guardians\", 0)\n        self.quorum = _get_int(decryption, \"quorum\", 0)\n        self.decryption_name = decryption.get(\"decryption_name\")\n        self.guardians_joined = _get_list(decryption, \"guardians_joined\")\n        self.decryption_shares = _get_list(decryption, \"decryption_shares\")\n        self.plaintext_tally = decryption.get(\"plaintext_tally\")\n        self.plaintext_spoiled_ballots = _get_dict(\n            decryption, \"plaintext_spoiled_ballots\"\n        )\n        self.lagrange_coefficients = decryption.get(\"lagrange_coefficients\")\n        self.ciphertext_tally = decryption.get(\"ciphertext_tally\")\n        self.completed_at_utc = decryption.get(\"completed_at\")\n        self.completed_at_str = utc_to_str(decryption.get(\"completed_at\"))\n        self.created_by = decryption.get(\"created_by\")\n        self.created_at_utc = decryption.get(\"created_at\")\n        self.created_at_str = utc_to_str(decryption.get(\"created_at\"))\n        self.can_join = False\n\n    def get_status(self) -> str:\n        if len(self.guardians_joined) < self.guardians:\n            return \"waiting for all guardians to join\"\n        if self.completed_at_utc is None:\n            return \"performing decryption\"\n        return \"decryption complete\"\n\n    def to_id_name_dict(self) -> dict[str, Any]:\n        return {\n            \"id\": self.decryption_id,\n            \"decryption_name\": self.decryption_name,\n        }\n\n    def to_dict(self) -> dict[str, Any]:\n        return {\n            \"decryption_id\": self.decryption_id,\n            \"election_id\": self.election_id,\n            \"election_name\": self.election_name,\n            \"ballot_upload_count\": self.ballot_upload_count,\n            \"ballot_count\": self.ballot_count,\n            \"decryption_name\": self.decryption_name,\n            \"guardians_joined\": self.guardians_joined,\n            \"status\": self.get_status(),\n            \"completed_at_str\": self.completed_at_str,\n            \"spoiled_ballots\": (\n                list(self.plaintext_spoiled_ballots.keys())\n                if self.plaintext_spoiled_ballots\n                else []\n            ),\n            \"can_join\": self.can_join,\n            \"created_by\": self.created_by,\n            \"created_at\": self.created_at_str,\n        }\n\n    def get_decryption_shares(self) -> list[GuardianDecryptionShare]:\n        return [\n            GuardianDecryptionShare(\n                ballot_share_dict[\"guardian_id\"],\n                ballot_share_dict[\"decryption_share\"],\n                ballot_share_dict[\"ballot_shares\"],\n                ballot_share_dict[\"guardian_key\"],\n            )\n            for ballot_share_dict in self.decryption_shares\n        ]\n\n    def set_can_join(self, auth_service: AuthorizationService) -> None:\n        user_id = auth_service.get_user_id()\n        already_joined = user_id in self.guardians_joined\n        is_admin = auth_service.is_admin()\n        self.can_join = not already_joined and not is_admin\n\n    def get_plaintext_tally(self) -> PlaintextTally:\n        if not self.plaintext_tally:\n            raise ValueError(\"No plaintext tally found\")\n        return from_raw(PlaintextTally, self.plaintext_tally)\n\n    def get_plaintext_spoiled_ballots(self) -> list[PlaintextTally]:\n        return [\n            from_raw(PlaintextTally, tally)\n            for tally in self.plaintext_spoiled_ballots.values()\n        ]\n\n    def get_lagrange_coefficients(self) -> LagrangeCoefficientsRecord:\n        if not self.lagrange_coefficients:\n            raise ValueError(\"No lagrange coefficients found\")\n        return from_raw(LagrangeCoefficientsRecord, self.lagrange_coefficients)\n\n    def get_ciphertext_tally(self) -> PublishedCiphertextTally:\n        if not self.ciphertext_tally:\n            raise ValueError(\"No ciphertext tally found\")\n        return from_raw(PublishedCiphertextTally, self.ciphertext_tally)\n\n\ndef _get_list(decryption: dict[str, Any], name: str) -> list:\n    value = decryption.get(name)\n    if value:\n        return list(value)\n    return []\n\n\ndef _get_dict(decryption: dict[str, Any], name: str) -> dict:\n    value = decryption.get(name)\n    if value:\n        return dict(value)\n    return {}\n\n\ndef _get_int(decryption: dict[str, Any], name: str, default: int) -> int:\n    value = decryption.get(name)\n    if value:\n        return int(value)\n    return default\n"
  },
  {
    "path": "src/electionguard_gui/models/election_dto.py",
    "content": "from typing import Any, Optional\nfrom datetime import datetime\nfrom electionguard.election import CiphertextElectionContext\nfrom electionguard.encrypt import EncryptionDevice\nfrom electionguard.guardian import GuardianRecord\nfrom electionguard.manifest import Manifest\nfrom electionguard.serialize import from_list_raw, from_raw\n\nfrom electionguard_gui.eel_utils import utc_to_str\n\n\n# pylint: disable=too-many-instance-attributes\nclass ElectionDto:\n    \"\"\"Responsible for serializing to the front-end GUI and providing helper functions to Python.\"\"\"\n\n    id: str\n    election_name: Optional[str]\n    key_ceremony_id: Optional[str]\n    guardians: Optional[int]\n    quorum: Optional[int]\n    manifest: Optional[dict[str, Any]]\n    context: Optional[str]\n    constants: Optional[int]\n    guardian_records: Optional[str]\n    encryption_package_file: Optional[str]\n    election_url: Optional[str]\n    ballot_uploads: list[dict[str, Any]]\n    decryptions: list[dict[str, Any]]\n    created_by: Optional[str]\n    created_at_utc: Optional[datetime]\n    created_at_str: str\n\n    def __init__(self, election: dict[str, Any]):\n        self.id = str(election.get(\"_id\"))\n        self.election_name = election.get(\"election_name\")\n        self.key_ceremony_id = election.get(\"key_ceremony_id\")\n        self.guardians = election.get(\"guardians\")\n        self.quorum = election.get(\"quorum\")\n        self.manifest = election.get(\"manifest\")\n        self.context = election.get(\"context\")\n        self.constants = election.get(\"constants\")\n        self.guardian_records = election.get(\"guardian_records\")\n        self.encryption_package_file = election.get(\"encryption_package_file\")\n        self.election_url = election.get(\"election_url\")\n        self.ballot_uploads = _get_list(election, \"ballot_uploads\")\n        self.decryptions = _get_list(election, \"decryptions\")\n        self.created_by = election.get(\"created_by\")\n        self.created_at_utc = election.get(\"created_at\")\n        self.created_at_str = utc_to_str(election.get(\"created_at\"))\n\n    def to_id_name_dict(self) -> dict[str, Any]:\n        return {\n            \"id\": self.id,\n            \"election_name\": self.election_name,\n        }\n\n    def _get_manifest_field(self, field: str) -> Any:\n        return self.manifest.get(field) if self.manifest else None\n\n    def to_dict(self) -> dict[str, Any]:\n        return {\n            \"id\": self.id,\n            \"election_name\": self.election_name,\n            \"guardians\": self.guardians,\n            \"quorum\": self.quorum,\n            \"election_url\": self.election_url,\n            \"manifest\": {\n                \"name\": self._get_manifest_field(\"name\"),\n                \"scope\": self._get_manifest_field(\"scope\"),\n                \"geopolitical_units\": self._get_manifest_field(\"geopolitical_units\"),\n                \"parties\": self._get_manifest_field(\"parties\"),\n                \"candidates\": self._get_manifest_field(\"candidates\"),\n                \"contests\": self._get_manifest_field(\"contests\"),\n                \"ballot_styles\": self._get_manifest_field(\"ballot_styles\"),\n            },\n            \"ballot_uploads\": [\n                {\n                    \"location\": ballot_upload[\"location\"],\n                    \"ballot_count\": ballot_upload[\"ballot_count\"],\n                    \"created_at\": utc_to_str(ballot_upload.get(\"created_at\")),\n                }\n                for ballot_upload in self.ballot_uploads\n            ],\n            \"decryptions\": [\n                {\n                    \"decryption_id\": decryption[\"decryption_id\"],\n                    \"name\": decryption[\"name\"],\n                    \"created_at\": utc_to_str(decryption.get(\"created_at\")),\n                }\n                for decryption in self.decryptions\n            ],\n            \"created_by\": self.created_by,\n            \"created_at\": self.created_at_str,\n        }\n\n    def get_manifest(self) -> Manifest:\n        if not self.manifest:\n            raise Exception(\"No manifest found\")\n        return from_raw(Manifest, self.manifest[\"raw\"])\n\n    def get_context(self) -> CiphertextElectionContext:\n        if not self.context:\n            raise Exception(\"No context found\")\n        return from_raw(CiphertextElectionContext, self.context)\n\n    def get_encryption_devices(self) -> list[EncryptionDevice]:\n        return [\n            EncryptionDevice(\n                ballot_upload[\"device_id\"],\n                ballot_upload[\"session_id\"],\n                ballot_upload[\"launch_code\"],\n                ballot_upload[\"location\"],\n            )\n            for ballot_upload in self.ballot_uploads\n        ]\n\n    def get_guardian_records(self) -> list[GuardianRecord]:\n        if not self.guardian_records:\n            raise Exception(\"No guardian records found\")\n        return from_list_raw(GuardianRecord, self.guardian_records)\n\n    def get_guardian_sequence_order(self, guardian_id: str) -> int:\n        for record in self.get_guardian_records():\n            if record.guardian_id == guardian_id:\n                return record.sequence_order\n        raise Exception(\"Guardian not found\")\n\n    def sum_ballots(self) -> int:\n        return sum(ballot[\"ballot_count\"] for ballot in self.ballot_uploads)\n\n\ndef _get_list(election: dict[str, Any], name: str) -> list:\n    value = election.get(name)\n    if value:\n        return list(value)\n    return []\n"
  },
  {
    "path": "src/electionguard_gui/models/key_ceremony_dto.py",
    "content": "from typing import Any, List\nfrom datetime import datetime\nfrom electionguard import ElectionPartialKeyVerification\nfrom electionguard.group import ElementModP, ElementModQ\nfrom electionguard.key_ceremony import (\n    ElectionJointKey,\n    ElectionPartialKeyBackup,\n    ElectionPublicKey,\n)\nfrom electionguard.election_polynomial import PublicCommitment\nfrom electionguard.elgamal import ElGamalPublicKey, HashedElGamalCiphertext\nfrom electionguard.schnorr import SchnorrProof\n\nfrom electionguard_gui.eel_utils import utc_to_str\nfrom electionguard_gui.services.authorization_service import AuthorizationService\n\n\n# pylint: disable=too-many-instance-attributes\nclass KeyCeremonyDto:\n    \"\"\"A key ceremony for serializing to the front-end GUI and providing helper functions to Python.\"\"\"\n\n    def __init__(self, key_ceremony: Any):\n        self.id = str(key_ceremony[\"_id\"])\n        self.guardian_count = key_ceremony[\"guardian_count\"]\n        self.key_ceremony_name = key_ceremony[\"key_ceremony_name\"]\n        self.quorum = key_ceremony[\"quorum\"]\n        self.guardians_joined = key_ceremony[\"guardians_joined\"]\n        self.other_keys = key_ceremony[\"other_keys\"]\n        self.backups = key_ceremony[\"backups\"]\n        self.shared_backups = key_ceremony[\"shared_backups\"]\n        self.verifications = key_ceremony[\"verifications\"]\n        self.keys = [_dict_to_election_public_key(key) for key in key_ceremony[\"keys\"]]\n        self.joint_key = key_ceremony[\"joint_key\"]\n        self.created_by = key_ceremony[\"created_by\"]\n        self.created_at_utc = key_ceremony[\"created_at\"]\n        self.created_at_str = utc_to_str(key_ceremony[\"created_at\"])\n        self.completed_at_utc = key_ceremony[\"completed_at\"]\n        self.completed_at_str = utc_to_str(key_ceremony[\"completed_at\"])\n\n    def to_id_name_dict(self) -> dict[str, Any]:\n        return {\n            \"id\": self.id,\n            \"key_ceremony_name\": self.key_ceremony_name,\n        }\n\n    def to_dict(self) -> dict[str, Any]:\n        return {\n            \"id\": self.id,\n            \"guardian_count\": self.guardian_count,\n            \"key_ceremony_name\": self.key_ceremony_name,\n            \"quorum\": self.quorum,\n            \"guardians_joined\": self.guardians_joined,\n            \"created_by\": self.created_by,\n            \"created_at_str\": self.created_at_str,\n            \"completed_at_str\": self.completed_at_str,\n            \"can_join\": self.can_join,\n            \"status\": self.status,\n        }\n\n    id: str\n    guardian_count: int\n    key_ceremony_name: str\n    quorum: int\n    guardians_joined: List[str]\n    other_keys: List[Any]\n    backups: List[Any]\n    shared_backups: List[Any]\n    verifications: List[Any]\n    keys: List[ElectionPublicKey]\n    joint_key: Any\n    created_by: str\n    created_at_utc: datetime\n    completed_at_utc: datetime\n    created_at_str: str\n    completed_at_str: str\n    can_join: bool\n    status: str\n\n    def find_key(self, guardian_id: str) -> ElectionPublicKey:\n        keys = self.keys\n        key = next((key for key in keys if key.owner_id == guardian_id), None)\n        if key is None:\n            raise Exception(\"Key not found for guardian: \" + guardian_id)\n        return key\n\n    def get_backup_count_for_user(self, user_id: str) -> int:\n        backups = [backup for backup in self.backups if backup[\"owner_id\"] == user_id]\n        return len(backups)\n\n    def get_verification_count_for_user(self, user_id: str) -> int:\n        return len(\n            [\n                verification\n                for verification in self.verifications\n                if verification[\"designated_id\"] == user_id\n            ]\n        )\n\n    def get_verifications(self) -> List[ElectionPartialKeyVerification]:\n        return [\n            _dict_to_verification(verification) for verification in self.verifications\n        ]\n\n    def get_shared_backups_for_guardian(\n        self, guardian_id: str\n    ) -> List[ElectionPartialKeyBackup]:\n        shared_backup_wrapper = next(\n            filter(\n                lambda backup: backup[\"owner_id\"] == guardian_id, self.shared_backups\n            )\n        )\n        backups = shared_backup_wrapper[\"backups\"]\n        return [_dict_to_backup(backup) for backup in backups]\n\n    def get_backups(self) -> List[ElectionPartialKeyBackup]:\n        return [_dict_to_backup(backup) for backup in self.backups]\n\n    def find_other_keys_for_user(self, user_id: str) -> List[ElectionPublicKey]:\n        other_key_wrapper = next(\n            filter(\n                lambda other_key: other_key[\"owner_id\"] == user_id,\n                self.other_keys,\n            )\n        )\n        other_keys = other_key_wrapper[\"other_keys\"]\n        return [_dict_to_election_public_key(other_key) for other_key in other_keys]\n\n    def joint_key_exists(self) -> bool:\n        return self.joint_key is not None\n\n    def get_joint_key(self) -> ElectionJointKey:\n        return ElectionJointKey(\n            ElGamalPublicKey(self.joint_key[\"joint_public_key\"]),\n            ElementModQ(self.joint_key[\"commitment_hash\"]),\n        )\n\n    def set_can_join(self, auth_service: AuthorizationService) -> None:\n        user_id = auth_service.get_user_id()\n        already_joined = user_id in self.guardians_joined\n        is_admin = auth_service.is_admin()\n        self.can_join = not already_joined and not is_admin\n\n\ndef _dict_to_verification(verification: Any) -> ElectionPartialKeyVerification:\n    return ElectionPartialKeyVerification(\n        verification[\"owner_id\"],\n        verification[\"designated_id\"],\n        verification[\"verifier_id\"],\n        verification[\"verified\"],\n    )\n\n\ndef _dict_to_backup(backup: Any) -> ElectionPartialKeyBackup:\n    coordinate = backup[\"encrypted_coordinate\"]\n    ciphertext = HashedElGamalCiphertext(\n        ElementModP(coordinate[\"pad\"]), coordinate[\"data\"], coordinate[\"mac\"]\n    )\n    return ElectionPartialKeyBackup(\n        backup[\"owner_id\"],\n        backup[\"designated_id\"],\n        backup[\"designated_sequence_order\"],\n        ciphertext,\n    )\n\n\ndef _dict_to_election_public_key(key: Any) -> ElectionPublicKey:\n    coefficient_commitments = [\n        PublicCommitment(x) for x in key[\"coefficient_commitments\"]\n    ]\n    coefficient_proofs = [\n        SchnorrProof(\n            cp[\"public_key\"],\n            cp[\"commitment\"],\n            cp[\"challenge\"],\n            cp[\"response\"],\n            cp[\"usage\"],\n        )\n        for cp in key[\"coefficient_proofs\"]\n    ]\n    guardian_public_key = ElectionPublicKey(\n        key[\"owner_id\"],\n        key[\"sequence_order\"],\n        ElGamalPublicKey(key[\"key\"]),\n        coefficient_commitments,\n        coefficient_proofs,\n    )\n    return guardian_public_key\n"
  },
  {
    "path": "src/electionguard_gui/models/key_ceremony_states.py",
    "content": "from enum import Enum\n\n\nclass KeyCeremonyStates(Enum):\n    \"\"\"A list of states for the key ceremony.\"\"\"\n\n    PendingGuardiansJoin = 1\n    PendingAdminAnnounce = 2\n    PendingGuardianBackups = 3\n    PendingAdminToShareBackups = 4\n    PendingGuardiansVerifyBackups = 5\n    PendingAdminToPublishJointKey = 6\n    Complete = 7\n"
  },
  {
    "path": "src/electionguard_gui/services/__init__.py",
    "content": "from electionguard_gui.services import authorization_service\nfrom electionguard_gui.services import ballot_upload_service\nfrom electionguard_gui.services import configuration_service\nfrom electionguard_gui.services import db_serialization_service\nfrom electionguard_gui.services import db_service\nfrom electionguard_gui.services import db_watcher_service\nfrom electionguard_gui.services import decryption_service\nfrom electionguard_gui.services import decryption_stages\nfrom electionguard_gui.services import directory_service\nfrom electionguard_gui.services import eel_log_service\nfrom electionguard_gui.services import election_service\nfrom electionguard_gui.services import export_service\nfrom electionguard_gui.services import guardian_service\nfrom electionguard_gui.services import gui_setup_input_retrieval_step\nfrom electionguard_gui.services import key_ceremony_service\nfrom electionguard_gui.services import key_ceremony_stages\nfrom electionguard_gui.services import key_ceremony_state_service\nfrom electionguard_gui.services import plaintext_ballot_service\nfrom electionguard_gui.services import service_base\nfrom electionguard_gui.services import version_service\n\nfrom electionguard_gui.services.authorization_service import (\n    AuthorizationService,\n)\nfrom electionguard_gui.services.ballot_upload_service import (\n    BallotUploadService,\n    RetryException,\n)\nfrom electionguard_gui.services.configuration_service import (\n    ConfigurationService,\n    DB_HOST_KEY,\n    DB_PASSWORD_KEY,\n    HOST_KEY,\n    IS_ADMIN_KEY,\n    MODE_KEY,\n    PORT_KEY,\n)\nfrom electionguard_gui.services.db_serialization_service import (\n    backup_to_dict,\n    joint_key_to_dict,\n    public_key_to_dict,\n    verification_to_dict,\n)\nfrom electionguard_gui.services.db_service import (\n    DbService,\n)\nfrom electionguard_gui.services.db_watcher_service import (\n    DbWatcherService,\n)\nfrom electionguard_gui.services.decryption_service import (\n    DecryptionService,\n    to_ballot_share_raw,\n)\nfrom electionguard_gui.services.decryption_stages import (\n    DecryptionS1JoinService,\n    DecryptionS2AnnounceService,\n    DecryptionStageBase,\n    decryption_s1_join_service,\n    decryption_s2_announce_service,\n    decryption_stage_base,\n    get_tally,\n)\nfrom electionguard_gui.services.directory_service import (\n    DOCKER_MOUNT_DIR,\n    get_data_dir,\n    get_export_dir,\n)\nfrom electionguard_gui.services.eel_log_service import (\n    EelLogService,\n)\nfrom electionguard_gui.services.election_service import (\n    ElectionService,\n)\nfrom electionguard_gui.services.export_service import (\n    get_export_locations,\n    get_removable_drives,\n)\nfrom electionguard_gui.services.guardian_service import (\n    GuardianService,\n    announce_guardians,\n    make_guardian,\n    make_mediator,\n)\nfrom electionguard_gui.services.gui_setup_input_retrieval_step import (\n    GuiSetupInputRetrievalStep,\n)\nfrom electionguard_gui.services.key_ceremony_service import (\n    KeyCeremonyService,\n    get_guardian_number,\n)\nfrom electionguard_gui.services.key_ceremony_stages import (\n    KeyCeremonyS1JoinService,\n    KeyCeremonyS2AnnounceService,\n    KeyCeremonyS3MakeBackupService,\n    KeyCeremonyS4ShareBackupService,\n    KeyCeremonyS5VerifyBackupService,\n    KeyCeremonyS6PublishKeyService,\n    KeyCeremonyStageBase,\n    key_ceremony_s1_join_service,\n    key_ceremony_s2_announce_service,\n    key_ceremony_s3_make_backup_service,\n    key_ceremony_s4_share_backup_service,\n    key_ceremony_s5_verify_backup_service,\n    key_ceremony_s6_publish_key_service,\n    key_ceremony_stage_base,\n)\nfrom electionguard_gui.services.key_ceremony_state_service import (\n    KeyCeremonyStateService,\n    get_key_ceremony_status,\n    status_descriptions,\n)\nfrom electionguard_gui.services.plaintext_ballot_service import (\n    get_plaintext_ballot_report,\n)\nfrom electionguard_gui.services.service_base import (\n    ServiceBase,\n)\nfrom electionguard_gui.services.version_service import (\n    VersionService,\n)\n\n__all__ = [\n    \"AuthorizationService\",\n    \"BallotUploadService\",\n    \"ConfigurationService\",\n    \"DB_HOST_KEY\",\n    \"DB_PASSWORD_KEY\",\n    \"DOCKER_MOUNT_DIR\",\n    \"DbService\",\n    \"DbWatcherService\",\n    \"DecryptionS1JoinService\",\n    \"DecryptionS2AnnounceService\",\n    \"DecryptionService\",\n    \"DecryptionStageBase\",\n    \"EelLogService\",\n    \"ElectionService\",\n    \"GuardianService\",\n    \"GuiSetupInputRetrievalStep\",\n    \"HOST_KEY\",\n    \"IS_ADMIN_KEY\",\n    \"KeyCeremonyS1JoinService\",\n    \"KeyCeremonyS2AnnounceService\",\n    \"KeyCeremonyS3MakeBackupService\",\n    \"KeyCeremonyS4ShareBackupService\",\n    \"KeyCeremonyS5VerifyBackupService\",\n    \"KeyCeremonyS6PublishKeyService\",\n    \"KeyCeremonyService\",\n    \"KeyCeremonyStageBase\",\n    \"KeyCeremonyStateService\",\n    \"MODE_KEY\",\n    \"PORT_KEY\",\n    \"RetryException\",\n    \"ServiceBase\",\n    \"VersionService\",\n    \"announce_guardians\",\n    \"authorization_service\",\n    \"backup_to_dict\",\n    \"ballot_upload_service\",\n    \"configuration_service\",\n    \"db_serialization_service\",\n    \"db_service\",\n    \"db_watcher_service\",\n    \"decryption_s1_join_service\",\n    \"decryption_s2_announce_service\",\n    \"decryption_service\",\n    \"decryption_stage_base\",\n    \"decryption_stages\",\n    \"directory_service\",\n    \"eel_log_service\",\n    \"election_service\",\n    \"export_service\",\n    \"get_data_dir\",\n    \"get_export_dir\",\n    \"get_export_locations\",\n    \"get_guardian_number\",\n    \"get_key_ceremony_status\",\n    \"get_plaintext_ballot_report\",\n    \"get_removable_drives\",\n    \"get_tally\",\n    \"guardian_service\",\n    \"gui_setup_input_retrieval_step\",\n    \"joint_key_to_dict\",\n    \"key_ceremony_s1_join_service\",\n    \"key_ceremony_s2_announce_service\",\n    \"key_ceremony_s3_make_backup_service\",\n    \"key_ceremony_s4_share_backup_service\",\n    \"key_ceremony_s5_verify_backup_service\",\n    \"key_ceremony_s6_publish_key_service\",\n    \"key_ceremony_service\",\n    \"key_ceremony_stage_base\",\n    \"key_ceremony_stages\",\n    \"key_ceremony_state_service\",\n    \"make_guardian\",\n    \"make_mediator\",\n    \"plaintext_ballot_service\",\n    \"public_key_to_dict\",\n    \"service_base\",\n    \"status_descriptions\",\n    \"to_ballot_share_raw\",\n    \"verification_to_dict\",\n    \"version_service\",\n]\n"
  },
  {
    "path": "src/electionguard_gui/services/authorization_service.py",
    "content": "from typing import Optional\nimport eel\n\nfrom electionguard_gui.services.configuration_service import ConfigurationService\nfrom electionguard_gui.services.service_base import ServiceBase\n\n\nclass AuthorizationService(ServiceBase):\n    \"\"\"Responsible for functionality related to authorization and user identify\"\"\"\n\n    _is_admin: bool\n\n    def __init__(self, config_service: ConfigurationService) -> None:\n        self._is_admin = config_service.get_is_admin()\n\n    # todo: replace state based storage with configparser https://docs.python.org/3/library/configparser.html\n    user_id: Optional[str] = None\n\n    def expose(self) -> None:\n        eel.expose(self.get_user_id)\n        eel.expose(self.set_user_id)\n        eel.expose(self.is_admin)\n\n    def get_required_user_id(self) -> str:\n        if self.user_id is None:\n            raise Exception(\"User must be logged in\")\n        return self.user_id\n\n    def get_user_id(self) -> Optional[str]:\n        return self.user_id\n\n    def set_user_id(self, user_id: str) -> None:\n        self.user_id = user_id\n\n    def is_admin(self) -> bool:\n        return self._is_admin\n"
  },
  {
    "path": "src/electionguard_gui/services/ballot_upload_service.py",
    "content": "from datetime import datetime\nfrom time import sleep\nfrom typing import Callable\nfrom pymongo.database import Database\nfrom electionguard.ballot import SubmittedBallot\nfrom electionguard.serialize import from_raw\nfrom electionguard_gui.services.eel_log_service import EelLogService\nfrom electionguard_gui.services.service_base import ServiceBase\nfrom electionguard_gui.services.authorization_service import AuthorizationService\n\n\nclass BallotUploadService(ServiceBase):\n    \"\"\"Responsible for functionality related to ballot uploads\"\"\"\n\n    _log: EelLogService\n    _auth_service: AuthorizationService\n\n    def __init__(\n        self, log_service: EelLogService, auth_service: AuthorizationService\n    ) -> None:\n        self._log = log_service\n        self._auth_service = auth_service\n\n    def create(\n        self,\n        db: Database,\n        election_id: str,\n        device_file_name: str,\n        device_file_contents: str,\n        created_at: datetime,\n    ) -> str:\n        ballot_upload = {\n            \"election_id\": election_id,\n            \"device_file_name\": device_file_name,\n            \"device_file_contents\": device_file_contents,\n            \"ballot_count\": 0,\n            \"created_by\": self._auth_service.get_user_id(),\n            \"created_at\": created_at,\n        }\n        self._log.trace(f\"inserting ballot upload for: {election_id}\")\n        inserted_id = db.ballot_uploads.insert_one(ballot_upload).inserted_id\n        return str(inserted_id)\n\n    def add_ballot(\n        self,\n        db: Database,\n        ballot_upload_id: str,\n        election_id: str,\n        file_name: str,\n        file_contents: str,\n        ballot_object_id: str,\n    ) -> bool:\n        self._log.trace(f\"adding ballot {file_name} to {ballot_upload_id}\")\n        db.ballot_uploads.insert_one(\n            {\n                \"ballot_upload_id\": ballot_upload_id,\n                \"election_id\": election_id,\n                \"file_name\": file_name,\n                \"object_id\": ballot_object_id,\n                \"file_contents\": file_contents,\n            }\n        )\n        return True\n\n    def increment_ballot_count(self, db: Database, ballot_upload_id: str) -> None:\n        self._log.trace(f\"incrementing ballot count for {ballot_upload_id}\")\n        db.ballot_uploads.update_one(\n            {\"_id\": ballot_upload_id, \"ballot_count\": {\"$exists\": True}},\n            {\"$inc\": {\"ballot_count\": 1}},\n        )\n\n    def any_ballot_exists(self, db: Database, election_id: str, object_id: str) -> bool:\n        self._log.trace(\"checking if ballot exists for {election_id}\")\n        return (\n            db.ballot_uploads.count_documents(\n                {\"election_id\": election_id, \"object_id\": object_id}\n            )\n            > 0\n        )\n\n    def get_ballots(\n        self, db: Database, election_id: str, report_status: Callable[[str], None]\n    ) -> list[SubmittedBallot]:\n        self._log.debug(f\"getting ballots for {election_id}\")\n        ballot_uploads = list(\n            db.ballot_uploads.find(\n                {\"election_id\": election_id, \"file_contents\": {\"$exists\": True}},\n                projection={\"_id\": 1, \"file_contents\": 0},\n            )\n        )\n        ballots = []\n        total_ballots = len(ballot_uploads)\n        ballot_num = 1\n        for ballot_id_obj in ballot_uploads:\n            ballot_id = ballot_id_obj[\"_id\"]\n            report_status(f\"Loading ballot {ballot_num}/{total_ballots}\")\n            try:\n                ballot = self.get_submitted_ballot_with_retry(db, ballot_id)\n                ballots.append(ballot)\n            # pylint: disable=broad-except\n            except Exception as e:\n                self._log.error(\n                    f\"Error deserializing ballot {ballot_id}. \"\n                    + \"Skipping ballot, but this may cause Chaum Pederson errors later.\",\n                    e,\n                )\n                # per RC 8/15/22 log errors and continue processing even if it makes numbers incorrect\n            ballot_num += 1\n        return ballots\n\n    def get_submitted_ballot_with_retry(\n        self, db: Database, ballot_upload_id: str\n    ) -> SubmittedBallot:\n        retry_num = 0\n        max_retries = 3\n        while retry_num < max_retries:\n            try:\n                return self.get_submitted_ballot(db, ballot_upload_id)\n            except RetryException:\n                self._log.warn(\n                    f\"retrying get ballot {ballot_upload_id} in {retry_num + 1} second(s). Retry #{retry_num + 1}\"\n                )\n                # wait 1 second before retrying in case network was slow\n                sleep(retry_num + 1)\n            retry_num += 1\n        raise Exception(\n            f\"Failed to get ballot {ballot_upload_id} after {max_retries} retries\"\n        )\n\n    def get_submitted_ballot(\n        self, db: Database, ballot_upload_id: str\n    ) -> SubmittedBallot:\n        self._log.trace(f\"getting submitted ballot {ballot_upload_id}\")\n        ballot_obj = None\n        try:\n            ballot_obj = db.ballot_uploads.find_one(\n                {\"_id\": ballot_upload_id}, projection={\"file_contents\": 1}\n            )\n        except Exception as e:\n            self._log.error(f\"mongo error getting ballot {ballot_upload_id}\", e)\n            raise RetryException from e\n        if ballot_obj is None:\n            raise Exception(f\"Ballot {ballot_upload_id} not found\")\n        ballot_str = ballot_obj[\"file_contents\"]\n        # if ballot_str ends with a }\n        if not ballot_str[-1] == \"}\":\n            self._log.warn(f\"ballot {ballot_upload_id} is missing a closing bracket\")\n            raise RetryException\n        try:\n            ballot = from_raw(SubmittedBallot, ballot_str)\n        except Exception as e:\n            self._log.error(f\"error deserializing ballot {ballot_upload_id}\", e)\n            raise RetryException from e\n        return ballot\n\n\nclass RetryException(Exception):\n    \"\"\"An exception to notify the caller to retry\"\"\"\n"
  },
  {
    "path": "src/electionguard_gui/services/configuration_service.py",
    "content": "from os import environ\nfrom sys import exit\nfrom typing import Optional\n\nDB_PASSWORD_KEY = \"EG_DB_PASSWORD\"\nDB_HOST_KEY = \"EG_DB_HOST\"\nIS_ADMIN_KEY = \"EG_IS_ADMIN\"\nPORT_KEY = \"EG_PORT\"\nMODE_KEY = \"EG_MODE\"\nHOST_KEY = \"EG_HOST\"\n\n\nclass ConfigurationService:\n    \"\"\"Responsible for retrieving configuration values, generally from environment variables\"\"\"\n\n    # 'chrome', 'electron', 'edge', 'custom', or 'none', see also https://github.com/ChrisKnott/Eel#app-options\n    def get_mode(self) -> Optional[str]:\n        mode = self._get_param_or_default(MODE_KEY, \"chrome\")\n        return None if mode == \"none\" else mode\n\n    def get_port(self) -> int:\n        return int(self._get_param_or_default(PORT_KEY, \"0\"))\n\n    def get_host(self) -> str:\n        return str(self._get_param_or_default(HOST_KEY, \"localhost\"))\n\n    def get_db_password(self) -> str:\n        return self._get_param(DB_PASSWORD_KEY)\n\n    def get_db_host(self, default: str) -> str:\n        return self._get_param_or_default(DB_HOST_KEY, default)\n\n    def get_is_admin(self) -> bool:\n        return self._get_param_or_default(IS_ADMIN_KEY, \"false\").lower() == \"true\"\n\n    def _get_param(self, param_name: str) -> str:\n        try:\n            return environ[param_name]\n        except KeyError:\n            print(f\"The environment variable {param_name} is not set.\")\n            exit(1)\n\n    def _get_param_or_default(self, param_name: str, default: str) -> str:\n        try:\n            return environ[param_name]\n        except KeyError:\n            return default\n"
  },
  {
    "path": "src/electionguard_gui/services/db_serialization_service.py",
    "content": "from typing import Any\nfrom electionguard.key_ceremony import (\n    ElectionJointKey,\n    ElectionPartialKeyBackup,\n    ElectionPartialKeyVerification,\n    ElectionPublicKey,\n)\n\n\ndef public_key_to_dict(key: ElectionPublicKey) -> dict[str, Any]:\n    return {\n        \"owner_id\": key.owner_id,\n        \"sequence_order\": key.sequence_order,\n        \"key\": str(key.key),\n        \"coefficient_commitments\": [str(c) for c in key.coefficient_commitments],\n        \"coefficient_proofs\": [\n            {\n                \"public_key\": str(cp.public_key),\n                \"commitment\": str(cp.commitment),\n                \"challenge\": str(cp.challenge),\n                \"response\": str(cp.response),\n                \"usage\": str(cp.usage),\n            }\n            for cp in key.coefficient_proofs\n        ],\n    }\n\n\ndef backup_to_dict(backup: ElectionPartialKeyBackup) -> dict[str, Any]:\n    coordinate = backup.encrypted_coordinate\n    return {\n        \"owner_id\": backup.owner_id,\n        \"designated_id\": backup.designated_id,\n        \"designated_sequence_order\": backup.designated_sequence_order,\n        \"encrypted_coordinate\": {\n            \"pad\": str(coordinate.pad),\n            \"data\": coordinate.data,\n            \"mac\": coordinate.mac,\n        },\n    }\n\n\ndef verification_to_dict(\n    verification: ElectionPartialKeyVerification,\n) -> dict[str, Any]:\n    return {\n        \"owner_id\": verification.owner_id,\n        \"designated_id\": verification.designated_id,\n        \"verifier_id\": verification.verifier_id,\n        \"verified\": verification.verified,\n    }\n\n\ndef joint_key_to_dict(\n    key: ElectionJointKey,\n) -> dict[str, Any]:\n    return {\n        \"joint_public_key\": str(key.joint_public_key),\n        \"commitment_hash\": str(key.commitment_hash),\n    }\n"
  },
  {
    "path": "src/electionguard_gui/services/db_service.py",
    "content": "from pymongo import MongoClient\nfrom pymongo.database import Database\nfrom electionguard_gui.services.configuration_service import (\n    ConfigurationService,\n)\nfrom electionguard_gui.services.eel_log_service import EelLogService\n\nfrom electionguard_gui.services.service_base import ServiceBase\n\n\nclass DbService(ServiceBase):\n    \"\"\"Responsible for instantiating a database\"\"\"\n\n    log_service: EelLogService\n\n    def __init__(\n        self, log_service: EelLogService, config_service: ConfigurationService\n    ) -> None:\n        self.log_service = log_service\n        self._db_password = config_service.get_db_password()\n        self._db_host = config_service.get_db_host(self.DEFAULT_HOST)\n\n    DEFAULT_HOST = \"localhost\"\n    DEFAULT_PORT = 27017\n    DEFAULT_USERNAME = \"root\"\n\n    _db_password: str\n    _db_host: str\n\n    def get_db(self) -> Database:\n        client: MongoClient = MongoClient(\n            self._db_host,\n            self.DEFAULT_PORT,\n            username=self.DEFAULT_USERNAME,\n            password=self._db_password,\n        )\n        db: Database = client.ElectionGuardDb\n        return db\n\n    def verify_db_connection(self) -> None:\n        self.log_service.debug(\"Verifying database connection\")\n        db = self.get_db()\n        db.list_collections()\n"
  },
  {
    "path": "src/electionguard_gui/services/db_watcher_service.py",
    "content": "from typing import Callable, Optional\nfrom threading import Event\nimport eel\nfrom pymongo.database import Database\nfrom pymongo import CursorType\nfrom electionguard_gui.services.eel_log_service import EelLogService\nfrom electionguard_gui.services.service_base import ServiceBase\n\n\nclass DbWatcherService(ServiceBase):\n    \"\"\"\n    Responsible for long polling against the database in order to notify clients that\n    changes have occurred to data within collections\n    \"\"\"\n\n    _log: EelLogService\n\n    def __init__(self, log_service: EelLogService) -> None:\n        self._log = log_service\n\n    MS_TO_BLOCK = 200\n\n    # assumptions: 1. only one thread will be watching the database at a time, and 2. a class instance will be\n    # maintained for the duration of the time watching the database.  However, both will always be true given\n    # how eel works.\n    watching_database = Event()\n\n    def notify_changed(self, db: Database, collection: str, id: str) -> None:\n        # notify any watchers that the collection was modified\n        self._log.debug(f\"notifying watchers of change to {collection} for {id}\")\n        db.db_deltas.insert_one({\"collection\": collection, \"changed_id\": id})\n\n    def watch_database(\n        self,\n        db: Database,\n        id_to_watch: Optional[str],\n        on_found: Callable[[str, str], None],\n    ) -> None:\n        # retrieve a tailable cursor of the deltas in the database to avoid polling\n        cursor = db.db_deltas.find(\n            {}, cursor_type=CursorType.TAILABLE_AWAIT\n        ).max_await_time_ms(self.MS_TO_BLOCK)\n        # burn through all updates that have occurred up till now so next time we only get new ones\n        for _ in cursor:\n            pass\n\n        if self.watching_database.is_set():\n            self.stop_watching()\n\n        # set a semaphore to indicate that we are watching the database\n        self.watching_database.set()\n        while self.watching_database.is_set() and cursor.alive:\n            try:\n                # block for up to a few seconds until someone adds a new delta\n                delta = cursor.next()\n                collection = delta[\"collection\"]\n                changed_id = delta[\"changed_id\"]\n                if id_to_watch is None or id_to_watch == changed_id:\n                    self._log.debug(f\"new delta found for {collection} {changed_id}\")\n                    on_found(collection, changed_id)\n\n            except StopIteration:\n                # the tailable cursor times out after a few seconds and fires a StopIteration exception,\n                # so we need to catch it and restart watching. The sleep is required by eel to allow\n                # it to respond to events such as the very important stop_watching event.\n                eel.sleep(0.8)\n\n    def stop_watching(self) -> None:\n        self.watching_database.clear()\n"
  },
  {
    "path": "src/electionguard_gui/services/decryption_service.py",
    "content": "from datetime import datetime, timezone\nfrom typing import Any, Dict, List, Optional\nfrom bson import ObjectId\nfrom pymongo.database import Database\nfrom electionguard.decryption_share import DecryptionShare\nfrom electionguard.election_polynomial import LagrangeCoefficientsRecord\nfrom electionguard.key_ceremony import ElectionPublicKey\nfrom electionguard.serialize import to_raw\nfrom electionguard.tally import PlaintextTally, PublishedCiphertextTally\nfrom electionguard.type import BallotId\nfrom electionguard_gui.models.decryption_dto import DecryptionDto\nfrom electionguard_gui.models.election_dto import ElectionDto\nfrom electionguard_gui.services.db_watcher_service import DbWatcherService\nfrom electionguard_gui.services.eel_log_service import EelLogService\nfrom electionguard_gui.services.service_base import ServiceBase\nfrom electionguard_gui.services.authorization_service import AuthorizationService\n\n\nclass DecryptionService(ServiceBase):\n    \"\"\"Responsible for functionality related to decryption operations\"\"\"\n\n    _log: EelLogService\n    _auth_service: AuthorizationService\n    _db_watcher_service: DbWatcherService\n\n    def __init__(\n        self,\n        log_service: EelLogService,\n        auth_service: AuthorizationService,\n        db_watcher_service: DbWatcherService,\n    ) -> None:\n        self._log = log_service\n        self._auth_service = auth_service\n        self._db_watcher_service = db_watcher_service\n\n    def create(\n        self,\n        db: Database,\n        election: ElectionDto,\n        decryption_name: str,\n    ) -> str:\n        ballot_count = election.sum_ballots()\n        ballot_upload_count = len(election.ballot_uploads)\n        decryption: dict[str, Any] = {\n            \"election_id\": election.id,\n            \"election_name\": election.election_name,\n            \"ballot_count\": ballot_count,\n            \"ballot_upload_count\": ballot_upload_count,\n            \"key_ceremony_id\": election.key_ceremony_id,\n            \"guardians\": election.guardians,\n            \"quorum\": election.quorum,\n            \"decryption_name\": decryption_name,\n            \"guardians_joined\": [],\n            \"decryption_shares\": [],\n            \"plaintext_spoiled_ballots\": None,\n            \"plaintext_tally\": None,\n            \"lagrange_coefficients\": None,\n            \"ciphertext_tally\": None,\n            \"completed_at\": None,\n            \"created_by\": self._auth_service.get_user_id(),\n            \"created_at\": datetime.now(timezone.utc),\n        }\n        self._log.trace(f\"inserting decryption for: {election.id}\")\n        insert_result = db.decryptions.insert_one(decryption)\n        inserted_id = str(insert_result.inserted_id)\n        self.notify_changed(db, inserted_id)\n        return inserted_id\n\n    def notify_changed(self, db: Database, decryption_id: str) -> None:\n        self._db_watcher_service.notify_changed(db, \"decryptions\", decryption_id)\n\n    def name_exists(self, db: Database, name: str) -> Any:\n        self._log.trace(f\"getting decryption by name: {name}\")\n        decryption = db.decryptions.find_one({\"decryption_name\": name})\n        return decryption is not None\n\n    def get(self, db: Database, decryption_id: str) -> DecryptionDto:\n        self._log.trace(f\"getting decryption {decryption_id}\")\n        decryption = db.decryptions.find_one({\"_id\": ObjectId(decryption_id)})\n        if decryption is None:\n            raise Exception(f\"decryption {decryption_id} not found\")\n        dto = DecryptionDto(decryption)\n        dto.set_can_join(self._auth_service)\n        return dto\n\n    def get_decryption_count(self, db: Database, election_id: str) -> int:\n        self._log.trace(f\"getting decryption count for election {election_id}\")\n        decryption_count: int = db.decryptions.count_documents(\n            {\"election_id\": election_id}\n        )\n        return decryption_count\n\n    def get_active(self, db: Database) -> List[DecryptionDto]:\n        self._log.trace(\"getting all decryptions\")\n        decryption_cursor = db.decryptions.find(\n            {\n                \"completed_at\": None,\n            }\n        )\n        decryption_list = [\n            DecryptionDto(decryption) for decryption in decryption_cursor\n        ]\n        return decryption_list\n\n    def append_guardian_joined(\n        self,\n        db: Database,\n        decryption_id: str,\n        guardian_id: str,\n        decryption_share: DecryptionShare,\n        ballot_shares: Dict[BallotId, Optional[DecryptionShare]],\n        guardian_key: ElectionPublicKey,\n    ) -> None:\n        decryption_share_raw = to_raw(decryption_share)\n        self._log.trace(\n            f\"appending guardian {guardian_id} to decryption {decryption_id}\"\n        )\n        ballot_shares_dict = {\n            ballot_id: to_ballot_share_raw(ballot_share)\n            for (ballot_id, ballot_share) in ballot_shares.items()\n        }\n        db.decryptions.update_one(\n            {\"_id\": ObjectId(decryption_id)},\n            {\n                \"$push\": {\n                    \"decryption_shares\": {\n                        \"guardian_id\": guardian_id,\n                        \"guardian_key\": to_raw(guardian_key),\n                        \"decryption_share\": decryption_share_raw,\n                        \"ballot_shares\": ballot_shares_dict,\n                    }\n                }\n            },\n        )\n        db.decryptions.update_one(\n            {\"_id\": ObjectId(decryption_id)},\n            {\"$push\": {\"guardians_joined\": guardian_id}},\n        )\n\n    def set_decryption_completed(\n        self,\n        db: Database,\n        decryption_id: str,\n        plaintext_tally: PlaintextTally,\n        plaintext_spoiled_ballots: Dict[BallotId, PlaintextTally],\n        lagrange_coefficients: LagrangeCoefficientsRecord,\n        ciphertext_tally: PublishedCiphertextTally,\n    ) -> None:\n        self._log.trace(\"setting decryption completed\")\n\n        plaintext_spoiled_ballots_dict = {\n            str(ballot_id): to_raw(plaintext_tally)\n            for (ballot_id, plaintext_tally) in plaintext_spoiled_ballots.items()\n        }\n\n        db.decryptions.update_one(\n            {\"_id\": ObjectId(decryption_id)},\n            {\n                \"$set\": {\n                    \"completed_at\": datetime.now(timezone.utc),\n                    \"plaintext_tally\": to_raw(plaintext_tally),\n                    \"plaintext_spoiled_ballots\": plaintext_spoiled_ballots_dict,\n                    \"lagrange_coefficients\": to_raw(lagrange_coefficients),\n                    \"ciphertext_tally\": to_raw(ciphertext_tally),\n                }\n            },\n        )\n\n\ndef to_ballot_share_raw(ballot_share: Optional[DecryptionShare]) -> Optional[str]:\n    if ballot_share is None:\n        return None\n    return to_raw(ballot_share)\n"
  },
  {
    "path": "src/electionguard_gui/services/decryption_stages/__init__.py",
    "content": "from electionguard_gui.services.decryption_stages import decryption_s1_join_service\nfrom electionguard_gui.services.decryption_stages import decryption_s2_announce_service\nfrom electionguard_gui.services.decryption_stages import decryption_stage_base\n\nfrom electionguard_gui.services.decryption_stages.decryption_s1_join_service import (\n    DecryptionS1JoinService,\n)\nfrom electionguard_gui.services.decryption_stages.decryption_s2_announce_service import (\n    DecryptionS2AnnounceService,\n)\nfrom electionguard_gui.services.decryption_stages.decryption_stage_base import (\n    DecryptionStageBase,\n    get_tally,\n)\n\n__all__ = [\n    \"DecryptionS1JoinService\",\n    \"DecryptionS2AnnounceService\",\n    \"DecryptionStageBase\",\n    \"decryption_s1_join_service\",\n    \"decryption_s2_announce_service\",\n    \"decryption_stage_base\",\n    \"get_tally\",\n]\n"
  },
  {
    "path": "src/electionguard_gui/services/decryption_stages/decryption_s1_join_service.py",
    "content": "from pymongo.database import Database\nimport eel  # type: ignore[import-untyped]\nfrom electionguard.ballot import BallotBoxState\n\nfrom electionguard_gui.models.decryption_dto import DecryptionDto\nfrom electionguard_gui.services.decryption_stages.decryption_stage_base import (\n    DecryptionStageBase,\n    get_tally,\n)\n\n\nclass DecryptionS1JoinService(DecryptionStageBase):\n    \"\"\"Responsible for the 1st stage during a decryption were guardians join the decryption\"\"\"\n\n    def run(self, db: Database, decryption: DecryptionDto) -> None:\n        _update_decrypt_status(\"Starting tally\")\n        current_user_id = self._auth_service.get_required_user_id()\n        self._log.info(f\"S1: {current_user_id} decrypting  {decryption.decryption_id}\")\n        election = self._election_service.get(db, decryption.election_id)\n        manifest = election.get_manifest()\n        context = election.get_context()\n\n        guardian = self._guardian_service.load_guardian_from_decryption(\n            current_user_id, decryption\n        )\n        ballots = self._ballot_upload_service.get_ballots(\n            db, election.id, _update_decrypt_status\n        )\n        _update_decrypt_status(\"Calculating tally\")\n        self._log.debug(f\"getting tally for {len(ballots)} ballots\")\n        ciphertext_tally = get_tally(manifest, context, ballots, False)\n        self._log.debug(\"computing tally share\")\n        decryption_share = guardian.compute_tally_share(ciphertext_tally, context)\n        if decryption_share is None:\n            raise Exception(\"No decryption_shares found\")\n\n        _update_decrypt_status(\"Calculating spoiled ballots\")\n        self._log.debug(\"decrypting spoiled ballots\")\n        spoiled_ballots = [\n            ballot for ballot in ballots if ballot.state == BallotBoxState.SPOILED\n        ]\n        ballot_shares = guardian.compute_ballot_shares(spoiled_ballots, context)\n        if ballot_shares is None:\n            raise Exception(\"No ballot shares found\")\n        guardian_key = guardian.share_key()\n\n        _update_decrypt_status(\"Finalizing tally\")\n        self._decryption_service.append_guardian_joined(\n            db,\n            decryption.decryption_id,\n            current_user_id,\n            decryption_share,\n            ballot_shares,\n            guardian_key,\n        )\n        self._log.debug(\"Completed tally\")\n        self._decryption_service.notify_changed(db, decryption.decryption_id)\n\n\ndef _update_decrypt_status(status: str) -> None:\n    # pylint: disable=no-member\n    eel.update_decrypt_status(status)  # type: ignore[attr-defined]\n"
  },
  {
    "path": "src/electionguard_gui/services/decryption_stages/decryption_s2_announce_service.py",
    "content": "from pymongo.database import Database\nimport eel  # type: ignore[import-untyped]\nfrom electionguard import DecryptionMediator\nfrom electionguard.ballot import BallotBoxState\nfrom electionguard.election_polynomial import LagrangeCoefficientsRecord\nfrom electionguard_gui.models.decryption_dto import DecryptionDto\nfrom electionguard_gui.services.decryption_stages.decryption_stage_base import (\n    DecryptionStageBase,\n    get_tally,\n)\n\n\nclass DecryptionS2AnnounceService(DecryptionStageBase):\n    \"\"\"Responsible for the 2nd stage in decryptions where the admin announces guardian decryptions\"\"\"\n\n    def should_run(self, db: Database, decryption: DecryptionDto) -> bool:\n        is_admin = self._auth_service.is_admin()\n        all_guardians_joined = len(decryption.guardians_joined) >= decryption.guardians\n        is_completed = decryption.completed_at_utc is not None\n        return is_admin and all_guardians_joined and not is_completed\n\n    def run(self, db: Database, decryption: DecryptionDto) -> None:\n        _update_decrypt_status(\"Starting tally\")\n        self._log.info(f\"S2: Announcing decryption {decryption.decryption_id}\")\n        election = self._election_service.get(db, decryption.election_id)\n        context = election.get_context()\n\n        decryption_mediator = DecryptionMediator(\n            \"decryption-mediator\",\n            context,\n        )\n        decryption_shares = decryption.get_decryption_shares()\n        share_count = len(decryption_shares)\n        current_share = 1\n        for decryption_share_dict in decryption_shares:\n            _update_decrypt_status(f\"Calculating share {current_share}/{share_count}\")\n            self._log.debug(f\"announcing {decryption_share_dict.guardian_id}\")\n            guardian_sequence_number = election.get_guardian_sequence_order(\n                decryption_share_dict.guardian_id\n            )\n            # coefficients will fail validation unless the key is a numeric encoded\n            #       string of the guardian's sequence number\n            decryption_share_dict.guardian_key.owner_id = str(guardian_sequence_number)\n            decryption_mediator.announce(\n                decryption_share_dict.guardian_key,\n                decryption_share_dict.tally_share,\n                decryption_share_dict.ballot_shares,\n            )\n            current_share += 1\n\n        manifest = election.get_manifest()\n        ballots = self._ballot_upload_service.get_ballots(\n            db, election.id, _update_decrypt_status\n        )\n        spoiled_ballots = [\n            ballot for ballot in ballots if ballot.state == BallotBoxState.SPOILED\n        ]\n        _update_decrypt_status(\"Calculating tally\")\n        self._log.debug(f\"getting tally for {len(ballots)} ballots\")\n        ciphertext_tally = get_tally(manifest, context, ballots, False)\n        self._log.debug(\"getting plaintext tally\")\n        plaintext_tally = decryption_mediator.get_plaintext_tally(\n            ciphertext_tally, manifest\n        )\n        if plaintext_tally is None:\n            raise Exception(\"No plaintext tally found\")\n        self._log.debug(\"getting plaintext spoiled ballots\")\n        _update_decrypt_status(\"Processing spoiled ballots\")\n        plaintext_spoiled_ballots = decryption_mediator.get_plaintext_ballots(\n            spoiled_ballots, manifest\n        )\n        if plaintext_spoiled_ballots is None:\n            raise Exception(\"No plaintext spoiled ballots found\")\n\n        _update_decrypt_status(\"Finalizing tally\")\n\n        lagrange_coefficients = _get_lagrange_coefficients(decryption_mediator)\n\n        self._log.debug(\"setting decryption completed\")\n        self._decryption_service.set_decryption_completed(\n            db,\n            decryption.decryption_id,\n            plaintext_tally,\n            plaintext_spoiled_ballots,\n            lagrange_coefficients,\n            ciphertext_tally.publish(),\n        )\n\n        self._decryption_service.notify_changed(db, decryption.decryption_id)\n\n\ndef _get_lagrange_coefficients(\n    decryption_mediator: DecryptionMediator,\n) -> LagrangeCoefficientsRecord:\n    return LagrangeCoefficientsRecord(decryption_mediator.get_lagrange_coefficients())\n\n\ndef _update_decrypt_status(status: str) -> None:\n    # pylint: disable=no-member\n    eel.update_decrypt_status(status)  # type: ignore[attr-defined]\n"
  },
  {
    "path": "src/electionguard_gui/services/decryption_stages/decryption_stage_base.py",
    "content": "from abc import ABC\nfrom typing import List\nfrom pymongo.database import Database\nfrom electionguard.ballot import SubmittedBallot\nfrom electionguard.election import CiphertextElectionContext\nfrom electionguard.manifest import InternalManifest, Manifest\nfrom electionguard.tally import CiphertextTally\nfrom electionguard.scheduler import Scheduler\n\nfrom electionguard_gui.models.decryption_dto import DecryptionDto\nfrom electionguard_gui.services.authorization_service import AuthorizationService\nfrom electionguard_gui.services.ballot_upload_service import BallotUploadService\nfrom electionguard_gui.services.db_service import DbService\nfrom electionguard_gui.services.decryption_service import DecryptionService\nfrom electionguard_gui.services.eel_log_service import EelLogService\nfrom electionguard_gui.services.election_service import ElectionService\nfrom electionguard_gui.services.guardian_service import GuardianService\n\n\nclass DecryptionStageBase(ABC):\n    \"\"\"Responsible for shared functionality across all decryption stages\"\"\"\n\n    _log: EelLogService\n    _db_service: DbService\n    _decryption_service: DecryptionService\n    _auth_service: AuthorizationService\n    _guardian_service: GuardianService\n    _election_service: ElectionService\n    _ballot_upload_service: BallotUploadService\n\n    def __init__(\n        self,\n        log_service: EelLogService,\n        db_service: DbService,\n        decryption_service: DecryptionService,\n        auth_service: AuthorizationService,\n        guardian_service: GuardianService,\n        election_service: ElectionService,\n        ballot_upload_service: BallotUploadService,\n    ):\n        self._db_service = db_service\n        self._decryption_service = decryption_service\n        self._auth_service = auth_service\n        self._log = log_service\n        self._guardian_service = guardian_service\n        self._election_service = election_service\n        self._ballot_upload_service = ballot_upload_service\n\n    # pylint: disable=unused-argument\n    def should_run(self, db: Database, decryption: DecryptionDto) -> bool:\n        return False\n\n    def run(self, db: Database, decryption: DecryptionDto) -> None:\n        pass\n\n\ndef get_tally(\n    manifest: Manifest,\n    context: CiphertextElectionContext,\n    ballots: List[SubmittedBallot],\n    should_validate: bool,\n) -> CiphertextTally:\n    internal_manifest = InternalManifest(manifest)\n\n    tally = CiphertextTally(\n        \"election-results\",\n        internal_manifest,\n        context,\n    )\n    ballot_tuples = [(None, ballot) for ballot in ballots]\n    with Scheduler() as scheduler:\n        tally.batch_append(ballot_tuples, should_validate, scheduler)\n    return tally\n"
  },
  {
    "path": "src/electionguard_gui/services/directory_service.py",
    "content": "import os\n\n\nDOCKER_MOUNT_DIR = \"/egui_mnt\"\n\n\ndef get_export_dir() -> str:\n    return _get_egui_mnt_subdir(\"export\")\n\n\ndef get_data_dir() -> str:\n    return _get_egui_mnt_subdir(\"data\")\n\n\ndef _get_egui_mnt_subdir(subdir_name: str) -> str:\n    egui_mnt_dir = _get_egui_mnt_dir()\n    subdir_path = os.path.join(egui_mnt_dir, subdir_name)\n    os.makedirs(subdir_path, exist_ok=True)\n    return subdir_path\n\n\ndef _get_egui_mnt_dir() -> str:\n    # basically if we're in a docker container\n    if os.path.exists(DOCKER_MOUNT_DIR):\n        return DOCKER_MOUNT_DIR\n    return os.path.join(os.getcwd(), \"egui_mnt\")\n"
  },
  {
    "path": "src/electionguard_gui/services/eel_log_service.py",
    "content": "from datetime import datetime\nimport logging\nfrom os import path, makedirs\nfrom typing import Any\nfrom electionguard.logs import (\n    get_file_handler,\n    log_critical,\n    log_debug,\n    log_error,\n    log_info,\n    log_warning,\n    LOG,\n)\nfrom electionguard_gui.services.directory_service import get_data_dir\nfrom electionguard_gui.services.service_base import ServiceBase\n\n\nclass EelLogService(ServiceBase):\n    \"\"\"A facade for logging. Currently this simply writes to the console without using log levels, but\n    this may eventually be used to log to a file or database.\"\"\"\n\n    def __init__(self) -> None:\n        LOG.set_stream_log_level(logging.DEBUG)\n        file_dir = path.join(get_data_dir(), \"logs\")\n        makedirs(file_dir, exist_ok=True)\n        now = datetime.now().strftime(\"%Y-%m-%d-%H-%M-%S-%f\")\n        file_name = path.join(file_dir, f\"electionguard-{now}.log\")\n        LOG.add_handler(get_file_handler(logging.DEBUG, file_name))\n\n    def trace(self, message: str, *args: Any, **kwargs: Any) -> None:\n        pass\n\n    def debug(self, message: str, *args: Any, **kwargs: Any) -> None:\n        log_debug(message, *args, **kwargs)\n\n    def info(self, message: str, *args: Any, **kwargs: Any) -> None:\n        log_info(message, *args, **kwargs)\n\n    def warn(self, message: str, *args: Any, **kwargs: Any) -> None:\n        log_warning(message, *args, **kwargs)\n\n    def error(self, message: str, exception: Exception) -> None:\n        log_error(\n            f\"{message} '{exception}'\",\n            exc_info=1,\n            extra={\"exception\": exception},\n        )\n\n    def fatal(self, message: str, exception: Exception) -> None:\n        log_critical(\n            f\"{message} '{str(exception)}'\",\n            exc_info=1,\n            extra={\"exception\": exception},\n        )\n"
  },
  {
    "path": "src/electionguard_gui/services/election_service.py",
    "content": "import json\nfrom datetime import datetime, timezone\nfrom bson import ObjectId\nfrom pymongo.database import Database\nfrom electionguard.constants import ElectionConstants\nfrom electionguard.election import CiphertextElectionContext\nfrom electionguard.guardian import GuardianRecord\nfrom electionguard.manifest import Manifest\nfrom electionguard.serialize import to_raw\nfrom electionguard_gui.models import KeyCeremonyDto, ElectionDto\nfrom electionguard_gui.services.eel_log_service import EelLogService\nfrom electionguard_gui.services.service_base import ServiceBase\nfrom electionguard_gui.services.authorization_service import AuthorizationService\n\n\nclass ElectionService(ServiceBase):\n    \"\"\"Responsible for functionality related to elections\"\"\"\n\n    _log: EelLogService\n    _auth_service: AuthorizationService\n\n    def __init__(\n        self, log_service: EelLogService, auth_service: AuthorizationService\n    ) -> None:\n        self._log = log_service\n        self._auth_service = auth_service\n\n    def create_election(\n        self,\n        db: Database,\n        election_name: str,\n        key_ceremony: KeyCeremonyDto,\n        manifest: Manifest,\n        context: CiphertextElectionContext,\n        constants: ElectionConstants,\n        guardian_records: list[GuardianRecord],\n        encryption_package_file: str,\n        election_url: str,\n    ) -> str:\n        context_raw = to_raw(context)\n        manifest_raw = to_raw(manifest)\n        constants_raw = to_raw(constants)\n        guardian_records_raw = to_raw(guardian_records)\n        election = {\n            \"election_name\": election_name,\n            \"key_ceremony_id\": key_ceremony.id,\n            \"guardians\": context.number_of_guardians,\n            \"quorum\": context.quorum,\n            \"election_url\": election_url,\n            \"manifest\": {\n                \"raw\": manifest_raw,\n                \"name\": manifest.get_name(),\n                \"scope\": manifest.election_scope_id,\n                \"geopolitical_units\": len(manifest.geopolitical_units),\n                \"parties\": len(manifest.parties),\n                \"candidates\": len(manifest.candidates),\n                \"contests\": len(manifest.contests),\n                \"ballot_styles\": len(manifest.ballot_styles),\n            },\n            \"context\": context_raw,\n            \"constants\": constants_raw,\n            \"guardian_records\": guardian_records_raw,\n            # Mongo has a max size of 16MG, consider using GridFS https://www.mongodb.com/docs/manual/core/gridfs/\n            \"encryption_package_file\": encryption_package_file,\n            \"ballot_uploads\": [],\n            \"decryptions\": [],\n            \"created_by\": self._auth_service.get_user_id(),\n            \"created_at\": datetime.now(timezone.utc),\n        }\n        self._log.trace(f\"inserting election: {election}\")\n        inserted_id = db.elections.insert_one(election).inserted_id\n        return str(inserted_id)\n\n    def get(self, db: Database, election_id: str) -> ElectionDto:\n        self._log.trace(f\"getting election {election_id}\")\n        election = db.elections.find_one({\"_id\": ObjectId(election_id)})\n        if not election:\n            raise Exception(f\"Election not found: {election_id}\")\n        return ElectionDto(election)\n\n    def get_all(self, db: Database) -> list[ElectionDto]:\n        self._log.trace(\"getting all elections\")\n        elections = db.elections.find()\n        return [ElectionDto(election) for election in elections]\n\n    def append_ballot_upload(\n        self,\n        db: Database,\n        election_id: str,\n        ballot_upload_id: str,\n        device_file_contents: str,\n        created_at: datetime,\n    ) -> None:\n        self._log.trace(\n            f\"appending ballot upload {ballot_upload_id} to election {election_id}\"\n        )\n        device_file_json = json.loads(device_file_contents)\n        db.elections.update_one(\n            {\"_id\": ObjectId(election_id)},\n            {\n                \"$push\": {\n                    \"ballot_uploads\": {\n                        \"ballot_upload_id\": ballot_upload_id,\n                        \"device_file_contents\": device_file_contents,\n                        \"device_id\": device_file_json[\"device_id\"],\n                        \"launch_code\": device_file_json[\"launch_code\"],\n                        \"location\": device_file_json[\"location\"],\n                        \"session_id\": device_file_json[\"session_id\"],\n                        \"ballot_count\": 0,\n                        \"created_at\": created_at,\n                    }\n                }\n            },\n        )\n\n    def append_decryption(\n        self, db: Database, election_id: str, decryption_id: str, name: str\n    ) -> None:\n        self._log.trace(\n            f\"appending decryption {decryption_id} to election {election_id}\"\n        )\n        db.elections.update_one(\n            {\"_id\": ObjectId(election_id)},\n            {\n                \"$push\": {\n                    \"decryptions\": {\n                        \"decryption_id\": decryption_id,\n                        \"name\": name,\n                        \"created_at\": datetime.now(timezone.utc),\n                    }\n                }\n            },\n        )\n\n    def increment_ballot_upload_ballot_count(\n        self, db: Database, election_id: str, ballot_upload_id: str\n    ) -> None:\n        self._log.trace(\n            f\"incrementing ballot upload {ballot_upload_id} ballot count in election {election_id}\"\n        )\n        db.elections.update_one(\n            {\n                \"_id\": ObjectId(election_id),\n                \"ballot_uploads.ballot_upload_id\": ballot_upload_id,\n            },\n            {\"$inc\": {\"ballot_uploads.$.ballot_count\": 1}},\n        )\n"
  },
  {
    "path": "src/electionguard_gui/services/export_service.py",
    "content": "import os\nfrom electionguard_gui.services.directory_service import get_data_dir, get_export_dir\n\n\ndef get_export_locations() -> list[str]:\n    export_dir = get_export_dir()\n    if os.name == \"nt\":\n        drives = get_removable_drives()\n        return [export_dir, _get_download_path(), get_data_dir()] + drives\n    return [export_dir]\n\n\ndef get_removable_drives() -> list[str]:\n    dl = \"DEFGHIJKLMNOPQRSTUVWXYZ\"\n    drives = [f\"{d}:\\\\\" for d in dl if os.path.exists(f\"{d}:\")]\n    return drives\n\n\ndef _get_download_path() -> str:\n    \"\"\"\n    Returns the default downloads path for linux or windows.\n    Code from https://pyquestions.com/python-finding-the-user-s-downloads-folder\n    \"\"\"\n    if os.name == \"nt\":\n        # pylint: disable=import-outside-toplevel\n        # pylint: disable=import-error\n        import winreg  # type: ignore[import-not-found]\n\n        sub_key = (\n            r\"SOFTWARE\\\\Microsoft\\Windows\\\\CurrentVersion\\\\Explorer\\\\Shell Folders\"\n        )\n        downloads_guid = \"{374DE290-123F-4565-9164-39C4925E467B}\"\n        with winreg.OpenKey(  # type: ignore[attr-defined]\n            winreg.HKEY_CURRENT_USER, sub_key  # type: ignore[attr-defined]\n        ) as key:\n            location = winreg.QueryValueEx(  # type: ignore[attr-defined]\n                key, downloads_guid\n            )[0]\n        return str(location)\n    return os.path.join(os.path.expanduser(\"~\"), \"downloads\")\n"
  },
  {
    "path": "src/electionguard_gui/services/guardian_service.py",
    "content": "from os import path\nfrom electionguard.serialize import from_file, to_file\nfrom electionguard.guardian import Guardian, PrivateGuardianRecord\nfrom electionguard.key_ceremony import CeremonyDetails\nfrom electionguard.key_ceremony_mediator import KeyCeremonyMediator\nfrom electionguard_gui.models.decryption_dto import DecryptionDto\nfrom electionguard_gui.models.key_ceremony_dto import KeyCeremonyDto\nfrom electionguard_gui.services.directory_service import get_data_dir\nfrom electionguard_gui.services.eel_log_service import EelLogService\nfrom electionguard_gui.services.service_base import ServiceBase\nfrom electionguard_tools.helpers.export import GUARDIAN_PREFIX\n\n\nclass GuardianService(ServiceBase):\n    \"\"\"Responsible for functionality related to guardians\"\"\"\n\n    _log: EelLogService\n\n    def __init__(self, log_service: EelLogService) -> None:\n        self._log = log_service\n\n    def save_guardian(self, guardian: Guardian, key_ceremony: KeyCeremonyDto) -> None:\n        private_guardian_record = guardian.export_private_data()\n        file_name = GUARDIAN_PREFIX + private_guardian_record.guardian_id\n        file_path = path.join(get_data_dir(), \"gui_private_keys\", key_ceremony.id)\n        file = to_file(private_guardian_record, file_name, file_path)\n        self._log.warn(\n            f\"Guardian private data saved to {file}. This data should be carefully protected and never shared.\"\n        )\n\n    def _load_guardian(\n        self, guardian_id: str, key_ceremony_id: str, guardian_count: int, quorum: int\n    ) -> Guardian:\n        file_name = GUARDIAN_PREFIX + guardian_id + \".json\"\n        file_path = path.join(\n            get_data_dir(), \"gui_private_keys\", key_ceremony_id, file_name\n        )\n        self._log.debug(f\"loading guardian from {file_path}\")\n        if not path.exists(file_path):\n            raise Exception(f\"Guardian file not found: {file_path}\")\n        private_guardian_record = from_file(PrivateGuardianRecord, file_path)\n        return Guardian.from_private_record(\n            private_guardian_record,\n            guardian_count,\n            quorum,\n        )\n\n    def load_guardian_from_decryption(\n        self, guardian_id: str, decryption: DecryptionDto\n    ) -> Guardian:\n        if not decryption.key_ceremony_id:\n            raise Exception(\"key_ceremony_id is required\")\n        return self._load_guardian(\n            guardian_id,\n            decryption.key_ceremony_id,\n            decryption.guardians,\n            decryption.quorum,\n        )\n\n    def load_guardian_from_key_ceremony(\n        self, guardian_id: str, key_ceremony: KeyCeremonyDto\n    ) -> Guardian:\n        return self._load_guardian(\n            guardian_id,\n            key_ceremony.id,\n            key_ceremony.guardian_count,\n            key_ceremony.quorum,\n        )\n\n    def load_other_keys(\n        self, key_ceremony: KeyCeremonyDto, current_user_id: str, guardian: Guardian\n    ) -> None:\n        current_user_other_keys = key_ceremony.find_other_keys_for_user(current_user_id)\n        for other_key in current_user_other_keys:\n            other_user = other_key.owner_id\n            self._log.debug(f\"saving other_key from {other_user} for {current_user_id}\")\n            guardian.save_guardian_key(other_key)\n\n\ndef make_guardian(\n    user_id: str, guardian_number: int, key_ceremony: KeyCeremonyDto\n) -> Guardian:\n    return Guardian.from_nonce(\n        user_id,\n        guardian_number,\n        key_ceremony.guardian_count,\n        key_ceremony.quorum,\n    )\n\n\ndef make_mediator(key_ceremony: KeyCeremonyDto) -> KeyCeremonyMediator:\n    quorum = key_ceremony.quorum\n    guardian_count = key_ceremony.guardian_count\n    ceremony_details = CeremonyDetails(guardian_count, quorum)\n    mediator: KeyCeremonyMediator = KeyCeremonyMediator(\"mediator_1\", ceremony_details)\n    return mediator\n\n\ndef announce_guardians(\n    key_ceremony: KeyCeremonyDto, mediator: KeyCeremonyMediator\n) -> None:\n    for guardian_id in key_ceremony.guardians_joined:\n        key = key_ceremony.find_key(guardian_id)\n        mediator.announce(key)\n"
  },
  {
    "path": "src/electionguard_gui/services/gui_setup_input_retrieval_step.py",
    "content": "from typing import Optional\nfrom electionguard import Manifest\nfrom electionguard.guardian import Guardian\nfrom electionguard_cli import SetupInputs\nfrom electionguard_cli.setup_election import SetupInputRetrievalStep\n\n\nclass GuiSetupInputRetrievalStep(SetupInputRetrievalStep):\n    \"\"\"Responsible for retrieving and parsing user provided inputs for the GUI's setup election command.\"\"\"\n\n    def get_gui_inputs(\n        self,\n        guardian_count: int,\n        quorum: int,\n        guardians: list[Guardian],\n        verification_url: Optional[str],\n        manifest_raw: str,\n    ) -> SetupInputs:\n\n        self.print_header(\"Retrieving Inputs\")\n        manifest: Manifest = self._get_manifest_raw(manifest_raw)\n        return SetupInputs(\n            guardian_count, quorum, guardians, manifest, verification_url, force=True\n        )\n"
  },
  {
    "path": "src/electionguard_gui/services/key_ceremony_service.py",
    "content": "from typing import Any, List\nfrom datetime import datetime, timezone\nfrom pymongo.database import Database\nfrom bson import ObjectId\nfrom electionguard.key_ceremony import (\n    ElectionJointKey,\n    ElectionPartialKeyBackup,\n    ElectionPartialKeyVerification,\n    ElectionPublicKey,\n)\nfrom electionguard_gui.models.key_ceremony_dto import KeyCeremonyDto\nfrom electionguard_gui.services.authorization_service import AuthorizationService\nfrom electionguard_gui.services.db_serialization_service import (\n    backup_to_dict,\n    joint_key_to_dict,\n    public_key_to_dict,\n    verification_to_dict,\n)\nfrom electionguard_gui.services.db_watcher_service import DbWatcherService\nfrom electionguard_gui.services.eel_log_service import EelLogService\n\nfrom electionguard_gui.services.service_base import ServiceBase\n\n\nclass KeyCeremonyService(ServiceBase):\n    \"\"\"Responsible for functionality related to key ceremonies\"\"\"\n\n    _log: EelLogService\n    _auth_service: AuthorizationService\n    _db_watcher_service: DbWatcherService\n\n    def __init__(\n        self,\n        log_service: EelLogService,\n        auth_service: AuthorizationService,\n        db_watcher_service: DbWatcherService,\n    ) -> None:\n        self._log = log_service\n        self._auth_service = auth_service\n        self._db_watcher_service = db_watcher_service\n\n    def create(\n        self, db: Database, key_ceremony_name: str, guardian_count: int, quorum: int\n    ) -> str:\n        key_ceremony = {\n            \"key_ceremony_name\": key_ceremony_name,\n            \"guardian_count\": guardian_count,\n            \"quorum\": quorum,\n            \"guardians_joined\": [],\n            \"keys\": [],\n            \"guardians_keys\": [],\n            \"other_keys\": [],\n            \"backups\": [],\n            \"shared_backups\": [],\n            \"verifications\": [],\n            \"joint_key\": None,\n            \"created_by\": self._auth_service.get_user_id(),\n            \"created_at\": datetime.now(timezone.utc),\n            \"completed_at\": None,\n        }\n        inserted_id = db.key_ceremonies.insert_one(key_ceremony).inserted_id\n        self._log.debug(f\"created '{key_ceremony_name}' record, id: {inserted_id}\")\n        return str(inserted_id)\n\n    def notify_changed(self, db: Database, key_ceremony_id: str) -> None:\n        self._db_watcher_service.notify_changed(db, \"key_ceremonies\", key_ceremony_id)\n\n    def get(self, db: Database, id: str) -> KeyCeremonyDto:\n        key_ceremony_dict = db.key_ceremonies.find_one({\"_id\": ObjectId(id)})\n        if key_ceremony_dict is None:\n            raise ValueError(f\"key ceremony '{id}' not found\")\n        dto = KeyCeremonyDto(key_ceremony_dict)\n        dto.set_can_join(self._auth_service)\n        return dto\n\n    def append_guardian_joined(\n        self, db: Database, key_ceremony_id: str, guardian_id: str\n    ) -> None:\n        db.key_ceremonies.update_one(\n            {\"_id\": ObjectId(key_ceremony_id)},\n            {\"$push\": {\"guardians_joined\": guardian_id}},\n        )\n\n    def append_key(\n        self, db: Database, key_ceremony_id: str, key: ElectionPublicKey\n    ) -> None:\n        db.key_ceremonies.update_one(\n            {\"_id\": ObjectId(key_ceremony_id)},\n            {\"$push\": {\"keys\": public_key_to_dict(key)}},\n        )\n\n    def append_other_key(self, db: Database, key_ceremony_id: str, keys: Any) -> None:\n        db.key_ceremonies.update_one(\n            {\"_id\": ObjectId(key_ceremony_id)},\n            {\"$push\": {\"other_keys\": {\"$each\": keys}}},\n        )\n\n    def append_backups(\n        self,\n        db: Database,\n        key_ceremony_id: str,\n        backups: List[ElectionPartialKeyBackup],\n    ) -> None:\n        backups_dict = [backup_to_dict(backup) for backup in backups]\n        db.key_ceremonies.update_one(\n            {\"_id\": ObjectId(key_ceremony_id)},\n            {\"$push\": {\"backups\": {\"$each\": backups_dict}}},\n        )\n\n    def append_shared_backups(\n        self,\n        db: Database,\n        key_ceremony_id: str,\n        shared_backups: List[Any],\n    ) -> None:\n        db.key_ceremonies.update_one(\n            {\"_id\": ObjectId(key_ceremony_id)},\n            {\"$push\": {\"shared_backups\": {\"$each\": shared_backups}}},\n        )\n\n    def append_verifications(\n        self,\n        db: Database,\n        key_ceremony_id: str,\n        verifications: List[ElectionPartialKeyVerification],\n    ) -> None:\n        verifications_dict = [\n            verification_to_dict(verification) for verification in verifications\n        ]\n        db.key_ceremonies.update_one(\n            {\"_id\": ObjectId(key_ceremony_id)},\n            {\"$push\": {\"verifications\": {\"$each\": verifications_dict}}},\n        )\n\n    def append_joint_key(\n        self,\n        db: Database,\n        key_ceremony_id: str,\n        joint_key: ElectionJointKey,\n    ) -> None:\n        joint_key_dict = joint_key_to_dict(joint_key)\n        db.key_ceremonies.update_one(\n            {\"_id\": ObjectId(key_ceremony_id)},\n            {\"$set\": {\"joint_key\": joint_key_dict}},\n        )\n\n    def set_complete(\n        self,\n        db: Database,\n        key_ceremony_id: str,\n    ) -> None:\n        db.key_ceremonies.update_one(\n            {\"_id\": ObjectId(key_ceremony_id)},\n            {\"$set\": {\"completed_at\": datetime.now(timezone.utc)}},\n        )\n\n    def get_completed(self, db: Database) -> List[KeyCeremonyDto]:\n        key_ceremonies = db.key_ceremonies.find({\"completed_at\": {\"$ne\": None}})\n        return [KeyCeremonyDto(key_ceremony) for key_ceremony in key_ceremonies]\n\n    def get_active(self, db: Database) -> List[KeyCeremonyDto]:\n        key_ceremonies = db.key_ceremonies.find({\"completed_at\": {\"$eq\": None}})\n        return [KeyCeremonyDto(key_ceremony) for key_ceremony in key_ceremonies]\n\n    def exists(self, db: Database, key_ceremony_name: str) -> bool:\n        existing_key_ceremonies = db.key_ceremonies.find_one(\n            {\"key_ceremony_name\": key_ceremony_name}\n        )\n        return existing_key_ceremonies is not None\n\n\ndef get_guardian_number(key_ceremony: KeyCeremonyDto, guardian_id: str) -> int:\n    \"\"\"Returns the position of a guardian within the array of guardians that have joined\n    a key ceremony. This technique is important because it avoids concurrency problems\n    that could arise if simply retrieving the number of guardians\"\"\"\n    guardian_num = 1\n    for guardian in key_ceremony.guardians_joined:\n        if guardian == guardian_id:\n            return guardian_num\n        guardian_num += 1\n    raise ValueError(f\"guardian '{guardian_id}' not found\")\n"
  },
  {
    "path": "src/electionguard_gui/services/key_ceremony_stages/__init__.py",
    "content": "from electionguard_gui.services.key_ceremony_stages import key_ceremony_s1_join_service\nfrom electionguard_gui.services.key_ceremony_stages import (\n    key_ceremony_s2_announce_service,\n)\nfrom electionguard_gui.services.key_ceremony_stages import (\n    key_ceremony_s3_make_backup_service,\n)\nfrom electionguard_gui.services.key_ceremony_stages import (\n    key_ceremony_s4_share_backup_service,\n)\nfrom electionguard_gui.services.key_ceremony_stages import (\n    key_ceremony_s5_verify_backup_service,\n)\nfrom electionguard_gui.services.key_ceremony_stages import (\n    key_ceremony_s6_publish_key_service,\n)\nfrom electionguard_gui.services.key_ceremony_stages import key_ceremony_stage_base\n\nfrom electionguard_gui.services.key_ceremony_stages.key_ceremony_s1_join_service import (\n    KeyCeremonyS1JoinService,\n)\nfrom electionguard_gui.services.key_ceremony_stages.key_ceremony_s2_announce_service import (\n    KeyCeremonyS2AnnounceService,\n)\nfrom electionguard_gui.services.key_ceremony_stages.key_ceremony_s3_make_backup_service import (\n    KeyCeremonyS3MakeBackupService,\n)\nfrom electionguard_gui.services.key_ceremony_stages.key_ceremony_s4_share_backup_service import (\n    KeyCeremonyS4ShareBackupService,\n)\nfrom electionguard_gui.services.key_ceremony_stages.key_ceremony_s5_verify_backup_service import (\n    KeyCeremonyS5VerifyBackupService,\n)\nfrom electionguard_gui.services.key_ceremony_stages.key_ceremony_s6_publish_key_service import (\n    KeyCeremonyS6PublishKeyService,\n)\nfrom electionguard_gui.services.key_ceremony_stages.key_ceremony_stage_base import (\n    KeyCeremonyStageBase,\n)\n\n__all__ = [\n    \"KeyCeremonyS1JoinService\",\n    \"KeyCeremonyS2AnnounceService\",\n    \"KeyCeremonyS3MakeBackupService\",\n    \"KeyCeremonyS4ShareBackupService\",\n    \"KeyCeremonyS5VerifyBackupService\",\n    \"KeyCeremonyS6PublishKeyService\",\n    \"KeyCeremonyStageBase\",\n    \"key_ceremony_s1_join_service\",\n    \"key_ceremony_s2_announce_service\",\n    \"key_ceremony_s3_make_backup_service\",\n    \"key_ceremony_s4_share_backup_service\",\n    \"key_ceremony_s5_verify_backup_service\",\n    \"key_ceremony_s6_publish_key_service\",\n    \"key_ceremony_stage_base\",\n]\n"
  },
  {
    "path": "src/electionguard_gui/services/key_ceremony_stages/key_ceremony_s1_join_service.py",
    "content": "from pymongo.database import Database\n\nfrom electionguard_gui.models.key_ceremony_dto import KeyCeremonyDto\nfrom electionguard_gui.services.key_ceremony_service import get_guardian_number\nfrom electionguard_gui.services.key_ceremony_stages.key_ceremony_stage_base import (\n    KeyCeremonyStageBase,\n)\nfrom electionguard_gui.services.guardian_service import make_guardian\n\n\nclass KeyCeremonyS1JoinService(KeyCeremonyStageBase):\n    \"\"\"Responsible for stage 1 of the key ceremony where guardians join\"\"\"\n\n    def run(self, db: Database, key_ceremony: KeyCeremonyDto) -> None:\n        key_ceremony_id = key_ceremony.id\n        user_id = self._auth_service.get_required_user_id()\n        self._key_ceremony_service.append_guardian_joined(db, key_ceremony_id, user_id)\n        # refresh key ceremony to get the list of guardians with the authoritative order they joined in\n        key_ceremony = self._key_ceremony_service.get(db, key_ceremony_id)\n        guardian_number = get_guardian_number(key_ceremony, user_id)\n        self.log.debug(\n            f\"user {user_id} about to join key ceremony {key_ceremony_id} as guardian #{guardian_number}\"\n        )\n        guardian = make_guardian(user_id, guardian_number, key_ceremony)\n        self._guardian_service.save_guardian(guardian, key_ceremony)\n        public_key = guardian.share_key()\n        self._key_ceremony_service.append_key(db, key_ceremony_id, public_key)\n        self.log.debug(\n            f\"{user_id} joined key ceremony {key_ceremony_id} as guardian #{guardian_number}\"\n        )\n        self._key_ceremony_service.notify_changed(db, key_ceremony_id)\n"
  },
  {
    "path": "src/electionguard_gui/services/key_ceremony_stages/key_ceremony_s2_announce_service.py",
    "content": "from typing import Any, List\nfrom pymongo.database import Database\nfrom electionguard.key_ceremony import ElectionPublicKey\nfrom electionguard.utils import get_optional\nfrom electionguard_gui.models.key_ceremony_dto import KeyCeremonyDto\nfrom electionguard_gui.models.key_ceremony_states import KeyCeremonyStates\nfrom electionguard_gui.services.db_serialization_service import public_key_to_dict\nfrom electionguard_gui.services.guardian_service import (\n    announce_guardians,\n    make_mediator,\n)\nfrom electionguard_gui.services.key_ceremony_stages.key_ceremony_stage_base import (\n    KeyCeremonyStageBase,\n)\n\n\nclass KeyCeremonyS2AnnounceService(KeyCeremonyStageBase):\n    \"\"\"Responsible for stage 2 of the key ceremony where admins announce the key ceremony\"\"\"\n\n    def should_run(\n        self, key_ceremony: KeyCeremonyDto, state: KeyCeremonyStates\n    ) -> bool:\n        is_admin = self._auth_service.is_admin()\n        should_run: bool = is_admin and state == KeyCeremonyStates.PendingAdminAnnounce\n        return should_run\n\n    def run(self, db: Database, key_ceremony: KeyCeremonyDto) -> None:\n        key_ceremony_id = key_ceremony.id\n        self.log.info(\"all guardians have joined, announcing guardians\")\n        other_keys = self.announce(key_ceremony)\n        self.log.debug(\"saving other_keys\")\n        self._key_ceremony_service.append_other_key(db, key_ceremony_id, other_keys)\n        self._key_ceremony_service.notify_changed(db, key_ceremony_id)\n\n    def announce(self, key_ceremony: KeyCeremonyDto) -> List[dict[str, Any]]:\n        other_keys = []\n        mediator = make_mediator(key_ceremony)\n        announce_guardians(key_ceremony, mediator)\n        for guardian_id in key_ceremony.guardians_joined:\n            self.log.debug(f\"announcing guardian {guardian_id}\")\n            other_guardian_keys: List[ElectionPublicKey] = get_optional(\n                mediator.share_announced(guardian_id)\n            )\n            other_keys.append(\n                {\n                    \"owner_id\": guardian_id,\n                    \"other_keys\": [\n                        public_key_to_dict(key) for key in other_guardian_keys\n                    ],\n                }\n            )\n        return other_keys\n"
  },
  {
    "path": "src/electionguard_gui/services/key_ceremony_stages/key_ceremony_s3_make_backup_service.py",
    "content": "from pymongo.database import Database\nfrom electionguard_gui.models.key_ceremony_dto import KeyCeremonyDto\nfrom electionguard_gui.models.key_ceremony_states import KeyCeremonyStates\nfrom electionguard_gui.services.key_ceremony_stages.key_ceremony_stage_base import (\n    KeyCeremonyStageBase,\n)\n\n\nclass KeyCeremonyS3MakeBackupService(KeyCeremonyStageBase):\n    \"\"\"Responsible for stage 3 of the key ceremony where guardians create backups to send to the admin.\"\"\"\n\n    def should_run(\n        self, key_ceremony: KeyCeremonyDto, state: KeyCeremonyStates\n    ) -> bool:\n        is_guardian = not self._auth_service.is_admin()\n        current_user_id = self._auth_service.get_required_user_id()\n        current_user_backups = key_ceremony.get_backup_count_for_user(current_user_id)\n        current_user_backup_exists = current_user_backups > 0\n        return (\n            is_guardian\n            and state == KeyCeremonyStates.PendingGuardianBackups\n            and not current_user_backup_exists\n        )\n\n    def run(self, db: Database, key_ceremony: KeyCeremonyDto) -> None:\n        current_user_id = self._auth_service.get_required_user_id()\n        key_ceremony_id = key_ceremony.id\n        self.log.debug(f\"creating backups for guardian {current_user_id}\")\n        guardian = self._guardian_service.load_guardian_from_key_ceremony(\n            current_user_id, key_ceremony\n        )\n        self._guardian_service.load_other_keys(key_ceremony, current_user_id, guardian)\n        guardian.generate_election_partial_key_backups()\n        backups = guardian.share_election_partial_key_backups()\n        self._key_ceremony_service.append_backups(db, key_ceremony_id, backups)\n        # notify the admin that a new guardian has backups\n        self._key_ceremony_service.notify_changed(db, key_ceremony_id)\n"
  },
  {
    "path": "src/electionguard_gui/services/key_ceremony_stages/key_ceremony_s4_share_backup_service.py",
    "content": "from typing import Any, List\nfrom pymongo.database import Database\nfrom electionguard_gui.models.key_ceremony_dto import KeyCeremonyDto\nfrom electionguard_gui.models.key_ceremony_states import KeyCeremonyStates\nfrom electionguard_gui.services.db_serialization_service import backup_to_dict\nfrom electionguard_gui.services.guardian_service import (\n    announce_guardians,\n    make_mediator,\n)\nfrom electionguard_gui.services.key_ceremony_stages.key_ceremony_stage_base import (\n    KeyCeremonyStageBase,\n)\n\n\nclass KeyCeremonyS4ShareBackupService(KeyCeremonyStageBase):\n    \"\"\"\n    Responsible for stage 4 of the key ceremony where admins receive backups and share them\n    back to guardians for verification.\n    \"\"\"\n\n    def should_run(\n        self, key_ceremony: KeyCeremonyDto, state: KeyCeremonyStates\n    ) -> bool:\n        is_admin: bool = self._auth_service.is_admin()\n        return is_admin and state == KeyCeremonyStates.PendingAdminToShareBackups\n\n    def run(self, db: Database, key_ceremony: KeyCeremonyDto) -> None:\n        current_user_id = self._auth_service.get_user_id()\n        self.log.debug(f\"sharing backups for admin {current_user_id}\")\n        shared_backups = self.share_backups(key_ceremony)\n        self._key_ceremony_service.append_shared_backups(\n            db, key_ceremony.id, shared_backups\n        )\n        self._key_ceremony_service.notify_changed(db, key_ceremony.id)\n\n    def share_backups(self, key_ceremony: KeyCeremonyDto) -> List[Any]:\n        mediator = make_mediator(key_ceremony)\n        announce_guardians(key_ceremony, mediator)\n        mediator.receive_backups(key_ceremony.get_backups())\n        shared_backups = []\n        for guardian_id in key_ceremony.guardians_joined:\n            self.log.debug(f\"sharing backups for guardian {guardian_id}\")\n            guardian_backups = mediator.share_backups(guardian_id)\n            if guardian_backups is None:\n                raise Exception(\"Error sharing backups\")\n            backups_as_dict = [backup_to_dict(backup) for backup in guardian_backups]\n            shared_backups.append({\"owner_id\": guardian_id, \"backups\": backups_as_dict})\n        return shared_backups\n"
  },
  {
    "path": "src/electionguard_gui/services/key_ceremony_stages/key_ceremony_s5_verify_backup_service.py",
    "content": "from typing import List\nfrom pymongo.database import Database\nfrom electionguard.key_ceremony import ElectionPartialKeyVerification\nfrom electionguard_gui.models.key_ceremony_dto import KeyCeremonyDto\nfrom electionguard_gui.models.key_ceremony_states import KeyCeremonyStates\nfrom electionguard_gui.services.key_ceremony_stages.key_ceremony_stage_base import (\n    KeyCeremonyStageBase,\n)\n\n\nclass KeyCeremonyS5VerifyBackupService(KeyCeremonyStageBase):\n    \"\"\"Responsible for stage 5 of the key ceremony where guardians verify backups.\"\"\"\n\n    def should_run(\n        self, key_ceremony: KeyCeremonyDto, state: KeyCeremonyStates\n    ) -> bool:\n        is_guardian = not self._auth_service.is_admin()\n        current_user_id = self._auth_service.get_required_user_id()\n        current_user_verifications = key_ceremony.get_verification_count_for_user(\n            current_user_id\n        )\n        current_user_verification_exists = current_user_verifications > 0\n        return (\n            is_guardian\n            and state == KeyCeremonyStates.PendingGuardiansVerifyBackups\n            and not current_user_verification_exists\n        )\n\n    def run(self, db: Database, key_ceremony: KeyCeremonyDto) -> None:\n        current_user_id = self._auth_service.get_required_user_id()\n        shared_backups = key_ceremony.get_shared_backups_for_guardian(current_user_id)\n        guardian = self._guardian_service.load_guardian_from_key_ceremony(\n            current_user_id, key_ceremony\n        )\n        self._guardian_service.load_other_keys(key_ceremony, current_user_id, guardian)\n        verifications: List[ElectionPartialKeyVerification] = []\n        for backup in shared_backups:\n            self.log.debug(\n                f\"verifying backup from {backup.owner_id} to {current_user_id}\"\n            )\n            guardian.save_election_partial_key_backup(backup)\n            verification = guardian.verify_election_partial_key_backup(backup.owner_id)\n            if verification is None:\n                raise Exception(\"Error verifying backup\")\n            verifications.append(verification)\n        self._key_ceremony_service.append_verifications(\n            db, key_ceremony.id, verifications\n        )\n        # notify the admin that a new verification was created\n        self._key_ceremony_service.notify_changed(db, key_ceremony.id)\n"
  },
  {
    "path": "src/electionguard_gui/services/key_ceremony_stages/key_ceremony_s6_publish_key_service.py",
    "content": "from pymongo.database import Database\nfrom electionguard_gui.models.key_ceremony_dto import KeyCeremonyDto\nfrom electionguard_gui.models.key_ceremony_states import KeyCeremonyStates\nfrom electionguard_gui.services.guardian_service import (\n    announce_guardians,\n    make_mediator,\n)\nfrom electionguard_gui.services.key_ceremony_stages.key_ceremony_stage_base import (\n    KeyCeremonyStageBase,\n)\n\n\nclass KeyCeremonyS6PublishKeyService(KeyCeremonyStageBase):\n    \"\"\"\n    Responsible for stage 6 of the key ceremony where admins receive verifications, publish\n    a joint key, and generate a context.\n    \"\"\"\n\n    def should_run(\n        self, key_ceremony: KeyCeremonyDto, state: KeyCeremonyStates\n    ) -> bool:\n        is_admin = self._auth_service.is_admin()\n        return is_admin and state == KeyCeremonyStates.PendingAdminToPublishJointKey\n\n    def run(self, db: Database, key_ceremony: KeyCeremonyDto) -> None:\n        current_user_id = self._auth_service.get_user_id()\n        self.log.debug(f\"receiving verifications for admin {current_user_id}\")\n        mediator = make_mediator(key_ceremony)\n        announce_guardians(key_ceremony, mediator)\n        mediator.receive_backups(key_ceremony.get_backups())\n        verifications = key_ceremony.get_verifications()\n        mediator.receive_backup_verifications(verifications)\n        election_joint_key = mediator.publish_joint_key()\n        if election_joint_key is None:\n            raise Exception(\"Failed to publish joint key\")\n        self.log.info(f\"joint key published: {election_joint_key.joint_public_key}\")\n        self._key_ceremony_service.append_joint_key(\n            db, key_ceremony.id, election_joint_key\n        )\n        self._key_ceremony_service.set_complete(db, key_ceremony.id)\n        # notify everyone that verifications completed and the joint key published\n        self._key_ceremony_service.notify_changed(db, key_ceremony.id)\n"
  },
  {
    "path": "src/electionguard_gui/services/key_ceremony_stages/key_ceremony_stage_base.py",
    "content": "from abc import ABC, abstractmethod\nfrom pymongo.database import Database\n\nfrom electionguard_gui.models.key_ceremony_dto import KeyCeremonyDto\nfrom electionguard_gui.models.key_ceremony_states import KeyCeremonyStates\nfrom electionguard_gui.services.authorization_service import AuthorizationService\nfrom electionguard_gui.services.db_service import DbService\nfrom electionguard_gui.services.eel_log_service import EelLogService\nfrom electionguard_gui.services.guardian_service import GuardianService\nfrom electionguard_gui.services.key_ceremony_service import KeyCeremonyService\nfrom electionguard_gui.services.key_ceremony_state_service import (\n    KeyCeremonyStateService,\n)\n\n\nclass KeyCeremonyStageBase(ABC):\n    \"\"\"Responsible for shared functionality across all key ceremony stages\"\"\"\n\n    log: EelLogService\n    _db_service: DbService\n    _key_ceremony_service: KeyCeremonyService\n    _auth_service: AuthorizationService\n    _key_ceremony_state_service: KeyCeremonyStateService\n    _guardian_service: GuardianService\n\n    def __init__(\n        self,\n        log_service: EelLogService,\n        db_service: DbService,\n        key_ceremony_service: KeyCeremonyService,\n        auth_service: AuthorizationService,\n        key_ceremony_state_service: KeyCeremonyStateService,\n        guardian_service: GuardianService,\n    ):\n        self._db_service = db_service\n        self._key_ceremony_service = key_ceremony_service\n        self._auth_service = auth_service\n        self._key_ceremony_state_service = key_ceremony_state_service\n        self.log = log_service\n        self._guardian_service = guardian_service\n\n    @abstractmethod\n    def should_run(\n        self, key_ceremony: KeyCeremonyDto, state: KeyCeremonyStates\n    ) -> bool:\n        raise NotImplementedError\n\n    @abstractmethod\n    def run(self, db: Database, key_ceremony: KeyCeremonyDto) -> None:\n        raise NotImplementedError\n"
  },
  {
    "path": "src/electionguard_gui/services/key_ceremony_state_service.py",
    "content": "from electionguard_gui.models.key_ceremony_dto import KeyCeremonyDto\nfrom electionguard_gui.models.key_ceremony_states import KeyCeremonyStates\nfrom electionguard_gui.services.eel_log_service import EelLogService\nfrom electionguard_gui.services.service_base import ServiceBase\n\n\nclass KeyCeremonyStateService(ServiceBase):\n    \"\"\"Responsible for determining the state of the key ceremony\"\"\"\n\n    log: EelLogService\n\n    def __init__(self, log_service: EelLogService) -> None:\n        self.log = log_service\n\n    # pylint: disable=too-many-return-statements\n    def get_key_ceremony_state(self, key_ceremony: KeyCeremonyDto) -> KeyCeremonyStates:\n        guardians_joined = len(key_ceremony.guardians_joined)\n        guardian_count = key_ceremony.guardian_count\n        other_keys = len(key_ceremony.other_keys)\n        backups = len(key_ceremony.backups)\n        shared_backups = len(key_ceremony.shared_backups)\n        expected_backups = pow(guardian_count, 2)\n        verifications = len(key_ceremony.verifications)\n        expected_verifications = pow(guardian_count, 2) - guardian_count\n        self.log.debug(\n            f\"guardians: {guardians_joined}/{guardian_count}; \"\n            + f\"other_keys: {other_keys}/{guardian_count}; \"\n            + f\"backups: {backups}/{expected_backups}; \"\n            + f\"shared_backups: {shared_backups}/{guardian_count}; \"\n            + f\"verifications: {verifications}/{expected_verifications}\"\n        )\n        if guardians_joined < guardian_count:\n            return KeyCeremonyStates.PendingGuardiansJoin\n        if other_keys == 0:\n            return KeyCeremonyStates.PendingAdminAnnounce\n        if backups < expected_backups:\n            return KeyCeremonyStates.PendingGuardianBackups\n        if shared_backups == 0:\n            return KeyCeremonyStates.PendingAdminToShareBackups\n        if verifications < expected_verifications:\n            return KeyCeremonyStates.PendingGuardiansVerifyBackups\n        if not key_ceremony.joint_key_exists():\n            return KeyCeremonyStates.PendingAdminToPublishJointKey\n        return KeyCeremonyStates.Complete\n\n\nstatus_descriptions = {\n    KeyCeremonyStates.PendingGuardiansJoin: \"waiting for guardians\",\n    KeyCeremonyStates.PendingAdminAnnounce: \"waiting for admin to announce guardians\",\n    KeyCeremonyStates.PendingGuardianBackups: \"waiting for guardians to create backups\",\n    KeyCeremonyStates.PendingAdminToShareBackups: \"waiting for admin to share backups\",\n    KeyCeremonyStates.PendingGuardiansVerifyBackups: \"waiting for guardians to verify backups\",\n    KeyCeremonyStates.PendingAdminToPublishJointKey: \"waiting for admin to publish the joint key\",\n    KeyCeremonyStates.Complete: \"key ceremony complete\",\n}\n\n\ndef get_key_ceremony_status(state: KeyCeremonyStates) -> str:\n    return status_descriptions[state]\n"
  },
  {
    "path": "src/electionguard_gui/services/plaintext_ballot_service.py",
    "content": "from typing import Any\nfrom electionguard import PlaintextTally\nfrom electionguard.manifest import Manifest, get_i8n_value\nfrom electionguard.tally import PlaintextTallySelection\nfrom electionguard_gui.models.election_dto import ElectionDto\n\n\ndef get_plaintext_ballot_report(\n    election: ElectionDto, plaintext_ballot: PlaintextTally\n) -> list:\n    manifest = election.get_manifest()\n    selection_names = manifest.get_selection_names(\"en\")\n    contest_names = manifest.get_contest_names()\n    selection_write_ins = _get_candidate_write_ins(manifest)\n    parties = _get_selection_parties(manifest)\n    tally_report = _get_tally_report(\n        plaintext_ballot, selection_names, contest_names, selection_write_ins, parties\n    )\n    return tally_report\n\n\ndef _get_tally_report(\n    plaintext_ballot: PlaintextTally,\n    selection_names: dict[str, str],\n    contest_names: dict[str, str],\n    selection_write_ins: dict[str, bool],\n    parties: dict[str, str],\n) -> list:\n    tally_report = []\n    contests = plaintext_ballot.contests.values()\n    for tally_contest in contests:\n        selections = list(tally_contest.selections.values())\n        contest_details = _get_contest_details(\n            selections, selection_names, selection_write_ins, parties\n        )\n        contest_name = contest_names.get(tally_contest.object_id, \"n/a\")\n        tally_report.append(\n            {\n                \"name\": contest_name,\n                \"details\": contest_details,\n            }\n        )\n    return tally_report\n\n\ndef _get_contest_details(\n    selections: list[PlaintextTallySelection],\n    selection_names: dict[str, str],\n    selection_write_ins: dict[str, bool],\n    parties: dict[str, str],\n) -> dict[str, Any]:\n\n    # non-write-in selections\n    non_write_in_selections = [\n        selection\n        for selection in selections\n        if not selection_write_ins[selection.object_id]\n    ]\n    non_write_in_total = sum(selection.tally for selection in non_write_in_selections)\n    non_write_in_selections_report = _get_selections_report(\n        non_write_in_selections, selection_names, parties, non_write_in_total\n    )\n\n    # write-in selections\n    write_ins = [\n        selection.tally\n        for selection in selections\n        if selection_write_ins[selection.object_id]\n    ]\n    any_write_ins = len(write_ins) > 0\n    write_ins_total = sum(write_ins) if any_write_ins else None\n\n    return {\n        \"selections\": non_write_in_selections_report,\n        \"nonWriteInTotal\": non_write_in_total,\n        \"writeInTotal\": write_ins_total,\n    }\n\n\ndef _get_selection_parties(manifest: Manifest) -> dict[str, str]:\n    parties = {\n        party.object_id: get_i8n_value(party.name, \"en\", \"\")\n        for party in manifest.parties\n    }\n    candidates = {\n        candidate.object_id: parties[candidate.party_id]\n        for candidate in manifest.candidates\n        if candidate.party_id is not None\n    }\n    contest_parties = {}\n    for contest in manifest.contests:\n        for selection in contest.ballot_selections:\n            party = candidates.get(selection.candidate_id, \"\")\n            contest_parties[selection.object_id] = party\n    return contest_parties\n\n\ndef _get_candidate_write_ins(manifest: Manifest) -> dict[str, bool]:\n    \"\"\"\n    Returns a dictionary where the key is a selection's object_id and the value is a boolean\n    indicating whether the selection's candidate is marked as a write-in.\n    \"\"\"\n    write_in_candidates = {\n        candidate.object_id: candidate.is_write_in is True\n        for candidate in manifest.candidates\n    }\n    contest_write_ins = {}\n    for contest in manifest.contests:\n        for selection in contest.ballot_selections:\n            candidate_is_write_in = write_in_candidates[selection.candidate_id]\n            contest_write_ins[selection.object_id] = candidate_is_write_in\n    return contest_write_ins\n\n\ndef _get_selections_report(\n    selections: list[PlaintextTallySelection],\n    selection_names: dict[str, str],\n    parties: dict[str, str],\n    total: int,\n) -> list:\n    selections_report = []\n    for selection in selections:\n        selection_name = selection_names[selection.object_id]\n        party = parties.get(selection.object_id, \"\")\n        percent: float = (\n            (float(selection.tally) / total) if selection.tally else float(0)\n        )\n        selections_report.append(\n            {\n                \"name\": selection_name,\n                \"tally\": selection.tally,\n                \"party\": party,\n                \"percent\": percent,\n            }\n        )\n    return selections_report\n"
  },
  {
    "path": "src/electionguard_gui/services/service_base.py",
    "content": "from abc import ABC\n\n\nclass ServiceBase(ABC):\n    \"\"\"Responsible for common functionality among services\"\"\"\n\n    def init(self) -> None:\n        self.expose()\n\n    def expose(self) -> None:\n        pass\n"
  },
  {
    "path": "src/electionguard_gui/services/version_service.py",
    "content": "from os import path\nfrom subprocess import check_output\nfrom typing import Optional\nimport eel\nfrom electionguard_gui.services.eel_log_service import EelLogService\nfrom electionguard_gui.services.service_base import ServiceBase\n\n\nclass VersionService(ServiceBase):\n    \"\"\"Responsible for retrieving version information\"\"\"\n\n    _log: EelLogService\n\n    def __init__(self, log_service: EelLogService) -> None:\n        self._log = log_service\n\n    def expose(self) -> None:\n        eel.expose(self.get_version)\n\n    def get_version(self) -> Optional[str]:\n        if not path.exists(\".git\"):\n            return None\n        commit_hash = (\n            check_output([\"git\", \"rev-parse\", \"--short\", \"HEAD\"])\n            .decode(\"ascii\")\n            .strip()\n        )\n        self._log.info(f\"Version: {commit_hash}\")\n        return commit_hash\n"
  },
  {
    "path": "src/electionguard_gui/start.py",
    "content": "from electionguard_gui.containers import Container\n\n\ndef run() -> None:\n    container = Container()\n    container.main_app().start()\n"
  },
  {
    "path": "src/electionguard_gui/web/components/admin/admin-home-component.js",
    "content": "import KeyCeremonyList from \"../shared/key-ceremony-list-component.js\";\nimport ElectionsList from \"../shared/election-list-component.js\";\n\nexport default {\n  components: {\n    KeyCeremonyList,\n    ElectionsList,\n  },\n  data() {\n    return {\n      loading: true,\n      keyCeremonies: [],\n    };\n  },\n  async mounted() {\n    const result = await eel.get_key_ceremonies()();\n    if (result.success) {\n      this.keyCeremonies = result.result;\n    } else {\n      console.error(result.error);\n    }\n    this.loading = false;\n  },\n  template: /*html*/ `\n  <div class=\"container col-md-6\">\n    <div class=\"text-center mb-4\">\n      <h1>Admin Menu</h1>\n    </div>\n    <div class=\"row justify-content-md-center\">\n      <div class=\"col-12 d-grid mb-3\">\n        <a href=\"#/admin/create-key-ceremony\" class=\"btn btn-primary\">Create Key Ceremony</a>\n      </div>\n      <div class=\"col-12 d-grid mb-3\">\n        <a href=\"#/admin/create-election\" class=\"btn btn-primary\">Create Election</a>\n      </div>\n    </div>\n  </div>\n  <div class=\"text-center mt-4\">\n    <elections-list></elections-list>\n  </div>\n  <div class=\"text-center mt-4\">\n    <key-ceremony-list :show-when-empty=\"false\" :is-admin=\"true\" :key-ceremonies=\"keyCeremonies\"></key-ceremony-list>\n  </div>\n\n  `,\n};\n"
  },
  {
    "path": "src/electionguard_gui/web/components/admin/create-decryption-component.js",
    "content": "import RouterService from \"../../services/router-service.js\";\nimport Spinner from \"../shared/spinner-component.js\";\n\nexport default {\n  props: {\n    electionId: String,\n  },\n  components: { Spinner },\n  data() {\n    return {\n      name: \"\",\n      alert: undefined,\n      loading: false,\n    };\n  },\n  methods: {\n    async createDecryption() {\n      console.log(\"createDecryption\");\n      this.loading = true;\n      const result = await eel.create_decryption(this.electionId, this.name)();\n      if (result.success) {\n        RouterService.goTo(RouterService.routes.viewDecryptionAdmin, {\n          decryptionId: result.result,\n        });\n      } else {\n        this.alert = result.message;\n      }\n      this.loading = false;\n    },\n    getElectionUrl: function () {\n      return RouterService.getElectionUrl(this.electionId);\n    },\n  },\n  async mounted() {\n    const result = await eel.get_suggested_decryption_name(this.electionId)();\n    if (result.success) {\n      this.name = result.result;\n    } else {\n      result.alert = result.message;\n    }\n  },\n  template: /*html*/ `\n    <div v-if=\"alert\" class=\"alert alert-danger\" role=\"alert\">\n      {{ alert }}\n    </div>\n    <form id=\"mainForm\" class=\"needs-validation\" novalidate @submit.prevent=\"createDecryption\">\n      <div class=\"row g-3 text-center col-6 mx-auto\">\n        <div class=\"col-12\">\n          <h1>Create Tally</h1>\n        </div>\n        <div class=\"col-12\">\n          <label for=\"name\" class=\"form-label\">Name</label>\n          <input type=\"text\" id=\"name\" class=\"form-control\" v-model=\"name\" required>\n        </div>\n        <div class=\"col-12 mt-4\">\n          <a :href=\"getElectionUrl()\" class=\"btn btn-secondary\">Cancel</a>\n          <button type=\"submit\" class=\"btn btn-primary ms-3\">Create</button>\n          <spinner :visible=\"loading\"></spinner>\n        </div>\n      </div>\n    </form>\n  `,\n};\n"
  },
  {
    "path": "src/electionguard_gui/web/components/admin/create-election-component.js",
    "content": "import Spinner from \"../shared/spinner-component.js\";\nimport RouterService from \"../../services/router-service.js\";\n\nexport default {\n  data() {\n    return {\n      loading: false,\n      alert: null,\n      electionName: \"\",\n      electionUrl: \"\",\n      keys: [],\n    };\n  },\n  components: {\n    Spinner,\n  },\n  methods: {\n    async createElection() {\n      const form = document.getElementById(\"mainForm\");\n      if (form.checkValidity()) {\n        self.loading = true;\n        self.alert = null;\n        const [manifest] = document.getElementById(\"manifest\").files;\n        const manifestContent = await manifest.text();\n        const result = await eel.create_election(\n          this.electionKey.id,\n          this.electionName,\n          manifestContent,\n          this.electionUrl\n        )();\n        console.log(\"creating election\");\n        this.loading = false;\n        console.log(\"creating election completed\", result);\n        if (result.success) {\n          RouterService.goTo(RouterService.routes.viewElectionAdmin, {\n            electionId: result.result,\n          });\n        } else {\n          this.alert = result.message;\n        }\n      }\n      form.classList.add(\"was-validated\");\n    },\n    keyChanged() {\n      if (!this.electionName) {\n        this.electionName = this.electionKey.key_ceremony_name;\n      }\n    },\n  },\n  async mounted() {\n    const result = await eel.get_keys()();\n    if (result.success) {\n      this.keys = result.result;\n    } else {\n      console.error(result.message);\n    }\n  },\n  template: /*html*/ `\n    <form id=\"mainForm\" class=\"needs-validation\" novalidate @submit.prevent=\"createElection\">\n      <div v-if=\"alert\" class=\"alert alert-danger\" role=\"alert\">\n        {{ alert }}\n      </div>\n      <div class=\"row g-3 align-items-center\">\n        <div class=\"col-12\">\n          <h1>Create Election</h1>\n        </div>\n        <div class=\"col-sm-12\">\n          <label for=\"electionKey\" class=\"form-label\">Key</label>\n          <select id=\"electionKey\" class=\"form-select\" v-model=\"electionKey\" @change=\"keyChanged()\">\n            <option v-for=\"key in keys\" :value=\"key\">{{ key.key_ceremony_name }}</option>\n          </select>\n        </div>\n        <div class=\"col-sm-12\">\n          <label for=\"electionName\" class=\"form-label\">Name</label>\n          <input\n            id=\"electionName\"\n            type=\"text\"\n            class=\"form-control\"\n            v-model=\"electionName\" \n            required\n          />\n          <div class=\"invalid-feedback\">Please provide an election name.</div>\n        </div>\n        <div class=\"col-12\">\n          <label for=\"manifest\" class=\"form-label\">Manifest</label>\n          <input\n            type=\"file\"\n            id=\"manifest\"\n            class=\"form-control\"\n            required\n          />\n          <div class=\"invalid-feedback\">Please provide a valid manifest.</div>\n        </div>\n        <div class=\"col-sm-12\">\n          <label for=\"electionUrl\" class=\"form-label\">Election URL</label>\n          <input\n            id=\"electionUrl\"\n            type=\"text\"\n            class=\"form-control\"\n            v-model=\"electionUrl\" \n          />\n        </div>\n        <div class=\"col-12 mt-4\">\n          <button type=\"submit\" class=\"btn btn-primary\">Create Election</button>\n          <spinner :visible=\"loading\"></spinner>\n        </div>\n      </div>\n    </form>`,\n};\n"
  },
  {
    "path": "src/electionguard_gui/web/components/admin/create-key-ceremony-component.js",
    "content": "import Spinner from \"../shared/spinner-component.js\";\nimport RouterService from \"../../services/router-service.js\";\n\nexport default {\n  components: {\n    Spinner,\n  },\n  data() {\n    return {\n      loading: false,\n      alert: null,\n      keyCeremonyName: \"\",\n      guardianCount: 2,\n      quorum: 2,\n    };\n  },\n  methods: {\n    startCeremony() {\n      const form = document.getElementById(\"mainForm\");\n      this.alert = null;\n      self.alert = null;\n      if (form.checkValidity()) {\n        this.loading = true;\n        const onDone = eel.create_key_ceremony(\n          this.keyCeremonyName,\n          this.guardianCount,\n          this.quorum\n        );\n        onDone((result) => {\n          this.loading = false;\n          console.debug(\"key ceremony creation finished\", result);\n          if (result.success) {\n            RouterService.goTo(RouterService.routes.viewKeyCeremonyAdminPage, {\n              keyCeremonyId: result.result,\n            });\n          } else {\n            this.alert = result.message;\n          }\n        });\n      }\n      form.classList.add(\"was-validated\");\n    },\n  },\n  template: /*html*/ `\n    <form id=\"mainForm\" class=\"needs-validation\" novalidate @submit.prevent=\"startCeremony\">\n      <div v-if=\"alert\" class=\"alert alert-danger\" role=\"alert\">\n        {{ alert }}\n      </div>\n      <div class=\"row g-3 align-items-center\">\n        <div class=\"col-12\">\n          <h1>Create Key Ceremony</h1>\n        </div>\n        <div class=\"col-sm-12\">\n          <label for=\"keyCeremonyName\" class=\"form-label\">Key Ceremony Name</label>\n          <input\n            type=\"text\"\n            class=\"form-control\"\n            id=\"keyCeremonyName\"\n            v-model=\"keyCeremonyName\" \n            required\n            min=\"2\"\n          />\n          <div class=\"invalid-feedback\">\n            Key Ceremony Name is required\n          </div>\n        </div>\n        <div class=\"col-sm-6\">\n          <label for=\"guardianCount\" class=\"form-label\">Number of Guardians</label>\n          <input\n            type=\"number\"\n            class=\"form-control\"\n            id=\"guardianCount\"\n            v-model=\"guardianCount\" \n            required\n            min=\"2\"\n          />\n          <div class=\"invalid-feedback\">\n            Please provide a valid number of guardians.\n          </div>\n        </div>\n        <div class=\"col-sm-6\">\n          <label for=\"quorum\" class=\"form-label\">Quorum</label>\n          <input\n            type=\"number\"\n            class=\"form-control\"\n            id=\"quorum\"\n            v-model=\"quorum\" \n            required\n          />\n          <div class=\"invalid-feedback\">Please provide a valid quorum.</div>\n        </div>\n        <div class=\"col-12 mt-4\">\n          <button type=\"submit\" class=\"btn btn-primary\" :disabled=\"loading\">Start Ceremony</button>\n          <spinner :visible=\"loading\"></spinner>\n        </div>\n      </div>\n    </form>`,\n};\n"
  },
  {
    "path": "src/electionguard_gui/web/components/admin/export-election-record-component.js",
    "content": "import RouterService from \"../../services/router-service.js\";\nimport Spinner from \"../shared/spinner-component.js\";\n\nexport default {\n  props: {\n    decryptionId: String,\n  },\n  components: { Spinner },\n  data() {\n    return {\n      locations: [],\n      location: null,\n      loading: false,\n      alert: undefined,\n      success: false,\n    };\n  },\n  methods: {\n    async exportRecord() {\n      this.loading = true;\n      this.alert = undefined;\n      const result = await eel.export_election_record(\n        this.decryptionId,\n        this.location\n      )();\n      this.loading = false;\n      this.success = result.success;\n      if (!result.success) {\n        console.error(result.message);\n        this.alert = \"An error occurred exporting the election record.\";\n      }\n    },\n    getDecryptionUrl: function () {\n      return RouterService.getUrl(RouterService.routes.viewDecryptionAdmin, {\n        decryptionId: this.decryptionId,\n      });\n    },\n  },\n  async mounted() {\n    this.alert = undefined;\n    const result = await eel.get_election_record_export_locations()();\n    if (result.success) {\n      this.locations = result.result;\n      this.location = this.locations[0];\n    } else {\n      console.error(result.message);\n      this.alert = \"An error occurred while loading the export locations.\";\n    }\n  },\n  template: /*html*/ `\n    <div v-if=\"alert\" class=\"alert alert-danger\" role=\"alert\">\n      {{ alert }}\n    </div>\n    <form id=\"mainForm\" class=\"needs-validation\" novalidate @submit.prevent=\"exportRecord\" v-if=\"!success\">\n      <div class=\"row g-3 align-items-center\">\n        <div class=\"col-12\">\n          <h1>Election Record</h1>\n        </div>\n        <div class=\"col-12\">\n          <label for=\"location\" class=\"form-label\">Location</label>\n          <select id=\"location\" class=\"form-select\" v-model=\"location\">\n              <option v-for=\"location in locations\" :value=\"location\">{{ location }}</option>\n          </select>\n        </div>\n        <div class=\"col-12 mt-4\">\n          <button type=\"submit\" class=\"btn btn-primary\">Export</button>\n          <a :href=\"getDecryptionUrl()\" class=\"btn btn-secondary ms-3\">Cancel</a>\n        </div>\n        <div class=\"col-12\">\n          <spinner :visible=\"loading\"></spinner>\n        </div>\n      </div>\n    </form>\n    <div v-if=\"success\" class=\"text-center\">\n      <img src=\"/images/check.svg\" width=\"200\" height=\"200\" class=\"mt-4 mb-2\"></img>\n      <p>The election record has been exported to {{ location }}.</p>\n      <a :href=\"getDecryptionUrl()\" class=\"btn btn-primary\">Continue</a>\n    </div>\n`,\n};\n"
  },
  {
    "path": "src/electionguard_gui/web/components/admin/export-encryption-package-component.js",
    "content": "import RouterService from \"../../services/router-service.js\";\nimport Spinner from \"../shared/spinner-component.js\";\n\nexport default {\n  props: {\n    electionId: String,\n  },\n  components: { Spinner },\n  data() {\n    return {\n      locations: [],\n      location: null,\n      loading: false,\n      alert: undefined,\n      success: false,\n    };\n  },\n  methods: {\n    async exportPackage() {\n      this.loading = true;\n      this.alert = undefined;\n      const result = await eel.export_encryption_package(\n        this.electionId,\n        this.location\n      )();\n      this.loading = false;\n      this.success = result.success;\n      if (!result.success) {\n        console.error(result.message);\n        this.alert = \"An error occurred exporting the encryption package.\";\n      }\n    },\n    getElectionUrl: function () {\n      return RouterService.getElectionUrl(this.electionId);\n    },\n  },\n  async mounted() {\n    this.alert = undefined;\n    const result = await eel.get_encryption_package_export_locations()();\n    if (result.success) {\n      this.locations = result.result;\n      this.location = this.locations[0];\n    } else {\n      console.error(result.message);\n      this.alert = \"An error occurred while loading the export locations.\";\n    }\n  },\n  template: /*html*/ `\n    <div v-if=\"alert\" class=\"alert alert-danger\" role=\"alert\">\n      {{ alert }}\n    </div>\n    <form id=\"mainForm\" class=\"needs-validation\" novalidate @submit.prevent=\"exportPackage\" v-if=\"!success\">\n      <div class=\"row g-3 align-items-center\">\n        <div class=\"col-12\">\n          <h1>Encryption Package</h1>\n        </div>\n        <div class=\"col-12\">\n          <label for=\"location\" class=\"form-label\">Location</label>\n          <select id=\"location\" class=\"form-select\" v-model=\"location\">\n              <option v-for=\"location in locations\" :value=\"location\">{{ location }}</option>\n          </select>\n        </div>\n        <div class=\"col-12 mt-4\">\n        <a :href=\"getElectionUrl()\" class=\"btn btn-secondary\">Cancel</a>\n        <button type=\"submit\" class=\"btn btn-primary ms-3\">Export</button>\n          <spinner :visible=\"loading\"></spinner>\n        </div>\n      </div>\n    </form>\n    <div v-if=\"success\" class=\"text-center\">\n      <img src=\"/images/check.svg\" width=\"200\" height=\"200\" class=\"mt-4 mb-2\"></img>\n      <p>The encryption package has been exported to {{ location }}.</p>\n      <a :href=\"getElectionUrl()\" class=\"btn btn-primary\">Continue</a>\n    </div>\n`,\n};\n"
  },
  {
    "path": "src/electionguard_gui/web/components/admin/upload-ballots-component.js",
    "content": "import UploadBallotsLegacy from \"./upload-ballots-legacy-component.js\";\nimport UploadBallotsWizard from \"./upload-ballots-wizard-component.js\";\n\nexport default {\n  props: {\n    electionId: String,\n  },\n  methods: {\n    closeWizard: function () {\n      this.useWizard = false;\n    },\n  },\n  data() {\n    return {\n      useWizard: null,\n    };\n  },\n  components: {\n    UploadBallotsLegacy,\n    UploadBallotsWizard,\n  },\n  async mounted() {\n    this.useWizard = await eel.is_wizard_supported()();\n  },\n  template: /*html*/ `\n    <upload-ballots-legacy :election-id=\"electionId\" v-if=\"useWizard !== null && !useWizard\"></upload-ballots-legacy>\n    <upload-ballots-wizard @close=\"closeWizard\" :election-id=\"electionId\" v-if=\"useWizard !== null && useWizard\"></upload-ballots-wizard>\n  `,\n};\n"
  },
  {
    "path": "src/electionguard_gui/web/components/admin/upload-ballots-legacy-component.js",
    "content": "import RouterService from \"../../services/router-service.js\";\nimport Spinner from \"../shared/spinner-component.js\";\nimport UploadBallotsSuccess from \"./upload-ballots-success-component.js\";\n\nexport default {\n  props: {\n    electionId: String,\n  },\n  components: { Spinner, UploadBallotsSuccess },\n  data() {\n    return {\n      election: null,\n      loading: false,\n      alert: null,\n      ballotsProcessed: null,\n      ballotsTotal: null,\n      success: false,\n      duplicateCount: 0,\n    };\n  },\n  methods: {\n    async uploadBallots() {\n      try {\n        const form = document.getElementById(\"mainForm\");\n        if (form.checkValidity()) {\n          this.loading = true;\n          this.alert = null;\n          this.ballotsProcessed = 0;\n          const ballotFiles = document.getElementById(\"ballotsFolder\").files;\n          this.ballotsTotal = ballotFiles.length;\n\n          const uploadId = await this.uploadDeviceFile();\n          await this.uploadBallotFiles(uploadId, ballotFiles);\n          this.success = true;\n        }\n        form.classList.add(\"was-validated\");\n      } catch (ex) {\n        console.error(ex);\n        this.alert = ex.message;\n      } finally {\n        this.loading = false;\n      }\n    },\n    async uploadDeviceFile() {\n      const [deviceFile] = document.getElementById(\"deviceFile\").files;\n      const deviceContents = await deviceFile.text();\n      console.log(\"Creating election\", deviceFile.name);\n      const result = await eel.create_ballot_upload(\n        this.electionId,\n        deviceFile.name,\n        deviceContents\n      )();\n      if (!result.success) {\n        throw new Error(result.message);\n      }\n      this.ballotsProcessed++;\n      return result.result;\n    },\n    async uploadBallotFiles(uploadId, ballotFiles) {\n      for (let i = 0; i < ballotFiles.length; i++) {\n        const ballotFile = ballotFiles[i];\n        const ballotContents = await ballotFile.text();\n        console.log(\"Uploading ballot\", ballotFile.name);\n        const result = await eel.upload_ballot(\n          uploadId,\n          this.electionId,\n          ballotFile.name,\n          ballotContents\n        )();\n        if (!result.success) {\n          throw new Error(result.message);\n        }\n        if (result.result.is_duplicate) {\n          this.duplicateCount++;\n        }\n        this.ballotsProcessed++;\n      }\n    },\n    getElectionUrl: function () {\n      return RouterService.getElectionUrl(this.electionId);\n    },\n    uploadMore: function () {\n      this.success = false;\n      this.duplicateCount = 0;\n      this.election = null;\n      this.loading = false;\n      this.alert = null;\n      this.ballotsProcessed = null;\n      this.ballotsTotal = null;\n      this.$nextTick(() => {\n        this.resetFiles();\n      });\n    },\n    resetFiles: function () {\n      document.getElementById(\"deviceFile\").value = null;\n      document.getElementById(\"ballotsFolder\").value = null;\n    },\n  },\n  mounted() {\n    this.resetFiles();\n  },\n  template: /*html*/ `\n    <div v-if=\"alert\" class=\"alert alert-danger\" role=\"alert\">\n      {{ alert }}\n    </div>\n    <div v-if=\"duplicateCount\" class=\"alert alert-warning\" role=\"alert\">\n      {{ duplicateCount }} ballots were skipped because their object_ids had already been uploaded for this election.\n    </div>\n    <form id=\"mainForm\" class=\"needs-validation\" novalidate @submit.prevent=\"uploadBallots\" v-if=\"!success\">\n      <div class=\"row g-3 align-items-center\">\n        <div class=\"col-12\">\n          <h1>Upload Ballots</h1>\n        </div>\n        <div class=\"col-12\">\n          <label for=\"deviceFile\" class=\"form-label\">Device File</label>\n          <input\n            type=\"file\"\n            id=\"deviceFile\"\n            class=\"form-control\"\n            required\n          />\n          <div class=\"invalid-feedback\">Please provide a device file.</div>\n        </div>\n        <div class=\"col-12\">\n          <label for=\"ballotsFolder\" class=\"form-label\">Ballot Folder</label>\n          <input\n            type=\"file\"\n            id=\"ballotsFolder\"\n            class=\"form-control\"\n            webkitdirectory directory\n            required\n          />\n          <div class=\"invalid-feedback\">Please provide a ballot folder.</div>\n        </div>\n        <div class=\"col-12 mt-4\">\n          <button type=\"submit\" :disabled=\"loading\" class=\"btn btn-primary me-2\">Upload</button>\n          <a :href=\"getElectionUrl()\" class=\"btn btn-secondary me-2\">Cancel</a>\n          <spinner :visible=\"loading\"></spinner>\n          <p v-if=\"loading && ballotsProcessed\">{{ ballotsProcessed }} of {{ ballotsTotal }} files processed.</p>\n      </div>\n    </form>\n    <upload-ballots-success v-if=\"success\" :back-url=\"getElectionUrl()\" @upload-more=\"uploadMore()\" :ballot-count=\"ballotsTotal-duplicateCount\"></upload-ballots-success>\n  `,\n};\n"
  },
  {
    "path": "src/electionguard_gui/web/components/admin/upload-ballots-success-component.js",
    "content": "export default {\n  props: {\n    ballotCount: Number,\n    electionId: String,\n    backUrl: String,\n  },\n  template: /*html*/ `\n  <div class=\"text-center\">\n    <img src=\"/images/check.svg\" width=\"200\" height=\"200\" class=\"mt-4 mb-2\"></img>\n    <p>Successfully uploaded {{ballotCount}} ballots.</p>\n    <button type=\"button\" @click=\"$emit('uploadMore')\" class=\"btn btn-secondary me-2\">Upload More Ballots</button>\n    <a :href=\"backUrl\" class=\"btn btn-primary\">Done Uploading</a>\n  </div>\n  `,\n};\n"
  },
  {
    "path": "src/electionguard_gui/web/components/admin/upload-ballots-wizard-component.js",
    "content": "import RouterService from \"../../services/router-service.js\";\nimport Spinner from \"../shared/spinner-component.js\";\nimport UploadBallotsSuccess from \"./upload-ballots-success-component.js\";\n\nexport default {\n  props: {\n    electionId: String,\n  },\n  data() {\n    return {\n      drive: null,\n      success: false,\n      alert: null,\n      ballotCount: null,\n      status: null,\n      loading: false,\n      duplicateCount: 0,\n    };\n  },\n  methods: {\n    uploadBallots: async function () {\n      this.loading = true;\n      try {\n        const result = await eel.upload_ballots(this.electionId)();\n        console.log(\"upload completed\", result);\n        if (result.success) {\n          this.success = true;\n          this.ballotCount = result.result.ballot_count;\n          this.duplicateCount = result.result.duplicate_count;\n        } else {\n          this.alert = result.message;\n        }\n      } finally {\n        this.loading = false;\n        this.status = null;\n      }\n    },\n    closeWizard: function () {\n      this.$emit(\"close\");\n    },\n    getElectionUrl: function () {\n      return RouterService.getElectionUrl(this.electionId);\n    },\n    uploadMore: async function () {\n      this.success = false;\n      this.drive = null;\n      this.alert = null;\n      this.ballotCount = null;\n      this.duplicateCount = 0;\n      this.status = null;\n      this.loading = false;\n      await this.scanDrives();\n    },\n    scanDrives: async function () {\n      const result = await eel.scan_drives()();\n      if (!result.success) {\n        console.error(result.message);\n        this.alert = result.message;\n      } else {\n        this.drive = result.result;\n        console.log(\"successfully uploaded ballots\", this.drive);\n      }\n    },\n    updateUploadStatus: function (status) {\n      console.log(\"updateUploadStatus\", status);\n      this.status = status;\n    },\n    pollDrives: async function () {\n      if (this.drive) return;\n      await this.scanDrives();\n      if (!this.drive) {\n        // keep polling until a valid drive is found\n        setTimeout(this.pollDrives.bind(this), 1000);\n      }\n    },\n  },\n  async mounted() {\n    eel.expose(this.updateUploadStatus, \"update_upload_status\");\n    await this.pollDrives();\n  },\n  components: {\n    UploadBallotsSuccess,\n    Spinner,\n  },\n  template: /*html*/ `\n  <div v-if=\"alert\" class=\"alert alert-danger\" role=\"alert\">\n    {{ alert }}\n  </div>\n  <div v-if=\"duplicateCount\" class=\"alert alert-warning\" role=\"alert\">\n    {{ duplicateCount }} ballots were skipped because their object_ids had already been uploaded for this election.\n  </div>\n  <upload-ballots-success v-if=\"success\" :back-url=\"getElectionUrl()\" @upload-more=\"uploadMore()\" :ballot-count=\"ballotCount\"></upload-ballots-success>\n  <div v-else>\n    <div class=\"row\">\n      <div class=\"col-md-12 text-end\">\n        <button type=\"button\" class=\"btn btn-sm btn-default\" @click=\"closeWizard\">\n          <i class=\"bi bi-x-square\"></i>\n        </button>\n      </div>\n    </div>\n    <div class=\"text-center\">\n      <h1>Upload Wizard</h1>\n      <div v-if=\"!drive\">\n        <p>Insert a USB drive containing ballots</p>\n        <spinner class=\"mt-4\" :visible=\"true\"></spinner>\n      </div>\n      <div v-if=\"drive\">\n        <p class=\"mt-4\">Ready to import?</p>\n        <div class=\"row g-1\">\n          <div class=\"col-6 fw-bold text-end\">\n            {{drive.drive}}\n          </div>\n          <div class=\"col-6 text-start\">\n            drive\n          </div>\n        </div>\n        <div class=\"row g-1\">\n          <div class=\"col-6 fw-bold text-end\">\n            {{drive.ballots}}\n          </div>\n          <div class=\"col-6 text-start\">\n            ballots\n          </div>\n        </div>\n        <div class=\"row g-1\">\n          <div class=\"col-6 fw-bold text-end\">\n            {{drive.location}}\n          </div>\n          <div class=\"col-6 text-start\">\n            device\n          </div>\n        </div>\n        <div class=\"mt-4\">\n          <a :href=\"getElectionUrl()\" class=\"btn btn-secondary me-2\">Cancel</a>\n          <button class=\"btn btn-primary\" :disabled=\"loading\" @click=\"uploadBallots\">Import</button>\n          <p class=\"mt-3\" v-if=\"status\">{{ status }}</p>\n          <spinner class=\"mt-4\" :visible=\"loading\"></spinner>\n        </div>\n      </div>\n    </div>\n  </div>\n  `,\n};\n"
  },
  {
    "path": "src/electionguard_gui/web/components/admin/view-decryption-admin-component.js",
    "content": "import RouterService from \"../../services/router-service.js\";\nimport Spinner from \"../shared/spinner-component.js\";\n\nexport default {\n  props: {\n    decryptionId: String,\n  },\n  components: { Spinner },\n  data() {\n    return { decryption: null, loading: false, error: false, status: null };\n  },\n  methods: {\n    getElectionUrl: function (electionId) {\n      return RouterService.getElectionUrl(electionId);\n    },\n    getExportElectionRecordUrl: function () {\n      return RouterService.getUrl(RouterService.routes.exportElectionRecord, {\n        decryptionId: this.decryptionId,\n      });\n    },\n    getViewTallyUrl: function () {\n      return RouterService.getUrl(RouterService.routes.viewTally, {\n        decryptionId: this.decryptionId,\n      });\n    },\n    getSpoiledBallotUrl: function (spoiledBallotId) {\n      return RouterService.getUrl(RouterService.routes.viewSpoiledBallot, {\n        decryptionId: this.decryptionId,\n        spoiledBallotId: spoiledBallotId,\n      });\n    },\n    refresh_decryption: async function (result) {\n      if (result.success) {\n        await this.get_decryption(true);\n      } else {\n        console.error(result.message);\n        this.error = true;\n        this.loading = false;\n        this.status = null;\n        this.decryption = null;\n      }\n    },\n    get_decryption: async function (is_refresh) {\n      console.log(\"getting decryption\");\n      this.loading = true;\n      try {\n        const result = await eel.get_decryption(\n          this.decryptionId,\n          is_refresh\n        )();\n        console.log(\"get_decryption complete\", result);\n        this.error = !result.success;\n        this.decryption = result.success ? result.result : null;\n      } catch (error) {\n        console.error(error);\n      } finally {\n        this.loading = false;\n        this.status = null;\n      }\n    },\n    updateDecryptStatus: function (status) {\n      this.status = status;\n    },\n  },\n  async mounted() {\n    eel.expose(this.refresh_decryption, \"refresh_decryption\");\n    eel.expose(this.updateDecryptStatus, \"update_decrypt_status\");\n    await this.get_decryption(false);\n    console.log(\"watching decryption\");\n    // only watch for changes if the decryption is in-progress\n    if (this.decryption && !this.decryption.completed_at_str) {\n      eel.watch_decryption(this.decryptionId);\n    }\n  },\n  unmounted() {\n    console.log(\"stop watching decryption\");\n    eel.stop_watching_decryption();\n  },\n  template: /*html*/ `\n    <div v-if=\"error\">\n      <p class=\"alert alert-danger\" role=\"alert\">An error occurred. Check the logs and/or <a href=\"javascript:history.back()\">try again</a>.</p>\n    </div>\n    <div v-if=\"decryption\">\n      <div class=\"text-end\">\n        <div v-if=\"decryption.completed_at_str\">\n          <div class=\"dropdown\">\n            <button class=\"btn btn-sm btn-secondary dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n              <i class=\"bi-gear-fill me-1\"></i>\n            </button>\n            <ul class=\"dropdown-menu\">\n              <li>\n                <a :href=\"getExportElectionRecordUrl()\" class=\"dropdown-item\">\n                  <i class=\"bi-download me-1\"></i> Download election record\n                </a>\n              </li>\n              <li>\n                <a :href=\"getViewTallyUrl()\" class=\"dropdown-item\" v-if=\"decryption.completed_at_str\">\n                  <i class=\"bi-card-text me-1\"></i> View Tally\n                </a>\n              </li>\n            </ul>\n          </div>\n        </div>\n      </div>\n      <div class=\"row\">\n        <h1>{{decryption.decryption_name}}</h1>\n      </div>\n      <div class=\"row\">\n        <div class=\"col col-12 col-md-6 col-lg-5\">\n          <div class=\"col-md-8\">\n            <dt>Election</dt>\n            <dd><a :href=\"getElectionUrl(decryption.election_id)\">{{decryption.election_name}}</a></dd>\n          </div>\n          <div class=\"mb-4\">\n            <div class=\"row\">\n              <div class=\"col-md-6\">\n                <dt>Ballot Uploads</dt>\n                <dd>{{decryption.ballot_upload_count}}</dd>\n              </div>\n              <div class=\"col-md-6\">\n                <dt>Total Ballots</dt>\n                <dd>{{decryption.ballot_count}}</dd>\n              </div>\n            </div>\n            <dl class=\"col-12\">\n              <dt>Created</dt>\n              <dd>by {{decryption.created_by}} on {{decryption.created_at}}</dd>\n            </dl>\n            <dl class=\"col-12\" v-if=\"decryption.completed_at_str\">\n              <dt>Completed</dt>\n              <dd>{{decryption.completed_at_str}}</dd>\n            </dl>\n          </div>\n          <div class=\"col-12 mb-4\">\n            <h3>Joined Guardians</h3>\n            <ul v-if=\"decryption.guardians_joined.length\">\n              <li v-for=\"guardian in decryption.guardians_joined\">{{guardian}}</li>\n            </ul>\n            <div v-else>\n              <p>No guardians have joined yet</p>\n            </div>\n          </div>\n          <div v-if=\"decryption.completed_at_str\" class=\"mb-4\">\n            <h3>Tally Results</h3>\n            <a :href=\"getViewTallyUrl()\" class=\"btn btn-sm btn-secondary m-2\"><i class=\"bi bi-binoculars-fill me-1\"></i> View Tally</a>\n          </div>\n          <div v-if=\"decryption.completed_at_str\">\n            <h3>Spoiled Ballots</h3>\n            <table class=\"table table-striped\" v-if=\"decryption.spoiled_ballots.length\">\n              <thead>\n                <tr>\n                  <th>Ballot ID</th>\n                </tr>\n              </thead>\n              <tbody class=\"table-group-divider\">\n                <tr v-for=\"spoiledBallot in decryption.spoiled_ballots\">\n                  <td><a :href=\"getSpoiledBallotUrl(spoiledBallot)\">{{spoiledBallot}}</a></td>\n                </tr>\n              </tbody>\n            </table>\n            <div v-else>\n              <p>No spoiled ballots existed at the time this tally was run.</p>\n            </div>\n          </div>\n        </div>\n        <div class=\"col col-12 col-md-6 col-lg-7 text-center\">\n          <img v-if=\"decryption.completed_at_str\" src=\"/images/check.svg\" width=\"150\" height=\"150\" class=\"mb-2\"></img>\n          <p class=\"key-ceremony-status\">{{decryption.status}}</p>\n          <p class=\"mt-3\" v-if=\"status\">{{ status }}</p>\n          <spinner :visible=\"loading || !decryption.completed_at_str\"></spinner>\n        </div>\n      </div>\n    </div>\n    <div v-else>\n      <spinner :visible=\"loading\"></spinner>\n    </div>\n`,\n};\n"
  },
  {
    "path": "src/electionguard_gui/web/components/admin/view-election-component.js",
    "content": "import RouterService from \"../../services/router-service.js\";\nimport Spinner from \"../shared/spinner-component.js\";\n\nexport default {\n  props: {\n    electionId: String,\n  },\n  components: { Spinner },\n  data() {\n    return { election: null, loading: false, error: false, ballotSum: 0 };\n  },\n  methods: {\n    getEncryptionPackageUrl: function () {\n      const page = RouterService.routes.exportEncryptionPackage;\n      return RouterService.getUrl(page, {\n        electionId: this.electionId,\n      });\n    },\n    getUploadBallotsUrl: function () {\n      const page = RouterService.routes.uploadBallots;\n      return RouterService.getUrl(page, {\n        electionId: this.electionId,\n      });\n    },\n    getCreateDecryptionUrl: function () {\n      const page = RouterService.routes.createDecryption;\n      return RouterService.getUrl(page, {\n        electionId: this.electionId,\n      });\n    },\n    getViewDecryptionUrl: function (decryptionId) {\n      const page = RouterService.routes.viewDecryptionAdmin;\n      return RouterService.getUrl(page, {\n        decryptionId: decryptionId,\n      });\n    },\n    setBallotSum: function (election) {\n      this.ballotSum = 0;\n      for (const spoiledBallot of election.ballot_uploads) {\n        this.ballotSum += spoiledBallot.ballot_count;\n      }\n    },\n  },\n  async mounted() {\n    const result = await eel.get_election(this.electionId)();\n    if (result.success) {\n      this.election = result.result;\n      this.setBallotSum(this.election);\n    } else {\n      this.error = true;\n    }\n  },\n  template: /*html*/ `\n    <div v-if=\"election\">\n      <div class=\"container\">\n        <div class=\"text-end\">\n          <div class=\"dropdown\">\n            <button class=\"btn btn-sm btn-secondary dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n              <i class=\"bi-gear-fill me-1\"></i>\n            </button>\n            <ul class=\"dropdown-menu\">\n              <li>\n                <a :href=\"getEncryptionPackageUrl()\" class=\"dropdown-item\">\n                  <i class=\"bi-download me-1\"></i> Download encryption package\n                </a>\n              </li>\n            </ul>\n          </div>\n        </div>\n        <div class=\"row\">\n          <div class=\"col col-12 col-md-8\">\n            <h1>{{election.election_name}}</h1>\n            <div class=\"row mb-4\">\n              <dl class=\"col-md-6 mb-1\">\n                <dt>Guardians</dt>\n                <dd>{{election.guardians}}</dd>\n              </dl>\n              <dl class=\"col-md-6 mb-1\">\n                <dt>Quorum</dt>\n                <dd>{{election.quorum}}</dd>\n              </dl>\n              <dl class=\"col-md-12 mb-1\" v-if=\"election.election_url\">\n                <dt>Election URL</dt>\n                <dd>{{election.election_url}}</dd>\n              </dl>\n              <dl class=\"col-12 mb-1\">\n                <dt>Created</dt>\n                <dd>by {{election.created_by}} on {{election.created_at}}</dd>\n              </dl>\n            </div>\n            <div class=\"row mb-4\">\n              <div class=\"col-12\">\n                <h2>Ballots</h2>\n                <table class=\"table table-striped\" v-if=\"election.ballot_uploads.length\">\n                  <thead>\n                    <tr>\n                      <th>Uploaded</th>\n                      <th>Location</th>\n                      <th>Ballot Count</th>\n                    </tr>\n                  </thead>\n                  <tbody class=\"table-group-divider\">\n                    <tr v-for=\"ballot_upload in election.ballot_uploads\">\n                      <td>{{ballot_upload.created_at}}</td>\n                      <td>{{ballot_upload.location}}</td>\n                      <td>{{ballot_upload.ballot_count}}</td>\n                    </tr>\n                  </tbody>\n                  <tfoot>\n                    <tr class=\"table-secondary\">\n                      <td><em>Total</em></td>\n                      <td>&nbsp;</td>\n                      <td>{{ballotSum}}\n                    </tr>\n                  </tfoot>\n                </table>\n                <div v-else>\n                  <p>No ballots have been added yet.</p>\n                </div>\n                <div>\n                  <a :href=\"getUploadBallotsUrl()\" class=\"btn btn-sm btn-secondary\">\n                    <i class=\"bi-plus bi-plus me-2\"></i> Add Ballots\n                  </a>\n                </div>\n              </div>\n            </div>\n            <div class=\"row mb-4\" v-if=\"election.ballot_uploads.length\">\n              <div class=\"col-12\">\n                <h2>Tallies</h2>\n                <table class=\"table table-striped\" v-if=\"election.decryptions.length\">\n                  <thead>\n                    <tr>\n                      <th>Created</th>\n                      <th>Name</th>\n                    </tr>\n                  </thead>\n                  <tbody class=\"table-group-divider\">\n                    <tr v-for=\"decryption in election.decryptions\">\n                      <td>{{decryption.created_at}}</td>\n                      <td><a :href=\"getViewDecryptionUrl(decryption.decryption_id)\">{{decryption.name}}</a></td>\n                    </tr>\n                  </tbody>\n                </table>\n                <div v-else>\n                  <p>No tallies have been created yet.</p>\n                </div>\n                <a :href=\"getCreateDecryptionUrl()\" class=\"btn btn-sm btn-secondary\">\n                  <i class=\"bi bi-people-fill me-1\"></i> Create tally\n                </a>\n          </div>\n            </div>\n          </div>\n          <div class=\"col col-12 col-md-4\">\n            <h2>Manifest</h2>\n            <p>{{election.manifest.name}}</p>\n            <div class=\"row\">\n              <dl class=\"col-md-6\">\n                <dt>Parties</dt>\n                <dd>{{election.manifest.parties}}</dd>\n              </dl>\n              <dl class=\"col-md-6\">\n                <dt>Candidates</dt>\n                <dd>{{election.manifest.candidates}}</dd>\n              </dl>\n              <dl class=\"col-md-6\">\n                <dt>Contests</dt>\n                <dd>{{election.manifest.contests}}</dd>\n              </dl>\n              <dl class=\"col-md-6\">\n                <dt>Ballot Styles</dt>\n                <dd>{{election.manifest.ballot_styles}}</dd>\n              </dl>\n              <dl class=\"col-md-12\">\n                <dt>Geopolitical Units</dt>\n                <dd>{{election.manifest.geopolitical_units}}</dd>\n            </dl>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div v-else>\n      <spinner :visible=\"loading\"></spinner>\n      <div v-if=\"error\">\n        <p class=\"alert alert-danger\" role=\"alert\">An error occurred with the election. Check the logs and try again.</p>\n      </div>\n    </div>\n  `,\n};\n"
  },
  {
    "path": "src/electionguard_gui/web/components/admin/view-key-ceremony-component.js",
    "content": "import KeyCeremonyDetails from \"../shared/key-ceremony-details-component.js\";\n\nexport default {\n  props: {\n    keyCeremonyId: String,\n  },\n  components: {\n    KeyCeremonyDetails,\n  },\n  template: /*html*/ `\n    <key-ceremony-details :keyCeremonyId=\"keyCeremonyId\"></key-ceremony-details>\n    `,\n};\n"
  },
  {
    "path": "src/electionguard_gui/web/components/admin/view-spoiled-ballot-component.js",
    "content": "import RouterService from \"../../services/router-service.js\";\nimport Spinner from \"../shared/spinner-component.js\";\nimport ViewPlaintextBallotComponent from \"../shared/view-plaintext-ballot-component.js\";\n\nexport default {\n  props: {\n    decryptionId: String,\n    spoiledBallotId: String,\n  },\n  components: { Spinner, ViewPlaintextBallotComponent },\n  data() {\n    return { spoiled_ballot: null, loading: true };\n  },\n  methods: {\n    getElectionUrl: function (electionId) {\n      return RouterService.getElectionUrl(electionId);\n    },\n    getDecryptionUrl: function () {\n      return RouterService.getUrl(RouterService.routes.viewDecryptionAdmin, {\n        decryptionId: this.decryptionId,\n      });\n    },\n  },\n  async mounted() {\n    const result = await eel.get_spoiled_ballot(\n      this.decryptionId,\n      this.spoiledBallotId\n    )();\n    if (result.success) {\n      this.spoiled_ballot = result.result;\n    } else {\n      console.error(result.error);\n    }\n    this.loading = false;\n  },\n  template: /*html*/ `\n    <div v-if=\"spoiled_ballot\" class=\"row\">\n      <div class=\"col col-12 mb-3\">\n        <a :href=\"getElectionUrl(spoiled_ballot.election_id)\">{{spoiled_ballot.election_name}}</a> \n        &gt; \n        <a :href=\"getDecryptionUrl()\">{{spoiled_ballot.decryption_name}}</a>\n        &gt;\n        {{this.spoiledBallotId}}\n      </div>\n      <div class=\"col-md-12\">\n        <view-plaintext-ballot-component :ballot=\"spoiled_ballot.report\"></view-plaintext-ballot-component>\n      </div>\n    </div>\n    <spinner :visible=\"loading\"></spinner>\n  `,\n};\n"
  },
  {
    "path": "src/electionguard_gui/web/components/admin/view-tally-component.js",
    "content": "import RouterService from \"../../services/router-service.js\";\nimport Spinner from \"../shared/spinner-component.js\";\nimport ViewPlaintextBallotComponent from \"../shared/view-plaintext-ballot-component.js\";\n\nexport default {\n  props: {\n    decryptionId: String,\n  },\n  components: { Spinner, ViewPlaintextBallotComponent },\n  data() {\n    return { tally: null, loading: true };\n  },\n  methods: {\n    getElectionUrl: function (electionId) {\n      return RouterService.getElectionUrl(electionId);\n    },\n    getDecryptionUrl: function () {\n      return RouterService.getUrl(RouterService.routes.viewDecryptionAdmin, {\n        decryptionId: this.decryptionId,\n      });\n    },\n  },\n  async mounted() {\n    const result = await eel.get_tally(this.decryptionId)();\n    if (result.success) {\n      this.tally = result.result;\n    } else {\n      console.error(result.error);\n    }\n    this.loading = false;\n  },\n  template: /*html*/ `\n    <div v-if=\"tally\" class=\"row\">\n      <div class=\"col col-12 mb-3\">\n        <a :href=\"getElectionUrl(tally.election_id)\">{{tally.election_name}}</a> \n        &gt; \n        <a :href=\"getDecryptionUrl()\">{{tally.decryption_name}}</a>\n        &gt;\n        Tally\n      </div>\n      <div class=\"col-md-12\">\n        <view-plaintext-ballot-component :ballot=\"tally.report\"></view-plaintext-ballot-component>\n      </div>\n    </div>\n    <spinner :visible=\"loading\"></spinner>\n  `,\n};\n"
  },
  {
    "path": "src/electionguard_gui/web/components/guardian/decryption-list-component.js",
    "content": "import RouterService from \"../../services/router-service.js\";\n\nexport default {\n  props: {\n    decryptions: Array,\n  },\n  data() {\n    return {\n      loading: true,\n    };\n  },\n  methods: {\n    getDecryptionUrl: function (decryption) {\n      return RouterService.getUrl(RouterService.routes.viewDecryptionGuardian, {\n        decryptionId: decryption.id,\n      });\n    },\n  },\n  template: /*html*/ `\n    <h2>Decryptions</h2>\n    <div v-if=\"!decryptions.length\">\n      <p>No decryptions found.</p>\n    </div>\n    <div v-if=\"decryptions.length\" class=\"d-grid gap-2 d-md-block\">\n      <a :href=\"getDecryptionUrl(decryption)\" v-for=\"decryption in decryptions\" class=\"btn btn-primary me-2 mt-2\">{{ decryption.decryption_name }}</a>\n    </div>\n    `,\n};\n"
  },
  {
    "path": "src/electionguard_gui/web/components/guardian/guardian-home-component.js",
    "content": "import KeyCeremonyList from \"../shared/key-ceremony-list-component.js\";\nimport DecryptionList from \"./decryption-list-component.js\";\nimport Spinner from \"../shared/spinner-component.js\";\n\nconst sleepResumeInterval = 2000;\n\nexport default {\n  components: {\n    KeyCeremonyList,\n    DecryptionList,\n    Spinner,\n  },\n  data() {\n    return {\n      loading: true,\n      decryptions: [],\n      keyCeremonies: [],\n      lastAwakeTime: new Date().getTime(),\n    };\n  },\n  methods: {\n    refresh: async function () {\n      this.loading = true;\n      try {\n        this.decryptions = [];\n        this.keyCeremonies = [];\n        await this.refreshDecryptions();\n        await this.refreshKeyCeremonies();\n      } finally {\n        this.loading = false;\n      }\n    },\n    keyCeremoniesChanged: async function () {\n      await this.refreshKeyCeremonies();\n    },\n    decryptionsChanged: async function () {\n      await this.refreshDecryptions();\n    },\n    refreshDecryptions: async function () {\n      const result = await eel.get_decryptions()();\n      if (result.success) {\n        this.decryptions = result.result;\n      } else {\n        console.error(result.error);\n      }\n    },\n    refreshKeyCeremonies: async function () {\n      const result = await eel.get_key_ceremonies()();\n      if (result.success) {\n        this.keyCeremonies = result.result;\n      } else {\n        console.error(result.error);\n      }\n    },\n    sleepResumeChecker: function () {\n      var currentTime = new Date().getTime();\n      if (currentTime > this.lastAwakeTime + sleepResumeInterval * 2) {\n        console.log(\"system appears to have returned from sleep, refreshing\");\n        document.location.reload(true);\n      }\n      this.lastAwakeTime = currentTime;\n    },\n  },\n  async mounted() {\n    setInterval(this.sleepResumeChecker, sleepResumeInterval);\n    eel.expose(this.keyCeremoniesChanged, \"key_ceremonies_changed\");\n    eel.expose(this.decryptionsChanged, \"decryptions_changed\");\n    console.log(\"begin watching for key ceremonies\");\n    eel.watch_db_collections();\n    await this.refreshKeyCeremonies();\n    await this.refreshDecryptions();\n    this.loading = false;\n  },\n  unmounted() {\n    console.log(\"stop watching key ceremonies\");\n    eel.stop_watching_db_collections();\n    clearInterval(this.sleepResumeChecker);\n  },\n  template: /*html*/ `\n  <div class=\"container\">\n    <div class=\"row\">\n      <div class=\"col-11\">\n        <h1>Guardian Home</h1>\n      </div>\n      <div class=\"col-1 text-end\">\n        <button class=\"btn btn-lg btn-light\" @click=\"refresh\"><i class=\"bi-arrow-clockwise\"></i></button>\n      </div>\n    </div>\n    <spinner :visible=\"loading\"></spinner>\n    <div v-if=\"!loading\" class=\"row\">\n      <div class=\"col-12 col-lg-6\">\n        <key-ceremony-list :show-when-empty=\"true\" :is-admin=\"false\" :key-ceremonies=\"keyCeremonies\"></key-ceremony-list>\n      </div>\n      <div class=\"col-12 col-lg-6\">\n        <decryption-list :decryptions=\"decryptions\"></decryption-list>\n      </div>\n    </div>\n  </div>\n  `,\n};\n"
  },
  {
    "path": "src/electionguard_gui/web/components/guardian/view-decryption-guardian-component.js",
    "content": "import RouterService from \"../../services/router-service.js\";\nimport Spinner from \"../shared/spinner-component.js\";\nimport AuthService from \"../../services/authorization-service.js\";\n\nexport default {\n  props: {\n    decryptionId: String,\n  },\n  components: { Spinner },\n  data() {\n    return {\n      decryption: null,\n      loading: false,\n      error: false,\n      successfully_joined: false,\n      status: null,\n    };\n  },\n  methods: {\n    getElectionUrl: function (electionId) {\n      return RouterService.getElectionUrl(electionId);\n    },\n    decrypt: async function () {\n      this.loading = true;\n      try {\n        this.error = false;\n        const result = await eel.join_decryption(this.decryptionId)();\n        if (result.success) {\n          this.success = true;\n        } else {\n          this.error = true;\n        }\n      } finally {\n        this.loading = false;\n        this.status = null;\n      }\n    },\n    refresh_decryption: async function () {\n      console.log(\"refreshing decryption\");\n      this.loading = true;\n      const result = await eel.get_decryption(this.decryptionId, true)();\n      this.error = !result.success;\n      if (result.success) {\n        this.decryption = result.result;\n        const currentUser = await AuthService.getUserId();\n        this.successfully_joined =\n          this.decryption.guardians_joined.includes(currentUser);\n      }\n      this.loading = false;\n    },\n    updateDecryptStatus: function (status) {\n      console.log(\"updateDecryptStatus\", status);\n      this.status = status;\n    },\n  },\n  async mounted() {\n    eel.expose(this.updateDecryptStatus, \"update_decrypt_status\");\n    eel.expose(this.refresh_decryption, \"refresh_decryption\");\n    await this.refresh_decryption();\n    console.log(\"watching decryption\");\n    eel.watch_decryption(this.decryptionId);\n  },\n  unmounted() {\n    console.log(\"stop watching decryption\");\n    eel.stop_watching_decryption();\n  },\n  template: /*html*/ `\n    <div v-if=\"error\">\n      <p class=\"alert alert-danger\" role=\"alert\">\n        ElectionGuard couldn’t perform the action you requested. \n        Ask an Administrator for help, or return to the last screen.\n      </p>\n    </div>\n    <div v-if=\"decryption\">\n      <div class=\"row text-center\">\n        <div class=\"col col-12\" v-if=\"decryption.can_join\">\n          <h1>Join Tally</h1>\n          <p>Click below to join <i>{{decryption.decryption_name}}</i></p>\n          <button @click=\"decrypt()\" :disabled=\"loading\" class=\"btn btn-primary mb-3\">Join</button>\n          <p class=\"mt-3\" v-if=\"status\">{{ status }}</p>\n          <spinner :visible=\"loading\"></spinner>\n        </div>\n        <div class=\"col col-12\" v-if=\"successfully_joined\">\n          <h1>{{decryption.decryption_name}}</h1>\n          <img src=\"/images/check.svg\" width=\"200\" height=\"200\" class=\"mb-2\"></img>\n          <p class=\"key-ceremony-status\">decryption complete</p>\n        </div>\n      </div>\n    </div>\n`,\n};\n"
  },
  {
    "path": "src/electionguard_gui/web/components/guardian/view-key-ceremony-component.js",
    "content": "import KeyCeremonyDetails from \"../shared/key-ceremony-details-component.js\";\n\nexport default {\n  props: {\n    keyCeremonyId: String,\n  },\n  components: {\n    KeyCeremonyDetails,\n  },\n  template: /*html*/ `\n    <key-ceremony-details :keyCeremonyId=\"keyCeremonyId\"></key-ceremony-details>\n    `,\n};\n"
  },
  {
    "path": "src/electionguard_gui/web/components/shared/election-list-component.js",
    "content": "import RouterService from \"../../services/router-service.js\";\nimport Spinner from \"./spinner-component.js\";\n\nexport default {\n  data() {\n    return {\n      loading: true,\n      elections: [],\n    };\n  },\n  components: {\n    Spinner,\n  },\n  methods: {\n    getElectionUrl: function (election) {\n      return RouterService.getElectionUrl(election.id);\n    },\n  },\n  async mounted() {\n    const result = await eel.get_elections()();\n    this.loading = false;\n    if (result.success) {\n      this.elections = result.result;\n    } else {\n      console.error(result.message);\n    }\n  },\n  template: /*html*/ `\n  <spinner :visible=\"loading\"></spinner>\n  <div v-if=\"elections && elections.length\" class=\"d-grid gap-2 d-md-block\">\n    <h2>Elections</h2>\n    <a :href=\"getElectionUrl(election)\" v-for=\"election in elections\" class=\"btn btn-primary me-2 mt-2\">{{ election.election_name }}</a>\n  </div>\n  `,\n};\n"
  },
  {
    "path": "src/electionguard_gui/web/components/shared/footer-component.js",
    "content": "export default {\n  data() {\n    return {\n      version: null,\n    };\n  },\n  async mounted() {\n    this.version = await eel.get_version()();\n  },\n  template: /*html*/ `\n  <nav class=\"navbar fixed-bottom bg-light\" v-if=\"version\">\n    <div class=\"container-fluid\">\n        <div class=\"col-12 pe-0 text-end text-secondary text-opacity-75\">\n          ElectionGuard version {{version}}\n        </div>\n    </div>\n  </nav>\n  `,\n};\n"
  },
  {
    "path": "src/electionguard_gui/web/components/shared/home-component.js",
    "content": "import AuthorizationService from \"../../services/authorization-service.js\";\nimport Spinner from \"./spinner-component.js\";\nimport AdminHome from \"../admin/admin-home-component.js\";\nimport GuardianHome from \"../guardian/guardian-home-component.js\";\n\nexport default {\n  mounted: async function () {\n    this.isAdmin = await AuthorizationService.isAdmin();\n  },\n  data() {\n    return {\n      isAdmin: undefined,\n    };\n  },\n  components: {\n    Spinner,\n    AdminHome,\n    GuardianHome,\n  },\n  template: /*html*/ `\n  <admin-home v-if=\"isAdmin === true\"></admin-home>\n  <guardian-home v-if=\"isAdmin === false\"></guardian-home>\n  <spinner :visible=\"isAdmin === undefined\"></spinner>\n  `,\n};\n"
  },
  {
    "path": "src/electionguard_gui/web/components/shared/key-ceremony-details-component.js",
    "content": "import Spinner from \"../shared/spinner-component.js\";\n\nexport default {\n  props: {\n    keyCeremonyId: String,\n  },\n  components: { Spinner },\n  data() {\n    return { keyCeremony: null, loading: false, error: false };\n  },\n  methods: {\n    join: async function () {\n      this.loading = true;\n      this.error = false;\n      const result = await eel.join_key_ceremony(this.keyCeremonyId)();\n      if (!result.success) {\n        this.error = true;\n      }\n      this.loading = false;\n    },\n    refresh_key_ceremony: function (eelMessage) {\n      console.log(\"key ceremony refreshed\", eelMessage);\n      if (eelMessage.success) {\n        this.keyCeremony = eelMessage.result;\n      } else {\n        console.error(eelMessage.message);\n        this.error = true;\n        this.keyCeremony = undefined;\n      }\n      this.loading = false;\n    },\n  },\n  mounted() {\n    eel.expose(this.refresh_key_ceremony, \"refresh_key_ceremony\");\n    this.error = false;\n    eel.watch_key_ceremony(this.keyCeremonyId);\n  },\n  unmounted() {\n    console.log(\"stop watching key ceremonies\");\n    eel.stop_watching_key_ceremony();\n  },\n  template: /*html*/ `\n    <div v-if=\"keyCeremony\">\n      <div class=\"container\">\n        <h1>{{keyCeremony.key_ceremony_name}}</h1>\n        <div class=\"row\">\n          <div class=\"col col-12 col-md-6 col-lg-5\">\n            <p>Guardians: {{keyCeremony.guardian_count}}</p>\n            <p>Quorum: {{keyCeremony.quorum}}</p>\n            <p>Created by: {{keyCeremony.created_by}}, {{keyCeremony.created_at_str}}</p>\n            <p v-if=\"keyCeremony.completed_at_str\">Completed: {{keyCeremony.completed_at_str}}</p>\n            <h3>Joined Guardians</h3>\n            <ul v-if=\"keyCeremony.guardians_joined.length\">\n              <li v-for=\"guardian in keyCeremony.guardians_joined\">{{guardian}}</li>\n            </ul>\n            <div v-else>\n              <p>No guardians have joined yet</p>\n            </div>\n            <button v-if=\"keyCeremony.can_join\" @click=\"join()\" :disabled=\"loading\" class=\"btn btn-primary\">Join</button>\n          </div>\n          <div class=\"col col-12 col-md-6 col-lg-7 text-center\">\n            <img v-if=\"keyCeremony.completed_at_str\" src=\"/images/check.svg\" width=\"200\" height=\"200\" class=\"mb-2\"></img>\n            <p class=\"key-ceremony-status\">{{keyCeremony.status}}</p>\n            <spinner :visible=\"loading || !keyCeremony.completed_at_str\"></spinner>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div v-else>\n      <div v-if=\"loading\">  \n        <spinner></spinner>\n      </div>\n      <div v-if=\"error\">\n        <p class=\"alert alert-danger\" role=\"alert\">An error occurred with the key ceremony. Check the logs and try again.</p>\n      </div>\n    </div>\n  `,\n};\n"
  },
  {
    "path": "src/electionguard_gui/web/components/shared/key-ceremony-list-component.js",
    "content": "import RouterService from \"../../services/router-service.js\";\nimport Spinner from \"./spinner-component.js\";\n\nexport default {\n  props: {\n    isAdmin: Boolean,\n    showWhenEmpty: Boolean,\n    keyCeremonies: Array,\n  },\n  data() {\n    return {\n      loading: true,\n    };\n  },\n  components: {\n    Spinner,\n  },\n  methods: {\n    getKeyCeremonyUrl: function (keyCeremony) {\n      const page = this.isAdmin\n        ? RouterService.routes.viewKeyCeremonyAdminPage\n        : RouterService.routes.viewKeyCeremonyGuardianPage;\n      return RouterService.getUrl(page, {\n        keyCeremonyId: keyCeremony.id,\n      });\n    },\n  },\n  template: /*html*/ `\n  <div v-if=\"showWhenEmpty && !keyCeremonies.length\">\n    <p>No key ceremonies found.</p>\n  </div>\n  <div v-if=\"keyCeremonies.length\" class=\"d-grid gap-2 d-md-block\">\n    <h2>Active Key Ceremonies</h2>\n    <a :href=\"getKeyCeremonyUrl(keyCeremony)\" v-for=\"keyCeremony in keyCeremonies\" class=\"btn btn-primary me-2 mt-2\">{{ keyCeremony.key_ceremony_name }}</a>\n  </div>\n  `,\n};\n"
  },
  {
    "path": "src/electionguard_gui/web/components/shared/login-component.js",
    "content": "import AuthorizationService from \"../../services/authorization-service.js\";\n\nexport default {\n  mounted: async function () {\n    const userId = await AuthorizationService.getUserId();\n    this.userId = userId;\n  },\n  data() {\n    return {\n      userId: null,\n    };\n  },\n  methods: {\n    async createUser() {\n      const form = document.getElementById(\"mainForm\");\n      if (form.checkValidity()) {\n        await AuthorizationService.setUserId(this.userId);\n        this.$emit(\"login\", this.userId);\n      }\n      form.classList.add(\"was-validated\");\n    },\n  },\n  template: /*html*/ `\n  <div class=\"col-md-8 mx-auto\">\n    <form id=\"mainForm\" class=\"needs-validation\" novalidate @submit.prevent=\"createUser\">\n      <div class=\"row\">\n        <div class=\"col-12\">\n          <h1>User Setup</h1>\n        </div>\n        <div class=\"col-12\">\n          <label for=\"keyName\" class=\"form-label\">User ID</label>\n          <input type=\"textbox\" class=\"form-control\" v-model=\"userId\" required pattern=\"[a-zA-Z0-9]+\" placeholder=\"Enter your name or other identifier\" />\n          <div class=\"invalid-feedback\">\n            User ID is required and cannot contain spaces\n          </div>\n        </div>\n        <div class=\"col-12 mt-4\">\n          <input type=\"submit\" class=\"btn btn-primary\" text=\"Continue\" />\n        </div>\n      </div>\n    </form>\n  </div>\n  `,\n};\n"
  },
  {
    "path": "src/electionguard_gui/web/components/shared/navbar-component.js",
    "content": "export default {\n  props: {\n    userId: String,\n  },\n  template: /*html*/ `\n  <nav class=\"navbar navbar-expand-md navbar-dark bg-primary\">\n    <div class=\"container-fluid\">\n      <a class=\"navbar-brand\" href=\"#/\">\n        <img\n          src=\"images/electionguard-icon.svg\"\n          height=\"30\"\n          class=\"d-inline-block align-text-top\"\n        />\n        ElectionGuard\n      </a>\n      <button\n        class=\"navbar-toggler ms-auto me-2\"\n        type=\"button\"\n        data-bs-toggle=\"collapse\"\n        data-bs-target=\"#navbarSupportedContent\"\n        aria-controls=\"navbarSupportedContent\"\n        aria-expanded=\"false\"\n        aria-label=\"Toggle navigation\"\n      >\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n      <div class=\"navbar-text\">\n        <span class=\"nav-link\">{{userId}}</span>\n      </div>\n    </div>\n  </nav>`,\n};\n"
  },
  {
    "path": "src/electionguard_gui/web/components/shared/not-found-component.js",
    "content": "export default {\n  template: /*html*/ `<h1>Page Not Found</h1>`,\n};\n"
  },
  {
    "path": "src/electionguard_gui/web/components/shared/spinner-component.js",
    "content": "let isMounted = false; /* Prevent duplicated styles in head tag */\n\nexport default {\n  props: {\n    visible: {\n      type: Boolean,\n      default: true,\n    },\n  },\n  mounted: function () {\n    console.debug(\"spinner activated\");\n    if (!isMounted) {\n      let styleElem = document.createElement(\"link\");\n      styleElem.id = \"spinner-style\";\n      styleElem.rel = \"stylesheet\";\n      styleElem.href = \"./css/spinner.css\";\n      document.head.appendChild(styleElem);\n      isMounted = true;\n    }\n  },\n  unmounted: function () {\n    console.debug(\"spinner deactivated\");\n    if (isMounted) {\n      document.head.removeChild(document.getElementById(\"spinner-style\"));\n      isMounted = false;\n    }\n  },\n  template: /*html*/ `\n    <div class=\"windows8\" v-if=\"visible\">\n        <div class=\"wBall\" id=\"wBall_1\">\n            <div class=\"wInnerBall\"></div>\n        </div>\n        <div class=\"wBall\" id=\"wBall_2\">\n            <div class=\"wInnerBall\"></div>\n        </div>\n        <div class=\"wBall\" id=\"wBall_3\">\n            <div class=\"wInnerBall\"></div>\n        </div>\n        <div class=\"wBall\" id=\"wBall_4\">\n            <div class=\"wInnerBall\"></div>\n        </div>\n        <div class=\"wBall\" id=\"wBall_5\">\n            <div class=\"wInnerBall\"></div>\n        </div>\n    </div>    \n    `,\n};\n"
  },
  {
    "path": "src/electionguard_gui/web/components/shared/view-plaintext-ballot-component.js",
    "content": "export default {\n  props: {\n    ballot: Object,\n  },\n  template: /*html*/ `\n    <div v-for=\"contest in ballot\" class=\"mb-5\">\n      <h2>{{contest.name}}</h2>\n      <table class=\"table table-striped\">\n        <thead>\n          <tr>\n            <th>Choice</th>\n            <th>Party</th>\n            <th class=\"text-end\" width=\"100\">Votes</th>\n            <th class=\"text-end\" width=\"100\">%</th>\n          </tr>\n        </thead>\n        <tbody>\n          <tr v-for=\"contestInfo in contest.details.selections\">\n            <td>{{contestInfo.name}}</td>\n            <td>{{contestInfo.party}}</td>\n            <td class=\"text-end\">{{contestInfo.tally}}</td>\n            <td class=\"text-end\">{{(contestInfo.percent * 100).toFixed(2) }}%</td>\n          </tr>\n          <tr class=\"table-secondary\">\n            <td></td>\n            <td></td>\n            <td class=\"text-end\"><strong>{{contest.details.nonWriteInTotal}}</strong></td>\n            <td class=\"text-end\"><strong>100.00%</strong></td>\n          </tr>\n          <tr v-if=\"contest.details.writeInTotal !== null\">\n            <td></td>\n            <td class=\"text-end\">Write-Ins</td>\n            <td class=\"text-end\">{{contest.details.writeInTotal}}</td>\n            <td class=\"text-end\"></td>\n          </tr>\n        </tbody>\n      </table>\n    </div>\n    `,\n};\n"
  },
  {
    "path": "src/electionguard_gui/web/css/bootstrap-icons.css",
    "content": "/* https://cdn.jsdelivr.net/npm/bootstrap-icons@1.9.1/font/bootstrap-icons.css */\n@font-face {\n  font-display: block;\n  font-family: \"bootstrap-icons\";\n  src: url(\"/fonts/bootstrap-icons.woff2?8d200481aa7f02a2d63a331fc782cfaf\")\n      format(\"woff2\"),\n    url(\"/fonts/bootstrap-icons.woff?8d200481aa7f02a2d63a331fc782cfaf\")\n      format(\"woff\");\n}\n\n.bi::before,\n[class^=\"bi-\"]::before,\n[class*=\" bi-\"]::before {\n  display: inline-block;\n  font-family: bootstrap-icons !important;\n  font-style: normal;\n  font-weight: normal !important;\n  font-variant: normal;\n  text-transform: none;\n  line-height: 1;\n  vertical-align: -0.125em;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\n.bi-123::before {\n  content: \"\\f67f\";\n}\n.bi-alarm-fill::before {\n  content: \"\\f101\";\n}\n.bi-alarm::before {\n  content: \"\\f102\";\n}\n.bi-align-bottom::before {\n  content: \"\\f103\";\n}\n.bi-align-center::before {\n  content: \"\\f104\";\n}\n.bi-align-end::before {\n  content: \"\\f105\";\n}\n.bi-align-middle::before {\n  content: \"\\f106\";\n}\n.bi-align-start::before {\n  content: \"\\f107\";\n}\n.bi-align-top::before {\n  content: \"\\f108\";\n}\n.bi-alt::before {\n  content: \"\\f109\";\n}\n.bi-app-indicator::before {\n  content: \"\\f10a\";\n}\n.bi-app::before {\n  content: \"\\f10b\";\n}\n.bi-archive-fill::before {\n  content: \"\\f10c\";\n}\n.bi-archive::before {\n  content: \"\\f10d\";\n}\n.bi-arrow-90deg-down::before {\n  content: \"\\f10e\";\n}\n.bi-arrow-90deg-left::before {\n  content: \"\\f10f\";\n}\n.bi-arrow-90deg-right::before {\n  content: \"\\f110\";\n}\n.bi-arrow-90deg-up::before {\n  content: \"\\f111\";\n}\n.bi-arrow-bar-down::before {\n  content: \"\\f112\";\n}\n.bi-arrow-bar-left::before {\n  content: \"\\f113\";\n}\n.bi-arrow-bar-right::before {\n  content: \"\\f114\";\n}\n.bi-arrow-bar-up::before {\n  content: \"\\f115\";\n}\n.bi-arrow-clockwise::before {\n  content: \"\\f116\";\n}\n.bi-arrow-counterclockwise::before {\n  content: \"\\f117\";\n}\n.bi-arrow-down-circle-fill::before {\n  content: \"\\f118\";\n}\n.bi-arrow-down-circle::before {\n  content: \"\\f119\";\n}\n.bi-arrow-down-left-circle-fill::before {\n  content: \"\\f11a\";\n}\n.bi-arrow-down-left-circle::before {\n  content: \"\\f11b\";\n}\n.bi-arrow-down-left-square-fill::before {\n  content: \"\\f11c\";\n}\n.bi-arrow-down-left-square::before {\n  content: \"\\f11d\";\n}\n.bi-arrow-down-left::before {\n  content: \"\\f11e\";\n}\n.bi-arrow-down-right-circle-fill::before {\n  content: \"\\f11f\";\n}\n.bi-arrow-down-right-circle::before {\n  content: \"\\f120\";\n}\n.bi-arrow-down-right-square-fill::before {\n  content: \"\\f121\";\n}\n.bi-arrow-down-right-square::before {\n  content: \"\\f122\";\n}\n.bi-arrow-down-right::before {\n  content: \"\\f123\";\n}\n.bi-arrow-down-short::before {\n  content: \"\\f124\";\n}\n.bi-arrow-down-square-fill::before {\n  content: \"\\f125\";\n}\n.bi-arrow-down-square::before {\n  content: \"\\f126\";\n}\n.bi-arrow-down-up::before {\n  content: \"\\f127\";\n}\n.bi-arrow-down::before {\n  content: \"\\f128\";\n}\n.bi-arrow-left-circle-fill::before {\n  content: \"\\f129\";\n}\n.bi-arrow-left-circle::before {\n  content: \"\\f12a\";\n}\n.bi-arrow-left-right::before {\n  content: \"\\f12b\";\n}\n.bi-arrow-left-short::before {\n  content: \"\\f12c\";\n}\n.bi-arrow-left-square-fill::before {\n  content: \"\\f12d\";\n}\n.bi-arrow-left-square::before {\n  content: \"\\f12e\";\n}\n.bi-arrow-left::before {\n  content: \"\\f12f\";\n}\n.bi-arrow-repeat::before {\n  content: \"\\f130\";\n}\n.bi-arrow-return-left::before {\n  content: \"\\f131\";\n}\n.bi-arrow-return-right::before {\n  content: \"\\f132\";\n}\n.bi-arrow-right-circle-fill::before {\n  content: \"\\f133\";\n}\n.bi-arrow-right-circle::before {\n  content: \"\\f134\";\n}\n.bi-arrow-right-short::before {\n  content: \"\\f135\";\n}\n.bi-arrow-right-square-fill::before {\n  content: \"\\f136\";\n}\n.bi-arrow-right-square::before {\n  content: \"\\f137\";\n}\n.bi-arrow-right::before {\n  content: \"\\f138\";\n}\n.bi-arrow-up-circle-fill::before {\n  content: \"\\f139\";\n}\n.bi-arrow-up-circle::before {\n  content: \"\\f13a\";\n}\n.bi-arrow-up-left-circle-fill::before {\n  content: \"\\f13b\";\n}\n.bi-arrow-up-left-circle::before {\n  content: \"\\f13c\";\n}\n.bi-arrow-up-left-square-fill::before {\n  content: \"\\f13d\";\n}\n.bi-arrow-up-left-square::before {\n  content: \"\\f13e\";\n}\n.bi-arrow-up-left::before {\n  content: \"\\f13f\";\n}\n.bi-arrow-up-right-circle-fill::before {\n  content: \"\\f140\";\n}\n.bi-arrow-up-right-circle::before {\n  content: \"\\f141\";\n}\n.bi-arrow-up-right-square-fill::before {\n  content: \"\\f142\";\n}\n.bi-arrow-up-right-square::before {\n  content: \"\\f143\";\n}\n.bi-arrow-up-right::before {\n  content: \"\\f144\";\n}\n.bi-arrow-up-short::before {\n  content: \"\\f145\";\n}\n.bi-arrow-up-square-fill::before {\n  content: \"\\f146\";\n}\n.bi-arrow-up-square::before {\n  content: \"\\f147\";\n}\n.bi-arrow-up::before {\n  content: \"\\f148\";\n}\n.bi-arrows-angle-contract::before {\n  content: \"\\f149\";\n}\n.bi-arrows-angle-expand::before {\n  content: \"\\f14a\";\n}\n.bi-arrows-collapse::before {\n  content: \"\\f14b\";\n}\n.bi-arrows-expand::before {\n  content: \"\\f14c\";\n}\n.bi-arrows-fullscreen::before {\n  content: \"\\f14d\";\n}\n.bi-arrows-move::before {\n  content: \"\\f14e\";\n}\n.bi-aspect-ratio-fill::before {\n  content: \"\\f14f\";\n}\n.bi-aspect-ratio::before {\n  content: \"\\f150\";\n}\n.bi-asterisk::before {\n  content: \"\\f151\";\n}\n.bi-at::before {\n  content: \"\\f152\";\n}\n.bi-award-fill::before {\n  content: \"\\f153\";\n}\n.bi-award::before {\n  content: \"\\f154\";\n}\n.bi-back::before {\n  content: \"\\f155\";\n}\n.bi-backspace-fill::before {\n  content: \"\\f156\";\n}\n.bi-backspace-reverse-fill::before {\n  content: \"\\f157\";\n}\n.bi-backspace-reverse::before {\n  content: \"\\f158\";\n}\n.bi-backspace::before {\n  content: \"\\f159\";\n}\n.bi-badge-3d-fill::before {\n  content: \"\\f15a\";\n}\n.bi-badge-3d::before {\n  content: \"\\f15b\";\n}\n.bi-badge-4k-fill::before {\n  content: \"\\f15c\";\n}\n.bi-badge-4k::before {\n  content: \"\\f15d\";\n}\n.bi-badge-8k-fill::before {\n  content: \"\\f15e\";\n}\n.bi-badge-8k::before {\n  content: \"\\f15f\";\n}\n.bi-badge-ad-fill::before {\n  content: \"\\f160\";\n}\n.bi-badge-ad::before {\n  content: \"\\f161\";\n}\n.bi-badge-ar-fill::before {\n  content: \"\\f162\";\n}\n.bi-badge-ar::before {\n  content: \"\\f163\";\n}\n.bi-badge-cc-fill::before {\n  content: \"\\f164\";\n}\n.bi-badge-cc::before {\n  content: \"\\f165\";\n}\n.bi-badge-hd-fill::before {\n  content: \"\\f166\";\n}\n.bi-badge-hd::before {\n  content: \"\\f167\";\n}\n.bi-badge-tm-fill::before {\n  content: \"\\f168\";\n}\n.bi-badge-tm::before {\n  content: \"\\f169\";\n}\n.bi-badge-vo-fill::before {\n  content: \"\\f16a\";\n}\n.bi-badge-vo::before {\n  content: \"\\f16b\";\n}\n.bi-badge-vr-fill::before {\n  content: \"\\f16c\";\n}\n.bi-badge-vr::before {\n  content: \"\\f16d\";\n}\n.bi-badge-wc-fill::before {\n  content: \"\\f16e\";\n}\n.bi-badge-wc::before {\n  content: \"\\f16f\";\n}\n.bi-bag-check-fill::before {\n  content: \"\\f170\";\n}\n.bi-bag-check::before {\n  content: \"\\f171\";\n}\n.bi-bag-dash-fill::before {\n  content: \"\\f172\";\n}\n.bi-bag-dash::before {\n  content: \"\\f173\";\n}\n.bi-bag-fill::before {\n  content: \"\\f174\";\n}\n.bi-bag-plus-fill::before {\n  content: \"\\f175\";\n}\n.bi-bag-plus::before {\n  content: \"\\f176\";\n}\n.bi-bag-x-fill::before {\n  content: \"\\f177\";\n}\n.bi-bag-x::before {\n  content: \"\\f178\";\n}\n.bi-bag::before {\n  content: \"\\f179\";\n}\n.bi-bar-chart-fill::before {\n  content: \"\\f17a\";\n}\n.bi-bar-chart-line-fill::before {\n  content: \"\\f17b\";\n}\n.bi-bar-chart-line::before {\n  content: \"\\f17c\";\n}\n.bi-bar-chart-steps::before {\n  content: \"\\f17d\";\n}\n.bi-bar-chart::before {\n  content: \"\\f17e\";\n}\n.bi-basket-fill::before {\n  content: \"\\f17f\";\n}\n.bi-basket::before {\n  content: \"\\f180\";\n}\n.bi-basket2-fill::before {\n  content: \"\\f181\";\n}\n.bi-basket2::before {\n  content: \"\\f182\";\n}\n.bi-basket3-fill::before {\n  content: \"\\f183\";\n}\n.bi-basket3::before {\n  content: \"\\f184\";\n}\n.bi-battery-charging::before {\n  content: \"\\f185\";\n}\n.bi-battery-full::before {\n  content: \"\\f186\";\n}\n.bi-battery-half::before {\n  content: \"\\f187\";\n}\n.bi-battery::before {\n  content: \"\\f188\";\n}\n.bi-bell-fill::before {\n  content: \"\\f189\";\n}\n.bi-bell::before {\n  content: \"\\f18a\";\n}\n.bi-bezier::before {\n  content: \"\\f18b\";\n}\n.bi-bezier2::before {\n  content: \"\\f18c\";\n}\n.bi-bicycle::before {\n  content: \"\\f18d\";\n}\n.bi-binoculars-fill::before {\n  content: \"\\f18e\";\n}\n.bi-binoculars::before {\n  content: \"\\f18f\";\n}\n.bi-blockquote-left::before {\n  content: \"\\f190\";\n}\n.bi-blockquote-right::before {\n  content: \"\\f191\";\n}\n.bi-book-fill::before {\n  content: \"\\f192\";\n}\n.bi-book-half::before {\n  content: \"\\f193\";\n}\n.bi-book::before {\n  content: \"\\f194\";\n}\n.bi-bookmark-check-fill::before {\n  content: \"\\f195\";\n}\n.bi-bookmark-check::before {\n  content: \"\\f196\";\n}\n.bi-bookmark-dash-fill::before {\n  content: \"\\f197\";\n}\n.bi-bookmark-dash::before {\n  content: \"\\f198\";\n}\n.bi-bookmark-fill::before {\n  content: \"\\f199\";\n}\n.bi-bookmark-heart-fill::before {\n  content: \"\\f19a\";\n}\n.bi-bookmark-heart::before {\n  content: \"\\f19b\";\n}\n.bi-bookmark-plus-fill::before {\n  content: \"\\f19c\";\n}\n.bi-bookmark-plus::before {\n  content: \"\\f19d\";\n}\n.bi-bookmark-star-fill::before {\n  content: \"\\f19e\";\n}\n.bi-bookmark-star::before {\n  content: \"\\f19f\";\n}\n.bi-bookmark-x-fill::before {\n  content: \"\\f1a0\";\n}\n.bi-bookmark-x::before {\n  content: \"\\f1a1\";\n}\n.bi-bookmark::before {\n  content: \"\\f1a2\";\n}\n.bi-bookmarks-fill::before {\n  content: \"\\f1a3\";\n}\n.bi-bookmarks::before {\n  content: \"\\f1a4\";\n}\n.bi-bookshelf::before {\n  content: \"\\f1a5\";\n}\n.bi-bootstrap-fill::before {\n  content: \"\\f1a6\";\n}\n.bi-bootstrap-reboot::before {\n  content: \"\\f1a7\";\n}\n.bi-bootstrap::before {\n  content: \"\\f1a8\";\n}\n.bi-border-all::before {\n  content: \"\\f1a9\";\n}\n.bi-border-bottom::before {\n  content: \"\\f1aa\";\n}\n.bi-border-center::before {\n  content: \"\\f1ab\";\n}\n.bi-border-inner::before {\n  content: \"\\f1ac\";\n}\n.bi-border-left::before {\n  content: \"\\f1ad\";\n}\n.bi-border-middle::before {\n  content: \"\\f1ae\";\n}\n.bi-border-outer::before {\n  content: \"\\f1af\";\n}\n.bi-border-right::before {\n  content: \"\\f1b0\";\n}\n.bi-border-style::before {\n  content: \"\\f1b1\";\n}\n.bi-border-top::before {\n  content: \"\\f1b2\";\n}\n.bi-border-width::before {\n  content: \"\\f1b3\";\n}\n.bi-border::before {\n  content: \"\\f1b4\";\n}\n.bi-bounding-box-circles::before {\n  content: \"\\f1b5\";\n}\n.bi-bounding-box::before {\n  content: \"\\f1b6\";\n}\n.bi-box-arrow-down-left::before {\n  content: \"\\f1b7\";\n}\n.bi-box-arrow-down-right::before {\n  content: \"\\f1b8\";\n}\n.bi-box-arrow-down::before {\n  content: \"\\f1b9\";\n}\n.bi-box-arrow-in-down-left::before {\n  content: \"\\f1ba\";\n}\n.bi-box-arrow-in-down-right::before {\n  content: \"\\f1bb\";\n}\n.bi-box-arrow-in-down::before {\n  content: \"\\f1bc\";\n}\n.bi-box-arrow-in-left::before {\n  content: \"\\f1bd\";\n}\n.bi-box-arrow-in-right::before {\n  content: \"\\f1be\";\n}\n.bi-box-arrow-in-up-left::before {\n  content: \"\\f1bf\";\n}\n.bi-box-arrow-in-up-right::before {\n  content: \"\\f1c0\";\n}\n.bi-box-arrow-in-up::before {\n  content: \"\\f1c1\";\n}\n.bi-box-arrow-left::before {\n  content: \"\\f1c2\";\n}\n.bi-box-arrow-right::before {\n  content: \"\\f1c3\";\n}\n.bi-box-arrow-up-left::before {\n  content: \"\\f1c4\";\n}\n.bi-box-arrow-up-right::before {\n  content: \"\\f1c5\";\n}\n.bi-box-arrow-up::before {\n  content: \"\\f1c6\";\n}\n.bi-box-seam::before {\n  content: \"\\f1c7\";\n}\n.bi-box::before {\n  content: \"\\f1c8\";\n}\n.bi-braces::before {\n  content: \"\\f1c9\";\n}\n.bi-bricks::before {\n  content: \"\\f1ca\";\n}\n.bi-briefcase-fill::before {\n  content: \"\\f1cb\";\n}\n.bi-briefcase::before {\n  content: \"\\f1cc\";\n}\n.bi-brightness-alt-high-fill::before {\n  content: \"\\f1cd\";\n}\n.bi-brightness-alt-high::before {\n  content: \"\\f1ce\";\n}\n.bi-brightness-alt-low-fill::before {\n  content: \"\\f1cf\";\n}\n.bi-brightness-alt-low::before {\n  content: \"\\f1d0\";\n}\n.bi-brightness-high-fill::before {\n  content: \"\\f1d1\";\n}\n.bi-brightness-high::before {\n  content: \"\\f1d2\";\n}\n.bi-brightness-low-fill::before {\n  content: \"\\f1d3\";\n}\n.bi-brightness-low::before {\n  content: \"\\f1d4\";\n}\n.bi-broadcast-pin::before {\n  content: \"\\f1d5\";\n}\n.bi-broadcast::before {\n  content: \"\\f1d6\";\n}\n.bi-brush-fill::before {\n  content: \"\\f1d7\";\n}\n.bi-brush::before {\n  content: \"\\f1d8\";\n}\n.bi-bucket-fill::before {\n  content: \"\\f1d9\";\n}\n.bi-bucket::before {\n  content: \"\\f1da\";\n}\n.bi-bug-fill::before {\n  content: \"\\f1db\";\n}\n.bi-bug::before {\n  content: \"\\f1dc\";\n}\n.bi-building::before {\n  content: \"\\f1dd\";\n}\n.bi-bullseye::before {\n  content: \"\\f1de\";\n}\n.bi-calculator-fill::before {\n  content: \"\\f1df\";\n}\n.bi-calculator::before {\n  content: \"\\f1e0\";\n}\n.bi-calendar-check-fill::before {\n  content: \"\\f1e1\";\n}\n.bi-calendar-check::before {\n  content: \"\\f1e2\";\n}\n.bi-calendar-date-fill::before {\n  content: \"\\f1e3\";\n}\n.bi-calendar-date::before {\n  content: \"\\f1e4\";\n}\n.bi-calendar-day-fill::before {\n  content: \"\\f1e5\";\n}\n.bi-calendar-day::before {\n  content: \"\\f1e6\";\n}\n.bi-calendar-event-fill::before {\n  content: \"\\f1e7\";\n}\n.bi-calendar-event::before {\n  content: \"\\f1e8\";\n}\n.bi-calendar-fill::before {\n  content: \"\\f1e9\";\n}\n.bi-calendar-minus-fill::before {\n  content: \"\\f1ea\";\n}\n.bi-calendar-minus::before {\n  content: \"\\f1eb\";\n}\n.bi-calendar-month-fill::before {\n  content: \"\\f1ec\";\n}\n.bi-calendar-month::before {\n  content: \"\\f1ed\";\n}\n.bi-calendar-plus-fill::before {\n  content: \"\\f1ee\";\n}\n.bi-calendar-plus::before {\n  content: \"\\f1ef\";\n}\n.bi-calendar-range-fill::before {\n  content: \"\\f1f0\";\n}\n.bi-calendar-range::before {\n  content: \"\\f1f1\";\n}\n.bi-calendar-week-fill::before {\n  content: \"\\f1f2\";\n}\n.bi-calendar-week::before {\n  content: \"\\f1f3\";\n}\n.bi-calendar-x-fill::before {\n  content: \"\\f1f4\";\n}\n.bi-calendar-x::before {\n  content: \"\\f1f5\";\n}\n.bi-calendar::before {\n  content: \"\\f1f6\";\n}\n.bi-calendar2-check-fill::before {\n  content: \"\\f1f7\";\n}\n.bi-calendar2-check::before {\n  content: \"\\f1f8\";\n}\n.bi-calendar2-date-fill::before {\n  content: \"\\f1f9\";\n}\n.bi-calendar2-date::before {\n  content: \"\\f1fa\";\n}\n.bi-calendar2-day-fill::before {\n  content: \"\\f1fb\";\n}\n.bi-calendar2-day::before {\n  content: \"\\f1fc\";\n}\n.bi-calendar2-event-fill::before {\n  content: \"\\f1fd\";\n}\n.bi-calendar2-event::before {\n  content: \"\\f1fe\";\n}\n.bi-calendar2-fill::before {\n  content: \"\\f1ff\";\n}\n.bi-calendar2-minus-fill::before {\n  content: \"\\f200\";\n}\n.bi-calendar2-minus::before {\n  content: \"\\f201\";\n}\n.bi-calendar2-month-fill::before {\n  content: \"\\f202\";\n}\n.bi-calendar2-month::before {\n  content: \"\\f203\";\n}\n.bi-calendar2-plus-fill::before {\n  content: \"\\f204\";\n}\n.bi-calendar2-plus::before {\n  content: \"\\f205\";\n}\n.bi-calendar2-range-fill::before {\n  content: \"\\f206\";\n}\n.bi-calendar2-range::before {\n  content: \"\\f207\";\n}\n.bi-calendar2-week-fill::before {\n  content: \"\\f208\";\n}\n.bi-calendar2-week::before {\n  content: \"\\f209\";\n}\n.bi-calendar2-x-fill::before {\n  content: \"\\f20a\";\n}\n.bi-calendar2-x::before {\n  content: \"\\f20b\";\n}\n.bi-calendar2::before {\n  content: \"\\f20c\";\n}\n.bi-calendar3-event-fill::before {\n  content: \"\\f20d\";\n}\n.bi-calendar3-event::before {\n  content: \"\\f20e\";\n}\n.bi-calendar3-fill::before {\n  content: \"\\f20f\";\n}\n.bi-calendar3-range-fill::before {\n  content: \"\\f210\";\n}\n.bi-calendar3-range::before {\n  content: \"\\f211\";\n}\n.bi-calendar3-week-fill::before {\n  content: \"\\f212\";\n}\n.bi-calendar3-week::before {\n  content: \"\\f213\";\n}\n.bi-calendar3::before {\n  content: \"\\f214\";\n}\n.bi-calendar4-event::before {\n  content: \"\\f215\";\n}\n.bi-calendar4-range::before {\n  content: \"\\f216\";\n}\n.bi-calendar4-week::before {\n  content: \"\\f217\";\n}\n.bi-calendar4::before {\n  content: \"\\f218\";\n}\n.bi-camera-fill::before {\n  content: \"\\f219\";\n}\n.bi-camera-reels-fill::before {\n  content: \"\\f21a\";\n}\n.bi-camera-reels::before {\n  content: \"\\f21b\";\n}\n.bi-camera-video-fill::before {\n  content: \"\\f21c\";\n}\n.bi-camera-video-off-fill::before {\n  content: \"\\f21d\";\n}\n.bi-camera-video-off::before {\n  content: \"\\f21e\";\n}\n.bi-camera-video::before {\n  content: \"\\f21f\";\n}\n.bi-camera::before {\n  content: \"\\f220\";\n}\n.bi-camera2::before {\n  content: \"\\f221\";\n}\n.bi-capslock-fill::before {\n  content: \"\\f222\";\n}\n.bi-capslock::before {\n  content: \"\\f223\";\n}\n.bi-card-checklist::before {\n  content: \"\\f224\";\n}\n.bi-card-heading::before {\n  content: \"\\f225\";\n}\n.bi-card-image::before {\n  content: \"\\f226\";\n}\n.bi-card-list::before {\n  content: \"\\f227\";\n}\n.bi-card-text::before {\n  content: \"\\f228\";\n}\n.bi-caret-down-fill::before {\n  content: \"\\f229\";\n}\n.bi-caret-down-square-fill::before {\n  content: \"\\f22a\";\n}\n.bi-caret-down-square::before {\n  content: \"\\f22b\";\n}\n.bi-caret-down::before {\n  content: \"\\f22c\";\n}\n.bi-caret-left-fill::before {\n  content: \"\\f22d\";\n}\n.bi-caret-left-square-fill::before {\n  content: \"\\f22e\";\n}\n.bi-caret-left-square::before {\n  content: \"\\f22f\";\n}\n.bi-caret-left::before {\n  content: \"\\f230\";\n}\n.bi-caret-right-fill::before {\n  content: \"\\f231\";\n}\n.bi-caret-right-square-fill::before {\n  content: \"\\f232\";\n}\n.bi-caret-right-square::before {\n  content: \"\\f233\";\n}\n.bi-caret-right::before {\n  content: \"\\f234\";\n}\n.bi-caret-up-fill::before {\n  content: \"\\f235\";\n}\n.bi-caret-up-square-fill::before {\n  content: \"\\f236\";\n}\n.bi-caret-up-square::before {\n  content: \"\\f237\";\n}\n.bi-caret-up::before {\n  content: \"\\f238\";\n}\n.bi-cart-check-fill::before {\n  content: \"\\f239\";\n}\n.bi-cart-check::before {\n  content: \"\\f23a\";\n}\n.bi-cart-dash-fill::before {\n  content: \"\\f23b\";\n}\n.bi-cart-dash::before {\n  content: \"\\f23c\";\n}\n.bi-cart-fill::before {\n  content: \"\\f23d\";\n}\n.bi-cart-plus-fill::before {\n  content: \"\\f23e\";\n}\n.bi-cart-plus::before {\n  content: \"\\f23f\";\n}\n.bi-cart-x-fill::before {\n  content: \"\\f240\";\n}\n.bi-cart-x::before {\n  content: \"\\f241\";\n}\n.bi-cart::before {\n  content: \"\\f242\";\n}\n.bi-cart2::before {\n  content: \"\\f243\";\n}\n.bi-cart3::before {\n  content: \"\\f244\";\n}\n.bi-cart4::before {\n  content: \"\\f245\";\n}\n.bi-cash-stack::before {\n  content: \"\\f246\";\n}\n.bi-cash::before {\n  content: \"\\f247\";\n}\n.bi-cast::before {\n  content: \"\\f248\";\n}\n.bi-chat-dots-fill::before {\n  content: \"\\f249\";\n}\n.bi-chat-dots::before {\n  content: \"\\f24a\";\n}\n.bi-chat-fill::before {\n  content: \"\\f24b\";\n}\n.bi-chat-left-dots-fill::before {\n  content: \"\\f24c\";\n}\n.bi-chat-left-dots::before {\n  content: \"\\f24d\";\n}\n.bi-chat-left-fill::before {\n  content: \"\\f24e\";\n}\n.bi-chat-left-quote-fill::before {\n  content: \"\\f24f\";\n}\n.bi-chat-left-quote::before {\n  content: \"\\f250\";\n}\n.bi-chat-left-text-fill::before {\n  content: \"\\f251\";\n}\n.bi-chat-left-text::before {\n  content: \"\\f252\";\n}\n.bi-chat-left::before {\n  content: \"\\f253\";\n}\n.bi-chat-quote-fill::before {\n  content: \"\\f254\";\n}\n.bi-chat-quote::before {\n  content: \"\\f255\";\n}\n.bi-chat-right-dots-fill::before {\n  content: \"\\f256\";\n}\n.bi-chat-right-dots::before {\n  content: \"\\f257\";\n}\n.bi-chat-right-fill::before {\n  content: \"\\f258\";\n}\n.bi-chat-right-quote-fill::before {\n  content: \"\\f259\";\n}\n.bi-chat-right-quote::before {\n  content: \"\\f25a\";\n}\n.bi-chat-right-text-fill::before {\n  content: \"\\f25b\";\n}\n.bi-chat-right-text::before {\n  content: \"\\f25c\";\n}\n.bi-chat-right::before {\n  content: \"\\f25d\";\n}\n.bi-chat-square-dots-fill::before {\n  content: \"\\f25e\";\n}\n.bi-chat-square-dots::before {\n  content: \"\\f25f\";\n}\n.bi-chat-square-fill::before {\n  content: \"\\f260\";\n}\n.bi-chat-square-quote-fill::before {\n  content: \"\\f261\";\n}\n.bi-chat-square-quote::before {\n  content: \"\\f262\";\n}\n.bi-chat-square-text-fill::before {\n  content: \"\\f263\";\n}\n.bi-chat-square-text::before {\n  content: \"\\f264\";\n}\n.bi-chat-square::before {\n  content: \"\\f265\";\n}\n.bi-chat-text-fill::before {\n  content: \"\\f266\";\n}\n.bi-chat-text::before {\n  content: \"\\f267\";\n}\n.bi-chat::before {\n  content: \"\\f268\";\n}\n.bi-check-all::before {\n  content: \"\\f269\";\n}\n.bi-check-circle-fill::before {\n  content: \"\\f26a\";\n}\n.bi-check-circle::before {\n  content: \"\\f26b\";\n}\n.bi-check-square-fill::before {\n  content: \"\\f26c\";\n}\n.bi-check-square::before {\n  content: \"\\f26d\";\n}\n.bi-check::before {\n  content: \"\\f26e\";\n}\n.bi-check2-all::before {\n  content: \"\\f26f\";\n}\n.bi-check2-circle::before {\n  content: \"\\f270\";\n}\n.bi-check2-square::before {\n  content: \"\\f271\";\n}\n.bi-check2::before {\n  content: \"\\f272\";\n}\n.bi-chevron-bar-contract::before {\n  content: \"\\f273\";\n}\n.bi-chevron-bar-down::before {\n  content: \"\\f274\";\n}\n.bi-chevron-bar-expand::before {\n  content: \"\\f275\";\n}\n.bi-chevron-bar-left::before {\n  content: \"\\f276\";\n}\n.bi-chevron-bar-right::before {\n  content: \"\\f277\";\n}\n.bi-chevron-bar-up::before {\n  content: \"\\f278\";\n}\n.bi-chevron-compact-down::before {\n  content: \"\\f279\";\n}\n.bi-chevron-compact-left::before {\n  content: \"\\f27a\";\n}\n.bi-chevron-compact-right::before {\n  content: \"\\f27b\";\n}\n.bi-chevron-compact-up::before {\n  content: \"\\f27c\";\n}\n.bi-chevron-contract::before {\n  content: \"\\f27d\";\n}\n.bi-chevron-double-down::before {\n  content: \"\\f27e\";\n}\n.bi-chevron-double-left::before {\n  content: \"\\f27f\";\n}\n.bi-chevron-double-right::before {\n  content: \"\\f280\";\n}\n.bi-chevron-double-up::before {\n  content: \"\\f281\";\n}\n.bi-chevron-down::before {\n  content: \"\\f282\";\n}\n.bi-chevron-expand::before {\n  content: \"\\f283\";\n}\n.bi-chevron-left::before {\n  content: \"\\f284\";\n}\n.bi-chevron-right::before {\n  content: \"\\f285\";\n}\n.bi-chevron-up::before {\n  content: \"\\f286\";\n}\n.bi-circle-fill::before {\n  content: \"\\f287\";\n}\n.bi-circle-half::before {\n  content: \"\\f288\";\n}\n.bi-circle-square::before {\n  content: \"\\f289\";\n}\n.bi-circle::before {\n  content: \"\\f28a\";\n}\n.bi-clipboard-check::before {\n  content: \"\\f28b\";\n}\n.bi-clipboard-data::before {\n  content: \"\\f28c\";\n}\n.bi-clipboard-minus::before {\n  content: \"\\f28d\";\n}\n.bi-clipboard-plus::before {\n  content: \"\\f28e\";\n}\n.bi-clipboard-x::before {\n  content: \"\\f28f\";\n}\n.bi-clipboard::before {\n  content: \"\\f290\";\n}\n.bi-clock-fill::before {\n  content: \"\\f291\";\n}\n.bi-clock-history::before {\n  content: \"\\f292\";\n}\n.bi-clock::before {\n  content: \"\\f293\";\n}\n.bi-cloud-arrow-down-fill::before {\n  content: \"\\f294\";\n}\n.bi-cloud-arrow-down::before {\n  content: \"\\f295\";\n}\n.bi-cloud-arrow-up-fill::before {\n  content: \"\\f296\";\n}\n.bi-cloud-arrow-up::before {\n  content: \"\\f297\";\n}\n.bi-cloud-check-fill::before {\n  content: \"\\f298\";\n}\n.bi-cloud-check::before {\n  content: \"\\f299\";\n}\n.bi-cloud-download-fill::before {\n  content: \"\\f29a\";\n}\n.bi-cloud-download::before {\n  content: \"\\f29b\";\n}\n.bi-cloud-drizzle-fill::before {\n  content: \"\\f29c\";\n}\n.bi-cloud-drizzle::before {\n  content: \"\\f29d\";\n}\n.bi-cloud-fill::before {\n  content: \"\\f29e\";\n}\n.bi-cloud-fog-fill::before {\n  content: \"\\f29f\";\n}\n.bi-cloud-fog::before {\n  content: \"\\f2a0\";\n}\n.bi-cloud-fog2-fill::before {\n  content: \"\\f2a1\";\n}\n.bi-cloud-fog2::before {\n  content: \"\\f2a2\";\n}\n.bi-cloud-hail-fill::before {\n  content: \"\\f2a3\";\n}\n.bi-cloud-hail::before {\n  content: \"\\f2a4\";\n}\n.bi-cloud-haze-1::before {\n  content: \"\\f2a5\";\n}\n.bi-cloud-haze-fill::before {\n  content: \"\\f2a6\";\n}\n.bi-cloud-haze::before {\n  content: \"\\f2a7\";\n}\n.bi-cloud-haze2-fill::before {\n  content: \"\\f2a8\";\n}\n.bi-cloud-lightning-fill::before {\n  content: \"\\f2a9\";\n}\n.bi-cloud-lightning-rain-fill::before {\n  content: \"\\f2aa\";\n}\n.bi-cloud-lightning-rain::before {\n  content: \"\\f2ab\";\n}\n.bi-cloud-lightning::before {\n  content: \"\\f2ac\";\n}\n.bi-cloud-minus-fill::before {\n  content: \"\\f2ad\";\n}\n.bi-cloud-minus::before {\n  content: \"\\f2ae\";\n}\n.bi-cloud-moon-fill::before {\n  content: \"\\f2af\";\n}\n.bi-cloud-moon::before {\n  content: \"\\f2b0\";\n}\n.bi-cloud-plus-fill::before {\n  content: \"\\f2b1\";\n}\n.bi-cloud-plus::before {\n  content: \"\\f2b2\";\n}\n.bi-cloud-rain-fill::before {\n  content: \"\\f2b3\";\n}\n.bi-cloud-rain-heavy-fill::before {\n  content: \"\\f2b4\";\n}\n.bi-cloud-rain-heavy::before {\n  content: \"\\f2b5\";\n}\n.bi-cloud-rain::before {\n  content: \"\\f2b6\";\n}\n.bi-cloud-slash-fill::before {\n  content: \"\\f2b7\";\n}\n.bi-cloud-slash::before {\n  content: \"\\f2b8\";\n}\n.bi-cloud-sleet-fill::before {\n  content: \"\\f2b9\";\n}\n.bi-cloud-sleet::before {\n  content: \"\\f2ba\";\n}\n.bi-cloud-snow-fill::before {\n  content: \"\\f2bb\";\n}\n.bi-cloud-snow::before {\n  content: \"\\f2bc\";\n}\n.bi-cloud-sun-fill::before {\n  content: \"\\f2bd\";\n}\n.bi-cloud-sun::before {\n  content: \"\\f2be\";\n}\n.bi-cloud-upload-fill::before {\n  content: \"\\f2bf\";\n}\n.bi-cloud-upload::before {\n  content: \"\\f2c0\";\n}\n.bi-cloud::before {\n  content: \"\\f2c1\";\n}\n.bi-clouds-fill::before {\n  content: \"\\f2c2\";\n}\n.bi-clouds::before {\n  content: \"\\f2c3\";\n}\n.bi-cloudy-fill::before {\n  content: \"\\f2c4\";\n}\n.bi-cloudy::before {\n  content: \"\\f2c5\";\n}\n.bi-code-slash::before {\n  content: \"\\f2c6\";\n}\n.bi-code-square::before {\n  content: \"\\f2c7\";\n}\n.bi-code::before {\n  content: \"\\f2c8\";\n}\n.bi-collection-fill::before {\n  content: \"\\f2c9\";\n}\n.bi-collection-play-fill::before {\n  content: \"\\f2ca\";\n}\n.bi-collection-play::before {\n  content: \"\\f2cb\";\n}\n.bi-collection::before {\n  content: \"\\f2cc\";\n}\n.bi-columns-gap::before {\n  content: \"\\f2cd\";\n}\n.bi-columns::before {\n  content: \"\\f2ce\";\n}\n.bi-command::before {\n  content: \"\\f2cf\";\n}\n.bi-compass-fill::before {\n  content: \"\\f2d0\";\n}\n.bi-compass::before {\n  content: \"\\f2d1\";\n}\n.bi-cone-striped::before {\n  content: \"\\f2d2\";\n}\n.bi-cone::before {\n  content: \"\\f2d3\";\n}\n.bi-controller::before {\n  content: \"\\f2d4\";\n}\n.bi-cpu-fill::before {\n  content: \"\\f2d5\";\n}\n.bi-cpu::before {\n  content: \"\\f2d6\";\n}\n.bi-credit-card-2-back-fill::before {\n  content: \"\\f2d7\";\n}\n.bi-credit-card-2-back::before {\n  content: \"\\f2d8\";\n}\n.bi-credit-card-2-front-fill::before {\n  content: \"\\f2d9\";\n}\n.bi-credit-card-2-front::before {\n  content: \"\\f2da\";\n}\n.bi-credit-card-fill::before {\n  content: \"\\f2db\";\n}\n.bi-credit-card::before {\n  content: \"\\f2dc\";\n}\n.bi-crop::before {\n  content: \"\\f2dd\";\n}\n.bi-cup-fill::before {\n  content: \"\\f2de\";\n}\n.bi-cup-straw::before {\n  content: \"\\f2df\";\n}\n.bi-cup::before {\n  content: \"\\f2e0\";\n}\n.bi-cursor-fill::before {\n  content: \"\\f2e1\";\n}\n.bi-cursor-text::before {\n  content: \"\\f2e2\";\n}\n.bi-cursor::before {\n  content: \"\\f2e3\";\n}\n.bi-dash-circle-dotted::before {\n  content: \"\\f2e4\";\n}\n.bi-dash-circle-fill::before {\n  content: \"\\f2e5\";\n}\n.bi-dash-circle::before {\n  content: \"\\f2e6\";\n}\n.bi-dash-square-dotted::before {\n  content: \"\\f2e7\";\n}\n.bi-dash-square-fill::before {\n  content: \"\\f2e8\";\n}\n.bi-dash-square::before {\n  content: \"\\f2e9\";\n}\n.bi-dash::before {\n  content: \"\\f2ea\";\n}\n.bi-diagram-2-fill::before {\n  content: \"\\f2eb\";\n}\n.bi-diagram-2::before {\n  content: \"\\f2ec\";\n}\n.bi-diagram-3-fill::before {\n  content: \"\\f2ed\";\n}\n.bi-diagram-3::before {\n  content: \"\\f2ee\";\n}\n.bi-diamond-fill::before {\n  content: \"\\f2ef\";\n}\n.bi-diamond-half::before {\n  content: \"\\f2f0\";\n}\n.bi-diamond::before {\n  content: \"\\f2f1\";\n}\n.bi-dice-1-fill::before {\n  content: \"\\f2f2\";\n}\n.bi-dice-1::before {\n  content: \"\\f2f3\";\n}\n.bi-dice-2-fill::before {\n  content: \"\\f2f4\";\n}\n.bi-dice-2::before {\n  content: \"\\f2f5\";\n}\n.bi-dice-3-fill::before {\n  content: \"\\f2f6\";\n}\n.bi-dice-3::before {\n  content: \"\\f2f7\";\n}\n.bi-dice-4-fill::before {\n  content: \"\\f2f8\";\n}\n.bi-dice-4::before {\n  content: \"\\f2f9\";\n}\n.bi-dice-5-fill::before {\n  content: \"\\f2fa\";\n}\n.bi-dice-5::before {\n  content: \"\\f2fb\";\n}\n.bi-dice-6-fill::before {\n  content: \"\\f2fc\";\n}\n.bi-dice-6::before {\n  content: \"\\f2fd\";\n}\n.bi-disc-fill::before {\n  content: \"\\f2fe\";\n}\n.bi-disc::before {\n  content: \"\\f2ff\";\n}\n.bi-discord::before {\n  content: \"\\f300\";\n}\n.bi-display-fill::before {\n  content: \"\\f301\";\n}\n.bi-display::before {\n  content: \"\\f302\";\n}\n.bi-distribute-horizontal::before {\n  content: \"\\f303\";\n}\n.bi-distribute-vertical::before {\n  content: \"\\f304\";\n}\n.bi-door-closed-fill::before {\n  content: \"\\f305\";\n}\n.bi-door-closed::before {\n  content: \"\\f306\";\n}\n.bi-door-open-fill::before {\n  content: \"\\f307\";\n}\n.bi-door-open::before {\n  content: \"\\f308\";\n}\n.bi-dot::before {\n  content: \"\\f309\";\n}\n.bi-download::before {\n  content: \"\\f30a\";\n}\n.bi-droplet-fill::before {\n  content: \"\\f30b\";\n}\n.bi-droplet-half::before {\n  content: \"\\f30c\";\n}\n.bi-droplet::before {\n  content: \"\\f30d\";\n}\n.bi-earbuds::before {\n  content: \"\\f30e\";\n}\n.bi-easel-fill::before {\n  content: \"\\f30f\";\n}\n.bi-easel::before {\n  content: \"\\f310\";\n}\n.bi-egg-fill::before {\n  content: \"\\f311\";\n}\n.bi-egg-fried::before {\n  content: \"\\f312\";\n}\n.bi-egg::before {\n  content: \"\\f313\";\n}\n.bi-eject-fill::before {\n  content: \"\\f314\";\n}\n.bi-eject::before {\n  content: \"\\f315\";\n}\n.bi-emoji-angry-fill::before {\n  content: \"\\f316\";\n}\n.bi-emoji-angry::before {\n  content: \"\\f317\";\n}\n.bi-emoji-dizzy-fill::before {\n  content: \"\\f318\";\n}\n.bi-emoji-dizzy::before {\n  content: \"\\f319\";\n}\n.bi-emoji-expressionless-fill::before {\n  content: \"\\f31a\";\n}\n.bi-emoji-expressionless::before {\n  content: \"\\f31b\";\n}\n.bi-emoji-frown-fill::before {\n  content: \"\\f31c\";\n}\n.bi-emoji-frown::before {\n  content: \"\\f31d\";\n}\n.bi-emoji-heart-eyes-fill::before {\n  content: \"\\f31e\";\n}\n.bi-emoji-heart-eyes::before {\n  content: \"\\f31f\";\n}\n.bi-emoji-laughing-fill::before {\n  content: \"\\f320\";\n}\n.bi-emoji-laughing::before {\n  content: \"\\f321\";\n}\n.bi-emoji-neutral-fill::before {\n  content: \"\\f322\";\n}\n.bi-emoji-neutral::before {\n  content: \"\\f323\";\n}\n.bi-emoji-smile-fill::before {\n  content: \"\\f324\";\n}\n.bi-emoji-smile-upside-down-fill::before {\n  content: \"\\f325\";\n}\n.bi-emoji-smile-upside-down::before {\n  content: \"\\f326\";\n}\n.bi-emoji-smile::before {\n  content: \"\\f327\";\n}\n.bi-emoji-sunglasses-fill::before {\n  content: \"\\f328\";\n}\n.bi-emoji-sunglasses::before {\n  content: \"\\f329\";\n}\n.bi-emoji-wink-fill::before {\n  content: \"\\f32a\";\n}\n.bi-emoji-wink::before {\n  content: \"\\f32b\";\n}\n.bi-envelope-fill::before {\n  content: \"\\f32c\";\n}\n.bi-envelope-open-fill::before {\n  content: \"\\f32d\";\n}\n.bi-envelope-open::before {\n  content: \"\\f32e\";\n}\n.bi-envelope::before {\n  content: \"\\f32f\";\n}\n.bi-eraser-fill::before {\n  content: \"\\f330\";\n}\n.bi-eraser::before {\n  content: \"\\f331\";\n}\n.bi-exclamation-circle-fill::before {\n  content: \"\\f332\";\n}\n.bi-exclamation-circle::before {\n  content: \"\\f333\";\n}\n.bi-exclamation-diamond-fill::before {\n  content: \"\\f334\";\n}\n.bi-exclamation-diamond::before {\n  content: \"\\f335\";\n}\n.bi-exclamation-octagon-fill::before {\n  content: \"\\f336\";\n}\n.bi-exclamation-octagon::before {\n  content: \"\\f337\";\n}\n.bi-exclamation-square-fill::before {\n  content: \"\\f338\";\n}\n.bi-exclamation-square::before {\n  content: \"\\f339\";\n}\n.bi-exclamation-triangle-fill::before {\n  content: \"\\f33a\";\n}\n.bi-exclamation-triangle::before {\n  content: \"\\f33b\";\n}\n.bi-exclamation::before {\n  content: \"\\f33c\";\n}\n.bi-exclude::before {\n  content: \"\\f33d\";\n}\n.bi-eye-fill::before {\n  content: \"\\f33e\";\n}\n.bi-eye-slash-fill::before {\n  content: \"\\f33f\";\n}\n.bi-eye-slash::before {\n  content: \"\\f340\";\n}\n.bi-eye::before {\n  content: \"\\f341\";\n}\n.bi-eyedropper::before {\n  content: \"\\f342\";\n}\n.bi-eyeglasses::before {\n  content: \"\\f343\";\n}\n.bi-facebook::before {\n  content: \"\\f344\";\n}\n.bi-file-arrow-down-fill::before {\n  content: \"\\f345\";\n}\n.bi-file-arrow-down::before {\n  content: \"\\f346\";\n}\n.bi-file-arrow-up-fill::before {\n  content: \"\\f347\";\n}\n.bi-file-arrow-up::before {\n  content: \"\\f348\";\n}\n.bi-file-bar-graph-fill::before {\n  content: \"\\f349\";\n}\n.bi-file-bar-graph::before {\n  content: \"\\f34a\";\n}\n.bi-file-binary-fill::before {\n  content: \"\\f34b\";\n}\n.bi-file-binary::before {\n  content: \"\\f34c\";\n}\n.bi-file-break-fill::before {\n  content: \"\\f34d\";\n}\n.bi-file-break::before {\n  content: \"\\f34e\";\n}\n.bi-file-check-fill::before {\n  content: \"\\f34f\";\n}\n.bi-file-check::before {\n  content: \"\\f350\";\n}\n.bi-file-code-fill::before {\n  content: \"\\f351\";\n}\n.bi-file-code::before {\n  content: \"\\f352\";\n}\n.bi-file-diff-fill::before {\n  content: \"\\f353\";\n}\n.bi-file-diff::before {\n  content: \"\\f354\";\n}\n.bi-file-earmark-arrow-down-fill::before {\n  content: \"\\f355\";\n}\n.bi-file-earmark-arrow-down::before {\n  content: \"\\f356\";\n}\n.bi-file-earmark-arrow-up-fill::before {\n  content: \"\\f357\";\n}\n.bi-file-earmark-arrow-up::before {\n  content: \"\\f358\";\n}\n.bi-file-earmark-bar-graph-fill::before {\n  content: \"\\f359\";\n}\n.bi-file-earmark-bar-graph::before {\n  content: \"\\f35a\";\n}\n.bi-file-earmark-binary-fill::before {\n  content: \"\\f35b\";\n}\n.bi-file-earmark-binary::before {\n  content: \"\\f35c\";\n}\n.bi-file-earmark-break-fill::before {\n  content: \"\\f35d\";\n}\n.bi-file-earmark-break::before {\n  content: \"\\f35e\";\n}\n.bi-file-earmark-check-fill::before {\n  content: \"\\f35f\";\n}\n.bi-file-earmark-check::before {\n  content: \"\\f360\";\n}\n.bi-file-earmark-code-fill::before {\n  content: \"\\f361\";\n}\n.bi-file-earmark-code::before {\n  content: \"\\f362\";\n}\n.bi-file-earmark-diff-fill::before {\n  content: \"\\f363\";\n}\n.bi-file-earmark-diff::before {\n  content: \"\\f364\";\n}\n.bi-file-earmark-easel-fill::before {\n  content: \"\\f365\";\n}\n.bi-file-earmark-easel::before {\n  content: \"\\f366\";\n}\n.bi-file-earmark-excel-fill::before {\n  content: \"\\f367\";\n}\n.bi-file-earmark-excel::before {\n  content: \"\\f368\";\n}\n.bi-file-earmark-fill::before {\n  content: \"\\f369\";\n}\n.bi-file-earmark-font-fill::before {\n  content: \"\\f36a\";\n}\n.bi-file-earmark-font::before {\n  content: \"\\f36b\";\n}\n.bi-file-earmark-image-fill::before {\n  content: \"\\f36c\";\n}\n.bi-file-earmark-image::before {\n  content: \"\\f36d\";\n}\n.bi-file-earmark-lock-fill::before {\n  content: \"\\f36e\";\n}\n.bi-file-earmark-lock::before {\n  content: \"\\f36f\";\n}\n.bi-file-earmark-lock2-fill::before {\n  content: \"\\f370\";\n}\n.bi-file-earmark-lock2::before {\n  content: \"\\f371\";\n}\n.bi-file-earmark-medical-fill::before {\n  content: \"\\f372\";\n}\n.bi-file-earmark-medical::before {\n  content: \"\\f373\";\n}\n.bi-file-earmark-minus-fill::before {\n  content: \"\\f374\";\n}\n.bi-file-earmark-minus::before {\n  content: \"\\f375\";\n}\n.bi-file-earmark-music-fill::before {\n  content: \"\\f376\";\n}\n.bi-file-earmark-music::before {\n  content: \"\\f377\";\n}\n.bi-file-earmark-person-fill::before {\n  content: \"\\f378\";\n}\n.bi-file-earmark-person::before {\n  content: \"\\f379\";\n}\n.bi-file-earmark-play-fill::before {\n  content: \"\\f37a\";\n}\n.bi-file-earmark-play::before {\n  content: \"\\f37b\";\n}\n.bi-file-earmark-plus-fill::before {\n  content: \"\\f37c\";\n}\n.bi-file-earmark-plus::before {\n  content: \"\\f37d\";\n}\n.bi-file-earmark-post-fill::before {\n  content: \"\\f37e\";\n}\n.bi-file-earmark-post::before {\n  content: \"\\f37f\";\n}\n.bi-file-earmark-ppt-fill::before {\n  content: \"\\f380\";\n}\n.bi-file-earmark-ppt::before {\n  content: \"\\f381\";\n}\n.bi-file-earmark-richtext-fill::before {\n  content: \"\\f382\";\n}\n.bi-file-earmark-richtext::before {\n  content: \"\\f383\";\n}\n.bi-file-earmark-ruled-fill::before {\n  content: \"\\f384\";\n}\n.bi-file-earmark-ruled::before {\n  content: \"\\f385\";\n}\n.bi-file-earmark-slides-fill::before {\n  content: \"\\f386\";\n}\n.bi-file-earmark-slides::before {\n  content: \"\\f387\";\n}\n.bi-file-earmark-spreadsheet-fill::before {\n  content: \"\\f388\";\n}\n.bi-file-earmark-spreadsheet::before {\n  content: \"\\f389\";\n}\n.bi-file-earmark-text-fill::before {\n  content: \"\\f38a\";\n}\n.bi-file-earmark-text::before {\n  content: \"\\f38b\";\n}\n.bi-file-earmark-word-fill::before {\n  content: \"\\f38c\";\n}\n.bi-file-earmark-word::before {\n  content: \"\\f38d\";\n}\n.bi-file-earmark-x-fill::before {\n  content: \"\\f38e\";\n}\n.bi-file-earmark-x::before {\n  content: \"\\f38f\";\n}\n.bi-file-earmark-zip-fill::before {\n  content: \"\\f390\";\n}\n.bi-file-earmark-zip::before {\n  content: \"\\f391\";\n}\n.bi-file-earmark::before {\n  content: \"\\f392\";\n}\n.bi-file-easel-fill::before {\n  content: \"\\f393\";\n}\n.bi-file-easel::before {\n  content: \"\\f394\";\n}\n.bi-file-excel-fill::before {\n  content: \"\\f395\";\n}\n.bi-file-excel::before {\n  content: \"\\f396\";\n}\n.bi-file-fill::before {\n  content: \"\\f397\";\n}\n.bi-file-font-fill::before {\n  content: \"\\f398\";\n}\n.bi-file-font::before {\n  content: \"\\f399\";\n}\n.bi-file-image-fill::before {\n  content: \"\\f39a\";\n}\n.bi-file-image::before {\n  content: \"\\f39b\";\n}\n.bi-file-lock-fill::before {\n  content: \"\\f39c\";\n}\n.bi-file-lock::before {\n  content: \"\\f39d\";\n}\n.bi-file-lock2-fill::before {\n  content: \"\\f39e\";\n}\n.bi-file-lock2::before {\n  content: \"\\f39f\";\n}\n.bi-file-medical-fill::before {\n  content: \"\\f3a0\";\n}\n.bi-file-medical::before {\n  content: \"\\f3a1\";\n}\n.bi-file-minus-fill::before {\n  content: \"\\f3a2\";\n}\n.bi-file-minus::before {\n  content: \"\\f3a3\";\n}\n.bi-file-music-fill::before {\n  content: \"\\f3a4\";\n}\n.bi-file-music::before {\n  content: \"\\f3a5\";\n}\n.bi-file-person-fill::before {\n  content: \"\\f3a6\";\n}\n.bi-file-person::before {\n  content: \"\\f3a7\";\n}\n.bi-file-play-fill::before {\n  content: \"\\f3a8\";\n}\n.bi-file-play::before {\n  content: \"\\f3a9\";\n}\n.bi-file-plus-fill::before {\n  content: \"\\f3aa\";\n}\n.bi-file-plus::before {\n  content: \"\\f3ab\";\n}\n.bi-file-post-fill::before {\n  content: \"\\f3ac\";\n}\n.bi-file-post::before {\n  content: \"\\f3ad\";\n}\n.bi-file-ppt-fill::before {\n  content: \"\\f3ae\";\n}\n.bi-file-ppt::before {\n  content: \"\\f3af\";\n}\n.bi-file-richtext-fill::before {\n  content: \"\\f3b0\";\n}\n.bi-file-richtext::before {\n  content: \"\\f3b1\";\n}\n.bi-file-ruled-fill::before {\n  content: \"\\f3b2\";\n}\n.bi-file-ruled::before {\n  content: \"\\f3b3\";\n}\n.bi-file-slides-fill::before {\n  content: \"\\f3b4\";\n}\n.bi-file-slides::before {\n  content: \"\\f3b5\";\n}\n.bi-file-spreadsheet-fill::before {\n  content: \"\\f3b6\";\n}\n.bi-file-spreadsheet::before {\n  content: \"\\f3b7\";\n}\n.bi-file-text-fill::before {\n  content: \"\\f3b8\";\n}\n.bi-file-text::before {\n  content: \"\\f3b9\";\n}\n.bi-file-word-fill::before {\n  content: \"\\f3ba\";\n}\n.bi-file-word::before {\n  content: \"\\f3bb\";\n}\n.bi-file-x-fill::before {\n  content: \"\\f3bc\";\n}\n.bi-file-x::before {\n  content: \"\\f3bd\";\n}\n.bi-file-zip-fill::before {\n  content: \"\\f3be\";\n}\n.bi-file-zip::before {\n  content: \"\\f3bf\";\n}\n.bi-file::before {\n  content: \"\\f3c0\";\n}\n.bi-files-alt::before {\n  content: \"\\f3c1\";\n}\n.bi-files::before {\n  content: \"\\f3c2\";\n}\n.bi-film::before {\n  content: \"\\f3c3\";\n}\n.bi-filter-circle-fill::before {\n  content: \"\\f3c4\";\n}\n.bi-filter-circle::before {\n  content: \"\\f3c5\";\n}\n.bi-filter-left::before {\n  content: \"\\f3c6\";\n}\n.bi-filter-right::before {\n  content: \"\\f3c7\";\n}\n.bi-filter-square-fill::before {\n  content: \"\\f3c8\";\n}\n.bi-filter-square::before {\n  content: \"\\f3c9\";\n}\n.bi-filter::before {\n  content: \"\\f3ca\";\n}\n.bi-flag-fill::before {\n  content: \"\\f3cb\";\n}\n.bi-flag::before {\n  content: \"\\f3cc\";\n}\n.bi-flower1::before {\n  content: \"\\f3cd\";\n}\n.bi-flower2::before {\n  content: \"\\f3ce\";\n}\n.bi-flower3::before {\n  content: \"\\f3cf\";\n}\n.bi-folder-check::before {\n  content: \"\\f3d0\";\n}\n.bi-folder-fill::before {\n  content: \"\\f3d1\";\n}\n.bi-folder-minus::before {\n  content: \"\\f3d2\";\n}\n.bi-folder-plus::before {\n  content: \"\\f3d3\";\n}\n.bi-folder-symlink-fill::before {\n  content: \"\\f3d4\";\n}\n.bi-folder-symlink::before {\n  content: \"\\f3d5\";\n}\n.bi-folder-x::before {\n  content: \"\\f3d6\";\n}\n.bi-folder::before {\n  content: \"\\f3d7\";\n}\n.bi-folder2-open::before {\n  content: \"\\f3d8\";\n}\n.bi-folder2::before {\n  content: \"\\f3d9\";\n}\n.bi-fonts::before {\n  content: \"\\f3da\";\n}\n.bi-forward-fill::before {\n  content: \"\\f3db\";\n}\n.bi-forward::before {\n  content: \"\\f3dc\";\n}\n.bi-front::before {\n  content: \"\\f3dd\";\n}\n.bi-fullscreen-exit::before {\n  content: \"\\f3de\";\n}\n.bi-fullscreen::before {\n  content: \"\\f3df\";\n}\n.bi-funnel-fill::before {\n  content: \"\\f3e0\";\n}\n.bi-funnel::before {\n  content: \"\\f3e1\";\n}\n.bi-gear-fill::before {\n  content: \"\\f3e2\";\n}\n.bi-gear-wide-connected::before {\n  content: \"\\f3e3\";\n}\n.bi-gear-wide::before {\n  content: \"\\f3e4\";\n}\n.bi-gear::before {\n  content: \"\\f3e5\";\n}\n.bi-gem::before {\n  content: \"\\f3e6\";\n}\n.bi-geo-alt-fill::before {\n  content: \"\\f3e7\";\n}\n.bi-geo-alt::before {\n  content: \"\\f3e8\";\n}\n.bi-geo-fill::before {\n  content: \"\\f3e9\";\n}\n.bi-geo::before {\n  content: \"\\f3ea\";\n}\n.bi-gift-fill::before {\n  content: \"\\f3eb\";\n}\n.bi-gift::before {\n  content: \"\\f3ec\";\n}\n.bi-github::before {\n  content: \"\\f3ed\";\n}\n.bi-globe::before {\n  content: \"\\f3ee\";\n}\n.bi-globe2::before {\n  content: \"\\f3ef\";\n}\n.bi-google::before {\n  content: \"\\f3f0\";\n}\n.bi-graph-down::before {\n  content: \"\\f3f1\";\n}\n.bi-graph-up::before {\n  content: \"\\f3f2\";\n}\n.bi-grid-1x2-fill::before {\n  content: \"\\f3f3\";\n}\n.bi-grid-1x2::before {\n  content: \"\\f3f4\";\n}\n.bi-grid-3x2-gap-fill::before {\n  content: \"\\f3f5\";\n}\n.bi-grid-3x2-gap::before {\n  content: \"\\f3f6\";\n}\n.bi-grid-3x2::before {\n  content: \"\\f3f7\";\n}\n.bi-grid-3x3-gap-fill::before {\n  content: \"\\f3f8\";\n}\n.bi-grid-3x3-gap::before {\n  content: \"\\f3f9\";\n}\n.bi-grid-3x3::before {\n  content: \"\\f3fa\";\n}\n.bi-grid-fill::before {\n  content: \"\\f3fb\";\n}\n.bi-grid::before {\n  content: \"\\f3fc\";\n}\n.bi-grip-horizontal::before {\n  content: \"\\f3fd\";\n}\n.bi-grip-vertical::before {\n  content: \"\\f3fe\";\n}\n.bi-hammer::before {\n  content: \"\\f3ff\";\n}\n.bi-hand-index-fill::before {\n  content: \"\\f400\";\n}\n.bi-hand-index-thumb-fill::before {\n  content: \"\\f401\";\n}\n.bi-hand-index-thumb::before {\n  content: \"\\f402\";\n}\n.bi-hand-index::before {\n  content: \"\\f403\";\n}\n.bi-hand-thumbs-down-fill::before {\n  content: \"\\f404\";\n}\n.bi-hand-thumbs-down::before {\n  content: \"\\f405\";\n}\n.bi-hand-thumbs-up-fill::before {\n  content: \"\\f406\";\n}\n.bi-hand-thumbs-up::before {\n  content: \"\\f407\";\n}\n.bi-handbag-fill::before {\n  content: \"\\f408\";\n}\n.bi-handbag::before {\n  content: \"\\f409\";\n}\n.bi-hash::before {\n  content: \"\\f40a\";\n}\n.bi-hdd-fill::before {\n  content: \"\\f40b\";\n}\n.bi-hdd-network-fill::before {\n  content: \"\\f40c\";\n}\n.bi-hdd-network::before {\n  content: \"\\f40d\";\n}\n.bi-hdd-rack-fill::before {\n  content: \"\\f40e\";\n}\n.bi-hdd-rack::before {\n  content: \"\\f40f\";\n}\n.bi-hdd-stack-fill::before {\n  content: \"\\f410\";\n}\n.bi-hdd-stack::before {\n  content: \"\\f411\";\n}\n.bi-hdd::before {\n  content: \"\\f412\";\n}\n.bi-headphones::before {\n  content: \"\\f413\";\n}\n.bi-headset::before {\n  content: \"\\f414\";\n}\n.bi-heart-fill::before {\n  content: \"\\f415\";\n}\n.bi-heart-half::before {\n  content: \"\\f416\";\n}\n.bi-heart::before {\n  content: \"\\f417\";\n}\n.bi-heptagon-fill::before {\n  content: \"\\f418\";\n}\n.bi-heptagon-half::before {\n  content: \"\\f419\";\n}\n.bi-heptagon::before {\n  content: \"\\f41a\";\n}\n.bi-hexagon-fill::before {\n  content: \"\\f41b\";\n}\n.bi-hexagon-half::before {\n  content: \"\\f41c\";\n}\n.bi-hexagon::before {\n  content: \"\\f41d\";\n}\n.bi-hourglass-bottom::before {\n  content: \"\\f41e\";\n}\n.bi-hourglass-split::before {\n  content: \"\\f41f\";\n}\n.bi-hourglass-top::before {\n  content: \"\\f420\";\n}\n.bi-hourglass::before {\n  content: \"\\f421\";\n}\n.bi-house-door-fill::before {\n  content: \"\\f422\";\n}\n.bi-house-door::before {\n  content: \"\\f423\";\n}\n.bi-house-fill::before {\n  content: \"\\f424\";\n}\n.bi-house::before {\n  content: \"\\f425\";\n}\n.bi-hr::before {\n  content: \"\\f426\";\n}\n.bi-hurricane::before {\n  content: \"\\f427\";\n}\n.bi-image-alt::before {\n  content: \"\\f428\";\n}\n.bi-image-fill::before {\n  content: \"\\f429\";\n}\n.bi-image::before {\n  content: \"\\f42a\";\n}\n.bi-images::before {\n  content: \"\\f42b\";\n}\n.bi-inbox-fill::before {\n  content: \"\\f42c\";\n}\n.bi-inbox::before {\n  content: \"\\f42d\";\n}\n.bi-inboxes-fill::before {\n  content: \"\\f42e\";\n}\n.bi-inboxes::before {\n  content: \"\\f42f\";\n}\n.bi-info-circle-fill::before {\n  content: \"\\f430\";\n}\n.bi-info-circle::before {\n  content: \"\\f431\";\n}\n.bi-info-square-fill::before {\n  content: \"\\f432\";\n}\n.bi-info-square::before {\n  content: \"\\f433\";\n}\n.bi-info::before {\n  content: \"\\f434\";\n}\n.bi-input-cursor-text::before {\n  content: \"\\f435\";\n}\n.bi-input-cursor::before {\n  content: \"\\f436\";\n}\n.bi-instagram::before {\n  content: \"\\f437\";\n}\n.bi-intersect::before {\n  content: \"\\f438\";\n}\n.bi-journal-album::before {\n  content: \"\\f439\";\n}\n.bi-journal-arrow-down::before {\n  content: \"\\f43a\";\n}\n.bi-journal-arrow-up::before {\n  content: \"\\f43b\";\n}\n.bi-journal-bookmark-fill::before {\n  content: \"\\f43c\";\n}\n.bi-journal-bookmark::before {\n  content: \"\\f43d\";\n}\n.bi-journal-check::before {\n  content: \"\\f43e\";\n}\n.bi-journal-code::before {\n  content: \"\\f43f\";\n}\n.bi-journal-medical::before {\n  content: \"\\f440\";\n}\n.bi-journal-minus::before {\n  content: \"\\f441\";\n}\n.bi-journal-plus::before {\n  content: \"\\f442\";\n}\n.bi-journal-richtext::before {\n  content: \"\\f443\";\n}\n.bi-journal-text::before {\n  content: \"\\f444\";\n}\n.bi-journal-x::before {\n  content: \"\\f445\";\n}\n.bi-journal::before {\n  content: \"\\f446\";\n}\n.bi-journals::before {\n  content: \"\\f447\";\n}\n.bi-joystick::before {\n  content: \"\\f448\";\n}\n.bi-justify-left::before {\n  content: \"\\f449\";\n}\n.bi-justify-right::before {\n  content: \"\\f44a\";\n}\n.bi-justify::before {\n  content: \"\\f44b\";\n}\n.bi-kanban-fill::before {\n  content: \"\\f44c\";\n}\n.bi-kanban::before {\n  content: \"\\f44d\";\n}\n.bi-key-fill::before {\n  content: \"\\f44e\";\n}\n.bi-key::before {\n  content: \"\\f44f\";\n}\n.bi-keyboard-fill::before {\n  content: \"\\f450\";\n}\n.bi-keyboard::before {\n  content: \"\\f451\";\n}\n.bi-ladder::before {\n  content: \"\\f452\";\n}\n.bi-lamp-fill::before {\n  content: \"\\f453\";\n}\n.bi-lamp::before {\n  content: \"\\f454\";\n}\n.bi-laptop-fill::before {\n  content: \"\\f455\";\n}\n.bi-laptop::before {\n  content: \"\\f456\";\n}\n.bi-layer-backward::before {\n  content: \"\\f457\";\n}\n.bi-layer-forward::before {\n  content: \"\\f458\";\n}\n.bi-layers-fill::before {\n  content: \"\\f459\";\n}\n.bi-layers-half::before {\n  content: \"\\f45a\";\n}\n.bi-layers::before {\n  content: \"\\f45b\";\n}\n.bi-layout-sidebar-inset-reverse::before {\n  content: \"\\f45c\";\n}\n.bi-layout-sidebar-inset::before {\n  content: \"\\f45d\";\n}\n.bi-layout-sidebar-reverse::before {\n  content: \"\\f45e\";\n}\n.bi-layout-sidebar::before {\n  content: \"\\f45f\";\n}\n.bi-layout-split::before {\n  content: \"\\f460\";\n}\n.bi-layout-text-sidebar-reverse::before {\n  content: \"\\f461\";\n}\n.bi-layout-text-sidebar::before {\n  content: \"\\f462\";\n}\n.bi-layout-text-window-reverse::before {\n  content: \"\\f463\";\n}\n.bi-layout-text-window::before {\n  content: \"\\f464\";\n}\n.bi-layout-three-columns::before {\n  content: \"\\f465\";\n}\n.bi-layout-wtf::before {\n  content: \"\\f466\";\n}\n.bi-life-preserver::before {\n  content: \"\\f467\";\n}\n.bi-lightbulb-fill::before {\n  content: \"\\f468\";\n}\n.bi-lightbulb-off-fill::before {\n  content: \"\\f469\";\n}\n.bi-lightbulb-off::before {\n  content: \"\\f46a\";\n}\n.bi-lightbulb::before {\n  content: \"\\f46b\";\n}\n.bi-lightning-charge-fill::before {\n  content: \"\\f46c\";\n}\n.bi-lightning-charge::before {\n  content: \"\\f46d\";\n}\n.bi-lightning-fill::before {\n  content: \"\\f46e\";\n}\n.bi-lightning::before {\n  content: \"\\f46f\";\n}\n.bi-link-45deg::before {\n  content: \"\\f470\";\n}\n.bi-link::before {\n  content: \"\\f471\";\n}\n.bi-linkedin::before {\n  content: \"\\f472\";\n}\n.bi-list-check::before {\n  content: \"\\f473\";\n}\n.bi-list-nested::before {\n  content: \"\\f474\";\n}\n.bi-list-ol::before {\n  content: \"\\f475\";\n}\n.bi-list-stars::before {\n  content: \"\\f476\";\n}\n.bi-list-task::before {\n  content: \"\\f477\";\n}\n.bi-list-ul::before {\n  content: \"\\f478\";\n}\n.bi-list::before {\n  content: \"\\f479\";\n}\n.bi-lock-fill::before {\n  content: \"\\f47a\";\n}\n.bi-lock::before {\n  content: \"\\f47b\";\n}\n.bi-mailbox::before {\n  content: \"\\f47c\";\n}\n.bi-mailbox2::before {\n  content: \"\\f47d\";\n}\n.bi-map-fill::before {\n  content: \"\\f47e\";\n}\n.bi-map::before {\n  content: \"\\f47f\";\n}\n.bi-markdown-fill::before {\n  content: \"\\f480\";\n}\n.bi-markdown::before {\n  content: \"\\f481\";\n}\n.bi-mask::before {\n  content: \"\\f482\";\n}\n.bi-megaphone-fill::before {\n  content: \"\\f483\";\n}\n.bi-megaphone::before {\n  content: \"\\f484\";\n}\n.bi-menu-app-fill::before {\n  content: \"\\f485\";\n}\n.bi-menu-app::before {\n  content: \"\\f486\";\n}\n.bi-menu-button-fill::before {\n  content: \"\\f487\";\n}\n.bi-menu-button-wide-fill::before {\n  content: \"\\f488\";\n}\n.bi-menu-button-wide::before {\n  content: \"\\f489\";\n}\n.bi-menu-button::before {\n  content: \"\\f48a\";\n}\n.bi-menu-down::before {\n  content: \"\\f48b\";\n}\n.bi-menu-up::before {\n  content: \"\\f48c\";\n}\n.bi-mic-fill::before {\n  content: \"\\f48d\";\n}\n.bi-mic-mute-fill::before {\n  content: \"\\f48e\";\n}\n.bi-mic-mute::before {\n  content: \"\\f48f\";\n}\n.bi-mic::before {\n  content: \"\\f490\";\n}\n.bi-minecart-loaded::before {\n  content: \"\\f491\";\n}\n.bi-minecart::before {\n  content: \"\\f492\";\n}\n.bi-moisture::before {\n  content: \"\\f493\";\n}\n.bi-moon-fill::before {\n  content: \"\\f494\";\n}\n.bi-moon-stars-fill::before {\n  content: \"\\f495\";\n}\n.bi-moon-stars::before {\n  content: \"\\f496\";\n}\n.bi-moon::before {\n  content: \"\\f497\";\n}\n.bi-mouse-fill::before {\n  content: \"\\f498\";\n}\n.bi-mouse::before {\n  content: \"\\f499\";\n}\n.bi-mouse2-fill::before {\n  content: \"\\f49a\";\n}\n.bi-mouse2::before {\n  content: \"\\f49b\";\n}\n.bi-mouse3-fill::before {\n  content: \"\\f49c\";\n}\n.bi-mouse3::before {\n  content: \"\\f49d\";\n}\n.bi-music-note-beamed::before {\n  content: \"\\f49e\";\n}\n.bi-music-note-list::before {\n  content: \"\\f49f\";\n}\n.bi-music-note::before {\n  content: \"\\f4a0\";\n}\n.bi-music-player-fill::before {\n  content: \"\\f4a1\";\n}\n.bi-music-player::before {\n  content: \"\\f4a2\";\n}\n.bi-newspaper::before {\n  content: \"\\f4a3\";\n}\n.bi-node-minus-fill::before {\n  content: \"\\f4a4\";\n}\n.bi-node-minus::before {\n  content: \"\\f4a5\";\n}\n.bi-node-plus-fill::before {\n  content: \"\\f4a6\";\n}\n.bi-node-plus::before {\n  content: \"\\f4a7\";\n}\n.bi-nut-fill::before {\n  content: \"\\f4a8\";\n}\n.bi-nut::before {\n  content: \"\\f4a9\";\n}\n.bi-octagon-fill::before {\n  content: \"\\f4aa\";\n}\n.bi-octagon-half::before {\n  content: \"\\f4ab\";\n}\n.bi-octagon::before {\n  content: \"\\f4ac\";\n}\n.bi-option::before {\n  content: \"\\f4ad\";\n}\n.bi-outlet::before {\n  content: \"\\f4ae\";\n}\n.bi-paint-bucket::before {\n  content: \"\\f4af\";\n}\n.bi-palette-fill::before {\n  content: \"\\f4b0\";\n}\n.bi-palette::before {\n  content: \"\\f4b1\";\n}\n.bi-palette2::before {\n  content: \"\\f4b2\";\n}\n.bi-paperclip::before {\n  content: \"\\f4b3\";\n}\n.bi-paragraph::before {\n  content: \"\\f4b4\";\n}\n.bi-patch-check-fill::before {\n  content: \"\\f4b5\";\n}\n.bi-patch-check::before {\n  content: \"\\f4b6\";\n}\n.bi-patch-exclamation-fill::before {\n  content: \"\\f4b7\";\n}\n.bi-patch-exclamation::before {\n  content: \"\\f4b8\";\n}\n.bi-patch-minus-fill::before {\n  content: \"\\f4b9\";\n}\n.bi-patch-minus::before {\n  content: \"\\f4ba\";\n}\n.bi-patch-plus-fill::before {\n  content: \"\\f4bb\";\n}\n.bi-patch-plus::before {\n  content: \"\\f4bc\";\n}\n.bi-patch-question-fill::before {\n  content: \"\\f4bd\";\n}\n.bi-patch-question::before {\n  content: \"\\f4be\";\n}\n.bi-pause-btn-fill::before {\n  content: \"\\f4bf\";\n}\n.bi-pause-btn::before {\n  content: \"\\f4c0\";\n}\n.bi-pause-circle-fill::before {\n  content: \"\\f4c1\";\n}\n.bi-pause-circle::before {\n  content: \"\\f4c2\";\n}\n.bi-pause-fill::before {\n  content: \"\\f4c3\";\n}\n.bi-pause::before {\n  content: \"\\f4c4\";\n}\n.bi-peace-fill::before {\n  content: \"\\f4c5\";\n}\n.bi-peace::before {\n  content: \"\\f4c6\";\n}\n.bi-pen-fill::before {\n  content: \"\\f4c7\";\n}\n.bi-pen::before {\n  content: \"\\f4c8\";\n}\n.bi-pencil-fill::before {\n  content: \"\\f4c9\";\n}\n.bi-pencil-square::before {\n  content: \"\\f4ca\";\n}\n.bi-pencil::before {\n  content: \"\\f4cb\";\n}\n.bi-pentagon-fill::before {\n  content: \"\\f4cc\";\n}\n.bi-pentagon-half::before {\n  content: \"\\f4cd\";\n}\n.bi-pentagon::before {\n  content: \"\\f4ce\";\n}\n.bi-people-fill::before {\n  content: \"\\f4cf\";\n}\n.bi-people::before {\n  content: \"\\f4d0\";\n}\n.bi-percent::before {\n  content: \"\\f4d1\";\n}\n.bi-person-badge-fill::before {\n  content: \"\\f4d2\";\n}\n.bi-person-badge::before {\n  content: \"\\f4d3\";\n}\n.bi-person-bounding-box::before {\n  content: \"\\f4d4\";\n}\n.bi-person-check-fill::before {\n  content: \"\\f4d5\";\n}\n.bi-person-check::before {\n  content: \"\\f4d6\";\n}\n.bi-person-circle::before {\n  content: \"\\f4d7\";\n}\n.bi-person-dash-fill::before {\n  content: \"\\f4d8\";\n}\n.bi-person-dash::before {\n  content: \"\\f4d9\";\n}\n.bi-person-fill::before {\n  content: \"\\f4da\";\n}\n.bi-person-lines-fill::before {\n  content: \"\\f4db\";\n}\n.bi-person-plus-fill::before {\n  content: \"\\f4dc\";\n}\n.bi-person-plus::before {\n  content: \"\\f4dd\";\n}\n.bi-person-square::before {\n  content: \"\\f4de\";\n}\n.bi-person-x-fill::before {\n  content: \"\\f4df\";\n}\n.bi-person-x::before {\n  content: \"\\f4e0\";\n}\n.bi-person::before {\n  content: \"\\f4e1\";\n}\n.bi-phone-fill::before {\n  content: \"\\f4e2\";\n}\n.bi-phone-landscape-fill::before {\n  content: \"\\f4e3\";\n}\n.bi-phone-landscape::before {\n  content: \"\\f4e4\";\n}\n.bi-phone-vibrate-fill::before {\n  content: \"\\f4e5\";\n}\n.bi-phone-vibrate::before {\n  content: \"\\f4e6\";\n}\n.bi-phone::before {\n  content: \"\\f4e7\";\n}\n.bi-pie-chart-fill::before {\n  content: \"\\f4e8\";\n}\n.bi-pie-chart::before {\n  content: \"\\f4e9\";\n}\n.bi-pin-angle-fill::before {\n  content: \"\\f4ea\";\n}\n.bi-pin-angle::before {\n  content: \"\\f4eb\";\n}\n.bi-pin-fill::before {\n  content: \"\\f4ec\";\n}\n.bi-pin::before {\n  content: \"\\f4ed\";\n}\n.bi-pip-fill::before {\n  content: \"\\f4ee\";\n}\n.bi-pip::before {\n  content: \"\\f4ef\";\n}\n.bi-play-btn-fill::before {\n  content: \"\\f4f0\";\n}\n.bi-play-btn::before {\n  content: \"\\f4f1\";\n}\n.bi-play-circle-fill::before {\n  content: \"\\f4f2\";\n}\n.bi-play-circle::before {\n  content: \"\\f4f3\";\n}\n.bi-play-fill::before {\n  content: \"\\f4f4\";\n}\n.bi-play::before {\n  content: \"\\f4f5\";\n}\n.bi-plug-fill::before {\n  content: \"\\f4f6\";\n}\n.bi-plug::before {\n  content: \"\\f4f7\";\n}\n.bi-plus-circle-dotted::before {\n  content: \"\\f4f8\";\n}\n.bi-plus-circle-fill::before {\n  content: \"\\f4f9\";\n}\n.bi-plus-circle::before {\n  content: \"\\f4fa\";\n}\n.bi-plus-square-dotted::before {\n  content: \"\\f4fb\";\n}\n.bi-plus-square-fill::before {\n  content: \"\\f4fc\";\n}\n.bi-plus-square::before {\n  content: \"\\f4fd\";\n}\n.bi-plus::before {\n  content: \"\\f4fe\";\n}\n.bi-power::before {\n  content: \"\\f4ff\";\n}\n.bi-printer-fill::before {\n  content: \"\\f500\";\n}\n.bi-printer::before {\n  content: \"\\f501\";\n}\n.bi-puzzle-fill::before {\n  content: \"\\f502\";\n}\n.bi-puzzle::before {\n  content: \"\\f503\";\n}\n.bi-question-circle-fill::before {\n  content: \"\\f504\";\n}\n.bi-question-circle::before {\n  content: \"\\f505\";\n}\n.bi-question-diamond-fill::before {\n  content: \"\\f506\";\n}\n.bi-question-diamond::before {\n  content: \"\\f507\";\n}\n.bi-question-octagon-fill::before {\n  content: \"\\f508\";\n}\n.bi-question-octagon::before {\n  content: \"\\f509\";\n}\n.bi-question-square-fill::before {\n  content: \"\\f50a\";\n}\n.bi-question-square::before {\n  content: \"\\f50b\";\n}\n.bi-question::before {\n  content: \"\\f50c\";\n}\n.bi-rainbow::before {\n  content: \"\\f50d\";\n}\n.bi-receipt-cutoff::before {\n  content: \"\\f50e\";\n}\n.bi-receipt::before {\n  content: \"\\f50f\";\n}\n.bi-reception-0::before {\n  content: \"\\f510\";\n}\n.bi-reception-1::before {\n  content: \"\\f511\";\n}\n.bi-reception-2::before {\n  content: \"\\f512\";\n}\n.bi-reception-3::before {\n  content: \"\\f513\";\n}\n.bi-reception-4::before {\n  content: \"\\f514\";\n}\n.bi-record-btn-fill::before {\n  content: \"\\f515\";\n}\n.bi-record-btn::before {\n  content: \"\\f516\";\n}\n.bi-record-circle-fill::before {\n  content: \"\\f517\";\n}\n.bi-record-circle::before {\n  content: \"\\f518\";\n}\n.bi-record-fill::before {\n  content: \"\\f519\";\n}\n.bi-record::before {\n  content: \"\\f51a\";\n}\n.bi-record2-fill::before {\n  content: \"\\f51b\";\n}\n.bi-record2::before {\n  content: \"\\f51c\";\n}\n.bi-reply-all-fill::before {\n  content: \"\\f51d\";\n}\n.bi-reply-all::before {\n  content: \"\\f51e\";\n}\n.bi-reply-fill::before {\n  content: \"\\f51f\";\n}\n.bi-reply::before {\n  content: \"\\f520\";\n}\n.bi-rss-fill::before {\n  content: \"\\f521\";\n}\n.bi-rss::before {\n  content: \"\\f522\";\n}\n.bi-rulers::before {\n  content: \"\\f523\";\n}\n.bi-save-fill::before {\n  content: \"\\f524\";\n}\n.bi-save::before {\n  content: \"\\f525\";\n}\n.bi-save2-fill::before {\n  content: \"\\f526\";\n}\n.bi-save2::before {\n  content: \"\\f527\";\n}\n.bi-scissors::before {\n  content: \"\\f528\";\n}\n.bi-screwdriver::before {\n  content: \"\\f529\";\n}\n.bi-search::before {\n  content: \"\\f52a\";\n}\n.bi-segmented-nav::before {\n  content: \"\\f52b\";\n}\n.bi-server::before {\n  content: \"\\f52c\";\n}\n.bi-share-fill::before {\n  content: \"\\f52d\";\n}\n.bi-share::before {\n  content: \"\\f52e\";\n}\n.bi-shield-check::before {\n  content: \"\\f52f\";\n}\n.bi-shield-exclamation::before {\n  content: \"\\f530\";\n}\n.bi-shield-fill-check::before {\n  content: \"\\f531\";\n}\n.bi-shield-fill-exclamation::before {\n  content: \"\\f532\";\n}\n.bi-shield-fill-minus::before {\n  content: \"\\f533\";\n}\n.bi-shield-fill-plus::before {\n  content: \"\\f534\";\n}\n.bi-shield-fill-x::before {\n  content: \"\\f535\";\n}\n.bi-shield-fill::before {\n  content: \"\\f536\";\n}\n.bi-shield-lock-fill::before {\n  content: \"\\f537\";\n}\n.bi-shield-lock::before {\n  content: \"\\f538\";\n}\n.bi-shield-minus::before {\n  content: \"\\f539\";\n}\n.bi-shield-plus::before {\n  content: \"\\f53a\";\n}\n.bi-shield-shaded::before {\n  content: \"\\f53b\";\n}\n.bi-shield-slash-fill::before {\n  content: \"\\f53c\";\n}\n.bi-shield-slash::before {\n  content: \"\\f53d\";\n}\n.bi-shield-x::before {\n  content: \"\\f53e\";\n}\n.bi-shield::before {\n  content: \"\\f53f\";\n}\n.bi-shift-fill::before {\n  content: \"\\f540\";\n}\n.bi-shift::before {\n  content: \"\\f541\";\n}\n.bi-shop-window::before {\n  content: \"\\f542\";\n}\n.bi-shop::before {\n  content: \"\\f543\";\n}\n.bi-shuffle::before {\n  content: \"\\f544\";\n}\n.bi-signpost-2-fill::before {\n  content: \"\\f545\";\n}\n.bi-signpost-2::before {\n  content: \"\\f546\";\n}\n.bi-signpost-fill::before {\n  content: \"\\f547\";\n}\n.bi-signpost-split-fill::before {\n  content: \"\\f548\";\n}\n.bi-signpost-split::before {\n  content: \"\\f549\";\n}\n.bi-signpost::before {\n  content: \"\\f54a\";\n}\n.bi-sim-fill::before {\n  content: \"\\f54b\";\n}\n.bi-sim::before {\n  content: \"\\f54c\";\n}\n.bi-skip-backward-btn-fill::before {\n  content: \"\\f54d\";\n}\n.bi-skip-backward-btn::before {\n  content: \"\\f54e\";\n}\n.bi-skip-backward-circle-fill::before {\n  content: \"\\f54f\";\n}\n.bi-skip-backward-circle::before {\n  content: \"\\f550\";\n}\n.bi-skip-backward-fill::before {\n  content: \"\\f551\";\n}\n.bi-skip-backward::before {\n  content: \"\\f552\";\n}\n.bi-skip-end-btn-fill::before {\n  content: \"\\f553\";\n}\n.bi-skip-end-btn::before {\n  content: \"\\f554\";\n}\n.bi-skip-end-circle-fill::before {\n  content: \"\\f555\";\n}\n.bi-skip-end-circle::before {\n  content: \"\\f556\";\n}\n.bi-skip-end-fill::before {\n  content: \"\\f557\";\n}\n.bi-skip-end::before {\n  content: \"\\f558\";\n}\n.bi-skip-forward-btn-fill::before {\n  content: \"\\f559\";\n}\n.bi-skip-forward-btn::before {\n  content: \"\\f55a\";\n}\n.bi-skip-forward-circle-fill::before {\n  content: \"\\f55b\";\n}\n.bi-skip-forward-circle::before {\n  content: \"\\f55c\";\n}\n.bi-skip-forward-fill::before {\n  content: \"\\f55d\";\n}\n.bi-skip-forward::before {\n  content: \"\\f55e\";\n}\n.bi-skip-start-btn-fill::before {\n  content: \"\\f55f\";\n}\n.bi-skip-start-btn::before {\n  content: \"\\f560\";\n}\n.bi-skip-start-circle-fill::before {\n  content: \"\\f561\";\n}\n.bi-skip-start-circle::before {\n  content: \"\\f562\";\n}\n.bi-skip-start-fill::before {\n  content: \"\\f563\";\n}\n.bi-skip-start::before {\n  content: \"\\f564\";\n}\n.bi-slack::before {\n  content: \"\\f565\";\n}\n.bi-slash-circle-fill::before {\n  content: \"\\f566\";\n}\n.bi-slash-circle::before {\n  content: \"\\f567\";\n}\n.bi-slash-square-fill::before {\n  content: \"\\f568\";\n}\n.bi-slash-square::before {\n  content: \"\\f569\";\n}\n.bi-slash::before {\n  content: \"\\f56a\";\n}\n.bi-sliders::before {\n  content: \"\\f56b\";\n}\n.bi-smartwatch::before {\n  content: \"\\f56c\";\n}\n.bi-snow::before {\n  content: \"\\f56d\";\n}\n.bi-snow2::before {\n  content: \"\\f56e\";\n}\n.bi-snow3::before {\n  content: \"\\f56f\";\n}\n.bi-sort-alpha-down-alt::before {\n  content: \"\\f570\";\n}\n.bi-sort-alpha-down::before {\n  content: \"\\f571\";\n}\n.bi-sort-alpha-up-alt::before {\n  content: \"\\f572\";\n}\n.bi-sort-alpha-up::before {\n  content: \"\\f573\";\n}\n.bi-sort-down-alt::before {\n  content: \"\\f574\";\n}\n.bi-sort-down::before {\n  content: \"\\f575\";\n}\n.bi-sort-numeric-down-alt::before {\n  content: \"\\f576\";\n}\n.bi-sort-numeric-down::before {\n  content: \"\\f577\";\n}\n.bi-sort-numeric-up-alt::before {\n  content: \"\\f578\";\n}\n.bi-sort-numeric-up::before {\n  content: \"\\f579\";\n}\n.bi-sort-up-alt::before {\n  content: \"\\f57a\";\n}\n.bi-sort-up::before {\n  content: \"\\f57b\";\n}\n.bi-soundwave::before {\n  content: \"\\f57c\";\n}\n.bi-speaker-fill::before {\n  content: \"\\f57d\";\n}\n.bi-speaker::before {\n  content: \"\\f57e\";\n}\n.bi-speedometer::before {\n  content: \"\\f57f\";\n}\n.bi-speedometer2::before {\n  content: \"\\f580\";\n}\n.bi-spellcheck::before {\n  content: \"\\f581\";\n}\n.bi-square-fill::before {\n  content: \"\\f582\";\n}\n.bi-square-half::before {\n  content: \"\\f583\";\n}\n.bi-square::before {\n  content: \"\\f584\";\n}\n.bi-stack::before {\n  content: \"\\f585\";\n}\n.bi-star-fill::before {\n  content: \"\\f586\";\n}\n.bi-star-half::before {\n  content: \"\\f587\";\n}\n.bi-star::before {\n  content: \"\\f588\";\n}\n.bi-stars::before {\n  content: \"\\f589\";\n}\n.bi-stickies-fill::before {\n  content: \"\\f58a\";\n}\n.bi-stickies::before {\n  content: \"\\f58b\";\n}\n.bi-sticky-fill::before {\n  content: \"\\f58c\";\n}\n.bi-sticky::before {\n  content: \"\\f58d\";\n}\n.bi-stop-btn-fill::before {\n  content: \"\\f58e\";\n}\n.bi-stop-btn::before {\n  content: \"\\f58f\";\n}\n.bi-stop-circle-fill::before {\n  content: \"\\f590\";\n}\n.bi-stop-circle::before {\n  content: \"\\f591\";\n}\n.bi-stop-fill::before {\n  content: \"\\f592\";\n}\n.bi-stop::before {\n  content: \"\\f593\";\n}\n.bi-stoplights-fill::before {\n  content: \"\\f594\";\n}\n.bi-stoplights::before {\n  content: \"\\f595\";\n}\n.bi-stopwatch-fill::before {\n  content: \"\\f596\";\n}\n.bi-stopwatch::before {\n  content: \"\\f597\";\n}\n.bi-subtract::before {\n  content: \"\\f598\";\n}\n.bi-suit-club-fill::before {\n  content: \"\\f599\";\n}\n.bi-suit-club::before {\n  content: \"\\f59a\";\n}\n.bi-suit-diamond-fill::before {\n  content: \"\\f59b\";\n}\n.bi-suit-diamond::before {\n  content: \"\\f59c\";\n}\n.bi-suit-heart-fill::before {\n  content: \"\\f59d\";\n}\n.bi-suit-heart::before {\n  content: \"\\f59e\";\n}\n.bi-suit-spade-fill::before {\n  content: \"\\f59f\";\n}\n.bi-suit-spade::before {\n  content: \"\\f5a0\";\n}\n.bi-sun-fill::before {\n  content: \"\\f5a1\";\n}\n.bi-sun::before {\n  content: \"\\f5a2\";\n}\n.bi-sunglasses::before {\n  content: \"\\f5a3\";\n}\n.bi-sunrise-fill::before {\n  content: \"\\f5a4\";\n}\n.bi-sunrise::before {\n  content: \"\\f5a5\";\n}\n.bi-sunset-fill::before {\n  content: \"\\f5a6\";\n}\n.bi-sunset::before {\n  content: \"\\f5a7\";\n}\n.bi-symmetry-horizontal::before {\n  content: \"\\f5a8\";\n}\n.bi-symmetry-vertical::before {\n  content: \"\\f5a9\";\n}\n.bi-table::before {\n  content: \"\\f5aa\";\n}\n.bi-tablet-fill::before {\n  content: \"\\f5ab\";\n}\n.bi-tablet-landscape-fill::before {\n  content: \"\\f5ac\";\n}\n.bi-tablet-landscape::before {\n  content: \"\\f5ad\";\n}\n.bi-tablet::before {\n  content: \"\\f5ae\";\n}\n.bi-tag-fill::before {\n  content: \"\\f5af\";\n}\n.bi-tag::before {\n  content: \"\\f5b0\";\n}\n.bi-tags-fill::before {\n  content: \"\\f5b1\";\n}\n.bi-tags::before {\n  content: \"\\f5b2\";\n}\n.bi-telegram::before {\n  content: \"\\f5b3\";\n}\n.bi-telephone-fill::before {\n  content: \"\\f5b4\";\n}\n.bi-telephone-forward-fill::before {\n  content: \"\\f5b5\";\n}\n.bi-telephone-forward::before {\n  content: \"\\f5b6\";\n}\n.bi-telephone-inbound-fill::before {\n  content: \"\\f5b7\";\n}\n.bi-telephone-inbound::before {\n  content: \"\\f5b8\";\n}\n.bi-telephone-minus-fill::before {\n  content: \"\\f5b9\";\n}\n.bi-telephone-minus::before {\n  content: \"\\f5ba\";\n}\n.bi-telephone-outbound-fill::before {\n  content: \"\\f5bb\";\n}\n.bi-telephone-outbound::before {\n  content: \"\\f5bc\";\n}\n.bi-telephone-plus-fill::before {\n  content: \"\\f5bd\";\n}\n.bi-telephone-plus::before {\n  content: \"\\f5be\";\n}\n.bi-telephone-x-fill::before {\n  content: \"\\f5bf\";\n}\n.bi-telephone-x::before {\n  content: \"\\f5c0\";\n}\n.bi-telephone::before {\n  content: \"\\f5c1\";\n}\n.bi-terminal-fill::before {\n  content: \"\\f5c2\";\n}\n.bi-terminal::before {\n  content: \"\\f5c3\";\n}\n.bi-text-center::before {\n  content: \"\\f5c4\";\n}\n.bi-text-indent-left::before {\n  content: \"\\f5c5\";\n}\n.bi-text-indent-right::before {\n  content: \"\\f5c6\";\n}\n.bi-text-left::before {\n  content: \"\\f5c7\";\n}\n.bi-text-paragraph::before {\n  content: \"\\f5c8\";\n}\n.bi-text-right::before {\n  content: \"\\f5c9\";\n}\n.bi-textarea-resize::before {\n  content: \"\\f5ca\";\n}\n.bi-textarea-t::before {\n  content: \"\\f5cb\";\n}\n.bi-textarea::before {\n  content: \"\\f5cc\";\n}\n.bi-thermometer-half::before {\n  content: \"\\f5cd\";\n}\n.bi-thermometer-high::before {\n  content: \"\\f5ce\";\n}\n.bi-thermometer-low::before {\n  content: \"\\f5cf\";\n}\n.bi-thermometer-snow::before {\n  content: \"\\f5d0\";\n}\n.bi-thermometer-sun::before {\n  content: \"\\f5d1\";\n}\n.bi-thermometer::before {\n  content: \"\\f5d2\";\n}\n.bi-three-dots-vertical::before {\n  content: \"\\f5d3\";\n}\n.bi-three-dots::before {\n  content: \"\\f5d4\";\n}\n.bi-toggle-off::before {\n  content: \"\\f5d5\";\n}\n.bi-toggle-on::before {\n  content: \"\\f5d6\";\n}\n.bi-toggle2-off::before {\n  content: \"\\f5d7\";\n}\n.bi-toggle2-on::before {\n  content: \"\\f5d8\";\n}\n.bi-toggles::before {\n  content: \"\\f5d9\";\n}\n.bi-toggles2::before {\n  content: \"\\f5da\";\n}\n.bi-tools::before {\n  content: \"\\f5db\";\n}\n.bi-tornado::before {\n  content: \"\\f5dc\";\n}\n.bi-trash-fill::before {\n  content: \"\\f5dd\";\n}\n.bi-trash::before {\n  content: \"\\f5de\";\n}\n.bi-trash2-fill::before {\n  content: \"\\f5df\";\n}\n.bi-trash2::before {\n  content: \"\\f5e0\";\n}\n.bi-tree-fill::before {\n  content: \"\\f5e1\";\n}\n.bi-tree::before {\n  content: \"\\f5e2\";\n}\n.bi-triangle-fill::before {\n  content: \"\\f5e3\";\n}\n.bi-triangle-half::before {\n  content: \"\\f5e4\";\n}\n.bi-triangle::before {\n  content: \"\\f5e5\";\n}\n.bi-trophy-fill::before {\n  content: \"\\f5e6\";\n}\n.bi-trophy::before {\n  content: \"\\f5e7\";\n}\n.bi-tropical-storm::before {\n  content: \"\\f5e8\";\n}\n.bi-truck-flatbed::before {\n  content: \"\\f5e9\";\n}\n.bi-truck::before {\n  content: \"\\f5ea\";\n}\n.bi-tsunami::before {\n  content: \"\\f5eb\";\n}\n.bi-tv-fill::before {\n  content: \"\\f5ec\";\n}\n.bi-tv::before {\n  content: \"\\f5ed\";\n}\n.bi-twitch::before {\n  content: \"\\f5ee\";\n}\n.bi-twitter::before {\n  content: \"\\f5ef\";\n}\n.bi-type-bold::before {\n  content: \"\\f5f0\";\n}\n.bi-type-h1::before {\n  content: \"\\f5f1\";\n}\n.bi-type-h2::before {\n  content: \"\\f5f2\";\n}\n.bi-type-h3::before {\n  content: \"\\f5f3\";\n}\n.bi-type-italic::before {\n  content: \"\\f5f4\";\n}\n.bi-type-strikethrough::before {\n  content: \"\\f5f5\";\n}\n.bi-type-underline::before {\n  content: \"\\f5f6\";\n}\n.bi-type::before {\n  content: \"\\f5f7\";\n}\n.bi-ui-checks-grid::before {\n  content: \"\\f5f8\";\n}\n.bi-ui-checks::before {\n  content: \"\\f5f9\";\n}\n.bi-ui-radios-grid::before {\n  content: \"\\f5fa\";\n}\n.bi-ui-radios::before {\n  content: \"\\f5fb\";\n}\n.bi-umbrella-fill::before {\n  content: \"\\f5fc\";\n}\n.bi-umbrella::before {\n  content: \"\\f5fd\";\n}\n.bi-union::before {\n  content: \"\\f5fe\";\n}\n.bi-unlock-fill::before {\n  content: \"\\f5ff\";\n}\n.bi-unlock::before {\n  content: \"\\f600\";\n}\n.bi-upc-scan::before {\n  content: \"\\f601\";\n}\n.bi-upc::before {\n  content: \"\\f602\";\n}\n.bi-upload::before {\n  content: \"\\f603\";\n}\n.bi-vector-pen::before {\n  content: \"\\f604\";\n}\n.bi-view-list::before {\n  content: \"\\f605\";\n}\n.bi-view-stacked::before {\n  content: \"\\f606\";\n}\n.bi-vinyl-fill::before {\n  content: \"\\f607\";\n}\n.bi-vinyl::before {\n  content: \"\\f608\";\n}\n.bi-voicemail::before {\n  content: \"\\f609\";\n}\n.bi-volume-down-fill::before {\n  content: \"\\f60a\";\n}\n.bi-volume-down::before {\n  content: \"\\f60b\";\n}\n.bi-volume-mute-fill::before {\n  content: \"\\f60c\";\n}\n.bi-volume-mute::before {\n  content: \"\\f60d\";\n}\n.bi-volume-off-fill::before {\n  content: \"\\f60e\";\n}\n.bi-volume-off::before {\n  content: \"\\f60f\";\n}\n.bi-volume-up-fill::before {\n  content: \"\\f610\";\n}\n.bi-volume-up::before {\n  content: \"\\f611\";\n}\n.bi-vr::before {\n  content: \"\\f612\";\n}\n.bi-wallet-fill::before {\n  content: \"\\f613\";\n}\n.bi-wallet::before {\n  content: \"\\f614\";\n}\n.bi-wallet2::before {\n  content: \"\\f615\";\n}\n.bi-watch::before {\n  content: \"\\f616\";\n}\n.bi-water::before {\n  content: \"\\f617\";\n}\n.bi-whatsapp::before {\n  content: \"\\f618\";\n}\n.bi-wifi-1::before {\n  content: \"\\f619\";\n}\n.bi-wifi-2::before {\n  content: \"\\f61a\";\n}\n.bi-wifi-off::before {\n  content: \"\\f61b\";\n}\n.bi-wifi::before {\n  content: \"\\f61c\";\n}\n.bi-wind::before {\n  content: \"\\f61d\";\n}\n.bi-window-dock::before {\n  content: \"\\f61e\";\n}\n.bi-window-sidebar::before {\n  content: \"\\f61f\";\n}\n.bi-window::before {\n  content: \"\\f620\";\n}\n.bi-wrench::before {\n  content: \"\\f621\";\n}\n.bi-x-circle-fill::before {\n  content: \"\\f622\";\n}\n.bi-x-circle::before {\n  content: \"\\f623\";\n}\n.bi-x-diamond-fill::before {\n  content: \"\\f624\";\n}\n.bi-x-diamond::before {\n  content: \"\\f625\";\n}\n.bi-x-octagon-fill::before {\n  content: \"\\f626\";\n}\n.bi-x-octagon::before {\n  content: \"\\f627\";\n}\n.bi-x-square-fill::before {\n  content: \"\\f628\";\n}\n.bi-x-square::before {\n  content: \"\\f629\";\n}\n.bi-x::before {\n  content: \"\\f62a\";\n}\n.bi-youtube::before {\n  content: \"\\f62b\";\n}\n.bi-zoom-in::before {\n  content: \"\\f62c\";\n}\n.bi-zoom-out::before {\n  content: \"\\f62d\";\n}\n.bi-bank::before {\n  content: \"\\f62e\";\n}\n.bi-bank2::before {\n  content: \"\\f62f\";\n}\n.bi-bell-slash-fill::before {\n  content: \"\\f630\";\n}\n.bi-bell-slash::before {\n  content: \"\\f631\";\n}\n.bi-cash-coin::before {\n  content: \"\\f632\";\n}\n.bi-check-lg::before {\n  content: \"\\f633\";\n}\n.bi-coin::before {\n  content: \"\\f634\";\n}\n.bi-currency-bitcoin::before {\n  content: \"\\f635\";\n}\n.bi-currency-dollar::before {\n  content: \"\\f636\";\n}\n.bi-currency-euro::before {\n  content: \"\\f637\";\n}\n.bi-currency-exchange::before {\n  content: \"\\f638\";\n}\n.bi-currency-pound::before {\n  content: \"\\f639\";\n}\n.bi-currency-yen::before {\n  content: \"\\f63a\";\n}\n.bi-dash-lg::before {\n  content: \"\\f63b\";\n}\n.bi-exclamation-lg::before {\n  content: \"\\f63c\";\n}\n.bi-file-earmark-pdf-fill::before {\n  content: \"\\f63d\";\n}\n.bi-file-earmark-pdf::before {\n  content: \"\\f63e\";\n}\n.bi-file-pdf-fill::before {\n  content: \"\\f63f\";\n}\n.bi-file-pdf::before {\n  content: \"\\f640\";\n}\n.bi-gender-ambiguous::before {\n  content: \"\\f641\";\n}\n.bi-gender-female::before {\n  content: \"\\f642\";\n}\n.bi-gender-male::before {\n  content: \"\\f643\";\n}\n.bi-gender-trans::before {\n  content: \"\\f644\";\n}\n.bi-headset-vr::before {\n  content: \"\\f645\";\n}\n.bi-info-lg::before {\n  content: \"\\f646\";\n}\n.bi-mastodon::before {\n  content: \"\\f647\";\n}\n.bi-messenger::before {\n  content: \"\\f648\";\n}\n.bi-piggy-bank-fill::before {\n  content: \"\\f649\";\n}\n.bi-piggy-bank::before {\n  content: \"\\f64a\";\n}\n.bi-pin-map-fill::before {\n  content: \"\\f64b\";\n}\n.bi-pin-map::before {\n  content: \"\\f64c\";\n}\n.bi-plus-lg::before {\n  content: \"\\f64d\";\n}\n.bi-question-lg::before {\n  content: \"\\f64e\";\n}\n.bi-recycle::before {\n  content: \"\\f64f\";\n}\n.bi-reddit::before {\n  content: \"\\f650\";\n}\n.bi-safe-fill::before {\n  content: \"\\f651\";\n}\n.bi-safe2-fill::before {\n  content: \"\\f652\";\n}\n.bi-safe2::before {\n  content: \"\\f653\";\n}\n.bi-sd-card-fill::before {\n  content: \"\\f654\";\n}\n.bi-sd-card::before {\n  content: \"\\f655\";\n}\n.bi-skype::before {\n  content: \"\\f656\";\n}\n.bi-slash-lg::before {\n  content: \"\\f657\";\n}\n.bi-translate::before {\n  content: \"\\f658\";\n}\n.bi-x-lg::before {\n  content: \"\\f659\";\n}\n.bi-safe::before {\n  content: \"\\f65a\";\n}\n.bi-apple::before {\n  content: \"\\f65b\";\n}\n.bi-microsoft::before {\n  content: \"\\f65d\";\n}\n.bi-windows::before {\n  content: \"\\f65e\";\n}\n.bi-behance::before {\n  content: \"\\f65c\";\n}\n.bi-dribbble::before {\n  content: \"\\f65f\";\n}\n.bi-line::before {\n  content: \"\\f660\";\n}\n.bi-medium::before {\n  content: \"\\f661\";\n}\n.bi-paypal::before {\n  content: \"\\f662\";\n}\n.bi-pinterest::before {\n  content: \"\\f663\";\n}\n.bi-signal::before {\n  content: \"\\f664\";\n}\n.bi-snapchat::before {\n  content: \"\\f665\";\n}\n.bi-spotify::before {\n  content: \"\\f666\";\n}\n.bi-stack-overflow::before {\n  content: \"\\f667\";\n}\n.bi-strava::before {\n  content: \"\\f668\";\n}\n.bi-wordpress::before {\n  content: \"\\f669\";\n}\n.bi-vimeo::before {\n  content: \"\\f66a\";\n}\n.bi-activity::before {\n  content: \"\\f66b\";\n}\n.bi-easel2-fill::before {\n  content: \"\\f66c\";\n}\n.bi-easel2::before {\n  content: \"\\f66d\";\n}\n.bi-easel3-fill::before {\n  content: \"\\f66e\";\n}\n.bi-easel3::before {\n  content: \"\\f66f\";\n}\n.bi-fan::before {\n  content: \"\\f670\";\n}\n.bi-fingerprint::before {\n  content: \"\\f671\";\n}\n.bi-graph-down-arrow::before {\n  content: \"\\f672\";\n}\n.bi-graph-up-arrow::before {\n  content: \"\\f673\";\n}\n.bi-hypnotize::before {\n  content: \"\\f674\";\n}\n.bi-magic::before {\n  content: \"\\f675\";\n}\n.bi-person-rolodex::before {\n  content: \"\\f676\";\n}\n.bi-person-video::before {\n  content: \"\\f677\";\n}\n.bi-person-video2::before {\n  content: \"\\f678\";\n}\n.bi-person-video3::before {\n  content: \"\\f679\";\n}\n.bi-person-workspace::before {\n  content: \"\\f67a\";\n}\n.bi-radioactive::before {\n  content: \"\\f67b\";\n}\n.bi-webcam-fill::before {\n  content: \"\\f67c\";\n}\n.bi-webcam::before {\n  content: \"\\f67d\";\n}\n.bi-yin-yang::before {\n  content: \"\\f67e\";\n}\n.bi-bandaid-fill::before {\n  content: \"\\f680\";\n}\n.bi-bandaid::before {\n  content: \"\\f681\";\n}\n.bi-bluetooth::before {\n  content: \"\\f682\";\n}\n.bi-body-text::before {\n  content: \"\\f683\";\n}\n.bi-boombox::before {\n  content: \"\\f684\";\n}\n.bi-boxes::before {\n  content: \"\\f685\";\n}\n.bi-dpad-fill::before {\n  content: \"\\f686\";\n}\n.bi-dpad::before {\n  content: \"\\f687\";\n}\n.bi-ear-fill::before {\n  content: \"\\f688\";\n}\n.bi-ear::before {\n  content: \"\\f689\";\n}\n.bi-envelope-check-1::before {\n  content: \"\\f68a\";\n}\n.bi-envelope-check-fill::before {\n  content: \"\\f68b\";\n}\n.bi-envelope-check::before {\n  content: \"\\f68c\";\n}\n.bi-envelope-dash-1::before {\n  content: \"\\f68d\";\n}\n.bi-envelope-dash-fill::before {\n  content: \"\\f68e\";\n}\n.bi-envelope-dash::before {\n  content: \"\\f68f\";\n}\n.bi-envelope-exclamation-1::before {\n  content: \"\\f690\";\n}\n.bi-envelope-exclamation-fill::before {\n  content: \"\\f691\";\n}\n.bi-envelope-exclamation::before {\n  content: \"\\f692\";\n}\n.bi-envelope-plus-fill::before {\n  content: \"\\f693\";\n}\n.bi-envelope-plus::before {\n  content: \"\\f694\";\n}\n.bi-envelope-slash-1::before {\n  content: \"\\f695\";\n}\n.bi-envelope-slash-fill::before {\n  content: \"\\f696\";\n}\n.bi-envelope-slash::before {\n  content: \"\\f697\";\n}\n.bi-envelope-x-1::before {\n  content: \"\\f698\";\n}\n.bi-envelope-x-fill::before {\n  content: \"\\f699\";\n}\n.bi-envelope-x::before {\n  content: \"\\f69a\";\n}\n.bi-explicit-fill::before {\n  content: \"\\f69b\";\n}\n.bi-explicit::before {\n  content: \"\\f69c\";\n}\n.bi-git::before {\n  content: \"\\f69d\";\n}\n.bi-infinity::before {\n  content: \"\\f69e\";\n}\n.bi-list-columns-reverse::before {\n  content: \"\\f69f\";\n}\n.bi-list-columns::before {\n  content: \"\\f6a0\";\n}\n.bi-meta::before {\n  content: \"\\f6a1\";\n}\n.bi-mortorboard-fill::before {\n  content: \"\\f6a2\";\n}\n.bi-mortorboard::before {\n  content: \"\\f6a3\";\n}\n.bi-nintendo-switch::before {\n  content: \"\\f6a4\";\n}\n.bi-pc-display-horizontal::before {\n  content: \"\\f6a5\";\n}\n.bi-pc-display::before {\n  content: \"\\f6a6\";\n}\n.bi-pc-horizontal::before {\n  content: \"\\f6a7\";\n}\n.bi-pc::before {\n  content: \"\\f6a8\";\n}\n.bi-playstation::before {\n  content: \"\\f6a9\";\n}\n.bi-plus-slash-minus::before {\n  content: \"\\f6aa\";\n}\n.bi-projector-fill::before {\n  content: \"\\f6ab\";\n}\n.bi-projector::before {\n  content: \"\\f6ac\";\n}\n.bi-qr-code-scan::before {\n  content: \"\\f6ad\";\n}\n.bi-qr-code::before {\n  content: \"\\f6ae\";\n}\n.bi-quora::before {\n  content: \"\\f6af\";\n}\n.bi-quote::before {\n  content: \"\\f6b0\";\n}\n.bi-robot::before {\n  content: \"\\f6b1\";\n}\n.bi-send-check-fill::before {\n  content: \"\\f6b2\";\n}\n.bi-send-check::before {\n  content: \"\\f6b3\";\n}\n.bi-send-dash-fill::before {\n  content: \"\\f6b4\";\n}\n.bi-send-dash::before {\n  content: \"\\f6b5\";\n}\n.bi-send-exclamation-1::before {\n  content: \"\\f6b6\";\n}\n.bi-send-exclamation-fill::before {\n  content: \"\\f6b7\";\n}\n.bi-send-exclamation::before {\n  content: \"\\f6b8\";\n}\n.bi-send-fill::before {\n  content: \"\\f6b9\";\n}\n.bi-send-plus-fill::before {\n  content: \"\\f6ba\";\n}\n.bi-send-plus::before {\n  content: \"\\f6bb\";\n}\n.bi-send-slash-fill::before {\n  content: \"\\f6bc\";\n}\n.bi-send-slash::before {\n  content: \"\\f6bd\";\n}\n.bi-send-x-fill::before {\n  content: \"\\f6be\";\n}\n.bi-send-x::before {\n  content: \"\\f6bf\";\n}\n.bi-send::before {\n  content: \"\\f6c0\";\n}\n.bi-steam::before {\n  content: \"\\f6c1\";\n}\n.bi-terminal-dash-1::before {\n  content: \"\\f6c2\";\n}\n.bi-terminal-dash::before {\n  content: \"\\f6c3\";\n}\n.bi-terminal-plus::before {\n  content: \"\\f6c4\";\n}\n.bi-terminal-split::before {\n  content: \"\\f6c5\";\n}\n.bi-ticket-detailed-fill::before {\n  content: \"\\f6c6\";\n}\n.bi-ticket-detailed::before {\n  content: \"\\f6c7\";\n}\n.bi-ticket-fill::before {\n  content: \"\\f6c8\";\n}\n.bi-ticket-perforated-fill::before {\n  content: \"\\f6c9\";\n}\n.bi-ticket-perforated::before {\n  content: \"\\f6ca\";\n}\n.bi-ticket::before {\n  content: \"\\f6cb\";\n}\n.bi-tiktok::before {\n  content: \"\\f6cc\";\n}\n.bi-window-dash::before {\n  content: \"\\f6cd\";\n}\n.bi-window-desktop::before {\n  content: \"\\f6ce\";\n}\n.bi-window-fullscreen::before {\n  content: \"\\f6cf\";\n}\n.bi-window-plus::before {\n  content: \"\\f6d0\";\n}\n.bi-window-split::before {\n  content: \"\\f6d1\";\n}\n.bi-window-stack::before {\n  content: \"\\f6d2\";\n}\n.bi-window-x::before {\n  content: \"\\f6d3\";\n}\n.bi-xbox::before {\n  content: \"\\f6d4\";\n}\n.bi-ethernet::before {\n  content: \"\\f6d5\";\n}\n.bi-hdmi-fill::before {\n  content: \"\\f6d6\";\n}\n.bi-hdmi::before {\n  content: \"\\f6d7\";\n}\n.bi-usb-c-fill::before {\n  content: \"\\f6d8\";\n}\n.bi-usb-c::before {\n  content: \"\\f6d9\";\n}\n.bi-usb-fill::before {\n  content: \"\\f6da\";\n}\n.bi-usb-plug-fill::before {\n  content: \"\\f6db\";\n}\n.bi-usb-plug::before {\n  content: \"\\f6dc\";\n}\n.bi-usb-symbol::before {\n  content: \"\\f6dd\";\n}\n.bi-usb::before {\n  content: \"\\f6de\";\n}\n.bi-boombox-fill::before {\n  content: \"\\f6df\";\n}\n.bi-displayport-1::before {\n  content: \"\\f6e0\";\n}\n.bi-displayport::before {\n  content: \"\\f6e1\";\n}\n.bi-gpu-card::before {\n  content: \"\\f6e2\";\n}\n.bi-memory::before {\n  content: \"\\f6e3\";\n}\n.bi-modem-fill::before {\n  content: \"\\f6e4\";\n}\n.bi-modem::before {\n  content: \"\\f6e5\";\n}\n.bi-motherboard-fill::before {\n  content: \"\\f6e6\";\n}\n.bi-motherboard::before {\n  content: \"\\f6e7\";\n}\n.bi-optical-audio-fill::before {\n  content: \"\\f6e8\";\n}\n.bi-optical-audio::before {\n  content: \"\\f6e9\";\n}\n.bi-pci-card::before {\n  content: \"\\f6ea\";\n}\n.bi-router-fill::before {\n  content: \"\\f6eb\";\n}\n.bi-router::before {\n  content: \"\\f6ec\";\n}\n.bi-ssd-fill::before {\n  content: \"\\f6ed\";\n}\n.bi-ssd::before {\n  content: \"\\f6ee\";\n}\n.bi-thunderbolt-fill::before {\n  content: \"\\f6ef\";\n}\n.bi-thunderbolt::before {\n  content: \"\\f6f0\";\n}\n.bi-usb-drive-fill::before {\n  content: \"\\f6f1\";\n}\n.bi-usb-drive::before {\n  content: \"\\f6f2\";\n}\n.bi-usb-micro-fill::before {\n  content: \"\\f6f3\";\n}\n.bi-usb-micro::before {\n  content: \"\\f6f4\";\n}\n.bi-usb-mini-fill::before {\n  content: \"\\f6f5\";\n}\n.bi-usb-mini::before {\n  content: \"\\f6f6\";\n}\n.bi-cloud-haze2::before {\n  content: \"\\f6f7\";\n}\n.bi-device-hdd-fill::before {\n  content: \"\\f6f8\";\n}\n.bi-device-hdd::before {\n  content: \"\\f6f9\";\n}\n.bi-device-ssd-fill::before {\n  content: \"\\f6fa\";\n}\n.bi-device-ssd::before {\n  content: \"\\f6fb\";\n}\n.bi-displayport-fill::before {\n  content: \"\\f6fc\";\n}\n.bi-mortarboard-fill::before {\n  content: \"\\f6fd\";\n}\n.bi-mortarboard::before {\n  content: \"\\f6fe\";\n}\n.bi-terminal-x::before {\n  content: \"\\f6ff\";\n}\n.bi-arrow-through-heart-fill::before {\n  content: \"\\f700\";\n}\n.bi-arrow-through-heart::before {\n  content: \"\\f701\";\n}\n.bi-badge-sd-fill::before {\n  content: \"\\f702\";\n}\n.bi-badge-sd::before {\n  content: \"\\f703\";\n}\n.bi-bag-heart-fill::before {\n  content: \"\\f704\";\n}\n.bi-bag-heart::before {\n  content: \"\\f705\";\n}\n.bi-balloon-fill::before {\n  content: \"\\f706\";\n}\n.bi-balloon-heart-fill::before {\n  content: \"\\f707\";\n}\n.bi-balloon-heart::before {\n  content: \"\\f708\";\n}\n.bi-balloon::before {\n  content: \"\\f709\";\n}\n.bi-box2-fill::before {\n  content: \"\\f70a\";\n}\n.bi-box2-heart-fill::before {\n  content: \"\\f70b\";\n}\n.bi-box2-heart::before {\n  content: \"\\f70c\";\n}\n.bi-box2::before {\n  content: \"\\f70d\";\n}\n.bi-braces-asterisk::before {\n  content: \"\\f70e\";\n}\n.bi-calendar-heart-fill::before {\n  content: \"\\f70f\";\n}\n.bi-calendar-heart::before {\n  content: \"\\f710\";\n}\n.bi-calendar2-heart-fill::before {\n  content: \"\\f711\";\n}\n.bi-calendar2-heart::before {\n  content: \"\\f712\";\n}\n.bi-chat-heart-fill::before {\n  content: \"\\f713\";\n}\n.bi-chat-heart::before {\n  content: \"\\f714\";\n}\n.bi-chat-left-heart-fill::before {\n  content: \"\\f715\";\n}\n.bi-chat-left-heart::before {\n  content: \"\\f716\";\n}\n.bi-chat-right-heart-fill::before {\n  content: \"\\f717\";\n}\n.bi-chat-right-heart::before {\n  content: \"\\f718\";\n}\n.bi-chat-square-heart-fill::before {\n  content: \"\\f719\";\n}\n.bi-chat-square-heart::before {\n  content: \"\\f71a\";\n}\n.bi-clipboard-check-fill::before {\n  content: \"\\f71b\";\n}\n.bi-clipboard-data-fill::before {\n  content: \"\\f71c\";\n}\n.bi-clipboard-fill::before {\n  content: \"\\f71d\";\n}\n.bi-clipboard-heart-fill::before {\n  content: \"\\f71e\";\n}\n.bi-clipboard-heart::before {\n  content: \"\\f71f\";\n}\n.bi-clipboard-minus-fill::before {\n  content: \"\\f720\";\n}\n.bi-clipboard-plus-fill::before {\n  content: \"\\f721\";\n}\n.bi-clipboard-pulse::before {\n  content: \"\\f722\";\n}\n.bi-clipboard-x-fill::before {\n  content: \"\\f723\";\n}\n.bi-clipboard2-check-fill::before {\n  content: \"\\f724\";\n}\n.bi-clipboard2-check::before {\n  content: \"\\f725\";\n}\n.bi-clipboard2-data-fill::before {\n  content: \"\\f726\";\n}\n.bi-clipboard2-data::before {\n  content: \"\\f727\";\n}\n.bi-clipboard2-fill::before {\n  content: \"\\f728\";\n}\n.bi-clipboard2-heart-fill::before {\n  content: \"\\f729\";\n}\n.bi-clipboard2-heart::before {\n  content: \"\\f72a\";\n}\n.bi-clipboard2-minus-fill::before {\n  content: \"\\f72b\";\n}\n.bi-clipboard2-minus::before {\n  content: \"\\f72c\";\n}\n.bi-clipboard2-plus-fill::before {\n  content: \"\\f72d\";\n}\n.bi-clipboard2-plus::before {\n  content: \"\\f72e\";\n}\n.bi-clipboard2-pulse-fill::before {\n  content: \"\\f72f\";\n}\n.bi-clipboard2-pulse::before {\n  content: \"\\f730\";\n}\n.bi-clipboard2-x-fill::before {\n  content: \"\\f731\";\n}\n.bi-clipboard2-x::before {\n  content: \"\\f732\";\n}\n.bi-clipboard2::before {\n  content: \"\\f733\";\n}\n.bi-emoji-kiss-fill::before {\n  content: \"\\f734\";\n}\n.bi-emoji-kiss::before {\n  content: \"\\f735\";\n}\n.bi-envelope-heart-fill::before {\n  content: \"\\f736\";\n}\n.bi-envelope-heart::before {\n  content: \"\\f737\";\n}\n.bi-envelope-open-heart-fill::before {\n  content: \"\\f738\";\n}\n.bi-envelope-open-heart::before {\n  content: \"\\f739\";\n}\n.bi-envelope-paper-fill::before {\n  content: \"\\f73a\";\n}\n.bi-envelope-paper-heart-fill::before {\n  content: \"\\f73b\";\n}\n.bi-envelope-paper-heart::before {\n  content: \"\\f73c\";\n}\n.bi-envelope-paper::before {\n  content: \"\\f73d\";\n}\n.bi-filetype-aac::before {\n  content: \"\\f73e\";\n}\n.bi-filetype-ai::before {\n  content: \"\\f73f\";\n}\n.bi-filetype-bmp::before {\n  content: \"\\f740\";\n}\n.bi-filetype-cs::before {\n  content: \"\\f741\";\n}\n.bi-filetype-css::before {\n  content: \"\\f742\";\n}\n.bi-filetype-csv::before {\n  content: \"\\f743\";\n}\n.bi-filetype-doc::before {\n  content: \"\\f744\";\n}\n.bi-filetype-docx::before {\n  content: \"\\f745\";\n}\n.bi-filetype-exe::before {\n  content: \"\\f746\";\n}\n.bi-filetype-gif::before {\n  content: \"\\f747\";\n}\n.bi-filetype-heic::before {\n  content: \"\\f748\";\n}\n.bi-filetype-html::before {\n  content: \"\\f749\";\n}\n.bi-filetype-java::before {\n  content: \"\\f74a\";\n}\n.bi-filetype-jpg::before {\n  content: \"\\f74b\";\n}\n.bi-filetype-js::before {\n  content: \"\\f74c\";\n}\n.bi-filetype-jsx::before {\n  content: \"\\f74d\";\n}\n.bi-filetype-key::before {\n  content: \"\\f74e\";\n}\n.bi-filetype-m4p::before {\n  content: \"\\f74f\";\n}\n.bi-filetype-md::before {\n  content: \"\\f750\";\n}\n.bi-filetype-mdx::before {\n  content: \"\\f751\";\n}\n.bi-filetype-mov::before {\n  content: \"\\f752\";\n}\n.bi-filetype-mp3::before {\n  content: \"\\f753\";\n}\n.bi-filetype-mp4::before {\n  content: \"\\f754\";\n}\n.bi-filetype-otf::before {\n  content: \"\\f755\";\n}\n.bi-filetype-pdf::before {\n  content: \"\\f756\";\n}\n.bi-filetype-php::before {\n  content: \"\\f757\";\n}\n.bi-filetype-png::before {\n  content: \"\\f758\";\n}\n.bi-filetype-ppt-1::before {\n  content: \"\\f759\";\n}\n.bi-filetype-ppt::before {\n  content: \"\\f75a\";\n}\n.bi-filetype-psd::before {\n  content: \"\\f75b\";\n}\n.bi-filetype-py::before {\n  content: \"\\f75c\";\n}\n.bi-filetype-raw::before {\n  content: \"\\f75d\";\n}\n.bi-filetype-rb::before {\n  content: \"\\f75e\";\n}\n.bi-filetype-sass::before {\n  content: \"\\f75f\";\n}\n.bi-filetype-scss::before {\n  content: \"\\f760\";\n}\n.bi-filetype-sh::before {\n  content: \"\\f761\";\n}\n.bi-filetype-svg::before {\n  content: \"\\f762\";\n}\n.bi-filetype-tiff::before {\n  content: \"\\f763\";\n}\n.bi-filetype-tsx::before {\n  content: \"\\f764\";\n}\n.bi-filetype-ttf::before {\n  content: \"\\f765\";\n}\n.bi-filetype-txt::before {\n  content: \"\\f766\";\n}\n.bi-filetype-wav::before {\n  content: \"\\f767\";\n}\n.bi-filetype-woff::before {\n  content: \"\\f768\";\n}\n.bi-filetype-xls-1::before {\n  content: \"\\f769\";\n}\n.bi-filetype-xls::before {\n  content: \"\\f76a\";\n}\n.bi-filetype-xml::before {\n  content: \"\\f76b\";\n}\n.bi-filetype-yml::before {\n  content: \"\\f76c\";\n}\n.bi-heart-arrow::before {\n  content: \"\\f76d\";\n}\n.bi-heart-pulse-fill::before {\n  content: \"\\f76e\";\n}\n.bi-heart-pulse::before {\n  content: \"\\f76f\";\n}\n.bi-heartbreak-fill::before {\n  content: \"\\f770\";\n}\n.bi-heartbreak::before {\n  content: \"\\f771\";\n}\n.bi-hearts::before {\n  content: \"\\f772\";\n}\n.bi-hospital-fill::before {\n  content: \"\\f773\";\n}\n.bi-hospital::before {\n  content: \"\\f774\";\n}\n.bi-house-heart-fill::before {\n  content: \"\\f775\";\n}\n.bi-house-heart::before {\n  content: \"\\f776\";\n}\n.bi-incognito::before {\n  content: \"\\f777\";\n}\n.bi-magnet-fill::before {\n  content: \"\\f778\";\n}\n.bi-magnet::before {\n  content: \"\\f779\";\n}\n.bi-person-heart::before {\n  content: \"\\f77a\";\n}\n.bi-person-hearts::before {\n  content: \"\\f77b\";\n}\n.bi-phone-flip::before {\n  content: \"\\f77c\";\n}\n.bi-plugin::before {\n  content: \"\\f77d\";\n}\n.bi-postage-fill::before {\n  content: \"\\f77e\";\n}\n.bi-postage-heart-fill::before {\n  content: \"\\f77f\";\n}\n.bi-postage-heart::before {\n  content: \"\\f780\";\n}\n.bi-postage::before {\n  content: \"\\f781\";\n}\n.bi-postcard-fill::before {\n  content: \"\\f782\";\n}\n.bi-postcard-heart-fill::before {\n  content: \"\\f783\";\n}\n.bi-postcard-heart::before {\n  content: \"\\f784\";\n}\n.bi-postcard::before {\n  content: \"\\f785\";\n}\n.bi-search-heart-fill::before {\n  content: \"\\f786\";\n}\n.bi-search-heart::before {\n  content: \"\\f787\";\n}\n.bi-sliders2-vertical::before {\n  content: \"\\f788\";\n}\n.bi-sliders2::before {\n  content: \"\\f789\";\n}\n.bi-trash3-fill::before {\n  content: \"\\f78a\";\n}\n.bi-trash3::before {\n  content: \"\\f78b\";\n}\n.bi-valentine::before {\n  content: \"\\f78c\";\n}\n.bi-valentine2::before {\n  content: \"\\f78d\";\n}\n.bi-wrench-adjustable-circle-fill::before {\n  content: \"\\f78e\";\n}\n.bi-wrench-adjustable-circle::before {\n  content: \"\\f78f\";\n}\n.bi-wrench-adjustable::before {\n  content: \"\\f790\";\n}\n.bi-filetype-json::before {\n  content: \"\\f791\";\n}\n.bi-filetype-pptx::before {\n  content: \"\\f792\";\n}\n.bi-filetype-xlsx::before {\n  content: \"\\f793\";\n}\n.bi-1-circle-1::before {\n  content: \"\\f794\";\n}\n.bi-1-circle-fill-1::before {\n  content: \"\\f795\";\n}\n.bi-1-circle-fill::before {\n  content: \"\\f796\";\n}\n.bi-1-circle::before {\n  content: \"\\f797\";\n}\n.bi-1-square-fill::before {\n  content: \"\\f798\";\n}\n.bi-1-square::before {\n  content: \"\\f799\";\n}\n.bi-2-circle-1::before {\n  content: \"\\f79a\";\n}\n.bi-2-circle-fill-1::before {\n  content: \"\\f79b\";\n}\n.bi-2-circle-fill::before {\n  content: \"\\f79c\";\n}\n.bi-2-circle::before {\n  content: \"\\f79d\";\n}\n.bi-2-square-fill::before {\n  content: \"\\f79e\";\n}\n.bi-2-square::before {\n  content: \"\\f79f\";\n}\n.bi-3-circle-1::before {\n  content: \"\\f7a0\";\n}\n.bi-3-circle-fill-1::before {\n  content: \"\\f7a1\";\n}\n.bi-3-circle-fill::before {\n  content: \"\\f7a2\";\n}\n.bi-3-circle::before {\n  content: \"\\f7a3\";\n}\n.bi-3-square-fill::before {\n  content: \"\\f7a4\";\n}\n.bi-3-square::before {\n  content: \"\\f7a5\";\n}\n.bi-4-circle-1::before {\n  content: \"\\f7a6\";\n}\n.bi-4-circle-fill-1::before {\n  content: \"\\f7a7\";\n}\n.bi-4-circle-fill::before {\n  content: \"\\f7a8\";\n}\n.bi-4-circle::before {\n  content: \"\\f7a9\";\n}\n.bi-4-square-fill::before {\n  content: \"\\f7aa\";\n}\n.bi-4-square::before {\n  content: \"\\f7ab\";\n}\n.bi-5-circle-1::before {\n  content: \"\\f7ac\";\n}\n.bi-5-circle-fill-1::before {\n  content: \"\\f7ad\";\n}\n.bi-5-circle-fill::before {\n  content: \"\\f7ae\";\n}\n.bi-5-circle::before {\n  content: \"\\f7af\";\n}\n.bi-5-square-fill::before {\n  content: \"\\f7b0\";\n}\n.bi-5-square::before {\n  content: \"\\f7b1\";\n}\n.bi-6-circle-1::before {\n  content: \"\\f7b2\";\n}\n.bi-6-circle-fill-1::before {\n  content: \"\\f7b3\";\n}\n.bi-6-circle-fill::before {\n  content: \"\\f7b4\";\n}\n.bi-6-circle::before {\n  content: \"\\f7b5\";\n}\n.bi-6-square-fill::before {\n  content: \"\\f7b6\";\n}\n.bi-6-square::before {\n  content: \"\\f7b7\";\n}\n.bi-7-circle-1::before {\n  content: \"\\f7b8\";\n}\n.bi-7-circle-fill-1::before {\n  content: \"\\f7b9\";\n}\n.bi-7-circle-fill::before {\n  content: \"\\f7ba\";\n}\n.bi-7-circle::before {\n  content: \"\\f7bb\";\n}\n.bi-7-square-fill::before {\n  content: \"\\f7bc\";\n}\n.bi-7-square::before {\n  content: \"\\f7bd\";\n}\n.bi-8-circle-1::before {\n  content: \"\\f7be\";\n}\n.bi-8-circle-fill-1::before {\n  content: \"\\f7bf\";\n}\n.bi-8-circle-fill::before {\n  content: \"\\f7c0\";\n}\n.bi-8-circle::before {\n  content: \"\\f7c1\";\n}\n.bi-8-square-fill::before {\n  content: \"\\f7c2\";\n}\n.bi-8-square::before {\n  content: \"\\f7c3\";\n}\n.bi-9-circle-1::before {\n  content: \"\\f7c4\";\n}\n.bi-9-circle-fill-1::before {\n  content: \"\\f7c5\";\n}\n.bi-9-circle-fill::before {\n  content: \"\\f7c6\";\n}\n.bi-9-circle::before {\n  content: \"\\f7c7\";\n}\n.bi-9-square-fill::before {\n  content: \"\\f7c8\";\n}\n.bi-9-square::before {\n  content: \"\\f7c9\";\n}\n.bi-airplane-engines-fill::before {\n  content: \"\\f7ca\";\n}\n.bi-airplane-engines::before {\n  content: \"\\f7cb\";\n}\n.bi-airplane-fill::before {\n  content: \"\\f7cc\";\n}\n.bi-airplane::before {\n  content: \"\\f7cd\";\n}\n.bi-alexa::before {\n  content: \"\\f7ce\";\n}\n.bi-alipay::before {\n  content: \"\\f7cf\";\n}\n.bi-android::before {\n  content: \"\\f7d0\";\n}\n.bi-android2::before {\n  content: \"\\f7d1\";\n}\n.bi-box-fill::before {\n  content: \"\\f7d2\";\n}\n.bi-box-seam-fill::before {\n  content: \"\\f7d3\";\n}\n.bi-browser-chrome::before {\n  content: \"\\f7d4\";\n}\n.bi-browser-edge::before {\n  content: \"\\f7d5\";\n}\n.bi-browser-firefox::before {\n  content: \"\\f7d6\";\n}\n.bi-browser-safari::before {\n  content: \"\\f7d7\";\n}\n.bi-c-circle-1::before {\n  content: \"\\f7d8\";\n}\n.bi-c-circle-fill-1::before {\n  content: \"\\f7d9\";\n}\n.bi-c-circle-fill::before {\n  content: \"\\f7da\";\n}\n.bi-c-circle::before {\n  content: \"\\f7db\";\n}\n.bi-c-square-fill::before {\n  content: \"\\f7dc\";\n}\n.bi-c-square::before {\n  content: \"\\f7dd\";\n}\n.bi-capsule-pill::before {\n  content: \"\\f7de\";\n}\n.bi-capsule::before {\n  content: \"\\f7df\";\n}\n.bi-car-front-fill::before {\n  content: \"\\f7e0\";\n}\n.bi-car-front::before {\n  content: \"\\f7e1\";\n}\n.bi-cassette-fill::before {\n  content: \"\\f7e2\";\n}\n.bi-cassette::before {\n  content: \"\\f7e3\";\n}\n.bi-cc-circle-1::before {\n  content: \"\\f7e4\";\n}\n.bi-cc-circle-fill-1::before {\n  content: \"\\f7e5\";\n}\n.bi-cc-circle-fill::before {\n  content: \"\\f7e6\";\n}\n.bi-cc-circle::before {\n  content: \"\\f7e7\";\n}\n.bi-cc-square-fill::before {\n  content: \"\\f7e8\";\n}\n.bi-cc-square::before {\n  content: \"\\f7e9\";\n}\n.bi-cup-hot-fill::before {\n  content: \"\\f7ea\";\n}\n.bi-cup-hot::before {\n  content: \"\\f7eb\";\n}\n.bi-currency-rupee::before {\n  content: \"\\f7ec\";\n}\n.bi-dropbox::before {\n  content: \"\\f7ed\";\n}\n.bi-escape::before {\n  content: \"\\f7ee\";\n}\n.bi-fast-forward-btn-fill::before {\n  content: \"\\f7ef\";\n}\n.bi-fast-forward-btn::before {\n  content: \"\\f7f0\";\n}\n.bi-fast-forward-circle-fill::before {\n  content: \"\\f7f1\";\n}\n.bi-fast-forward-circle::before {\n  content: \"\\f7f2\";\n}\n.bi-fast-forward-fill::before {\n  content: \"\\f7f3\";\n}\n.bi-fast-forward::before {\n  content: \"\\f7f4\";\n}\n.bi-filetype-sql::before {\n  content: \"\\f7f5\";\n}\n.bi-fire::before {\n  content: \"\\f7f6\";\n}\n.bi-google-play::before {\n  content: \"\\f7f7\";\n}\n.bi-h-circle-1::before {\n  content: \"\\f7f8\";\n}\n.bi-h-circle-fill-1::before {\n  content: \"\\f7f9\";\n}\n.bi-h-circle-fill::before {\n  content: \"\\f7fa\";\n}\n.bi-h-circle::before {\n  content: \"\\f7fb\";\n}\n.bi-h-square-fill::before {\n  content: \"\\f7fc\";\n}\n.bi-h-square::before {\n  content: \"\\f7fd\";\n}\n.bi-indent::before {\n  content: \"\\f7fe\";\n}\n.bi-lungs-fill::before {\n  content: \"\\f7ff\";\n}\n.bi-lungs::before {\n  content: \"\\f800\";\n}\n.bi-microsoft-teams::before {\n  content: \"\\f801\";\n}\n.bi-p-circle-1::before {\n  content: \"\\f802\";\n}\n.bi-p-circle-fill-1::before {\n  content: \"\\f803\";\n}\n.bi-p-circle-fill::before {\n  content: \"\\f804\";\n}\n.bi-p-circle::before {\n  content: \"\\f805\";\n}\n.bi-p-square-fill::before {\n  content: \"\\f806\";\n}\n.bi-p-square::before {\n  content: \"\\f807\";\n}\n.bi-pass-fill::before {\n  content: \"\\f808\";\n}\n.bi-pass::before {\n  content: \"\\f809\";\n}\n.bi-prescription::before {\n  content: \"\\f80a\";\n}\n.bi-prescription2::before {\n  content: \"\\f80b\";\n}\n.bi-r-circle-1::before {\n  content: \"\\f80c\";\n}\n.bi-r-circle-fill-1::before {\n  content: \"\\f80d\";\n}\n.bi-r-circle-fill::before {\n  content: \"\\f80e\";\n}\n.bi-r-circle::before {\n  content: \"\\f80f\";\n}\n.bi-r-square-fill::before {\n  content: \"\\f810\";\n}\n.bi-r-square::before {\n  content: \"\\f811\";\n}\n.bi-repeat-1::before {\n  content: \"\\f812\";\n}\n.bi-repeat::before {\n  content: \"\\f813\";\n}\n.bi-rewind-btn-fill::before {\n  content: \"\\f814\";\n}\n.bi-rewind-btn::before {\n  content: \"\\f815\";\n}\n.bi-rewind-circle-fill::before {\n  content: \"\\f816\";\n}\n.bi-rewind-circle::before {\n  content: \"\\f817\";\n}\n.bi-rewind-fill::before {\n  content: \"\\f818\";\n}\n.bi-rewind::before {\n  content: \"\\f819\";\n}\n.bi-train-freight-front-fill::before {\n  content: \"\\f81a\";\n}\n.bi-train-freight-front::before {\n  content: \"\\f81b\";\n}\n.bi-train-front-fill::before {\n  content: \"\\f81c\";\n}\n.bi-train-front::before {\n  content: \"\\f81d\";\n}\n.bi-train-lightrail-front-fill::before {\n  content: \"\\f81e\";\n}\n.bi-train-lightrail-front::before {\n  content: \"\\f81f\";\n}\n.bi-truck-front-fill::before {\n  content: \"\\f820\";\n}\n.bi-truck-front::before {\n  content: \"\\f821\";\n}\n.bi-ubuntu::before {\n  content: \"\\f822\";\n}\n.bi-unindent::before {\n  content: \"\\f823\";\n}\n.bi-unity::before {\n  content: \"\\f824\";\n}\n.bi-universal-access-circle::before {\n  content: \"\\f825\";\n}\n.bi-universal-access::before {\n  content: \"\\f826\";\n}\n.bi-virus::before {\n  content: \"\\f827\";\n}\n.bi-virus2::before {\n  content: \"\\f828\";\n}\n.bi-wechat::before {\n  content: \"\\f829\";\n}\n.bi-yelp::before {\n  content: \"\\f82a\";\n}\n.bi-sign-stop-fill::before {\n  content: \"\\f82b\";\n}\n.bi-sign-stop-lights-fill::before {\n  content: \"\\f82c\";\n}\n.bi-sign-stop-lights::before {\n  content: \"\\f82d\";\n}\n.bi-sign-stop::before {\n  content: \"\\f82e\";\n}\n.bi-sign-turn-left-fill::before {\n  content: \"\\f82f\";\n}\n.bi-sign-turn-left::before {\n  content: \"\\f830\";\n}\n.bi-sign-turn-right-fill::before {\n  content: \"\\f831\";\n}\n.bi-sign-turn-right::before {\n  content: \"\\f832\";\n}\n.bi-sign-turn-slight-left-fill::before {\n  content: \"\\f833\";\n}\n.bi-sign-turn-slight-left::before {\n  content: \"\\f834\";\n}\n.bi-sign-turn-slight-right-fill::before {\n  content: \"\\f835\";\n}\n.bi-sign-turn-slight-right::before {\n  content: \"\\f836\";\n}\n.bi-sign-yield-fill::before {\n  content: \"\\f837\";\n}\n.bi-sign-yield::before {\n  content: \"\\f838\";\n}\n.bi-ev-station-fill::before {\n  content: \"\\f839\";\n}\n.bi-ev-station::before {\n  content: \"\\f83a\";\n}\n.bi-fuel-pump-diesel-fill::before {\n  content: \"\\f83b\";\n}\n.bi-fuel-pump-diesel::before {\n  content: \"\\f83c\";\n}\n.bi-fuel-pump-fill::before {\n  content: \"\\f83d\";\n}\n.bi-fuel-pump::before {\n  content: \"\\f83e\";\n}\n"
  },
  {
    "path": "src/electionguard_gui/web/css/bootstrap-overrides.css",
    "content": ".bg-primary {\n  background-color: #009688 !important;\n}\n\n.btn-primary {\n  --bs-btn-bg: #009688 !important;\n  --bs-btn-border-color: teal !important;\n  --bs-btn-hover-bg: #008477 !important;\n  --bs-btn-hover-border-color: #005d53 !important;\n  --bs-btn-active-bg: #005d53 !important;\n  --bs-btn-active-border-color: #005d53 !important;\n}\n"
  },
  {
    "path": "src/electionguard_gui/web/css/eg-styles.css",
    "content": ".key-ceremony-status {\n  font-size: 1.2rem;\n  font-weight: bold;\n}\n"
  },
  {
    "path": "src/electionguard_gui/web/css/spinner.css",
    "content": "/* via https://cssload.net/en/spinners */\n\n.windows8 {\n  position: relative;\n  width: 30px;\n  height: 30px;\n  margin: auto;\n}\n\n.windows8 .wBall {\n  position: absolute;\n  width: 28px;\n  height: 28px;\n  opacity: 0;\n  transform: rotate(225deg);\n  -o-transform: rotate(225deg);\n  -ms-transform: rotate(225deg);\n  -webkit-transform: rotate(225deg);\n  -moz-transform: rotate(225deg);\n  animation: orbit 6.96s infinite;\n  -o-animation: orbit 6.96s infinite;\n  -ms-animation: orbit 6.96s infinite;\n  -webkit-animation: orbit 6.96s infinite;\n  -moz-animation: orbit 6.96s infinite;\n}\n\n.windows8 .wBall .wInnerBall {\n  position: absolute;\n  width: 4px;\n  height: 4px;\n  background: rgb(0, 150, 136);\n  left: 0px;\n  top: 0px;\n  border-radius: 4px;\n}\n\n.windows8 #wBall_1 {\n  animation-delay: 1.52s;\n  -o-animation-delay: 1.52s;\n  -ms-animation-delay: 1.52s;\n  -webkit-animation-delay: 1.52s;\n  -moz-animation-delay: 1.52s;\n}\n\n.windows8 #wBall_2 {\n  animation-delay: 0.3s;\n  -o-animation-delay: 0.3s;\n  -ms-animation-delay: 0.3s;\n  -webkit-animation-delay: 0.3s;\n  -moz-animation-delay: 0.3s;\n}\n\n.windows8 #wBall_3 {\n  animation-delay: 0.61s;\n  -o-animation-delay: 0.61s;\n  -ms-animation-delay: 0.61s;\n  -webkit-animation-delay: 0.61s;\n  -moz-animation-delay: 0.61s;\n}\n\n.windows8 #wBall_4 {\n  animation-delay: 0.91s;\n  -o-animation-delay: 0.91s;\n  -ms-animation-delay: 0.91s;\n  -webkit-animation-delay: 0.91s;\n  -moz-animation-delay: 0.91s;\n}\n\n.windows8 #wBall_5 {\n  animation-delay: 1.22s;\n  -o-animation-delay: 1.22s;\n  -ms-animation-delay: 1.22s;\n  -webkit-animation-delay: 1.22s;\n  -moz-animation-delay: 1.22s;\n}\n\n@keyframes orbit {\n  0% {\n    opacity: 1;\n    z-index: 99;\n    transform: rotate(180deg);\n    animation-timing-function: ease-out;\n  }\n\n  7% {\n    opacity: 1;\n    transform: rotate(300deg);\n    animation-timing-function: linear;\n    origin: 0%;\n  }\n\n  30% {\n    opacity: 1;\n    transform: rotate(410deg);\n    animation-timing-function: ease-in-out;\n    origin: 7%;\n  }\n\n  39% {\n    opacity: 1;\n    transform: rotate(645deg);\n    animation-timing-function: linear;\n    origin: 30%;\n  }\n\n  70% {\n    opacity: 1;\n    transform: rotate(770deg);\n    animation-timing-function: ease-out;\n    origin: 39%;\n  }\n\n  75% {\n    opacity: 1;\n    transform: rotate(900deg);\n    animation-timing-function: ease-out;\n    origin: 70%;\n  }\n\n  76% {\n    opacity: 0;\n    transform: rotate(900deg);\n  }\n\n  100% {\n    opacity: 0;\n    transform: rotate(900deg);\n  }\n}\n\n@-o-keyframes orbit {\n  0% {\n    opacity: 1;\n    z-index: 99;\n    -o-transform: rotate(180deg);\n    -o-animation-timing-function: ease-out;\n  }\n\n  7% {\n    opacity: 1;\n    -o-transform: rotate(300deg);\n    -o-animation-timing-function: linear;\n    -o-origin: 0%;\n  }\n\n  30% {\n    opacity: 1;\n    -o-transform: rotate(410deg);\n    -o-animation-timing-function: ease-in-out;\n    -o-origin: 7%;\n  }\n\n  39% {\n    opacity: 1;\n    -o-transform: rotate(645deg);\n    -o-animation-timing-function: linear;\n    -o-origin: 30%;\n  }\n\n  70% {\n    opacity: 1;\n    -o-transform: rotate(770deg);\n    -o-animation-timing-function: ease-out;\n    -o-origin: 39%;\n  }\n\n  75% {\n    opacity: 1;\n    -o-transform: rotate(900deg);\n    -o-animation-timing-function: ease-out;\n    -o-origin: 70%;\n  }\n\n  76% {\n    opacity: 0;\n    -o-transform: rotate(900deg);\n  }\n\n  100% {\n    opacity: 0;\n    -o-transform: rotate(900deg);\n  }\n}\n\n@-ms-keyframes orbit {\n  0% {\n    opacity: 1;\n    z-index: 99;\n    -ms-transform: rotate(180deg);\n    -ms-animation-timing-function: ease-out;\n  }\n\n  7% {\n    opacity: 1;\n    -ms-transform: rotate(300deg);\n    -ms-animation-timing-function: linear;\n    -ms-origin: 0%;\n  }\n\n  30% {\n    opacity: 1;\n    -ms-transform: rotate(410deg);\n    -ms-animation-timing-function: ease-in-out;\n    -ms-origin: 7%;\n  }\n\n  39% {\n    opacity: 1;\n    -ms-transform: rotate(645deg);\n    -ms-animation-timing-function: linear;\n    -ms-origin: 30%;\n  }\n\n  70% {\n    opacity: 1;\n    -ms-transform: rotate(770deg);\n    -ms-animation-timing-function: ease-out;\n    -ms-origin: 39%;\n  }\n\n  75% {\n    opacity: 1;\n    -ms-transform: rotate(900deg);\n    -ms-animation-timing-function: ease-out;\n    -ms-origin: 70%;\n  }\n\n  76% {\n    opacity: 0;\n    -ms-transform: rotate(900deg);\n  }\n\n  100% {\n    opacity: 0;\n    -ms-transform: rotate(900deg);\n  }\n}\n\n@-webkit-keyframes orbit {\n  0% {\n    opacity: 1;\n    z-index: 99;\n    -webkit-transform: rotate(180deg);\n    -webkit-animation-timing-function: ease-out;\n  }\n\n  7% {\n    opacity: 1;\n    -webkit-transform: rotate(300deg);\n    -webkit-animation-timing-function: linear;\n    -webkit-origin: 0%;\n  }\n\n  30% {\n    opacity: 1;\n    -webkit-transform: rotate(410deg);\n    -webkit-animation-timing-function: ease-in-out;\n    -webkit-origin: 7%;\n  }\n\n  39% {\n    opacity: 1;\n    -webkit-transform: rotate(645deg);\n    -webkit-animation-timing-function: linear;\n    -webkit-origin: 30%;\n  }\n\n  70% {\n    opacity: 1;\n    -webkit-transform: rotate(770deg);\n    -webkit-animation-timing-function: ease-out;\n    -webkit-origin: 39%;\n  }\n\n  75% {\n    opacity: 1;\n    -webkit-transform: rotate(900deg);\n    -webkit-animation-timing-function: ease-out;\n    -webkit-origin: 70%;\n  }\n\n  76% {\n    opacity: 0;\n    -webkit-transform: rotate(900deg);\n  }\n\n  100% {\n    opacity: 0;\n    -webkit-transform: rotate(900deg);\n  }\n}\n\n@-moz-keyframes orbit {\n  0% {\n    opacity: 1;\n    z-index: 99;\n    -moz-transform: rotate(180deg);\n    -moz-animation-timing-function: ease-out;\n  }\n\n  7% {\n    opacity: 1;\n    -moz-transform: rotate(300deg);\n    -moz-animation-timing-function: linear;\n    -moz-origin: 0%;\n  }\n\n  30% {\n    opacity: 1;\n    -moz-transform: rotate(410deg);\n    -moz-animation-timing-function: ease-in-out;\n    -moz-origin: 7%;\n  }\n\n  39% {\n    opacity: 1;\n    -moz-transform: rotate(645deg);\n    -moz-animation-timing-function: linear;\n    -moz-origin: 30%;\n  }\n\n  70% {\n    opacity: 1;\n    -moz-transform: rotate(770deg);\n    -moz-animation-timing-function: ease-out;\n    -moz-origin: 39%;\n  }\n\n  75% {\n    opacity: 1;\n    -moz-transform: rotate(900deg);\n    -moz-animation-timing-function: ease-out;\n    -moz-origin: 70%;\n  }\n\n  76% {\n    opacity: 0;\n    -moz-transform: rotate(900deg);\n  }\n\n  100% {\n    opacity: 0;\n    -moz-transform: rotate(900deg);\n  }\n}\n"
  },
  {
    "path": "src/electionguard_gui/web/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\" dir=\"ltr\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <script type=\"text/javascript\" src=\"/eel.js\"></script>\n    <link href=\"css/bootstrap.min.css\" rel=\"stylesheet\" />\n    <link href=\"css/bootstrap-overrides.css\" rel=\"stylesheet\" />\n    <link rel=\"stylesheet\" href=\"css/bootstrap-icons.css\" />\n    <link href=\"css/eg-styles.css\" rel=\"stylesheet\" />\n    <link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"/apple-touch-icon.png\" />\n    <link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/favicon-32x32.png\" />\n    <link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/favicon-16x16.png\" />\n    <link rel=\"manifest\" href=\"/site.webmanifest\" />\n  </head>\n  <body>\n    <script type=\"importmap\">\n      {\n        \"imports\": {\n          \"vue\": \"./js/vue.esm-browser.prod.js\"\n        }\n      }\n    </script>\n\n    <div id=\"app\">\n      <navbar :user-id=\"userId\"></navbar>\n      <div class=\"container mt-3 mb-4\">\n        <Login @login=\"login\" v-if=\"showLogin\"></Login>\n        <component :is=\"currentView\" v-bind=\"currentViewProperties\" />\n      </div>\n      <eg-footer></eg-footer>\n    </div>\n\n    <script type=\"module\">\n      import { createApp } from \"vue\";\n\n      // components\n      import Navbar from \"./components/shared/navbar-component.js\";\n      import Login from \"./components/shared/login-component.js\";\n      import EgFooter from \"./components/shared/footer-component.js\";\n\n      // services\n      import AuthorizationService from \"./services/authorization-service.js\";\n      import RouterService from \"./services/router-service.js\";\n\n      createApp({\n        data() {\n          return {\n            currentPath: window.location.hash,\n            showLogin: false,\n            userId: null,\n          };\n        },\n        computed: {\n          currentView() {\n            if (this.showLogin) return null;\n            const route = this.getRoute(this.currentPath);\n            console.log(\"navigating\", route);\n            return route.component;\n          },\n          currentViewProperties() {\n            const querystringParams = this.currentPath.split(\"?\")[1];\n            const urlSearchParams = new URLSearchParams(querystringParams);\n            const params = Object.fromEntries(urlSearchParams.entries());\n            console.log(\"params\", params);\n            return params;\n          },\n        },\n        methods: {\n          getRoute(path) {\n            return RouterService.getRoute(path);\n          },\n          login(username) {\n            console.log(\"login\", username);\n            this.showLogin = false;\n            this.userId = username;\n          },\n        },\n        async mounted() {\n          window.addEventListener(\"hashchange\", () => {\n            // setting currentPath will trigger the computed properties to update\n            this.currentPath = window.location.hash;\n            const route = this.getRoute(this.currentPath);\n            this.showLogin = route.secured && !this.userId;\n          });\n          this.userId = await AuthorizationService.getUserId();\n          this.showLogin = !this.userId;\n        },\n        components: {\n          Navbar,\n          Login,\n          EgFooter,\n        },\n      }).mount(\"#app\");\n    </script>\n\n    <script src=\"js/popper.min.js\"></script>\n    <script src=\"js/bootstrap.bundle.min.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "src/electionguard_gui/web/js/vue.esm-browser.prod.js",
    "content": "function e(e,t){const n=Object.create(null),o=e.split(\",\");for(let r=0;r<o.length;r++)n[o[r]]=!0;return t?e=>!!n[e.toLowerCase()]:e=>!!n[e]}const t=e(\"Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt\"),n=e(\"itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly\");function o(e){return!!e||\"\"===e}function r(e){if(E(e)){const t={};for(let n=0;n<e.length;n++){const o=e[n],s=P(o)?l(o):r(o);if(s)for(const e in s)t[e]=s[e]}return t}return P(e)||M(e)?e:void 0}const s=/;(?![^(]*\\))/g,i=/:(.+)/;function l(e){const t={};return e.split(s).forEach((e=>{if(e){const n=e.split(i);n.length>1&&(t[n[0].trim()]=n[1].trim())}})),t}function c(e){let t=\"\";if(P(e))t=e;else if(E(e))for(let n=0;n<e.length;n++){const o=c(e[n]);o&&(t+=o+\" \")}else if(M(e))for(const n in e)e[n]&&(t+=n+\" \");return t.trim()}function a(e){if(!e)return null;let{class:t,style:n}=e;return t&&!P(t)&&(e.class=c(t)),n&&(e.style=r(n)),e}const u=e(\"html,body,base,head,link,meta,style,title,address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,output,progress,select,textarea,details,dialog,menu,summary,template,blockquote,iframe,tfoot\"),p=e(\"svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,feDistanceLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,text,textPath,title,tspan,unknown,use,view\"),f=e(\"area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr\");function d(e,t){if(e===t)return!0;let n=R(e),o=R(t);if(n||o)return!(!n||!o)&&e.getTime()===t.getTime();if(n=A(e),o=A(t),n||o)return e===t;if(n=E(e),o=E(t),n||o)return!(!n||!o)&&function(e,t){if(e.length!==t.length)return!1;let n=!0;for(let o=0;n&&o<e.length;o++)n=d(e[o],t[o]);return n}(e,t);if(n=M(e),o=M(t),n||o){if(!n||!o)return!1;if(Object.keys(e).length!==Object.keys(t).length)return!1;for(const n in e){const o=e.hasOwnProperty(n),r=t.hasOwnProperty(n);if(o&&!r||!o&&r||!d(e[n],t[n]))return!1}}return String(e)===String(t)}function h(e,t){return e.findIndex((e=>d(e,t)))}const m=e=>P(e)?e:null==e?\"\":E(e)||M(e)&&(e.toString===I||!F(e.toString))?JSON.stringify(e,g,2):String(e),g=(e,t)=>t&&t.__v_isRef?g(e,t.value):$(t)?{[`Map(${t.size})`]:[...t.entries()].reduce(((e,[t,n])=>(e[`${t} =>`]=n,e)),{})}:O(t)?{[`Set(${t.size})`]:[...t.values()]}:!M(t)||E(t)||L(t)?t:String(t),v={},y=[],_=()=>{},b=()=>!1,S=/^on[^a-z]/,x=e=>S.test(e),C=e=>e.startsWith(\"onUpdate:\"),w=Object.assign,k=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},T=Object.prototype.hasOwnProperty,N=(e,t)=>T.call(e,t),E=Array.isArray,$=e=>\"[object Map]\"===B(e),O=e=>\"[object Set]\"===B(e),R=e=>\"[object Date]\"===B(e),F=e=>\"function\"==typeof e,P=e=>\"string\"==typeof e,A=e=>\"symbol\"==typeof e,M=e=>null!==e&&\"object\"==typeof e,V=e=>M(e)&&F(e.then)&&F(e.catch),I=Object.prototype.toString,B=e=>I.call(e),L=e=>\"[object Object]\"===B(e),j=e=>P(e)&&\"NaN\"!==e&&\"-\"!==e[0]&&\"\"+parseInt(e,10)===e,U=e(\",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted\"),D=e(\"bind,cloak,else-if,else,for,html,if,model,on,once,pre,show,slot,text,memo\"),H=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},W=/-(\\w)/g,z=H((e=>e.replace(W,((e,t)=>t?t.toUpperCase():\"\")))),K=/\\B([A-Z])/g,G=H((e=>e.replace(K,\"-$1\").toLowerCase())),q=H((e=>e.charAt(0).toUpperCase()+e.slice(1))),J=H((e=>e?`on${q(e)}`:\"\")),Y=(e,t)=>!Object.is(e,t),Z=(e,t)=>{for(let n=0;n<e.length;n++)e[n](t)},Q=(e,t,n)=>{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,value:n})},X=e=>{const t=parseFloat(e);return isNaN(t)?e:t};let ee;let te;class ne{constructor(e=!1){this.active=!0,this.effects=[],this.cleanups=[],!e&&te&&(this.parent=te,this.index=(te.scopes||(te.scopes=[])).push(this)-1)}run(e){if(this.active){const t=te;try{return te=this,e()}finally{te=t}}}on(){te=this}off(){te=this.parent}stop(e){if(this.active){let t,n;for(t=0,n=this.effects.length;t<n;t++)this.effects[t].stop();for(t=0,n=this.cleanups.length;t<n;t++)this.cleanups[t]();if(this.scopes)for(t=0,n=this.scopes.length;t<n;t++)this.scopes[t].stop(!0);if(this.parent&&!e){const e=this.parent.scopes.pop();e&&e!==this&&(this.parent.scopes[this.index]=e,e.index=this.index)}this.active=!1}}}function oe(e){return new ne(e)}function re(e,t=te){t&&t.active&&t.effects.push(e)}function se(){return te}function ie(e){te&&te.cleanups.push(e)}const le=e=>{const t=new Set(e);return t.w=0,t.n=0,t},ce=e=>(e.w&fe)>0,ae=e=>(e.n&fe)>0,ue=new WeakMap;let pe=0,fe=1;let de;const he=Symbol(\"\"),me=Symbol(\"\");class ge{constructor(e,t=null,n){this.fn=e,this.scheduler=t,this.active=!0,this.deps=[],this.parent=void 0,re(this,n)}run(){if(!this.active)return this.fn();let e=de,t=be;for(;e;){if(e===this)return;e=e.parent}try{return this.parent=de,de=this,be=!0,fe=1<<++pe,pe<=30?(({deps:e})=>{if(e.length)for(let t=0;t<e.length;t++)e[t].w|=fe})(this):ve(this),this.fn()}finally{pe<=30&&(e=>{const{deps:t}=e;if(t.length){let n=0;for(let o=0;o<t.length;o++){const r=t[o];ce(r)&&!ae(r)?r.delete(e):t[n++]=r,r.w&=~fe,r.n&=~fe}t.length=n}})(this),fe=1<<--pe,de=this.parent,be=t,this.parent=void 0,this.deferStop&&this.stop()}}stop(){de===this?this.deferStop=!0:this.active&&(ve(this),this.onStop&&this.onStop(),this.active=!1)}}function ve(e){const{deps:t}=e;if(t.length){for(let n=0;n<t.length;n++)t[n].delete(e);t.length=0}}function ye(e,t){e.effect&&(e=e.effect.fn);const n=new ge(e);t&&(w(n,t),t.scope&&re(n,t.scope)),t&&t.lazy||n.run();const o=n.run.bind(n);return o.effect=n,o}function _e(e){e.effect.stop()}let be=!0;const Se=[];function xe(){Se.push(be),be=!1}function Ce(){const e=Se.pop();be=void 0===e||e}function we(e,t,n){if(be&&de){let t=ue.get(e);t||ue.set(e,t=new Map);let o=t.get(n);o||t.set(n,o=le()),ke(o)}}function ke(e,t){let n=!1;pe<=30?ae(e)||(e.n|=fe,n=!ce(e)):n=!e.has(de),n&&(e.add(de),de.deps.push(e))}function Te(e,t,n,o,r,s){const i=ue.get(e);if(!i)return;let l=[];if(\"clear\"===t)l=[...i.values()];else if(\"length\"===n&&E(e))i.forEach(((e,t)=>{(\"length\"===t||t>=o)&&l.push(e)}));else switch(void 0!==n&&l.push(i.get(n)),t){case\"add\":E(e)?j(n)&&l.push(i.get(\"length\")):(l.push(i.get(he)),$(e)&&l.push(i.get(me)));break;case\"delete\":E(e)||(l.push(i.get(he)),$(e)&&l.push(i.get(me)));break;case\"set\":$(e)&&l.push(i.get(he))}if(1===l.length)l[0]&&Ne(l[0]);else{const e=[];for(const t of l)t&&e.push(...t);Ne(le(e))}}function Ne(e,t){const n=E(e)?e:[...e];for(const o of n)o.computed&&Ee(o);for(const o of n)o.computed||Ee(o)}function Ee(e,t){(e!==de||e.allowRecurse)&&(e.scheduler?e.scheduler():e.run())}const $e=e(\"__proto__,__v_isRef,__isVue\"),Oe=new Set(Object.getOwnPropertyNames(Symbol).filter((e=>\"arguments\"!==e&&\"caller\"!==e)).map((e=>Symbol[e])).filter(A)),Re=Ie(),Fe=Ie(!1,!0),Pe=Ie(!0),Ae=Ie(!0,!0),Me=Ve();function Ve(){const e={};return[\"includes\",\"indexOf\",\"lastIndexOf\"].forEach((t=>{e[t]=function(...e){const n=kt(this);for(let t=0,r=this.length;t<r;t++)we(n,0,t+\"\");const o=n[t](...e);return-1===o||!1===o?n[t](...e.map(kt)):o}})),[\"push\",\"pop\",\"shift\",\"unshift\",\"splice\"].forEach((t=>{e[t]=function(...e){xe();const n=kt(this)[t].apply(this,e);return Ce(),n}})),e}function Ie(e=!1,t=!1){return function(n,o,r){if(\"__v_isReactive\"===o)return!e;if(\"__v_isReadonly\"===o)return e;if(\"__v_isShallow\"===o)return t;if(\"__v_raw\"===o&&r===(e?t?ht:dt:t?ft:pt).get(n))return n;const s=E(n);if(!e&&s&&N(Me,o))return Reflect.get(Me,o,r);const i=Reflect.get(n,o,r);return(A(o)?Oe.has(o):$e(o))?i:(e||we(n,0,o),t?i:Rt(i)?s&&j(o)?i:i.value:M(i)?e?yt(i):gt(i):i)}}function Be(e=!1){return function(t,n,o,r){let s=t[n];if(xt(s)&&Rt(s)&&!Rt(o))return!1;if(!e&&!xt(o)&&(Ct(o)||(o=kt(o),s=kt(s)),!E(t)&&Rt(s)&&!Rt(o)))return s.value=o,!0;const i=E(t)&&j(n)?Number(n)<t.length:N(t,n),l=Reflect.set(t,n,o,r);return t===kt(r)&&(i?Y(o,s)&&Te(t,\"set\",n,o):Te(t,\"add\",n,o)),l}}const Le={get:Re,set:Be(),deleteProperty:function(e,t){const n=N(e,t),o=Reflect.deleteProperty(e,t);return o&&n&&Te(e,\"delete\",t,void 0),o},has:function(e,t){const n=Reflect.has(e,t);return A(t)&&Oe.has(t)||we(e,0,t),n},ownKeys:function(e){return we(e,0,E(e)?\"length\":he),Reflect.ownKeys(e)}},je={get:Pe,set:(e,t)=>!0,deleteProperty:(e,t)=>!0},Ue=w({},Le,{get:Fe,set:Be(!0)}),De=w({},je,{get:Ae}),He=e=>e,We=e=>Reflect.getPrototypeOf(e);function ze(e,t,n=!1,o=!1){const r=kt(e=e.__v_raw),s=kt(t);n||(t!==s&&we(r,0,t),we(r,0,s));const{has:i}=We(r),l=o?He:n?Et:Nt;return i.call(r,t)?l(e.get(t)):i.call(r,s)?l(e.get(s)):void(e!==r&&e.get(t))}function Ke(e,t=!1){const n=this.__v_raw,o=kt(n),r=kt(e);return t||(e!==r&&we(o,0,e),we(o,0,r)),e===r?n.has(e):n.has(e)||n.has(r)}function Ge(e,t=!1){return e=e.__v_raw,!t&&we(kt(e),0,he),Reflect.get(e,\"size\",e)}function qe(e){e=kt(e);const t=kt(this);return We(t).has.call(t,e)||(t.add(e),Te(t,\"add\",e,e)),this}function Je(e,t){t=kt(t);const n=kt(this),{has:o,get:r}=We(n);let s=o.call(n,e);s||(e=kt(e),s=o.call(n,e));const i=r.call(n,e);return n.set(e,t),s?Y(t,i)&&Te(n,\"set\",e,t):Te(n,\"add\",e,t),this}function Ye(e){const t=kt(this),{has:n,get:o}=We(t);let r=n.call(t,e);r||(e=kt(e),r=n.call(t,e)),o&&o.call(t,e);const s=t.delete(e);return r&&Te(t,\"delete\",e,void 0),s}function Ze(){const e=kt(this),t=0!==e.size,n=e.clear();return t&&Te(e,\"clear\",void 0,void 0),n}function Qe(e,t){return function(n,o){const r=this,s=r.__v_raw,i=kt(s),l=t?He:e?Et:Nt;return!e&&we(i,0,he),s.forEach(((e,t)=>n.call(o,l(e),l(t),r)))}}function Xe(e,t,n){return function(...o){const r=this.__v_raw,s=kt(r),i=$(s),l=\"entries\"===e||e===Symbol.iterator&&i,c=\"keys\"===e&&i,a=r[e](...o),u=n?He:t?Et:Nt;return!t&&we(s,0,c?me:he),{next(){const{value:e,done:t}=a.next();return t?{value:e,done:t}:{value:l?[u(e[0]),u(e[1])]:u(e),done:t}},[Symbol.iterator](){return this}}}}function et(e){return function(...t){return\"delete\"!==e&&this}}function tt(){const e={get(e){return ze(this,e)},get size(){return Ge(this)},has:Ke,add:qe,set:Je,delete:Ye,clear:Ze,forEach:Qe(!1,!1)},t={get(e){return ze(this,e,!1,!0)},get size(){return Ge(this)},has:Ke,add:qe,set:Je,delete:Ye,clear:Ze,forEach:Qe(!1,!0)},n={get(e){return ze(this,e,!0)},get size(){return Ge(this,!0)},has(e){return Ke.call(this,e,!0)},add:et(\"add\"),set:et(\"set\"),delete:et(\"delete\"),clear:et(\"clear\"),forEach:Qe(!0,!1)},o={get(e){return ze(this,e,!0,!0)},get size(){return Ge(this,!0)},has(e){return Ke.call(this,e,!0)},add:et(\"add\"),set:et(\"set\"),delete:et(\"delete\"),clear:et(\"clear\"),forEach:Qe(!0,!0)};return[\"keys\",\"values\",\"entries\",Symbol.iterator].forEach((r=>{e[r]=Xe(r,!1,!1),n[r]=Xe(r,!0,!1),t[r]=Xe(r,!1,!0),o[r]=Xe(r,!0,!0)})),[e,n,t,o]}const[nt,ot,rt,st]=tt();function it(e,t){const n=t?e?st:rt:e?ot:nt;return(t,o,r)=>\"__v_isReactive\"===o?!e:\"__v_isReadonly\"===o?e:\"__v_raw\"===o?t:Reflect.get(N(n,o)&&o in t?n:t,o,r)}const lt={get:it(!1,!1)},ct={get:it(!1,!0)},at={get:it(!0,!1)},ut={get:it(!0,!0)},pt=new WeakMap,ft=new WeakMap,dt=new WeakMap,ht=new WeakMap;function mt(e){return e.__v_skip||!Object.isExtensible(e)?0:function(e){switch(e){case\"Object\":case\"Array\":return 1;case\"Map\":case\"Set\":case\"WeakMap\":case\"WeakSet\":return 2;default:return 0}}((e=>B(e).slice(8,-1))(e))}function gt(e){return xt(e)?e:bt(e,!1,Le,lt,pt)}function vt(e){return bt(e,!1,Ue,ct,ft)}function yt(e){return bt(e,!0,je,at,dt)}function _t(e){return bt(e,!0,De,ut,ht)}function bt(e,t,n,o,r){if(!M(e))return e;if(e.__v_raw&&(!t||!e.__v_isReactive))return e;const s=r.get(e);if(s)return s;const i=mt(e);if(0===i)return e;const l=new Proxy(e,2===i?o:n);return r.set(e,l),l}function St(e){return xt(e)?St(e.__v_raw):!(!e||!e.__v_isReactive)}function xt(e){return!(!e||!e.__v_isReadonly)}function Ct(e){return!(!e||!e.__v_isShallow)}function wt(e){return St(e)||xt(e)}function kt(e){const t=e&&e.__v_raw;return t?kt(t):e}function Tt(e){return Q(e,\"__v_skip\",!0),e}const Nt=e=>M(e)?gt(e):e,Et=e=>M(e)?yt(e):e;function $t(e){be&&de&&ke((e=kt(e)).dep||(e.dep=le()))}function Ot(e,t){(e=kt(e)).dep&&Ne(e.dep)}function Rt(e){return!(!e||!0!==e.__v_isRef)}function Ft(e){return At(e,!1)}function Pt(e){return At(e,!0)}function At(e,t){return Rt(e)?e:new Mt(e,t)}class Mt{constructor(e,t){this.__v_isShallow=t,this.dep=void 0,this.__v_isRef=!0,this._rawValue=t?e:kt(e),this._value=t?e:Nt(e)}get value(){return $t(this),this._value}set value(e){e=this.__v_isShallow?e:kt(e),Y(e,this._rawValue)&&(this._rawValue=e,this._value=this.__v_isShallow?e:Nt(e),Ot(this))}}function Vt(e){Ot(e)}function It(e){return Rt(e)?e.value:e}const Bt={get:(e,t,n)=>It(Reflect.get(e,t,n)),set:(e,t,n,o)=>{const r=e[t];return Rt(r)&&!Rt(n)?(r.value=n,!0):Reflect.set(e,t,n,o)}};function Lt(e){return St(e)?e:new Proxy(e,Bt)}class jt{constructor(e){this.dep=void 0,this.__v_isRef=!0;const{get:t,set:n}=e((()=>$t(this)),(()=>Ot(this)));this._get=t,this._set=n}get value(){return this._get()}set value(e){this._set(e)}}function Ut(e){return new jt(e)}function Dt(e){const t=E(e)?new Array(e.length):{};for(const n in e)t[n]=Wt(e,n);return t}class Ht{constructor(e,t,n){this._object=e,this._key=t,this._defaultValue=n,this.__v_isRef=!0}get value(){const e=this._object[this._key];return void 0===e?this._defaultValue:e}set value(e){this._object[this._key]=e}}function Wt(e,t,n){const o=e[t];return Rt(o)?o:new Ht(e,t,n)}class zt{constructor(e,t,n,o){this._setter=t,this.dep=void 0,this.__v_isRef=!0,this._dirty=!0,this.effect=new ge(e,(()=>{this._dirty||(this._dirty=!0,Ot(this))})),this.effect.computed=this,this.effect.active=this._cacheable=!o,this.__v_isReadonly=n}get value(){const e=kt(this);return $t(e),!e._dirty&&e._cacheable||(e._dirty=!1,e._value=e.effect.run()),e._value}set value(e){this._setter(e)}}const Kt=[];function Gt(e,...t){xe();const n=Kt.length?Kt[Kt.length-1].component:null,o=n&&n.appContext.config.warnHandler,r=function(){let e=Kt[Kt.length-1];if(!e)return[];const t=[];for(;e;){const n=t[0];n&&n.vnode===e?n.recurseCount++:t.push({vnode:e,recurseCount:0});const o=e.component&&e.component.parent;e=o&&o.vnode}return t}();if(o)Yt(o,n,11,[e+t.join(\"\"),n&&n.proxy,r.map((({vnode:e})=>`at <${Ls(n,e.type)}>`)).join(\"\\n\"),r]);else{const n=[`[Vue warn]: ${e}`,...t];r.length&&n.push(\"\\n\",...function(e){const t=[];return e.forEach(((e,n)=>{t.push(...0===n?[]:[\"\\n\"],...function({vnode:e,recurseCount:t}){const n=t>0?`... (${t} recursive calls)`:\"\",o=` at <${Ls(e.component,e.type,!!e.component&&null==e.component.parent)}`,r=\">\"+n;return e.props?[o,...qt(e.props),r]:[o+r]}(e))})),t}(r)),console.warn(...n)}Ce()}function qt(e){const t=[],n=Object.keys(e);return n.slice(0,3).forEach((n=>{t.push(...Jt(n,e[n]))})),n.length>3&&t.push(\" ...\"),t}function Jt(e,t,n){return P(t)?(t=JSON.stringify(t),n?t:[`${e}=${t}`]):\"number\"==typeof t||\"boolean\"==typeof t||null==t?n?t:[`${e}=${t}`]:Rt(t)?(t=Jt(e,kt(t.value),!0),n?t:[`${e}=Ref<`,t,\">\"]):F(t)?[`${e}=fn${t.name?`<${t.name}>`:\"\"}`]:(t=kt(t),n?t:[`${e}=`,t])}function Yt(e,t,n,o){let r;try{r=o?e(...o):e()}catch(s){Qt(s,t,n)}return r}function Zt(e,t,n,o){if(F(e)){const r=Yt(e,t,n,o);return r&&V(r)&&r.catch((e=>{Qt(e,t,n)})),r}const r=[];for(let s=0;s<e.length;s++)r.push(Zt(e[s],t,n,o));return r}function Qt(e,t,n,o=!0){if(t){let o=t.parent;const r=t.proxy,s=n;for(;o;){const t=o.ec;if(t)for(let n=0;n<t.length;n++)if(!1===t[n](e,r,s))return;o=o.parent}const i=t.appContext.config.errorHandler;if(i)return void Yt(i,null,10,[e,r,s])}!function(e,t,n,o=!0){console.error(e)}(e,0,0,o)}let Xt=!1,en=!1;const tn=[];let nn=0;const on=[];let rn=null,sn=0;const ln=[];let cn=null,an=0;const un=Promise.resolve();let pn=null,fn=null;function dn(e){const t=pn||un;return e?t.then(this?e.bind(this):e):t}function hn(e){tn.length&&tn.includes(e,Xt&&e.allowRecurse?nn+1:nn)||e===fn||(null==e.id?tn.push(e):tn.splice(function(e){let t=nn+1,n=tn.length;for(;t<n;){const o=t+n>>>1;bn(tn[o])<e?t=o+1:n=o}return t}(e.id),0,e),mn())}function mn(){Xt||en||(en=!0,pn=un.then(Sn))}function gn(e,t,n,o){E(e)?n.push(...e):t&&t.includes(e,e.allowRecurse?o+1:o)||n.push(e),mn()}function vn(e){gn(e,cn,ln,an)}function yn(e,t=null){if(on.length){for(fn=t,rn=[...new Set(on)],on.length=0,sn=0;sn<rn.length;sn++)rn[sn]();rn=null,sn=0,fn=null,yn(e,t)}}function _n(e){if(yn(),ln.length){const e=[...new Set(ln)];if(ln.length=0,cn)return void cn.push(...e);for(cn=e,cn.sort(((e,t)=>bn(e)-bn(t))),an=0;an<cn.length;an++)cn[an]();cn=null,an=0}}const bn=e=>null==e.id?1/0:e.id;function Sn(e){en=!1,Xt=!0,yn(e),tn.sort(((e,t)=>bn(e)-bn(t)));try{for(nn=0;nn<tn.length;nn++){const e=tn[nn];e&&!1!==e.active&&Yt(e,null,14)}}finally{nn=0,tn.length=0,_n(),Xt=!1,pn=null,(tn.length||on.length||ln.length)&&Sn(e)}}let xn,Cn=[];function wn(e,t){var n,o;if(xn=e,xn)xn.enabled=!0,Cn.forEach((({event:e,args:t})=>xn.emit(e,...t))),Cn=[];else if(\"undefined\"!=typeof window&&window.HTMLElement&&!(null===(o=null===(n=window.navigator)||void 0===n?void 0:n.userAgent)||void 0===o?void 0:o.includes(\"jsdom\"))){(t.__VUE_DEVTOOLS_HOOK_REPLAY__=t.__VUE_DEVTOOLS_HOOK_REPLAY__||[]).push((e=>{wn(e,t)})),setTimeout((()=>{xn||(t.__VUE_DEVTOOLS_HOOK_REPLAY__=null,Cn=[])}),3e3)}else Cn=[]}function kn(e,t,...n){if(e.isUnmounted)return;const o=e.vnode.props||v;let r=n;const s=t.startsWith(\"update:\"),i=s&&t.slice(7);if(i&&i in o){const e=`${\"modelValue\"===i?\"model\":i}Modifiers`,{number:t,trim:s}=o[e]||v;s&&(r=n.map((e=>e.trim()))),t&&(r=n.map(X))}let l,c=o[l=J(t)]||o[l=J(z(t))];!c&&s&&(c=o[l=J(G(t))]),c&&Zt(c,e,6,r);const a=o[l+\"Once\"];if(a){if(e.emitted){if(e.emitted[l])return}else e.emitted={};e.emitted[l]=!0,Zt(a,e,6,r)}}function Tn(e,t,n=!1){const o=t.emitsCache,r=o.get(e);if(void 0!==r)return r;const s=e.emits;let i={},l=!1;if(!F(e)){const o=e=>{const n=Tn(e,t,!0);n&&(l=!0,w(i,n))};!n&&t.mixins.length&&t.mixins.forEach(o),e.extends&&o(e.extends),e.mixins&&e.mixins.forEach(o)}return s||l?(E(s)?s.forEach((e=>i[e]=null)):w(i,s),o.set(e,i),i):(o.set(e,null),null)}function Nn(e,t){return!(!e||!x(t))&&(t=t.slice(2).replace(/Once$/,\"\"),N(e,t[0].toLowerCase()+t.slice(1))||N(e,G(t))||N(e,t))}let En=null,$n=null;function On(e){const t=En;return En=e,$n=e&&e.type.__scopeId||null,t}function Rn(e){$n=e}function Fn(){$n=null}const Pn=e=>An;function An(e,t=En,n){if(!t)return e;if(e._n)return e;const o=(...n)=>{o._d&&Xr(-1);const r=On(t),s=e(...n);return On(r),o._d&&Xr(1),s};return o._n=!0,o._c=!0,o._d=!0,o}function Mn(e){const{type:t,vnode:n,proxy:o,withProxy:r,props:s,propsOptions:[i],slots:l,attrs:c,emit:a,render:u,renderCache:p,data:f,setupState:d,ctx:h,inheritAttrs:m}=e;let g,v;const y=On(e);try{if(4&n.shapeFlag){const e=r||o;g=gs(u.call(e,e,p,s,d,f,h)),v=c}else{const e=t;0,g=gs(e(s,e.length>1?{attrs:c,slots:l,emit:a}:null)),v=t.props?c:Vn(c)}}catch(b){qr.length=0,Qt(b,e,1),g=us(Kr)}let _=g;if(v&&!1!==m){const e=Object.keys(v),{shapeFlag:t}=_;e.length&&7&t&&(i&&e.some(C)&&(v=In(v,i)),_=fs(_,v))}return n.dirs&&(_=fs(_),_.dirs=_.dirs?_.dirs.concat(n.dirs):n.dirs),n.transition&&(_.transition=n.transition),g=_,On(y),g}const Vn=e=>{let t;for(const n in e)(\"class\"===n||\"style\"===n||x(n))&&((t||(t={}))[n]=e[n]);return t},In=(e,t)=>{const n={};for(const o in e)C(o)&&o.slice(9)in t||(n[o]=e[o]);return n};function Bn(e,t,n){const o=Object.keys(t);if(o.length!==Object.keys(e).length)return!0;for(let r=0;r<o.length;r++){const s=o[r];if(t[s]!==e[s]&&!Nn(n,s))return!0}return!1}function Ln({vnode:e,parent:t},n){for(;t&&t.subTree===e;)(e=t.vnode).el=n,t=t.parent}const jn=e=>e.__isSuspense,Un={name:\"Suspense\",__isSuspense:!0,process(e,t,n,o,r,s,i,l,c,a){null==e?function(e,t,n,o,r,s,i,l,c){const{p:a,o:{createElement:u}}=c,p=u(\"div\"),f=e.suspense=Hn(e,r,o,t,p,n,s,i,l,c);a(null,f.pendingBranch=e.ssContent,p,null,o,f,s,i),f.deps>0?(Dn(e,\"onPending\"),Dn(e,\"onFallback\"),a(null,e.ssFallback,t,n,o,null,s,i),Kn(f,e.ssFallback)):f.resolve()}(t,n,o,r,s,i,l,c,a):function(e,t,n,o,r,s,i,l,{p:c,um:a,o:{createElement:u}}){const p=t.suspense=e.suspense;p.vnode=t,t.el=e.el;const f=t.ssContent,d=t.ssFallback,{activeBranch:h,pendingBranch:m,isInFallback:g,isHydrating:v}=p;if(m)p.pendingBranch=f,rs(f,m)?(c(m,f,p.hiddenContainer,null,r,p,s,i,l),p.deps<=0?p.resolve():g&&(c(h,d,n,o,r,null,s,i,l),Kn(p,d))):(p.pendingId++,v?(p.isHydrating=!1,p.activeBranch=m):a(m,r,p),p.deps=0,p.effects.length=0,p.hiddenContainer=u(\"div\"),g?(c(null,f,p.hiddenContainer,null,r,p,s,i,l),p.deps<=0?p.resolve():(c(h,d,n,o,r,null,s,i,l),Kn(p,d))):h&&rs(f,h)?(c(h,f,n,o,r,p,s,i,l),p.resolve(!0)):(c(null,f,p.hiddenContainer,null,r,p,s,i,l),p.deps<=0&&p.resolve()));else if(h&&rs(f,h))c(h,f,n,o,r,p,s,i,l),Kn(p,f);else if(Dn(t,\"onPending\"),p.pendingBranch=f,p.pendingId++,c(null,f,p.hiddenContainer,null,r,p,s,i,l),p.deps<=0)p.resolve();else{const{timeout:e,pendingId:t}=p;e>0?setTimeout((()=>{p.pendingId===t&&p.fallback(d)}),e):0===e&&p.fallback(d)}}(e,t,n,o,r,i,l,c,a)},hydrate:function(e,t,n,o,r,s,i,l,c){const a=t.suspense=Hn(t,o,n,e.parentNode,document.createElement(\"div\"),null,r,s,i,l,!0),u=c(e,a.pendingBranch=t.ssContent,n,a,s,i);0===a.deps&&a.resolve();return u},create:Hn,normalize:function(e){const{shapeFlag:t,children:n}=e,o=32&t;e.ssContent=Wn(o?n.default:n),e.ssFallback=o?Wn(n.fallback):us(Kr)}};function Dn(e,t){const n=e.props&&e.props[t];F(n)&&n()}function Hn(e,t,n,o,r,s,i,l,c,a,u=!1){const{p:p,m:f,um:d,n:h,o:{parentNode:m,remove:g}}=a,v=X(e.props&&e.props.timeout),y={vnode:e,parent:t,parentComponent:n,isSVG:i,container:o,hiddenContainer:r,anchor:s,deps:0,pendingId:0,timeout:\"number\"==typeof v?v:-1,activeBranch:null,pendingBranch:null,isInFallback:!0,isHydrating:u,isUnmounted:!1,effects:[],resolve(e=!1){const{vnode:t,activeBranch:n,pendingBranch:o,pendingId:r,effects:s,parentComponent:i,container:l}=y;if(y.isHydrating)y.isHydrating=!1;else if(!e){const e=n&&o.transition&&\"out-in\"===o.transition.mode;e&&(n.transition.afterLeave=()=>{r===y.pendingId&&f(o,l,t,0)});let{anchor:t}=y;n&&(t=h(n),d(n,i,y,!0)),e||f(o,l,t,0)}Kn(y,o),y.pendingBranch=null,y.isInFallback=!1;let c=y.parent,a=!1;for(;c;){if(c.pendingBranch){c.effects.push(...s),a=!0;break}c=c.parent}a||vn(s),y.effects=[],Dn(t,\"onResolve\")},fallback(e){if(!y.pendingBranch)return;const{vnode:t,activeBranch:n,parentComponent:o,container:r,isSVG:s}=y;Dn(t,\"onFallback\");const i=h(n),a=()=>{y.isInFallback&&(p(null,e,r,i,o,null,s,l,c),Kn(y,e))},u=e.transition&&\"out-in\"===e.transition.mode;u&&(n.transition.afterLeave=a),y.isInFallback=!0,d(n,o,null,!0),u||a()},move(e,t,n){y.activeBranch&&f(y.activeBranch,e,t,n),y.container=e},next:()=>y.activeBranch&&h(y.activeBranch),registerDep(e,t){const n=!!y.pendingBranch;n&&y.deps++;const o=e.vnode.el;e.asyncDep.catch((t=>{Qt(t,e,0)})).then((r=>{if(e.isUnmounted||y.isUnmounted||y.pendingId!==e.suspenseId)return;e.asyncResolved=!0;const{vnode:s}=e;Rs(e,r,!1),o&&(s.el=o);const l=!o&&e.subTree.el;t(e,s,m(o||e.subTree.el),o?null:h(e.subTree),y,i,c),l&&g(l),Ln(e,s.el),n&&0==--y.deps&&y.resolve()}))},unmount(e,t){y.isUnmounted=!0,y.activeBranch&&d(y.activeBranch,n,e,t),y.pendingBranch&&d(y.pendingBranch,n,e,t)}};return y}function Wn(e){let t;if(F(e)){const n=Qr&&e._c;n&&(e._d=!1,Yr()),e=e(),n&&(e._d=!0,t=Jr,Zr())}if(E(e)){const t=function(e){let t;for(let n=0;n<e.length;n++){const o=e[n];if(!os(o))return;if(o.type!==Kr||\"v-if\"===o.children){if(t)return;t=o}}return t}(e);e=t}return e=gs(e),t&&!e.dynamicChildren&&(e.dynamicChildren=t.filter((t=>t!==e))),e}function zn(e,t){t&&t.pendingBranch?E(e)?t.effects.push(...e):t.effects.push(e):vn(e)}function Kn(e,t){e.activeBranch=t;const{vnode:n,parentComponent:o}=e,r=n.el=t.el;o&&o.subTree===n&&(o.vnode.el=r,Ln(o,r))}function Gn(e,t){if(Cs){let n=Cs.provides;const o=Cs.parent&&Cs.parent.provides;o===n&&(n=Cs.provides=Object.create(o)),n[e]=t}else;}function qn(e,t,n=!1){const o=Cs||En;if(o){const r=null==o.parent?o.vnode.appContext&&o.vnode.appContext.provides:o.parent.provides;if(r&&e in r)return r[e];if(arguments.length>1)return n&&F(t)?t.call(o.proxy):t}}function Jn(e,t){return eo(e,null,t)}function Yn(e,t){return eo(e,null,{flush:\"post\"})}function Zn(e,t){return eo(e,null,{flush:\"sync\"})}const Qn={};function Xn(e,t,n){return eo(e,t,n)}function eo(e,t,{immediate:n,deep:o,flush:r}=v){const s=Cs;let i,l,c=!1,a=!1;if(Rt(e)?(i=()=>e.value,c=Ct(e)):St(e)?(i=()=>e,o=!0):E(e)?(a=!0,c=e.some((e=>St(e)||Ct(e))),i=()=>e.map((e=>Rt(e)?e.value:St(e)?oo(e):F(e)?Yt(e,s,2):void 0))):i=F(e)?t?()=>Yt(e,s,2):()=>{if(!s||!s.isUnmounted)return l&&l(),Zt(e,s,3,[u])}:_,t&&o){const e=i;i=()=>oo(e())}let u=e=>{l=h.onStop=()=>{Yt(e,s,4)}},p=a?[]:Qn;const f=()=>{if(h.active)if(t){const e=h.run();(o||c||(a?e.some(((e,t)=>Y(e,p[t]))):Y(e,p)))&&(l&&l(),Zt(t,s,3,[e,p===Qn?void 0:p,u]),p=e)}else h.run()};let d;f.allowRecurse=!!t,d=\"sync\"===r?f:\"post\"===r?()=>Pr(f,s&&s.suspense):()=>function(e){gn(e,rn,on,sn)}(f);const h=new ge(i,d);return t?n?f():p=h.run():\"post\"===r?Pr(h.run.bind(h),s&&s.suspense):h.run(),()=>{h.stop(),s&&s.scope&&k(s.scope.effects,h)}}function to(e,t,n){const o=this.proxy,r=P(e)?e.includes(\".\")?no(o,e):()=>o[e]:e.bind(o,o);let s;F(t)?s=t:(s=t.handler,n=t);const i=Cs;ks(this);const l=eo(r,s.bind(o),n);return i?ks(i):Ts(),l}function no(e,t){const n=t.split(\".\");return()=>{let t=e;for(let e=0;e<n.length&&t;e++)t=t[n[e]];return t}}function oo(e,t){if(!M(e)||e.__v_skip)return e;if((t=t||new Set).has(e))return e;if(t.add(e),Rt(e))oo(e.value,t);else if(E(e))for(let n=0;n<e.length;n++)oo(e[n],t);else if(O(e)||$(e))e.forEach((e=>{oo(e,t)}));else if(L(e))for(const n in e)oo(e[n],t);return e}function ro(){const e={isMounted:!1,isLeaving:!1,isUnmounting:!1,leavingVNodes:new Map};return Oo((()=>{e.isMounted=!0})),Po((()=>{e.isUnmounting=!0})),e}const so=[Function,Array],io={name:\"BaseTransition\",props:{mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:so,onEnter:so,onAfterEnter:so,onEnterCancelled:so,onBeforeLeave:so,onLeave:so,onAfterLeave:so,onLeaveCancelled:so,onBeforeAppear:so,onAppear:so,onAfterAppear:so,onAppearCancelled:so},setup(e,{slots:t}){const n=ws(),o=ro();let r;return()=>{const s=t.default&&fo(t.default(),!0);if(!s||!s.length)return;let i=s[0];if(s.length>1)for(const e of s)if(e.type!==Kr){i=e;break}const l=kt(e),{mode:c}=l;if(o.isLeaving)return ao(i);const a=uo(i);if(!a)return ao(i);const u=co(a,l,o,n);po(a,u);const p=n.subTree,f=p&&uo(p);let d=!1;const{getTransitionKey:h}=a.type;if(h){const e=h();void 0===r?r=e:e!==r&&(r=e,d=!0)}if(f&&f.type!==Kr&&(!rs(a,f)||d)){const e=co(f,l,o,n);if(po(f,e),\"out-in\"===c)return o.isLeaving=!0,e.afterLeave=()=>{o.isLeaving=!1,n.update()},ao(i);\"in-out\"===c&&a.type!==Kr&&(e.delayLeave=(e,t,n)=>{lo(o,f)[String(f.key)]=f,e._leaveCb=()=>{t(),e._leaveCb=void 0,delete u.delayedLeave},u.delayedLeave=n})}return i}}};function lo(e,t){const{leavingVNodes:n}=e;let o=n.get(t.type);return o||(o=Object.create(null),n.set(t.type,o)),o}function co(e,t,n,o){const{appear:r,mode:s,persisted:i=!1,onBeforeEnter:l,onEnter:c,onAfterEnter:a,onEnterCancelled:u,onBeforeLeave:p,onLeave:f,onAfterLeave:d,onLeaveCancelled:h,onBeforeAppear:m,onAppear:g,onAfterAppear:v,onAppearCancelled:y}=t,_=String(e.key),b=lo(n,e),S=(e,t)=>{e&&Zt(e,o,9,t)},x=(e,t)=>{const n=t[1];S(e,t),E(e)?e.every((e=>e.length<=1))&&n():e.length<=1&&n()},C={mode:s,persisted:i,beforeEnter(t){let o=l;if(!n.isMounted){if(!r)return;o=m||l}t._leaveCb&&t._leaveCb(!0);const s=b[_];s&&rs(e,s)&&s.el._leaveCb&&s.el._leaveCb(),S(o,[t])},enter(e){let t=c,o=a,s=u;if(!n.isMounted){if(!r)return;t=g||c,o=v||a,s=y||u}let i=!1;const l=e._enterCb=t=>{i||(i=!0,S(t?s:o,[e]),C.delayedLeave&&C.delayedLeave(),e._enterCb=void 0)};t?x(t,[e,l]):l()},leave(t,o){const r=String(e.key);if(t._enterCb&&t._enterCb(!0),n.isUnmounting)return o();S(p,[t]);let s=!1;const i=t._leaveCb=n=>{s||(s=!0,o(),S(n?h:d,[t]),t._leaveCb=void 0,b[r]===e&&delete b[r])};b[r]=e,f?x(f,[t,i]):i()},clone:e=>co(e,t,n,o)};return C}function ao(e){if(yo(e))return(e=fs(e)).children=null,e}function uo(e){return yo(e)?e.children?e.children[0]:void 0:e}function po(e,t){6&e.shapeFlag&&e.component?po(e.component.subTree,t):128&e.shapeFlag?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function fo(e,t=!1,n){let o=[],r=0;for(let s=0;s<e.length;s++){let i=e[s];const l=null==n?i.key:String(n)+String(null!=i.key?i.key:s);i.type===Wr?(128&i.patchFlag&&r++,o=o.concat(fo(i.children,t,l))):(t||i.type!==Kr)&&o.push(null!=l?fs(i,{key:l}):i)}if(r>1)for(let s=0;s<o.length;s++)o[s].patchFlag=-2;return o}function ho(e){return F(e)?{setup:e,name:e.name}:e}const mo=e=>!!e.type.__asyncLoader;function go(e){F(e)&&(e={loader:e});const{loader:t,loadingComponent:n,errorComponent:o,delay:r=200,timeout:s,suspensible:i=!0,onError:l}=e;let c,a=null,u=0;const p=()=>{let e;return a||(e=a=t().catch((e=>{if(e=e instanceof Error?e:new Error(String(e)),l)return new Promise(((t,n)=>{l(e,(()=>t((u++,a=null,p()))),(()=>n(e)),u+1)}));throw e})).then((t=>e!==a&&a?a:(t&&(t.__esModule||\"Module\"===t[Symbol.toStringTag])&&(t=t.default),c=t,t))))};return ho({name:\"AsyncComponentWrapper\",__asyncLoader:p,get __asyncResolved(){return c},setup(){const e=Cs;if(c)return()=>vo(c,e);const t=t=>{a=null,Qt(t,e,13,!o)};if(i&&e.suspense)return p().then((t=>()=>vo(t,e))).catch((e=>(t(e),()=>o?us(o,{error:e}):null)));const l=Ft(!1),u=Ft(),f=Ft(!!r);return r&&setTimeout((()=>{f.value=!1}),r),null!=s&&setTimeout((()=>{if(!l.value&&!u.value){const e=new Error(`Async component timed out after ${s}ms.`);t(e),u.value=e}}),s),p().then((()=>{l.value=!0,e.parent&&yo(e.parent.vnode)&&hn(e.parent.update)})).catch((e=>{t(e),u.value=e})),()=>l.value&&c?vo(c,e):u.value&&o?us(o,{error:u.value}):n&&!f.value?us(n):void 0}})}function vo(e,{vnode:{ref:t,props:n,children:o}}){const r=us(e,n,o);return r.ref=t,r}const yo=e=>e.type.__isKeepAlive,_o={name:\"KeepAlive\",__isKeepAlive:!0,props:{include:[String,RegExp,Array],exclude:[String,RegExp,Array],max:[String,Number]},setup(e,{slots:t}){const n=ws(),o=n.ctx,r=new Map,s=new Set;let i=null;const l=n.suspense,{renderer:{p:c,m:a,um:u,o:{createElement:p}}}=o,f=p(\"div\");function d(e){ko(e),u(e,n,l,!0)}function h(e){r.forEach(((t,n)=>{const o=Bs(t.type);!o||e&&e(o)||m(n)}))}function m(e){const t=r.get(e);i&&t.type===i.type?i&&ko(i):d(t),r.delete(e),s.delete(e)}o.activate=(e,t,n,o,r)=>{const s=e.component;a(e,t,n,0,l),c(s.vnode,e,t,n,s,l,o,e.slotScopeIds,r),Pr((()=>{s.isDeactivated=!1,s.a&&Z(s.a);const t=e.props&&e.props.onVnodeMounted;t&&bs(t,s.parent,e)}),l)},o.deactivate=e=>{const t=e.component;a(e,f,null,1,l),Pr((()=>{t.da&&Z(t.da);const n=e.props&&e.props.onVnodeUnmounted;n&&bs(n,t.parent,e),t.isDeactivated=!0}),l)},Xn((()=>[e.include,e.exclude]),(([e,t])=>{e&&h((t=>bo(e,t))),t&&h((e=>!bo(t,e)))}),{flush:\"post\",deep:!0});let g=null;const v=()=>{null!=g&&r.set(g,To(n.subTree))};return Oo(v),Fo(v),Po((()=>{r.forEach((e=>{const{subTree:t,suspense:o}=n,r=To(t);if(e.type!==r.type)d(e);else{ko(r);const e=r.component.da;e&&Pr(e,o)}}))})),()=>{if(g=null,!t.default)return null;const n=t.default(),o=n[0];if(n.length>1)return i=null,n;if(!(os(o)&&(4&o.shapeFlag||128&o.shapeFlag)))return i=null,o;let l=To(o);const c=l.type,a=Bs(mo(l)?l.type.__asyncResolved||{}:c),{include:u,exclude:p,max:f}=e;if(u&&(!a||!bo(u,a))||p&&a&&bo(p,a))return i=l,o;const d=null==l.key?c:l.key,h=r.get(d);return l.el&&(l=fs(l),128&o.shapeFlag&&(o.ssContent=l)),g=d,h?(l.el=h.el,l.component=h.component,l.transition&&po(l,l.transition),l.shapeFlag|=512,s.delete(d),s.add(d)):(s.add(d),f&&s.size>parseInt(f,10)&&m(s.values().next().value)),l.shapeFlag|=256,i=l,jn(o.type)?o:l}}};function bo(e,t){return E(e)?e.some((e=>bo(e,t))):P(e)?e.split(\",\").includes(t):!!e.test&&e.test(t)}function So(e,t){Co(e,\"a\",t)}function xo(e,t){Co(e,\"da\",t)}function Co(e,t,n=Cs){const o=e.__wdc||(e.__wdc=()=>{let t=n;for(;t;){if(t.isDeactivated)return;t=t.parent}return e()});if(No(t,o,n),n){let e=n.parent;for(;e&&e.parent;)yo(e.parent.vnode)&&wo(o,t,n,e),e=e.parent}}function wo(e,t,n,o){const r=No(t,e,o,!0);Ao((()=>{k(o[t],r)}),n)}function ko(e){let t=e.shapeFlag;256&t&&(t-=256),512&t&&(t-=512),e.shapeFlag=t}function To(e){return 128&e.shapeFlag?e.ssContent:e}function No(e,t,n=Cs,o=!1){if(n){const r=n[e]||(n[e]=[]),s=t.__weh||(t.__weh=(...o)=>{if(n.isUnmounted)return;xe(),ks(n);const r=Zt(t,n,e,o);return Ts(),Ce(),r});return o?r.unshift(s):r.push(s),s}}const Eo=e=>(t,n=Cs)=>(!Os||\"sp\"===e)&&No(e,t,n),$o=Eo(\"bm\"),Oo=Eo(\"m\"),Ro=Eo(\"bu\"),Fo=Eo(\"u\"),Po=Eo(\"bum\"),Ao=Eo(\"um\"),Mo=Eo(\"sp\"),Vo=Eo(\"rtg\"),Io=Eo(\"rtc\");function Bo(e,t=Cs){No(\"ec\",e,t)}function Lo(e,t){const n=En;if(null===n)return e;const o=Vs(n)||n.proxy,r=e.dirs||(e.dirs=[]);for(let s=0;s<t.length;s++){let[e,n,i,l=v]=t[s];F(e)&&(e={mounted:e,updated:e}),e.deep&&oo(n),r.push({dir:e,instance:o,value:n,oldValue:void 0,arg:i,modifiers:l})}return e}function jo(e,t,n,o){const r=e.dirs,s=t&&t.dirs;for(let i=0;i<r.length;i++){const l=r[i];s&&(l.oldValue=s[i].value);let c=l.dir[o];c&&(xe(),Zt(c,n,8,[e.el,l,e,t]),Ce())}}function Uo(e,t){return zo(\"components\",e,!0,t)||e}const Do=Symbol();function Ho(e){return P(e)?zo(\"components\",e,!1)||e:e||Do}function Wo(e){return zo(\"directives\",e)}function zo(e,t,n=!0,o=!1){const r=En||Cs;if(r){const n=r.type;if(\"components\"===e){const e=Bs(n,!1);if(e&&(e===t||e===z(t)||e===q(z(t))))return n}const s=Ko(r[e]||n[e],t)||Ko(r.appContext[e],t);return!s&&o?n:s}}function Ko(e,t){return e&&(e[t]||e[z(t)]||e[q(z(t))])}function Go(e,t,n,o){let r;const s=n&&n[o];if(E(e)||P(e)){r=new Array(e.length);for(let n=0,o=e.length;n<o;n++)r[n]=t(e[n],n,void 0,s&&s[n])}else if(\"number\"==typeof e){r=new Array(e);for(let n=0;n<e;n++)r[n]=t(n+1,n,void 0,s&&s[n])}else if(M(e))if(e[Symbol.iterator])r=Array.from(e,((e,n)=>t(e,n,void 0,s&&s[n])));else{const n=Object.keys(e);r=new Array(n.length);for(let o=0,i=n.length;o<i;o++){const i=n[o];r[o]=t(e[i],i,o,s&&s[o])}}else r=[];return n&&(n[o]=r),r}function qo(e,t){for(let n=0;n<t.length;n++){const o=t[n];if(E(o))for(let t=0;t<o.length;t++)e[o[t].name]=o[t].fn;else o&&(e[o.name]=o.fn)}return e}function Jo(e,t,n={},o,r){if(En.isCE||En.parent&&mo(En.parent)&&En.parent.isCE)return us(\"slot\",\"default\"===t?null:{name:t},o&&o());let s=e[t];s&&s._c&&(s._d=!1),Yr();const i=s&&Yo(s(n)),l=ns(Wr,{key:n.key||`_${t}`},i||(o?o():[]),i&&1===e._?64:-2);return!r&&l.scopeId&&(l.slotScopeIds=[l.scopeId+\"-s\"]),s&&s._c&&(s._d=!0),l}function Yo(e){return e.some((e=>!os(e)||e.type!==Kr&&!(e.type===Wr&&!Yo(e.children))))?e:null}function Zo(e){const t={};for(const n in e)t[J(n)]=e[n];return t}const Qo=e=>e?Ns(e)?Vs(e)||e.proxy:Qo(e.parent):null,Xo=w(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>Qo(e.parent),$root:e=>Qo(e.root),$emit:e=>e.emit,$options:e=>ir(e),$forceUpdate:e=>e.f||(e.f=()=>hn(e.update)),$nextTick:e=>e.n||(e.n=dn.bind(e.proxy)),$watch:e=>to.bind(e)}),er={get({_:e},t){const{ctx:n,setupState:o,data:r,props:s,accessCache:i,type:l,appContext:c}=e;let a;if(\"$\"!==t[0]){const l=i[t];if(void 0!==l)switch(l){case 1:return o[t];case 2:return r[t];case 4:return n[t];case 3:return s[t]}else{if(o!==v&&N(o,t))return i[t]=1,o[t];if(r!==v&&N(r,t))return i[t]=2,r[t];if((a=e.propsOptions[0])&&N(a,t))return i[t]=3,s[t];if(n!==v&&N(n,t))return i[t]=4,n[t];nr&&(i[t]=0)}}const u=Xo[t];let p,f;return u?(\"$attrs\"===t&&we(e,0,t),u(e)):(p=l.__cssModules)&&(p=p[t])?p:n!==v&&N(n,t)?(i[t]=4,n[t]):(f=c.config.globalProperties,N(f,t)?f[t]:void 0)},set({_:e},t,n){const{data:o,setupState:r,ctx:s}=e;return r!==v&&N(r,t)?(r[t]=n,!0):o!==v&&N(o,t)?(o[t]=n,!0):!N(e.props,t)&&((\"$\"!==t[0]||!(t.slice(1)in e))&&(s[t]=n,!0))},has({_:{data:e,setupState:t,accessCache:n,ctx:o,appContext:r,propsOptions:s}},i){let l;return!!n[i]||e!==v&&N(e,i)||t!==v&&N(t,i)||(l=s[0])&&N(l,i)||N(o,i)||N(Xo,i)||N(r.config.globalProperties,i)},defineProperty(e,t,n){return null!=n.get?e._.accessCache[t]=0:N(n,\"value\")&&this.set(e,t,n.value,null),Reflect.defineProperty(e,t,n)}},tr=w({},er,{get(e,t){if(t!==Symbol.unscopables)return er.get(e,t,e)},has:(e,n)=>\"_\"!==n[0]&&!t(n)});let nr=!0;function or(e){const t=ir(e),n=e.proxy,o=e.ctx;nr=!1,t.beforeCreate&&rr(t.beforeCreate,e,\"bc\");const{data:r,computed:s,methods:i,watch:l,provide:c,inject:a,created:u,beforeMount:p,mounted:f,beforeUpdate:d,updated:h,activated:m,deactivated:g,beforeUnmount:v,unmounted:y,render:b,renderTracked:S,renderTriggered:x,errorCaptured:C,serverPrefetch:w,expose:k,inheritAttrs:T,components:N,directives:$}=t;if(a&&function(e,t,n=_,o=!1){E(e)&&(e=ur(e));for(const r in e){const n=e[r];let s;s=M(n)?\"default\"in n?qn(n.from||r,n.default,!0):qn(n.from||r):qn(n),Rt(s)&&o?Object.defineProperty(t,r,{enumerable:!0,configurable:!0,get:()=>s.value,set:e=>s.value=e}):t[r]=s}}(a,o,null,e.appContext.config.unwrapInjectedRef),i)for(const _ in i){const e=i[_];F(e)&&(o[_]=e.bind(n))}if(r){const t=r.call(n,n);M(t)&&(e.data=gt(t))}if(nr=!0,s)for(const E in s){const e=s[E],t=F(e)?e.bind(n,n):F(e.get)?e.get.bind(n,n):_,r=!F(e)&&F(e.set)?e.set.bind(n):_,i=js({get:t,set:r});Object.defineProperty(o,E,{enumerable:!0,configurable:!0,get:()=>i.value,set:e=>i.value=e})}if(l)for(const _ in l)sr(l[_],o,n,_);if(c){const e=F(c)?c.call(n):c;Reflect.ownKeys(e).forEach((t=>{Gn(t,e[t])}))}function O(e,t){E(t)?t.forEach((t=>e(t.bind(n)))):t&&e(t.bind(n))}if(u&&rr(u,e,\"c\"),O($o,p),O(Oo,f),O(Ro,d),O(Fo,h),O(So,m),O(xo,g),O(Bo,C),O(Io,S),O(Vo,x),O(Po,v),O(Ao,y),O(Mo,w),E(k))if(k.length){const t=e.exposed||(e.exposed={});k.forEach((e=>{Object.defineProperty(t,e,{get:()=>n[e],set:t=>n[e]=t})}))}else e.exposed||(e.exposed={});b&&e.render===_&&(e.render=b),null!=T&&(e.inheritAttrs=T),N&&(e.components=N),$&&(e.directives=$)}function rr(e,t,n){Zt(E(e)?e.map((e=>e.bind(t.proxy))):e.bind(t.proxy),t,n)}function sr(e,t,n,o){const r=o.includes(\".\")?no(n,o):()=>n[o];if(P(e)){const n=t[e];F(n)&&Xn(r,n)}else if(F(e))Xn(r,e.bind(n));else if(M(e))if(E(e))e.forEach((e=>sr(e,t,n,o)));else{const o=F(e.handler)?e.handler.bind(n):t[e.handler];F(o)&&Xn(r,o,e)}}function ir(e){const t=e.type,{mixins:n,extends:o}=t,{mixins:r,optionsCache:s,config:{optionMergeStrategies:i}}=e.appContext,l=s.get(t);let c;return l?c=l:r.length||n||o?(c={},r.length&&r.forEach((e=>lr(c,e,i,!0))),lr(c,t,i)):c=t,s.set(t,c),c}function lr(e,t,n,o=!1){const{mixins:r,extends:s}=t;s&&lr(e,s,n,!0),r&&r.forEach((t=>lr(e,t,n,!0)));for(const i in t)if(o&&\"expose\"===i);else{const o=cr[i]||n&&n[i];e[i]=o?o(e[i],t[i]):t[i]}return e}const cr={data:ar,props:fr,emits:fr,methods:fr,computed:fr,beforeCreate:pr,created:pr,beforeMount:pr,mounted:pr,beforeUpdate:pr,updated:pr,beforeDestroy:pr,beforeUnmount:pr,destroyed:pr,unmounted:pr,activated:pr,deactivated:pr,errorCaptured:pr,serverPrefetch:pr,components:fr,directives:fr,watch:function(e,t){if(!e)return t;if(!t)return e;const n=w(Object.create(null),e);for(const o in t)n[o]=pr(e[o],t[o]);return n},provide:ar,inject:function(e,t){return fr(ur(e),ur(t))}};function ar(e,t){return t?e?function(){return w(F(e)?e.call(this,this):e,F(t)?t.call(this,this):t)}:t:e}function ur(e){if(E(e)){const t={};for(let n=0;n<e.length;n++)t[e[n]]=e[n];return t}return e}function pr(e,t){return e?[...new Set([].concat(e,t))]:t}function fr(e,t){return e?w(w(Object.create(null),e),t):t}function dr(e,t,n,o){const[r,s]=e.propsOptions;let i,l=!1;if(t)for(let c in t){if(U(c))continue;const a=t[c];let u;r&&N(r,u=z(c))?s&&s.includes(u)?(i||(i={}))[u]=a:n[u]=a:Nn(e.emitsOptions,c)||c in o&&a===o[c]||(o[c]=a,l=!0)}if(s){const t=kt(n),o=i||v;for(let i=0;i<s.length;i++){const l=s[i];n[l]=hr(r,t,l,o[l],e,!N(o,l))}}return l}function hr(e,t,n,o,r,s){const i=e[n];if(null!=i){const e=N(i,\"default\");if(e&&void 0===o){const e=i.default;if(i.type!==Function&&F(e)){const{propsDefaults:s}=r;n in s?o=s[n]:(ks(r),o=s[n]=e.call(null,t),Ts())}else o=e}i[0]&&(s&&!e?o=!1:!i[1]||\"\"!==o&&o!==G(n)||(o=!0))}return o}function mr(e,t,n=!1){const o=t.propsCache,r=o.get(e);if(r)return r;const s=e.props,i={},l=[];let c=!1;if(!F(e)){const o=e=>{c=!0;const[n,o]=mr(e,t,!0);w(i,n),o&&l.push(...o)};!n&&t.mixins.length&&t.mixins.forEach(o),e.extends&&o(e.extends),e.mixins&&e.mixins.forEach(o)}if(!s&&!c)return o.set(e,y),y;if(E(s))for(let u=0;u<s.length;u++){const e=z(s[u]);gr(e)&&(i[e]=v)}else if(s)for(const u in s){const e=z(u);if(gr(e)){const t=s[u],n=i[e]=E(t)||F(t)?{type:t}:t;if(n){const t=_r(Boolean,n.type),o=_r(String,n.type);n[0]=t>-1,n[1]=o<0||t<o,(t>-1||N(n,\"default\"))&&l.push(e)}}}const a=[i,l];return o.set(e,a),a}function gr(e){return\"$\"!==e[0]}function vr(e){const t=e&&e.toString().match(/^\\s*function (\\w+)/);return t?t[1]:null===e?\"null\":\"\"}function yr(e,t){return vr(e)===vr(t)}function _r(e,t){return E(t)?t.findIndex((t=>yr(t,e))):F(t)&&yr(t,e)?0:-1}const br=e=>\"_\"===e[0]||\"$stable\"===e,Sr=e=>E(e)?e.map(gs):[gs(e)],xr=(e,t,n)=>{if(t._n)return t;const o=An(((...e)=>Sr(t(...e))),n);return o._c=!1,o},Cr=(e,t,n)=>{const o=e._ctx;for(const r in e){if(br(r))continue;const n=e[r];if(F(n))t[r]=xr(0,n,o);else if(null!=n){const e=Sr(n);t[r]=()=>e}}},wr=(e,t)=>{const n=Sr(t);e.slots.default=()=>n};function kr(){return{app:null,config:{isNativeTag:b,performance:!1,globalProperties:{},optionMergeStrategies:{},errorHandler:void 0,warnHandler:void 0,compilerOptions:{}},mixins:[],components:{},directives:{},provides:Object.create(null),optionsCache:new WeakMap,propsCache:new WeakMap,emitsCache:new WeakMap}}let Tr=0;function Nr(e,t){return function(n,o=null){F(n)||(n=Object.assign({},n)),null==o||M(o)||(o=null);const r=kr(),s=new Set;let i=!1;const l=r.app={_uid:Tr++,_component:n,_props:o,_container:null,_context:r,_instance:null,version:oi,get config(){return r.config},set config(e){},use:(e,...t)=>(s.has(e)||(e&&F(e.install)?(s.add(e),e.install(l,...t)):F(e)&&(s.add(e),e(l,...t))),l),mixin:e=>(r.mixins.includes(e)||r.mixins.push(e),l),component:(e,t)=>t?(r.components[e]=t,l):r.components[e],directive:(e,t)=>t?(r.directives[e]=t,l):r.directives[e],mount(s,c,a){if(!i){const u=us(n,o);return u.appContext=r,c&&t?t(u,s):e(u,s,a),i=!0,l._container=s,s.__vue_app__=l,Vs(u.component)||u.component.proxy}},unmount(){i&&(e(null,l._container),delete l._container.__vue_app__)},provide:(e,t)=>(r.provides[e]=t,l)};return l}}function Er(e,t,n,o,r=!1){if(E(e))return void e.forEach(((e,s)=>Er(e,t&&(E(t)?t[s]:t),n,o,r)));if(mo(o)&&!r)return;const s=4&o.shapeFlag?Vs(o.component)||o.component.proxy:o.el,i=r?null:s,{i:l,r:c}=e,a=t&&t.r,u=l.refs===v?l.refs={}:l.refs,p=l.setupState;if(null!=a&&a!==c&&(P(a)?(u[a]=null,N(p,a)&&(p[a]=null)):Rt(a)&&(a.value=null)),F(c))Yt(c,l,12,[i,u]);else{const t=P(c),o=Rt(c);if(t||o){const l=()=>{if(e.f){const n=t?u[c]:c.value;r?E(n)&&k(n,s):E(n)?n.includes(s)||n.push(s):t?(u[c]=[s],N(p,c)&&(p[c]=u[c])):(c.value=[s],e.k&&(u[e.k]=c.value))}else t?(u[c]=i,N(p,c)&&(p[c]=i)):o&&(c.value=i,e.k&&(u[e.k]=i))};i?(l.id=-1,Pr(l,n)):l()}}}let $r=!1;const Or=e=>/svg/.test(e.namespaceURI)&&\"foreignObject\"!==e.tagName,Rr=e=>8===e.nodeType;function Fr(e){const{mt:t,p:n,o:{patchProp:o,createText:r,nextSibling:s,parentNode:i,remove:l,insert:c,createComment:a}}=e,u=(n,o,l,a,g,v=!1)=>{const y=Rr(n)&&\"[\"===n.data,_=()=>h(n,o,l,a,g,y),{type:b,ref:S,shapeFlag:x,patchFlag:C}=o,w=n.nodeType;o.el=n,-2===C&&(v=!1,o.dynamicChildren=null);let k=null;switch(b){case zr:3!==w?\"\"===o.children?(c(o.el=r(\"\"),i(n),n),k=n):k=_():(n.data!==o.children&&($r=!0,n.data=o.children),k=s(n));break;case Kr:k=8!==w||y?_():s(n);break;case Gr:if(1===w||3===w){k=n;const e=!o.children.length;for(let t=0;t<o.staticCount;t++)e&&(o.children+=1===k.nodeType?k.outerHTML:k.data),t===o.staticCount-1&&(o.anchor=k),k=s(k);return k}k=_();break;case Wr:k=y?d(n,o,l,a,g,v):_();break;default:if(1&x)k=1!==w||o.type.toLowerCase()!==n.tagName.toLowerCase()?_():p(n,o,l,a,g,v);else if(6&x){o.slotScopeIds=g;const e=i(n);if(t(o,e,null,l,a,Or(e),v),k=y?m(n):s(n),k&&Rr(k)&&\"teleport end\"===k.data&&(k=s(k)),mo(o)){let t;y?(t=us(Wr),t.anchor=k?k.previousSibling:e.lastChild):t=3===n.nodeType?ds(\"\"):us(\"div\"),t.el=n,o.component.subTree=t}}else 64&x?k=8!==w?_():o.type.hydrate(n,o,l,a,g,v,e,f):128&x&&(k=o.type.hydrate(n,o,l,a,Or(i(n)),g,v,e,u))}return null!=S&&Er(S,null,a,o),k},p=(e,t,n,r,s,i)=>{i=i||!!t.dynamicChildren;const{type:c,props:a,patchFlag:u,shapeFlag:p,dirs:d}=t,h=\"input\"===c&&d||\"option\"===c;if(h||-1!==u){if(d&&jo(t,null,n,\"created\"),a)if(h||!i||48&u)for(const t in a)(h&&t.endsWith(\"value\")||x(t)&&!U(t))&&o(e,t,null,a[t],!1,void 0,n);else a.onClick&&o(e,\"onClick\",null,a.onClick,!1,void 0,n);let c;if((c=a&&a.onVnodeBeforeMount)&&bs(c,n,t),d&&jo(t,null,n,\"beforeMount\"),((c=a&&a.onVnodeMounted)||d)&&zn((()=>{c&&bs(c,n,t),d&&jo(t,null,n,\"mounted\")}),r),16&p&&(!a||!a.innerHTML&&!a.textContent)){let o=f(e.firstChild,t,e,n,r,s,i);for(;o;){$r=!0;const e=o;o=o.nextSibling,l(e)}}else 8&p&&e.textContent!==t.children&&($r=!0,e.textContent=t.children)}return e.nextSibling},f=(e,t,o,r,s,i,l)=>{l=l||!!t.dynamicChildren;const c=t.children,a=c.length;for(let p=0;p<a;p++){const t=l?c[p]:c[p]=gs(c[p]);if(e)e=u(e,t,r,s,i,l);else{if(t.type===zr&&!t.children)continue;$r=!0,n(null,t,o,null,r,s,Or(o),i)}}return e},d=(e,t,n,o,r,l)=>{const{slotScopeIds:u}=t;u&&(r=r?r.concat(u):u);const p=i(e),d=f(s(e),t,p,n,o,r,l);return d&&Rr(d)&&\"]\"===d.data?s(t.anchor=d):($r=!0,c(t.anchor=a(\"]\"),p,d),d)},h=(e,t,o,r,c,a)=>{if($r=!0,t.el=null,a){const t=m(e);for(;;){const n=s(e);if(!n||n===t)break;l(n)}}const u=s(e),p=i(e);return l(e),n(null,t,p,u,o,r,Or(p),c),u},m=e=>{let t=0;for(;e;)if((e=s(e))&&Rr(e)&&(\"[\"===e.data&&t++,\"]\"===e.data)){if(0===t)return s(e);t--}return e};return[(e,t)=>{if(!t.hasChildNodes())return n(null,e,t),_n(),void(t._vnode=e);$r=!1,u(t.firstChild,e,null,null,null),_n(),t._vnode=e,$r&&console.error(\"Hydration completed but contains mismatches.\")},u]}const Pr=zn;function Ar(e){return Vr(e)}function Mr(e){return Vr(e,Fr)}function Vr(e,t){(ee||(ee=\"undefined\"!=typeof globalThis?globalThis:\"undefined\"!=typeof self?self:\"undefined\"!=typeof window?window:\"undefined\"!=typeof global?global:{})).__VUE__=!0;const{insert:n,remove:o,patchProp:r,createElement:s,createText:i,createComment:l,setText:c,setElementText:a,parentNode:u,nextSibling:p,setScopeId:f=_,cloneNode:d,insertStaticContent:h}=e,m=(e,t,n,o=null,r=null,s=null,i=!1,l=null,c=!!t.dynamicChildren)=>{if(e===t)return;e&&!rs(e,t)&&(o=Y(e),H(e,r,s,!0),e=null),-2===t.patchFlag&&(c=!1,t.dynamicChildren=null);const{type:a,ref:u,shapeFlag:p}=t;switch(a){case zr:g(e,t,n,o);break;case Kr:b(e,t,n,o);break;case Gr:null==e&&S(t,n,o,i);break;case Wr:R(e,t,n,o,r,s,i,l,c);break;default:1&p?x(e,t,n,o,r,s,i,l,c):6&p?F(e,t,n,o,r,s,i,l,c):(64&p||128&p)&&a.process(e,t,n,o,r,s,i,l,c,te)}null!=u&&r&&Er(u,e&&e.ref,s,t||e,!t)},g=(e,t,o,r)=>{if(null==e)n(t.el=i(t.children),o,r);else{const n=t.el=e.el;t.children!==e.children&&c(n,t.children)}},b=(e,t,o,r)=>{null==e?n(t.el=l(t.children||\"\"),o,r):t.el=e.el},S=(e,t,n,o)=>{[e.el,e.anchor]=h(e.children,t,n,o,e.el,e.anchor)},x=(e,t,n,o,r,s,i,l,c)=>{i=i||\"svg\"===t.type,null==e?C(t,n,o,r,s,i,l,c):E(e,t,r,s,i,l,c)},C=(e,t,o,i,l,c,u,p)=>{let f,h;const{type:m,props:g,shapeFlag:v,transition:y,patchFlag:_,dirs:b}=e;if(e.el&&void 0!==d&&-1===_)f=e.el=d(e.el);else{if(f=e.el=s(e.type,c,g&&g.is,g),8&v?a(f,e.children):16&v&&T(e.children,f,null,i,l,c&&\"foreignObject\"!==m,u,p),b&&jo(e,null,i,\"created\"),g){for(const t in g)\"value\"===t||U(t)||r(f,t,null,g[t],c,e.children,i,l,J);\"value\"in g&&r(f,\"value\",null,g.value),(h=g.onVnodeBeforeMount)&&bs(h,i,e)}k(f,e,e.scopeId,u,i)}b&&jo(e,null,i,\"beforeMount\");const S=(!l||l&&!l.pendingBranch)&&y&&!y.persisted;S&&y.beforeEnter(f),n(f,t,o),((h=g&&g.onVnodeMounted)||S||b)&&Pr((()=>{h&&bs(h,i,e),S&&y.enter(f),b&&jo(e,null,i,\"mounted\")}),l)},k=(e,t,n,o,r)=>{if(n&&f(e,n),o)for(let s=0;s<o.length;s++)f(e,o[s]);if(r){if(t===r.subTree){const t=r.vnode;k(e,t,t.scopeId,t.slotScopeIds,r.parent)}}},T=(e,t,n,o,r,s,i,l,c=0)=>{for(let a=c;a<e.length;a++){const c=e[a]=l?vs(e[a]):gs(e[a]);m(null,c,t,n,o,r,s,i,l)}},E=(e,t,n,o,s,i,l)=>{const c=t.el=e.el;let{patchFlag:u,dynamicChildren:p,dirs:f}=t;u|=16&e.patchFlag;const d=e.props||v,h=t.props||v;let m;n&&Ir(n,!1),(m=h.onVnodeBeforeUpdate)&&bs(m,n,t,e),f&&jo(t,e,n,\"beforeUpdate\"),n&&Ir(n,!0);const g=s&&\"foreignObject\"!==t.type;if(p?$(e.dynamicChildren,p,c,n,o,g,i):l||B(e,t,c,null,n,o,g,i,!1),u>0){if(16&u)O(c,t,d,h,n,o,s);else if(2&u&&d.class!==h.class&&r(c,\"class\",null,h.class,s),4&u&&r(c,\"style\",d.style,h.style,s),8&u){const i=t.dynamicProps;for(let t=0;t<i.length;t++){const l=i[t],a=d[l],u=h[l];u===a&&\"value\"!==l||r(c,l,a,u,s,e.children,n,o,J)}}1&u&&e.children!==t.children&&a(c,t.children)}else l||null!=p||O(c,t,d,h,n,o,s);((m=h.onVnodeUpdated)||f)&&Pr((()=>{m&&bs(m,n,t,e),f&&jo(t,e,n,\"updated\")}),o)},$=(e,t,n,o,r,s,i)=>{for(let l=0;l<t.length;l++){const c=e[l],a=t[l],p=c.el&&(c.type===Wr||!rs(c,a)||70&c.shapeFlag)?u(c.el):n;m(c,a,p,null,o,r,s,i,!0)}},O=(e,t,n,o,s,i,l)=>{if(n!==o){for(const c in o){if(U(c))continue;const a=o[c],u=n[c];a!==u&&\"value\"!==c&&r(e,c,u,a,l,t.children,s,i,J)}if(n!==v)for(const c in n)U(c)||c in o||r(e,c,n[c],null,l,t.children,s,i,J);\"value\"in o&&r(e,\"value\",n.value,o.value)}},R=(e,t,o,r,s,l,c,a,u)=>{const p=t.el=e?e.el:i(\"\"),f=t.anchor=e?e.anchor:i(\"\");let{patchFlag:d,dynamicChildren:h,slotScopeIds:m}=t;m&&(a=a?a.concat(m):m),null==e?(n(p,o,r),n(f,o,r),T(t.children,o,f,s,l,c,a,u)):d>0&&64&d&&h&&e.dynamicChildren?($(e.dynamicChildren,h,o,s,l,c,a),(null!=t.key||s&&t===s.subTree)&&Br(e,t,!0)):B(e,t,o,f,s,l,c,a,u)},F=(e,t,n,o,r,s,i,l,c)=>{t.slotScopeIds=l,null==e?512&t.shapeFlag?r.ctx.activate(t,n,o,i,c):P(t,n,o,r,s,i,c):A(e,t,c)},P=(e,t,n,o,r,s,i)=>{const l=e.component=function(e,t,n){const o=e.type,r=(t?t.appContext:e.appContext)||Ss,s={uid:xs++,vnode:e,type:o,parent:t,appContext:r,root:null,next:null,subTree:null,effect:null,update:null,scope:new ne(!0),render:null,proxy:null,exposed:null,exposeProxy:null,withProxy:null,provides:t?t.provides:Object.create(r.provides),accessCache:null,renderCache:[],components:null,directives:null,propsOptions:mr(o,r),emitsOptions:Tn(o,r),emit:null,emitted:null,propsDefaults:v,inheritAttrs:o.inheritAttrs,ctx:v,data:v,props:v,attrs:v,slots:v,refs:v,setupState:v,setupContext:null,suspense:n,suspenseId:n?n.pendingId:0,asyncDep:null,asyncResolved:!1,isMounted:!1,isUnmounted:!1,isDeactivated:!1,bc:null,c:null,bm:null,m:null,bu:null,u:null,um:null,bum:null,da:null,a:null,rtg:null,rtc:null,ec:null,sp:null};s.ctx={_:s},s.root=t?t.root:s,s.emit=kn.bind(null,s),e.ce&&e.ce(s);return s}(e,o,r);if(yo(e)&&(l.ctx.renderer=te),function(e,t=!1){Os=t;const{props:n,children:o}=e.vnode,r=Ns(e);(function(e,t,n,o=!1){const r={},s={};Q(s,is,1),e.propsDefaults=Object.create(null),dr(e,t,r,s);for(const i in e.propsOptions[0])i in r||(r[i]=void 0);e.props=n?o?r:vt(r):e.type.props?r:s,e.attrs=s})(e,n,r,t),((e,t)=>{if(32&e.vnode.shapeFlag){const n=t._;n?(e.slots=kt(t),Q(t,\"_\",n)):Cr(t,e.slots={})}else e.slots={},t&&wr(e,t);Q(e.slots,is,1)})(e,o);const s=r?function(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=Tt(new Proxy(e.ctx,er));const{setup:o}=n;if(o){const n=e.setupContext=o.length>1?Ms(e):null;ks(e),xe();const r=Yt(o,e,0,[e.props,n]);if(Ce(),Ts(),V(r)){if(r.then(Ts,Ts),t)return r.then((n=>{Rs(e,n,t)})).catch((t=>{Qt(t,e,0)}));e.asyncDep=r}else Rs(e,r,t)}else As(e,t)}(e,t):void 0;Os=!1}(l),l.asyncDep){if(r&&r.registerDep(l,M),!e.el){const e=l.subTree=us(Kr);b(null,e,t,n)}}else M(l,e,t,n,r,s,i)},A=(e,t,n)=>{const o=t.component=e.component;if(function(e,t,n){const{props:o,children:r,component:s}=e,{props:i,children:l,patchFlag:c}=t,a=s.emitsOptions;if(t.dirs||t.transition)return!0;if(!(n&&c>=0))return!(!r&&!l||l&&l.$stable)||o!==i&&(o?!i||Bn(o,i,a):!!i);if(1024&c)return!0;if(16&c)return o?Bn(o,i,a):!!i;if(8&c){const e=t.dynamicProps;for(let t=0;t<e.length;t++){const n=e[t];if(i[n]!==o[n]&&!Nn(a,n))return!0}}return!1}(e,t,n)){if(o.asyncDep&&!o.asyncResolved)return void I(o,t,n);o.next=t,function(e){const t=tn.indexOf(e);t>nn&&tn.splice(t,1)}(o.update),o.update()}else t.el=e.el,o.vnode=t},M=(e,t,n,o,r,s,i)=>{const l=e.effect=new ge((()=>{if(e.isMounted){let t,{next:n,bu:o,u:l,parent:c,vnode:a}=e,p=n;Ir(e,!1),n?(n.el=a.el,I(e,n,i)):n=a,o&&Z(o),(t=n.props&&n.props.onVnodeBeforeUpdate)&&bs(t,c,n,a),Ir(e,!0);const f=Mn(e),d=e.subTree;e.subTree=f,m(d,f,u(d.el),Y(d),e,r,s),n.el=f.el,null===p&&Ln(e,f.el),l&&Pr(l,r),(t=n.props&&n.props.onVnodeUpdated)&&Pr((()=>bs(t,c,n,a)),r)}else{let i;const{el:l,props:c}=t,{bm:a,m:u,parent:p}=e,f=mo(t);if(Ir(e,!1),a&&Z(a),!f&&(i=c&&c.onVnodeBeforeMount)&&bs(i,p,t),Ir(e,!0),l&&re){const n=()=>{e.subTree=Mn(e),re(l,e.subTree,e,r,null)};f?t.type.__asyncLoader().then((()=>!e.isUnmounted&&n())):n()}else{const i=e.subTree=Mn(e);m(null,i,n,o,e,r,s),t.el=i.el}if(u&&Pr(u,r),!f&&(i=c&&c.onVnodeMounted)){const e=t;Pr((()=>bs(i,p,e)),r)}(256&t.shapeFlag||p&&mo(p.vnode)&&256&p.vnode.shapeFlag)&&e.a&&Pr(e.a,r),e.isMounted=!0,t=n=o=null}}),(()=>hn(c)),e.scope),c=e.update=()=>l.run();c.id=e.uid,Ir(e,!0),c()},I=(e,t,n)=>{t.component=e;const o=e.vnode.props;e.vnode=t,e.next=null,function(e,t,n,o){const{props:r,attrs:s,vnode:{patchFlag:i}}=e,l=kt(r),[c]=e.propsOptions;let a=!1;if(!(o||i>0)||16&i){let o;dr(e,t,r,s)&&(a=!0);for(const s in l)t&&(N(t,s)||(o=G(s))!==s&&N(t,o))||(c?!n||void 0===n[s]&&void 0===n[o]||(r[s]=hr(c,l,s,void 0,e,!0)):delete r[s]);if(s!==l)for(const e in s)t&&N(t,e)||(delete s[e],a=!0)}else if(8&i){const n=e.vnode.dynamicProps;for(let o=0;o<n.length;o++){let i=n[o];if(Nn(e.emitsOptions,i))continue;const u=t[i];if(c)if(N(s,i))u!==s[i]&&(s[i]=u,a=!0);else{const t=z(i);r[t]=hr(c,l,t,u,e,!1)}else u!==s[i]&&(s[i]=u,a=!0)}}a&&Te(e,\"set\",\"$attrs\")}(e,t.props,o,n),((e,t,n)=>{const{vnode:o,slots:r}=e;let s=!0,i=v;if(32&o.shapeFlag){const e=t._;e?n&&1===e?s=!1:(w(r,t),n||1!==e||delete r._):(s=!t.$stable,Cr(t,r)),i=t}else t&&(wr(e,t),i={default:1});if(s)for(const l in r)br(l)||l in i||delete r[l]})(e,t.children,n),xe(),yn(void 0,e.update),Ce()},B=(e,t,n,o,r,s,i,l,c=!1)=>{const u=e&&e.children,p=e?e.shapeFlag:0,f=t.children,{patchFlag:d,shapeFlag:h}=t;if(d>0){if(128&d)return void j(u,f,n,o,r,s,i,l,c);if(256&d)return void L(u,f,n,o,r,s,i,l,c)}8&h?(16&p&&J(u,r,s),f!==u&&a(n,f)):16&p?16&h?j(u,f,n,o,r,s,i,l,c):J(u,r,s,!0):(8&p&&a(n,\"\"),16&h&&T(f,n,o,r,s,i,l,c))},L=(e,t,n,o,r,s,i,l,c)=>{const a=(e=e||y).length,u=(t=t||y).length,p=Math.min(a,u);let f;for(f=0;f<p;f++){const o=t[f]=c?vs(t[f]):gs(t[f]);m(e[f],o,n,null,r,s,i,l,c)}a>u?J(e,r,s,!0,!1,p):T(t,n,o,r,s,i,l,c,p)},j=(e,t,n,o,r,s,i,l,c)=>{let a=0;const u=t.length;let p=e.length-1,f=u-1;for(;a<=p&&a<=f;){const o=e[a],u=t[a]=c?vs(t[a]):gs(t[a]);if(!rs(o,u))break;m(o,u,n,null,r,s,i,l,c),a++}for(;a<=p&&a<=f;){const o=e[p],a=t[f]=c?vs(t[f]):gs(t[f]);if(!rs(o,a))break;m(o,a,n,null,r,s,i,l,c),p--,f--}if(a>p){if(a<=f){const e=f+1,p=e<u?t[e].el:o;for(;a<=f;)m(null,t[a]=c?vs(t[a]):gs(t[a]),n,p,r,s,i,l,c),a++}}else if(a>f)for(;a<=p;)H(e[a],r,s,!0),a++;else{const d=a,h=a,g=new Map;for(a=h;a<=f;a++){const e=t[a]=c?vs(t[a]):gs(t[a]);null!=e.key&&g.set(e.key,a)}let v,_=0;const b=f-h+1;let S=!1,x=0;const C=new Array(b);for(a=0;a<b;a++)C[a]=0;for(a=d;a<=p;a++){const o=e[a];if(_>=b){H(o,r,s,!0);continue}let u;if(null!=o.key)u=g.get(o.key);else for(v=h;v<=f;v++)if(0===C[v-h]&&rs(o,t[v])){u=v;break}void 0===u?H(o,r,s,!0):(C[u-h]=a+1,u>=x?x=u:S=!0,m(o,t[u],n,null,r,s,i,l,c),_++)}const w=S?function(e){const t=e.slice(),n=[0];let o,r,s,i,l;const c=e.length;for(o=0;o<c;o++){const c=e[o];if(0!==c){if(r=n[n.length-1],e[r]<c){t[o]=r,n.push(o);continue}for(s=0,i=n.length-1;s<i;)l=s+i>>1,e[n[l]]<c?s=l+1:i=l;c<e[n[s]]&&(s>0&&(t[o]=n[s-1]),n[s]=o)}}s=n.length,i=n[s-1];for(;s-- >0;)n[s]=i,i=t[i];return n}(C):y;for(v=w.length-1,a=b-1;a>=0;a--){const e=h+a,p=t[e],f=e+1<u?t[e+1].el:o;0===C[a]?m(null,p,n,f,r,s,i,l,c):S&&(v<0||a!==w[v]?D(p,n,f,2):v--)}}},D=(e,t,o,r,s=null)=>{const{el:i,type:l,transition:c,children:a,shapeFlag:u}=e;if(6&u)return void D(e.component.subTree,t,o,r);if(128&u)return void e.suspense.move(t,o,r);if(64&u)return void l.move(e,t,o,te);if(l===Wr){n(i,t,o);for(let e=0;e<a.length;e++)D(a[e],t,o,r);return void n(e.anchor,t,o)}if(l===Gr)return void(({el:e,anchor:t},o,r)=>{let s;for(;e&&e!==t;)s=p(e),n(e,o,r),e=s;n(t,o,r)})(e,t,o);if(2!==r&&1&u&&c)if(0===r)c.beforeEnter(i),n(i,t,o),Pr((()=>c.enter(i)),s);else{const{leave:e,delayLeave:r,afterLeave:s}=c,l=()=>n(i,t,o),a=()=>{e(i,(()=>{l(),s&&s()}))};r?r(i,l,a):a()}else n(i,t,o)},H=(e,t,n,o=!1,r=!1)=>{const{type:s,props:i,ref:l,children:c,dynamicChildren:a,shapeFlag:u,patchFlag:p,dirs:f}=e;if(null!=l&&Er(l,null,n,e,!0),256&u)return void t.ctx.deactivate(e);const d=1&u&&f,h=!mo(e);let m;if(h&&(m=i&&i.onVnodeBeforeUnmount)&&bs(m,t,e),6&u)q(e.component,n,o);else{if(128&u)return void e.suspense.unmount(n,o);d&&jo(e,null,t,\"beforeUnmount\"),64&u?e.type.remove(e,t,n,r,te,o):a&&(s!==Wr||p>0&&64&p)?J(a,t,n,!1,!0):(s===Wr&&384&p||!r&&16&u)&&J(c,t,n),o&&W(e)}(h&&(m=i&&i.onVnodeUnmounted)||d)&&Pr((()=>{m&&bs(m,t,e),d&&jo(e,null,t,\"unmounted\")}),n)},W=e=>{const{type:t,el:n,anchor:r,transition:s}=e;if(t===Wr)return void K(n,r);if(t===Gr)return void(({el:e,anchor:t})=>{let n;for(;e&&e!==t;)n=p(e),o(e),e=n;o(t)})(e);const i=()=>{o(n),s&&!s.persisted&&s.afterLeave&&s.afterLeave()};if(1&e.shapeFlag&&s&&!s.persisted){const{leave:t,delayLeave:o}=s,r=()=>t(n,i);o?o(e.el,i,r):r()}else i()},K=(e,t)=>{let n;for(;e!==t;)n=p(e),o(e),e=n;o(t)},q=(e,t,n)=>{const{bum:o,scope:r,update:s,subTree:i,um:l}=e;o&&Z(o),r.stop(),s&&(s.active=!1,H(i,e,t,n)),l&&Pr(l,t),Pr((()=>{e.isUnmounted=!0}),t),t&&t.pendingBranch&&!t.isUnmounted&&e.asyncDep&&!e.asyncResolved&&e.suspenseId===t.pendingId&&(t.deps--,0===t.deps&&t.resolve())},J=(e,t,n,o=!1,r=!1,s=0)=>{for(let i=s;i<e.length;i++)H(e[i],t,n,o,r)},Y=e=>6&e.shapeFlag?Y(e.component.subTree):128&e.shapeFlag?e.suspense.next():p(e.anchor||e.el),X=(e,t,n)=>{null==e?t._vnode&&H(t._vnode,null,null,!0):m(t._vnode||null,e,t,null,null,null,n),_n(),t._vnode=e},te={p:m,um:H,m:D,r:W,mt:P,mc:T,pc:B,pbc:$,n:Y,o:e};let oe,re;return t&&([oe,re]=t(te)),{render:X,hydrate:oe,createApp:Nr(X,oe)}}function Ir({effect:e,update:t},n){e.allowRecurse=t.allowRecurse=n}function Br(e,t,n=!1){const o=e.children,r=t.children;if(E(o)&&E(r))for(let s=0;s<o.length;s++){const e=o[s];let t=r[s];1&t.shapeFlag&&!t.dynamicChildren&&((t.patchFlag<=0||32===t.patchFlag)&&(t=r[s]=vs(r[s]),t.el=e.el),n||Br(e,t))}}const Lr=e=>e&&(e.disabled||\"\"===e.disabled),jr=e=>\"undefined\"!=typeof SVGElement&&e instanceof SVGElement,Ur=(e,t)=>{const n=e&&e.to;if(P(n)){if(t){return t(n)}return null}return n};function Dr(e,t,n,{o:{insert:o},m:r},s=2){0===s&&o(e.targetAnchor,t,n);const{el:i,anchor:l,shapeFlag:c,children:a,props:u}=e,p=2===s;if(p&&o(i,t,n),(!p||Lr(u))&&16&c)for(let f=0;f<a.length;f++)r(a[f],t,n,2);p&&o(l,t,n)}const Hr={__isTeleport:!0,process(e,t,n,o,r,s,i,l,c,a){const{mc:u,pc:p,pbc:f,o:{insert:d,querySelector:h,createText:m}}=a,g=Lr(t.props);let{shapeFlag:v,children:y,dynamicChildren:_}=t;if(null==e){const e=t.el=m(\"\"),a=t.anchor=m(\"\");d(e,n,o),d(a,n,o);const p=t.target=Ur(t.props,h),f=t.targetAnchor=m(\"\");p&&(d(f,p),i=i||jr(p));const _=(e,t)=>{16&v&&u(y,e,t,r,s,i,l,c)};g?_(n,a):p&&_(p,f)}else{t.el=e.el;const o=t.anchor=e.anchor,u=t.target=e.target,d=t.targetAnchor=e.targetAnchor,m=Lr(e.props),v=m?n:u,y=m?o:d;if(i=i||jr(u),_?(f(e.dynamicChildren,_,v,r,s,i,l),Br(e,t,!0)):c||p(e,t,v,y,r,s,i,l,!1),g)m||Dr(t,n,o,a,1);else if((t.props&&t.props.to)!==(e.props&&e.props.to)){const e=t.target=Ur(t.props,h);e&&Dr(t,e,null,a,0)}else m&&Dr(t,u,d,a,1)}},remove(e,t,n,o,{um:r,o:{remove:s}},i){const{shapeFlag:l,children:c,anchor:a,targetAnchor:u,target:p,props:f}=e;if(p&&s(u),(i||!Lr(f))&&(s(a),16&l))for(let d=0;d<c.length;d++){const e=c[d];r(e,t,n,!0,!!e.dynamicChildren)}},move:Dr,hydrate:function(e,t,n,o,r,s,{o:{nextSibling:i,parentNode:l,querySelector:c}},a){const u=t.target=Ur(t.props,c);if(u){const c=u._lpa||u.firstChild;if(16&t.shapeFlag)if(Lr(t.props))t.anchor=a(i(e),t,l(e),n,o,r,s),t.targetAnchor=c;else{t.anchor=i(e);let l=c;for(;l;)if(l=i(l),l&&8===l.nodeType&&\"teleport anchor\"===l.data){t.targetAnchor=l,u._lpa=t.targetAnchor&&i(t.targetAnchor);break}a(c,t,u,n,o,r,s)}}return t.anchor&&i(t.anchor)}},Wr=Symbol(void 0),zr=Symbol(void 0),Kr=Symbol(void 0),Gr=Symbol(void 0),qr=[];let Jr=null;function Yr(e=!1){qr.push(Jr=e?null:[])}function Zr(){qr.pop(),Jr=qr[qr.length-1]||null}let Qr=1;function Xr(e){Qr+=e}function es(e){return e.dynamicChildren=Qr>0?Jr||y:null,Zr(),Qr>0&&Jr&&Jr.push(e),e}function ts(e,t,n,o,r,s){return es(as(e,t,n,o,r,s,!0))}function ns(e,t,n,o,r){return es(us(e,t,n,o,r,!0))}function os(e){return!!e&&!0===e.__v_isVNode}function rs(e,t){return e.type===t.type&&e.key===t.key}function ss(e){}const is=\"__vInternal\",ls=({key:e})=>null!=e?e:null,cs=({ref:e,ref_key:t,ref_for:n})=>null!=e?P(e)||Rt(e)||F(e)?{i:En,r:e,k:t,f:!!n}:e:null;function as(e,t=null,n=null,o=0,r=null,s=(e===Wr?0:1),i=!1,l=!1){const c={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&ls(t),ref:t&&cs(t),scopeId:$n,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetAnchor:null,staticCount:0,shapeFlag:s,patchFlag:o,dynamicProps:r,dynamicChildren:null,appContext:null};return l?(ys(c,n),128&s&&e.normalize(c)):n&&(c.shapeFlag|=P(n)?8:16),Qr>0&&!i&&Jr&&(c.patchFlag>0||6&s)&&32!==c.patchFlag&&Jr.push(c),c}const us=function(e,t=null,n=null,o=0,s=null,i=!1){e&&e!==Do||(e=Kr);if(os(e)){const o=fs(e,t,!0);return n&&ys(o,n),Qr>0&&!i&&Jr&&(6&o.shapeFlag?Jr[Jr.indexOf(e)]=o:Jr.push(o)),o.patchFlag|=-2,o}l=e,F(l)&&\"__vccOpts\"in l&&(e=e.__vccOpts);var l;if(t){t=ps(t);let{class:e,style:n}=t;e&&!P(e)&&(t.class=c(e)),M(n)&&(wt(n)&&!E(n)&&(n=w({},n)),t.style=r(n))}const a=P(e)?1:jn(e)?128:(e=>e.__isTeleport)(e)?64:M(e)?4:F(e)?2:0;return as(e,t,n,o,s,a,i,!0)};function ps(e){return e?wt(e)||is in e?w({},e):e:null}function fs(e,t,n=!1){const{props:o,ref:r,patchFlag:s,children:i}=e,l=t?_s(o||{},t):o;return{__v_isVNode:!0,__v_skip:!0,type:e.type,props:l,key:l&&ls(l),ref:t&&t.ref?n&&r?E(r)?r.concat(cs(t)):[r,cs(t)]:cs(t):r,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:i,target:e.target,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==Wr?-1===s?16:16|s:s,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:e.transition,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&fs(e.ssContent),ssFallback:e.ssFallback&&fs(e.ssFallback),el:e.el,anchor:e.anchor}}function ds(e=\" \",t=0){return us(zr,null,e,t)}function hs(e,t){const n=us(Gr,null,e);return n.staticCount=t,n}function ms(e=\"\",t=!1){return t?(Yr(),ns(Kr,null,e)):us(Kr,null,e)}function gs(e){return null==e||\"boolean\"==typeof e?us(Kr):E(e)?us(Wr,null,e.slice()):\"object\"==typeof e?vs(e):us(zr,null,String(e))}function vs(e){return null===e.el||e.memo?e:fs(e)}function ys(e,t){let n=0;const{shapeFlag:o}=e;if(null==t)t=null;else if(E(t))n=16;else if(\"object\"==typeof t){if(65&o){const n=t.default;return void(n&&(n._c&&(n._d=!1),ys(e,n()),n._c&&(n._d=!0)))}{n=32;const o=t._;o||is in t?3===o&&En&&(1===En.slots._?t._=1:(t._=2,e.patchFlag|=1024)):t._ctx=En}}else F(t)?(t={default:t,_ctx:En},n=32):(t=String(t),64&o?(n=16,t=[ds(t)]):n=8);e.children=t,e.shapeFlag|=n}function _s(...e){const t={};for(let n=0;n<e.length;n++){const o=e[n];for(const e in o)if(\"class\"===e)t.class!==o.class&&(t.class=c([t.class,o.class]));else if(\"style\"===e)t.style=r([t.style,o.style]);else if(x(e)){const n=t[e],r=o[e];!r||n===r||E(n)&&n.includes(r)||(t[e]=n?[].concat(n,r):r)}else\"\"!==e&&(t[e]=o[e])}return t}function bs(e,t,n,o=null){Zt(e,t,7,[n,o])}const Ss=kr();let xs=0;let Cs=null;const ws=()=>Cs||En,ks=e=>{Cs=e,e.scope.on()},Ts=()=>{Cs&&Cs.scope.off(),Cs=null};function Ns(e){return 4&e.vnode.shapeFlag}let Es,$s,Os=!1;function Rs(e,t,n){F(t)?e.render=t:M(t)&&(e.setupState=Lt(t)),As(e,n)}function Fs(e){Es=e,$s=e=>{e.render._rc&&(e.withProxy=new Proxy(e.ctx,tr))}}const Ps=()=>!Es;function As(e,t,n){const o=e.type;if(!e.render){if(!t&&Es&&!o.render){const t=o.template;if(t){const{isCustomElement:n,compilerOptions:r}=e.appContext.config,{delimiters:s,compilerOptions:i}=o,l=w(w({isCustomElement:n,delimiters:s},r),i);o.render=Es(t,l)}}e.render=o.render||_,$s&&$s(e)}ks(e),xe(),or(e),Ce(),Ts()}function Ms(e){const t=t=>{e.exposed=t||{}};let n;return{get attrs(){return n||(n=function(e){return new Proxy(e.attrs,{get:(t,n)=>(we(e,0,\"$attrs\"),t[n])})}(e))},slots:e.slots,emit:e.emit,expose:t}}function Vs(e){if(e.exposed)return e.exposeProxy||(e.exposeProxy=new Proxy(Lt(Tt(e.exposed)),{get:(t,n)=>n in t?t[n]:n in Xo?Xo[n](e):void 0}))}const Is=/(?:^|[-_])(\\w)/g;function Bs(e,t=!0){return F(e)?e.displayName||e.name:e.name||t&&e.__name}function Ls(e,t,n=!1){let o=Bs(t);if(!o&&t.__file){const e=t.__file.match(/([^/\\\\]+)\\.\\w+$/);e&&(o=e[1])}if(!o&&e&&e.parent){const n=e=>{for(const n in e)if(e[n]===t)return n};o=n(e.components||e.parent.type.components)||n(e.appContext.components)}return o?o.replace(Is,(e=>e.toUpperCase())).replace(/[-_]/g,\"\"):n?\"App\":\"Anonymous\"}const js=(e,t)=>function(e,t,n=!1){let o,r;const s=F(e);return s?(o=e,r=_):(o=e.get,r=e.set),new zt(o,r,s||!r,n)}(e,0,Os);function Us(){return null}function Ds(){return null}function Hs(e){}function Ws(e,t){return null}function zs(){return Gs().slots}function Ks(){return Gs().attrs}function Gs(){const e=ws();return e.setupContext||(e.setupContext=Ms(e))}function qs(e,t){const n=E(e)?e.reduce(((e,t)=>(e[t]={},e)),{}):e;for(const o in t){const e=n[o];e?E(e)||F(e)?n[o]={type:e,default:t[o]}:e.default=t[o]:null===e&&(n[o]={default:t[o]})}return n}function Js(e,t){const n={};for(const o in e)t.includes(o)||Object.defineProperty(n,o,{enumerable:!0,get:()=>e[o]});return n}function Ys(e){const t=ws();let n=e();return Ts(),V(n)&&(n=n.catch((e=>{throw ks(t),e}))),[n,()=>ks(t)]}function Zs(e,t,n){const o=arguments.length;return 2===o?M(t)&&!E(t)?os(t)?us(e,null,[t]):us(e,t):us(e,null,t):(o>3?n=Array.prototype.slice.call(arguments,2):3===o&&os(n)&&(n=[n]),us(e,t,n))}const Qs=Symbol(\"\"),Xs=()=>{{const e=qn(Qs);return e||Gt(\"Server rendering context not provided. Make sure to only call useSSRContext() conditionally in the server build.\"),e}};function ei(){}function ti(e,t,n,o){const r=n[o];if(r&&ni(r,e))return r;const s=t();return s.memo=e.slice(),n[o]=s}function ni(e,t){const n=e.memo;if(n.length!=t.length)return!1;for(let o=0;o<n.length;o++)if(Y(n[o],t[o]))return!1;return Qr>0&&Jr&&Jr.push(e),!0}const oi=\"3.2.37\",ri=null,si=null,ii=null,li=\"undefined\"!=typeof document?document:null,ci=li&&li.createElement(\"template\"),ai={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,o)=>{const r=t?li.createElementNS(\"http://www.w3.org/2000/svg\",e):li.createElement(e,n?{is:n}:void 0);return\"select\"===e&&o&&null!=o.multiple&&r.setAttribute(\"multiple\",o.multiple),r},createText:e=>li.createTextNode(e),createComment:e=>li.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>li.querySelector(e),setScopeId(e,t){e.setAttribute(t,\"\")},cloneNode(e){const t=e.cloneNode(!0);return\"_value\"in e&&(t._value=e._value),t},insertStaticContent(e,t,n,o,r,s){const i=n?n.previousSibling:t.lastChild;if(r&&(r===s||r.nextSibling))for(;t.insertBefore(r.cloneNode(!0),n),r!==s&&(r=r.nextSibling););else{ci.innerHTML=o?`<svg>${e}</svg>`:e;const r=ci.content;if(o){const e=r.firstChild;for(;e.firstChild;)r.appendChild(e.firstChild);r.removeChild(e)}t.insertBefore(r,n)}return[i?i.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}};const ui=/\\s*!important$/;function pi(e,t,n){if(E(n))n.forEach((n=>pi(e,t,n)));else if(null==n&&(n=\"\"),t.startsWith(\"--\"))e.setProperty(t,n);else{const o=function(e,t){const n=di[t];if(n)return n;let o=z(t);if(\"filter\"!==o&&o in e)return di[t]=o;o=q(o);for(let r=0;r<fi.length;r++){const n=fi[r]+o;if(n in e)return di[t]=n}return t}(e,t);ui.test(n)?e.setProperty(G(o),n.replace(ui,\"\"),\"important\"):e[o]=n}}const fi=[\"Webkit\",\"Moz\",\"ms\"],di={};const hi=\"http://www.w3.org/1999/xlink\";const[mi,gi]=(()=>{let e=Date.now,t=!1;if(\"undefined\"!=typeof window){Date.now()>document.createEvent(\"Event\").timeStamp&&(e=performance.now.bind(performance));const n=navigator.userAgent.match(/firefox\\/(\\d+)/i);t=!!(n&&Number(n[1])<=53)}return[e,t]})();let vi=0;const yi=Promise.resolve(),_i=()=>{vi=0};function bi(e,t,n,o){e.addEventListener(t,n,o)}function Si(e,t,n,o,r=null){const s=e._vei||(e._vei={}),i=s[t];if(o&&i)i.value=o;else{const[n,l]=function(e){let t;if(xi.test(e)){let n;for(t={};n=e.match(xi);)e=e.slice(0,e.length-n[0].length),t[n[0].toLowerCase()]=!0}return[G(e.slice(2)),t]}(t);if(o){const i=s[t]=function(e,t){const n=e=>{const o=e.timeStamp||mi();(gi||o>=n.attached-1)&&Zt(function(e,t){if(E(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map((e=>t=>!t._stopped&&e&&e(t)))}return t}(e,n.value),t,5,[e])};return n.value=e,n.attached=(()=>vi||(yi.then(_i),vi=mi()))(),n}(o,r);bi(e,n,i,l)}else i&&(!function(e,t,n,o){e.removeEventListener(t,n,o)}(e,n,i,l),s[t]=void 0)}}const xi=/(?:Once|Passive|Capture)$/;const Ci=/^on[a-z]/;function wi(e,t){const n=ho(e);class o extends Ni{constructor(e){super(n,e,t)}}return o.def=n,o}const ki=e=>wi(e,Tl),Ti=\"undefined\"!=typeof HTMLElement?HTMLElement:class{};class Ni extends Ti{constructor(e,t={},n){super(),this._def=e,this._props=t,this._instance=null,this._connected=!1,this._resolved=!1,this._numberProps=null,this.shadowRoot&&n?n(this._createVNode(),this.shadowRoot):this.attachShadow({mode:\"open\"})}connectedCallback(){this._connected=!0,this._instance||this._resolveDef()}disconnectedCallback(){this._connected=!1,dn((()=>{this._connected||(kl(null,this.shadowRoot),this._instance=null)}))}_resolveDef(){if(this._resolved)return;this._resolved=!0;for(let n=0;n<this.attributes.length;n++)this._setAttr(this.attributes[n].name);new MutationObserver((e=>{for(const t of e)this._setAttr(t.attributeName)})).observe(this,{attributes:!0});const e=e=>{const{props:t,styles:n}=e,o=!E(t),r=t?o?Object.keys(t):t:[];let s;if(o)for(const i in this._props){const e=t[i];(e===Number||e&&e.type===Number)&&(this._props[i]=X(this._props[i]),(s||(s=Object.create(null)))[i]=!0)}this._numberProps=s;for(const i of Object.keys(this))\"_\"!==i[0]&&this._setProp(i,this[i],!0,!1);for(const i of r.map(z))Object.defineProperty(this,i,{get(){return this._getProp(i)},set(e){this._setProp(i,e)}});this._applyStyles(n),this._update()},t=this._def.__asyncLoader;t?t().then(e):e(this._def)}_setAttr(e){let t=this.getAttribute(e);this._numberProps&&this._numberProps[e]&&(t=X(t)),this._setProp(z(e),t,!1)}_getProp(e){return this._props[e]}_setProp(e,t,n=!0,o=!0){t!==this._props[e]&&(this._props[e]=t,o&&this._instance&&this._update(),n&&(!0===t?this.setAttribute(G(e),\"\"):\"string\"==typeof t||\"number\"==typeof t?this.setAttribute(G(e),t+\"\"):t||this.removeAttribute(G(e))))}_update(){kl(this._createVNode(),this.shadowRoot)}_createVNode(){const e=us(this._def,w({},this._props));return this._instance||(e.ce=e=>{this._instance=e,e.isCE=!0,e.emit=(e,...t)=>{this.dispatchEvent(new CustomEvent(e,{detail:t}))};let t=this;for(;t=t&&(t.parentNode||t.host);)if(t instanceof Ni){e.parent=t._instance;break}}),e}_applyStyles(e){e&&e.forEach((e=>{const t=document.createElement(\"style\");t.textContent=e,this.shadowRoot.appendChild(t)}))}}function Ei(e=\"$style\"){{const t=ws();if(!t)return v;const n=t.type.__cssModules;if(!n)return v;const o=n[e];return o||v}}function $i(e){const t=ws();if(!t)return;const n=()=>Oi(t.subTree,e(t.proxy));Yn(n),Oo((()=>{const e=new MutationObserver(n);e.observe(t.subTree.el.parentNode,{childList:!0}),Ao((()=>e.disconnect()))}))}function Oi(e,t){if(128&e.shapeFlag){const n=e.suspense;e=n.activeBranch,n.pendingBranch&&!n.isHydrating&&n.effects.push((()=>{Oi(n.activeBranch,t)}))}for(;e.component;)e=e.component.subTree;if(1&e.shapeFlag&&e.el)Ri(e.el,t);else if(e.type===Wr)e.children.forEach((e=>Oi(e,t)));else if(e.type===Gr){let{el:n,anchor:o}=e;for(;n&&(Ri(n,t),n!==o);)n=n.nextSibling}}function Ri(e,t){if(1===e.nodeType){const n=e.style;for(const e in t)n.setProperty(`--${e}`,t[e])}}const Fi=(e,{slots:t})=>Zs(io,Ii(e),t);Fi.displayName=\"Transition\";const Pi={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String},Ai=Fi.props=w({},io.props,Pi),Mi=(e,t=[])=>{E(e)?e.forEach((e=>e(...t))):e&&e(...t)},Vi=e=>!!e&&(E(e)?e.some((e=>e.length>1)):e.length>1);function Ii(e){const t={};for(const w in e)w in Pi||(t[w]=e[w]);if(!1===e.css)return t;const{name:n=\"v\",type:o,duration:r,enterFromClass:s=`${n}-enter-from`,enterActiveClass:i=`${n}-enter-active`,enterToClass:l=`${n}-enter-to`,appearFromClass:c=s,appearActiveClass:a=i,appearToClass:u=l,leaveFromClass:p=`${n}-leave-from`,leaveActiveClass:f=`${n}-leave-active`,leaveToClass:d=`${n}-leave-to`}=e,h=function(e){if(null==e)return null;if(M(e))return[Bi(e.enter),Bi(e.leave)];{const t=Bi(e);return[t,t]}}(r),m=h&&h[0],g=h&&h[1],{onBeforeEnter:v,onEnter:y,onEnterCancelled:_,onLeave:b,onLeaveCancelled:S,onBeforeAppear:x=v,onAppear:C=y,onAppearCancelled:k=_}=t,T=(e,t,n)=>{ji(e,t?u:l),ji(e,t?a:i),n&&n()},N=(e,t)=>{e._isLeaving=!1,ji(e,p),ji(e,d),ji(e,f),t&&t()},E=e=>(t,n)=>{const r=e?C:y,i=()=>T(t,e,n);Mi(r,[t,i]),Ui((()=>{ji(t,e?c:s),Li(t,e?u:l),Vi(r)||Hi(t,o,m,i)}))};return w(t,{onBeforeEnter(e){Mi(v,[e]),Li(e,s),Li(e,i)},onBeforeAppear(e){Mi(x,[e]),Li(e,c),Li(e,a)},onEnter:E(!1),onAppear:E(!0),onLeave(e,t){e._isLeaving=!0;const n=()=>N(e,t);Li(e,p),Gi(),Li(e,f),Ui((()=>{e._isLeaving&&(ji(e,p),Li(e,d),Vi(b)||Hi(e,o,g,n))})),Mi(b,[e,n])},onEnterCancelled(e){T(e,!1),Mi(_,[e])},onAppearCancelled(e){T(e,!0),Mi(k,[e])},onLeaveCancelled(e){N(e),Mi(S,[e])}})}function Bi(e){return X(e)}function Li(e,t){t.split(/\\s+/).forEach((t=>t&&e.classList.add(t))),(e._vtc||(e._vtc=new Set)).add(t)}function ji(e,t){t.split(/\\s+/).forEach((t=>t&&e.classList.remove(t)));const{_vtc:n}=e;n&&(n.delete(t),n.size||(e._vtc=void 0))}function Ui(e){requestAnimationFrame((()=>{requestAnimationFrame(e)}))}let Di=0;function Hi(e,t,n,o){const r=e._endId=++Di,s=()=>{r===e._endId&&o()};if(n)return setTimeout(s,n);const{type:i,timeout:l,propCount:c}=Wi(e,t);if(!i)return o();const a=i+\"end\";let u=0;const p=()=>{e.removeEventListener(a,f),s()},f=t=>{t.target===e&&++u>=c&&p()};setTimeout((()=>{u<c&&p()}),l+1),e.addEventListener(a,f)}function Wi(e,t){const n=window.getComputedStyle(e),o=e=>(n[e]||\"\").split(\", \"),r=o(\"transitionDelay\"),s=o(\"transitionDuration\"),i=zi(r,s),l=o(\"animationDelay\"),c=o(\"animationDuration\"),a=zi(l,c);let u=null,p=0,f=0;\"transition\"===t?i>0&&(u=\"transition\",p=i,f=s.length):\"animation\"===t?a>0&&(u=\"animation\",p=a,f=c.length):(p=Math.max(i,a),u=p>0?i>a?\"transition\":\"animation\":null,f=u?\"transition\"===u?s.length:c.length:0);return{type:u,timeout:p,propCount:f,hasTransform:\"transition\"===u&&/\\b(transform|all)(,|$)/.test(n.transitionProperty)}}function zi(e,t){for(;e.length<t.length;)e=e.concat(e);return Math.max(...t.map(((t,n)=>Ki(t)+Ki(e[n]))))}function Ki(e){return 1e3*Number(e.slice(0,-1).replace(\",\",\".\"))}function Gi(){return document.body.offsetHeight}const qi=new WeakMap,Ji=new WeakMap,Yi={name:\"TransitionGroup\",props:w({},Ai,{tag:String,moveClass:String}),setup(e,{slots:t}){const n=ws(),o=ro();let r,s;return Fo((()=>{if(!r.length)return;const t=e.moveClass||`${e.name||\"v\"}-move`;if(!function(e,t,n){const o=e.cloneNode();e._vtc&&e._vtc.forEach((e=>{e.split(/\\s+/).forEach((e=>e&&o.classList.remove(e)))}));n.split(/\\s+/).forEach((e=>e&&o.classList.add(e))),o.style.display=\"none\";const r=1===t.nodeType?t:t.parentNode;r.appendChild(o);const{hasTransform:s}=Wi(o);return r.removeChild(o),s}(r[0].el,n.vnode.el,t))return;r.forEach(Zi),r.forEach(Qi);const o=r.filter(Xi);Gi(),o.forEach((e=>{const n=e.el,o=n.style;Li(n,t),o.transform=o.webkitTransform=o.transitionDuration=\"\";const r=n._moveCb=e=>{e&&e.target!==n||e&&!/transform$/.test(e.propertyName)||(n.removeEventListener(\"transitionend\",r),n._moveCb=null,ji(n,t))};n.addEventListener(\"transitionend\",r)}))})),()=>{const i=kt(e),l=Ii(i);let c=i.tag||Wr;r=s,s=t.default?fo(t.default()):[];for(let e=0;e<s.length;e++){const t=s[e];null!=t.key&&po(t,co(t,l,o,n))}if(r)for(let e=0;e<r.length;e++){const t=r[e];po(t,co(t,l,o,n)),qi.set(t,t.el.getBoundingClientRect())}return us(c,null,s)}}};function Zi(e){const t=e.el;t._moveCb&&t._moveCb(),t._enterCb&&t._enterCb()}function Qi(e){Ji.set(e,e.el.getBoundingClientRect())}function Xi(e){const t=qi.get(e),n=Ji.get(e),o=t.left-n.left,r=t.top-n.top;if(o||r){const t=e.el.style;return t.transform=t.webkitTransform=`translate(${o}px,${r}px)`,t.transitionDuration=\"0s\",e}}const el=e=>{const t=e.props[\"onUpdate:modelValue\"]||!1;return E(t)?e=>Z(t,e):t};function tl(e){e.target.composing=!0}function nl(e){const t=e.target;t.composing&&(t.composing=!1,t.dispatchEvent(new Event(\"input\")))}const ol={created(e,{modifiers:{lazy:t,trim:n,number:o}},r){e._assign=el(r);const s=o||r.props&&\"number\"===r.props.type;bi(e,t?\"change\":\"input\",(t=>{if(t.target.composing)return;let o=e.value;n&&(o=o.trim()),s&&(o=X(o)),e._assign(o)})),n&&bi(e,\"change\",(()=>{e.value=e.value.trim()})),t||(bi(e,\"compositionstart\",tl),bi(e,\"compositionend\",nl),bi(e,\"change\",nl))},mounted(e,{value:t}){e.value=null==t?\"\":t},beforeUpdate(e,{value:t,modifiers:{lazy:n,trim:o,number:r}},s){if(e._assign=el(s),e.composing)return;if(document.activeElement===e&&\"range\"!==e.type){if(n)return;if(o&&e.value.trim()===t)return;if((r||\"number\"===e.type)&&X(e.value)===t)return}const i=null==t?\"\":t;e.value!==i&&(e.value=i)}},rl={deep:!0,created(e,t,n){e._assign=el(n),bi(e,\"change\",(()=>{const t=e._modelValue,n=al(e),o=e.checked,r=e._assign;if(E(t)){const e=h(t,n),s=-1!==e;if(o&&!s)r(t.concat(n));else if(!o&&s){const n=[...t];n.splice(e,1),r(n)}}else if(O(t)){const e=new Set(t);o?e.add(n):e.delete(n),r(e)}else r(ul(e,o))}))},mounted:sl,beforeUpdate(e,t,n){e._assign=el(n),sl(e,t,n)}};function sl(e,{value:t,oldValue:n},o){e._modelValue=t,E(t)?e.checked=h(t,o.props.value)>-1:O(t)?e.checked=t.has(o.props.value):t!==n&&(e.checked=d(t,ul(e,!0)))}const il={created(e,{value:t},n){e.checked=d(t,n.props.value),e._assign=el(n),bi(e,\"change\",(()=>{e._assign(al(e))}))},beforeUpdate(e,{value:t,oldValue:n},o){e._assign=el(o),t!==n&&(e.checked=d(t,o.props.value))}},ll={deep:!0,created(e,{value:t,modifiers:{number:n}},o){const r=O(t);bi(e,\"change\",(()=>{const t=Array.prototype.filter.call(e.options,(e=>e.selected)).map((e=>n?X(al(e)):al(e)));e._assign(e.multiple?r?new Set(t):t:t[0])})),e._assign=el(o)},mounted(e,{value:t}){cl(e,t)},beforeUpdate(e,t,n){e._assign=el(n)},updated(e,{value:t}){cl(e,t)}};function cl(e,t){const n=e.multiple;if(!n||E(t)||O(t)){for(let o=0,r=e.options.length;o<r;o++){const r=e.options[o],s=al(r);if(n)r.selected=E(t)?h(t,s)>-1:t.has(s);else if(d(al(r),t))return void(e.selectedIndex!==o&&(e.selectedIndex=o))}n||-1===e.selectedIndex||(e.selectedIndex=-1)}}function al(e){return\"_value\"in e?e._value:e.value}function ul(e,t){const n=t?\"_trueValue\":\"_falseValue\";return n in e?e[n]:t}const pl={created(e,t,n){fl(e,t,n,null,\"created\")},mounted(e,t,n){fl(e,t,n,null,\"mounted\")},beforeUpdate(e,t,n,o){fl(e,t,n,o,\"beforeUpdate\")},updated(e,t,n,o){fl(e,t,n,o,\"updated\")}};function fl(e,t,n,o,r){const s=function(e,t){switch(e){case\"SELECT\":return ll;case\"TEXTAREA\":return ol;default:switch(t){case\"checkbox\":return rl;case\"radio\":return il;default:return ol}}}(e.tagName,n.props&&n.props.type)[r];s&&s(e,t,n,o)}const dl=[\"ctrl\",\"shift\",\"alt\",\"meta\"],hl={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>\"button\"in e&&0!==e.button,middle:e=>\"button\"in e&&1!==e.button,right:e=>\"button\"in e&&2!==e.button,exact:(e,t)=>dl.some((n=>e[`${n}Key`]&&!t.includes(n)))},ml=(e,t)=>(n,...o)=>{for(let e=0;e<t.length;e++){const o=hl[t[e]];if(o&&o(n,t))return}return e(n,...o)},gl={esc:\"escape\",space:\" \",up:\"arrow-up\",left:\"arrow-left\",right:\"arrow-right\",down:\"arrow-down\",delete:\"backspace\"},vl=(e,t)=>n=>{if(!(\"key\"in n))return;const o=G(n.key);return t.some((e=>e===o||gl[e]===o))?e(n):void 0},yl={beforeMount(e,{value:t},{transition:n}){e._vod=\"none\"===e.style.display?\"\":e.style.display,n&&t?n.beforeEnter(e):_l(e,t)},mounted(e,{value:t},{transition:n}){n&&t&&n.enter(e)},updated(e,{value:t,oldValue:n},{transition:o}){!t!=!n&&(o?t?(o.beforeEnter(e),_l(e,!0),o.enter(e)):o.leave(e,(()=>{_l(e,!1)})):_l(e,t))},beforeUnmount(e,{value:t}){_l(e,t)}};function _l(e,t){e.style.display=t?e._vod:\"none\"}const bl=w({patchProp:(e,t,r,s,i=!1,l,c,a,u)=>{\"class\"===t?function(e,t,n){const o=e._vtc;o&&(t=(t?[t,...o]:[...o]).join(\" \")),null==t?e.removeAttribute(\"class\"):n?e.setAttribute(\"class\",t):e.className=t}(e,s,i):\"style\"===t?function(e,t,n){const o=e.style,r=P(n);if(n&&!r){for(const e in n)pi(o,e,n[e]);if(t&&!P(t))for(const e in t)null==n[e]&&pi(o,e,\"\")}else{const s=o.display;r?t!==n&&(o.cssText=n):t&&e.removeAttribute(\"style\"),\"_vod\"in e&&(o.display=s)}}(e,r,s):x(t)?C(t)||Si(e,t,0,s,c):(\".\"===t[0]?(t=t.slice(1),1):\"^\"===t[0]?(t=t.slice(1),0):function(e,t,n,o){if(o)return\"innerHTML\"===t||\"textContent\"===t||!!(t in e&&Ci.test(t)&&F(n));if(\"spellcheck\"===t||\"draggable\"===t||\"translate\"===t)return!1;if(\"form\"===t)return!1;if(\"list\"===t&&\"INPUT\"===e.tagName)return!1;if(\"type\"===t&&\"TEXTAREA\"===e.tagName)return!1;if(Ci.test(t)&&P(n))return!1;return t in e}(e,t,s,i))?function(e,t,n,r,s,i,l){if(\"innerHTML\"===t||\"textContent\"===t)return r&&l(r,s,i),void(e[t]=null==n?\"\":n);if(\"value\"===t&&\"PROGRESS\"!==e.tagName&&!e.tagName.includes(\"-\")){e._value=n;const o=null==n?\"\":n;return e.value===o&&\"OPTION\"!==e.tagName||(e.value=o),void(null==n&&e.removeAttribute(t))}let c=!1;if(\"\"===n||null==n){const r=typeof e[t];\"boolean\"===r?n=o(n):null==n&&\"string\"===r?(n=\"\",c=!0):\"number\"===r&&(n=0,c=!0)}try{e[t]=n}catch(a){}c&&e.removeAttribute(t)}(e,t,s,l,c,a,u):(\"true-value\"===t?e._trueValue=s:\"false-value\"===t&&(e._falseValue=s),function(e,t,r,s,i){if(s&&t.startsWith(\"xlink:\"))null==r?e.removeAttributeNS(hi,t.slice(6,t.length)):e.setAttributeNS(hi,t,r);else{const s=n(t);null==r||s&&!o(r)?e.removeAttribute(t):e.setAttribute(t,s?\"\":r)}}(e,t,s,i))}},ai);let Sl,xl=!1;function Cl(){return Sl||(Sl=Ar(bl))}function wl(){return Sl=xl?Sl:Mr(bl),xl=!0,Sl}const kl=(...e)=>{Cl().render(...e)},Tl=(...e)=>{wl().hydrate(...e)},Nl=(...e)=>{const t=Cl().createApp(...e),{mount:n}=t;return t.mount=e=>{const o=$l(e);if(!o)return;const r=t._component;F(r)||r.render||r.template||(r.template=o.innerHTML),o.innerHTML=\"\";const s=n(o,!1,o instanceof SVGElement);return o instanceof Element&&(o.removeAttribute(\"v-cloak\"),o.setAttribute(\"data-v-app\",\"\")),s},t},El=(...e)=>{const t=wl().createApp(...e),{mount:n}=t;return t.mount=e=>{const t=$l(e);if(t)return n(t,!0,t instanceof SVGElement)},t};function $l(e){if(P(e)){return document.querySelector(e)}return e}const Ol=_;var Rl=Object.freeze({__proto__:null,render:kl,hydrate:Tl,createApp:Nl,createSSRApp:El,initDirectivesForSSR:Ol,defineCustomElement:wi,defineSSRCustomElement:ki,VueElement:Ni,useCssModule:Ei,useCssVars:$i,Transition:Fi,TransitionGroup:Yi,vModelText:ol,vModelCheckbox:rl,vModelRadio:il,vModelSelect:ll,vModelDynamic:pl,withModifiers:ml,withKeys:vl,vShow:yl,reactive:gt,ref:Ft,readonly:yt,unref:It,proxyRefs:Lt,isRef:Rt,toRef:Wt,toRefs:Dt,isProxy:wt,isReactive:St,isReadonly:xt,isShallow:Ct,customRef:Ut,triggerRef:Vt,shallowRef:Pt,shallowReactive:vt,shallowReadonly:_t,markRaw:Tt,toRaw:kt,effect:ye,stop:_e,ReactiveEffect:ge,effectScope:oe,EffectScope:ne,getCurrentScope:se,onScopeDispose:ie,computed:js,watch:Xn,watchEffect:Jn,watchPostEffect:Yn,watchSyncEffect:Zn,onBeforeMount:$o,onMounted:Oo,onBeforeUpdate:Ro,onUpdated:Fo,onBeforeUnmount:Po,onUnmounted:Ao,onActivated:So,onDeactivated:xo,onRenderTracked:Io,onRenderTriggered:Vo,onErrorCaptured:Bo,onServerPrefetch:Mo,provide:Gn,inject:qn,nextTick:dn,defineComponent:ho,defineAsyncComponent:go,useAttrs:Ks,useSlots:zs,defineProps:Us,defineEmits:Ds,defineExpose:Hs,withDefaults:Ws,mergeDefaults:qs,createPropsRestProxy:Js,withAsyncContext:Ys,getCurrentInstance:ws,h:Zs,createVNode:us,cloneVNode:fs,mergeProps:_s,isVNode:os,Fragment:Wr,Text:zr,Comment:Kr,Static:Gr,Teleport:Hr,Suspense:Un,KeepAlive:_o,BaseTransition:io,withDirectives:Lo,useSSRContext:Xs,ssrContextKey:Qs,createRenderer:Ar,createHydrationRenderer:Mr,queuePostFlushCb:vn,warn:Gt,handleError:Qt,callWithErrorHandling:Yt,callWithAsyncErrorHandling:Zt,resolveComponent:Uo,resolveDirective:Wo,resolveDynamicComponent:Ho,registerRuntimeCompiler:Fs,isRuntimeOnly:Ps,useTransitionState:ro,resolveTransitionHooks:co,setTransitionHooks:po,getTransitionRawChildren:fo,initCustomFormatter:ei,get devtools(){return xn},setDevtoolsHook:wn,withCtx:An,pushScopeId:Rn,popScopeId:Fn,withScopeId:Pn,renderList:Go,toHandlers:Zo,renderSlot:Jo,createSlots:qo,withMemo:ti,isMemoSame:ni,openBlock:Yr,createBlock:ns,setBlockTracking:Xr,createTextVNode:ds,createCommentVNode:ms,createStaticVNode:hs,createElementVNode:as,createElementBlock:ts,guardReactiveProps:ps,toDisplayString:m,camelize:z,capitalize:q,toHandlerKey:J,normalizeProps:a,normalizeClass:c,normalizeStyle:r,transformVNodeArgs:ss,version:oi,ssrUtils:null,resolveFilter:null,compatUtils:null});function Fl(e){throw e}function Pl(e){}function Al(e,t,n,o){const r=new SyntaxError(String(e));return r.code=e,r.loc=t,r}const Ml=Symbol(\"\"),Vl=Symbol(\"\"),Il=Symbol(\"\"),Bl=Symbol(\"\"),Ll=Symbol(\"\"),jl=Symbol(\"\"),Ul=Symbol(\"\"),Dl=Symbol(\"\"),Hl=Symbol(\"\"),Wl=Symbol(\"\"),zl=Symbol(\"\"),Kl=Symbol(\"\"),Gl=Symbol(\"\"),ql=Symbol(\"\"),Jl=Symbol(\"\"),Yl=Symbol(\"\"),Zl=Symbol(\"\"),Ql=Symbol(\"\"),Xl=Symbol(\"\"),ec=Symbol(\"\"),tc=Symbol(\"\"),nc=Symbol(\"\"),oc=Symbol(\"\"),rc=Symbol(\"\"),sc=Symbol(\"\"),ic=Symbol(\"\"),lc=Symbol(\"\"),cc=Symbol(\"\"),ac=Symbol(\"\"),uc=Symbol(\"\"),pc=Symbol(\"\"),fc=Symbol(\"\"),dc=Symbol(\"\"),hc=Symbol(\"\"),mc=Symbol(\"\"),gc=Symbol(\"\"),vc=Symbol(\"\"),yc=Symbol(\"\"),_c=Symbol(\"\"),bc={[Ml]:\"Fragment\",[Vl]:\"Teleport\",[Il]:\"Suspense\",[Bl]:\"KeepAlive\",[Ll]:\"BaseTransition\",[jl]:\"openBlock\",[Ul]:\"createBlock\",[Dl]:\"createElementBlock\",[Hl]:\"createVNode\",[Wl]:\"createElementVNode\",[zl]:\"createCommentVNode\",[Kl]:\"createTextVNode\",[Gl]:\"createStaticVNode\",[ql]:\"resolveComponent\",[Jl]:\"resolveDynamicComponent\",[Yl]:\"resolveDirective\",[Zl]:\"resolveFilter\",[Ql]:\"withDirectives\",[Xl]:\"renderList\",[ec]:\"renderSlot\",[tc]:\"createSlots\",[nc]:\"toDisplayString\",[oc]:\"mergeProps\",[rc]:\"normalizeClass\",[sc]:\"normalizeStyle\",[ic]:\"normalizeProps\",[lc]:\"guardReactiveProps\",[cc]:\"toHandlers\",[ac]:\"camelize\",[uc]:\"capitalize\",[pc]:\"toHandlerKey\",[fc]:\"setBlockTracking\",[dc]:\"pushScopeId\",[hc]:\"popScopeId\",[mc]:\"withCtx\",[gc]:\"unref\",[vc]:\"isRef\",[yc]:\"withMemo\",[_c]:\"isMemoSame\"};const Sc={source:\"\",start:{line:1,column:1,offset:0},end:{line:1,column:1,offset:0}};function xc(e,t,n,o,r,s,i,l=!1,c=!1,a=!1,u=Sc){return e&&(l?(e.helper(jl),e.helper(Zc(e.inSSR,a))):e.helper(Yc(e.inSSR,a)),i&&e.helper(Ql)),{type:13,tag:t,props:n,children:o,patchFlag:r,dynamicProps:s,directives:i,isBlock:l,disableTracking:c,isComponent:a,loc:u}}function Cc(e,t=Sc){return{type:17,loc:t,elements:e}}function wc(e,t=Sc){return{type:15,loc:t,properties:e}}function kc(e,t){return{type:16,loc:Sc,key:P(e)?Tc(e,!0):e,value:t}}function Tc(e,t=!1,n=Sc,o=0){return{type:4,loc:n,content:e,isStatic:t,constType:t?3:o}}function Nc(e,t=Sc){return{type:8,loc:t,children:e}}function Ec(e,t=[],n=Sc){return{type:14,loc:n,callee:e,arguments:t}}function $c(e,t,n=!1,o=!1,r=Sc){return{type:18,params:e,returns:t,newline:n,isSlot:o,loc:r}}function Oc(e,t,n,o=!0){return{type:19,test:e,consequent:t,alternate:n,newline:o,loc:Sc}}const Rc=e=>4===e.type&&e.isStatic,Fc=(e,t)=>e===t||e===G(t);function Pc(e){return Fc(e,\"Teleport\")?Vl:Fc(e,\"Suspense\")?Il:Fc(e,\"KeepAlive\")?Bl:Fc(e,\"BaseTransition\")?Ll:void 0}const Ac=/^\\d|[^\\$\\w]/,Mc=e=>!Ac.test(e),Vc=/[A-Za-z_$\\xA0-\\uFFFF]/,Ic=/[\\.\\?\\w$\\xA0-\\uFFFF]/,Bc=/\\s+[.[]\\s*|\\s*[.[]\\s+/g,Lc=e=>{e=e.trim().replace(Bc,(e=>e.trim()));let t=0,n=[],o=0,r=0,s=null;for(let i=0;i<e.length;i++){const l=e.charAt(i);switch(t){case 0:if(\"[\"===l)n.push(t),t=1,o++;else if(\"(\"===l)n.push(t),t=2,r++;else if(!(0===i?Vc:Ic).test(l))return!1;break;case 1:\"'\"===l||'\"'===l||\"`\"===l?(n.push(t),t=3,s=l):\"[\"===l?o++:\"]\"===l&&(--o||(t=n.pop()));break;case 2:if(\"'\"===l||'\"'===l||\"`\"===l)n.push(t),t=3,s=l;else if(\"(\"===l)r++;else if(\")\"===l){if(i===e.length-1)return!1;--r||(t=n.pop())}break;case 3:l===s&&(t=n.pop(),s=null)}}return!o&&!r};function jc(e,t,n){const o={source:e.source.slice(t,t+n),start:Uc(e.start,e.source,t),end:e.end};return null!=n&&(o.end=Uc(e.start,e.source,t+n)),o}function Uc(e,t,n=t.length){return Dc(w({},e),t,n)}function Dc(e,t,n=t.length){let o=0,r=-1;for(let s=0;s<n;s++)10===t.charCodeAt(s)&&(o++,r=s);return e.offset+=n,e.line+=o,e.column=-1===r?e.column+n:n-r,e}function Hc(e,t,n=!1){for(let o=0;o<e.props.length;o++){const r=e.props[o];if(7===r.type&&(n||r.exp)&&(P(t)?r.name===t:t.test(r.name)))return r}}function Wc(e,t,n=!1,o=!1){for(let r=0;r<e.props.length;r++){const s=e.props[r];if(6===s.type){if(n)continue;if(s.name===t&&(s.value||o))return s}else if(\"bind\"===s.name&&(s.exp||o)&&zc(s.arg,t))return s}}function zc(e,t){return!(!e||!Rc(e)||e.content!==t)}function Kc(e){return 5===e.type||2===e.type}function Gc(e){return 7===e.type&&\"slot\"===e.name}function qc(e){return 1===e.type&&3===e.tagType}function Jc(e){return 1===e.type&&2===e.tagType}function Yc(e,t){return e||t?Hl:Wl}function Zc(e,t){return e||t?Ul:Dl}const Qc=new Set([ic,lc]);function Xc(e,t=[]){if(e&&!P(e)&&14===e.type){const n=e.callee;if(!P(n)&&Qc.has(n))return Xc(e.arguments[0],t.concat(e))}return[e,t]}function ea(e,t,n){let o,r,s=13===e.type?e.props:e.arguments[2],i=[];if(s&&!P(s)&&14===s.type){const e=Xc(s);s=e[0],i=e[1],r=i[i.length-1]}if(null==s||P(s))o=wc([t]);else if(14===s.type){const e=s.arguments[0];P(e)||15!==e.type?s.callee===cc?o=Ec(n.helper(oc),[wc([t]),s]):s.arguments.unshift(wc([t])):e.properties.unshift(t),!o&&(o=s)}else if(15===s.type){let e=!1;if(4===t.key.type){const n=t.key.content;e=s.properties.some((e=>4===e.key.type&&e.key.content===n))}e||s.properties.unshift(t),o=s}else o=Ec(n.helper(oc),[wc([t]),s]),r&&r.callee===lc&&(r=i[i.length-2]);13===e.type?r?r.arguments[0]=o:e.props=o:r?r.arguments[0]=o:e.arguments[2]=o}function ta(e,t){return`_${t}_${e.replace(/[^\\w]/g,((t,n)=>\"-\"===t?\"_\":e.charCodeAt(n).toString()))}`}function na(e,{helper:t,removeHelper:n,inSSR:o}){e.isBlock||(e.isBlock=!0,n(Yc(o,e.isComponent)),t(jl),t(Zc(o,e.isComponent)))}const oa=/&(gt|lt|amp|apos|quot);/g,ra={gt:\">\",lt:\"<\",amp:\"&\",apos:\"'\",quot:'\"'},sa={delimiters:[\"{{\",\"}}\"],getNamespace:()=>0,getTextMode:()=>0,isVoidTag:b,isPreTag:b,isCustomElement:b,decodeEntities:e=>e.replace(oa,((e,t)=>ra[t])),onError:Fl,onWarn:Pl,comments:!1};function ia(e,t={}){const n=function(e,t){const n=w({},sa);let o;for(o in t)n[o]=void 0===t[o]?sa[o]:t[o];return{options:n,column:1,line:1,offset:0,originalSource:e,source:e,inPre:!1,inVPre:!1,onWarn:n.onWarn}}(e,t),o=ba(n);return function(e,t=Sc){return{type:0,children:e,helpers:[],components:[],directives:[],hoists:[],imports:[],cached:0,temps:0,codegenNode:void 0,loc:t}}(la(n,0,[]),Sa(n,o))}function la(e,t,n){const o=xa(n),r=o?o.ns:0,s=[];for(;!Na(e,t,n);){const i=e.source;let l;if(0===t||1===t)if(!e.inVPre&&Ca(i,e.options.delimiters[0]))l=va(e,t);else if(0===t&&\"<\"===i[0])if(1===i.length);else if(\"!\"===i[1])l=Ca(i,\"\\x3c!--\")?ua(e):Ca(i,\"<!DOCTYPE\")?pa(e):Ca(i,\"<![CDATA[\")&&0!==r?aa(e,n):pa(e);else if(\"/\"===i[1])if(2===i.length);else{if(\">\"===i[2]){wa(e,3);continue}if(/[a-z]/i.test(i[2])){ha(e,1,o);continue}l=pa(e)}else/[a-z]/i.test(i[1])?l=fa(e,n):\"?\"===i[1]&&(l=pa(e));if(l||(l=ya(e,t)),E(l))for(let e=0;e<l.length;e++)ca(s,l[e]);else ca(s,l)}let i=!1;if(2!==t&&1!==t){const t=\"preserve\"!==e.options.whitespace;for(let n=0;n<s.length;n++){const o=s[n];if(e.inPre||2!==o.type)3!==o.type||e.options.comments||(i=!0,s[n]=null);else if(/[^\\t\\r\\n\\f ]/.test(o.content))t&&(o.content=o.content.replace(/[\\t\\r\\n\\f ]+/g,\" \"));else{const e=s[n-1],r=s[n+1];!e||!r||t&&(3===e.type||3===r.type||1===e.type&&1===r.type&&/[\\r\\n]/.test(o.content))?(i=!0,s[n]=null):o.content=\" \"}}if(e.inPre&&o&&e.options.isPreTag(o.tag)){const e=s[0];e&&2===e.type&&(e.content=e.content.replace(/^\\r?\\n/,\"\"))}}return i?s.filter(Boolean):s}function ca(e,t){if(2===t.type){const n=xa(e);if(n&&2===n.type&&n.loc.end.offset===t.loc.start.offset)return n.content+=t.content,n.loc.end=t.loc.end,void(n.loc.source+=t.loc.source)}e.push(t)}function aa(e,t){wa(e,9);const n=la(e,3,t);return 0===e.source.length||wa(e,3),n}function ua(e){const t=ba(e);let n;const o=/--(\\!)?>/.exec(e.source);if(o){n=e.source.slice(4,o.index);const t=e.source.slice(0,o.index);let r=1,s=0;for(;-1!==(s=t.indexOf(\"\\x3c!--\",r));)wa(e,s-r+1),r=s+1;wa(e,o.index+o[0].length-r+1)}else n=e.source.slice(4),wa(e,e.source.length);return{type:3,content:n,loc:Sa(e,t)}}function pa(e){const t=ba(e),n=\"?\"===e.source[1]?1:2;let o;const r=e.source.indexOf(\">\");return-1===r?(o=e.source.slice(n),wa(e,e.source.length)):(o=e.source.slice(n,r),wa(e,r+1)),{type:3,content:o,loc:Sa(e,t)}}function fa(e,t){const n=e.inPre,o=e.inVPre,r=xa(t),s=ha(e,0,r),i=e.inPre&&!n,l=e.inVPre&&!o;if(s.isSelfClosing||e.options.isVoidTag(s.tag))return i&&(e.inPre=!1),l&&(e.inVPre=!1),s;t.push(s);const c=e.options.getTextMode(s,r),a=la(e,c,t);if(t.pop(),s.children=a,Ea(e.source,s.tag))ha(e,1,r);else if(0===e.source.length&&\"script\"===s.tag.toLowerCase()){const e=a[0];e&&Ca(e.loc.source,\"\\x3c!--\")}return s.loc=Sa(e,s.loc.start),i&&(e.inPre=!1),l&&(e.inVPre=!1),s}const da=e(\"if,else,else-if,for,slot\");function ha(e,t,n){const o=ba(e),r=/^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(e.source),s=r[1],i=e.options.getNamespace(s,n);wa(e,r[0].length),ka(e);const l=ba(e),c=e.source;e.options.isPreTag(s)&&(e.inPre=!0);let a=ma(e,t);0===t&&!e.inVPre&&a.some((e=>7===e.type&&\"pre\"===e.name))&&(e.inVPre=!0,w(e,l),e.source=c,a=ma(e,t).filter((e=>\"v-pre\"!==e.name)));let u=!1;if(0===e.source.length||(u=Ca(e.source,\"/>\"),wa(e,u?2:1)),1===t)return;let p=0;return e.inVPre||(\"slot\"===s?p=2:\"template\"===s?a.some((e=>7===e.type&&da(e.name)))&&(p=3):function(e,t,n){const o=n.options;if(o.isCustomElement(e))return!1;if(\"component\"===e||/^[A-Z]/.test(e)||Pc(e)||o.isBuiltInComponent&&o.isBuiltInComponent(e)||o.isNativeTag&&!o.isNativeTag(e))return!0;for(let r=0;r<t.length;r++){const e=t[r];if(6===e.type){if(\"is\"===e.name&&e.value&&e.value.content.startsWith(\"vue:\"))return!0}else{if(\"is\"===e.name)return!0;\"bind\"===e.name&&zc(e.arg,\"is\")}}}(s,a,e)&&(p=1)),{type:1,ns:i,tag:s,tagType:p,props:a,isSelfClosing:u,children:[],loc:Sa(e,o),codegenNode:void 0}}function ma(e,t){const n=[],o=new Set;for(;e.source.length>0&&!Ca(e.source,\">\")&&!Ca(e.source,\"/>\");){if(Ca(e.source,\"/\")){wa(e,1),ka(e);continue}const r=ga(e,o);6===r.type&&r.value&&\"class\"===r.name&&(r.value.content=r.value.content.replace(/\\s+/g,\" \").trim()),0===t&&n.push(r),/^[^\\t\\r\\n\\f />]/.test(e.source),ka(e)}return n}function ga(e,t){const n=ba(e),o=/^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(e.source)[0];t.has(o),t.add(o);{const e=/[\"'<]/g;let t;for(;t=e.exec(o););}let r;wa(e,o.length),/^[\\t\\r\\n\\f ]*=/.test(e.source)&&(ka(e),wa(e,1),ka(e),r=function(e){const t=ba(e);let n;const o=e.source[0],r='\"'===o||\"'\"===o;if(r){wa(e,1);const t=e.source.indexOf(o);-1===t?n=_a(e,e.source.length,4):(n=_a(e,t,4),wa(e,1))}else{const t=/^[^\\t\\r\\n\\f >]+/.exec(e.source);if(!t)return;const o=/[\"'<=`]/g;let r;for(;r=o.exec(t[0]););n=_a(e,t[0].length,4)}return{content:n,isQuoted:r,loc:Sa(e,t)}}(e));const s=Sa(e,n);if(!e.inVPre&&/^(v-[A-Za-z0-9-]|:|\\.|@|#)/.test(o)){const t=/(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(o);let i,l=Ca(o,\".\"),c=t[1]||(l||Ca(o,\":\")?\"bind\":Ca(o,\"@\")?\"on\":\"slot\");if(t[2]){const r=\"slot\"===c,s=o.lastIndexOf(t[2]),l=Sa(e,Ta(e,n,s),Ta(e,n,s+t[2].length+(r&&t[3]||\"\").length));let a=t[2],u=!0;a.startsWith(\"[\")?(u=!1,a=a.endsWith(\"]\")?a.slice(1,a.length-1):a.slice(1)):r&&(a+=t[3]||\"\"),i={type:4,content:a,isStatic:u,constType:u?3:0,loc:l}}if(r&&r.isQuoted){const e=r.loc;e.start.offset++,e.start.column++,e.end=Uc(e.start,r.content),e.source=e.source.slice(1,-1)}const a=t[3]?t[3].slice(1).split(\".\"):[];return l&&a.push(\"prop\"),{type:7,name:c,exp:r&&{type:4,content:r.content,isStatic:!1,constType:0,loc:r.loc},arg:i,modifiers:a,loc:s}}return!e.inVPre&&Ca(o,\"v-\"),{type:6,name:o,value:r&&{type:2,content:r.content,loc:r.loc},loc:s}}function va(e,t){const[n,o]=e.options.delimiters,r=e.source.indexOf(o,n.length);if(-1===r)return;const s=ba(e);wa(e,n.length);const i=ba(e),l=ba(e),c=r-n.length,a=e.source.slice(0,c),u=_a(e,c,t),p=u.trim(),f=u.indexOf(p);f>0&&Dc(i,a,f);return Dc(l,a,c-(u.length-p.length-f)),wa(e,o.length),{type:5,content:{type:4,isStatic:!1,constType:0,content:p,loc:Sa(e,i,l)},loc:Sa(e,s)}}function ya(e,t){const n=3===t?[\"]]>\"]:[\"<\",e.options.delimiters[0]];let o=e.source.length;for(let s=0;s<n.length;s++){const t=e.source.indexOf(n[s],1);-1!==t&&o>t&&(o=t)}const r=ba(e);return{type:2,content:_a(e,o,t),loc:Sa(e,r)}}function _a(e,t,n){const o=e.source.slice(0,t);return wa(e,t),2!==n&&3!==n&&o.includes(\"&\")?e.options.decodeEntities(o,4===n):o}function ba(e){const{column:t,line:n,offset:o}=e;return{column:t,line:n,offset:o}}function Sa(e,t,n){return{start:t,end:n=n||ba(e),source:e.originalSource.slice(t.offset,n.offset)}}function xa(e){return e[e.length-1]}function Ca(e,t){return e.startsWith(t)}function wa(e,t){const{source:n}=e;Dc(e,n,t),e.source=n.slice(t)}function ka(e){const t=/^[\\t\\r\\n\\f ]+/.exec(e.source);t&&wa(e,t[0].length)}function Ta(e,t,n){return Uc(t,e.originalSource.slice(t.offset,n),n)}function Na(e,t,n){const o=e.source;switch(t){case 0:if(Ca(o,\"</\"))for(let e=n.length-1;e>=0;--e)if(Ea(o,n[e].tag))return!0;break;case 1:case 2:{const e=xa(n);if(e&&Ea(o,e.tag))return!0;break}case 3:if(Ca(o,\"]]>\"))return!0}return!o}function Ea(e,t){return Ca(e,\"</\")&&e.slice(2,2+t.length).toLowerCase()===t.toLowerCase()&&/[\\t\\r\\n\\f />]/.test(e[2+t.length]||\">\")}function $a(e,t){Ra(e,t,Oa(e,e.children[0]))}function Oa(e,t){const{children:n}=e;return 1===n.length&&1===t.type&&!Jc(t)}function Ra(e,t,n=!1){const{children:o}=e,r=o.length;let s=0;for(let i=0;i<o.length;i++){const e=o[i];if(1===e.type&&0===e.tagType){const o=n?0:Fa(e,t);if(o>0){if(o>=2){e.codegenNode.patchFlag=\"-1\",e.codegenNode=t.hoist(e.codegenNode),s++;continue}}else{const n=e.codegenNode;if(13===n.type){const o=Ia(n);if((!o||512===o||1===o)&&Ma(e,t)>=2){const o=Va(e);o&&(n.props=t.hoist(o))}n.dynamicProps&&(n.dynamicProps=t.hoist(n.dynamicProps))}}}else 12===e.type&&Fa(e.content,t)>=2&&(e.codegenNode=t.hoist(e.codegenNode),s++);if(1===e.type){const n=1===e.tagType;n&&t.scopes.vSlot++,Ra(e,t),n&&t.scopes.vSlot--}else if(11===e.type)Ra(e,t,1===e.children.length);else if(9===e.type)for(let n=0;n<e.branches.length;n++)Ra(e.branches[n],t,1===e.branches[n].children.length)}s&&t.transformHoist&&t.transformHoist(o,t,e),s&&s===r&&1===e.type&&0===e.tagType&&e.codegenNode&&13===e.codegenNode.type&&E(e.codegenNode.children)&&(e.codegenNode.children=t.hoist(Cc(e.codegenNode.children)))}function Fa(e,t){const{constantCache:n}=t;switch(e.type){case 1:if(0!==e.tagType)return 0;const o=n.get(e);if(void 0!==o)return o;const r=e.codegenNode;if(13!==r.type)return 0;if(r.isBlock&&\"svg\"!==e.tag&&\"foreignObject\"!==e.tag)return 0;if(Ia(r))return n.set(e,0),0;{let o=3;const s=Ma(e,t);if(0===s)return n.set(e,0),0;s<o&&(o=s);for(let r=0;r<e.children.length;r++){const s=Fa(e.children[r],t);if(0===s)return n.set(e,0),0;s<o&&(o=s)}if(o>1)for(let r=0;r<e.props.length;r++){const s=e.props[r];if(7===s.type&&\"bind\"===s.name&&s.exp){const r=Fa(s.exp,t);if(0===r)return n.set(e,0),0;r<o&&(o=r)}}if(r.isBlock){for(let t=0;t<e.props.length;t++){if(7===e.props[t].type)return n.set(e,0),0}t.removeHelper(jl),t.removeHelper(Zc(t.inSSR,r.isComponent)),r.isBlock=!1,t.helper(Yc(t.inSSR,r.isComponent))}return n.set(e,o),o}case 2:case 3:return 3;case 9:case 11:case 10:default:return 0;case 5:case 12:return Fa(e.content,t);case 4:return e.constType;case 8:let s=3;for(let n=0;n<e.children.length;n++){const o=e.children[n];if(P(o)||A(o))continue;const r=Fa(o,t);if(0===r)return 0;r<s&&(s=r)}return s}}const Pa=new Set([rc,sc,ic,lc]);function Aa(e,t){if(14===e.type&&!P(e.callee)&&Pa.has(e.callee)){const n=e.arguments[0];if(4===n.type)return Fa(n,t);if(14===n.type)return Aa(n,t)}return 0}function Ma(e,t){let n=3;const o=Va(e);if(o&&15===o.type){const{properties:e}=o;for(let o=0;o<e.length;o++){const{key:r,value:s}=e[o],i=Fa(r,t);if(0===i)return i;let l;if(i<n&&(n=i),l=4===s.type?Fa(s,t):14===s.type?Aa(s,t):0,0===l)return l;l<n&&(n=l)}}return n}function Va(e){const t=e.codegenNode;if(13===t.type)return t.props}function Ia(e){const t=e.patchFlag;return t?parseInt(t,10):void 0}function Ba(e,{filename:t=\"\",prefixIdentifiers:n=!1,hoistStatic:o=!1,cacheHandlers:r=!1,nodeTransforms:s=[],directiveTransforms:i={},transformHoist:l=null,isBuiltInComponent:c=_,isCustomElement:a=_,expressionPlugins:u=[],scopeId:p=null,slotted:f=!0,ssr:d=!1,inSSR:h=!1,ssrCssVars:m=\"\",bindingMetadata:g=v,inline:y=!1,isTS:b=!1,onError:S=Fl,onWarn:x=Pl,compatConfig:C}){const w=t.replace(/\\?.*$/,\"\").match(/([^/\\\\]+)\\.\\w+$/),k={selfName:w&&q(z(w[1])),prefixIdentifiers:n,hoistStatic:o,cacheHandlers:r,nodeTransforms:s,directiveTransforms:i,transformHoist:l,isBuiltInComponent:c,isCustomElement:a,expressionPlugins:u,scopeId:p,slotted:f,ssr:d,inSSR:h,ssrCssVars:m,bindingMetadata:g,inline:y,isTS:b,onError:S,onWarn:x,compatConfig:C,root:e,helpers:new Map,components:new Set,directives:new Set,hoists:[],imports:[],constantCache:new Map,temps:0,cached:0,identifiers:Object.create(null),scopes:{vFor:0,vSlot:0,vPre:0,vOnce:0},parent:null,currentNode:e,childIndex:0,inVOnce:!1,helper(e){const t=k.helpers.get(e)||0;return k.helpers.set(e,t+1),e},removeHelper(e){const t=k.helpers.get(e);if(t){const n=t-1;n?k.helpers.set(e,n):k.helpers.delete(e)}},helperString:e=>`_${bc[k.helper(e)]}`,replaceNode(e){k.parent.children[k.childIndex]=k.currentNode=e},removeNode(e){const t=e?k.parent.children.indexOf(e):k.currentNode?k.childIndex:-1;e&&e!==k.currentNode?k.childIndex>t&&(k.childIndex--,k.onNodeRemoved()):(k.currentNode=null,k.onNodeRemoved()),k.parent.children.splice(t,1)},onNodeRemoved:()=>{},addIdentifiers(e){},removeIdentifiers(e){},hoist(e){P(e)&&(e=Tc(e)),k.hoists.push(e);const t=Tc(`_hoisted_${k.hoists.length}`,!1,e.loc,2);return t.hoisted=e,t},cache:(e,t=!1)=>function(e,t,n=!1){return{type:20,index:e,value:t,isVNode:n,loc:Sc}}(k.cached++,e,t)};return k}function La(e,t){const n=Ba(e,t);ja(e,n),t.hoistStatic&&$a(e,n),t.ssr||function(e,t){const{helper:n}=t,{children:o}=e;if(1===o.length){const n=o[0];if(Oa(e,n)&&n.codegenNode){const o=n.codegenNode;13===o.type&&na(o,t),e.codegenNode=o}else e.codegenNode=n}else if(o.length>1){let o=64;e.codegenNode=xc(t,n(Ml),void 0,e.children,o+\"\",void 0,void 0,!0,void 0,!1)}}(e,n),e.helpers=[...n.helpers.keys()],e.components=[...n.components],e.directives=[...n.directives],e.imports=n.imports,e.hoists=n.hoists,e.temps=n.temps,e.cached=n.cached}function ja(e,t){t.currentNode=e;const{nodeTransforms:n}=t,o=[];for(let s=0;s<n.length;s++){const r=n[s](e,t);if(r&&(E(r)?o.push(...r):o.push(r)),!t.currentNode)return;e=t.currentNode}switch(e.type){case 3:t.ssr||t.helper(zl);break;case 5:t.ssr||t.helper(nc);break;case 9:for(let n=0;n<e.branches.length;n++)ja(e.branches[n],t);break;case 10:case 11:case 1:case 0:!function(e,t){let n=0;const o=()=>{n--};for(;n<e.children.length;n++){const r=e.children[n];P(r)||(t.parent=e,t.childIndex=n,t.onNodeRemoved=o,ja(r,t))}}(e,t)}t.currentNode=e;let r=o.length;for(;r--;)o[r]()}function Ua(e,t){const n=P(e)?t=>t===e:t=>e.test(t);return(e,o)=>{if(1===e.type){const{props:r}=e;if(3===e.tagType&&r.some(Gc))return;const s=[];for(let i=0;i<r.length;i++){const l=r[i];if(7===l.type&&n(l.name)){r.splice(i,1),i--;const n=t(e,l,o);n&&s.push(n)}}return s}}}const Da=e=>`${bc[e]}: _${bc[e]}`;function Ha(e,t={}){const n=function(e,{mode:t=\"function\",prefixIdentifiers:n=\"module\"===t,sourceMap:o=!1,filename:r=\"template.vue.html\",scopeId:s=null,optimizeImports:i=!1,runtimeGlobalName:l=\"Vue\",runtimeModuleName:c=\"vue\",ssrRuntimeModuleName:a=\"vue/server-renderer\",ssr:u=!1,isTS:p=!1,inSSR:f=!1}){const d={mode:t,prefixIdentifiers:n,sourceMap:o,filename:r,scopeId:s,optimizeImports:i,runtimeGlobalName:l,runtimeModuleName:c,ssrRuntimeModuleName:a,ssr:u,isTS:p,inSSR:f,source:e.loc.source,code:\"\",column:1,line:1,offset:0,indentLevel:0,pure:!1,map:void 0,helper:e=>`_${bc[e]}`,push(e,t){d.code+=e},indent(){h(++d.indentLevel)},deindent(e=!1){e?--d.indentLevel:h(--d.indentLevel)},newline(){h(d.indentLevel)}};function h(e){d.push(\"\\n\"+\"  \".repeat(e))}return d}(e,t);t.onContextCreated&&t.onContextCreated(n);const{mode:o,push:r,prefixIdentifiers:s,indent:i,deindent:l,newline:c,ssr:a}=n,u=e.helpers.length>0,p=!s&&\"module\"!==o;!function(e,t){const{push:n,newline:o,runtimeGlobalName:r}=t,s=r;if(e.helpers.length>0&&(n(`const _Vue = ${s}\\n`),e.hoists.length)){n(`const { ${[Hl,Wl,zl,Kl,Gl].filter((t=>e.helpers.includes(t))).map(Da).join(\", \")} } = _Vue\\n`)}(function(e,t){if(!e.length)return;t.pure=!0;const{push:n,newline:o}=t;o();for(let r=0;r<e.length;r++){const s=e[r];s&&(n(`const _hoisted_${r+1} = `),Ga(s,t),o())}t.pure=!1})(e.hoists,t),o(),n(\"return \")}(e,n);if(r(`function ${a?\"ssrRender\":\"render\"}(${(a?[\"_ctx\",\"_push\",\"_parent\",\"_attrs\"]:[\"_ctx\",\"_cache\"]).join(\", \")}) {`),i(),p&&(r(\"with (_ctx) {\"),i(),u&&(r(`const { ${e.helpers.map(Da).join(\", \")} } = _Vue`),r(\"\\n\"),c())),e.components.length&&(Wa(e.components,\"component\",n),(e.directives.length||e.temps>0)&&c()),e.directives.length&&(Wa(e.directives,\"directive\",n),e.temps>0&&c()),e.temps>0){r(\"let \");for(let t=0;t<e.temps;t++)r(`${t>0?\", \":\"\"}_temp${t}`)}return(e.components.length||e.directives.length||e.temps)&&(r(\"\\n\"),c()),a||r(\"return \"),e.codegenNode?Ga(e.codegenNode,n):r(\"null\"),p&&(l(),r(\"}\")),l(),r(\"}\"),{ast:e,code:n.code,preamble:\"\",map:n.map?n.map.toJSON():void 0}}function Wa(e,t,{helper:n,push:o,newline:r,isTS:s}){const i=n(\"component\"===t?ql:Yl);for(let l=0;l<e.length;l++){let n=e[l];const c=n.endsWith(\"__self\");c&&(n=n.slice(0,-6)),o(`const ${ta(n,t)} = ${i}(${JSON.stringify(n)}${c?\", true\":\"\"})${s?\"!\":\"\"}`),l<e.length-1&&r()}}function za(e,t){const n=e.length>3||!1;t.push(\"[\"),n&&t.indent(),Ka(e,t,n),n&&t.deindent(),t.push(\"]\")}function Ka(e,t,n=!1,o=!0){const{push:r,newline:s}=t;for(let i=0;i<e.length;i++){const l=e[i];P(l)?r(l):E(l)?za(l,t):Ga(l,t),i<e.length-1&&(n?(o&&r(\",\"),s()):o&&r(\", \"))}}function Ga(e,t){if(P(e))t.push(e);else if(A(e))t.push(t.helper(e));else switch(e.type){case 1:case 9:case 11:case 12:Ga(e.codegenNode,t);break;case 2:!function(e,t){t.push(JSON.stringify(e.content),e)}(e,t);break;case 4:qa(e,t);break;case 5:!function(e,t){const{push:n,helper:o,pure:r}=t;r&&n(\"/*#__PURE__*/\");n(`${o(nc)}(`),Ga(e.content,t),n(\")\")}(e,t);break;case 8:Ja(e,t);break;case 3:!function(e,t){const{push:n,helper:o,pure:r}=t;r&&n(\"/*#__PURE__*/\");n(`${o(zl)}(${JSON.stringify(e.content)})`,e)}(e,t);break;case 13:!function(e,t){const{push:n,helper:o,pure:r}=t,{tag:s,props:i,children:l,patchFlag:c,dynamicProps:a,directives:u,isBlock:p,disableTracking:f,isComponent:d}=e;u&&n(o(Ql)+\"(\");p&&n(`(${o(jl)}(${f?\"true\":\"\"}), `);r&&n(\"/*#__PURE__*/\");const h=p?Zc(t.inSSR,d):Yc(t.inSSR,d);n(o(h)+\"(\",e),Ka(function(e){let t=e.length;for(;t--&&null==e[t];);return e.slice(0,t+1).map((e=>e||\"null\"))}([s,i,l,c,a]),t),n(\")\"),p&&n(\")\");u&&(n(\", \"),Ga(u,t),n(\")\"))}(e,t);break;case 14:!function(e,t){const{push:n,helper:o,pure:r}=t,s=P(e.callee)?e.callee:o(e.callee);r&&n(\"/*#__PURE__*/\");n(s+\"(\",e),Ka(e.arguments,t),n(\")\")}(e,t);break;case 15:!function(e,t){const{push:n,indent:o,deindent:r,newline:s}=t,{properties:i}=e;if(!i.length)return void n(\"{}\",e);const l=i.length>1||!1;n(l?\"{\":\"{ \"),l&&o();for(let c=0;c<i.length;c++){const{key:e,value:o}=i[c];Ya(e,t),n(\": \"),Ga(o,t),c<i.length-1&&(n(\",\"),s())}l&&r(),n(l?\"}\":\" }\")}(e,t);break;case 17:!function(e,t){za(e.elements,t)}(e,t);break;case 18:!function(e,t){const{push:n,indent:o,deindent:r}=t,{params:s,returns:i,body:l,newline:c,isSlot:a}=e;a&&n(`_${bc[mc]}(`);n(\"(\",e),E(s)?Ka(s,t):s&&Ga(s,t);n(\") => \"),(c||l)&&(n(\"{\"),o());i?(c&&n(\"return \"),E(i)?za(i,t):Ga(i,t)):l&&Ga(l,t);(c||l)&&(r(),n(\"}\"));a&&n(\")\")}(e,t);break;case 19:!function(e,t){const{test:n,consequent:o,alternate:r,newline:s}=e,{push:i,indent:l,deindent:c,newline:a}=t;if(4===n.type){const e=!Mc(n.content);e&&i(\"(\"),qa(n,t),e&&i(\")\")}else i(\"(\"),Ga(n,t),i(\")\");s&&l(),t.indentLevel++,s||i(\" \"),i(\"? \"),Ga(o,t),t.indentLevel--,s&&a(),s||i(\" \"),i(\": \");const u=19===r.type;u||t.indentLevel++;Ga(r,t),u||t.indentLevel--;s&&c(!0)}(e,t);break;case 20:!function(e,t){const{push:n,helper:o,indent:r,deindent:s,newline:i}=t;n(`_cache[${e.index}] || (`),e.isVNode&&(r(),n(`${o(fc)}(-1),`),i());n(`_cache[${e.index}] = `),Ga(e.value,t),e.isVNode&&(n(\",\"),i(),n(`${o(fc)}(1),`),i(),n(`_cache[${e.index}]`),s());n(\")\")}(e,t);break;case 21:Ka(e.body,t,!0,!1)}}function qa(e,t){const{content:n,isStatic:o}=e;t.push(o?JSON.stringify(n):n,e)}function Ja(e,t){for(let n=0;n<e.children.length;n++){const o=e.children[n];P(o)?t.push(o):Ga(o,t)}}function Ya(e,t){const{push:n}=t;if(8===e.type)n(\"[\"),Ja(e,t),n(\"]\");else if(e.isStatic){n(Mc(e.content)?e.content:JSON.stringify(e.content),e)}else n(`[${e.content}]`,e)}const Za=Ua(/^(if|else|else-if)$/,((e,t,n)=>function(e,t,n,o){if(!(\"else\"===t.name||t.exp&&t.exp.content.trim())){t.exp=Tc(\"true\",!1,t.exp?t.exp.loc:e.loc)}if(\"if\"===t.name){const r=Qa(e,t),s={type:9,loc:e.loc,branches:[r]};if(n.replaceNode(s),o)return o(s,r,!0)}else{const r=n.parent.children;let s=r.indexOf(e);for(;s-- >=-1;){const i=r[s];if(!i||2!==i.type||i.content.trim().length){if(i&&9===i.type){n.removeNode();const r=Qa(e,t);i.branches.push(r);const s=o&&o(i,r,!1);ja(r,n),s&&s(),n.currentNode=null}break}n.removeNode(i)}}}(e,t,n,((e,t,o)=>{const r=n.parent.children;let s=r.indexOf(e),i=0;for(;s-- >=0;){const e=r[s];e&&9===e.type&&(i+=e.branches.length)}return()=>{if(o)e.codegenNode=Xa(t,i,n);else{const o=function(e){for(;;)if(19===e.type){if(19!==e.alternate.type)return e;e=e.alternate}else 20===e.type&&(e=e.value)}(e.codegenNode);o.alternate=Xa(t,i+e.branches.length-1,n)}}}))));function Qa(e,t){const n=3===e.tagType;return{type:10,loc:e.loc,condition:\"else\"===t.name?void 0:t.exp,children:n&&!Hc(e,\"for\")?e.children:[e],userKey:Wc(e,\"key\"),isTemplateIf:n}}function Xa(e,t,n){return e.condition?Oc(e.condition,eu(e,t,n),Ec(n.helper(zl),['\"\"',\"true\"])):eu(e,t,n)}function eu(e,t,n){const{helper:o}=n,r=kc(\"key\",Tc(`${t}`,!1,Sc,2)),{children:s}=e,i=s[0];if(1!==s.length||1!==i.type){if(1===s.length&&11===i.type){const e=i.codegenNode;return ea(e,r,n),e}{let t=64;return xc(n,o(Ml),wc([r]),s,t+\"\",void 0,void 0,!0,!1,!1,e.loc)}}{const e=i.codegenNode,t=14===(l=e).type&&l.callee===yc?l.arguments[1].returns:l;return 13===t.type&&na(t,n),ea(t,r,n),e}var l}const tu=Ua(\"for\",((e,t,n)=>{const{helper:o,removeHelper:r}=n;return function(e,t,n,o){if(!t.exp)return;const r=su(t.exp);if(!r)return;const{scopes:s}=n,{source:i,value:l,key:c,index:a}=r,u={type:11,loc:t.loc,source:i,valueAlias:l,keyAlias:c,objectIndexAlias:a,parseResult:r,children:qc(e)?e.children:[e]};n.replaceNode(u),s.vFor++;const p=o&&o(u);return()=>{s.vFor--,p&&p()}}(e,t,n,(t=>{const s=Ec(o(Xl),[t.source]),i=qc(e),l=Hc(e,\"memo\"),c=Wc(e,\"key\"),a=c&&(6===c.type?Tc(c.value.content,!0):c.exp),u=c?kc(\"key\",a):null,p=4===t.source.type&&t.source.constType>0,f=p?64:c?128:256;return t.codegenNode=xc(n,o(Ml),void 0,s,f+\"\",void 0,void 0,!0,!p,!1,e.loc),()=>{let c;const{children:f}=t,d=1!==f.length||1!==f[0].type,h=Jc(e)?e:i&&1===e.children.length&&Jc(e.children[0])?e.children[0]:null;if(h?(c=h.codegenNode,i&&u&&ea(c,u,n)):d?c=xc(n,o(Ml),u?wc([u]):void 0,e.children,\"64\",void 0,void 0,!0,void 0,!1):(c=f[0].codegenNode,i&&u&&ea(c,u,n),c.isBlock!==!p&&(c.isBlock?(r(jl),r(Zc(n.inSSR,c.isComponent))):r(Yc(n.inSSR,c.isComponent))),c.isBlock=!p,c.isBlock?(o(jl),o(Zc(n.inSSR,c.isComponent))):o(Yc(n.inSSR,c.isComponent))),l){const e=$c(lu(t.parseResult,[Tc(\"_cached\")]));e.body={type:21,body:[Nc([\"const _memo = (\",l.exp,\")\"]),Nc([\"if (_cached\",...a?[\" && _cached.key === \",a]:[],` && ${n.helperString(_c)}(_cached, _memo)) return _cached`]),Nc([\"const _item = \",c]),Tc(\"_item.memo = _memo\"),Tc(\"return _item\")],loc:Sc},s.arguments.push(e,Tc(\"_cache\"),Tc(String(n.cached++)))}else s.arguments.push($c(lu(t.parseResult),c,!0))}}))}));const nu=/([\\s\\S]*?)\\s+(?:in|of)\\s+([\\s\\S]*)/,ou=/,([^,\\}\\]]*)(?:,([^,\\}\\]]*))?$/,ru=/^\\(|\\)$/g;function su(e,t){const n=e.loc,o=e.content,r=o.match(nu);if(!r)return;const[,s,i]=r,l={source:iu(n,i.trim(),o.indexOf(i,s.length)),value:void 0,key:void 0,index:void 0};let c=s.trim().replace(ru,\"\").trim();const a=s.indexOf(c),u=c.match(ou);if(u){c=c.replace(ou,\"\").trim();const e=u[1].trim();let t;if(e&&(t=o.indexOf(e,a+c.length),l.key=iu(n,e,t)),u[2]){const r=u[2].trim();r&&(l.index=iu(n,r,o.indexOf(r,l.key?t+e.length:a+c.length)))}}return c&&(l.value=iu(n,c,a)),l}function iu(e,t,n){return Tc(t,!1,jc(e,n,t.length))}function lu({value:e,key:t,index:n},o=[]){return function(e){let t=e.length;for(;t--&&!e[t];);return e.slice(0,t+1).map(((e,t)=>e||Tc(\"_\".repeat(t+1),!1)))}([e,t,n,...o])}const cu=Tc(\"undefined\",!1),au=(e,t)=>{if(1===e.type&&(1===e.tagType||3===e.tagType)){const n=Hc(e,\"slot\");if(n)return t.scopes.vSlot++,()=>{t.scopes.vSlot--}}},uu=(e,t,n)=>$c(e,t,!1,!0,t.length?t[0].loc:n);function pu(e,t,n=uu){t.helper(mc);const{children:o,loc:r}=e,s=[],i=[];let l=t.scopes.vSlot>0||t.scopes.vFor>0;const c=Hc(e,\"slot\",!0);if(c){const{arg:e,exp:t}=c;e&&!Rc(e)&&(l=!0),s.push(kc(e||Tc(\"default\",!0),n(t,o,r)))}let a=!1,u=!1;const p=[],f=new Set;for(let m=0;m<o.length;m++){const e=o[m];let r;if(!qc(e)||!(r=Hc(e,\"slot\",!0))){3!==e.type&&p.push(e);continue}if(c)break;a=!0;const{children:d,loc:h}=e,{arg:g=Tc(\"default\",!0),exp:v}=r;let y;Rc(g)?y=g?g.content:\"default\":l=!0;const _=n(v,d,h);let b,S,x;if(b=Hc(e,\"if\"))l=!0,i.push(Oc(b.exp,fu(g,_),cu));else if(S=Hc(e,/^else(-if)?$/,!0)){let e,t=m;for(;t--&&(e=o[t],3===e.type););if(e&&qc(e)&&Hc(e,\"if\")){o.splice(m,1),m--;let e=i[i.length-1];for(;19===e.alternate.type;)e=e.alternate;e.alternate=S.exp?Oc(S.exp,fu(g,_),cu):fu(g,_)}}else if(x=Hc(e,\"for\")){l=!0;const e=x.parseResult||su(x.exp);e&&i.push(Ec(t.helper(Xl),[e.source,$c(lu(e),fu(g,_),!0)]))}else{if(y){if(f.has(y))continue;f.add(y),\"default\"===y&&(u=!0)}s.push(kc(g,_))}}if(!c){const e=(e,t)=>kc(\"default\",n(e,t,r));a?p.length&&p.some((e=>hu(e)))&&(u||s.push(e(void 0,p))):s.push(e(void 0,o))}const d=l?2:du(e.children)?3:1;let h=wc(s.concat(kc(\"_\",Tc(d+\"\",!1))),r);return i.length&&(h=Ec(t.helper(tc),[h,Cc(i)])),{slots:h,hasDynamicSlots:l}}function fu(e,t){return wc([kc(\"name\",e),kc(\"fn\",t)])}function du(e){for(let t=0;t<e.length;t++){const n=e[t];switch(n.type){case 1:if(2===n.tagType||du(n.children))return!0;break;case 9:if(du(n.branches))return!0;break;case 10:case 11:if(du(n.children))return!0}}return!1}function hu(e){return 2!==e.type&&12!==e.type||(2===e.type?!!e.content.trim():hu(e.content))}const mu=new WeakMap,gu=(e,t)=>function(){if(1!==(e=t.currentNode).type||0!==e.tagType&&1!==e.tagType)return;const{tag:n,props:o}=e,r=1===e.tagType;let s=r?function(e,t,n=!1){let{tag:o}=e;const r=bu(o),s=Wc(e,\"is\");if(s)if(r){const e=6===s.type?s.value&&Tc(s.value.content,!0):s.exp;if(e)return Ec(t.helper(Jl),[e])}else 6===s.type&&s.value.content.startsWith(\"vue:\")&&(o=s.value.content.slice(4));const i=!r&&Hc(e,\"is\");if(i&&i.exp)return Ec(t.helper(Jl),[i.exp]);const l=Pc(o)||t.isBuiltInComponent(o);if(l)return n||t.helper(l),l;return t.helper(ql),t.components.add(o),ta(o,\"component\")}(e,t):`\"${n}\"`;const i=M(s)&&s.callee===Jl;let l,c,a,u,p,f,d=0,h=i||s===Vl||s===Il||!r&&(\"svg\"===n||\"foreignObject\"===n);if(o.length>0){const n=vu(e,t,void 0,r,i);l=n.props,d=n.patchFlag,p=n.dynamicPropNames;const o=n.directives;f=o&&o.length?Cc(o.map((e=>function(e,t){const n=[],o=mu.get(e);o?n.push(t.helperString(o)):(t.helper(Yl),t.directives.add(e.name),n.push(ta(e.name,\"directive\")));const{loc:r}=e;e.exp&&n.push(e.exp);e.arg&&(e.exp||n.push(\"void 0\"),n.push(e.arg));if(Object.keys(e.modifiers).length){e.arg||(e.exp||n.push(\"void 0\"),n.push(\"void 0\"));const t=Tc(\"true\",!1,r);n.push(wc(e.modifiers.map((e=>kc(e,t))),r))}return Cc(n,e.loc)}(e,t)))):void 0,n.shouldUseBlock&&(h=!0)}if(e.children.length>0){s===Bl&&(h=!0,d|=1024);if(r&&s!==Vl&&s!==Bl){const{slots:n,hasDynamicSlots:o}=pu(e,t);c=n,o&&(d|=1024)}else if(1===e.children.length&&s!==Vl){const n=e.children[0],o=n.type,r=5===o||8===o;r&&0===Fa(n,t)&&(d|=1),c=r||2===o?n:e.children}else c=e.children}0!==d&&(a=String(d),p&&p.length&&(u=function(e){let t=\"[\";for(let n=0,o=e.length;n<o;n++)t+=JSON.stringify(e[n]),n<o-1&&(t+=\", \");return t+\"]\"}(p))),e.codegenNode=xc(t,s,l,c,a,u,f,!!h,!1,r,e.loc)};function vu(e,t,n=e.props,o,r,s=!1){const{tag:i,loc:l,children:c}=e;let a=[];const u=[],p=[],f=c.length>0;let d=!1,h=0,m=!1,g=!1,v=!1,y=!1,_=!1,b=!1;const S=[],C=({key:e,value:n})=>{if(Rc(e)){const s=e.content,i=x(s);if(!i||o&&!r||\"onclick\"===s.toLowerCase()||\"onUpdate:modelValue\"===s||U(s)||(y=!0),i&&U(s)&&(b=!0),20===n.type||(4===n.type||8===n.type)&&Fa(n,t)>0)return;\"ref\"===s?m=!0:\"class\"===s?g=!0:\"style\"===s?v=!0:\"key\"===s||S.includes(s)||S.push(s),!o||\"class\"!==s&&\"style\"!==s||S.includes(s)||S.push(s)}else _=!0};for(let x=0;x<n.length;x++){const o=n[x];if(6===o.type){const{loc:e,name:n,value:r}=o;let s=!0;if(\"ref\"===n&&(m=!0,t.scopes.vFor>0&&a.push(kc(Tc(\"ref_for\",!0),Tc(\"true\")))),\"is\"===n&&(bu(i)||r&&r.content.startsWith(\"vue:\")))continue;a.push(kc(Tc(n,!0,jc(e,0,n.length)),Tc(r?r.content:\"\",s,r?r.loc:e)))}else{const{name:n,arg:r,exp:c,loc:h}=o,m=\"bind\"===n,g=\"on\"===n;if(\"slot\"===n)continue;if(\"once\"===n||\"memo\"===n)continue;if(\"is\"===n||m&&zc(r,\"is\")&&bu(i))continue;if(g&&s)continue;if((m&&zc(r,\"key\")||g&&f&&zc(r,\"vue:before-update\"))&&(d=!0),m&&zc(r,\"ref\")&&t.scopes.vFor>0&&a.push(kc(Tc(\"ref_for\",!0),Tc(\"true\"))),!r&&(m||g)){_=!0,c&&(a.length&&(u.push(wc(yu(a),l)),a=[]),u.push(m?c:{type:14,loc:h,callee:t.helper(cc),arguments:[c]}));continue}const v=t.directiveTransforms[n];if(v){const{props:n,needRuntime:r}=v(o,e,t);!s&&n.forEach(C),a.push(...n),r&&(p.push(o),A(r)&&mu.set(o,r))}else D(n)||(p.push(o),f&&(d=!0))}}let w;if(u.length?(a.length&&u.push(wc(yu(a),l)),w=u.length>1?Ec(t.helper(oc),u,l):u[0]):a.length&&(w=wc(yu(a),l)),_?h|=16:(g&&!o&&(h|=2),v&&!o&&(h|=4),S.length&&(h|=8),y&&(h|=32)),d||0!==h&&32!==h||!(m||b||p.length>0)||(h|=512),!t.inSSR&&w)switch(w.type){case 15:let e=-1,n=-1,o=!1;for(let t=0;t<w.properties.length;t++){const r=w.properties[t].key;Rc(r)?\"class\"===r.content?e=t:\"style\"===r.content&&(n=t):r.isHandlerKey||(o=!0)}const r=w.properties[e],s=w.properties[n];o?w=Ec(t.helper(ic),[w]):(r&&!Rc(r.value)&&(r.value=Ec(t.helper(rc),[r.value])),s&&(v||4===s.value.type&&\"[\"===s.value.content.trim()[0]||17===s.value.type)&&(s.value=Ec(t.helper(sc),[s.value])));break;case 14:break;default:w=Ec(t.helper(ic),[Ec(t.helper(lc),[w])])}return{props:w,directives:p,patchFlag:h,dynamicPropNames:S,shouldUseBlock:d}}function yu(e){const t=new Map,n=[];for(let o=0;o<e.length;o++){const r=e[o];if(8===r.key.type||!r.key.isStatic){n.push(r);continue}const s=r.key.content,i=t.get(s);i?(\"style\"===s||\"class\"===s||x(s))&&_u(i,r):(t.set(s,r),n.push(r))}return n}function _u(e,t){17===e.value.type?e.value.elements.push(t.value):e.value=Cc([e.value,t.value],e.loc)}function bu(e){return\"component\"===e||\"Component\"===e}const Su=(e,t)=>{if(Jc(e)){const{children:n,loc:o}=e,{slotName:r,slotProps:s}=function(e,t){let n,o='\"default\"';const r=[];for(let s=0;s<e.props.length;s++){const t=e.props[s];6===t.type?t.value&&(\"name\"===t.name?o=JSON.stringify(t.value.content):(t.name=z(t.name),r.push(t))):\"bind\"===t.name&&zc(t.arg,\"name\")?t.exp&&(o=t.exp):(\"bind\"===t.name&&t.arg&&Rc(t.arg)&&(t.arg.content=z(t.arg.content)),r.push(t))}if(r.length>0){const{props:o,directives:s}=vu(e,t,r,!1,!1);n=o}return{slotName:o,slotProps:n}}(e,t),i=[t.prefixIdentifiers?\"_ctx.$slots\":\"$slots\",r,\"{}\",\"undefined\",\"true\"];let l=2;s&&(i[2]=s,l=3),n.length&&(i[3]=$c([],n,!1,!1,o),l=4),t.scopeId&&!t.slotted&&(l=5),i.splice(l),e.codegenNode=Ec(t.helper(ec),i,o)}};const xu=/^\\s*([\\w$_]+|(async\\s*)?\\([^)]*?\\))\\s*=>|^\\s*(async\\s+)?function(?:\\s+[\\w$]+)?\\s*\\(/,Cu=(e,t,n,o)=>{const{loc:r,modifiers:s,arg:i}=e;let l;if(4===i.type)if(i.isStatic){let e=i.content;e.startsWith(\"vue:\")&&(e=`vnode-${e.slice(4)}`),l=Tc(J(z(e)),!0,i.loc)}else l=Nc([`${n.helperString(pc)}(`,i,\")\"]);else l=i,l.children.unshift(`${n.helperString(pc)}(`),l.children.push(\")\");let c=e.exp;c&&!c.content.trim()&&(c=void 0);let a=n.cacheHandlers&&!c&&!n.inVOnce;if(c){const e=Lc(c.content),t=!(e||xu.test(c.content)),n=c.content.includes(\";\");(t||a&&e)&&(c=Nc([`${t?\"$event\":\"(...args)\"} => ${n?\"{\":\"(\"}`,c,n?\"}\":\")\"]))}let u={props:[kc(l,c||Tc(\"() => {}\",!1,r))]};return o&&(u=o(u)),a&&(u.props[0].value=n.cache(u.props[0].value)),u.props.forEach((e=>e.key.isHandlerKey=!0)),u},wu=(e,t,n)=>{const{exp:o,modifiers:r,loc:s}=e,i=e.arg;return 4!==i.type?(i.children.unshift(\"(\"),i.children.push(') || \"\"')):i.isStatic||(i.content=`${i.content} || \"\"`),r.includes(\"camel\")&&(4===i.type?i.content=i.isStatic?z(i.content):`${n.helperString(ac)}(${i.content})`:(i.children.unshift(`${n.helperString(ac)}(`),i.children.push(\")\"))),n.inSSR||(r.includes(\"prop\")&&ku(i,\".\"),r.includes(\"attr\")&&ku(i,\"^\")),!o||4===o.type&&!o.content.trim()?{props:[kc(i,Tc(\"\",!0,s))]}:{props:[kc(i,o)]}},ku=(e,t)=>{4===e.type?e.content=e.isStatic?t+e.content:`\\`${t}\\${${e.content}}\\``:(e.children.unshift(`'${t}' + (`),e.children.push(\")\"))},Tu=(e,t)=>{if(0===e.type||1===e.type||11===e.type||10===e.type)return()=>{const n=e.children;let o,r=!1;for(let e=0;e<n.length;e++){const t=n[e];if(Kc(t)){r=!0;for(let r=e+1;r<n.length;r++){const s=n[r];if(!Kc(s)){o=void 0;break}o||(o=n[e]=Nc([t],t.loc)),o.children.push(\" + \",s),n.splice(r,1),r--}}}if(r&&(1!==n.length||0!==e.type&&(1!==e.type||0!==e.tagType||e.props.find((e=>7===e.type&&!t.directiveTransforms[e.name])))))for(let e=0;e<n.length;e++){const o=n[e];if(Kc(o)||8===o.type){const r=[];2===o.type&&\" \"===o.content||r.push(o),t.ssr||0!==Fa(o,t)||r.push(\"1\"),n[e]={type:12,content:o,loc:o.loc,codegenNode:Ec(t.helper(Kl),r)}}}}},Nu=new WeakSet,Eu=(e,t)=>{if(1===e.type&&Hc(e,\"once\",!0)){if(Nu.has(e)||t.inVOnce)return;return Nu.add(e),t.inVOnce=!0,t.helper(fc),()=>{t.inVOnce=!1;const e=t.currentNode;e.codegenNode&&(e.codegenNode=t.cache(e.codegenNode,!0))}}},$u=(e,t,n)=>{const{exp:o,arg:r}=e;if(!o)return Ou();const s=o.loc.source,i=4===o.type?o.content:s;if(!i.trim()||!Lc(i))return Ou();const l=r||Tc(\"modelValue\",!0),c=r?Rc(r)?`onUpdate:${r.content}`:Nc(['\"onUpdate:\" + ',r]):\"onUpdate:modelValue\";let a;a=Nc([`${n.isTS?\"($event: any)\":\"$event\"} => ((`,o,\") = $event)\"]);const u=[kc(l,e.exp),kc(c,a)];if(e.modifiers.length&&1===t.tagType){const t=e.modifiers.map((e=>(Mc(e)?e:JSON.stringify(e))+\": true\")).join(\", \"),n=r?Rc(r)?`${r.content}Modifiers`:Nc([r,' + \"Modifiers\"']):\"modelModifiers\";u.push(kc(n,Tc(`{ ${t} }`,!1,e.loc,2)))}return Ou(u)};function Ou(e=[]){return{props:e}}const Ru=new WeakSet,Fu=(e,t)=>{if(1===e.type){const n=Hc(e,\"memo\");if(!n||Ru.has(e))return;return Ru.add(e),()=>{const o=e.codegenNode||t.currentNode.codegenNode;o&&13===o.type&&(1!==e.tagType&&na(o,t),e.codegenNode=Ec(t.helper(yc),[n.exp,$c(void 0,o),\"_cache\",String(t.cached++)]))}}};function Pu(e,t={}){const n=t.onError||Fl,o=\"module\"===t.mode;!0===t.prefixIdentifiers?n(Al(46)):o&&n(Al(47));t.cacheHandlers&&n(Al(48)),t.scopeId&&!o&&n(Al(49));const r=P(e)?ia(e,t):e,[s,i]=[[Eu,Za,Fu,tu,Su,gu,au,Tu],{on:Cu,bind:wu,model:$u}];return La(r,w({},t,{prefixIdentifiers:false,nodeTransforms:[...s,...t.nodeTransforms||[]],directiveTransforms:w({},i,t.directiveTransforms||{})})),Ha(r,w({},t,{prefixIdentifiers:false}))}const Au=Symbol(\"\"),Mu=Symbol(\"\"),Vu=Symbol(\"\"),Iu=Symbol(\"\"),Bu=Symbol(\"\"),Lu=Symbol(\"\"),ju=Symbol(\"\"),Uu=Symbol(\"\"),Du=Symbol(\"\"),Hu=Symbol(\"\");var Wu;let zu;Wu={[Au]:\"vModelRadio\",[Mu]:\"vModelCheckbox\",[Vu]:\"vModelText\",[Iu]:\"vModelSelect\",[Bu]:\"vModelDynamic\",[Lu]:\"withModifiers\",[ju]:\"withKeys\",[Uu]:\"vShow\",[Du]:\"Transition\",[Hu]:\"TransitionGroup\"},Object.getOwnPropertySymbols(Wu).forEach((e=>{bc[e]=Wu[e]}));const Ku=e(\"style,iframe,script,noscript\",!0),Gu={isVoidTag:f,isNativeTag:e=>u(e)||p(e),isPreTag:e=>\"pre\"===e,decodeEntities:function(e,t=!1){return zu||(zu=document.createElement(\"div\")),t?(zu.innerHTML=`<div foo=\"${e.replace(/\"/g,\"&quot;\")}\">`,zu.children[0].getAttribute(\"foo\")):(zu.innerHTML=e,zu.textContent)},isBuiltInComponent:e=>Fc(e,\"Transition\")?Du:Fc(e,\"TransitionGroup\")?Hu:void 0,getNamespace(e,t){let n=t?t.ns:0;if(t&&2===n)if(\"annotation-xml\"===t.tag){if(\"svg\"===e)return 1;t.props.some((e=>6===e.type&&\"encoding\"===e.name&&null!=e.value&&(\"text/html\"===e.value.content||\"application/xhtml+xml\"===e.value.content)))&&(n=0)}else/^m(?:[ions]|text)$/.test(t.tag)&&\"mglyph\"!==e&&\"malignmark\"!==e&&(n=0);else t&&1===n&&(\"foreignObject\"!==t.tag&&\"desc\"!==t.tag&&\"title\"!==t.tag||(n=0));if(0===n){if(\"svg\"===e)return 1;if(\"math\"===e)return 2}return n},getTextMode({tag:e,ns:t}){if(0===t){if(\"textarea\"===e||\"title\"===e)return 1;if(Ku(e))return 2}return 0}},qu=(e,t)=>{const n=l(e);return Tc(JSON.stringify(n),!1,t,3)};const Ju=e(\"passive,once,capture\"),Yu=e(\"stop,prevent,self,ctrl,shift,alt,meta,exact,middle\"),Zu=e(\"left,right\"),Qu=e(\"onkeyup,onkeydown,onkeypress\",!0),Xu=(e,t)=>Rc(e)&&\"onclick\"===e.content.toLowerCase()?Tc(t,!0):4!==e.type?Nc([\"(\",e,`) === \"onClick\" ? \"${t}\" : (`,e,\")\"]):e,ep=(e,t)=>{1!==e.type||0!==e.tagType||\"script\"!==e.tag&&\"style\"!==e.tag||t.removeNode()},tp=[e=>{1===e.type&&e.props.forEach(((t,n)=>{6===t.type&&\"style\"===t.name&&t.value&&(e.props[n]={type:7,name:\"bind\",arg:Tc(\"style\",!0,t.loc),exp:qu(t.value.content,t.loc),modifiers:[],loc:t.loc})}))}],np={cloak:()=>({props:[]}),html:(e,t,n)=>{const{exp:o,loc:r}=e;return t.children.length&&(t.children.length=0),{props:[kc(Tc(\"innerHTML\",!0,r),o||Tc(\"\",!0))]}},text:(e,t,n)=>{const{exp:o,loc:r}=e;return t.children.length&&(t.children.length=0),{props:[kc(Tc(\"textContent\",!0),o?Fa(o,n)>0?o:Ec(n.helperString(nc),[o],r):Tc(\"\",!0))]}},model:(e,t,n)=>{const o=$u(e,t,n);if(!o.props.length||1===t.tagType)return o;const{tag:r}=t,s=n.isCustomElement(r);if(\"input\"===r||\"textarea\"===r||\"select\"===r||s){let e=Vu,i=!1;if(\"input\"===r||s){const n=Wc(t,\"type\");if(n){if(7===n.type)e=Bu;else if(n.value)switch(n.value.content){case\"radio\":e=Au;break;case\"checkbox\":e=Mu;break;case\"file\":i=!0}}else(function(e){return e.props.some((e=>!(7!==e.type||\"bind\"!==e.name||e.arg&&4===e.arg.type&&e.arg.isStatic)))})(t)&&(e=Bu)}else\"select\"===r&&(e=Iu);i||(o.needRuntime=n.helper(e))}return o.props=o.props.filter((e=>!(4===e.key.type&&\"modelValue\"===e.key.content))),o},on:(e,t,n)=>Cu(e,0,n,(t=>{const{modifiers:o}=e;if(!o.length)return t;let{key:r,value:s}=t.props[0];const{keyModifiers:i,nonKeyModifiers:l,eventOptionModifiers:c}=((e,t,n,o)=>{const r=[],s=[],i=[];for(let l=0;l<t.length;l++){const n=t[l];Ju(n)?i.push(n):Zu(n)?Rc(e)?Qu(e.content)?r.push(n):s.push(n):(r.push(n),s.push(n)):Yu(n)?s.push(n):r.push(n)}return{keyModifiers:r,nonKeyModifiers:s,eventOptionModifiers:i}})(r,o);if(l.includes(\"right\")&&(r=Xu(r,\"onContextmenu\")),l.includes(\"middle\")&&(r=Xu(r,\"onMouseup\")),l.length&&(s=Ec(n.helper(Lu),[s,JSON.stringify(l)])),!i.length||Rc(r)&&!Qu(r.content)||(s=Ec(n.helper(ju),[s,JSON.stringify(i)])),c.length){const e=c.map(q).join(\"\");r=Rc(r)?Tc(`${r.content}${e}`,!0):Nc([\"(\",r,`) + \"${e}\"`])}return{props:[kc(r,s)]}})),show:(e,t,n)=>({props:[],needRuntime:n.helper(Uu)})};const op=Object.create(null);function rp(e,t){if(!P(e)){if(!e.nodeType)return _;e=e.innerHTML}const n=e,o=op[n];if(o)return o;if(\"#\"===e[0]){const t=document.querySelector(e);e=t?t.innerHTML:\"\"}const{code:r}=function(e,t={}){return Pu(e,w({},Gu,t,{nodeTransforms:[ep,...tp,...t.nodeTransforms||[]],directiveTransforms:w({},np,t.directiveTransforms||{}),transformHoist:null}))}(e,w({hoistStatic:!0,onError:void 0,onWarn:_},t)),s=new Function(\"Vue\",r)(Rl);return s._rc=!0,op[n]=s}Fs(rp);export{io as BaseTransition,Kr as Comment,ne as EffectScope,Wr as Fragment,_o as KeepAlive,ge as ReactiveEffect,Gr as Static,Un as Suspense,Hr as Teleport,zr as Text,Fi as Transition,Yi as TransitionGroup,Ni as VueElement,Zt as callWithAsyncErrorHandling,Yt as callWithErrorHandling,z as camelize,q as capitalize,fs as cloneVNode,ii as compatUtils,rp as compile,js as computed,Nl as createApp,ns as createBlock,ms as createCommentVNode,ts as createElementBlock,as as createElementVNode,Mr as createHydrationRenderer,Js as createPropsRestProxy,Ar as createRenderer,El as createSSRApp,qo as createSlots,hs as createStaticVNode,ds as createTextVNode,us as createVNode,Ut as customRef,go as defineAsyncComponent,ho as defineComponent,wi as defineCustomElement,Ds as defineEmits,Hs as defineExpose,Us as defineProps,ki as defineSSRCustomElement,xn as devtools,ye as effect,oe as effectScope,ws as getCurrentInstance,se as getCurrentScope,fo as getTransitionRawChildren,ps as guardReactiveProps,Zs as h,Qt as handleError,Tl as hydrate,ei as initCustomFormatter,Ol as initDirectivesForSSR,qn as inject,ni as isMemoSame,wt as isProxy,St as isReactive,xt as isReadonly,Rt as isRef,Ps as isRuntimeOnly,Ct as isShallow,os as isVNode,Tt as markRaw,qs as mergeDefaults,_s as mergeProps,dn as nextTick,c as normalizeClass,a as normalizeProps,r as normalizeStyle,So as onActivated,$o as onBeforeMount,Po as onBeforeUnmount,Ro as onBeforeUpdate,xo as onDeactivated,Bo as onErrorCaptured,Oo as onMounted,Io as onRenderTracked,Vo as onRenderTriggered,ie as onScopeDispose,Mo as onServerPrefetch,Ao as onUnmounted,Fo as onUpdated,Yr as openBlock,Fn as popScopeId,Gn as provide,Lt as proxyRefs,Rn as pushScopeId,vn as queuePostFlushCb,gt as reactive,yt as readonly,Ft as ref,Fs as registerRuntimeCompiler,kl as render,Go as renderList,Jo as renderSlot,Uo as resolveComponent,Wo as resolveDirective,Ho as resolveDynamicComponent,si as resolveFilter,co as resolveTransitionHooks,Xr as setBlockTracking,wn as setDevtoolsHook,po as setTransitionHooks,vt as shallowReactive,_t as shallowReadonly,Pt as shallowRef,Qs as ssrContextKey,ri as ssrUtils,_e as stop,m as toDisplayString,J as toHandlerKey,Zo as toHandlers,kt as toRaw,Wt as toRef,Dt as toRefs,ss as transformVNodeArgs,Vt as triggerRef,It as unref,Ks as useAttrs,Ei as useCssModule,$i as useCssVars,Xs as useSSRContext,zs as useSlots,ro as useTransitionState,rl as vModelCheckbox,pl as vModelDynamic,il as vModelRadio,ll as vModelSelect,ol as vModelText,yl as vShow,oi as version,Gt as warn,Xn as watch,Jn as watchEffect,Yn as watchPostEffect,Zn as watchSyncEffect,Ys as withAsyncContext,An as withCtx,Ws as withDefaults,Lo as withDirectives,vl as withKeys,ti as withMemo,ml as withModifiers,Pn as withScopeId};\n"
  },
  {
    "path": "src/electionguard_gui/web/services/authorization-service.js",
    "content": "export default {\n  _userId: undefined,\n  _isAdmin: undefined,\n  async getUserId() {\n    if (!this._userId) {\n      this._userId = await eel.get_user_id()();\n    }\n    return this._userId;\n  },\n  async setUserId(id) {\n    await eel.set_user_id(id)();\n    this._userId = id;\n  },\n  async isAdmin() {\n    if (!this._isAdmin) {\n      this._isAdmin = await eel.is_admin()();\n    }\n    return this._isAdmin;\n  },\n};\n"
  },
  {
    "path": "src/electionguard_gui/web/services/router-service.js",
    "content": "// shared components\nimport Home from \"../components/shared/home-component.js\";\nimport NotFound from \"../components/shared/not-found-component.js\";\nimport Login from \"../components/shared/login-component.js\";\n\n// admin components\nimport AdminHome from \"../components/admin/admin-home-component.js\";\nimport CreateElection from \"../components/admin/create-election-component.js\";\nimport CreateKeyCeremony from \"../components/admin/create-key-ceremony-component.js\";\nimport ViewKeyCeremonyAdmin from \"../components/admin/view-key-ceremony-component.js\";\nimport ViewElectionAdmin from \"../components/admin/view-election-component.js\";\nimport ExportEncryptionPackage from \"../components/admin/export-encryption-package-component.js\";\nimport ExportElectionRecord from \"../components/admin/export-election-record-component.js\";\nimport UploadBallots from \"../components/admin/upload-ballots-component.js\";\nimport CreateDecryption from \"../components/admin/create-decryption-component.js\";\nimport ViewDecryptionAdmin from \"../components/admin/view-decryption-admin-component.js\";\nimport ViewTally from \"../components/admin/view-tally-component.js\";\nimport ViewSpoiledBallot from \"../components/admin/view-spoiled-ballot-component.js\";\n\n// guardian components\nimport GuardianHome from \"../components/guardian/guardian-home-component.js\";\nimport ViewKeyCeremonyGuardian from \"../components/guardian/view-key-ceremony-component.js\";\nimport ViewDecryptionGuardian from \"../components/guardian/view-decryption-guardian-component.js\";\n\nexport default {\n  getUrl(route, params) {\n    if (!route) throw new Error(\"Invalid route specified\");\n    return \"#\" + route.url + \"?\" + new URLSearchParams(params);\n  },\n  goTo(route, params) {\n    const urlWithParams = this.getUrl(route, params);\n    window.location.href = urlWithParams;\n  },\n  getRouteByUrl(url) {\n    return Object.values(this.routes).filter((r) => r.url === url)[0];\n  },\n  getRoute(path) {\n    const cleanPath = path.split(\"?\")[0].slice(1) || \"/\";\n    const foundRoute = this.getRouteByUrl(cleanPath);\n    console.log(\"getRoute\", cleanPath, foundRoute);\n    return foundRoute || this.routes.notFound;\n  },\n  routes: {\n    // shared pages\n    root: { url: \"/\", secured: true, component: Home },\n    notFound: { url: \"/not-found\", secured: false, component: NotFound },\n    login: { url: \"/login\", secured: false, component: Login },\n\n    // admin pages\n    adminHome: { url: \"/admin/home\", secured: true, component: AdminHome },\n    createElection: {\n      url: \"/admin/create-election\",\n      secured: true,\n      component: CreateElection,\n    },\n    viewElectionAdmin: {\n      url: \"/admin/view-election\",\n      secured: true,\n      component: ViewElectionAdmin,\n    },\n    exportEncryptionPackage: {\n      url: \"/admin/export-encryption-package\",\n      secured: true,\n      component: ExportEncryptionPackage,\n    },\n    exportElectionRecord: {\n      url: \"/admin/export-election-record\",\n      secured: true,\n      component: ExportElectionRecord,\n    },\n    createKeyCeremony: {\n      url: \"/admin/create-key-ceremony\",\n      secured: true,\n      component: CreateKeyCeremony,\n    },\n    viewKeyCeremonyAdminPage: {\n      url: \"/admin/view-key-ceremony\",\n      secured: true,\n      component: ViewKeyCeremonyAdmin,\n    },\n    uploadBallots: {\n      url: \"/admin/upload-ballots\",\n      secured: true,\n      component: UploadBallots,\n    },\n    createDecryption: {\n      url: \"/admin/create-decryption\",\n      secured: true,\n      component: CreateDecryption,\n    },\n    viewDecryptionAdmin: {\n      url: \"/admin/view-decryption\",\n      secured: true,\n      component: ViewDecryptionAdmin,\n    },\n    viewTally: {\n      url: \"/admin/view-tally\",\n      secured: true,\n      component: ViewTally,\n    },\n    viewSpoiledBallot: {\n      url: \"/admin/view-spoiled-ballot\",\n      secured: true,\n      component: ViewSpoiledBallot,\n    },\n\n    // guardian pages\n    guardianHome: {\n      url: \"/guardian/home\",\n      secured: true,\n      component: GuardianHome,\n    },\n    viewKeyCeremonyGuardianPage: {\n      url: \"/guardian/view-key-ceremony\",\n      secured: true,\n      component: ViewKeyCeremonyGuardian,\n    },\n    viewDecryptionGuardian: {\n      url: \"/guardian/view-decryption\",\n      secured: true,\n      component: ViewDecryptionGuardian,\n    },\n  },\n  getElectionUrl(electionId) {\n    return this.getUrl(this.routes.viewElectionAdmin, {\n      electionId: electionId,\n    });\n  },\n};\n"
  },
  {
    "path": "src/electionguard_gui/web/site.webmanifest",
    "content": "{\n  \"name\": \"ElectionGuard\",\n  \"short_name\": \"ElectionGuard\",\n  \"icons\": [\n    {\n      \"src\": \"/android-chrome-192x192.png\",\n      \"sizes\": \"192x192\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"/android-chrome-512x512.png\",\n      \"sizes\": \"512x512\",\n      \"type\": \"image/png\"\n    }\n  ],\n  \"theme_color\": \"#009688\",\n  \"background_color\": \"#ffffff\",\n  \"display\": \"standalone\"\n}\n"
  },
  {
    "path": "src/electionguard_tools/__init__.py",
    "content": "import importlib.metadata\n\n# <AUTOGEN_INIT>\nfrom electionguard_tools import factories\nfrom electionguard_tools import helpers\nfrom electionguard_tools import scripts\nfrom electionguard_tools import strategies\n\nfrom electionguard_tools.factories import (\n    AllPrivateElectionData,\n    AllPublicElectionData,\n    BallotFactory,\n    ElectionFactory,\n    NUMBER_OF_GUARDIANS,\n    QUORUM,\n    ballot_factory,\n    election_factory,\n    get_contest_description_well_formed,\n    get_selection_description_well_formed,\n    get_selection_poorly_formed,\n    get_selection_well_formed,\n)\nfrom electionguard_tools.helpers import (\n    CIPHERTEXT_BALLOT_PREFIX,\n    COEFFICIENTS_FILE_NAME,\n    CONSTANTS_FILE_NAME,\n    CONTEXT_FILE_NAME,\n    DEVICES_DIR,\n    DEVICE_PREFIX,\n    ELECTION_RECORD_DIR,\n    ENCRYPTED_TALLY_FILE_NAME,\n    ElectionBuilder,\n    GUARDIANS_DIR,\n    GUARDIAN_PREFIX,\n    KeyCeremonyOrchestrator,\n    MANIFEST_FILE_NAME,\n    PLAINTEXT_BALLOT_PREFIX,\n    PRIVATE_DATA_DIR,\n    PRIVATE_GUARDIAN_PREFIX,\n    SPOILED_BALLOTS_DIR,\n    SPOILED_BALLOT_PREFIX,\n    SUBMITTED_BALLOTS_DIR,\n    SUBMITTED_BALLOT_PREFIX,\n    TALLY_FILE_NAME,\n    TallyCeremonyOrchestrator,\n    accumulate_plaintext_ballots,\n    election_builder,\n    export,\n    export_private_data,\n    export_record,\n    key_ceremony_orchestrator,\n    tally_accumulate,\n    tally_ceremony_orchestrator,\n)\nfrom electionguard_tools.scripts import (\n    DEFAULT_NUMBER_OF_BALLOTS,\n    DEFAULT_SAMPLE_MANIFEST,\n    DEFAULT_SPEC_VERSION,\n    DEFAULT_SPOIL_RATE,\n    DEFAULT_USE_ALL_GUARDIANS,\n    DEFAULT_USE_PRIVATE_DATA,\n    ElectionSampleDataGenerator,\n    sample_generator,\n)\nfrom electionguard_tools.strategies import (\n    CiphertextElectionsTupleType,\n    ElectionsAndBallotsTupleType,\n    annotated_emails,\n    annotated_strings,\n    ballot_styles,\n    candidate_contest_descriptions,\n    candidates,\n    ciphertext_elections,\n    contact_infos,\n    contest_descriptions,\n    contest_descriptions_room_for_overvoting,\n    election,\n    election_descriptions,\n    election_types,\n    elections_and_ballots,\n    elements_mod_p,\n    elements_mod_p_no_zero,\n    elements_mod_q,\n    elements_mod_q_no_zero,\n    elgamal,\n    elgamal_keypairs,\n    geopolitical_units,\n    group,\n    human_names,\n    internationalized_human_names,\n    internationalized_texts,\n    language_human_names,\n    languages,\n    party_lists,\n    plaintext_voted_ballot,\n    plaintext_voted_ballots,\n    referendum_contest_descriptions,\n    reporting_unit_types,\n    two_letter_codes,\n)\n\n__all__ = [\n    \"AllPrivateElectionData\",\n    \"AllPublicElectionData\",\n    \"BallotFactory\",\n    \"CIPHERTEXT_BALLOT_PREFIX\",\n    \"COEFFICIENTS_FILE_NAME\",\n    \"CONSTANTS_FILE_NAME\",\n    \"CONTEXT_FILE_NAME\",\n    \"CiphertextElectionsTupleType\",\n    \"DEFAULT_NUMBER_OF_BALLOTS\",\n    \"DEFAULT_SAMPLE_MANIFEST\",\n    \"DEFAULT_SPEC_VERSION\",\n    \"DEFAULT_SPOIL_RATE\",\n    \"DEFAULT_USE_ALL_GUARDIANS\",\n    \"DEFAULT_USE_PRIVATE_DATA\",\n    \"DEVICES_DIR\",\n    \"DEVICE_PREFIX\",\n    \"ELECTION_RECORD_DIR\",\n    \"ENCRYPTED_TALLY_FILE_NAME\",\n    \"ElectionBuilder\",\n    \"ElectionFactory\",\n    \"ElectionSampleDataGenerator\",\n    \"ElectionsAndBallotsTupleType\",\n    \"GUARDIANS_DIR\",\n    \"GUARDIAN_PREFIX\",\n    \"KeyCeremonyOrchestrator\",\n    \"MANIFEST_FILE_NAME\",\n    \"NUMBER_OF_GUARDIANS\",\n    \"PLAINTEXT_BALLOT_PREFIX\",\n    \"PRIVATE_DATA_DIR\",\n    \"PRIVATE_GUARDIAN_PREFIX\",\n    \"QUORUM\",\n    \"SPOILED_BALLOTS_DIR\",\n    \"SPOILED_BALLOT_PREFIX\",\n    \"SUBMITTED_BALLOTS_DIR\",\n    \"SUBMITTED_BALLOT_PREFIX\",\n    \"TALLY_FILE_NAME\",\n    \"TallyCeremonyOrchestrator\",\n    \"accumulate_plaintext_ballots\",\n    \"annotated_emails\",\n    \"annotated_strings\",\n    \"ballot_factory\",\n    \"ballot_styles\",\n    \"candidate_contest_descriptions\",\n    \"candidates\",\n    \"ciphertext_elections\",\n    \"contact_infos\",\n    \"contest_descriptions\",\n    \"contest_descriptions_room_for_overvoting\",\n    \"election\",\n    \"election_builder\",\n    \"election_descriptions\",\n    \"election_factory\",\n    \"election_types\",\n    \"elections_and_ballots\",\n    \"elements_mod_p\",\n    \"elements_mod_p_no_zero\",\n    \"elements_mod_q\",\n    \"elements_mod_q_no_zero\",\n    \"elgamal\",\n    \"elgamal_keypairs\",\n    \"export\",\n    \"export_private_data\",\n    \"export_record\",\n    \"factories\",\n    \"geopolitical_units\",\n    \"get_contest_description_well_formed\",\n    \"get_selection_description_well_formed\",\n    \"get_selection_poorly_formed\",\n    \"get_selection_well_formed\",\n    \"group\",\n    \"helpers\",\n    \"human_names\",\n    \"internationalized_human_names\",\n    \"internationalized_texts\",\n    \"key_ceremony_orchestrator\",\n    \"language_human_names\",\n    \"languages\",\n    \"party_lists\",\n    \"plaintext_voted_ballot\",\n    \"plaintext_voted_ballots\",\n    \"referendum_contest_descriptions\",\n    \"reporting_unit_types\",\n    \"sample_generator\",\n    \"scripts\",\n    \"strategies\",\n    \"tally_accumulate\",\n    \"tally_ceremony_orchestrator\",\n    \"two_letter_codes\",\n]\n\n# </AUTOGEN_INIT>\n\n# single source version from pyproject.toml\ntry:\n    __version__ = importlib.metadata.version(__package__.split(\"_\", maxsplit=1)[0])\nexcept importlib.metadata.PackageNotFoundError:\n    __version__ = \"0.0.0\"\n"
  },
  {
    "path": "src/electionguard_tools/factories/__init__.py",
    "content": "from electionguard_tools.factories import ballot_factory\nfrom electionguard_tools.factories import election_factory\n\nfrom electionguard_tools.factories.ballot_factory import (\n    BallotFactory,\n    get_selection_poorly_formed,\n    get_selection_well_formed,\n)\nfrom electionguard_tools.factories.election_factory import (\n    AllPrivateElectionData,\n    AllPublicElectionData,\n    ElectionFactory,\n    NUMBER_OF_GUARDIANS,\n    QUORUM,\n    get_contest_description_well_formed,\n    get_selection_description_well_formed,\n)\n\n__all__ = [\n    \"AllPrivateElectionData\",\n    \"AllPublicElectionData\",\n    \"BallotFactory\",\n    \"ElectionFactory\",\n    \"NUMBER_OF_GUARDIANS\",\n    \"QUORUM\",\n    \"ballot_factory\",\n    \"election_factory\",\n    \"get_contest_description_well_formed\",\n    \"get_selection_description_well_formed\",\n    \"get_selection_poorly_formed\",\n    \"get_selection_well_formed\",\n]\n"
  },
  {
    "path": "src/electionguard_tools/factories/ballot_factory.py",
    "content": "from typing import Any, TypeVar, Callable, List, Tuple, Optional\nimport os\nfrom random import Random, randint\nimport uuid\n\nfrom hypothesis.strategies import (\n    composite,\n    booleans,\n    integers,\n    text,\n    uuids,\n    SearchStrategy,\n)\n\nfrom electionguard.ballot import (\n    PlaintextBallot,\n    PlaintextBallotContest,\n    PlaintextBallotSelection,\n)\nfrom electionguard.encrypt import selection_from\nfrom electionguard.manifest import (\n    ContestDescription,\n    SelectionDescription,\n    InternalManifest,\n)\nfrom electionguard.serialize import from_file, from_list_in_file\n\n\n_T = TypeVar(\"_T\")\n_DrawType = Callable[[SearchStrategy[_T]], _T]\n\n_data = os.path.realpath(os.path.join(__file__, \"../../../../data\"))\n\n\nclass BallotFactory:\n    \"\"\"Factory to create ballots\"\"\"\n\n    simple_ballot_filename = \"ballot_in_simple.json\"\n    simple_ballots_filename = \"plaintext_ballots_simple.json\"\n\n    @staticmethod\n    def get_random_selection_from(\n        description: SelectionDescription,\n        random_source: Random,\n        is_placeholder: bool = False,\n    ) -> PlaintextBallotSelection:\n\n        selected = bool(random_source.randint(0, 1))\n        return selection_from(description, is_placeholder, selected)\n\n    @staticmethod\n    def get_random_contest_from(\n        description: ContestDescription,\n        random: Random,\n        suppress_validity_check: bool = False,\n        allow_null_votes: bool = True,\n        allow_under_votes: bool = True,\n    ) -> PlaintextBallotContest:\n        \"\"\"\n        Get a randomly filled contest for the given description that\n        may be undervoted and may include explicitly false votes.\n        Since this is only used for testing, the random number generator\n        (`random`) must be provided to make this function deterministic.\n        \"\"\"\n        if not suppress_validity_check:\n            assert description.is_valid(), \"the contest description must be valid\"\n\n        shuffled_selections = description.ballot_selections[:]\n        random.shuffle(shuffled_selections)\n\n        if allow_null_votes and not allow_under_votes:\n            cut_point = random.choice([0, description.number_elected])\n        else:\n            min_votes = description.number_elected\n            if allow_under_votes:\n                min_votes = 1\n            if allow_null_votes:\n                min_votes = 0\n            cut_point = random.randint(min_votes, description.number_elected)\n\n        selections = [\n            selection_from(selection_description, is_affirmative=True)\n            for selection_description in shuffled_selections[0:cut_point]\n        ]\n\n        for selection_description in shuffled_selections[cut_point:]:\n            # Possibly append the false selections as well, indicating some choices\n            # may be explicitly false\n            if bool(random.randint(0, 1)) == 1:\n                selections.append(\n                    selection_from(selection_description, is_affirmative=False)\n                )\n\n        random.shuffle(selections)\n        return PlaintextBallotContest(description.object_id, selections)\n\n    def get_fake_ballot(\n        self,\n        internal_manifest: InternalManifest,\n        ballot_id: Optional[str] = None,\n        allow_null_votes: bool = False,\n    ) -> PlaintextBallot:\n        \"\"\"\n        Get a single Fake Ballot object that is manually constructed with default vaules\n        \"\"\"\n\n        if ballot_id is None:\n            ballot_id = \"some-unique-ballot-id-123\"\n\n        contests: List[PlaintextBallotContest] = []\n        for contest in internal_manifest.get_contests_for(\n            internal_manifest.ballot_styles[0].object_id\n        ):\n            contests.append(\n                self.get_random_contest_from(\n                    contest, Random(), allow_null_votes=allow_null_votes\n                )\n            )\n\n        fake_ballot = PlaintextBallot(\n            ballot_id, internal_manifest.ballot_styles[0].object_id, contests\n        )\n\n        return fake_ballot\n\n    def generate_fake_plaintext_ballots_for_election(\n        self,\n        internal_manifest: InternalManifest,\n        number_of_ballots: int,\n        ballot_style_id: Optional[str] = None,\n        allow_null_votes: bool = False,\n        allow_under_votes: bool = True,\n    ) -> List[PlaintextBallot]:\n        ballots: List[PlaintextBallot] = []\n        for _i in range(number_of_ballots):\n            if ballot_style_id is not None:\n                ballot_style = internal_manifest.get_ballot_style(ballot_style_id)\n            else:\n                style_index = randint(0, len(internal_manifest.ballot_styles) - 1)\n                ballot_style = internal_manifest.ballot_styles[style_index]\n\n            ballot_id = f\"ballot-{uuid.uuid1()}\"\n\n            contests: List[PlaintextBallotContest] = []\n            for contest in internal_manifest.get_contests_for(ballot_style.object_id):\n                contests.append(\n                    self.get_random_contest_from(\n                        contest,\n                        Random(),\n                        allow_null_votes=allow_null_votes,\n                        allow_under_votes=allow_under_votes,\n                    )\n                )\n\n            ballots.append(PlaintextBallot(ballot_id, ballot_style.object_id, contests))\n\n        return ballots\n\n    def get_simple_ballot_from_file(self) -> PlaintextBallot:\n        return self._get_ballot_from_file(self.simple_ballot_filename)\n\n    def get_simple_ballots_from_file(self) -> List[PlaintextBallot]:\n        return self._get_ballots_from_file(self.simple_ballots_filename)\n\n    @staticmethod\n    def _get_ballot_from_file(filename: str) -> PlaintextBallot:\n        return from_file(PlaintextBallot, os.path.join(_data, filename))\n\n    @staticmethod\n    def _get_ballots_from_file(filename: str) -> List[PlaintextBallot]:\n        return from_list_in_file(PlaintextBallot, os.path.join(_data, filename))\n\n\n# TODO Migrate to strategies\n@composite\ndef get_selection_well_formed(\n    draw: _DrawType,\n    ids: Any = uuids(),\n    bools: Any = booleans(),\n    txt: Any = text(),\n    vote: Any = integers(0, 1),\n) -> Tuple[str, PlaintextBallotSelection]:\n    use_none = draw(bools)\n    if use_none:\n        extended_data = None\n    else:\n        extended_data = draw(txt)\n    object_id = f\"selection-{draw(ids)}\"\n    return (\n        object_id,\n        PlaintextBallotSelection(object_id, draw(vote), draw(bools), extended_data),\n    )\n\n\n# TODO Migrate to strategies\n@composite\ndef get_selection_poorly_formed(\n    draw: _DrawType,\n    ids: Any = uuids(),\n    bools: Any = booleans(),\n    txt: Any = text(),\n    vote: Any = integers(0, 1),\n) -> Tuple[str, PlaintextBallotSelection]:\n    use_none = draw(bools)\n    if use_none:\n        extended_data = None\n    else:\n        extended_data = draw(txt)\n    object_id = f\"selection-{draw(ids)}\"\n    return (\n        object_id,\n        PlaintextBallotSelection(object_id, draw(vote), draw(bools), extended_data),\n    )\n"
  },
  {
    "path": "src/electionguard_tools/factories/election_factory.py",
    "content": "\"\"\"Factory to create elections for testing purposes.\"\"\"\n\nfrom datetime import datetime\nimport os\nfrom dataclasses import dataclass\nfrom typing import Any, TypeVar, Callable, Optional, Tuple, List\n\nfrom hypothesis.strategies import (\n    composite,\n    emails,\n    integers,\n    text,\n    uuids,\n    SearchStrategy,\n)\n\nfrom electionguard.ballot import PlaintextBallot\nfrom electionguard.constants import ElectionConstants, get_constants\nfrom electionguard.election import CiphertextElectionContext\nfrom electionguard.elgamal import ElGamalPublicKey\nfrom electionguard.encrypt import EncryptionDevice, contest_from, generate_device_uuid\nfrom electionguard.group import TWO_MOD_Q\nfrom electionguard.guardian import Guardian, GuardianRecord\nfrom electionguard.key_ceremony import CeremonyDetails\nfrom electionguard.key_ceremony_mediator import KeyCeremonyMediator\nfrom electionguard.manifest import (\n    BallotStyle,\n    Manifest,\n    ElectionType,\n    InternalManifest,\n    SpecVersion,\n    generate_placeholder_selections_from,\n    GeopoliticalUnit,\n    Candidate,\n    Party,\n    ContestDescription,\n    SelectionDescription,\n    ReportingUnitType,\n    VoteVariationType,\n    contest_description_with_placeholders_from,\n    CandidateContestDescription,\n    ReferendumContestDescription,\n)\nfrom electionguard.serialize import from_file\nfrom electionguard.utils import get_optional\n\nfrom electionguard_tools.helpers.key_ceremony_orchestrator import (\n    KeyCeremonyOrchestrator,\n)\nfrom electionguard_tools.helpers.election_builder import ElectionBuilder\n\n\n_T = TypeVar(\"_T\")\n_DrawType = Callable[[SearchStrategy[_T]], _T]\n\n_data = os.path.realpath(os.path.join(__file__, \"../../../../data\"))\n\nNUMBER_OF_GUARDIANS = 5\nQUORUM = 3\n\n\n@dataclass\nclass AllPublicElectionData:\n    \"\"\"All public data for election\"\"\"\n\n    manifest: Manifest\n    internal_manifest: InternalManifest\n    context: CiphertextElectionContext\n    constants: ElectionConstants\n    guardians: List[GuardianRecord]\n\n\n@dataclass\nclass AllPrivateElectionData:\n    \"\"\"All private data for election.\"\"\"\n\n    guardians: List[Guardian]\n\n\nclass ElectionFactory:\n    \"\"\"Factory to create elections.\"\"\"\n\n    simple_election_manifest_file_name = \"election_manifest_simple.json\"\n\n    def get_simple_manifest_from_file(self) -> Manifest:\n        \"\"\"Get simple manifest from json file.\"\"\"\n        return self._get_manifest_from_file(self.simple_election_manifest_file_name)\n\n    def get_manifest_from_filename(self, filename: str) -> Manifest:\n        \"\"\"Get simple manifest from json file.\"\"\"\n        return self._get_manifest_from_file(filename)\n\n    @staticmethod\n    def get_manifest_from_file(spec_version: str, sample_manifest: str) -> Manifest:\n        \"\"\"Get simple manifest from json file.\"\"\"\n        return from_file(\n            Manifest,\n            os.path.join(\n                _data,\n                spec_version,\n                \"sample\",\n                sample_manifest,\n                \"election_record\",\n                \"manifest.json\",\n            ),\n        )\n\n    @staticmethod\n    def get_hamilton_manifest_from_file() -> Manifest:\n        \"\"\"Get Hamilton County manifest from json file.\"\"\"\n        return from_file(\n            Manifest,\n            os.path.join(_data, os.path.join(_data, \"manifest-hamilton-general.json\")),\n        )\n\n    def get_sample_manifest_with_encryption_context(\n        self, sample_manifest: str\n    ) -> Tuple[AllPublicElectionData, AllPrivateElectionData]:\n        \"\"\"Get hamilton manifest and context\"\"\"\n        guardians: List[Guardian] = []\n        guardian_records: List[GuardianRecord] = []\n\n        # Configure the election builder\n        manifest = self.get_manifest_from_filename(f\"manifest-{sample_manifest}.json\")\n        builder = ElectionBuilder(NUMBER_OF_GUARDIANS, QUORUM, manifest)\n\n        # Run the Key Ceremony\n        ceremony_details = CeremonyDetails(NUMBER_OF_GUARDIANS, QUORUM)\n        guardians = KeyCeremonyOrchestrator.create_guardians(ceremony_details)\n        mediator = KeyCeremonyMediator(\"key-ceremony-mediator\", ceremony_details)\n        KeyCeremonyOrchestrator.perform_full_ceremony(guardians, mediator)\n\n        # Final: Joint Key\n        joint_key = mediator.publish_joint_key()\n\n        # Publish Guardian Records\n        guardian_records = [guardian.publish() for guardian in guardians]\n\n        builder.set_public_key(get_optional(joint_key).joint_public_key)\n        builder.set_commitment_hash(get_optional(joint_key).commitment_hash)\n        internal_manifest, context = get_optional(builder.build())\n        constants = get_constants()\n\n        return (\n            AllPublicElectionData(\n                manifest,\n                internal_manifest,\n                context,\n                constants,\n                guardian_records,\n            ),\n            AllPrivateElectionData(guardians),\n        )\n\n    @staticmethod\n    def get_fake_manifest() -> Manifest:\n        \"\"\"Get a single fake manifest object that is manually constructed with default values.\"\"\"\n        fake_ballot_style = BallotStyle(\"some-ballot-style-id\")\n        fake_ballot_style.geopolitical_unit_ids = [\"some-geopoltical-unit-id\"]\n\n        fake_referendum_ballot_selections = [\n            # Referendum selections are simply a special case of `candidate` in the object model\n            SelectionDescription(\n                \"some-object-id-affirmative\", 0, \"some-candidate-id-1\"\n            ),\n            SelectionDescription(\"some-object-id-negative\", 1, \"some-candidate-id-2\"),\n        ]\n\n        sequence_order = 0\n        number_elected = 1\n        votes_allowed = 1\n        fake_referendum_contest = ReferendumContestDescription(\n            \"some-referendum-contest-object-id\",\n            sequence_order,\n            \"some-geopoltical-unit-id\",\n            VoteVariationType.one_of_m,\n            number_elected,\n            votes_allowed,\n            \"some-referendum-contest-name\",\n            fake_referendum_ballot_selections,\n        )\n\n        fake_candidate_ballot_selections = [\n            SelectionDescription(\n                \"some-object-id-candidate-1\", 0, \"some-candidate-id-1\"\n            ),\n            SelectionDescription(\n                \"some-object-id-candidate-2\", 1, \"some-candidate-id-2\"\n            ),\n            SelectionDescription(\n                \"some-object-id-candidate-3\", 2, \"some-candidate-id-3\"\n            ),\n        ]\n\n        sequence_order_2 = 1\n        number_elected_2 = 2\n        votes_allowed_2 = 2\n        fake_candidate_contest = CandidateContestDescription(\n            \"some-candidate-contest-object-id\",\n            sequence_order_2,\n            \"some-geopoltical-unit-id\",\n            VoteVariationType.one_of_m,\n            number_elected_2,\n            votes_allowed_2,\n            \"some-candidate-contest-name\",\n            fake_candidate_ballot_selections,\n        )\n\n        fake_manifest = Manifest(\n            spec_version=SpecVersion.EG0_95,\n            election_scope_id=\"some-scope-id\",\n            type=ElectionType.unknown,\n            start_date=datetime.now(),\n            end_date=datetime.now(),\n            geopolitical_units=[\n                GeopoliticalUnit(\n                    \"some-geopoltical-unit-id\",\n                    \"some-gp-unit-name\",\n                    ReportingUnitType.unknown,\n                )\n            ],\n            parties=[Party(\"some-party-id-1\"), Party(\"some-party-id-2\")],\n            candidates=[\n                Candidate(\"some-candidate-id-1\"),\n                Candidate(\"some-candidate-id-2\"),\n                Candidate(\"some-candidate-id-3\"),\n            ],\n            contests=[fake_referendum_contest, fake_candidate_contest],\n            ballot_styles=[fake_ballot_style],\n        )\n\n        return fake_manifest\n\n    @staticmethod\n    def get_fake_ciphertext_election(\n        manifest: Manifest, elgamal_public_key: ElGamalPublicKey\n    ) -> Tuple[InternalManifest, CiphertextElectionContext]:\n        \"\"\"Get mock election.\"\"\"\n        builder = ElectionBuilder(number_of_guardians=1, quorum=1, manifest=manifest)\n        builder.set_public_key(elgamal_public_key)\n        builder.set_commitment_hash(TWO_MOD_Q)\n        internal_manifest, context = get_optional(builder.build())\n        return internal_manifest, context\n\n    # TODO: Move to ballot Factory?\n    def get_fake_ballot(\n        self, manifest: Optional[Manifest] = None, ballot_id: Optional[str] = None\n    ) -> PlaintextBallot:\n        \"\"\"Get a single mock Ballot object that is manually constructed with default values.\"\"\"\n        if manifest is None:\n            manifest = self.get_fake_manifest()\n\n        if ballot_id is None:\n            ballot_id = \"some-unique-ballot-id-123\"\n\n        fake_ballot = PlaintextBallot(\n            ballot_id,\n            manifest.ballot_styles[0].object_id,\n            [contest_from(manifest.contests[0]), contest_from(manifest.contests[1])],\n        )\n\n        return fake_ballot\n\n    @staticmethod\n    def _get_manifest_from_file(filename: str) -> Manifest:\n        return from_file(Manifest, os.path.join(_data, filename))\n\n    @staticmethod\n    def get_encryption_device() -> EncryptionDevice:\n        \"\"\"Get mock encryption device.\"\"\"\n        return EncryptionDevice(\n            generate_device_uuid(),\n            12345,\n            45678,\n            \"polling-place\",\n        )\n\n\n@composite\ndef get_selection_description_well_formed(\n    draw: _DrawType,\n    ints: Any = integers(1, 20),\n    email_addresses: Any = emails(),\n    candidate_id: Optional[str] = None,\n    sequence_order: Optional[int] = None,\n    ids: Any = uuids(),\n) -> Tuple[str, SelectionDescription]:\n    \"\"\"Get mock well formed selection description.\"\"\"\n    if candidate_id is None:\n        candidate_id = draw(email_addresses)\n\n    if sequence_order is None:\n        sequence_order = draw(ints)\n\n    object_id = f\"{candidate_id}-selection-{draw(ids)}\"\n\n    return (object_id, SelectionDescription(object_id, sequence_order, candidate_id))\n\n\n@composite\ndef get_contest_description_well_formed(\n    draw: _DrawType,\n    ints: Any = integers(1, 20),\n    txt: Any = text(),\n    email_addresses: Any = emails(),\n    selections: Any = get_selection_description_well_formed(),\n    sequence_order: Optional[int] = None,\n    electoral_district_id: Optional[str] = None,\n) -> Tuple[str, ContestDescription]:\n    \"\"\"Get mock well formed selection contest.\"\"\"\n    object_id = f\"{draw(email_addresses)}-contest\"\n\n    if sequence_order is None:\n        sequence_order = draw(ints)\n\n    if electoral_district_id is None:\n        electoral_district_id = f\"{draw(email_addresses)}-gp-unit\"\n\n    first_int = draw(ints)\n    second_int = draw(ints)\n\n    # TODO ISSUE #33: support more votes than seats for other VoteVariationType options\n    number_elected = min(first_int, second_int)\n    votes_allowed = number_elected\n\n    selection_descriptions: List[SelectionDescription] = []\n    for i in range(max(first_int, second_int)):\n        selection: Tuple[str, SelectionDescription] = draw(selections)\n        _, selection_description = selection\n        selection_description.sequence_order = i\n        selection_descriptions.append(selection_description)\n\n    contest_description = ContestDescription(\n        object_id,\n        sequence_order,\n        electoral_district_id,\n        VoteVariationType.n_of_m,\n        number_elected,\n        votes_allowed,\n        draw(txt),\n        selection_descriptions,\n    )\n\n    placeholder_selections = generate_placeholder_selections_from(\n        contest_description, number_elected\n    )\n\n    return (\n        object_id,\n        contest_description_with_placeholders_from(\n            contest_description, placeholder_selections\n        ),\n    )\n"
  },
  {
    "path": "src/electionguard_tools/helpers/__init__.py",
    "content": "from electionguard_tools.helpers import election_builder\nfrom electionguard_tools.helpers import export\nfrom electionguard_tools.helpers import key_ceremony_orchestrator\nfrom electionguard_tools.helpers import tally_accumulate\nfrom electionguard_tools.helpers import tally_ceremony_orchestrator\n\nfrom electionguard_tools.helpers.election_builder import (\n    ElectionBuilder,\n)\nfrom electionguard_tools.helpers.export import (\n    CIPHERTEXT_BALLOT_PREFIX,\n    COEFFICIENTS_FILE_NAME,\n    CONSTANTS_FILE_NAME,\n    CONTEXT_FILE_NAME,\n    DEVICES_DIR,\n    DEVICE_PREFIX,\n    ELECTION_RECORD_DIR,\n    ENCRYPTED_TALLY_FILE_NAME,\n    GUARDIANS_DIR,\n    GUARDIAN_PREFIX,\n    MANIFEST_FILE_NAME,\n    PLAINTEXT_BALLOT_PREFIX,\n    PRIVATE_DATA_DIR,\n    PRIVATE_GUARDIAN_PREFIX,\n    SPOILED_BALLOTS_DIR,\n    SPOILED_BALLOT_PREFIX,\n    SUBMITTED_BALLOTS_DIR,\n    SUBMITTED_BALLOT_PREFIX,\n    TALLY_FILE_NAME,\n    export_private_data,\n    export_record,\n)\nfrom electionguard_tools.helpers.key_ceremony_orchestrator import (\n    KeyCeremonyOrchestrator,\n)\nfrom electionguard_tools.helpers.tally_accumulate import (\n    accumulate_plaintext_ballots,\n)\nfrom electionguard_tools.helpers.tally_ceremony_orchestrator import (\n    TallyCeremonyOrchestrator,\n)\n\n__all__ = [\n    \"CIPHERTEXT_BALLOT_PREFIX\",\n    \"COEFFICIENTS_FILE_NAME\",\n    \"CONSTANTS_FILE_NAME\",\n    \"CONTEXT_FILE_NAME\",\n    \"DEVICES_DIR\",\n    \"DEVICE_PREFIX\",\n    \"ELECTION_RECORD_DIR\",\n    \"ENCRYPTED_TALLY_FILE_NAME\",\n    \"ElectionBuilder\",\n    \"GUARDIANS_DIR\",\n    \"GUARDIAN_PREFIX\",\n    \"KeyCeremonyOrchestrator\",\n    \"MANIFEST_FILE_NAME\",\n    \"PLAINTEXT_BALLOT_PREFIX\",\n    \"PRIVATE_DATA_DIR\",\n    \"PRIVATE_GUARDIAN_PREFIX\",\n    \"SPOILED_BALLOTS_DIR\",\n    \"SPOILED_BALLOT_PREFIX\",\n    \"SUBMITTED_BALLOTS_DIR\",\n    \"SUBMITTED_BALLOT_PREFIX\",\n    \"TALLY_FILE_NAME\",\n    \"TallyCeremonyOrchestrator\",\n    \"accumulate_plaintext_ballots\",\n    \"election_builder\",\n    \"export\",\n    \"export_private_data\",\n    \"export_record\",\n    \"key_ceremony_orchestrator\",\n    \"tally_accumulate\",\n    \"tally_ceremony_orchestrator\",\n]\n"
  },
  {
    "path": "src/electionguard_tools/helpers/election_builder.py",
    "content": "from __future__ import annotations\n\nfrom dataclasses import dataclass, field\nfrom typing import Dict, Optional, Tuple\n\nfrom electionguard.elgamal import ElGamalPublicKey\n\nfrom electionguard.election import (\n    CiphertextElectionContext,\n    make_ciphertext_election_context,\n)\nfrom electionguard.group import ElementModQ\nfrom electionguard.manifest import Manifest, InternalManifest\nfrom electionguard.utils import get_optional\n\n\n@dataclass\nclass ElectionBuilder:\n    \"\"\"\n    `ElectionBuilder` is a stateful builder object that constructs `CiphertextElectionContext` objects\n    following the initialization process that ElectionGuard Expects.\n    \"\"\"\n\n    number_of_guardians: int\n    \"\"\"\n    The number of guardians necessary to generate the public key\n    \"\"\"\n    quorum: int\n    \"\"\"\n    The quorum of guardians necessary to decrypt an election.  Must be fewer than `number_of_guardians`\n    \"\"\"\n\n    manifest: Manifest\n\n    internal_manifest: InternalManifest = field(init=False)\n\n    election_key: Optional[ElGamalPublicKey] = field(default=None)\n\n    commitment_hash: Optional[ElementModQ] = field(default=None)\n\n    extended_data: Optional[Dict[str, str]] = field(default=None)\n\n    def __post_init__(self) -> None:\n        self.internal_manifest = InternalManifest(self.manifest)\n\n    def set_public_key(\n        self, election_joint_public_key: ElGamalPublicKey\n    ) -> ElectionBuilder:\n        \"\"\"\n        Set election public key\n        :param election_joint_public_key: elgamal public key for election\n        :return: election builder\n        \"\"\"\n        self.election_key = election_joint_public_key\n        return self\n\n    def set_commitment_hash(self, commitment_hash: ElementModQ) -> ElectionBuilder:\n        \"\"\"\n        Set commitment hash\n        :param commitment_hash: hash of the commitments guardians make to each other\n        :return: election builder\n        \"\"\"\n        self.commitment_hash = commitment_hash\n        return self\n\n    def add_extended_data_field(self, name: str, value: str) -> ElectionBuilder:\n        \"\"\"\n        Set extended data field\n        :param name: name of the extended data entry to add\n        :param value: value of the extended data entry\n        \"\"\"\n        if self.extended_data is None:\n            self.extended_data = {}\n        self.extended_data[name] = value\n        return self\n\n    def build(\n        self,\n    ) -> Optional[Tuple[InternalManifest, CiphertextElectionContext]]:\n        \"\"\"\n        Build election\n        :return: election manifest and context or none\n        \"\"\"\n        if not self.manifest.is_valid():\n            return None\n\n        if self.election_key is None:\n            return None\n\n        return (\n            self.internal_manifest,\n            make_ciphertext_election_context(\n                self.number_of_guardians,\n                self.quorum,\n                get_optional(self.election_key),\n                get_optional(self.commitment_hash),\n                self.manifest.crypto_hash(),\n                extended_data=self.extended_data,\n            ),\n        )\n"
  },
  {
    "path": "src/electionguard_tools/helpers/export.py",
    "content": "\"\"\"\nSample generation tool to export data from the election.\n\nSpecifically constructed to assist with creating sample data.\nThe export here is by no means exhaustive or prescriptive of how one\nmay choose to export the data for publishing the election.\n\nRefer to the ElectionGuard spec for any specifics.\n\"\"\"\n\nfrom os import path\nfrom typing import Iterable\n\nfrom electionguard.ballot import PlaintextBallot, CiphertextBallot, SubmittedBallot\nfrom electionguard.constants import ElectionConstants\nfrom electionguard.election_polynomial import LagrangeCoefficientsRecord\nfrom electionguard.guardian import GuardianRecord, PrivateGuardianRecord\nfrom electionguard.election import CiphertextElectionContext\nfrom electionguard.encrypt import EncryptionDevice\nfrom electionguard.manifest import Manifest\nfrom electionguard.serialize import to_file\nfrom electionguard.tally import PlaintextTally, PublishedCiphertextTally\n\n\n# Public\nELECTION_RECORD_DIR = \"election_record\"\nDEVICES_DIR = \"encryption_devices\"\nGUARDIANS_DIR = \"guardians\"\nSUBMITTED_BALLOTS_DIR = \"submitted_ballots\"\nSPOILED_BALLOTS_DIR = \"spoiled_ballots\"\n\nMANIFEST_FILE_NAME = \"manifest\"\nCONTEXT_FILE_NAME = \"context\"\nCONSTANTS_FILE_NAME = \"constants\"\nCOEFFICIENTS_FILE_NAME = \"coefficients\"\nENCRYPTED_TALLY_FILE_NAME = \"encrypted_tally\"\nTALLY_FILE_NAME = \"tally\"\nSUBMITTED_BALLOT_PREFIX = \"submitted_ballot_\"\nSPOILED_BALLOT_PREFIX = \"spoiled_ballot_\"\nDEVICE_PREFIX = \"device_\"\nGUARDIAN_PREFIX = \"guardian_\"\n\n# Private\nPRIVATE_DATA_DIR = \"election_private_data\"\nPLAINTEXT_BALLOT_PREFIX = \"plaintext_ballot_\"\nCIPHERTEXT_BALLOT_PREFIX = \"ciphertext_ballot_\"\nPRIVATE_GUARDIAN_PREFIX = \"private_guardian_\"\n\n\n# TODO #148 Revert PlaintextTally to PublishedPlaintextTally after moving spoiled info\ndef export_record(\n    manifest: Manifest,\n    context: CiphertextElectionContext,\n    constants: ElectionConstants,\n    devices: Iterable[EncryptionDevice],\n    submitted_ballots: Iterable[SubmittedBallot],\n    spoiled_ballots: Iterable[PlaintextTally],\n    ciphertext_tally: PublishedCiphertextTally,\n    plaintext_tally: PlaintextTally,\n    guardian_records: Iterable[GuardianRecord],\n    lagrange_coefficients: LagrangeCoefficientsRecord,\n    election_record_directory: str = ELECTION_RECORD_DIR,\n) -> None:\n    \"\"\"Export a publishable election record\"\"\"\n    devices_directory = path.join(election_record_directory, DEVICES_DIR)\n    guardian_directory = path.join(election_record_directory, GUARDIANS_DIR)\n    ballots_directory = path.join(election_record_directory, SUBMITTED_BALLOTS_DIR)\n    spoiled_directory = path.join(election_record_directory, SPOILED_BALLOTS_DIR)\n\n    to_file(manifest, MANIFEST_FILE_NAME, election_record_directory)\n    to_file(context, CONTEXT_FILE_NAME, election_record_directory)\n    to_file(constants, CONSTANTS_FILE_NAME, election_record_directory)\n    to_file(lagrange_coefficients, COEFFICIENTS_FILE_NAME, election_record_directory)\n\n    for device in devices:\n        to_file(device, DEVICE_PREFIX + str(device.device_id), devices_directory)\n\n    if guardian_records is not None:\n        for guardian_record in guardian_records:\n            to_file(\n                guardian_record,\n                GUARDIAN_PREFIX + guardian_record.guardian_id,\n                guardian_directory,\n            )\n\n    for ballot in submitted_ballots:\n        to_file(ballot, SUBMITTED_BALLOT_PREFIX + ballot.object_id, ballots_directory)\n\n    for spoiled_ballot in spoiled_ballots:\n        to_file(\n            spoiled_ballot,\n            SPOILED_BALLOT_PREFIX + spoiled_ballot.object_id,\n            spoiled_directory,\n        )\n\n    to_file(ciphertext_tally, ENCRYPTED_TALLY_FILE_NAME, election_record_directory)\n    to_file(plaintext_tally, TALLY_FILE_NAME, election_record_directory)\n\n\ndef export_private_data(\n    plaintext_ballots: Iterable[PlaintextBallot],\n    ciphertext_ballots: Iterable[CiphertextBallot],\n    private_guardian_records: Iterable[PrivateGuardianRecord],\n    private_directory: str = PRIVATE_DATA_DIR,\n) -> None:\n    \"\"\"Export non-publishable private data for an election.\n\n    Useful for generating sample data sets.\n    WARNING: DO NOT USE this in a production application.\n    \"\"\"\n    gaurdians_directory = path.join(private_directory, \"private_guardians\")\n    plaintext_ballots_directory = path.join(private_directory, \"plaintext_ballots\")\n    encrypted_ballots_directory = path.join(private_directory, \"ciphertext_ballots\")\n\n    for private_guardian_record in private_guardian_records:\n        to_file(\n            private_guardian_record,\n            PRIVATE_GUARDIAN_PREFIX + private_guardian_record.guardian_id,\n            gaurdians_directory,\n        )\n\n    for plaintext_ballot in plaintext_ballots:\n        to_file(\n            plaintext_ballot,\n            PLAINTEXT_BALLOT_PREFIX + plaintext_ballot.object_id,\n            plaintext_ballots_directory,\n        )\n\n    for ciphertext_ballot in ciphertext_ballots:\n        to_file(\n            ciphertext_ballot,\n            CIPHERTEXT_BALLOT_PREFIX + ciphertext_ballot.object_id,\n            encrypted_ballots_directory,\n        )\n"
  },
  {
    "path": "src/electionguard_tools/helpers/key_ceremony_orchestrator.py",
    "content": "from typing import List\n\nfrom electionguard.guardian import Guardian\nfrom electionguard.key_ceremony import CeremonyDetails, ElectionPartialKeyVerification\nfrom electionguard.key_ceremony_mediator import GuardianPair, KeyCeremonyMediator\nfrom electionguard.utils import get_optional\n\n\nclass KeyCeremonyOrchestrator:\n    \"\"\"Helper to assist in the key ceremony particularly for testing\"\"\"\n\n    @staticmethod\n    def create_guardians(ceremony_details: CeremonyDetails) -> List[Guardian]:\n        return [\n            Guardian.from_nonce(\n                str(i + 1),\n                i + 1,\n                ceremony_details.number_of_guardians,\n                ceremony_details.quorum,\n            )\n            for i in range(ceremony_details.number_of_guardians)\n        ]\n\n    @staticmethod\n    def perform_full_ceremony(\n        guardians: List[Guardian], mediator: KeyCeremonyMediator\n    ) -> None:\n        \"\"\"Perform full key ceremony so joint election key is ready for publish\"\"\"\n\n        KeyCeremonyOrchestrator.perform_round_1(guardians, mediator)\n        KeyCeremonyOrchestrator.perform_round_2(guardians, mediator)\n        KeyCeremonyOrchestrator.perform_round_3(guardians, mediator)\n\n    @staticmethod\n    def perform_round_1(\n        guardians: List[Guardian], mediator: KeyCeremonyMediator\n    ) -> None:\n        \"\"\"Perform Round 1 including announcing guardians and sharing public keys\"\"\"\n\n        for guardian in guardians:\n            mediator.announce(guardian.share_key())\n\n        for guardian in guardians:\n            other_guardian_keys = get_optional(mediator.share_announced(guardian.id))\n            for guardian_key in other_guardian_keys:\n                guardian.save_guardian_key(guardian_key)\n\n    @staticmethod\n    def perform_round_2(\n        guardians: List[Guardian], mediator: KeyCeremonyMediator\n    ) -> None:\n        \"\"\"Perform Round 2 including generating backups and sharing backups\"\"\"\n\n        for guardian in guardians:\n            guardian.generate_election_partial_key_backups()\n            mediator.receive_backups(guardian.share_election_partial_key_backups())\n\n        for guardian in guardians:\n            backups = get_optional(mediator.share_backups(guardian.id))\n            for backup in backups:\n                guardian.save_election_partial_key_backup(backup)\n\n    @staticmethod\n    def perform_round_3(\n        guardians: List[Guardian], mediator: KeyCeremonyMediator\n    ) -> None:\n        \"\"\"Perform Round 3 including verifying backups\"\"\"\n\n        for guardian in guardians:\n            for other_guardian in guardians:\n                verifications = []\n                if guardian.id is not other_guardian.id:\n                    verifications.append(\n                        get_optional(\n                            guardian.verify_election_partial_key_backup(\n                                other_guardian.id,\n                            )\n                        )\n                    )\n                mediator.receive_backup_verifications(verifications)\n\n    @staticmethod\n    def fail_round_3(\n        guardians: List[Guardian], mediator: KeyCeremonyMediator\n    ) -> GuardianPair:\n        \"\"\"Perform Round 3 including verifying backups but fail a single backup\"\"\"\n\n        failing_guardian_pair = GuardianPair(guardians[0].id, guardians[1].id)\n\n        for guardian in guardians:\n            for other_guardian in guardians:\n                verifications = []\n                if guardian.id is not other_guardian.id:\n                    verification = get_optional(\n                        guardian.verify_election_partial_key_backup(other_guardian.id)\n                    )\n                    if (\n                        verification.owner_id is failing_guardian_pair.owner_id\n                        and verification.designated_id\n                        is failing_guardian_pair.designated_id\n                    ):\n                        verification = ElectionPartialKeyVerification(\n                            failing_guardian_pair.owner_id,\n                            failing_guardian_pair.designated_id,\n                            failing_guardian_pair.designated_id,\n                            False,\n                        )\n                    verifications.append(verification)\n                mediator.receive_backup_verifications(verifications)\n\n        return failing_guardian_pair\n"
  },
  {
    "path": "src/electionguard_tools/helpers/tally_accumulate.py",
    "content": "from typing import List, Dict\nfrom electionguard.ballot import PlaintextBallot\n\n\ndef accumulate_plaintext_ballots(ballots: List[PlaintextBallot]) -> Dict[str, int]:\n    \"\"\"\n    Internal helper function for testing: takes a list of plaintext ballots as input,\n    digs into all of the individual selections and then accumulates them, using\n    their `object_id` fields as keys. This function only knows what to do with\n    `n_of_m` elections. It's not a general-purpose tallying mechanism for other\n    election types.\n\n    :param ballots: a list of plaintext ballots\n    :return: a dict from selection object_id's to integer totals\n    \"\"\"\n    tally: Dict[str, int] = {}\n    for ballot in ballots:\n        for contest in ballot.contests:\n            for selection in contest.ballot_selections:\n                assert (\n                    not selection.is_placeholder_selection\n                ), \"Placeholder selections should not exist in the plaintext ballots\"\n                desc_id = selection.object_id\n                if desc_id not in tally:\n                    tally[desc_id] = 0\n                # returns 1 or 0 for n-of-m ballot selections\n                tally[desc_id] += selection.vote\n    return tally\n"
  },
  {
    "path": "src/electionguard_tools/helpers/tally_ceremony_orchestrator.py",
    "content": "from typing import List, Optional\n\nfrom electionguard.ballot import SubmittedBallot\nfrom electionguard.election import CiphertextElectionContext\nfrom electionguard.guardian import Guardian, get_valid_ballot_shares\nfrom electionguard.decryption_mediator import DecryptionMediator\nfrom electionguard.key_ceremony import ElectionPublicKey\nfrom electionguard.tally import CiphertextTally\nfrom electionguard.utils import get_optional\n\n\nclass TallyCeremonyOrchestrator:\n    \"\"\"Helper to assist in the decryption process particularly for testing\"\"\"\n\n    @staticmethod\n    def perform_decryption_setup(\n        available_guardians: List[Guardian],\n        mediator: DecryptionMediator,\n        context: CiphertextElectionContext,\n        ciphertext_tally: CiphertextTally,\n        submitted_ballots: Optional[List[SubmittedBallot]] = None,\n    ) -> None:\n        \"\"\"\n        Perform the necessary setup to ensure that a mediator can decrypt with all guardians available\n        \"\"\"\n        TallyCeremonyOrchestrator.announcement(\n            available_guardians,\n            [guardian.share_key() for guardian in available_guardians],\n            mediator,\n            context,\n            ciphertext_tally,\n            submitted_ballots,\n        )\n\n    @staticmethod\n    def perform_compensated_decryption_setup(\n        available_guardians: List[Guardian],\n        all_guardians_keys: List[ElectionPublicKey],\n        mediator: DecryptionMediator,\n        context: CiphertextElectionContext,\n        ciphertext_tally: CiphertextTally,\n        submitted_ballots: Optional[List[SubmittedBallot]] = None,\n    ) -> None:\n        \"\"\"\n        Perform the necessary setup to ensure that a mediator can decrypt when there are guardians missing\n        \"\"\"\n        TallyCeremonyOrchestrator.announcement(\n            available_guardians,\n            all_guardians_keys,\n            mediator,\n            context,\n            ciphertext_tally,\n            submitted_ballots,\n        )\n        TallyCeremonyOrchestrator.exchange_compensated_decryption_shares(\n            available_guardians, mediator, context, ciphertext_tally, submitted_ballots\n        )\n\n    @staticmethod\n    def announcement(\n        available_guardians: List[Guardian],\n        all_guardians_keys: List[ElectionPublicKey],\n        mediator: DecryptionMediator,\n        context: CiphertextElectionContext,\n        ciphertext_tally: CiphertextTally,\n        submitted_ballots: Optional[List[SubmittedBallot]] = None,\n    ) -> None:\n        \"\"\"\n        Each available guardian announces their presence. The missing guardians are also announced\n        \"\"\"\n        if submitted_ballots is None:\n            submitted_ballots = []\n\n        # Announce available guardians\n        for available_guardian in available_guardians:\n            guardian_key = available_guardian.share_key()\n            tally_share = get_optional(\n                available_guardian.compute_tally_share(ciphertext_tally, context)\n            )\n            ballot_shares = get_valid_ballot_shares(\n                available_guardian.compute_ballot_shares(submitted_ballots, context)\n            )\n\n            mediator.announce(guardian_key, tally_share, ballot_shares)  # type: ignore\n\n        # Announce missing guardians\n        # Get all guardian keys and filter to determine the missing guardians\n        available_guardian_ids = [guardian.id for guardian in available_guardians]\n        missing_guardians = [\n            key\n            for key in all_guardians_keys\n            if key.owner_id not in available_guardian_ids\n        ]\n\n        for missing_guardian_key in missing_guardians:\n            mediator.announce_missing(missing_guardian_key)\n\n    @staticmethod\n    def exchange_compensated_decryption_shares(\n        available_guardians: List[Guardian],\n        mediator: DecryptionMediator,\n        context: CiphertextElectionContext,\n        ciphertext_tally: CiphertextTally,\n        submitted_ballots: Optional[List[SubmittedBallot]] = None,\n    ) -> None:\n        \"\"\"\n        Available guardians generate the compensated decryption shares for the missing guardians\n        and send to the mediator.\n        \"\"\"\n        if submitted_ballots is None:\n            submitted_ballots = []\n\n        # Exchange compensated shares\n        missing_guardians = mediator.get_missing_guardians()\n        for available_guardian in available_guardians:\n            for missing_guardian in missing_guardians:\n                tally_share = available_guardian.compute_compensated_tally_share(\n                    missing_guardian.owner_id,\n                    ciphertext_tally,\n                    context,\n                )\n                if tally_share is not None:\n                    mediator.receive_tally_compensation_share(tally_share)\n\n                ballot_shares = get_valid_ballot_shares(\n                    available_guardian.compute_compensated_ballot_shares(\n                        missing_guardian.owner_id,\n                        submitted_ballots,\n                        context,\n                    )\n                )\n                mediator.receive_ballot_compensation_shares(ballot_shares)\n\n        # Combine compensated shares into decryption share for missing guardians\n        mediator.reconstruct_shares_for_tally(ciphertext_tally)\n        mediator.reconstruct_shares_for_ballots(submitted_ballots)\n"
  },
  {
    "path": "src/electionguard_tools/scripts/__init__.py",
    "content": "from electionguard_tools.scripts import sample_generator\n\nfrom electionguard_tools.scripts.sample_generator import (\n    DEFAULT_NUMBER_OF_BALLOTS,\n    DEFAULT_SAMPLE_MANIFEST,\n    DEFAULT_SPEC_VERSION,\n    DEFAULT_SPOIL_RATE,\n    DEFAULT_USE_ALL_GUARDIANS,\n    DEFAULT_USE_PRIVATE_DATA,\n    ElectionSampleDataGenerator,\n)\n\n__all__ = [\n    \"DEFAULT_NUMBER_OF_BALLOTS\",\n    \"DEFAULT_SAMPLE_MANIFEST\",\n    \"DEFAULT_SPEC_VERSION\",\n    \"DEFAULT_SPOIL_RATE\",\n    \"DEFAULT_USE_ALL_GUARDIANS\",\n    \"DEFAULT_USE_PRIVATE_DATA\",\n    \"ElectionSampleDataGenerator\",\n    \"sample_generator\",\n]\n"
  },
  {
    "path": "src/electionguard_tools/scripts/sample_generator.py",
    "content": "#!/usr/bin/env python\nfrom random import randint\nfrom shutil import rmtree\nfrom typing import List\n\nfrom electionguard.ballot import (\n    BallotBoxState,\n    CiphertextBallot,\n    SubmittedBallot,\n)\nfrom electionguard.data_store import DataStore\nfrom electionguard.ballot_box import BallotBox, get_ballots\nfrom electionguard.decryption_mediator import DecryptionMediator\nfrom electionguard.election_polynomial import LagrangeCoefficientsRecord\nfrom electionguard.encrypt import (\n    EncryptionDevice,\n    EncryptionMediator,\n)\nfrom electionguard.guardian import PrivateGuardianRecord\nfrom electionguard.tally import tally_ballots\nfrom electionguard.type import BallotId\nfrom electionguard.utils import get_optional\n\nfrom electionguard_tools.factories.ballot_factory import BallotFactory\nfrom electionguard_tools.factories.election_factory import ElectionFactory, QUORUM\nfrom electionguard_tools.helpers.tally_ceremony_orchestrator import (\n    TallyCeremonyOrchestrator,\n)\nfrom electionguard_tools.helpers.export import (\n    export_record,\n    export_private_data,\n    ELECTION_RECORD_DIR,\n    PRIVATE_DATA_DIR,\n)\n\n\nDEFAULT_NUMBER_OF_BALLOTS = 5\nDEFAULT_SPOIL_RATE = 50\nDEFAULT_USE_ALL_GUARDIANS = False\nDEFAULT_USE_PRIVATE_DATA = False\nDEFAULT_SPEC_VERSION = \"1.0\"\nDEFAULT_SAMPLE_MANIFEST = \"hamilton-general\"\n\n\nclass ElectionSampleDataGenerator:\n    \"\"\"\n    Generates sample data for an example election using the \"Hamilton County\" data set.\n    \"\"\"\n\n    election_factory: ElectionFactory\n    ballot_factory: BallotFactory\n\n    encryption_device: EncryptionDevice\n    encrypter: EncryptionMediator\n\n    def __init__(self) -> None:\n        \"\"\"Initialize the class\"\"\"\n        self.election_factory = ElectionFactory()\n        self.ballot_factory = BallotFactory()\n        self.encryption_device = self.election_factory.get_encryption_device()\n\n    def generate(\n        self,\n        number_of_ballots: int = DEFAULT_NUMBER_OF_BALLOTS,\n        spoil_rate: int = DEFAULT_SPOIL_RATE,\n        use_all_guardians: bool = DEFAULT_USE_ALL_GUARDIANS,\n        use_private_data: bool = DEFAULT_USE_PRIVATE_DATA,\n        sample_manifest: str = DEFAULT_SAMPLE_MANIFEST,\n    ) -> None:\n        \"\"\"\n        Generate the sample data set\n        \"\"\"\n\n        # Clear the results directory\n        rmtree(ELECTION_RECORD_DIR, ignore_errors=True)\n        rmtree(PRIVATE_DATA_DIR, ignore_errors=True)\n\n        # Configure the election\n        # TODO: pass the spec version and the manifest name in\n        (\n            public_data,\n            private_data,\n        ) = self.election_factory.get_sample_manifest_with_encryption_context(\n            sample_manifest\n        )\n        plaintext_ballots = (\n            self.ballot_factory.generate_fake_plaintext_ballots_for_election(\n                public_data.internal_manifest, number_of_ballots\n            )\n        )\n        self.encrypter = EncryptionMediator(\n            public_data.internal_manifest, public_data.context, self.encryption_device\n        )\n\n        # Encrypt some ballots\n        ciphertext_ballots: List[CiphertextBallot] = []\n        for plaintext_ballot in plaintext_ballots:\n            ciphertext_ballots.append(\n                get_optional(self.encrypter.encrypt(plaintext_ballot))\n            )\n\n        ballot_store: DataStore[BallotId, SubmittedBallot] = DataStore()\n        ballot_box = BallotBox(\n            public_data.internal_manifest, public_data.context, ballot_store\n        )\n\n        # Randomly cast/spoil the ballots\n        submitted_ballots: List[SubmittedBallot] = []\n        for ballot in ciphertext_ballots:\n            if randint(0, 100) < spoil_rate:\n                submitted_ballots.append(get_optional(ballot_box.spoil(ballot)))\n            else:\n                submitted_ballots.append(get_optional(ballot_box.cast(ballot)))\n\n        # Tally\n        spoiled_ciphertext_ballots = get_ballots(ballot_store, BallotBoxState.SPOILED)\n        ciphertext_tally = get_optional(\n            tally_ballots(\n                ballot_store, public_data.internal_manifest, public_data.context\n            )\n        )\n\n        # Decrypt\n        mediator = DecryptionMediator(\"sample-manifest-decrypter\", public_data.context)\n        available_guardians = (\n            private_data.guardians\n            if use_all_guardians\n            else private_data.guardians[0:QUORUM]\n        )\n\n        spoiled_ballots = list(spoiled_ciphertext_ballots.values())\n\n        if not use_all_guardians:\n            available_guardians = private_data.guardians[0:QUORUM]\n            all_guardian_keys = [\n                guardian.share_key() for guardian in private_data.guardians\n            ]\n\n            TallyCeremonyOrchestrator.perform_compensated_decryption_setup(\n                available_guardians,\n                all_guardian_keys,\n                mediator,\n                public_data.context,\n                ciphertext_tally,\n                spoiled_ballots,\n            )\n        else:\n            TallyCeremonyOrchestrator.perform_decryption_setup(\n                available_guardians,\n                mediator,\n                public_data.context,\n                ciphertext_tally,\n                spoiled_ballots,\n            )\n\n        plaintext_tally = mediator.get_plaintext_tally(\n            ciphertext_tally, public_data.manifest\n        )\n        plaintext_spoiled_ballots = list(\n            get_optional(\n                mediator.get_plaintext_ballots(spoiled_ballots, public_data.manifest)\n            ).values()\n        )\n\n        if plaintext_tally:\n            export_record(\n                public_data.manifest,\n                public_data.context,\n                public_data.constants,\n                [self.encryption_device],\n                submitted_ballots,\n                plaintext_spoiled_ballots,\n                ciphertext_tally.publish(),\n                plaintext_tally,\n                public_data.guardians,\n                LagrangeCoefficientsRecord(mediator.get_lagrange_coefficients()),\n            )\n\n            if use_private_data:\n                export_private_data(\n                    plaintext_ballots,\n                    ciphertext_ballots,\n                    [\n                        # pylint: disable=protected-access\n                        PrivateGuardianRecord(\n                            guardian.id,\n                            guardian._election_keys,\n                            guardian._backups_to_share,\n                            guardian._guardian_election_public_keys,\n                            guardian._guardian_election_partial_key_backups,\n                            guardian._guardian_election_partial_key_verifications,\n                        )\n                        for guardian in private_data.guardians\n                    ],\n                )\n\n\nif __name__ == \"__main__\":\n    import argparse\n\n    parser = argparse.ArgumentParser(\n        description=\"Generate sample ballot data\",\n        formatter_class=argparse.ArgumentDefaultsHelpFormatter,\n    )\n\n    parser.add_argument(\n        \"-m\",\n        \"--manifest\",\n        metavar=\"<manifest>\",\n        default=DEFAULT_SAMPLE_MANIFEST,\n        type=str,\n        help=\"The manifest to use to generate sample data.\",\n        choices=[\"full\", \"hamilton-general\", \"minimal\", \"small\"],\n    )\n    parser.add_argument(\n        \"-n\",\n        \"--number-of-ballots\",\n        metavar=\"<count>\",\n        default=DEFAULT_NUMBER_OF_BALLOTS,\n        type=int,\n        help=\"The number of ballots to generate.\",\n    )\n    parser.add_argument(\n        \"-s\",\n        \"--spoil-rate\",\n        metavar=\"<rate>\",\n        default=DEFAULT_SPOIL_RATE,\n        type=int,\n        help=\"The approximate percentage of total ballots to spoil instead of cast. Provide a number from 0-100.\",\n    )\n    parser.add_argument(\n        \"-a\",\n        \"--all-guardians\",\n        default=DEFAULT_USE_ALL_GUARDIANS,\n        action=\"store_true\",\n        help=\"If specified, all guardians will be included.  Otherwise, only the threshold number will be included.\",\n    )\n    parser.add_argument(\n        \"-p\",\n        \"--private-data\",\n        default=DEFAULT_USE_PRIVATE_DATA,\n        action=\"store_true\",\n        help=\"Include private data when generating.\",\n    )\n    parser.add_argument(\n        \"-v\",\n        \"--version\",\n        default=DEFAULT_SPEC_VERSION,\n        type=str,\n        help=\"The spec version to use.\",\n        choices=[DEFAULT_SPEC_VERSION],\n    )\n    args = parser.parse_args()\n\n    ElectionSampleDataGenerator().generate(\n        args.number_of_ballots,\n        args.spoil_rate,\n        args.all_guardians,\n        args.private_data,\n        args.manifest,\n    )\n"
  },
  {
    "path": "src/electionguard_tools/strategies/__init__.py",
    "content": "from electionguard_tools.strategies import election\nfrom electionguard_tools.strategies import elgamal\nfrom electionguard_tools.strategies import group\n\nfrom electionguard_tools.strategies.election import (\n    CiphertextElectionsTupleType,\n    ElectionsAndBallotsTupleType,\n    annotated_emails,\n    annotated_strings,\n    ballot_styles,\n    candidate_contest_descriptions,\n    candidates,\n    ciphertext_elections,\n    contact_infos,\n    contest_descriptions,\n    contest_descriptions_room_for_overvoting,\n    election_descriptions,\n    election_types,\n    elections_and_ballots,\n    geopolitical_units,\n    human_names,\n    internationalized_human_names,\n    internationalized_texts,\n    language_human_names,\n    languages,\n    party_lists,\n    plaintext_voted_ballot,\n    plaintext_voted_ballots,\n    referendum_contest_descriptions,\n    reporting_unit_types,\n    two_letter_codes,\n)\nfrom electionguard_tools.strategies.elgamal import (\n    elgamal_keypairs,\n)\nfrom electionguard_tools.strategies.group import (\n    elements_mod_p,\n    elements_mod_p_no_zero,\n    elements_mod_q,\n    elements_mod_q_no_zero,\n)\n\n__all__ = [\n    \"CiphertextElectionsTupleType\",\n    \"ElectionsAndBallotsTupleType\",\n    \"annotated_emails\",\n    \"annotated_strings\",\n    \"ballot_styles\",\n    \"candidate_contest_descriptions\",\n    \"candidates\",\n    \"ciphertext_elections\",\n    \"contact_infos\",\n    \"contest_descriptions\",\n    \"contest_descriptions_room_for_overvoting\",\n    \"election\",\n    \"election_descriptions\",\n    \"election_types\",\n    \"elections_and_ballots\",\n    \"elements_mod_p\",\n    \"elements_mod_p_no_zero\",\n    \"elements_mod_q\",\n    \"elements_mod_q_no_zero\",\n    \"elgamal\",\n    \"elgamal_keypairs\",\n    \"geopolitical_units\",\n    \"group\",\n    \"human_names\",\n    \"internationalized_human_names\",\n    \"internationalized_texts\",\n    \"language_human_names\",\n    \"languages\",\n    \"party_lists\",\n    \"plaintext_voted_ballot\",\n    \"plaintext_voted_ballots\",\n    \"referendum_contest_descriptions\",\n    \"reporting_unit_types\",\n    \"two_letter_codes\",\n]\n"
  },
  {
    "path": "src/electionguard_tools/strategies/election.py",
    "content": "from copy import deepcopy\nfrom functools import reduce\nfrom random import Random\nfrom typing import Any, TypeVar, Callable, List, Optional, Tuple, Union\n\nfrom hypothesis.provisional import urls\nfrom hypothesis.strategies import (\n    composite,\n    emails,\n    integers,\n    lists,\n    SearchStrategy,\n    text,\n    uuids,\n    datetimes,\n    one_of,\n    just,\n)\nfrom hypothesis.strategies._internal.core import sampled_from\n\nfrom electionguard.ballot import PlaintextBallotContest, PlaintextBallot\nfrom electionguard.election import (\n    CiphertextElectionContext,\n    make_ciphertext_election_context,\n)\nfrom electionguard.elgamal import ElGamalKeyPair\nfrom electionguard.encrypt import selection_from\nfrom electionguard.group import ElementModQ\nfrom electionguard.manifest import (\n    Candidate,\n    ElectionType,\n    ReportingUnitType,\n    SpecVersion,\n    VoteVariationType,\n    ContactInformation,\n    GeopoliticalUnit,\n    BallotStyle,\n    Language,\n    InternationalizedText,\n    AnnotatedString,\n    Party,\n    CandidateContestDescription,\n    ReferendumContestDescription,\n    SelectionDescription,\n    ContestDescription,\n    Manifest,\n    InternalManifest,\n)\n\nfrom electionguard_tools.strategies.elgamal import elgamal_keypairs\nfrom electionguard_tools.strategies.group import elements_mod_q_no_zero\n\n\n_T = TypeVar(\"_T\")\n_DrawType = Callable[[SearchStrategy[_T]], _T]\n\n_first_names = [\n    \"James\",\n    \"Mary\",\n    \"John\",\n    \"Patricia\",\n    \"Robert\",\n    \"Jennifer\",\n    \"Michael\",\n    \"Linda\",\n    \"William\",\n    \"Elizabeth\",\n    \"David\",\n    \"Barbara\",\n    \"Richard\",\n    \"Susan\",\n    \"Joseph\",\n    \"Jessica\",\n    \"Thomas\",\n    \"Sarah\",\n    \"Charles\",\n    \"Karen\",\n    \"Christopher\",\n    \"Nancy\",\n    \"Daniel\",\n    \"Margaret\",\n    \"Matthew\",\n    \"Lisa\",\n    \"Anthony\",\n    \"Betty\",\n    \"Donald\",\n    \"Dorothy\",\n    \"Sylvia\",\n    \"Viktor\",\n    \"Camille\",\n    \"Mirai\",\n    \"Anant\",\n    \"Rohan\",\n    \"François\",\n    \"Altuğ\",\n    \"Sigurður\",\n    \"Böðmóður\",\n    \"Quang Dũng\",\n]\n\n_last_names = [\n    \"SMITH\",\n    \"JOHNSON\",\n    \"WILLIAMS\",\n    \"JONES\",\n    \"BROWN\",\n    \"DAVIS\",\n    \"MILLER\",\n    \"WILSON\",\n    \"MOORE\",\n    \"TAYLOR\",\n    \"ANDERSON\",\n    \"THOMAS\",\n    \"JACKSON\",\n    \"WHITE\",\n    \"HARRIS\",\n    \"MARTIN\",\n    \"THOMPSON\",\n    \"GARCIA\",\n    \"MARTINEZ\",\n    \"ROBINSON\",\n    \"CLARK\",\n    \"RODRIGUEZ\",\n    \"LEWIS\",\n    \"LEE\",\n    \"WALKER\",\n    \"HALL\",\n    \"ALLEN\",\n    \"YOUNG\",\n    \"HERNANDEZ\",\n    \"KING\",\n    \"WRIGHT\",\n    \"LOPEZ\",\n    \"HILL\",\n    \"SCOTT\",\n    \"GREEN\",\n    \"ADAMS\",\n    \"BAKER\",\n    \"GONZALEZ\",\n    \"STEELE-LOY\",\n    \"O'CONNOR\",\n    \"ANAND\",\n    \"PATEL\",\n    \"GUPTA\",\n    \"ĐẶNG\",\n]\n\n\n@composite\ndef human_names(draw: _DrawType) -> str:\n    \"\"\"\n    Generates a string with a human first and last name.\n    :param draw: Hidden argument, used by Hypothesis.\n    \"\"\"\n    return (\n        f\"{_first_names[draw(integers(0, len(_first_names) - 1))]} \"\n        f\"{_last_names[draw(integers(0, len(_last_names) - 1))]}\"\n    )\n\n\n@composite\ndef election_types(draw: _DrawType) -> ElectionType:\n    \"\"\"\n    Generates an `ElectionType`.\n    :param draw: Hidden argument, used by Hypothesis.\n    \"\"\"\n    n = draw(sampled_from(ElectionType))\n    return ElectionType(n)\n\n\n@composite\ndef reporting_unit_types(draw: _DrawType) -> ReportingUnitType:\n    \"\"\"\n    Generates a `ReportingUnitType` object.\n    :param draw: Hidden argument, used by Hypothesis.\n    \"\"\"\n    n = draw(sampled_from(ReportingUnitType))\n    return ReportingUnitType(n)\n\n\n@composite\ndef contact_infos(draw: _DrawType) -> ContactInformation:\n    \"\"\"\n    Generates a `ContactInformation` object.\n    :param draw: Hidden argument, used by Hypothesis.\n    \"\"\"\n    return ContactInformation(\n        None,\n        draw(lists(annotated_emails(), min_size=1, max_size=3)),\n        None,\n        draw(human_names()),\n    )\n\n\n@composite\ndef two_letter_codes(draw: _DrawType, min_size: int = 2, max_size: int = 2) -> Any:\n    \"\"\"\n    Generates a string with only a few characters, by default 2 letters\n    from `a` to `z`, but configurable with the `min_size` and `max_size`\n    parameters. Useful when you want something like a two-letter country\n    or language code.\n    :param draw: Hidden argument, used by Hypothesis.\n    :param min_size: minimum number of characters to generate (default: 2)\n    :param max_size: maximum number of characters to generate (default: 2)\n    \"\"\"\n    return draw(\n        text(\n            alphabet=\"abcdefghijklmnopqrstuvwxyz\", min_size=min_size, max_size=max_size\n        )\n    )\n\n\n@composite\ndef languages(draw: _DrawType) -> Language:\n    \"\"\"\n    Generates a `Language` object with an arbitrary two-letter string as the code and\n    something messier for the text ostensibly written in that language.\n    :param draw: Hidden argument, used by Hypothesis.\n    \"\"\"\n    return Language(draw(emails()), draw(two_letter_codes()))\n\n\n@composite\ndef language_human_names(draw: _DrawType) -> Language:\n    \"\"\"\n    Generates a `Language` object with an arbitrary two-letter string as the code and\n    a human name for the text ostensibly written in that language.\n    :param draw: Hidden argument, used by Hypothesis.\n    \"\"\"\n    return Language(draw(human_names()), draw(two_letter_codes()))\n\n\n@composite\ndef internationalized_texts(draw: _DrawType) -> InternationalizedText:\n    \"\"\"\n    Generates an `InternationalizedText` object with a list of `Language` objects\n    within (representing a multilingual string).\n    :param draw: Hidden argument, used by Hypothesis.\n    \"\"\"\n    return InternationalizedText(draw(lists(languages(), min_size=1, max_size=3)))\n\n\n@composite\ndef internationalized_human_names(draw: _DrawType) -> InternationalizedText:\n    \"\"\"\n    Generates an `InternationalizedText` object with a list of `Language` objects\n    within (representing a multilingual human name).\n    :param draw: Hidden argument, used by Hypothesis.\n    \"\"\"\n    return InternationalizedText(\n        draw(lists(language_human_names(), min_size=1, max_size=3))\n    )\n\n\n@composite\ndef annotated_strings(draw: _DrawType) -> AnnotatedString:\n    \"\"\"\n    Generates an `AnnotatedString` object with one `Language` and an associated\n    `value` string.\n    :param draw: Hidden argument, used by Hypothesis.\n    \"\"\"\n    s = draw(languages())\n    # We're just reusing the \"value\" string already associated with the language for now.\n    return AnnotatedString(annotation=s.language, value=s.value)\n\n\n@composite\ndef annotated_emails(draw: _DrawType) -> AnnotatedString:\n    \"\"\"\n    Generates a `Email` object with an arbitrary two-letter string as annotation and an\n    email format string as value.\n    :param draw: Hidden argument, used by Hypothesis.\n    \"\"\"\n    return AnnotatedString(draw(two_letter_codes()), draw(emails()))\n\n\n@composite\ndef ballot_styles(\n    draw: _DrawType, parties: List[Party], geo_units: List[GeopoliticalUnit]\n) -> BallotStyle:\n    \"\"\"\n    Generates a `BallotStyle` object, which rolls up a list of parties and\n    geopolitical units (passed as arguments), with some additional information\n    added on as well.\n    :param draw: Hidden argument, used by Hypothesis.\n    :param party_ids: a list of `Party` objects to be used in this ballot style\n    :param geo_units: a list of `GeopoliticalUnit` objects to be used in this ballot style\n    \"\"\"\n    assert len(parties) > 0\n    assert len(geo_units) > 0\n\n    gp_unit_ids = [x.object_id for x in geo_units]\n    if len(gp_unit_ids) == 0:\n        gp_unit_ids = []\n\n    party_ids = [party.get_party_id() for party in parties]\n    if len(party_ids) == 0:\n        party_ids = []\n\n    image_uri = draw(urls())\n    return BallotStyle(str(draw(uuids())), gp_unit_ids, party_ids, image_uri)\n\n\n@composite\ndef party_lists(draw: _DrawType, num_parties: int) -> List[Party]:\n    \"\"\"\n    Generates a `List[Party]` of the requested length.\n    :param draw: Hidden argument, used by Hypothesis.\n    :param num_parties: Number of parties to generate in the list.\n    \"\"\"\n    party_names = [f\"Party{n}\" for n in range(num_parties)]\n    party_abbrvs = [f\"P{n}\" for n in range(num_parties)]\n\n    assert num_parties > 0\n\n    return [\n        Party(\n            object_id=str(draw(uuids())),\n            name=InternationalizedText([Language(party_names[i], \"en\")]),\n            abbreviation=party_abbrvs[i],\n            color=None,\n            logo_uri=draw(urls()),\n        )\n        for i in range(num_parties)\n    ]\n\n\n@composite\ndef geopolitical_units(draw: _DrawType) -> GeopoliticalUnit:\n    \"\"\"\n    Generates a `GeopoliticalUnit` object.\n    :param draw: Hidden argument, used by Hypothesis.\n    \"\"\"\n    return GeopoliticalUnit(\n        object_id=str(draw(uuids())),\n        name=draw(emails()),\n        type=draw(reporting_unit_types()),\n        contact_information=draw(contact_infos()),\n    )\n\n\n@composite\ndef candidates(draw: _DrawType, party_list: Optional[List[Party]]) -> Candidate:\n    \"\"\"\n    Generates a `Candidate` object, assigning it one of the parties from `party_list` at random,\n    with a chance that there will be no party assigned at all.\n    :param draw: Hidden argument, used by Hypothesis.\n    :param party_list: A list of `Party` objects. If None, then the resulting `Candidate`\n        will have no party.\n    \"\"\"\n    if party_list:\n        party = party_list[draw(integers(0, len(party_list) - 1))]\n        party_id = party.get_party_id()\n    else:\n        party_id = None\n\n    return Candidate(\n        str(draw(uuids())),\n        draw(internationalized_human_names()),\n        party_id,\n        draw(one_of(just(None), urls())),\n    )\n\n\ndef _candidate_to_selection_description(\n    candidate: Candidate, sequence_order: int\n) -> SelectionDescription:\n    \"\"\"\n    Given a `Candidate` and its position in a list of candidates, returns an equivalent\n    `SelectionDescription`. The selection's `object_id` will contain the candidates's\n    `object_id` within, but will have a \"c-\" prefix attached, so you'll be able to\n    tell that they're related.\n    \"\"\"\n    return SelectionDescription(\n        f\"c-{candidate.object_id}\", sequence_order, candidate.get_candidate_id()\n    )\n\n\n@composite\ndef candidate_contest_descriptions(\n    draw: _DrawType,\n    sequence_order: int,\n    party_list: List[Party],\n    geo_units: List[GeopoliticalUnit],\n    n: Optional[int] = None,\n    m: Optional[int] = None,\n) -> Tuple[List[Candidate], CandidateContestDescription]:\n    \"\"\"\n    Generates a tuple: a `List[Candidate]` and a corresponding `CandidateContestDescription` for\n    an n-of-m contest.\n    :param draw: Hidden argument, used by Hypothesis.\n    :param sequence_order: integer describing the order of this contest; make these sequential when\n        generating many contests.\n    :param party_list: A list of `Party` objects; each candidate's party is drawn at random from this list.\n    :param geo_units: A list of `GeopoliticalUnit`; one of these goes into the `electoral_district_id`\n    :param n: optional integer, specifying a particular value for n in this n-of-m contest, otherwise\n        it's varied by Hypothesis.\n    :param m: optional integer, specifying a particular value for m in this n-of-m contest, otherwise\n        it's varied by Hypothesis.\n    \"\"\"\n\n    if n is None:\n        n = draw(integers(1, 3))\n    if m is None:\n        m = n + draw(integers(0, 3))  # for an n-of-m election\n\n    party_ids = [p.get_party_id() for p in party_list]\n\n    contest_candidates = draw(lists(candidates(party_list), min_size=m, max_size=m))\n    selection_descriptions = [\n        _candidate_to_selection_description(contest_candidates[i], i) for i in range(m)\n    ]\n\n    vote_variation = VoteVariationType.one_of_m if n == 1 else VoteVariationType.n_of_m\n\n    return (\n        contest_candidates,\n        CandidateContestDescription(\n            object_id=str(draw(uuids())),\n            electoral_district_id=geo_units[\n                draw(integers(0, len(geo_units) - 1))\n            ].object_id,\n            sequence_order=sequence_order,\n            vote_variation=vote_variation,\n            number_elected=n,\n            votes_allowed=n,  # should this be None or n?\n            name=draw(emails()),\n            ballot_selections=selection_descriptions,\n            ballot_title=draw(internationalized_texts()),\n            ballot_subtitle=draw(internationalized_texts()),\n            primary_party_ids=party_ids,\n        ),\n    )\n\n\n@composite\ndef contest_descriptions_room_for_overvoting(\n    draw: _DrawType,\n    sequence_order: int,\n    party_list: List[Party],\n    geo_units: List[GeopoliticalUnit],\n) -> Any:\n    \"\"\"\n    Similar to `contest_descriptions`, but guarantees that for the n-of-m contest that n < m,\n    therefore it's possible to construct an \"overvoted\" plaintext, which should then fail subsequent tests.\n    :param draw: Hidden argument, used by Hypothesis.\n    :param sequence_order: integer describing the order of this contest; make these sequential when\n        generating many contests.\n    :param party_list: A list of `Party` objects; each candidate's party is drawn at random from this list.\n    :param geo_units: A list of `GeopoliticalUnit`; one of these goes into the `electoral_district_id`\n    \"\"\"\n    n = draw(integers(1, 3))\n    m = n + draw(integers(1, 3))\n    return draw(\n        candidate_contest_descriptions(\n            sequence_order=sequence_order,\n            party_list=party_list,\n            geo_units=geo_units,\n            n=n,\n            m=m,\n        )\n    )\n\n\n@composite\ndef referendum_contest_descriptions(\n    draw: _DrawType, sequence_order: int, geo_units: List[GeopoliticalUnit]\n) -> Tuple[List[Candidate], ReferendumContestDescription]:\n    \"\"\"\n    Generates a tuple: a list of party-less candidates and a corresponding `ReferendumContestDescription`.\n    :param draw: Hidden argument, used by Hypothesis.\n    :param sequence_order: integer describing the order of this contest; make these sequential when\n        generating many contests.\n    :param geo_units: A list of `GeopoliticalUnit`; one of these goes into the `electoral_district_id`\n    \"\"\"\n    n = draw(integers(1, 3))\n\n    contest_candidates = draw(lists(candidates(None), min_size=n, max_size=n))\n    selection_descriptions = [\n        _candidate_to_selection_description(contest_candidates[i], i) for i in range(n)\n    ]\n\n    return (\n        contest_candidates,\n        ReferendumContestDescription(\n            object_id=str(draw(uuids())),\n            electoral_district_id=geo_units[\n                draw(integers(0, len(geo_units) - 1))\n            ].object_id,\n            sequence_order=sequence_order,\n            vote_variation=VoteVariationType.one_of_m,\n            number_elected=1,\n            votes_allowed=1,  # should this be None or 1?\n            name=draw(emails()),\n            ballot_selections=selection_descriptions,\n            ballot_title=draw(internationalized_texts()),\n            ballot_subtitle=draw(internationalized_texts()),\n        ),\n    )\n\n\n@composite\ndef contest_descriptions(\n    draw: _DrawType,\n    sequence_order: int,\n    party_list: List[Party],\n    geo_units: List[GeopoliticalUnit],\n) -> Any:\n    \"\"\"\n    Generates either the result of `referendum_contest_descriptions` or `candidate_contest_descriptions`.\n    :param draw: Hidden argument, used by Hypothesis.\n    :param sequence_order: integer describing the order of this contest; make these sequential when\n        generating many contests.\n    :param party_list: A list of `Party` objects; each candidate's party is drawn at random from this list.\n        See `candidates` for details on this assignment.\n    :param geo_units: A list of `GeopoliticalUnit`; one of these goes into the `electoral_district_id`\n    \"\"\"\n    return draw(\n        one_of(\n            referendum_contest_descriptions(sequence_order, geo_units),\n            candidate_contest_descriptions(sequence_order, party_list, geo_units),\n        )\n    )\n\n\n@composite\ndef election_descriptions(\n    draw: _DrawType, max_num_parties: int = 3, max_num_contests: int = 3\n) -> Manifest:\n    \"\"\"\n    Generates a `Manifest` -- the top-level object describing an election.\n    :param draw: Hidden argument, used by Hypothesis.\n    :param max_num_parties: The largest number of parties that will be generated (default: 3)\n    :param max_num_contests: The largest number of contests that will be generated (default: 3)\n    \"\"\"\n    assert max_num_parties > 0, \"need at least one party\"\n    assert max_num_contests > 0, \"need at least one contest\"\n\n    geo_units = [draw(geopolitical_units())]\n    num_parties: int = draw(integers(1, max_num_parties))\n\n    # keep this small so tests run faster\n    parties: List[Party] = draw(party_lists(num_parties))\n    num_contests: int = draw(integers(1, max_num_contests))\n\n    # generate a collection candidates mapped to contest descriptions\n    candidate_contests: List[Tuple[List[Candidate], ContestDescription]] = [\n        draw(contest_descriptions(i, parties, geo_units)) for i in range(num_contests)\n    ]\n    assert len(candidate_contests) > 0\n\n    candidates_ = reduce(\n        lambda a, b: a + b,\n        [candidate_contest[0] for candidate_contest in candidate_contests],\n    )\n    contests = [candidate_contest[1] for candidate_contest in candidate_contests]\n\n    styles = [draw(ballot_styles(parties, geo_units))]\n\n    # maybe later on we'll do something more complicated with dates\n    start_date = draw(datetimes())\n    end_date = start_date\n\n    return Manifest(\n        election_scope_id=draw(emails()),\n        spec_version=SpecVersion.EG0_95,\n        type=ElectionType.general,  # good enough for now\n        start_date=start_date,\n        end_date=end_date,\n        geopolitical_units=geo_units,\n        parties=parties,\n        candidates=candidates_,\n        contests=contests,\n        ballot_styles=styles,\n        name=draw(internationalized_texts()),\n        contact_information=draw(contact_infos()),\n    )\n\n\n@composite\ndef plaintext_voted_ballots(\n    draw: _DrawType, internal_manifest: InternalManifest, count: int = 1\n) -> Union[Any, List[PlaintextBallot]]:\n    \"\"\"\n    Given\n    \"\"\"\n    if count == 1:\n        return draw(plaintext_voted_ballot(internal_manifest))\n    ballots: List[PlaintextBallot] = []\n    for _i in range(count):\n        ballots.append(draw(plaintext_voted_ballot(internal_manifest)))\n    return ballots\n\n\n@composite\ndef plaintext_voted_ballot(\n    draw: _DrawType, internal_manifest: InternalManifest\n) -> PlaintextBallot:\n    \"\"\"\n    Given an `InternalManifest` object, generates an arbitrary `PlaintextBallot` with the\n    choices made randomly.\n    :param draw: Hidden argument, used by Hypothesis.\n    :param internal_manifest: Any `InternalManifest`\n    \"\"\"\n\n    num_ballot_styles = len(internal_manifest.ballot_styles)\n    assert num_ballot_styles > 0, \"invalid election with no ballot styles\"\n\n    # pick a ballot style at random\n    ballot_style = internal_manifest.ballot_styles[\n        draw(integers(0, num_ballot_styles - 1))\n    ]\n\n    contests = internal_manifest.get_contests_for(ballot_style.object_id)\n    assert len(contests) > 0, \"invalid ballot style with no contests in it\"\n\n    voted_contests: List[PlaintextBallotContest] = []\n    for contest in contests:\n        assert contest.is_valid(), \"every contest needs to be valid\"\n        n = contest.number_elected  # we need exactly this many 1's, and the rest 0's\n        ballot_selections = deepcopy(contest.ballot_selections)\n        assert len(ballot_selections) >= n\n\n        random = Random(draw(integers()))\n        random.shuffle(ballot_selections)\n        cut_point = draw(integers(0, n))\n        yes_votes = ballot_selections[:cut_point]\n        no_votes = ballot_selections[cut_point:]\n\n        voted_selections = [\n            selection_from(description, is_placeholder=False, is_affirmative=True)\n            for description in yes_votes\n        ] + [\n            selection_from(description, is_placeholder=False, is_affirmative=False)\n            for description in no_votes\n        ]\n\n        voted_contests.append(\n            PlaintextBallotContest(contest.object_id, voted_selections)\n        )\n\n    return PlaintextBallot(str(draw(uuids())), ballot_style.object_id, voted_contests)\n\n\nCiphertextElectionsTupleType = Tuple[ElementModQ, CiphertextElectionContext]\n\n\n@composite\ndef ciphertext_elections(\n    draw: _DrawType, manifest: Manifest\n) -> CiphertextElectionsTupleType:\n    \"\"\"\n    Generates a `CiphertextElectionContext` with a single public-private key pair as the encryption context.\n\n    In a real election, the key ceremony would be used to generate a shared public key.\n\n    :param draw: Hidden argument, used by Hypothesis.\n    :param manifest: An `Manifest` object, with\n    which the `CiphertextElectionContext` will be associated\n    :return: a tuple of a `CiphertextElectionContext` and the secret key associated with it\n    \"\"\"\n    keypair: ElGamalKeyPair = draw(elgamal_keypairs())\n    secret_key = keypair.secret_key\n    public_key = keypair.public_key\n    commitment_hash = draw(elements_mod_q_no_zero())\n    ciphertext_election_with_secret: CiphertextElectionsTupleType = (\n        secret_key,\n        make_ciphertext_election_context(\n            number_of_guardians=1,\n            quorum=1,\n            elgamal_public_key=public_key,\n            commitment_hash=commitment_hash,\n            manifest_hash=manifest.crypto_hash(),\n        ),\n    )\n    return ciphertext_election_with_secret\n\n\nElectionsAndBallotsTupleType = Tuple[\n    Manifest,\n    InternalManifest,\n    List[PlaintextBallot],\n    ElementModQ,\n    CiphertextElectionContext,\n]\n\n\n@composite\ndef elections_and_ballots(\n    draw: _DrawType, num_ballots: int = 3\n) -> ElectionsAndBallotsTupleType:\n    \"\"\"\n    A convenience generator to generate all of the necessary components for simulating an election.\n    Every ballot will match the same ballot style. Hypothesis doesn't\n    let us declare a type hint on strategy return values, so you can use `ELECTIONS_AND_BALLOTS_TUPLE_TYPE`.\n\n    :param draw: Hidden argument, used by Hypothesis.\n    :param num_ballots: The number of ballots to generate (default: 3).\n    :reeturn: a tuple of: an `InternalManifest`, a list of plaintext ballots, an ElGamal secret key,\n        and a `CiphertextElectionContext`\n    \"\"\"\n    assert num_ballots >= 0, \"You're asking for a negative number of ballots?\"\n    manifest = draw(election_descriptions())\n    internal_manifest = InternalManifest(manifest)\n\n    ballots = [\n        draw(plaintext_voted_ballots(internal_manifest)) for _ in range(num_ballots)\n    ]\n\n    secret_key, context = draw(ciphertext_elections(manifest))\n\n    mock_election: ElectionsAndBallotsTupleType = (\n        manifest,\n        internal_manifest,\n        ballots,\n        secret_key,\n        context,\n    )\n    return mock_election\n"
  },
  {
    "path": "src/electionguard_tools/strategies/elgamal.py",
    "content": "from typing import Optional, TypeVar, Callable\n\nfrom hypothesis.strategies import composite, SearchStrategy\n\nfrom electionguard.elgamal import ElGamalKeyPair, elgamal_keypair_from_secret\nfrom electionguard.group import ONE_MOD_Q, TWO_MOD_Q\nfrom electionguard_tools.strategies.group import elements_mod_q_no_zero\n\n_T = TypeVar(\"_T\")\n_DrawType = Callable[[SearchStrategy[_T]], _T]\n\n\n@composite\ndef elgamal_keypairs(draw: _DrawType) -> Optional[ElGamalKeyPair]:\n    \"\"\"\n    Generates an arbitrary ElGamal secret/public keypair.\n\n    :param draw: Hidden argument, used by Hypothesis.\n    \"\"\"\n    e = draw(elements_mod_q_no_zero())\n    return elgamal_keypair_from_secret(e if e != ONE_MOD_Q else TWO_MOD_Q)\n"
  },
  {
    "path": "src/electionguard_tools/strategies/group.py",
    "content": "from typing import TypeVar, Callable\n\nfrom hypothesis.strategies import composite, integers, SearchStrategy\n\nfrom electionguard.constants import (\n    get_small_prime,\n    get_large_prime,\n)\nfrom electionguard.group import (\n    ElementModP,\n    ElementModQ,\n)\n\n_T = TypeVar(\"_T\")\n_DrawType = Callable[[SearchStrategy[_T]], _T]\n\n\n@composite\ndef elements_mod_q(draw: _DrawType) -> ElementModQ:\n    \"\"\"\n    Generates an arbitrary element from [0,Q).\n\n    :param draw: Hidden argument, used by Hypothesis.\n    \"\"\"\n    return ElementModQ(draw(integers(min_value=0, max_value=get_small_prime() - 1)))\n\n\n@composite\ndef elements_mod_q_no_zero(draw: _DrawType) -> ElementModQ:\n    \"\"\"\n    Generates an arbitrary element from [1,Q).\n\n    :param draw: Hidden argument, used by Hypothesis.\n    \"\"\"\n    return ElementModQ(draw(integers(min_value=1, max_value=get_small_prime() - 1)))\n\n\n@composite\ndef elements_mod_p(draw: _DrawType) -> ElementModP:\n    \"\"\"\n    Generates an arbitrary element from [0,P).\n\n    :param draw: Hidden argument, used by Hypothesis.\n    \"\"\"\n    return ElementModP(draw(integers(min_value=0, max_value=get_large_prime() - 1)))\n\n\n@composite\ndef elements_mod_p_no_zero(draw: _DrawType) -> ElementModP:\n    \"\"\"\n    Generates an arbitrary element from [1,P).\n\n    :param draw: Hidden argument, used by Hypothesis.\n    \"\"\"\n    return ElementModP(draw(integers(min_value=1, max_value=get_large_prime() - 1)))\n"
  },
  {
    "path": "src/electionguard_verify/__init__.py",
    "content": "import importlib.metadata\n\n# <AUTOGEN_INIT>\nfrom electionguard_verify import verify\n\nfrom electionguard_verify.verify import (\n    Verification,\n    verify_aggregation,\n    verify_ballot,\n    verify_decryption,\n)\n\n__all__ = [\n    \"Verification\",\n    \"verify\",\n    \"verify_aggregation\",\n    \"verify_ballot\",\n    \"verify_decryption\",\n]\n\n# </AUTOGEN_INIT>\n\n# single source version from pyproject.toml\ntry:\n    __version__ = importlib.metadata.version(__package__.split(\"_\", maxsplit=1)[0])\nexcept importlib.metadata.PackageNotFoundError:\n    __version__ = \"0.0.0\"\n"
  },
  {
    "path": "src/electionguard_verify/verify.py",
    "content": "from dataclasses import dataclass\nfrom typing import Dict, Optional, List\n\nfrom electionguard.ballot import CiphertextBallot, SubmittedBallot\nfrom electionguard.election import CiphertextElectionContext\nfrom electionguard.key_ceremony import ElectionPublicKey\nfrom electionguard.manifest import (\n    InternalManifest,\n    Manifest,\n)\nfrom electionguard.type import GuardianId\nfrom electionguard.tally import PlaintextTally, CiphertextTally\n\n\n@dataclass\nclass Verification:\n    \"\"\"\n    Representation of a verification result with an optional message\n    \"\"\"\n\n    verified: bool\n    \"\"\"Verification successful?\"\"\"\n    message: Optional[str]\n\n\ndef verify_ballot(\n    ballot: CiphertextBallot,\n    manifest: Manifest,\n    context: CiphertextElectionContext,\n) -> Verification:\n    \"\"\"\n    Method to verify the validity of a ballot\n    \"\"\"\n\n    if not ballot.is_valid_encryption(\n        manifest.crypto_hash(),\n        context.elgamal_public_key,\n        context.crypto_extended_base_hash,\n    ):\n        return Verification(\n            False,\n            message=f\"verify_ballot: mismatching ballot encryption {ballot.object_id}\",\n        )\n\n    return Verification(True, message=None)\n\n\ndef verify_decryption(\n    tally: PlaintextTally,\n    election_public_keys: Dict[GuardianId, ElectionPublicKey],\n    context: CiphertextElectionContext,\n) -> Verification:\n    for _, contest in tally.contests.items():\n        for selection_id, selection in contest.selections.items():\n            for share in selection.shares:\n                election_public_key = election_public_keys.get(share.guardian_id).key\n                if not share.proof.is_valid(\n                    selection.message,\n                    election_public_key,\n                    share.share,\n                    context.crypto_extended_base_hash,\n                ):\n                    return Verification(\n                        False,\n                        message=f\"verify_decryption: {selection_id} selection is not valid\",\n                    )\n\n    return Verification(True, message=None)\n\n\ndef verify_aggregation(\n    submitted_ballots: List[SubmittedBallot],\n    tally: CiphertextTally,\n    manifest: Manifest,\n    context: CiphertextElectionContext,\n) -> Verification:\n    new_tally = CiphertextTally(\"verify\", InternalManifest(manifest), context)\n\n    for ballot in submitted_ballots:\n        new_tally.append(ballot, True)\n\n    if (\n        isinstance(tally, CiphertextTally)\n        and new_tally.cast_ballot_ids == tally.cast_ballot_ids\n        and new_tally.spoiled_ballot_ids == tally.spoiled_ballot_ids\n        and new_tally.contests == tally.contests\n    ):\n        return Verification(True, message=None)\n\n    return Verification(\n        False,\n        message=\"verify_aggregation: aggregated value of ballots doesn't matches with tally\",\n    )\n"
  },
  {
    "path": "stubs/gmpy2.pyi",
    "content": "\"\"\"\nStub of gympy2 mpz to support typecheck.\n\nCreated by running `stubgen -p gmpy2`, and then modifying the output manually\nNecessary to support mypy typechecking\n\"\"\"\n\nfrom typing import Union, Any, Tuple, Text, Optional\n\nclass mpz(int):\n    def __new__(\n        self, x: Union[Text, bytes, bytearray, int], base: int = ...\n    ) -> \"mpz\": ...\n    def bit_clear(self, n: int) -> mpz: ...\n    def bit_flip(self, n: int) -> mpz: ...\n    def bit_length(self, *args: int, **kwargs: Any) -> int: ...\n    def bit_scan0(self, n: int = ...) -> int: ...\n    def bit_scan1(self, n: int = ...) -> int: ...\n    def bit_set(self, n: int) -> mpz: ...\n    def bit_test(self, n: int) -> bool: ...\n    def digits(self) -> str: ...\n    def is_divisible(self, d: int) -> bool: ...\n    def is_even(self) -> bool: ...\n    def is_odd(self) -> bool: ...\n    def is_power(self) -> bool: ...\n    def is_prime(self) -> bool: ...\n    def is_square(self) -> bool: ...\n    def num_digits(self, base: int = ...) -> int: ...\n    def __abs__(self) -> mpz: ...\n    def __add__(self, other: int) -> mpz: ...\n    def __and__(self, other: int) -> mpz: ...\n    def __bool__(self) -> bool: ...\n    def __ceil__(self) -> mpz: ...\n    def __divmod__(self, other: int) -> Tuple[int, int]: ...\n    def __eq__(self, other: object) -> bool: ...\n    def __float__(self) -> mpz: ...  # maybe not mpz?\n    def __floor__(self) -> mpz: ...\n    def __floordiv__(self, other: int) -> mpz: ...\n    def __format__(self, *args: Any, **kwargs: Any) -> str: ...\n    def __ge__(self, other: int) -> bool: ...\n    def __getitem__(self, index: int) -> mpz: ...\n    def __gt__(self, other: int) -> bool: ...\n    def __hash__(self) -> int: ...\n    def __iadd__(self, other: int) -> mpz: ...\n    def __ifloordiv__(self, other: int) -> mpz: ...\n    def __ilshift__(self, other: int) -> mpz: ...\n    def __imod__(self, other: int) -> mpz: ...\n    def __imul__(self, other: int) -> mpz: ...\n    def __index__(self) -> int: ...\n    def __int__(self) -> int: ...\n    def __invert__(self) -> mpz: ...\n    def __irshift__(self, other: int) -> mpz: ...\n    def __isub__(self, other: int) -> mpz: ...\n    def __le__(self, other: int) -> bool: ...\n    def __len__(self) -> int: ...\n    def __lshift__(self, other: int) -> mpz: ...\n    def __lt__(self, other: int) -> bool: ...\n    def __mod__(self, other: int) -> mpz: ...\n    def __mul__(self, other: int) -> mpz: ...\n    def __ne__(self, other: object) -> bool: ...\n    def __neg__(self) -> mpz: ...\n    def __or__(self, other: int) -> mpz: ...\n    def __pos__(self) -> bool: ...\n    def __pow__(self, other: int, __modulo: Optional[int] = ...) -> Any: ...  # type: ignore[override]\n    def __radd__(self, other: int) -> mpz: ...\n    def __rand__(self, other: int) -> mpz: ...\n    def __rdivmod__(self, other: int) -> Tuple[int, int]: ...\n    def __rfloordiv__(self, other: int) -> mpz: ...\n    def __rlshift__(self, other: int) -> mpz: ...\n    def __rmod__(self, other: int) -> mpz: ...\n    def __rmul__(self, other: int) -> mpz: ...\n    def __ror__(self, other: int) -> mpz: ...\n    def __rpow__(self, other: int, __modulo: Optional[int] = ...) -> Any: ...  # type: ignore[misc]\n    def __rrshift__(self, other: int) -> mpz: ...\n    def __rshift__(self, other: int) -> mpz: ...\n    def __rsub__(self, other: int) -> mpz: ...\n    def __rtruediv__(self, other: float) -> float: ...\n    def __rxor__(self, other: int) -> mpz: ...\n    def __sizeof__(self) -> int: ...\n    def __sub__(self, other: int) -> mpz: ...\n    def __truediv__(self, other: float) -> float: ...\n    def __trunc__(self) -> mpz: ...\n    def __xor__(self, other: int) -> mpz: ...\n\ndef invert(x: mpz, m: mpz) -> mpz: ...\ndef powmod(a: int, e: int, p: int) -> mpz: ...\ndef to_binary(a: mpz) -> bytes: ...\ndef from_binary(b: bytes) -> mpz: ...\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/base_test_case.py",
    "content": "import os\nfrom unittest import TestCase\nfrom unittest.mock import patch\nfrom pytest import fixture\nfrom pytest_mock import MockerFixture\nfrom electionguard.constants import PrimeOption\n\n\nclass BaseTestCase(TestCase):\n    \"\"\"Base Test Case for overriding environment variables.\"\"\"\n\n    mocker: MockerFixture\n\n    # pylint: disable=unused-private-member\n    @fixture(autouse=True)\n    def __inject_fixtures(self, mocker):\n        self.mocker = mocker\n\n    @classmethod\n    def setUpClass(cls):\n        \"\"\"Set up class.\"\"\"\n        cls.env_patcher = patch.dict(\n            os.environ, {\"PRIME_OPTION\": PrimeOption.TestOnly.value}\n        )\n        cls.env_patcher.start()\n\n        super().setUpClass()\n\n    @classmethod\n    def tearDownClass(cls):\n        \"\"\"Tear down class.\"\"\"\n        super().tearDownClass()\n\n        cls.env_patcher.stop()\n"
  },
  {
    "path": "tests/bench/__init__.py",
    "content": ""
  },
  {
    "path": "tests/bench/bench_chaum_pedersen.py",
    "content": "from dataclasses import dataclass\nfrom timeit import default_timer as timer\nfrom typing import Dict, List, Tuple\n\nfrom statistics import mean, stdev\n\nfrom electionguard.chaum_pedersen import make_disjunctive_chaum_pedersen_zero\n\nfrom electionguard.elgamal import (\n    elgamal_keypair_from_secret,\n    ElGamalKeyPair,\n    elgamal_encrypt,\n)\nfrom electionguard.group import ElementModQ, ONE_MOD_Q\nfrom electionguard.nonces import Nonces\nfrom electionguard.scheduler import Scheduler\nfrom electionguard.utils import get_optional\n\n\n@dataclass\nclass BenchInput:\n    \"\"\"Input for benchmark\"\"\"\n\n    keypair: ElGamalKeyPair\n    r: ElementModQ\n    s: ElementModQ\n\n\ndef chaum_pedersen_bench(bi: BenchInput) -> Tuple[float, float]:\n    \"\"\"\n    Given an input (instance of the BenchInput tuple), constructs and validates\n    a disjunctive Chaum-Pedersen proof, returning the time (in seconds) to do each operation.\n    \"\"\"\n    ciphertext = get_optional(elgamal_encrypt(0, bi.r, bi.keypair.public_key))\n    start1 = timer()\n    proof = make_disjunctive_chaum_pedersen_zero(\n        ciphertext, bi.r, bi.keypair.public_key, ONE_MOD_Q, bi.s\n    )\n    end1 = timer()\n    valid = proof.is_valid(ciphertext, bi.keypair.public_key, ONE_MOD_Q)\n    end2 = timer()\n    if not valid:\n        raise Exception(\"Wasn't expecting an invalid proof during a benchmark!\")\n    return end1 - start1, end2 - end1\n\n\ndef identity(x: int) -> int:\n    \"\"\"Placeholder function used just to warm up the parallel mapper prior to benchmarking.\"\"\"\n    return x\n\n\nif __name__ == \"__main__\":\n    problem_sizes = (100, 500, 1000, 5000)\n    rands = Nonces(ElementModQ(31337))\n    speedup: Dict[int, float] = {}\n\n    # warm up the pool to help get consistent measurements\n    with Scheduler() as scheduler:\n        results: List[int] = scheduler.schedule(\n            identity, [list([x]) for x in range(1, 30000)]\n        )\n        assert results == list(range(1, 30000))\n\n        bench_start = timer()\n\n        for size in problem_sizes:\n            print(\"Benchmarking on problem size: \", size)\n            seeds = rands[0:size]\n            inputs = [\n                BenchInput(\n                    get_optional(elgamal_keypair_from_secret(a)),\n                    rands[size],\n                    rands[size + 1],\n                )\n                for a in seeds\n            ]\n            start_all_scalar = timer()\n            timing_data = [chaum_pedersen_bench(i) for i in inputs]\n            end_all_scalar = timer()\n\n            print(f\"  Creating Chaum-Pedersen proofs ({size} iterations)\")\n            avg_proof_scalar = mean([t[0] for t in timing_data])\n            std_proof_scalar = stdev([t[0] for t in timing_data])\n            print(f\"    Avg    = {avg_proof_scalar:.6f} sec\")\n            print(f\"    Stddev = {std_proof_scalar:.6f} sec\")\n\n            print(f\"  Validating Chaum-Pedersen proofs ({size} iterations)\")\n            avg_verify_scalar = mean([t[1] for t in timing_data])\n            std_verify_scalar = stdev([t[1] for t in timing_data])\n            print(f\"    Avg    = {avg_verify_scalar:.6f} sec\")\n            print(f\"    Stddev = {std_verify_scalar:.6f} sec\")\n\n            # Run in parallel\n            start_all_parallel = timer()\n            timing_data_parallel: List[Tuple[float, float]] = scheduler.schedule(\n                chaum_pedersen_bench, [list([input]) for input in inputs]\n            )\n            end_all_parallel = timer()\n\n            speedup[size] = (end_all_scalar - start_all_scalar) / (\n                end_all_parallel - start_all_parallel\n            )\n            print(f\"  Parallel speedup: {speedup[size]:.3f}x\")\n\n        print()\n        print(\"PARALLELISM SPEEDUPS\")\n        print(\"Size / Speedup\")\n        for size in problem_sizes:\n            print(f\"{size:4d} / {speedup[size]:.3f}x\")\n\n        bench_end = timer()\n        print()\n        print(f\"Total benchmark runtime: {bench_end - bench_start} sec\")\n\n##############################################################################################################\n# Performance conclusions (Dan Wallach, 21 March 2020):\n\n# On my MacPro (Xeon 6-core with hyperthreading, 3.5GHz, Python 3.8), this benchmark runs in roughly 5 minutes\n# and reports that C-P proofs take 10-11ms to compute and 23-24ms to verify. The parallelism numbers are:\n\n# Size / Speedup\n#  100 / 5.749x\n#  500 / 5.765x\n# 1000 / 5.507x\n# 5000 / 5.548x\n"
  },
  {
    "path": "tests/integration/__init__.py",
    "content": ""
  },
  {
    "path": "tests/integration/test_create_schema.py",
    "content": "import json\nfrom unittest import TestCase\nfrom shutil import rmtree\n\nfrom electionguard.constants import ElectionConstants\nfrom electionguard.election import CiphertextElectionContext\nfrom electionguard.guardian import GuardianRecord\nfrom electionguard.manifest import Manifest\nfrom electionguard.ballot import (\n    CiphertextBallot,\n    PlaintextBallot,\n    SubmittedBallot,\n)\nfrom electionguard.ballot_compact import CompactPlaintextBallot, CompactSubmittedBallot\nfrom electionguard.election_polynomial import LagrangeCoefficientsRecord\nfrom electionguard.encrypt import EncryptionDevice\nfrom electionguard.tally import (\n    PublishedCiphertextTally,\n    PlaintextTally,\n)\nfrom electionguard.serialize import construct_path, get_schema, to_file\n\n\nclass TestCreateSchema(TestCase):\n    \"\"\"Test creating schema.\"\"\"\n\n    schema_dir = \"schemas\"\n    remove_schema = False\n\n    # TODO Fix Pydantic errors with json schema\n    resolve_pydantic_errors = False\n\n    def test_create_schema(self) -> None:\n\n        to_file(\n            json.loads((get_schema(CiphertextElectionContext))),\n            construct_path(\"context_schema\"),\n            self.schema_dir,\n        )\n\n        to_file(\n            json.loads(get_schema(ElectionConstants)),\n            construct_path(\"constants_schema\"),\n            self.schema_dir,\n        )\n\n        to_file(\n            json.loads(get_schema(EncryptionDevice)),\n            construct_path(\"device_schema\"),\n            self.schema_dir,\n        )\n\n        to_file(\n            json.loads(get_schema(LagrangeCoefficientsRecord)),\n            construct_path(\"coefficients_schema\"),\n            self.schema_dir,\n        )\n\n        if self.resolve_pydantic_errors:\n            to_file(\n                json.loads(get_schema(Manifest)),\n                construct_path(\"manifest_schema\"),\n                self.schema_dir,\n            )\n            to_file(\n                json.loads(get_schema(GuardianRecord)),\n                construct_path(\"guardian_schema\"),\n                self.schema_dir,\n            )\n            to_file(\n                json.loads(get_schema(PlaintextBallot)),\n                construct_path(\"plaintext_ballot_schema\"),\n                self.schema_dir,\n            )\n            to_file(\n                json.loads(get_schema(CiphertextBallot)),\n                construct_path(\"ciphertext_ballot_schema\"),\n                self.schema_dir,\n            )\n            to_file(\n                json.loads(get_schema(SubmittedBallot)),\n                construct_path(\"submitted_ballot_schema\"),\n                self.schema_dir,\n            )\n            to_file(\n                json.loads(get_schema(CompactPlaintextBallot)),\n                construct_path(\"compact_plaintext_ballot_schema\"),\n                self.schema_dir,\n            )\n            to_file(\n                json.loads(get_schema(CompactSubmittedBallot)),\n                construct_path(\"compact_submitted_ballot_schema\"),\n                self.schema_dir,\n            )\n            to_file(\n                json.loads(get_schema(PlaintextTally)),\n                construct_path(\"plaintext_tally_schema\"),\n                self.schema_dir,\n            )\n            to_file(\n                json.loads(get_schema(PublishedCiphertextTally)),\n                construct_path(\"ciphertext_tally_schema\"),\n                self.schema_dir,\n            )\n\n        if self.remove_schema:\n            rmtree(self.schema_dir)\n"
  },
  {
    "path": "tests/integration/test_end_to_end_election.py",
    "content": "#!/usr/bin/env python\n\nfrom typing import Callable, Dict, List, Union\nfrom os import path, remove\nfrom shutil import rmtree, make_archive\nfrom random import randint\nfrom dataclasses import asdict\n\nfrom tests.base_test_case import BaseTestCase\n\nfrom electionguard.type import BallotId\nfrom electionguard.utils import get_optional\n\n# Step 0 - Configure Election\nfrom electionguard.constants import ElectionConstants, get_constants\nfrom electionguard.election import CiphertextElectionContext\nfrom electionguard.manifest import Manifest, InternalManifest\n\n# Step 1 - Key Ceremony\nfrom electionguard.guardian import Guardian, GuardianRecord, PrivateGuardianRecord\nfrom electionguard.key_ceremony_mediator import KeyCeremonyMediator\n\n# Step 2 - Encrypt Votes\nfrom electionguard.ballot import (\n    BallotBoxState,\n    CiphertextBallot,\n    PlaintextBallot,\n    SubmittedBallot,\n)\nfrom electionguard.encrypt import EncryptionDevice\nfrom electionguard.encrypt import EncryptionMediator\n\n# Step 3 - Cast and Spoil\nfrom electionguard.data_store import DataStore\nfrom electionguard.ballot_box import BallotBox, get_ballots\n\n# Step 4 - Decrypt Tally\nfrom electionguard.tally import (\n    PublishedCiphertextTally,\n    tally_ballots,\n    CiphertextTally,\n    PlaintextTally,\n)\nfrom electionguard.decryption_mediator import DecryptionMediator\nfrom electionguard.election_polynomial import LagrangeCoefficientsRecord\n\n# Step 5 - Publish and Verify\nfrom electionguard.serialize import from_file, construct_path\nfrom electionguard_tools.helpers.export import (\n    COEFFICIENTS_FILE_NAME,\n    DEVICES_DIR,\n    GUARDIANS_DIR,\n    PRIVATE_DATA_DIR,\n    SPOILED_BALLOTS_DIR,\n    SUBMITTED_BALLOTS_DIR,\n    ELECTION_RECORD_DIR,\n    SUBMITTED_BALLOT_PREFIX,\n    SPOILED_BALLOT_PREFIX,\n    CONSTANTS_FILE_NAME,\n    CONTEXT_FILE_NAME,\n    DEVICE_PREFIX,\n    ENCRYPTED_TALLY_FILE_NAME,\n    GUARDIAN_PREFIX,\n    MANIFEST_FILE_NAME,\n    TALLY_FILE_NAME,\n    export_private_data,\n    export_record,\n)\n\nfrom electionguard_tools.factories.ballot_factory import BallotFactory\nfrom electionguard_tools.factories.election_factory import (\n    ElectionFactory,\n    NUMBER_OF_GUARDIANS,\n)\nfrom electionguard_tools.helpers.election_builder import ElectionBuilder\n\n\ndevices_directory = path.join(ELECTION_RECORD_DIR, DEVICES_DIR)\nguardians_directory = path.join(ELECTION_RECORD_DIR, GUARDIANS_DIR)\nsubmitted_ballots_directory = path.join(ELECTION_RECORD_DIR, SUBMITTED_BALLOTS_DIR)\nspoiled_ballots_directory = path.join(ELECTION_RECORD_DIR, SPOILED_BALLOTS_DIR)\n\n\n# pylint: disable=too-many-instance-attributes\nclass TestEndToEndElection(BaseTestCase):\n    \"\"\"\n    Test a complete simple example of executing an End-to-End encrypted election.\n    In a real world scenario all of these steps would not be completed on the same machine.\n    \"\"\"\n\n    NUMBER_OF_GUARDIANS = 5\n    QUORUM = 3\n\n    REMOVE_RAW_OUTPUT = True\n    REMOVE_ZIP_OUTPUT = True\n\n    # Step 0 - Configure Election\n    manifest: Manifest\n    election_builder: ElectionBuilder\n    internal_manifest: InternalManifest\n    context: CiphertextElectionContext\n    constants: ElectionConstants\n\n    # Step 1 - Key Ceremony\n    mediator: KeyCeremonyMediator\n    guardians: List[Guardian] = []\n\n    # Step 2 - Encrypt Votes\n    device: EncryptionDevice\n    encrypter: EncryptionMediator\n    plaintext_ballots: List[PlaintextBallot]\n    ciphertext_ballots: List[CiphertextBallot] = []\n\n    # Step 3 - Cast and Spoil\n    ballot_store: DataStore[BallotId, SubmittedBallot]\n    ballot_box: BallotBox\n    submitted_ballots: Dict[BallotId, SubmittedBallot]\n\n    # Step 4 - Decrypt Tally\n    ciphertext_tally: CiphertextTally\n    plaintext_tally: PlaintextTally\n    plaintext_spoiled_ballots: Dict[str, PlaintextTally]\n    decryption_mediator: DecryptionMediator\n    lagrange_coefficients: LagrangeCoefficientsRecord\n\n    # Step 5 - Publish\n    guardian_records: List[GuardianRecord] = []\n    private_guardian_records: List[PrivateGuardianRecord] = []\n\n    def test_end_to_end_election(self) -> None:\n        \"\"\"\n        Execute the simplified end-to-end test demonstrating each component of the system.\n        \"\"\"\n        self.step_0_configure_election()\n        self.step_1_key_ceremony()\n        self.step_2_encrypt_votes()\n        self.step_3_cast_and_spoil()\n        self.step_4_decrypt_tally()\n        self.step_5_publish()\n\n    def step_0_configure_election(self) -> None:\n        \"\"\"\n        To conduct an election, load an `Manifest` file.\n        \"\"\"\n\n        # Load a pre-configured Election Description\n        # TODO: replace with complex election\n        self.manifest = ElectionFactory().get_simple_manifest_from_file()\n        print(\n            f\"\"\"\n            {'-'*40}\\n\n            # Election Summary:\n            # Scope: {self.manifest.election_scope_id}\n            # Geopolitical Units: {len(self.manifest.geopolitical_units)}\n            # Parties: {len(self.manifest.parties)}\n            # Candidates: {len(self.manifest.candidates)}\n            # Contests: {len(self.manifest.contests)}\n            # Ballot Styles: {len(self.manifest.ballot_styles)}\\n\n            {'-'*40}\\n\n            \"\"\"\n        )\n        self._assert_message(\n            Manifest.is_valid.__qualname__,\n            \"Verify that the input election meta-data is well-formed\",\n            self.manifest.is_valid(),\n        )\n\n        # Create an Election Builder\n        self.election_builder = ElectionBuilder(\n            self.NUMBER_OF_GUARDIANS, self.QUORUM, self.manifest\n        )\n        self._assert_message(\n            ElectionBuilder.__qualname__,\n            f\"Created with number_of_guardians: {self.NUMBER_OF_GUARDIANS} quorum: {self.QUORUM}\",\n        )\n\n        # Move on to the Key Ceremony\n\n    def step_1_key_ceremony(self) -> None:\n        \"\"\"\n        Using the NUMBER_OF_GUARDIANS, generate public-private keypairs and share\n        representations of those keys with QUORUM of other Guardians.  Then, combine\n        the public election keys to make a joint election key that is used to encrypt ballots.\n        \"\"\"\n\n        # Setup Guardians\n        for i in range(self.NUMBER_OF_GUARDIANS):\n            self.guardians.append(\n                Guardian.from_nonce(\n                    str(i + 1),\n                    i + 1,\n                    self.NUMBER_OF_GUARDIANS,\n                    self.QUORUM,\n                )\n            )\n\n        # Setup Mediator\n        self.mediator = KeyCeremonyMediator(\n            \"mediator_1\", self.guardians[0].ceremony_details\n        )\n\n        # ROUND 1: Public Key Sharing\n        # Announce\n        for guardian in self.guardians:\n            self.mediator.announce(guardian.share_key())\n\n        # Share Keys\n        for guardian in self.guardians:\n            announced_keys = get_optional(self.mediator.share_announced())\n            for key in announced_keys:\n                if guardian.id is not key.owner_id:\n                    guardian.save_guardian_key(key)\n\n        self._assert_message(\n            KeyCeremonyMediator.all_guardians_announced.__qualname__,\n            \"Confirms all guardians have shared their public keys\",\n            self.mediator.all_guardians_announced(),\n        )\n\n        # ROUND 2: Election Partial Key Backup Sharing\n        # Share Backups\n        for sending_guardian in self.guardians:\n            sending_guardian.generate_election_partial_key_backups()\n            backups = []\n            for designated_guardian in self.guardians:\n                if designated_guardian.id != sending_guardian.id:\n                    backups.append(\n                        get_optional(\n                            sending_guardian.share_election_partial_key_backup(\n                                designated_guardian.id\n                            )\n                        )\n                    )\n            self.mediator.receive_backups(backups)\n            self._assert_message(\n                KeyCeremonyMediator.receive_backups.__qualname__,\n                \"Receive election partial key backups from key owning guardian\",\n                len(backups) == NUMBER_OF_GUARDIANS - 1,\n            )\n\n        self._assert_message(\n            KeyCeremonyMediator.all_backups_available.__qualname__,\n            \"Confirm all guardians have shared their election partial key backups\",\n            self.mediator.all_backups_available(),\n        )\n\n        # Receive Backups\n        for designated_guardian in self.guardians:\n            backups = get_optional(self.mediator.share_backups(designated_guardian.id))\n            self._assert_message(\n                KeyCeremonyMediator.share_backups.__qualname__,\n                \"Share election partial key backups for the designated guardian\",\n                len(backups) == NUMBER_OF_GUARDIANS - 1,\n            )\n            for backup in backups:\n                designated_guardian.save_election_partial_key_backup(backup)\n\n        # ROUND 3: Verification of Backups\n        # Verify Backups\n        for designated_guardian in self.guardians:\n            verifications = []\n            for backup_owner in self.guardians:\n                if designated_guardian.id is not backup_owner.id:\n                    verification = (\n                        designated_guardian.verify_election_partial_key_backup(\n                            backup_owner.id\n                        )\n                    )\n                    verifications.append(get_optional(verification))\n            self.mediator.receive_backup_verifications(verifications)\n\n        self._assert_message(\n            KeyCeremonyMediator.all_backups_verified.__qualname__,\n            \"Confirms all guardians have verified the backups of all other guardians\",\n            self.mediator.all_backups_verified(),\n        )\n\n        # FINAL: Publish Joint Key\n        joint_key = self.mediator.publish_joint_key()\n        self._assert_message(\n            KeyCeremonyMediator.publish_joint_key.__qualname__,\n            \"Publishes the Joint Election Key\",\n            joint_key is not None,\n        )\n\n        # Build the Election\n        self.election_builder.set_public_key(get_optional(joint_key).joint_public_key)\n        self.election_builder.set_commitment_hash(\n            get_optional(joint_key).commitment_hash\n        )\n        self.internal_manifest, self.context = get_optional(\n            self.election_builder.build()\n        )\n        self.constants = get_constants()\n\n        # Move on to encrypting ballots\n\n    def step_2_encrypt_votes(self) -> None:\n        \"\"\"\n        Using the `CiphertextElectionContext` encrypt ballots for the election.\n        \"\"\"\n\n        # Configure the Encryption Device\n        self.device = ElectionFactory.get_encryption_device()\n        self.encrypter = EncryptionMediator(\n            self.internal_manifest, self.context, self.device\n        )\n        self._assert_message(\n            EncryptionDevice.__qualname__,\n            f\"Ready to encrypt at location: {self.device.location}\",\n        )\n\n        # Load some Ballots\n        self.plaintext_ballots = BallotFactory().get_simple_ballots_from_file()\n        self._assert_message(\n            PlaintextBallot.__qualname__,\n            f\"Loaded ballots: {len(self.plaintext_ballots)}\",\n            len(self.plaintext_ballots) > 0,\n        )\n\n        # Encrypt the Ballots\n        for plaintext_ballot in self.plaintext_ballots:\n            encrypted_ballot = self.encrypter.encrypt(plaintext_ballot)\n            self._assert_message(\n                EncryptionMediator.encrypt.__qualname__,\n                f\"Ballot Id: {plaintext_ballot.object_id}\",\n                encrypted_ballot is not None,\n            )\n            self.ciphertext_ballots.append(get_optional(encrypted_ballot))\n\n        # Next, we cast or spoil the ballots\n\n    def step_3_cast_and_spoil(self) -> None:\n        \"\"\"\n        Accept each ballot by marking it as either cast or spoiled.\n        This example demonstrates one way to accept ballots using the `BallotBox` class.\n        \"\"\"\n\n        # Configure the Ballot Box\n        self.ballot_store = DataStore()\n        self.ballot_box = BallotBox(\n            self.internal_manifest, self.context, self.ballot_store\n        )\n\n        # Randomly cast or spoil the ballots\n        for ballot in self.ciphertext_ballots:\n            if randint(0, 1):\n                submitted_ballot = self.ballot_box.cast(ballot)\n            else:\n                submitted_ballot = self.ballot_box.spoil(ballot)\n\n            self._assert_message(\n                BallotBox.__qualname__,\n                f\"Submitted Ballot Id: {ballot.object_id} state: {get_optional(submitted_ballot).state}\",\n                submitted_ballot is not None,\n            )\n\n    def step_4_decrypt_tally(self) -> None:\n        \"\"\"\n        Homomorphically combine the selections made on all of the cast ballots\n        and use the Available Guardians to decrypt the combined tally.\n        In this way, no individual voter's cast ballot is ever decrypted drectly.\n        \"\"\"\n\n        # Generate a Homomorphically Accumulated Tally of the ballots\n        self.ciphertext_tally = get_optional(\n            tally_ballots(self.ballot_store, self.internal_manifest, self.context)\n        )\n        self.submitted_ballots = get_ballots(self.ballot_store, BallotBoxState.SPOILED)\n        self._assert_message(\n            tally_ballots.__qualname__,\n            f\"\"\"\n            - cast: {self.ciphertext_tally.cast()}\n            - spoiled: {self.ciphertext_tally.spoiled()}\n            Total: {len(self.ciphertext_tally)}\n            \"\"\",\n            self.ciphertext_tally is not None,\n        )\n\n        # Configure the Decryption\n        submitted_ballots_list = list(self.submitted_ballots.values())\n        self.decryption_mediator = DecryptionMediator(\n            \"decryption-mediator\",\n            self.context,\n        )\n\n        # Announce each guardian as present\n        count = 0\n        for guardian in self.guardians:\n            guardian_key = guardian.share_key()\n            tally_share = guardian.compute_tally_share(\n                self.ciphertext_tally, self.context\n            )\n            ballot_shares = guardian.compute_ballot_shares(\n                submitted_ballots_list, self.context\n            )\n            self.decryption_mediator.announce(\n                guardian_key, get_optional(tally_share), ballot_shares\n            )\n            count += 1\n            self._assert_message(\n                DecryptionMediator.announce.__qualname__,\n                f\"Guardian Present: {guardian.id}\",\n                len(self.decryption_mediator.get_available_guardians()) == count,\n            )\n\n        self.lagrange_coefficients = LagrangeCoefficientsRecord(\n            self.decryption_mediator.get_lagrange_coefficients()\n        )\n\n        # Get the plaintext Tally\n        self.plaintext_tally = get_optional(\n            self.decryption_mediator.get_plaintext_tally(\n                self.ciphertext_tally, self.manifest\n            )\n        )\n        self._assert_message(\n            DecryptionMediator.get_plaintext_tally.__qualname__,\n            \"Tally Decrypted\",\n            self.plaintext_tally is not None,\n        )\n\n        # Get the plaintext Spoiled Ballots\n        self.plaintext_spoiled_ballots = get_optional(\n            self.decryption_mediator.get_plaintext_ballots(\n                submitted_ballots_list, self.manifest\n            )\n        )\n        self._assert_message(\n            DecryptionMediator.get_plaintext_ballots.__qualname__,\n            \"Spoiled Ballots Decrypted\",\n            self.plaintext_tally is not None,\n        )\n\n        # Now, compare the results\n        self.compare_results()\n\n    def compare_results(self) -> None:\n        \"\"\"\n        Compare the results to ensure the decryption was done correctly.\n        \"\"\"\n        print(\n            f\"\"\"\n            {'-'*40}\n            # Election Results:\n\n            \"\"\"\n        )\n\n        # Create a representation of each contest's tally\n        selection_ids = [\n            selection.object_id\n            for contest in self.manifest.contests\n            for selection in contest.ballot_selections\n        ]\n        expected_plaintext_tally: Dict[str, int] = {key: 0 for key in selection_ids}\n\n        # Tally the expected values from the loaded ballots\n        for ballot in self.plaintext_ballots:\n            if (\n                get_optional(self.ballot_store.get(ballot.object_id)).state\n                == BallotBoxState.CAST\n            ):\n                for contest in ballot.contests:\n                    for selection in contest.ballot_selections:\n                        expected_plaintext_tally[selection.object_id] += selection.vote\n\n        # Compare the expected tally to the decrypted tally\n        for tally_contest in self.plaintext_tally.contests.values():\n            print(f\" Contest: {tally_contest.object_id}\")\n            for tally_selection in tally_contest.selections.values():\n                expected = expected_plaintext_tally[tally_selection.object_id]\n                self._assert_message(\n                    f\"   - Selection: {tally_selection.object_id}\",\n                    f\"expected: {expected}, actual: {tally_selection.tally}\",\n                    expected == tally_selection.tally,\n                )\n        print(f\"\\n{'-'*40}\\n\")\n\n        # Compare the expected values for each spoiled ballot\n        for ballot in self.plaintext_ballots:\n            if (\n                get_optional(self.ballot_store.get(ballot.object_id)).state\n                == BallotBoxState.SPOILED\n            ):\n                print(f\"\\nSpoiled Ballot: {ballot.object_id}\")\n                for contest in ballot.contests:\n                    print(f\"\\n Contest: {contest.object_id}\")\n                    for selection in contest.ballot_selections:\n                        expected = selection.vote\n                        decrypted_selection = (\n                            self.plaintext_spoiled_ballots[ballot.object_id]\n                            .contests[contest.object_id]\n                            .selections[selection.object_id]\n                        )\n                        self._assert_message(\n                            f\"   - Selection: {selection.object_id}\",\n                            f\"expected: {expected}, actual: {decrypted_selection.tally}\",\n                            expected == decrypted_selection.tally,\n                        )\n\n    def step_5_publish(self) -> None:\n        \"\"\"Publish results/artifacts of the election.\"\"\"\n\n        self.guardian_records = [guardian.publish() for guardian in self.guardians]\n        self.private_guardian_records = [\n            guardian.export_private_data() for guardian in self.guardians\n        ]\n\n        export_record(\n            self.manifest,\n            self.context,\n            self.constants,\n            [self.device],\n            self.ballot_store.all(),\n            self.plaintext_spoiled_ballots.values(),\n            self.ciphertext_tally.publish(),\n            self.plaintext_tally,\n            self.guardian_records,\n            self.lagrange_coefficients,\n        )\n        self._assert_message(\n            \"Publish\",\n            f\"Election Record published to: {ELECTION_RECORD_DIR}\",\n            path.exists(ELECTION_RECORD_DIR),\n        )\n\n        export_private_data(\n            self.plaintext_ballots,\n            self.ciphertext_ballots,\n            self.private_guardian_records,\n        )\n        self._assert_message(\n            \"Export private data\",\n            f\"Private data exported to: {PRIVATE_DATA_DIR}\",\n            path.exists(PRIVATE_DATA_DIR),\n        )\n\n        ZIP_SUFFIX = \"zip\"\n        make_archive(ELECTION_RECORD_DIR, ZIP_SUFFIX, ELECTION_RECORD_DIR)\n        make_archive(PRIVATE_DATA_DIR, ZIP_SUFFIX, PRIVATE_DATA_DIR)\n\n        self.deserialize_data()\n\n        if self.REMOVE_RAW_OUTPUT:\n            rmtree(ELECTION_RECORD_DIR)\n            rmtree(PRIVATE_DATA_DIR)\n\n        if self.REMOVE_ZIP_OUTPUT:\n            remove(f\"{ELECTION_RECORD_DIR}.{ZIP_SUFFIX}\")\n            remove(f\"{PRIVATE_DATA_DIR}.{ZIP_SUFFIX}\")\n\n    def deserialize_data(self) -> None:\n        \"\"\"Ensure published data can be deserialized.\"\"\"\n\n        # Deserialize\n        manifest_from_file = from_file(\n            Manifest,\n            construct_path(MANIFEST_FILE_NAME, ELECTION_RECORD_DIR),\n        )\n        self.assertEqualAsDicts(self.manifest, manifest_from_file)\n\n        context_from_file = from_file(\n            CiphertextElectionContext,\n            construct_path(CONTEXT_FILE_NAME, ELECTION_RECORD_DIR),\n        )\n        self.assertEqualAsDicts(self.context, context_from_file)\n\n        constants_from_file = from_file(\n            ElectionConstants, construct_path(CONSTANTS_FILE_NAME, ELECTION_RECORD_DIR)\n        )\n        self.assertEqualAsDicts(self.constants, constants_from_file)\n\n        coefficients_from_file = from_file(\n            LagrangeCoefficientsRecord,\n            construct_path(COEFFICIENTS_FILE_NAME, ELECTION_RECORD_DIR),\n        )\n        self.assertEqualAsDicts(self.lagrange_coefficients, coefficients_from_file)\n\n        device_from_file = from_file(\n            EncryptionDevice,\n            construct_path(\n                DEVICE_PREFIX + str(self.device.device_id), devices_directory\n            ),\n        )\n        self.assertEqualAsDicts(self.device, device_from_file)\n\n        for ballot in self.ballot_store.all():\n            ballot_from_file = from_file(\n                SubmittedBallot,\n                construct_path(\n                    SUBMITTED_BALLOT_PREFIX + ballot.object_id,\n                    submitted_ballots_directory,\n                ),\n            )\n            self.assertTrue(\n                ballot_from_file.is_valid_encryption(\n                    self.internal_manifest.manifest_hash,\n                    self.context.elgamal_public_key,\n                    self.context.crypto_extended_base_hash,\n                )\n            )\n            self.assertEqualAsDicts(ballot, ballot_from_file)\n\n        for spoiled_ballot in self.plaintext_spoiled_ballots.values():\n            spoiled_ballot_from_file = from_file(\n                PlaintextTally,\n                construct_path(\n                    SPOILED_BALLOT_PREFIX + spoiled_ballot.object_id,\n                    spoiled_ballots_directory,\n                ),\n            )\n            self.assertEqualAsDicts(spoiled_ballot, spoiled_ballot_from_file)\n\n        published_ciphertext_tally_from_file = from_file(\n            PublishedCiphertextTally,\n            construct_path(ENCRYPTED_TALLY_FILE_NAME, ELECTION_RECORD_DIR),\n        )\n        self.assertEqualAsDicts(\n            self.ciphertext_tally.publish(),\n            published_ciphertext_tally_from_file,\n        )\n\n        plainttext_tally_from_file = from_file(\n            PlaintextTally, construct_path(TALLY_FILE_NAME, ELECTION_RECORD_DIR)\n        )\n        self.assertEqualAsDicts(self.plaintext_tally, plainttext_tally_from_file)\n\n        for guardian_record in self.guardian_records:\n            guardian_record_from_file = from_file(\n                GuardianRecord,\n                construct_path(\n                    GUARDIAN_PREFIX + guardian_record.guardian_id, guardians_directory\n                ),\n            )\n            self.assertEqualAsDicts(guardian_record, guardian_record_from_file)\n\n    def _assert_message(\n        self, name: str, message: str, condition: Union[Callable, bool] = True\n    ) -> None:\n        if callable(condition):\n            result = condition()\n        else:\n            result = condition\n\n        print(f\"{name}: {message}: {result}\")\n        self.assertTrue(result)\n\n    def assertEqualAsDicts(self, first: object, second: object) -> None:\n        \"\"\"\n        Specialty assertEqual to compare dataclasses as dictionaries.\n\n        This is relevant specifically to using pydantic dataclasses to import.\n        Pydantic reconstructs dataclasses with name uniqueness to add their validation.\n        This creates a naming issue where the default equality check fails.\n        \"\"\"\n        self.assertEqual(asdict(first), asdict(second))\n\n\nif __name__ == \"__main__\":\n    print(\"Welcome to the ElectionGuard end-to-end test\")\n    TestEndToEndElection().test_end_to_end_election()\n"
  },
  {
    "path": "tests/integration/test_functional_key_ceremony.py",
    "content": "from typing import Dict, List\n\nfrom tests.base_test_case import BaseTestCase\n\nfrom electionguard.key_ceremony import (\n    ElectionKeyPair,\n    ElectionPartialKeyBackup,\n    ElectionPartialKeyVerification,\n    ElectionPublicKey,\n    combine_election_public_keys,\n    generate_election_key_pair,\n    generate_election_partial_key_backup,\n    generate_election_partial_key_challenge,\n    verify_election_partial_key_backup,\n    verify_election_partial_key_challenge,\n)\nfrom electionguard.type import GuardianId\n\n\nclass TestKeyCeremony(BaseTestCase):\n    \"\"\"\n    Test the key ceremony entirely from a functional sense\n    This demonstrates that no stateful models are required and\n    allows users to see the full flow utilizing only the core methods.\n    \"\"\"\n\n    # Basic Types\n    SENDER_ID = GuardianId\n    RECEIVER_ID = GuardianId\n\n    # Key Ceremony Inputs\n    NUMBER_OF_GUARDIANS = 5\n    QUORUM = 3\n\n    # ROUND 1\n    election_key_pairs: Dict[str, ElectionKeyPair] = {}\n    guardian_keys: Dict[str, ElectionPublicKey] = {}\n\n    # ROUND 2\n    sent_backups: Dict[SENDER_ID, List[ElectionPartialKeyBackup]] = {}\n    received_backups: Dict[RECEIVER_ID, List[ElectionPartialKeyBackup]] = {}\n\n    # ROUND 3\n    sent_verifications: Dict[SENDER_ID, List[ElectionPartialKeyVerification]] = {}\n    received_verifications: Dict[RECEIVER_ID, List[ElectionPartialKeyVerification]] = {}\n\n    def test_key_ceremony(self) -> None:\n        # Determine guardian id and sequence order\n        guardian_sequence_orders = [*range(1, self.NUMBER_OF_GUARDIANS + 1)]\n        guardian_ids = [f\"guardian_{i}\" for i in guardian_sequence_orders]\n\n        # ROUND 1 - SHARE KEYS\n        # Each guardian generates their own keys\n        for guardian_id, sequence_order in zip(guardian_ids, guardian_sequence_orders):\n            self._guardian_generates_keys(guardian_id, sequence_order)\n        self.assertEqual(len(self.election_key_pairs), self.NUMBER_OF_GUARDIANS)\n\n        # Each guardian shares their keys\n        for guardian_id in guardian_ids:\n            self._guardian_share_keys(guardian_id)\n        self.assertEqual(len(self.guardian_keys), self.NUMBER_OF_GUARDIANS)\n\n        # ROUND 2 - SHARE BACKUPS\n        # Each guardian generates and shares their backups\n        for guardian_id in guardian_ids:\n            self._guardian_generates_backups(guardian_id)\n            self._guardian_shares_backups(guardian_id)\n\n        # ROUND 3 - VERIFY BACKUPS\n        # Each guardian verifies the backups they received\n        # then shares each of the verifications to the owner of the key\n        for guardian_id in guardian_ids:\n            self._guardian_verifies_backups(guardian_id)\n            self._guardian_shares_verifications(guardian_id)\n\n        # ROUND 4 - CHALLENGES (IF NECESSARY)\n        # If a verification fails, the key owner can challenge\n        # and request another guardian verify the backup was indeed valid\n        # In this example, we make a fake failure.\n        self._guardian_challenges(guardian_ids)\n\n        # CREATE JOINT KEY\n        # If all backups are verified, publish joint key\n        self._publish_joint_key()\n\n    def _guardian_generates_keys(\n        self, guardian_id: GuardianId, sequence_order: int\n    ) -> None:\n        \"\"\"Guardian generates their keys\"\"\"\n\n        # Create Election Key Pair\n        election_key_pair = generate_election_key_pair(\n            guardian_id, sequence_order, self.QUORUM\n        )\n        self.assertIsNotNone(election_key_pair)\n        self.election_key_pairs[guardian_id] = election_key_pair\n\n    def _guardian_share_keys(self, guardian_id: GuardianId) -> None:\n        \"\"\"Guardian shares public keys\"\"\"\n        election_key_pair = self.election_key_pairs[guardian_id]\n        key = election_key_pair.share()\n        self.assertIsNotNone(key)\n        self.guardian_keys[guardian_id] = key\n\n    def _guardian_generates_backups(self, sender_id: str) -> None:\n        \"\"\"\n        Guardian generates a partial key backup of\n        all other guardian keys\n        \"\"\"\n\n        backups = []\n        for recipient_guardian in self.guardian_keys.values():\n            # Guardian skips themselves\n            if recipient_guardian.owner_id is sender_id:\n                continue\n\n            senders_polynomial = self.election_key_pairs[sender_id].polynomial\n            backup = generate_election_partial_key_backup(\n                sender_id,\n                senders_polynomial,\n                recipient_guardian,\n            )\n            backups.append(backup)\n        self.sent_backups[sender_id] = backups\n\n    def _guardian_shares_backups(self, sender_id: GuardianId) -> None:\n        \"\"\"Mock round robin to demonstrate sharing of backups\"\"\"\n\n        backups_to_send = self.sent_backups[sender_id]\n        for backup in backups_to_send:\n            received_backups = self.received_backups.get(backup.designated_id)\n            if received_backups is None:\n                received_backups = []\n            received_backups.append(backup)\n            self.received_backups[backup.designated_id] = received_backups\n\n    def _guardian_verifies_backups(self, verifier_id: str) -> None:\n        \"\"\"Guardian verifies the backups they have received\"\"\"\n\n        verifications = []\n\n        for backup in self.received_backups[verifier_id]:\n            owner_public_key = self.election_key_pairs[backup.owner_id].share()\n            verification = verify_election_partial_key_backup(\n                verifier_id,\n                backup,\n                owner_public_key,\n                self.election_key_pairs[verifier_id],\n            )\n            verifications.append(verification)\n        self.sent_verifications[verifier_id] = verifications\n\n    def _guardian_shares_verifications(self, verifier_id: str) -> None:\n        \"\"\"Mock round robin to demonstrate sharing of verifications\"\"\"\n\n        verifications_to_send = self.sent_verifications[verifier_id]\n        for verification in verifications_to_send:\n            received_verifications = self.received_verifications.get(\n                verification.owner_id\n            )\n            if received_verifications is None:\n                received_verifications = []\n            received_verifications.append(verification)\n            self.received_verifications[verification.owner_id] = received_verifications\n\n    def _guardian_checks_returned_verifications(self, key_owner_id: GuardianId) -> None:\n        \"\"\"\n        Guardian checks that all backups have been\n        verified sucessfully\n        \"\"\"\n\n        verifications = self.received_verifications[key_owner_id]\n        for verification in verifications:\n            self.assertTrue(verification.verified)\n\n    def _guardian_challenges(self, guardian_ids: List[GuardianId]) -> None:\n        key_owner_id = guardian_ids[0]\n        original_verification = self.received_verifications[key_owner_id][0]\n        failed_verification = ElectionPartialKeyVerification(\n            original_verification.owner_id,\n            original_verification.designated_id,\n            original_verification.verifier_id,\n            False,\n        )\n        designated_id = original_verification.designated_id\n\n        # If backup failed to be verified\n        self.assertFalse(failed_verification.verified)\n\n        # Key Owner generate challenge\n        designated_backup = [\n            backup\n            for backup in self.sent_backups[key_owner_id]\n            if backup.designated_id == designated_id\n        ][0]\n        self.assertIsNotNone(designated_backup)\n        election_key_pair = self.election_key_pairs[key_owner_id]\n        challenge = generate_election_partial_key_challenge(\n            designated_backup, election_key_pair.polynomial\n        )\n\n        # Get a mediator to verify who is not the owner or originally designated guardian who previously verified\n        new_verifier_id = \"mediator_id\"\n\n        # New verifier verifies challenge\n        verification = verify_election_partial_key_challenge(\n            new_verifier_id,\n            challenge,\n        )\n        self.assertIsNotNone(verification)\n        self.assertNotEqual(key_owner_id, verification.verifier_id)\n        self.assertNotEqual(designated_id, verification.verifier_id)\n        self.assertTrue(verification.verified)\n\n        self.received_verifications[key_owner_id][0] = verification\n\n    def _publish_joint_key(self) -> None:\n        guardian_public_keys = [\n            keys.share() for keys in self.election_key_pairs.values()\n        ]\n        election_joint_key = combine_election_public_keys(guardian_public_keys)\n        self.assertIsNotNone(election_joint_key)\n"
  },
  {
    "path": "tests/integration/test_hamilton_county_election.py",
    "content": "from tests.base_test_case import BaseTestCase\n\nimport electionguard_tools.factories.election_factory as ElectionFactory\nimport electionguard_tools.factories.ballot_factory as BallotFactory\n\nelection_factory = ElectionFactory.ElectionFactory()\nballot_factory = BallotFactory.BallotFactory()\n\n\nclass TestHamiltonCountyElection(BaseTestCase):\n    \"\"\"\n    Demonstrates a non-trivial example using realistic input data\n    \"\"\"\n\n    def test_manifest_is_valid(self) -> None:\n\n        # Act\n        subject = election_factory.get_hamilton_manifest_from_file()\n\n        # Assert\n        self.assertTrue(subject.is_valid())\n"
  },
  {
    "path": "tests/property/__init__.py",
    "content": ""
  },
  {
    "path": "tests/property/test_ballot.py",
    "content": "from typing import Tuple\nfrom datetime import timedelta\nfrom hypothesis import HealthCheck\nfrom hypothesis import given, settings\n\nfrom tests.base_test_case import BaseTestCase\n\nfrom electionguard.ballot import PlaintextBallotSelection\n\nimport electionguard_tools.factories.ballot_factory as BallotFactory\n\n\nclass TestBallot(BaseTestCase):\n    \"\"\"Ballot tests\"\"\"\n\n    def test_ballot_is_valid(self):\n        # Arrange\n        factory = BallotFactory.BallotFactory()\n\n        # Act\n        subject = factory.get_simple_ballot_from_file()\n        first_contest = subject.contests[0]\n        self.assertIsNotNone(first_contest)\n\n        # Assert\n        self.assertIsNotNone(subject.object_id)\n        self.assertEqual(subject.object_id, \"some-external-id-string-123\")\n        self.assertTrue(subject.is_valid(\"jefferson-county-ballot-style\"))\n        self.assertTrue(first_contest.is_valid(\"justice-supreme-court\", 2, 2))\n        self.assertFalse(first_contest.is_valid(\"some-other-contest\", 2, 2))\n        self.assertFalse(first_contest.is_valid(\"justice-supreme-court\", 1, 2))\n        self.assertFalse(first_contest.is_valid(\"justice-supreme-court\", 2, 1))\n        self.assertFalse(first_contest.is_valid(\"justice-supreme-court\", 2, 2, 1))\n\n    @settings(\n        deadline=timedelta(milliseconds=2000),\n        suppress_health_check=[HealthCheck.too_slow],\n        max_examples=10,\n    )\n    @given(BallotFactory.get_selection_well_formed())\n    def test_plaintext_ballot_selection_is_valid(\n        self, subject: Tuple[str, PlaintextBallotSelection]\n    ):\n        # Arrange\n        object_id, selection = subject\n\n        # Act\n        as_int = selection.vote\n        is_valid = selection.is_valid(object_id)\n\n        # Assert\n        self.assertTrue(is_valid)\n        self.assertTrue(0 <= as_int <= 1)\n\n    @settings(\n        deadline=timedelta(milliseconds=2000),\n        suppress_health_check=[HealthCheck.too_slow],\n        max_examples=10,\n    )\n    @given(BallotFactory.get_selection_poorly_formed())\n    def test_plaintext_ballot_selection_is_invalid(\n        self, subject: Tuple[str, PlaintextBallotSelection]\n    ):\n        # Arrange\n        object_id, selection = subject\n        a_different_object_id = f\"{object_id}-not-the-same\"\n\n        # Act\n        as_int = selection.vote\n        is_valid = selection.is_valid(a_different_object_id)\n\n        # Assert\n        self.assertFalse(is_valid)\n        self.assertTrue(0 <= as_int <= 1)\n"
  },
  {
    "path": "tests/property/test_chaum_pedersen.py",
    "content": "from datetime import timedelta\nfrom hypothesis import given, settings, HealthCheck, Phase\nfrom hypothesis.strategies import integers\n\n\nfrom tests.base_test_case import BaseTestCase\n\nfrom electionguard.chaum_pedersen import (\n    ConstantChaumPedersenProof,\n    make_disjunctive_chaum_pedersen_zero,\n    make_disjunctive_chaum_pedersen_one,\n    make_chaum_pedersen,\n    make_constant_chaum_pedersen,\n    make_disjunctive_chaum_pedersen,\n)\nfrom electionguard.elgamal import (\n    ElGamalKeyPair,\n    elgamal_encrypt,\n    elgamal_keypair_from_secret,\n)\nfrom electionguard.group import ElementModQ, TWO_MOD_Q, ONE_MOD_Q, int_to_p, TWO_MOD_P\nfrom electionguard.utils import get_optional\nfrom electionguard_tools.strategies.elgamal import elgamal_keypairs\nfrom electionguard_tools.strategies.group import elements_mod_q_no_zero, elements_mod_q\n\n\nclass TestDisjunctiveChaumPedersen(BaseTestCase):\n    \"\"\"Disjunctive Chaum Pedersen tests\"\"\"\n\n    def test_djcp_proofs_simple(self):\n        # doesn't get any simpler than this\n        keypair = elgamal_keypair_from_secret(TWO_MOD_Q)\n        nonce = ONE_MOD_Q\n        seed = TWO_MOD_Q\n        message0 = get_optional(elgamal_encrypt(0, nonce, keypair.public_key))\n        proof0 = make_disjunctive_chaum_pedersen_zero(\n            message0, nonce, keypair.public_key, ONE_MOD_Q, seed\n        )\n        proof0bad = make_disjunctive_chaum_pedersen_one(\n            message0, nonce, keypair.public_key, ONE_MOD_Q, seed\n        )\n        self.assertTrue(proof0.is_valid(message0, keypair.public_key, ONE_MOD_Q))\n        self.assertFalse(proof0bad.is_valid(message0, keypair.public_key, ONE_MOD_Q))\n\n        message1 = get_optional(elgamal_encrypt(1, nonce, keypair.public_key))\n        proof1 = make_disjunctive_chaum_pedersen_one(\n            message1, nonce, keypair.public_key, ONE_MOD_Q, seed\n        )\n        proof1bad = make_disjunctive_chaum_pedersen_zero(\n            message1, nonce, keypair.public_key, ONE_MOD_Q, seed\n        )\n        self.assertTrue(proof1.is_valid(message1, keypair.public_key, ONE_MOD_Q))\n        self.assertFalse(proof1bad.is_valid(message1, keypair.public_key, ONE_MOD_Q))\n\n    def test_djcp_proof_invalid_inputs(self):\n        # this is here to push up our coverage\n        keypair = elgamal_keypair_from_secret(TWO_MOD_Q)\n        nonce = ONE_MOD_Q\n        seed = TWO_MOD_Q\n        message0 = get_optional(elgamal_encrypt(0, nonce, keypair.public_key))\n        self.assertRaises(\n            Exception,\n            make_disjunctive_chaum_pedersen,\n            message0,\n            nonce,\n            keypair.public_key,\n            seed,\n            3,\n        )\n\n    @settings(\n        deadline=timedelta(milliseconds=2000),\n        suppress_health_check=[HealthCheck.too_slow],\n        max_examples=10,\n    )\n    @given(elgamal_keypairs(), elements_mod_q_no_zero(), elements_mod_q())\n    def test_djcp_proof_zero(\n        self, keypair: ElGamalKeyPair, nonce: ElementModQ, seed: ElementModQ\n    ):\n        message = get_optional(elgamal_encrypt(0, nonce, keypair.public_key))\n        proof = make_disjunctive_chaum_pedersen_zero(\n            message, nonce, keypair.public_key, ONE_MOD_Q, seed\n        )\n        proof_bad = make_disjunctive_chaum_pedersen_one(\n            message, nonce, keypair.public_key, ONE_MOD_Q, seed\n        )\n        self.assertTrue(proof.is_valid(message, keypair.public_key, ONE_MOD_Q))\n        self.assertFalse(proof_bad.is_valid(message, keypair.public_key, ONE_MOD_Q))\n\n    @settings(\n        deadline=timedelta(milliseconds=2000),\n        suppress_health_check=[HealthCheck.too_slow],\n        max_examples=10,\n    )\n    @given(elgamal_keypairs(), elements_mod_q_no_zero(), elements_mod_q())\n    def test_djcp_proof_one(\n        self, keypair: ElGamalKeyPair, nonce: ElementModQ, seed: ElementModQ\n    ):\n        message = get_optional(elgamal_encrypt(1, nonce, keypair.public_key))\n        proof = make_disjunctive_chaum_pedersen_one(\n            message, nonce, keypair.public_key, ONE_MOD_Q, seed\n        )\n        proof_bad = make_disjunctive_chaum_pedersen_zero(\n            message, nonce, keypair.public_key, ONE_MOD_Q, seed\n        )\n        self.assertTrue(proof.is_valid(message, keypair.public_key, ONE_MOD_Q))\n        self.assertFalse(proof_bad.is_valid(message, keypair.public_key, ONE_MOD_Q))\n\n    @settings(\n        deadline=timedelta(milliseconds=2000),\n        suppress_health_check=[HealthCheck.too_slow],\n        max_examples=10,\n    )\n    @given(elgamal_keypairs(), elements_mod_q_no_zero(), elements_mod_q())\n    def test_djcp_proof_broken(\n        self, keypair: ElGamalKeyPair, nonce: ElementModQ, seed: ElementModQ\n    ):\n        # verify two different ways to generate an invalid C-P proof.\n        message = get_optional(elgamal_encrypt(0, nonce, keypair.public_key))\n        message_bad = get_optional(elgamal_encrypt(2, nonce, keypair.public_key))\n        proof = make_disjunctive_chaum_pedersen_zero(\n            message, nonce, keypair.public_key, ONE_MOD_Q, seed\n        )\n        proof_bad = make_disjunctive_chaum_pedersen_zero(\n            message_bad, nonce, keypair.public_key, ONE_MOD_Q, seed\n        )\n\n        self.assertFalse(proof_bad.is_valid(message_bad, keypair.public_key, ONE_MOD_Q))\n        self.assertFalse(proof.is_valid(message_bad, keypair.public_key, ONE_MOD_Q))\n\n\nclass TestChaumPedersen(BaseTestCase):\n    \"\"\"Chaum Pedersen tests\"\"\"\n\n    def test_cp_proofs_simple(self):\n        keypair = elgamal_keypair_from_secret(TWO_MOD_Q)\n        nonce = ONE_MOD_Q\n        seed = TWO_MOD_Q\n        message = get_optional(elgamal_encrypt(0, nonce, keypair.public_key))\n        decryption = message.partial_decrypt(keypair.secret_key)\n        proof = make_chaum_pedersen(\n            message, keypair.secret_key, decryption, seed, ONE_MOD_Q\n        )\n        bad_proof = make_chaum_pedersen(\n            message, keypair.secret_key, TWO_MOD_P, seed, ONE_MOD_Q\n        )\n        self.assertTrue(\n            proof.is_valid(message, keypair.public_key, decryption, ONE_MOD_Q)\n        )\n        self.assertFalse(\n            bad_proof.is_valid(message, keypair.public_key, decryption, ONE_MOD_Q)\n        )\n\n    @settings(\n        deadline=timedelta(milliseconds=2000),\n        suppress_health_check=[HealthCheck.too_slow],\n        max_examples=10,\n        phases=[Phase.explicit, Phase.reuse, Phase.generate, Phase.target],\n    )\n    @given(\n        elgamal_keypairs(),\n        elements_mod_q_no_zero(),\n        elements_mod_q(),\n        integers(0, 100),\n        integers(0, 100),\n    )\n    def test_cp_proof(\n        self,\n        keypair: ElGamalKeyPair,\n        nonce: ElementModQ,\n        seed: ElementModQ,\n        constant: int,\n        bad_constant: int,\n    ):\n        if constant == bad_constant:\n            bad_constant = constant + 1\n\n        message = get_optional(elgamal_encrypt(constant, nonce, keypair.public_key))\n        decryption = message.partial_decrypt(keypair.secret_key)\n        proof = make_chaum_pedersen(\n            message, keypair.secret_key, decryption, seed, ONE_MOD_Q\n        )\n        bad_proof = make_chaum_pedersen(\n            message, keypair.secret_key, int_to_p(bad_constant), seed, ONE_MOD_Q\n        )\n        self.assertTrue(\n            proof.is_valid(message, keypair.public_key, decryption, ONE_MOD_Q)\n        )\n        self.assertFalse(\n            bad_proof.is_valid(message, keypair.public_key, decryption, ONE_MOD_Q)\n        )\n\n\nclass TestConstantChaumPedersen(BaseTestCase):\n    \"\"\"Constant Chaum Pedersen tests\"\"\"\n\n    def test_ccp_proofs_simple_encryption_of_zero(self):\n        keypair = elgamal_keypair_from_secret(TWO_MOD_Q)\n        nonce = ONE_MOD_Q\n        seed = TWO_MOD_Q\n        message = get_optional(elgamal_encrypt(0, nonce, keypair.public_key))\n        proof = make_constant_chaum_pedersen(\n            message, 0, nonce, keypair.public_key, seed, ONE_MOD_Q\n        )\n        bad_proof = make_constant_chaum_pedersen(\n            message, 1, nonce, keypair.public_key, seed, ONE_MOD_Q\n        )\n        self.assertTrue(proof.is_valid(message, keypair.public_key, ONE_MOD_Q))\n        self.assertFalse(bad_proof.is_valid(message, keypair.public_key, ONE_MOD_Q))\n\n    def test_ccp_proofs_simple_encryption_of_one(self):\n        keypair = elgamal_keypair_from_secret(TWO_MOD_Q)\n        nonce = ONE_MOD_Q\n        seed = TWO_MOD_Q\n        message = get_optional(elgamal_encrypt(1, nonce, keypair.public_key))\n        proof = make_constant_chaum_pedersen(\n            message, 1, nonce, keypair.public_key, seed, ONE_MOD_Q\n        )\n        bad_proof = make_constant_chaum_pedersen(\n            message, 0, nonce, keypair.public_key, seed, ONE_MOD_Q\n        )\n        self.assertTrue(proof.is_valid(message, keypair.public_key, ONE_MOD_Q))\n        self.assertFalse(bad_proof.is_valid(message, keypair.public_key, ONE_MOD_Q))\n\n    @settings(\n        deadline=timedelta(milliseconds=2000),\n        suppress_health_check=[HealthCheck.too_slow],\n        max_examples=10,\n    )\n    @given(\n        elgamal_keypairs(),\n        elements_mod_q_no_zero(),\n        elements_mod_q(),\n        integers(0, 100),\n        integers(0, 100),\n    )\n    def test_ccp_proof(\n        self,\n        keypair: ElGamalKeyPair,\n        nonce: ElementModQ,\n        seed: ElementModQ,\n        constant: int,\n        bad_constant: int,\n    ):\n        # assume() slows down the test-case generation\n        # so assume(constant != bad_constant)\n        if constant == bad_constant:\n            bad_constant = constant + 1\n\n        message = get_optional(elgamal_encrypt(constant, nonce, keypair.public_key))\n        message_bad = get_optional(\n            elgamal_encrypt(bad_constant, nonce, keypair.public_key)\n        )\n\n        proof = make_constant_chaum_pedersen(\n            message, constant, nonce, keypair.public_key, seed, ONE_MOD_Q\n        )\n        self.assertTrue(proof.is_valid(message, keypair.public_key, ONE_MOD_Q))\n\n        proof_bad1 = make_constant_chaum_pedersen(\n            message_bad, constant, nonce, keypair.public_key, seed, ONE_MOD_Q\n        )\n        self.assertFalse(\n            proof_bad1.is_valid(message_bad, keypair.public_key, ONE_MOD_Q)\n        )\n\n        proof_bad2 = make_constant_chaum_pedersen(\n            message, bad_constant, nonce, keypair.public_key, seed, ONE_MOD_Q\n        )\n        self.assertFalse(proof_bad2.is_valid(message, keypair.public_key, ONE_MOD_Q))\n\n        proof_bad3 = ConstantChaumPedersenProof(\n            proof.pad, proof.data, proof.challenge, proof.response, -1\n        )\n        self.assertFalse(proof_bad3.is_valid(message, keypair.public_key, ONE_MOD_Q))\n"
  },
  {
    "path": "tests/property/test_decrypt_with_secrets.py",
    "content": "from copy import deepcopy\nfrom datetime import timedelta\nfrom random import Random\nfrom typing import Tuple\n\nfrom hypothesis import HealthCheck, Phase\nfrom hypothesis import given, settings\nfrom hypothesis.strategies import integers\n\nfrom tests.base_test_case import BaseTestCase\n\nfrom electionguard.chaum_pedersen import DisjunctiveChaumPedersenProof\nfrom electionguard.decrypt_with_secrets import (\n    decrypt_selection_with_secret,\n    decrypt_selection_with_nonce,\n    decrypt_contest_with_secret,\n    decrypt_contest_with_nonce,\n    decrypt_ballot_with_nonce,\n    decrypt_ballot_with_secret,\n)\nfrom electionguard.elgamal import ElGamalKeyPair, ElGamalCiphertext\nfrom electionguard.encrypt import (\n    encrypt_contest,\n    encrypt_selection,\n    EncryptionMediator,\n)\nfrom electionguard.group import (\n    ElementModQ,\n    TWO_MOD_P,\n    ONE_MOD_Q,\n    mult_p,\n)\nfrom electionguard.manifest import (\n    ContestDescription,\n    SelectionDescription,\n    generate_placeholder_selections_from,\n    contest_description_with_placeholders_from,\n)\n\nimport electionguard_tools.factories.ballot_factory as BallotFactory\nimport electionguard_tools.factories.election_factory as ElectionFactory\nfrom electionguard_tools.strategies.elgamal import elgamal_keypairs\nfrom electionguard_tools.strategies.group import elements_mod_q_no_zero\n\n\nelection_factory = ElectionFactory.ElectionFactory()\nballot_factory = BallotFactory.BallotFactory()\n\n\nclass TestDecryptWithSecrets(BaseTestCase):\n    \"\"\"Decrypting with secrets tests\"\"\"\n\n    @settings(\n        deadline=timedelta(milliseconds=2000),\n        suppress_health_check=[HealthCheck.too_slow],\n        max_examples=10,\n        # disabling the \"shrink\" phase, because it runs very slowly\n        phases=[Phase.explicit, Phase.reuse, Phase.generate, Phase.target],\n    )\n    @given(\n        ElectionFactory.get_selection_description_well_formed(),\n        elgamal_keypairs(),\n        elements_mod_q_no_zero(),\n        integers(),\n    )\n    def test_decrypt_selection_valid_input_succeeds(\n        self,\n        selection_description: Tuple[str, SelectionDescription],\n        keypair: ElGamalKeyPair,\n        nonce_seed: ElementModQ,\n        random_seed: int,\n    ):\n\n        # Arrange\n        random = Random(random_seed)\n        _, description = selection_description\n        data = ballot_factory.get_random_selection_from(description, random)\n\n        # Act\n        subject = encrypt_selection(\n            data,\n            description,\n            keypair.public_key,\n            ONE_MOD_Q,\n            nonce_seed,\n            should_verify_proofs=True,\n        )\n        self.assertIsNotNone(subject)\n\n        result_from_key = decrypt_selection_with_secret(\n            subject, description, keypair.public_key, keypair.secret_key, ONE_MOD_Q\n        )\n        result_from_nonce = decrypt_selection_with_nonce(\n            subject, description, keypair.public_key, ONE_MOD_Q\n        )\n        result_from_nonce_seed = decrypt_selection_with_nonce(\n            subject, description, keypair.public_key, ONE_MOD_Q, nonce_seed\n        )\n\n        # Assert\n        self.assertIsNotNone(result_from_key)\n        self.assertIsNotNone(result_from_nonce)\n        self.assertIsNotNone(result_from_nonce_seed)\n        self.assertEqual(data.vote, result_from_key.vote)\n        self.assertEqual(data.vote, result_from_nonce.vote)\n        self.assertEqual(data.vote, result_from_nonce_seed.vote)\n\n    @settings(\n        deadline=timedelta(milliseconds=2000),\n        suppress_health_check=[HealthCheck.too_slow],\n        max_examples=10,\n        # disabling the \"shrink\" phase, because it runs very slowly\n        phases=[Phase.explicit, Phase.reuse, Phase.generate, Phase.target],\n    )\n    @given(\n        ElectionFactory.get_selection_description_well_formed(),\n        elgamal_keypairs(),\n        elements_mod_q_no_zero(),\n        integers(),\n    )\n    def test_decrypt_selection_valid_input_tampered_fails(\n        self,\n        selection_description: Tuple[str, SelectionDescription],\n        keypair: ElGamalKeyPair,\n        seed: ElementModQ,\n        random_seed: int,\n    ):\n\n        # Arrange\n        _, description = selection_description\n        random = Random(random_seed)\n        data = ballot_factory.get_random_selection_from(description, random)\n\n        # Act\n        subject = encrypt_selection(\n            data, description, keypair.public_key, ONE_MOD_Q, seed\n        )\n\n        # tamper with the encryption\n        malformed_encryption = deepcopy(subject)\n        malformed_encryption.ciphertext.pad = mult_p(subject.ciphertext.pad, TWO_MOD_P)\n\n        # tamper with the proof\n        malformed_proof = deepcopy(subject)\n        altered_a0 = mult_p(subject.proof.proof_zero_pad, TWO_MOD_P)\n        malformed_disjunctive = DisjunctiveChaumPedersenProof(\n            altered_a0,\n            malformed_proof.proof.proof_zero_data,\n            malformed_proof.proof.proof_one_pad,\n            malformed_proof.proof.proof_one_data,\n            malformed_proof.proof.proof_zero_challenge,\n            malformed_proof.proof.proof_one_challenge,\n            malformed_proof.proof.challenge,\n            malformed_proof.proof.proof_zero_response,\n            malformed_proof.proof.proof_one_response,\n        )\n        malformed_proof.proof = malformed_disjunctive\n\n        result_from_key_malformed_encryption = decrypt_selection_with_secret(\n            malformed_encryption,\n            description,\n            keypair.public_key,\n            keypair.secret_key,\n            ONE_MOD_Q,\n        )\n\n        result_from_key_malformed_proof = decrypt_selection_with_secret(\n            malformed_proof,\n            description,\n            keypair.public_key,\n            keypair.secret_key,\n            ONE_MOD_Q,\n        )\n\n        result_from_nonce_malformed_encryption = decrypt_selection_with_nonce(\n            malformed_encryption, description, keypair.public_key, ONE_MOD_Q\n        )\n        result_from_nonce_malformed_proof = decrypt_selection_with_nonce(\n            malformed_proof, description, keypair.public_key, ONE_MOD_Q\n        )\n\n        # Assert\n        self.assertIsNone(result_from_key_malformed_encryption)\n        self.assertIsNone(result_from_key_malformed_proof)\n        self.assertIsNone(result_from_nonce_malformed_encryption)\n        self.assertIsNone(result_from_nonce_malformed_proof)\n\n    @settings(\n        deadline=timedelta(milliseconds=2000),\n        suppress_health_check=[HealthCheck.too_slow],\n        max_examples=10,\n        # disabling the \"shrink\" phase, because it runs very slowly\n        phases=[Phase.explicit, Phase.reuse, Phase.generate, Phase.target],\n    )\n    @given(\n        ElectionFactory.get_selection_description_well_formed(),\n        elgamal_keypairs(),\n        elements_mod_q_no_zero(),\n        integers(),\n    )\n    def test_decrypt_selection_tampered_nonce_fails(\n        self,\n        selection_description: Tuple[str, SelectionDescription],\n        keypair: ElGamalKeyPair,\n        nonce_seed: ElementModQ,\n        random_seed: int,\n    ):\n\n        # Arrange\n        random = Random(random_seed)\n        _, description = selection_description\n        data = ballot_factory.get_random_selection_from(description, random)\n\n        # Act\n        subject = encrypt_selection(\n            data,\n            description,\n            keypair.public_key,\n            ONE_MOD_Q,\n            nonce_seed,\n            should_verify_proofs=True,\n        )\n        self.assertIsNotNone(subject)\n\n        # Tamper with the nonce by setting it to an arbitrary value\n        subject.nonce = nonce_seed\n\n        result_from_nonce_seed = decrypt_selection_with_nonce(\n            subject, description, keypair.public_key, ONE_MOD_Q, nonce_seed\n        )\n\n        # Assert\n        self.assertIsNone(result_from_nonce_seed)\n\n    @settings(\n        deadline=timedelta(milliseconds=5000),\n        suppress_health_check=[HealthCheck.too_slow],\n        max_examples=10,\n        # disabling the \"shrink\" phase, because it runs very slowly\n        phases=[Phase.explicit, Phase.reuse, Phase.generate, Phase.target],\n    )\n    @given(\n        ElectionFactory.get_contest_description_well_formed(),\n        elgamal_keypairs(),\n        elements_mod_q_no_zero(),\n        integers(),\n    )\n    def test_decrypt_contest_valid_input_succeeds(\n        self,\n        contest_description: Tuple[str, ContestDescription],\n        keypair: ElGamalKeyPair,\n        nonce_seed: ElementModQ,\n        random_seed: int,\n    ):\n\n        # Arrange\n        _, description = contest_description\n        random = Random(random_seed)\n        data = ballot_factory.get_random_contest_from(description, random)\n\n        placeholders = generate_placeholder_selections_from(\n            description, description.number_elected\n        )\n        description_with_placeholders = contest_description_with_placeholders_from(\n            description, placeholders\n        )\n\n        self.assertTrue(description_with_placeholders.is_valid())\n\n        # Act\n        subject = encrypt_contest(\n            data,\n            description_with_placeholders,\n            keypair.public_key,\n            ONE_MOD_Q,\n            nonce_seed,\n            should_verify_proofs=True,\n        )\n        self.assertIsNotNone(subject)\n\n        # Decrypt the contest, but keep the placeholders\n        # so we can verify the selection count matches as expected in the test\n        result_from_key = decrypt_contest_with_secret(\n            subject,\n            description_with_placeholders,\n            keypair.public_key,\n            keypair.secret_key,\n            ONE_MOD_Q,\n            remove_placeholders=False,\n        )\n        result_from_nonce = decrypt_contest_with_nonce(\n            subject,\n            description_with_placeholders,\n            keypair.public_key,\n            ONE_MOD_Q,\n            remove_placeholders=False,\n        )\n        result_from_nonce_seed = decrypt_contest_with_nonce(\n            subject,\n            description_with_placeholders,\n            keypair.public_key,\n            ONE_MOD_Q,\n            nonce_seed,\n            remove_placeholders=False,\n        )\n\n        # Assert\n        self.assertIsNotNone(result_from_key)\n        self.assertIsNotNone(result_from_nonce)\n        self.assertIsNotNone(result_from_nonce_seed)\n\n        # The decrypted contest should include an entry for each possible selection\n        # and placeholders for each seat\n        expected_entries = (\n            len(description.ballot_selections) + description.number_elected\n        )\n        self.assertTrue(\n            result_from_key.is_valid(\n                description.object_id,\n                expected_entries,\n                description.number_elected,\n                description.votes_allowed,\n            )\n        )\n        self.assertTrue(\n            result_from_nonce.is_valid(\n                description.object_id,\n                expected_entries,\n                description.number_elected,\n                description.votes_allowed,\n            )\n        )\n        self.assertTrue(\n            result_from_nonce_seed.is_valid(\n                description.object_id,\n                expected_entries,\n                description.number_elected,\n                description.votes_allowed,\n            )\n        )\n\n        # Assert the ballot selections sum to the expected number of selections\n        key_selected = sum(\n            selection.vote for selection in result_from_key.ballot_selections\n        )\n        nonce_selected = sum(\n            selection.vote for selection in result_from_nonce.ballot_selections\n        )\n        seed_selected = sum(\n            selection.vote for selection in result_from_nonce_seed.ballot_selections\n        )\n\n        self.assertEqual(key_selected, nonce_selected)\n        self.assertEqual(seed_selected, nonce_selected)\n        self.assertEqual(description.number_elected, key_selected)\n\n        # Assert each selection is valid\n        for selection_description in description.ballot_selections:\n\n            key_selection = [\n                selection\n                for selection in result_from_key.ballot_selections\n                if selection.object_id == selection_description.object_id\n            ][0]\n            nonce_selection = [\n                selection\n                for selection in result_from_nonce.ballot_selections\n                if selection.object_id == selection_description.object_id\n            ][0]\n            seed_selection = [\n                selection\n                for selection in result_from_nonce_seed.ballot_selections\n                if selection.object_id == selection_description.object_id\n            ][0]\n\n            data_selections_exist = [\n                selection\n                for selection in data.ballot_selections\n                if selection.object_id == selection_description.object_id\n            ]\n\n            # It's possible there are no selections in the original data collection\n            # since it is valid to pass in a ballot that is not complete\n            if any(data_selections_exist):\n                self.assertTrue(data_selections_exist[0].vote == key_selection.vote)\n                self.assertTrue(data_selections_exist[0].vote == nonce_selection.vote)\n                self.assertTrue(data_selections_exist[0].vote == seed_selection.vote)\n\n            # TODO: also check edge cases such as:\n            # - placeholder selections are true for under votes\n\n            self.assertTrue(key_selection.is_valid(selection_description.object_id))\n            self.assertTrue(nonce_selection.is_valid(selection_description.object_id))\n            self.assertTrue(seed_selection.is_valid(selection_description.object_id))\n\n    @settings(\n        deadline=timedelta(milliseconds=5000),\n        suppress_health_check=[HealthCheck.too_slow],\n        max_examples=10,\n        # disabling the \"shrink\" phase, because it runs very slowly\n        phases=[Phase.explicit, Phase.reuse, Phase.generate, Phase.target],\n    )\n    @given(\n        ElectionFactory.get_contest_description_well_formed(),\n        elgamal_keypairs(),\n        elements_mod_q_no_zero(),\n        integers(),\n    )\n    def test_decrypt_contest_invalid_input_fails(\n        self,\n        contest_description: Tuple[str, ContestDescription],\n        keypair: ElGamalKeyPair,\n        nonce_seed: ElementModQ,\n        random_seed: int,\n    ):\n\n        # Arrange\n        _, description = contest_description\n        random = Random(random_seed)\n        data = ballot_factory.get_random_contest_from(description, random)\n\n        placeholders = generate_placeholder_selections_from(\n            description, description.number_elected\n        )\n        description_with_placeholders = contest_description_with_placeholders_from(\n            description, placeholders\n        )\n\n        self.assertTrue(description_with_placeholders.is_valid())\n\n        # Act\n        subject = encrypt_contest(\n            data,\n            description_with_placeholders,\n            keypair.public_key,\n            ONE_MOD_Q,\n            nonce_seed,\n        )\n        self.assertIsNotNone(subject)\n\n        # tamper with the nonce\n        subject.nonce = ONE_MOD_Q\n\n        result_from_nonce = decrypt_contest_with_nonce(\n            subject,\n            description_with_placeholders,\n            keypair.public_key,\n            ONE_MOD_Q,\n            remove_placeholders=False,\n        )\n        result_from_nonce_seed = decrypt_contest_with_nonce(\n            subject,\n            description_with_placeholders,\n            keypair.public_key,\n            ONE_MOD_Q,\n            nonce_seed,\n            remove_placeholders=False,\n        )\n\n        # Assert\n        self.assertIsNone(result_from_nonce)\n        self.assertIsNone(result_from_nonce_seed)\n\n        # Tamper with the encryption\n        subject.ballot_selections[0].ciphertext = ElGamalCiphertext(\n            TWO_MOD_P, TWO_MOD_P\n        )\n\n        result_from_key_tampered = decrypt_contest_with_secret(\n            subject,\n            description_with_placeholders,\n            keypair.public_key,\n            keypair.secret_key,\n            ONE_MOD_Q,\n            remove_placeholders=False,\n        )\n        result_from_nonce_tampered = decrypt_contest_with_nonce(\n            subject,\n            description_with_placeholders,\n            keypair.public_key,\n            ONE_MOD_Q,\n            remove_placeholders=False,\n        )\n        result_from_nonce_seed_tampered = decrypt_contest_with_nonce(\n            subject,\n            description_with_placeholders,\n            keypair.public_key,\n            ONE_MOD_Q,\n            nonce_seed,\n            remove_placeholders=False,\n        )\n\n        # Assert\n        self.assertIsNone(result_from_key_tampered)\n        self.assertIsNone(result_from_nonce_tampered)\n        self.assertIsNone(result_from_nonce_seed_tampered)\n\n    @settings(\n        deadline=timedelta(milliseconds=5000),\n        suppress_health_check=[HealthCheck.too_slow],\n        max_examples=1,\n        # disabling the \"shrink\" phase, because it runs very slowly\n        phases=[Phase.explicit, Phase.reuse, Phase.generate, Phase.target],\n    )\n    @given(elgamal_keypairs())\n    def test_decrypt_ballot_valid_input_succeeds(self, keypair: ElGamalKeyPair):\n        \"\"\"\n        Check that decryption works as expected by encrypting a ballot using the stateful `EncryptionMediator`\n        and then calling the various decrypt functions.\n        \"\"\"\n\n        # TODO: Hypothesis test instead\n\n        # Arrange\n        election = election_factory.get_simple_manifest_from_file()\n        internal_manifest, context = election_factory.get_fake_ciphertext_election(\n            election, keypair.public_key\n        )\n\n        data = ballot_factory.get_simple_ballot_from_file()\n        device = election_factory.get_encryption_device()\n        operator = EncryptionMediator(internal_manifest, context, device)\n\n        # Act\n        subject = operator.encrypt(data)\n        self.assertIsNotNone(subject)\n\n        result_from_key = decrypt_ballot_with_secret(\n            subject,\n            internal_manifest,\n            context.crypto_extended_base_hash,\n            keypair.public_key,\n            keypair.secret_key,\n            remove_placeholders=False,\n        )\n        result_from_nonce = decrypt_ballot_with_nonce(\n            subject,\n            internal_manifest,\n            context.crypto_extended_base_hash,\n            keypair.public_key,\n            remove_placeholders=False,\n        )\n        result_from_nonce_seed = decrypt_ballot_with_nonce(\n            subject,\n            internal_manifest,\n            context.crypto_extended_base_hash,\n            keypair.public_key,\n            subject.nonce,\n            remove_placeholders=False,\n        )\n\n        # Assert\n        self.assertIsNotNone(result_from_key)\n        self.assertIsNotNone(result_from_nonce)\n        self.assertIsNotNone(result_from_nonce_seed)\n        self.assertEqual(data.object_id, subject.object_id)\n        self.assertEqual(data.object_id, result_from_key.object_id)\n        self.assertEqual(data.object_id, result_from_nonce.object_id)\n        self.assertEqual(data.object_id, result_from_nonce_seed.object_id)\n\n        for description in internal_manifest.get_contests_for(data.style_id):\n\n            expected_entries = (\n                len(description.ballot_selections) + description.number_elected\n            )\n\n            key_contest = [\n                contest\n                for contest in result_from_key.contests\n                if contest.object_id == description.object_id\n            ][0]\n            nonce_contest = [\n                contest\n                for contest in result_from_nonce.contests\n                if contest.object_id == description.object_id\n            ][0]\n            seed_contest = [\n                contest\n                for contest in result_from_nonce_seed.contests\n                if contest.object_id == description.object_id\n            ][0]\n\n            # Contests may not be voted on the ballot\n            data_contest_exists = [\n                contest\n                for contest in data.contests\n                if contest.object_id == description.object_id\n            ]\n            if any(data_contest_exists):\n                data_contest = data_contest_exists[0]\n            else:\n                data_contest = None\n\n            self.assertTrue(\n                key_contest.is_valid(\n                    description.object_id,\n                    expected_entries,\n                    description.number_elected,\n                    description.votes_allowed,\n                )\n            )\n            self.assertTrue(\n                nonce_contest.is_valid(\n                    description.object_id,\n                    expected_entries,\n                    description.number_elected,\n                    description.votes_allowed,\n                )\n            )\n            self.assertTrue(\n                seed_contest.is_valid(\n                    description.object_id,\n                    expected_entries,\n                    description.number_elected,\n                    description.votes_allowed,\n                )\n            )\n\n            for selection_description in description.ballot_selections:\n\n                key_selection = [\n                    selection\n                    for selection in key_contest.ballot_selections\n                    if selection.object_id == selection_description.object_id\n                ][0]\n                nonce_selection = [\n                    selection\n                    for selection in nonce_contest.ballot_selections\n                    if selection.object_id == selection_description.object_id\n                ][0]\n                seed_selection = [\n                    selection\n                    for selection in seed_contest.ballot_selections\n                    if selection.object_id == selection_description.object_id\n                ][0]\n\n                # Selections may be undervoted for a specific contest\n                if any(data_contest_exists):\n                    data_selection_exists = [\n                        selection\n                        for selection in data_contest.ballot_selections\n                        if selection.object_id == selection_description.object_id\n                    ]\n                else:\n                    data_selection_exists = []\n\n                if any(data_selection_exists):\n                    data_selection = data_selection_exists[0]\n                    self.assertTrue(data_selection.vote == key_selection.vote)\n                    self.assertTrue(data_selection.vote == nonce_selection.vote)\n                    self.assertTrue(data_selection.vote == seed_selection.vote)\n                else:\n                    data_selection = None\n\n                # TODO: also check edge cases such as:\n                # - placeholder selections are true for under votes\n\n                self.assertTrue(key_selection.is_valid(selection_description.object_id))\n                self.assertTrue(\n                    nonce_selection.is_valid(selection_description.object_id)\n                )\n                self.assertTrue(\n                    seed_selection.is_valid(selection_description.object_id)\n                )\n\n    @settings(\n        deadline=timedelta(milliseconds=2000),\n        suppress_health_check=[HealthCheck.too_slow],\n        max_examples=1,\n        # disabling the \"shrink\" phase, because it runs very slowly\n        phases=[Phase.explicit, Phase.reuse, Phase.generate, Phase.target],\n    )\n    @given(elgamal_keypairs())\n    def test_decrypt_ballot_valid_input_missing_nonce_fails(\n        self, keypair: ElGamalKeyPair\n    ):\n\n        # Arrange\n        election = election_factory.get_simple_manifest_from_file()\n        internal_manifest, context = election_factory.get_fake_ciphertext_election(\n            election, keypair.public_key\n        )\n\n        data = ballot_factory.get_simple_ballot_from_file()\n        device = election_factory.get_encryption_device()\n        operator = EncryptionMediator(internal_manifest, context, device)\n\n        # Act\n        subject = operator.encrypt(data)\n        self.assertIsNotNone(subject)\n        subject.nonce = None\n\n        missing_nonce_value = None\n\n        result_from_nonce = decrypt_ballot_with_nonce(\n            subject,\n            internal_manifest,\n            context.crypto_extended_base_hash,\n            keypair.public_key,\n        )\n\n        # SUGGEST this test is the same as the one above\n        result_from_nonce_seed = decrypt_ballot_with_nonce(\n            subject,\n            internal_manifest,\n            context.crypto_extended_base_hash,\n            keypair.public_key,\n            missing_nonce_value,\n        )\n\n        # Assert\n        self.assertIsNone(result_from_nonce)\n        self.assertIsNone(result_from_nonce_seed)\n"
  },
  {
    "path": "tests/property/test_decryption_mediator.py",
    "content": "# pylint: disable=too-many-instance-attributes\n\nfrom datetime import timedelta\nfrom typing import Dict, List\nfrom random import randrange\n\nfrom hypothesis import given, HealthCheck, settings, Phase\nfrom hypothesis.strategies import integers, data\n\nfrom tests.base_test_case import BaseTestCase\n\nfrom electionguard.ballot import PlaintextBallot\nfrom electionguard.ballot_box import BallotBox, BallotBoxState, cast_ballot, get_ballots\nfrom electionguard.data_store import DataStore\nfrom electionguard.decryption_mediator import DecryptionMediator\nfrom electionguard.election import CiphertextElectionContext\nfrom electionguard.encrypt import (\n    EncryptionMediator,\n    encrypt_ballot,\n)\nfrom electionguard.group import ONE_MOD_Q\nfrom electionguard.guardian import Guardian\nfrom electionguard.key_ceremony import CeremonyDetails\nfrom electionguard.key_ceremony_mediator import KeyCeremonyMediator\nfrom electionguard.manifest import InternalManifest\nfrom electionguard.tally import (\n    CiphertextTally,\n    PlaintextTally,\n    tally_ballots,\n)\nfrom electionguard.utils import get_optional\n\nimport electionguard_tools.factories.ballot_factory as BallotFactory\nimport electionguard_tools.factories.election_factory as ElectionFactory\nfrom electionguard_tools.strategies.election import (\n    election_descriptions,\n    plaintext_voted_ballots,\n)\nfrom electionguard_tools.helpers.tally_ceremony_orchestrator import (\n    TallyCeremonyOrchestrator,\n)\nfrom electionguard_tools.helpers.key_ceremony_orchestrator import (\n    KeyCeremonyOrchestrator,\n)\nfrom electionguard_tools.helpers.tally_accumulate import accumulate_plaintext_ballots\nfrom electionguard_tools.helpers.election_builder import ElectionBuilder\n\n\nelection_factory = ElectionFactory.ElectionFactory()\nballot_factory = BallotFactory.BallotFactory()\n\n\nclass TestDecryptionMediator(BaseTestCase):\n    \"\"\"Test suite for DecryptionMediator\"\"\"\n\n    NUMBER_OF_GUARDIANS = 3\n    QUORUM = 2\n    CEREMONY_DETAILS = CeremonyDetails(NUMBER_OF_GUARDIANS, QUORUM)\n    internal_manifest: InternalManifest\n\n    decryption_mediator_id = \"mediator-id\"\n\n    def setUp(self):\n\n        # Key Ceremony\n        key_ceremony_mediator = KeyCeremonyMediator(\n            \"key_ceremony_mediator_mediator\", self.CEREMONY_DETAILS\n        )\n        self.guardians: List[Guardian] = KeyCeremonyOrchestrator.create_guardians(\n            self.CEREMONY_DETAILS\n        )\n        KeyCeremonyOrchestrator.perform_full_ceremony(\n            self.guardians, key_ceremony_mediator\n        )\n        self.joint_public_key = key_ceremony_mediator.publish_joint_key()\n        self.assertIsNotNone(self.joint_public_key)\n\n        # Setup the election\n        self.manifest = election_factory.get_fake_manifest()\n        builder = ElectionBuilder(self.NUMBER_OF_GUARDIANS, self.QUORUM, self.manifest)\n\n        self.assertIsNone(builder.build())  # Can't build without the public key\n\n        builder.set_public_key(self.joint_public_key.joint_public_key)\n        builder.set_commitment_hash(self.joint_public_key.commitment_hash)\n        self.internal_manifest, self.context = get_optional(builder.build())\n\n        self.encryption_device = election_factory.get_encryption_device()\n        self.ballot_marking_device = EncryptionMediator(\n            self.internal_manifest, self.context, self.encryption_device\n        )\n\n        # get some fake ballots\n        self.fake_cast_ballot = ballot_factory.get_fake_ballot(\n            self.internal_manifest, \"some-unique-ballot-id-cast\"\n        )\n        more_fake_ballots = []\n        for i in range(10):\n            more_fake_ballots.append(\n                ballot_factory.get_fake_ballot(\n                    self.internal_manifest, f\"some-unique-ballot-id-cast{i}\"\n                )\n            )\n        self.fake_spoiled_ballot = ballot_factory.get_fake_ballot(\n            self.internal_manifest, \"some-unique-ballot-id-spoiled\"\n        )\n        more_fake_spoiled_ballots = []\n        for i in range(2):\n            more_fake_spoiled_ballots.append(\n                ballot_factory.get_fake_ballot(\n                    self.internal_manifest, f\"some-unique-ballot-id-spoiled{i}\"\n                )\n            )\n        self.assertTrue(\n            self.fake_cast_ballot.is_valid(\n                self.internal_manifest.ballot_styles[0].object_id\n            )\n        )\n        self.assertTrue(\n            self.fake_spoiled_ballot.is_valid(\n                self.internal_manifest.ballot_styles[0].object_id\n            )\n        )\n        self.expected_plaintext_tally = accumulate_plaintext_ballots(\n            [self.fake_cast_ballot] + more_fake_ballots\n        )\n\n        # Fill in the expected values with any missing selections\n        # that were not made on any ballots\n        selection_ids = {\n            selection.object_id\n            for contest in self.internal_manifest.contests\n            for selection in contest.ballot_selections\n        }\n\n        missing_selection_ids = selection_ids.difference(\n            set(self.expected_plaintext_tally)\n        )\n\n        for id in missing_selection_ids:\n            self.expected_plaintext_tally[id] = 0\n\n        # Encrypt\n        self.encrypted_fake_cast_ballot = self.ballot_marking_device.encrypt(\n            self.fake_cast_ballot\n        )\n        self.encrypted_fake_spoiled_ballot = self.ballot_marking_device.encrypt(\n            self.fake_spoiled_ballot\n        )\n        self.assertIsNotNone(self.encrypted_fake_cast_ballot)\n        self.assertIsNotNone(self.encrypted_fake_spoiled_ballot)\n        self.assertTrue(\n            self.encrypted_fake_cast_ballot.is_valid_encryption(\n                self.internal_manifest.manifest_hash,\n                self.joint_public_key.joint_public_key,\n                self.context.crypto_extended_base_hash,\n            )\n        )\n\n        # encrypt some more fake ballots\n        more_fake_encrypted_ballots = []\n        for fake_ballot in more_fake_ballots:\n            more_fake_encrypted_ballots.append(\n                self.ballot_marking_device.encrypt(fake_ballot)\n            )\n        # encrypt some more fake ballots\n        self.more_fake_encrypted_spoiled_ballots = []\n        for fake_ballot in more_fake_spoiled_ballots:\n            self.more_fake_encrypted_spoiled_ballots.append(\n                self.ballot_marking_device.encrypt(fake_ballot)\n            )\n\n        # configure the ballot box\n        ballot_store = DataStore()\n        ballot_box = BallotBox(self.internal_manifest, self.context, ballot_store)\n        ballot_box.cast(self.encrypted_fake_cast_ballot)\n        ballot_box.spoil(self.encrypted_fake_spoiled_ballot)\n\n        # Cast some more fake ballots\n        for fake_ballot in more_fake_encrypted_ballots:\n            ballot_box.cast(fake_ballot)\n        # Spoil some more fake ballots\n        for fake_ballot in self.more_fake_encrypted_spoiled_ballots:\n            ballot_box.spoil(fake_ballot)\n\n        # generate encrypted tally\n        self.ciphertext_tally = tally_ballots(\n            ballot_store, self.internal_manifest, self.context\n        )\n        self.ciphertext_ballots = list(\n            get_ballots(ballot_store, BallotBoxState.SPOILED).values()\n        )\n\n    def test_announce(self):\n        # Arrange\n        mediator = DecryptionMediator(\n            self.decryption_mediator_id,\n            self.context,\n        )\n        guardian = self.guardians[0]\n        guardian_key = self.guardians[0].share_key()\n        tally_share = guardian.compute_tally_share(self.ciphertext_tally, self.context)\n        ballot_shares = {}\n\n        # Act\n        mediator.announce(guardian_key, tally_share, ballot_shares)\n\n        # Assert\n        self.assertEqual(len(mediator.get_available_guardians()), 1)\n\n        # Act\n        # Announce again\n        mediator.announce(guardian_key, tally_share, ballot_shares)\n\n        # Assert\n        # Can only announce once\n        self.assertEqual(len(mediator.get_available_guardians()), 1)\n        # Cannot get plaintext tally or spoiled ballots without a quorum\n        self.assertIsNone(\n            mediator.get_plaintext_tally(self.ciphertext_tally, self.manifest)\n        )\n        self.assertIsNone(\n            mediator.get_plaintext_ballots(self.ciphertext_ballots, self.manifest)\n        )\n\n    def test_get_plaintext_with_all_guardians_present(self):\n        # Arrange\n        mediator = DecryptionMediator(\n            self.decryption_mediator_id,\n            self.context,\n        )\n\n        available_guardians = self.guardians\n\n        TallyCeremonyOrchestrator.perform_decryption_setup(\n            available_guardians,\n            mediator,\n            self.context,\n            self.ciphertext_tally,\n            self.ciphertext_ballots,\n        )\n\n        # Act\n        plaintext_tally = mediator.get_plaintext_tally(\n            self.ciphertext_tally, self.manifest\n        )\n        plaintext_ballots = mediator.get_plaintext_ballots(\n            self.ciphertext_ballots, self.manifest\n        )\n\n        # Convert to selections to check for the same tally\n        selections = _convert_to_selections(plaintext_tally)\n\n        # Verify we get the same tally back if we call again\n        another_plaintext_tally = mediator.get_plaintext_tally(\n            self.ciphertext_tally, self.manifest\n        )\n\n        # Assert\n        self.assertIsNotNone(plaintext_tally)\n        self.assertIsNotNone(plaintext_ballots)\n        self.assertEqual(len(self.ciphertext_ballots), len(plaintext_ballots))\n\n        self.assertIsNotNone(selections)\n        self.assertEqual(self.expected_plaintext_tally, selections)\n\n        self.assertEqual(plaintext_tally, another_plaintext_tally)\n\n    def test_get_plaintext_with_a_missing_guardian(self):\n\n        # Arrange\n        mediator = DecryptionMediator(\n            self.decryption_mediator_id,\n            self.context,\n        )\n\n        available_guardians = self.guardians[0:2]\n        all_guardian_keys = [guardian.share_key() for guardian in self.guardians]\n\n        TallyCeremonyOrchestrator.perform_compensated_decryption_setup(\n            available_guardians,\n            all_guardian_keys,\n            mediator,\n            self.context,\n            self.ciphertext_tally,\n            self.ciphertext_ballots,\n        )\n\n        # Act\n        plaintext_tally = mediator.get_plaintext_tally(\n            self.ciphertext_tally, self.manifest\n        )\n        plaintext_ballots = mediator.get_plaintext_ballots(\n            self.ciphertext_ballots, self.manifest\n        )\n\n        # Convert to selections to check for the same tally\n        selections = _convert_to_selections(plaintext_tally)\n\n        # Verify we get the same tally back if we call again\n        another_plaintext_tally = mediator.get_plaintext_tally(\n            self.ciphertext_tally, self.manifest\n        )\n\n        # Assert\n        self.assertIsNotNone(plaintext_tally)\n        self.assertIsNotNone(plaintext_ballots)\n        self.assertEqual(len(self.ciphertext_ballots), len(plaintext_ballots))\n\n        self.assertIsNotNone(selections)\n        self.assertEqual(self.expected_plaintext_tally, selections)\n\n        self.assertEqual(plaintext_tally, another_plaintext_tally)\n\n    @settings(\n        deadline=timedelta(milliseconds=15000),\n        suppress_health_check=[HealthCheck.too_slow],\n        max_examples=8,\n        # disabling the \"shrink\" phase, because it runs very slowly\n        phases=[Phase.explicit, Phase.reuse, Phase.generate, Phase.target],\n    )\n    @given(data(), integers(1, 3), integers(2, 5))\n    def test_get_plaintext_tally_with_all_guardians_present(\n        self, values, parties: int, contests: int\n    ):\n        # Arrange\n        manifest = values.draw(election_descriptions(parties, contests))\n        builder = ElectionBuilder(self.NUMBER_OF_GUARDIANS, self.QUORUM, manifest)\n        internal_manifest, context = (\n            builder.set_public_key(self.joint_public_key.joint_public_key)\n            .set_commitment_hash(self.joint_public_key.commitment_hash)\n            .build()\n        )\n\n        plaintext_ballots: List[PlaintextBallot] = values.draw(\n            plaintext_voted_ballots(internal_manifest, randrange(3, 6))\n        )\n        expected_plaintext_tally = accumulate_plaintext_ballots(plaintext_ballots)\n\n        encrypted_tally = self._generate_encrypted_tally(\n            internal_manifest, context, plaintext_ballots\n        )\n\n        mediator = DecryptionMediator(self.decryption_mediator_id, context)\n        available_guardians = self.guardians\n        TallyCeremonyOrchestrator.perform_decryption_setup(\n            available_guardians, mediator, context, encrypted_tally, []\n        )\n\n        # Act\n        plaintext_tally = mediator.get_plaintext_tally(encrypted_tally, manifest)\n        selections = _convert_to_selections(plaintext_tally)\n\n        # Assert\n        self.assertIsNotNone(plaintext_tally)\n        self.assertIsNotNone(selections)\n        self.assertEqual(expected_plaintext_tally, selections)\n\n    def _generate_encrypted_tally(\n        self,\n        internal_manifest: InternalManifest,\n        context: CiphertextElectionContext,\n        ballots: List[PlaintextBallot],\n    ) -> CiphertextTally:\n\n        # encrypt each ballot\n        store = DataStore()\n        for ballot in ballots:\n            encrypted_ballot = encrypt_ballot(\n                ballot, internal_manifest, context, ONE_MOD_Q, should_verify_proofs=True\n            )\n            self.assertIsNotNone(encrypted_ballot)\n            # add to the ballot store\n            store.set(\n                encrypted_ballot.object_id,\n                cast_ballot(encrypted_ballot),\n            )\n\n        tally = tally_ballots(store, internal_manifest, context)\n        self.assertIsNotNone(tally)\n        return get_optional(tally)\n\n\ndef _convert_to_selections(tally: PlaintextTally) -> Dict[str, int]:\n    plaintext_selections: Dict[str, int] = {}\n    for _, contest in tally.contests.items():\n        for selection_id, selection in contest.selections.items():\n            plaintext_selections[selection_id] = selection.tally\n\n    return plaintext_selections\n"
  },
  {
    "path": "tests/property/test_discrete_log.py",
    "content": "import asyncio\nfrom hypothesis import given\nfrom hypothesis.strategies import integers\n\nfrom tests.base_test_case import BaseTestCase\n\nfrom electionguard.constants import get_generator, get_large_prime\nfrom electionguard.discrete_log import (\n    compute_discrete_log,\n    compute_discrete_log_async,\n    DiscreteLog,\n    precompute_discrete_log_cache,\n)\nfrom electionguard.group import (\n    ElementModP,\n    ElementModQ,\n    ONE_MOD_P,\n    ONE_MOD_Q,\n    mult_p,\n    g_pow_p,\n)\n\n\ndef _discrete_log_uncached(e: ElementModP) -> int:\n    \"\"\"\n    A simpler implementation of discrete_log, only meant for comparison testing of the caching version.\n    \"\"\"\n    count = 0\n    g_inv = ElementModP(pow(get_generator(), -1, get_large_prime()), False)\n    while e != ONE_MOD_P:\n        e = mult_p(e, g_inv)\n        count = count + 1\n\n    return count\n\n\nclass TestDiscreteLogFunctions(BaseTestCase):\n    \"\"\"Discrete log tests\"\"\"\n\n    @given(integers(0, 100))\n    def test_uncached(self, exp: int) -> None:\n        # Arrange\n        plaintext = ElementModQ(exp)\n        exp_plaintext = g_pow_p(plaintext)\n\n        # Act\n        plaintext_again = _discrete_log_uncached(exp_plaintext)\n\n        # Assert\n        self.assertEqual(plaintext, plaintext_again)\n\n    @given(integers(0, 1000))\n    def test_cached(self, exp: int) -> None:\n        # Arrange\n        cache = {ONE_MOD_P: 0}\n        plaintext = ElementModQ(exp)\n        exp_plaintext = g_pow_p(plaintext)\n\n        # Act\n        (plaintext_again, returned_cache) = compute_discrete_log(exp_plaintext, cache)\n\n        # Assert\n        self.assertEqual(plaintext, plaintext_again)\n        self.assertEqual(len(cache), len(returned_cache))\n\n    def test_cached_one(self) -> None:\n        cache = {ONE_MOD_P: 0}\n        plaintext = ONE_MOD_Q\n        ciphertext = g_pow_p(plaintext)\n        (plaintext_again, returned_cache) = compute_discrete_log(ciphertext, cache)\n\n        self.assertEqual(plaintext, plaintext_again)\n        self.assertEqual(len(cache), len(returned_cache))\n\n    def test_cached_one_async(self) -> None:\n        # Arrange\n        cache = {ONE_MOD_P: 0}\n        plaintext = ONE_MOD_Q\n        ciphertext = g_pow_p(plaintext)\n\n        # Act\n        loop = asyncio.new_event_loop()\n        (plaintext_again, returned_cache) = loop.run_until_complete(\n            compute_discrete_log_async(ciphertext, cache)\n        )\n        loop.close()\n\n        # Assert\n        self.assertEqual(plaintext, plaintext_again)\n        self.assertEqual(len(cache), len(returned_cache))\n\n    @given(integers(0, 1000))\n    def test_precompute_discrete_log(self, exponent: int) -> None:\n        # Arrange\n        minimum_cache_size = exponent + 1\n        element = g_pow_p(exponent)\n\n        # Act\n        cache = precompute_discrete_log_cache(exponent)\n        (calculated_exponent, _returned_cache) = compute_discrete_log(element, cache)\n\n        # Assert\n        self.assertGreaterEqual(len(cache), minimum_cache_size)\n        self.assertEqual(exponent, calculated_exponent)\n\n\nclass TestDiscreteLogClass(BaseTestCase):\n    \"\"\"Discrete log tests\"\"\"\n\n    @given(integers(0, 1000))\n    def test_precompute(self, exponent: int) -> None:\n        # Arrange\n        # Due to Singleton it could be bigger on previous run.\n        minimum_cache_size = exponent + 1\n        element = g_pow_p(exponent)\n\n        # Act\n        DiscreteLog().set_lazy_evaluation(False)\n        DiscreteLog().precompute_cache(exponent)\n        calculated_exponent = DiscreteLog().discrete_log(element)\n\n        # Assert\n        self.assertGreaterEqual(len(DiscreteLog().get_cache()), minimum_cache_size)\n        self.assertEqual(exponent, calculated_exponent)\n\n    @given(integers(0, 1000))\n    def test_cached(self, exp: int) -> None:\n        # Arrange\n        plaintext = ElementModQ(exp)\n        exp_plaintext = g_pow_p(plaintext)\n\n        # Act\n        plaintext_again = DiscreteLog().discrete_log(exp_plaintext)\n\n        # Assert\n        self.assertEqual(plaintext, plaintext_again)\n\n    def test_cached_one(self) -> None:\n        # Arrange\n        plaintext = ONE_MOD_Q\n        ciphertext = g_pow_p(plaintext)\n\n        # Act\n        plaintext_again = DiscreteLog().discrete_log(ciphertext)\n\n        # Assert\n        self.assertEqual(plaintext, plaintext_again)\n\n    def test_cached_one_async(self) -> None:\n        # Arrange\n        plaintext = ONE_MOD_Q\n        ciphertext = g_pow_p(plaintext)\n\n        # Act\n        loop = asyncio.new_event_loop()\n        plaintext_again = loop.run_until_complete(\n            DiscreteLog().discrete_log_async(ciphertext)\n        )\n        loop.close()\n\n        # Assert\n        self.assertEqual(plaintext, plaintext_again)\n"
  },
  {
    "path": "tests/property/test_elgamal.py",
    "content": "from timeit import default_timer as timer\nfrom hypothesis import given\nfrom hypothesis.strategies import integers\n\n\nfrom tests.base_test_case import BaseTestCase\n\nfrom electionguard.constants import (\n    get_generator,\n    get_small_prime,\n    get_large_prime,\n)\nfrom electionguard.elgamal import (\n    ElGamalKeyPair,\n    elgamal_encrypt,\n    elgamal_add,\n    elgamal_keypair_from_secret,\n    elgamal_keypair_random,\n    elgamal_combine_public_keys,\n    hashed_elgamal_encrypt,\n)\nfrom electionguard.encrypt import ContestData\nfrom electionguard.group import (\n    ElementModQ,\n    g_pow_p,\n    ZERO_MOD_Q,\n    TWO_MOD_Q,\n    ONE_MOD_Q,\n    ONE_MOD_P,\n)\nfrom electionguard.logs import log_info\nfrom electionguard.nonces import Nonces\nfrom electionguard.serialize import padded_decode\nfrom electionguard.scheduler import Scheduler\nfrom electionguard.utils import ContestErrorType, get_optional\nfrom electionguard_tools.strategies.elgamal import elgamal_keypairs\nfrom electionguard_tools.strategies.group import elements_mod_q_no_zero\n\n\nclass TestElGamal(BaseTestCase):\n    \"\"\"ElGamal tests\"\"\"\n\n    def test_simple_elgamal_encryption_decryption(self) -> None:\n        nonce = ONE_MOD_Q\n        secret_key = TWO_MOD_Q\n        keypair = get_optional(elgamal_keypair_from_secret(secret_key))\n        public_key = keypair.public_key\n\n        self.assertLess(public_key, get_large_prime())\n        elem = g_pow_p(ZERO_MOD_Q)\n        self.assertEqual(elem, ONE_MOD_P)  # g^0 == 1\n\n        ciphertext = get_optional(elgamal_encrypt(0, nonce, keypair.public_key))\n        self.assertEqual(get_generator(), ciphertext.pad)\n        self.assertEqual(\n            pow(ciphertext.pad.value, secret_key.value, get_large_prime()),\n            pow(public_key.value, nonce.value, get_large_prime()),\n        )\n        self.assertEqual(\n            ciphertext.data.value,\n            pow(public_key.value, nonce.value, get_large_prime()),\n        )\n\n        plaintext = ciphertext.decrypt(keypair.secret_key)\n\n        self.assertEqual(0, plaintext)\n\n    @given(integers(0, 100), elgamal_keypairs())\n    def test_elgamal_encrypt_requires_nonzero_nonce(\n        self, message: int, keypair: ElGamalKeyPair\n    ) -> None:\n        self.assertEqual(None, elgamal_encrypt(message, ZERO_MOD_Q, keypair.public_key))\n\n    def test_elgamal_keypair_from_secret_requires_key_greater_than_one(self) -> None:\n        self.assertEqual(None, elgamal_keypair_from_secret(ZERO_MOD_Q))\n        self.assertEqual(None, elgamal_keypair_from_secret(ONE_MOD_Q))\n\n    @given(integers(0, 100), elements_mod_q_no_zero(), elgamal_keypairs())\n    def test_elgamal_encryption_decryption_inverses(\n        self, message: int, nonce: ElementModQ, keypair: ElGamalKeyPair\n    ) -> None:\n        ciphertext = get_optional(elgamal_encrypt(message, nonce, keypair.public_key))\n        plaintext = ciphertext.decrypt(keypair.secret_key)\n\n        self.assertEqual(message, plaintext)\n\n    @given(integers(0, 100), elements_mod_q_no_zero(), elgamal_keypairs())\n    def test_elgamal_encryption_decryption_with_known_nonce_inverses(\n        self, message: int, nonce: ElementModQ, keypair: ElGamalKeyPair\n    ) -> None:\n        ciphertext = get_optional(elgamal_encrypt(message, nonce, keypair.public_key))\n        plaintext = ciphertext.decrypt_known_nonce(keypair.public_key, nonce)\n\n        self.assertEqual(message, plaintext)\n\n    @given(elgamal_keypairs())\n    def test_elgamal_generated_keypairs_are_within_range(\n        self, keypair: ElGamalKeyPair\n    ) -> None:\n        self.assertLess(keypair.public_key, get_large_prime())\n        self.assertLess(keypair.secret_key, get_small_prime())\n        self.assertEqual(g_pow_p(keypair.secret_key), keypair.public_key)\n\n    @given(\n        elgamal_keypairs(),\n        integers(0, 100),\n        elements_mod_q_no_zero(),\n        integers(0, 100),\n        elements_mod_q_no_zero(),\n    )\n    def test_elgamal_add_homomorphic_accumulation_decrypts_successfully(\n        self,\n        keypair: ElGamalKeyPair,\n        m1: int,\n        r1: ElementModQ,\n        m2: int,\n        r2: ElementModQ,\n    ) -> None:\n        c1 = get_optional(elgamal_encrypt(m1, r1, keypair.public_key))\n        c2 = get_optional(elgamal_encrypt(m2, r2, keypair.public_key))\n        c_sum = elgamal_add(c1, c2)\n        total = c_sum.decrypt(keypair.secret_key)\n\n        self.assertEqual(total, m1 + m2)\n\n    def test_elgamal_add_requires_args(self) -> None:\n        self.assertRaises(Exception, elgamal_add)\n\n    @given(elgamal_keypairs())\n    def test_elgamal_keypair_produces_valid_residue(self, keypair) -> None:\n        self.assertTrue(keypair.public_key.is_valid_residue())\n\n    def test_elgamal_keypair_random(self) -> None:\n        # Act\n        random_keypair = elgamal_keypair_random()\n        random_keypair_two = elgamal_keypair_random()\n\n        # Assert\n        self.assertIsNotNone(random_keypair)\n        self.assertIsNotNone(random_keypair.public_key)\n        self.assertIsNotNone(random_keypair.secret_key)\n        self.assertNotEqual(random_keypair, random_keypair_two)\n\n    def test_elgamal_combine_public_keys(self) -> None:\n        # Arrange\n        random_keypair = elgamal_keypair_random()\n        random_keypair_two = elgamal_keypair_random()\n        public_keys = [random_keypair.public_key, random_keypair_two.public_key]\n\n        # Act\n        joint_key = elgamal_combine_public_keys(public_keys)\n\n        # Assert\n        self.assertIsNotNone(joint_key)\n        self.assertNotEqual(joint_key, random_keypair.public_key)\n        self.assertNotEqual(joint_key, random_keypair_two.public_key)\n\n    def test_gmpy2_parallelism_is_safe(self) -> None:\n        \"\"\"\n        Ensures running lots of parallel exponentiations still yields the correct answer.\n        This verifies that nothing incorrect is happening in the GMPY2 library\n        \"\"\"\n\n        # Arrange\n        scheduler = Scheduler()\n        problem_size = 1000\n        random_secret_keys = Nonces(ElementModQ(3))[0:problem_size]\n        log_info(\n            f\"testing GMPY2 powmod parallelism safety (cpus = {scheduler.cpu_count}, problem_size = {problem_size})\"\n        )\n\n        # Act\n        start = timer()\n        keypairs = scheduler.schedule(\n            elgamal_keypair_from_secret,\n            [list([secret_key]) for secret_key in random_secret_keys],\n        )\n        end1 = timer()\n\n        # Assert\n        for keypair in keypairs:\n            self.assertEqual(\n                keypair.public_key,\n                elgamal_keypair_from_secret(keypair.secret_key).public_key,\n            )\n        end2 = timer()\n        scheduler.close()\n        log_info(f\"Parallelism speedup: {(end2 - end1) / (end1 - start):.3f}\")\n\n    def test_hashed_elgamal_encryption(self) -> None:\n        \"\"\"\n        Ensure Hashed ElGamal encrypts and decrypts as expected.\n        \"\"\"\n\n        # Arrange\n        message = ContestData(ContestErrorType.Default)\n        keypair = elgamal_keypair_random()\n        nonce = ONE_MOD_Q\n        seed = ONE_MOD_Q\n\n        # Act\n        padded_message = message.to_bytes()\n        encrypted_message = hashed_elgamal_encrypt(\n            padded_message, nonce, keypair.public_key, seed\n        )\n        decrypted_message = encrypted_message.decrypt(keypair.secret_key, seed)\n        if decrypted_message is not None:\n            unpadded_message = padded_decode(ContestData, decrypted_message)\n\n        # Assert\n        self.assertIsNotNone(encrypted_message)\n        self.assertIsNotNone(decrypted_message)\n        if decrypted_message is not None:\n            self.assertEqual(padded_message, decrypted_message)\n            self.assertEqual(message, unpadded_message)\n"
  },
  {
    "path": "tests/property/test_encrypt.py",
    "content": "from unittest import skip\nfrom unittest.mock import patch\nfrom copy import deepcopy\nfrom datetime import timedelta\nfrom random import Random\nfrom secrets import randbelow\nfrom typing import Tuple\n\nfrom hypothesis import HealthCheck\nfrom hypothesis import given, settings\nfrom hypothesis.strategies import integers\n\n\nfrom tests.base_test_case import BaseTestCase\n\nimport electionguard_tools.factories.ballot_factory as BallotFactory\nimport electionguard_tools.factories.election_factory as ElectionFactory\nfrom electionguard_tools.strategies.elgamal import elgamal_keypairs\nfrom electionguard_tools.strategies.group import elements_mod_q_no_zero\n\nfrom electionguard.constants import get_small_prime\nfrom electionguard.chaum_pedersen import (\n    ConstantChaumPedersenProof,\n    DisjunctiveChaumPedersenProof,\n    make_constant_chaum_pedersen,\n    make_disjunctive_chaum_pedersen,\n)\nfrom electionguard.elgamal import (\n    ElGamalKeyPair,\n    elgamal_keypair_from_secret,\n    elgamal_add,\n)\nfrom electionguard.encrypt import (\n    EncryptionDevice,\n    encrypt_ballot,\n    encrypt_contest,\n    encrypt_selection,\n    selection_from,\n    EncryptionMediator,\n)\nfrom electionguard.group import (\n    ElementModQ,\n    ONE_MOD_Q,\n    TWO_MOD_Q,\n    int_to_q,\n    add_q,\n    TWO_MOD_P,\n    mult_p,\n)\nfrom electionguard.manifest import (\n    ContestDescription,\n    contest_description_with_placeholders_from,\n    generate_placeholder_selections_from,\n    SelectionDescription,\n    VoteVariationType,\n)\n\n\nelection_factory = ElectionFactory.ElectionFactory()\nballot_factory = BallotFactory.BallotFactory()\nSEED = election_factory.get_encryption_device().get_hash()\n\n\nclass TestEncrypt(BaseTestCase):\n    \"\"\"Encryption tests\"\"\"\n\n    def test_encrypt_simple_selection_succeeds(self):\n\n        # Arrange\n        keypair = elgamal_keypair_from_secret(int_to_q(2))\n        nonce = randbelow(get_small_prime())\n        metadata = SelectionDescription(\n            \"some-selection-object-id\", 1, \"some-candidate-id\"\n        )\n        hash_context = metadata.crypto_hash()\n\n        subject = selection_from(metadata)\n        self.assertTrue(subject.is_valid(metadata.object_id))\n\n        # Act\n        result = encrypt_selection(\n            subject,\n            metadata,\n            keypair.public_key,\n            ONE_MOD_Q,\n            nonce,\n            should_verify_proofs=True,\n        )\n\n        # Assert\n        self.assertIsNotNone(result)\n        self.assertIsNotNone(result.ciphertext)\n        self.assertTrue(\n            result.is_valid_encryption(hash_context, keypair.public_key, ONE_MOD_Q)\n        )\n\n    def test_encrypt_simple_selection_malformed_data_fails(self):\n\n        # Arrange\n        keypair = elgamal_keypair_from_secret(int_to_q(2))\n        nonce = randbelow(get_small_prime())\n        metadata = SelectionDescription(\n            \"some-selection-object-id\", 1, \"some-candidate-id\"\n        )\n        hash_context = metadata.crypto_hash()\n\n        subject = selection_from(metadata)\n        self.assertTrue(subject.is_valid(metadata.object_id))\n\n        # Act\n        result = encrypt_selection(\n            subject, metadata, keypair.public_key, ONE_MOD_Q, nonce\n        )\n\n        # tamper with the description_hash\n        malformed_description_hash = deepcopy(result)\n        malformed_description_hash.description_hash = TWO_MOD_Q\n\n        # remove the proof\n        missing_proof = deepcopy(result)\n        missing_proof.proof = None\n\n        # Assert\n        self.assertFalse(\n            malformed_description_hash.is_valid_encryption(\n                hash_context, keypair.public_key, ONE_MOD_Q\n            )\n        )\n        self.assertFalse(\n            missing_proof.is_valid_encryption(\n                hash_context, keypair.public_key, ONE_MOD_Q\n            )\n        )\n\n    @settings(\n        deadline=timedelta(milliseconds=2000),\n        suppress_health_check=[HealthCheck.too_slow],\n        max_examples=10,\n    )\n    @given(\n        ElectionFactory.get_selection_description_well_formed(),\n        elgamal_keypairs(),\n        elements_mod_q_no_zero(),\n        integers(),\n    )\n    def test_encrypt_selection_valid_input_succeeds(\n        self,\n        selection_description: Tuple[str, SelectionDescription],\n        keypair: ElGamalKeyPair,\n        seed: ElementModQ,\n        random_seed: int,\n    ):\n\n        # Arrange\n        _, description = selection_description\n        random = Random(random_seed)\n        subject = ballot_factory.get_random_selection_from(description, random)\n\n        # Act\n        result = encrypt_selection(\n            subject,\n            description,\n            keypair.public_key,\n            ONE_MOD_Q,\n            seed,\n            should_verify_proofs=True,\n        )\n\n        # Assert\n        self.assertIsNotNone(result)\n        self.assertIsNotNone(result.ciphertext)\n        self.assertTrue(\n            result.is_valid_encryption(\n                description.crypto_hash(), keypair.public_key, ONE_MOD_Q\n            )\n        )\n\n    @settings(\n        deadline=timedelta(milliseconds=2000),\n        suppress_health_check=[HealthCheck.too_slow],\n        max_examples=10,\n    )\n    @given(\n        ElectionFactory.get_selection_description_well_formed(),\n        elgamal_keypairs(),\n        elements_mod_q_no_zero(),\n        integers(),\n    )\n    def test_encrypt_selection_valid_input_tampered_encryption_fails(\n        self,\n        selection_description: Tuple[str, SelectionDescription],\n        keypair: ElGamalKeyPair,\n        seed: ElementModQ,\n        random_seed: int,\n    ):\n\n        # Arrange\n        _, description = selection_description\n        random = Random(random_seed)\n        subject = ballot_factory.get_random_selection_from(description, random)\n\n        # Act\n        result = encrypt_selection(\n            subject,\n            description,\n            keypair.public_key,\n            ONE_MOD_Q,\n            seed,\n            should_verify_proofs=False,\n        )\n        self.assertTrue(\n            result.is_valid_encryption(\n                description.crypto_hash(), keypair.public_key, ONE_MOD_Q\n            )\n        )\n\n        # tamper with the encryption\n        malformed_encryption = deepcopy(result)\n        malformed_encryption.ciphertext.pad = mult_p(result.ciphertext.pad, TWO_MOD_P)\n\n        # tamper with the proof\n        malformed_proof = deepcopy(result)\n        altered_a0 = mult_p(result.proof.proof_zero_pad, TWO_MOD_P)\n        malformed_disjunctive = DisjunctiveChaumPedersenProof(\n            altered_a0,\n            malformed_proof.proof.proof_zero_data,\n            malformed_proof.proof.proof_one_pad,\n            malformed_proof.proof.proof_one_data,\n            malformed_proof.proof.proof_zero_challenge,\n            malformed_proof.proof.proof_one_challenge,\n            malformed_proof.proof.challenge,\n            malformed_proof.proof.proof_zero_response,\n            malformed_proof.proof.proof_one_response,\n        )\n        malformed_proof.proof = malformed_disjunctive\n\n        # Assert\n        self.assertFalse(\n            malformed_encryption.is_valid_encryption(\n                description.crypto_hash(), keypair.public_key, ONE_MOD_Q\n            )\n        )\n        self.assertFalse(\n            malformed_proof.is_valid_encryption(\n                description.crypto_hash(), keypair.public_key, ONE_MOD_Q\n            )\n        )\n\n    @settings(\n        deadline=timedelta(milliseconds=4000),\n        suppress_health_check=[HealthCheck.too_slow],\n        max_examples=10,\n    )\n    @given(\n        ElectionFactory.get_contest_description_well_formed(),\n        elgamal_keypairs(),\n        elements_mod_q_no_zero(),\n        integers(),\n    )\n    def test_encrypt_contest_valid_input_succeeds(\n        self,\n        contest_description: Tuple[str, ContestDescription],\n        keypair: ElGamalKeyPair,\n        nonce_seed: ElementModQ,\n        random_seed: int,\n    ):\n\n        # Arrange\n        _id, description = contest_description\n        random = Random(random_seed)\n        subject = ballot_factory.get_random_contest_from(description, random)\n\n        # Act\n        result = encrypt_contest(\n            subject,\n            description,\n            keypair.public_key,\n            ONE_MOD_Q,\n            nonce_seed,\n            should_verify_proofs=True,\n        )\n\n        # Assert\n        self.assertIsNotNone(result)\n        self.assertTrue(\n            result.is_valid_encryption(\n                description.crypto_hash(), keypair.public_key, ONE_MOD_Q\n            )\n        )\n\n        # The encrypted contest should include an entry for each possible selection\n        # and placeholders for each seat\n        expected_entries = (\n            len(description.ballot_selections) + description.number_elected\n        )\n        self.assertEqual(len(result.ballot_selections), expected_entries)\n\n    @settings(\n        deadline=timedelta(milliseconds=2000),\n        suppress_health_check=[HealthCheck.too_slow],\n        max_examples=10,\n    )\n    @given(\n        ElectionFactory.get_contest_description_well_formed(),\n        elgamal_keypairs(),\n        elements_mod_q_no_zero(),\n        integers(),\n    )\n    def test_encrypt_contest_valid_input_tampered_proof_fails(\n        self,\n        contest_description: Tuple[str, ContestDescription],\n        keypair: ElGamalKeyPair,\n        nonce_seed: ElementModQ,\n        random_seed: int,\n    ):\n\n        # Arrange\n        _id, description = contest_description\n        random = Random(random_seed)\n        subject = ballot_factory.get_random_contest_from(description, random)\n\n        # Act\n        result = encrypt_contest(\n            subject, description, keypair.public_key, ONE_MOD_Q, nonce_seed\n        )\n        self.assertTrue(\n            result.is_valid_encryption(\n                description.crypto_hash(), keypair.public_key, ONE_MOD_Q\n            )\n        )\n\n        # tamper with the proof\n        malformed_proof = deepcopy(result)\n        altered_a = mult_p(result.proof.pad, TWO_MOD_P)\n        malformed_disjunctive = ConstantChaumPedersenProof(\n            altered_a,\n            malformed_proof.proof.data,\n            malformed_proof.proof.challenge,\n            malformed_proof.proof.response,\n            malformed_proof.proof.constant,\n        )\n        malformed_proof.proof = malformed_disjunctive\n\n        # remove the proof\n        missing_proof = deepcopy(result)\n        missing_proof.proof = None\n\n        # Assert\n        self.assertFalse(\n            malformed_proof.is_valid_encryption(\n                description.crypto_hash(), keypair.public_key, ONE_MOD_Q\n            )\n        )\n        self.assertFalse(\n            missing_proof.is_valid_encryption(\n                description.crypto_hash(), keypair.public_key, ONE_MOD_Q\n            )\n        )\n\n    @skip(\"runs forever\")\n    @settings(\n        deadline=timedelta(milliseconds=2000),\n        suppress_health_check=[HealthCheck.too_slow],\n        max_examples=10,\n    )\n    @given(\n        ElectionFactory.get_contest_description_well_formed(),\n        elgamal_keypairs(),\n        elements_mod_q_no_zero(),\n        integers(1, 6),\n        integers(),\n    )\n    def test_encrypt_contest_overvote_fails(\n        self,\n        contest_description: Tuple[str, ContestDescription],\n        keypair: ElGamalKeyPair,\n        seed: ElementModQ,\n        overvotes: int,\n        random_seed: int,\n    ):\n        # Arrange\n        _id, description = contest_description\n        random = Random(random_seed)\n        subject = ballot_factory.get_random_contest_from(description, random)\n\n        for _i in range(overvotes):\n            overvote = ballot_factory.get_random_selection_from(\n                description.ballot_selections[0], random\n            )\n            subject.ballot_selections.append(overvote)\n\n        # Act\n        result = encrypt_contest(\n            subject, description, keypair.public_key, ONE_MOD_Q, seed\n        )\n\n        # Assert\n        self.assertIsNone(result)\n\n    def test_encrypt_contest_manually_formed_contest_description_valid_succeeds(self):\n        description = ContestDescription(\n            object_id=\"0@A.com-contest\",\n            electoral_district_id=\"0@A.com-gp-unit\",\n            sequence_order=1,\n            vote_variation=VoteVariationType.n_of_m,\n            number_elected=1,\n            votes_allowed=1,\n            name=\"\",\n            ballot_selections=[\n                SelectionDescription(\n                    \"0@A.com-selection\",\n                    0,\n                    \"0@A.com\",\n                ),\n                SelectionDescription(\"0@B.com-selection\", 1, \"0@B.com\"),\n            ],\n            ballot_title=None,\n            ballot_subtitle=None,\n        )\n\n        keypair = elgamal_keypair_from_secret(TWO_MOD_Q)\n        seed = ONE_MOD_Q\n\n        ####################\n        data = ballot_factory.get_random_contest_from(description, Random(0))\n\n        placeholders = generate_placeholder_selections_from(\n            description, description.number_elected\n        )\n        description_with_placeholders = contest_description_with_placeholders_from(\n            description, placeholders\n        )\n\n        # Act\n        subject = encrypt_contest(\n            data,\n            description_with_placeholders,\n            keypair.public_key,\n            ONE_MOD_Q,\n            seed,\n            should_verify_proofs=True,\n        )\n        self.assertIsNotNone(subject)\n\n    def test_encrypt_contest_duplicate_selection_object_ids_fails(self):\n        \"\"\"\n        This is an example test of a failing test where the contest description\n        is malformed\n        \"\"\"\n\n        description = ContestDescription(\n            object_id=\"0@A.com-contest\",\n            electoral_district_id=\"0@A.com-gp-unit\",\n            sequence_order=1,\n            vote_variation=VoteVariationType.n_of_m,\n            number_elected=1,\n            votes_allowed=1,\n            name=\"\",\n            ballot_selections=[\n                SelectionDescription(\n                    \"0@A.com-selection\",\n                    0,\n                    \"0@A.com\",\n                ),\n                # Note the selection description is the same as the first sequence element\n                SelectionDescription(\n                    \"0@A.com-selection\",\n                    1,\n                    \"0@A.com\",\n                ),\n            ],\n        )\n\n        keypair = elgamal_keypair_from_secret(TWO_MOD_Q)\n        seed = ONE_MOD_Q\n\n        # Bypass checking the validity of the description\n        data = ballot_factory.get_random_contest_from(\n            description, Random(0), suppress_validity_check=True\n        )\n\n        placeholders = generate_placeholder_selections_from(\n            description, description.number_elected\n        )\n        description_with_placeholders = contest_description_with_placeholders_from(\n            description, placeholders\n        )\n\n        # Act\n        subject = encrypt_contest(\n            data, description_with_placeholders, keypair.public_key, ONE_MOD_Q, seed\n        )\n        self.assertIsNone(subject)\n\n    def test_encrypt_ballot_simple_succeeds(self):\n\n        # Arrange\n        keypair = elgamal_keypair_from_secret(int_to_q(2))\n        manifest = election_factory.get_fake_manifest()\n        internal_manifest, context = election_factory.get_fake_ciphertext_election(\n            manifest, keypair.public_key\n        )\n        nonce_seed = TWO_MOD_Q\n\n        # TODO: Ballot Factory\n        subject = election_factory.get_fake_ballot(internal_manifest)\n        self.assertTrue(subject.is_valid(internal_manifest.ballot_styles[0].object_id))\n\n        # Act\n        result = encrypt_ballot(subject, internal_manifest, context, SEED)\n        result_from_seed = encrypt_ballot(\n            subject,\n            internal_manifest,\n            context,\n            SEED,\n            nonce_seed,\n            should_verify_proofs=True,\n        )\n\n        # Assert\n        self.assertIsNotNone(result)\n        self.assertIsNotNone(result.code)\n        self.assertIsNotNone(result_from_seed)\n        self.assertTrue(\n            result.is_valid_encryption(\n                internal_manifest.manifest_hash,\n                keypair.public_key,\n                context.crypto_extended_base_hash,\n            )\n        )\n        self.assertTrue(\n            result_from_seed.is_valid_encryption(\n                internal_manifest.manifest_hash,\n                keypair.public_key,\n                context.crypto_extended_base_hash,\n            )\n        )\n\n    def test_encrypt_ballot_with_composer_succeeds(self):\n        # Arrange\n        keypair = elgamal_keypair_from_secret(int_to_q(2))\n        manifest = election_factory.get_fake_manifest()\n        internal_manifest, context = election_factory.get_fake_ciphertext_election(\n            manifest, keypair.public_key\n        )\n\n        data = election_factory.get_fake_ballot(internal_manifest)\n        self.assertTrue(data.is_valid(internal_manifest.ballot_styles[0].object_id))\n\n        device = election_factory.get_encryption_device()\n        subject = EncryptionMediator(internal_manifest, context, device)\n\n        # Act\n        result = subject.encrypt(data)\n\n        # Assert\n        self.assertIsNotNone(result)\n        self.assertTrue(\n            result.is_valid_encryption(\n                internal_manifest.manifest_hash,\n                keypair.public_key,\n                context.crypto_extended_base_hash,\n            )\n        )\n\n    def test_encrypt_simple_ballot_from_file_with_composer_succeeds(self):\n        # Arrange\n        keypair = elgamal_keypair_from_secret(int_to_q(2))\n        manifest = election_factory.get_simple_manifest_from_file()\n        internal_manifest, context = election_factory.get_fake_ciphertext_election(\n            manifest, keypair.public_key\n        )\n\n        data = ballot_factory.get_simple_ballot_from_file()\n        self.assertTrue(data.is_valid(internal_manifest.ballot_styles[0].object_id))\n\n        device = EncryptionDevice(12345, 23456, 34567, \"Location\")\n        subject = EncryptionMediator(internal_manifest, context, device)\n\n        # Act\n        result = subject.encrypt(data)\n\n        # Assert\n        self.assertIsNotNone(result)\n        self.assertEqual(data.object_id, result.object_id)\n        self.assertTrue(\n            result.is_valid_encryption(\n                internal_manifest.manifest_hash,\n                keypair.public_key,\n                context.crypto_extended_base_hash,\n            )\n        )\n\n    def test_encrypt_simple_ballot_from_files_succeeds(self) -> None:\n        # Arrange\n        keypair = elgamal_keypair_from_secret(int_to_q(2))\n        manifest = election_factory.get_simple_manifest_from_file()\n        internal_manifest, context = election_factory.get_fake_ciphertext_election(\n            manifest, keypair.public_key\n        )\n\n        device = EncryptionDevice(12345, 23456, 34567, \"Location\")\n        ballot = ballot_factory.get_simple_ballot_from_file()\n        self.assertTrue(ballot.is_valid(internal_manifest.ballot_styles[0].object_id))\n\n        # Act\n\n        ciphertext = encrypt_ballot(\n            ballot,\n            internal_manifest,\n            context,\n            device.get_hash(),\n            TWO_MOD_Q,\n            should_verify_proofs=True,\n        )\n\n        # Assert\n        self.assertIsNotNone(ciphertext)\n        self.assertEqual(ballot.object_id, ciphertext.object_id)\n        self.assertTrue(\n            ciphertext.is_valid_encryption(\n                internal_manifest.manifest_hash,\n                keypair.public_key,\n                context.crypto_extended_base_hash,\n            )\n        )\n\n    @settings(\n        deadline=timedelta(milliseconds=4000),\n        suppress_health_check=[HealthCheck.too_slow],\n        max_examples=10,\n    )\n    @given(elgamal_keypairs())\n    def test_encrypt_ballot_with_derivative_nonces_regenerates_valid_proofs(\n        self, keypair: ElGamalKeyPair\n    ):\n        \"\"\"\n        This test verifies that we can regenerate the contest and selection proofs from the cached nonce values\n        \"\"\"\n\n        # TODO: Hypothesis test instead\n\n        # Arrange\n        manifest = election_factory.get_simple_manifest_from_file()\n        internal_manifest, context = election_factory.get_fake_ciphertext_election(\n            manifest, keypair.public_key\n        )\n\n        data = ballot_factory.get_simple_ballot_from_file()\n        self.assertTrue(data.is_valid(internal_manifest.ballot_styles[0].object_id))\n\n        device = election_factory.get_encryption_device()\n        subject = EncryptionMediator(internal_manifest, context, device)\n\n        # Act\n        result = subject.encrypt(data)\n        self.assertTrue(\n            result.is_valid_encryption(\n                internal_manifest.manifest_hash,\n                keypair.public_key,\n                context.crypto_extended_base_hash,\n            )\n        )\n\n        # Assert\n        for contest in result.contests:\n            # Find the contest description\n            contest_description = list(\n                filter(\n                    lambda i, c=contest: i.object_id == c.object_id,\n                    internal_manifest.contests,\n                )\n            )[0]\n\n            # Homomorpically accumulate the selection encryptions\n            elgamal_accumulation = elgamal_add(\n                *[selection.ciphertext for selection in contest.ballot_selections]\n            )\n            # accumulate the selection nonce's\n            aggregate_nonce = add_q(\n                *[selection.nonce for selection in contest.ballot_selections]\n            )\n\n            regenerated_constant = make_constant_chaum_pedersen(\n                elgamal_accumulation,\n                contest_description.number_elected,\n                aggregate_nonce,\n                keypair.public_key,\n                add_q(contest.nonce, TWO_MOD_Q),\n                context.crypto_extended_base_hash,\n            )\n\n            self.assertTrue(\n                regenerated_constant.is_valid(\n                    elgamal_accumulation,\n                    keypair.public_key,\n                    context.crypto_extended_base_hash,\n                )\n            )\n\n            for selection in contest.ballot_selections:\n                # Since we know the nonce, we can decrypt the plaintext\n                representation = selection.ciphertext.decrypt_known_nonce(\n                    keypair.public_key, selection.nonce\n                )\n\n                # one could also decrypt with the secret key:\n                # representation = selection.message.decrypt(keypair.secret_key)\n\n                regenerated_disjuctive = make_disjunctive_chaum_pedersen(\n                    selection.ciphertext,\n                    selection.nonce,\n                    keypair.public_key,\n                    context.crypto_extended_base_hash,\n                    add_q(selection.nonce, TWO_MOD_Q),\n                    representation,\n                )\n\n                self.assertTrue(\n                    regenerated_disjuctive.is_valid(\n                        selection.ciphertext,\n                        keypair.public_key,\n                        context.crypto_extended_base_hash,\n                    )\n                )\n\n    def test_encrypt_ballot_with_verify_proofs_false_passed_on(self):\n        \"\"\"\n        This test is for https://github.com/microsoft/electionguard-python/issues/459\n        \"\"\"\n        with (\n            patch(\"electionguard.encrypt.encrypt_contest\") as patched_contest,\n            patch(\"electionguard.encrypt.encrypt_selection\") as patched_selection,\n        ):\n            # Arrange\n            keypair = elgamal_keypair_from_secret(int_to_q(2))\n            manifest = election_factory.get_fake_manifest()\n            internal_manifest, context = election_factory.get_fake_ciphertext_election(\n                manifest, keypair.public_key\n            )\n            subject = election_factory.get_fake_ballot(internal_manifest)\n            self.assertTrue(\n                subject.is_valid(internal_manifest.ballot_styles[0].object_id)\n            )\n\n            patched_contest.side_effect = encrypt_contest\n            patched_selection.side_effect = encrypt_selection\n\n            # Act\n            encrypt_ballot(\n                subject, internal_manifest, context, SEED, should_verify_proofs=False\n            )\n\n            # Assert\n            for call in patched_contest.call_args_list:\n                self.assertFalse(call.kwargs.get(\"should_verify_proofs\"))\n            for call in patched_selection.call_args_list:\n                self.assertFalse(call.kwargs.get(\"should_verify_proofs\"))\n"
  },
  {
    "path": "tests/property/test_encrypt_hypotheses.py",
    "content": "from datetime import timedelta\nfrom typing import List, Dict\n\nfrom hypothesis import given, HealthCheck, settings, Phase\nfrom hypothesis.strategies import integers\n\nfrom tests.base_test_case import BaseTestCase\n\nfrom electionguard.ballot import CiphertextBallot\nfrom electionguard.decrypt_with_secrets import decrypt_ballot_with_secret\nfrom electionguard.elgamal import ElGamalCiphertext, elgamal_encrypt, elgamal_add\nfrom electionguard.encrypt import encrypt_ballot\nfrom electionguard.group import ElementModQ\nfrom electionguard.manifest import Manifest\nfrom electionguard.nonces import Nonces\nfrom electionguard_tools.strategies.election import (\n    election_descriptions,\n    elections_and_ballots,\n    ElectionsAndBallotsTupleType,\n)\nfrom electionguard_tools.factories.election_factory import ElectionFactory\nfrom electionguard_tools.strategies.group import elements_mod_q\nfrom electionguard_tools.helpers.tally_accumulate import accumulate_plaintext_ballots\n\n\nSEED = ElectionFactory.get_encryption_device().get_hash()\n\n\nclass TestElections(BaseTestCase):\n    \"\"\"Election hypothesis encryption tests\"\"\"\n\n    @settings(\n        deadline=timedelta(milliseconds=2000),\n        suppress_health_check=[HealthCheck.too_slow],\n        max_examples=10,\n    )\n    @given(election_descriptions())\n    def test_generators_yield_valid_output(self, manifest: Manifest):\n        \"\"\"\n        Tests that our Hypothesis election strategies generate \"valid\" output, also exercises the full stack\n        of `is_valid` methods.\n        \"\"\"\n\n        self.assertTrue(manifest.is_valid())\n\n    @settings(\n        deadline=timedelta(milliseconds=10000),\n        suppress_health_check=[HealthCheck.too_slow],\n        max_examples=5,\n        # disabling the \"shrink\" phase, because it runs very slowly\n        phases=[Phase.explicit, Phase.reuse, Phase.generate, Phase.target],\n    )\n    @given(\n        integers(1, 3).flatmap(lambda n: elections_and_ballots(n)),\n        elements_mod_q(),\n    )\n    def test_accumulation_encryption_decryption(\n        self,\n        everything: ElectionsAndBallotsTupleType,\n        nonce: ElementModQ,\n    ):\n        \"\"\"\n        Tests that decryption is the inverse of encryption over arbitrarily generated elections and ballots.\n\n        This test uses an abitrarily generated dataset with a single public-private keypair for the election\n        encryption context.  It also manually verifies that homomorphic accumulation works as expected.\n        \"\"\"\n        # Arrange\n        (\n            _election_description,\n            internal_manifest,\n            ballots,\n            secret_key,\n            context,\n        ) = everything\n\n        # Tally the plaintext ballots for comparison later\n        plaintext_tallies = accumulate_plaintext_ballots(ballots)\n        num_ballots = len(ballots)\n        num_contests = len(internal_manifest.contests)\n        zero_nonce, *nonces = Nonces(nonce)[: num_ballots + 1]\n        self.assertEqual(len(nonces), num_ballots)\n        self.assertTrue(len(internal_manifest.contests) > 0)\n\n        # Generate a valid encryption of zero\n        encrypted_zero = elgamal_encrypt(0, zero_nonce, context.elgamal_public_key)\n\n        # Act\n        encrypted_ballots = []\n\n        # encrypt each ballot\n        for i in range(num_ballots):\n            encrypted_ballot = encrypt_ballot(\n                ballots[i],\n                internal_manifest,\n                context,\n                SEED,\n                nonces[i],\n                should_verify_proofs=True,\n            )\n            encrypted_ballots.append(encrypted_ballot)\n\n            # sanity check the encryption\n            self.assertIsNotNone(encrypted_ballot)\n            self.assertEqual(num_contests, len(encrypted_ballot.contests))\n\n            # decrypt the ballot with secret and verify it matches the plaintext\n            decrypted_ballot = decrypt_ballot_with_secret(\n                encrypted_ballot,\n                internal_manifest,\n                context.crypto_extended_base_hash,\n                context.elgamal_public_key,\n                secret_key,\n            )\n            self.assertEqual(ballots[i], decrypted_ballot)\n\n        # homomorphically accumulate the encrypted ballot representations\n        encrypted_tallies = _accumulate_encrypted_ballots(\n            encrypted_zero, encrypted_ballots\n        )\n\n        decrypted_tallies = {}\n        for object_id, encrypted_tally in encrypted_tallies.items():\n            decrypted_tallies[object_id] = encrypted_tally.decrypt(secret_key)\n\n        # loop through the contest descriptions and verify\n        # the decrypted tallies match the plaintext tallies\n        for contest in internal_manifest.contests:\n            # Sanity check the generated data\n            self.assertTrue(len(contest.ballot_selections) > 0)\n            self.assertTrue(len(contest.placeholder_selections) > 0)\n\n            decrypted_selection_tallies = [\n                decrypted_tallies[selection.object_id]\n                for selection in contest.ballot_selections\n            ]\n            decrypted_placeholder_tallies = [\n                decrypted_tallies[placeholder.object_id]\n                for placeholder in contest.placeholder_selections\n            ]\n            plaintext_tally_values = [\n                plaintext_tallies[selection.object_id]\n                for selection in contest.ballot_selections\n            ]\n\n            # verify the plaintext tallies match the decrypted tallies\n            self.assertEqual(decrypted_selection_tallies, plaintext_tally_values)\n\n            # validate the right number of selections including placeholders across all ballots\n            self.assertEqual(\n                contest.number_elected * num_ballots,\n                sum(decrypted_selection_tallies) + sum(decrypted_placeholder_tallies),\n            )\n\n\ndef _accumulate_encrypted_ballots(\n    encrypted_zero: ElGamalCiphertext, ballots: List[CiphertextBallot]\n) -> Dict[str, ElGamalCiphertext]:\n    \"\"\"\n    Internal helper function for testing: takes a list of encrypted ballots as input,\n    digs into all of the individual selections and then accumulates them, using\n    their `object_id` fields as keys. This function only knows what to do with\n    `n_of_m` elections. It's not a general-purpose tallying mechanism for other\n    election types.\n\n    Note that the output will include both \"normal\" and \"placeholder\" selections.\n\n    :param encrypted_zero: an encrypted zero, used for the accumulation\n    :param ballots: a list of encrypted ballots\n    :return: a dict from selection object_id's to `ElGamalCiphertext` totals\n    \"\"\"\n    tally: Dict[str, ElGamalCiphertext] = {}\n    for ballot in ballots:\n        for contest in ballot.contests:\n            for selection in contest.ballot_selections:\n                desc_id = (\n                    selection.object_id\n                )  # this should be the same as in the PlaintextBallot!\n                if desc_id not in tally:\n                    tally[desc_id] = encrypted_zero\n                tally[desc_id] = elgamal_add(tally[desc_id], selection.ciphertext)\n    return tally\n"
  },
  {
    "path": "tests/property/test_group.py",
    "content": "from typing import Optional\n\nfrom hypothesis import given\n\nfrom tests.base_test_case import BaseTestCase\n\nfrom electionguard.constants import (\n    get_small_prime,\n    get_large_prime,\n    get_generator,\n    get_cofactor,\n)\nfrom electionguard.group import (\n    ElementModP,\n    ElementModQ,\n    a_minus_b_q,\n    mult_inv_p,\n    ONE_MOD_P,\n    mult_p,\n    ZERO_MOD_P,\n    ONE_MOD_Q,\n    g_pow_p,\n    ZERO_MOD_Q,\n    int_to_p,\n    int_to_q,\n    add_q,\n    div_q,\n    div_p,\n    a_plus_bc_q,\n)\nfrom electionguard.utils import (\n    flatmap_optional,\n    get_or_else_optional,\n    match_optional,\n    get_optional,\n)\nfrom electionguard_tools.strategies.group import (\n    elements_mod_p_no_zero,\n    elements_mod_p,\n    elements_mod_q,\n    elements_mod_q_no_zero,\n)\n\n\nclass TestEquality(BaseTestCase):\n    \"\"\"Math equality tests\"\"\"\n\n    @given(elements_mod_q(), elements_mod_q())\n    def test_p_not_equal_to_q(self, q: ElementModQ, q2: ElementModQ) -> None:\n        i = int(q)\n        i2 = int(q2)\n        p = ElementModP(q)\n        p2 = ElementModP(q2)\n\n        # same value should imply they're equal\n        self.assertEqual(p, q)\n        self.assertEqual(q, p)\n        self.assertEqual(p, i)\n        self.assertEqual(q, i)\n\n        if q != q2:\n            # these are genuinely different numbers\n            self.assertNotEqual(q, q2)\n            self.assertNotEqual(p, p2)\n            self.assertNotEqual(q, p2)\n            self.assertNotEqual(p, q2)\n            self.assertNotEqual(q, i2)\n            self.assertNotEqual(p, i2)\n            self.assertNotEqual(q2, i)\n            self.assertNotEqual(p2, i)\n\n        # of course, we're going to make sure that a number is equal to itself\n        self.assertEqual(p, p)\n        self.assertEqual(q, q)\n\n\nclass TestModularArithmetic(BaseTestCase):\n    \"\"\"Math Modular Arithmetic tests\"\"\"\n\n    @given(elements_mod_q())\n    def test_add_q(self, q: ElementModQ) -> None:\n        as_int = add_q(q, 1)\n        as_elem = add_q(q, ElementModQ(1))\n        self.assertEqual(as_int, as_elem)\n\n    @given(elements_mod_q())\n    def test_a_plus_bc_q(self, q: ElementModQ) -> None:\n        as_int = a_plus_bc_q(q, 1, 1)\n        as_elem = a_plus_bc_q(q, ElementModQ(1), ElementModQ(1))\n        self.assertEqual(as_int, as_elem)\n\n    @given(elements_mod_q())\n    def test_a_minus_b_q(self, q: ElementModQ) -> None:\n        as_int = a_minus_b_q(q, 1)\n        as_elem = a_minus_b_q(q, ElementModQ(1))\n        self.assertEqual(as_int, as_elem)\n\n    @given(elements_mod_q())\n    def test_div_q(self, q: ElementModQ) -> None:\n        as_int = div_q(q, 1)\n        as_elem = div_q(q, ElementModQ(1))\n        self.assertEqual(as_int, as_elem)\n\n    @given(elements_mod_p())\n    def test_div_p(self, p: ElementModQ) -> None:\n        as_int = div_p(p, 1)\n        as_elem = div_p(p, ElementModP(1))\n        self.assertEqual(as_int, as_elem)\n\n    def test_no_mult_inv_of_zero(self) -> None:\n        self.assertRaises(Exception, mult_inv_p, ZERO_MOD_P)\n\n    @given(elements_mod_p_no_zero())\n    def test_mult_inverses(self, elem: ElementModP) -> None:\n        inv = mult_inv_p(elem)\n        self.assertEqual(mult_p(elem, inv), ONE_MOD_P)\n\n    @given(elements_mod_p())\n    def test_mult_identity(self, elem: ElementModP) -> None:\n        self.assertEqual(elem, mult_p(elem))\n\n    def test_mult_noargs(self) -> None:\n        self.assertEqual(ONE_MOD_P, mult_p())\n\n    def test_add_noargs(self) -> None:\n        self.assertEqual(ZERO_MOD_Q, add_q())\n\n    def test_properties_for_constants(self) -> None:\n        self.assertNotEqual(get_generator(), 1)\n        self.assertEqual(\n            (get_cofactor() * get_small_prime()) % get_large_prime(),\n            get_large_prime() - 1,\n        )\n        self.assertLess(get_small_prime(), get_large_prime())\n        self.assertLess(get_generator(), get_large_prime())\n        self.assertLess(get_cofactor(), get_large_prime())\n\n    def test_simple_powers(self) -> None:\n        gp = int_to_p(get_generator())\n        self.assertEqual(gp, g_pow_p(ONE_MOD_Q))\n        self.assertEqual(ONE_MOD_P, g_pow_p(ZERO_MOD_Q))\n\n    @given(elements_mod_q())\n    def test_in_bounds_q(self, q: ElementModQ) -> None:\n        self.assertTrue(q.is_in_bounds())\n        too_big = q.value + get_small_prime()\n        too_small = q.value - get_small_prime()\n        self.assertFalse(ElementModQ(too_big, False).is_in_bounds())\n        self.assertFalse(ElementModQ(too_small, False).is_in_bounds())\n        self.assertEqual(None, int_to_q(too_big))\n        self.assertEqual(None, int_to_q(too_small))\n        with self.assertRaises(OverflowError):\n            ElementModQ(too_big)\n        with self.assertRaises(OverflowError):\n            ElementModQ(too_small)\n\n    @given(elements_mod_p())\n    def test_in_bounds_p(self, p: ElementModP) -> None:\n        self.assertTrue(p.is_in_bounds())\n        too_big = p.value + get_large_prime()\n        too_small = p.value - get_large_prime()\n        self.assertFalse(ElementModP(too_big, False).is_in_bounds())\n        self.assertFalse(ElementModP(too_small, False).is_in_bounds())\n        self.assertEqual(None, int_to_p(too_big))\n        self.assertEqual(None, int_to_p(too_small))\n        with self.assertRaises(OverflowError):\n            ElementModP(too_big)\n        with self.assertRaises(OverflowError):\n            ElementModP(too_small)\n\n    @given(elements_mod_q_no_zero())\n    def test_in_bounds_q_no_zero(self, q: ElementModQ):\n        self.assertTrue(q.is_in_bounds_no_zero())\n        self.assertFalse(ZERO_MOD_Q.is_in_bounds_no_zero())\n        self.assertFalse(\n            ElementModQ(q.value + get_small_prime(), False).is_in_bounds_no_zero()\n        )\n        self.assertFalse(\n            ElementModQ(q.value - get_small_prime(), False).is_in_bounds_no_zero()\n        )\n\n    @given(elements_mod_p_no_zero())\n    def test_in_bounds_p_no_zero(self, p: ElementModP) -> None:\n        self.assertTrue(p.is_in_bounds_no_zero())\n        self.assertFalse(ZERO_MOD_P.is_in_bounds_no_zero())\n        self.assertFalse(\n            ElementModP(p.value + get_large_prime(), False).is_in_bounds_no_zero()\n        )\n        self.assertFalse(\n            ElementModP(p.value - get_large_prime(), False).is_in_bounds_no_zero()\n        )\n\n    @given(elements_mod_q())\n    def test_large_values_rejected_by_int_to_q(self, q: ElementModQ) -> None:\n        oversize = q.value + get_small_prime()\n        self.assertEqual(None, int_to_q(oversize))\n\n\nclass TestOptionalFunctions(BaseTestCase):\n    \"\"\"Math Optional Functions tests\"\"\"\n\n    def test_unwrap(self) -> None:\n        good: Optional[int] = 3\n        bad: Optional[int] = None\n\n        self.assertEqual(get_optional(good), 3)\n        self.assertRaises(Exception, get_optional, bad)\n\n    def test_match(self) -> None:\n        good: Optional[int] = 3\n        bad: Optional[int] = None\n\n        self.assertEqual(5, match_optional(good, lambda: 1, lambda x: x + 2))\n        self.assertEqual(1, match_optional(bad, lambda: 1, lambda x: x + 2))\n\n    def test_get_or_else(self) -> None:\n        good: Optional[int] = 3\n        bad: Optional[int] = None\n\n        self.assertEqual(3, get_or_else_optional(good, 5))\n        self.assertEqual(5, get_or_else_optional(bad, 5))\n\n    def test_flatmap(self) -> None:\n        good: Optional[int] = 3\n        bad: Optional[int] = None\n\n        self.assertEqual(5, get_optional(flatmap_optional(good, lambda x: x + 2)))\n        self.assertIsNone(flatmap_optional(bad, lambda x: x + 2))\n"
  },
  {
    "path": "tests/property/test_hash.py",
    "content": "from typing import List, Optional\n\nfrom hypothesis import given\nfrom hypothesis.strategies import integers\n\n\nfrom tests.base_test_case import BaseTestCase\n\nfrom electionguard.big_integer import BigInteger\nfrom electionguard.group import ElementModP, ElementModQ\nfrom electionguard.hash import hash_elems\nfrom electionguard_tools.strategies.group import elements_mod_p, elements_mod_q\n\n\nclass TestHash(BaseTestCase):\n    \"\"\"Hash tests\"\"\"\n\n    @given(elements_mod_p(), elements_mod_q())\n    def test_same_answer_twice_in_a_row(self, a: ElementModQ, b: ElementModQ):\n        # if this doesn't work, then our hash function isn't a function\n        h1 = hash_elems(a, b)\n        h2 = hash_elems(a, b)\n        self.assertEqual(h1, h2)\n\n    @given(elements_mod_q(), elements_mod_q())\n    def test_basic_hash_properties(self, a: ElementModQ, b: ElementModQ):\n        ha = hash_elems(a)\n        hb = hash_elems(b)\n        if a == b:\n            self.assertEqual(ha, hb)\n        if ha != hb:\n            self.assertNotEqual(a, b)\n\n    @given(elements_mod_p(), integers(min_value=0, max_value=10))\n    def test_hash_of_big_integer_with_leading_zero_bytes(\n        self, input: ElementModP, multiplier: int\n    ) -> None:\n        \"\"\"Test hashing of larger integers with leading zero bytes\"\"\"\n\n        # Arrange.\n        zero_byte = \"00\"\n        input_hash = hash_elems(input)\n        leading_zeroes = zero_byte * multiplier + input.to_hex()\n\n        # Act.\n        leading_zeroes_big_int = BigInteger(leading_zeroes)\n        leading_zeroes_hash = hash_elems(leading_zeroes_big_int)\n\n        # Assert.\n        self.assertEqual(input, leading_zeroes_big_int)\n        self.assertEqual(input_hash, leading_zeroes_hash)\n\n    @given(elements_mod_p())\n    def test_hash_of_big_integer_with_single_leading_zero(\n        self, input: ElementModP\n    ) -> None:\n        \"\"\"Test hashing of big integer with a single leading zero creating an invalid hex byte reprsentation.\"\"\"\n\n        # Arrange.\n        invalid_hex = \"0\" + input.to_hex()\n        input_hash = hash_elems(input)\n\n        # Act.\n        invalid_hex_big_int = BigInteger(invalid_hex)\n        invalid_hex_hash = hash_elems(invalid_hex_big_int)\n\n        # Assert.\n        self.assertEqual(input, invalid_hex_big_int)\n        self.assertEqual(input_hash, invalid_hex_hash)\n\n    def test_hash_for_zero_number_is_zero_string(self):\n        self.assertEqual(hash_elems(0), hash_elems(\"0\"))\n\n    def test_hash_for_non_zero_number_string_same_as_explicit_number(self):\n        self.assertEqual(hash_elems(1), hash_elems(\"1\"))\n\n    def test_different_strings_casing_not_the_same_hash(self):\n        self.assertNotEqual(\n            hash_elems(\"Welcome To ElectionGuard\"),\n            hash_elems(\"welcome to electionguard\"),\n        )\n\n    def test_hash_for_none_same_as_null_string(self):\n        self.assertEqual(hash_elems(None), hash_elems(\"null\"))\n\n    def test_hash_of_save_values_in_list_are_same_hash(self):\n        self.assertEqual(hash_elems([\"0\", \"0\"]), hash_elems([\"0\", \"0\"]))\n\n    def test_hash_null_equivalents(self):\n        null_list: Optional[List[str]] = None\n        empty_list: List[str] = []\n\n        self.assertEqual(hash_elems(null_list), hash_elems(empty_list))\n        self.assertEqual(hash_elems(empty_list), hash_elems(None))\n        self.assertEqual(hash_elems(empty_list), hash_elems(\"null\"))\n\n    def test_hash_not_null_equivalents(self):\n        self.assertNotEqual(hash_elems(None), hash_elems(\"\"))\n        self.assertNotEqual(hash_elems(None), hash_elems(0))\n\n    def test_hash_value_from_nested_list_and_result_of_hashed_list_by_taking_the_hex(\n        self,\n    ):\n        nested_hash = hash_elems([\"0\", \"1\"], \"3\")\n        non_nested_1 = hash_elems(\"0\", \"1\")\n        non_nested_2 = hash_elems(non_nested_1.to_hex(), \"3\")\n\n        self.assertNotEqual(nested_hash, non_nested_1)\n        self.assertEqual(nested_hash, non_nested_2)\n"
  },
  {
    "path": "tests/property/test_nonces.py",
    "content": "from typing import List\n\nfrom hypothesis import given, assume\nfrom hypothesis.strategies import integers\n\nfrom tests.base_test_case import BaseTestCase\n\nfrom electionguard.group import ElementModQ\nfrom electionguard.nonces import Nonces\nfrom electionguard_tools.strategies.group import elements_mod_q\n\n\nclass TestNonces(BaseTestCase):\n    \"\"\"Nonces tests\"\"\"\n\n    @given(elements_mod_q())\n    def test_nonces_iterable(self, seed: ElementModQ):\n        n = Nonces(seed)\n        i = iter(n)\n        q0 = next(i)\n        q1 = next(i)\n        self.assertTrue(q0 != q1)\n\n    @given(elements_mod_q(), integers(min_value=0, max_value=1000000))\n    def test_nonces_deterministic(self, seed: ElementModQ, i: int):\n        n1 = Nonces(seed)\n        n2 = Nonces(seed)\n        self.assertEqual(n1[i], n2[i])\n\n    @given(\n        elements_mod_q(),\n        elements_mod_q(),\n    )\n    def test_nonces_seed_matters(self, seed1: ElementModQ, seed2: ElementModQ):\n        assume(seed1 != seed2)\n        n1 = Nonces(seed1)  # pylint: disable=unreachable\n        n2 = Nonces(seed2)  # pylint: disable=unreachable\n        self.assertNotEqual(n1[0], n2[0])\n\n    @given(elements_mod_q())\n    def test_nonces_with_slices(self, seed: ElementModQ):\n        n = Nonces(seed)\n        count: int = 0\n        l: List[ElementModQ] = []\n\n        for i in iter(n):\n            count += 1\n            l.append(i)\n            if count == 10:\n                break\n        self.assertEqual(len(l), 10)\n\n        l2 = Nonces(seed)[0:10]\n        self.assertEqual(len(l2), 10)\n        self.assertEqual(l, l2)\n\n    def test_nonces_type_errors(self):\n        n = Nonces(ElementModQ(3))\n        self.assertRaises(TypeError, len, n)\n        self.assertRaises(TypeError, lambda: n[1:])\n        self.assertRaises(TypeError, lambda: n.get_with_headers(-1))\n"
  },
  {
    "path": "tests/property/test_schnorr.py",
    "content": "from hypothesis import given, assume\n\nfrom tests.base_test_case import BaseTestCase\n\nfrom electionguard.constants import get_large_prime\nfrom electionguard.elgamal import ElGamalKeyPair, elgamal_keypair_from_secret\nfrom electionguard.group import (\n    ElementModQ,\n    ElementModP,\n    ZERO_MOD_P,\n    TWO_MOD_Q,\n    ONE_MOD_Q,\n)\nfrom electionguard.schnorr import (\n    make_schnorr_proof,\n    SchnorrProof,\n)\nfrom electionguard.utils import get_optional\nfrom electionguard_tools.strategies.elgamal import elgamal_keypairs\nfrom electionguard_tools.strategies.group import (\n    elements_mod_q,\n    elements_mod_p_no_zero,\n    elements_mod_p,\n)\n\n\nclass TestSchnorr(BaseTestCase):\n    \"\"\"Schnorr tests\"\"\"\n\n    def test_schnorr_proofs_simple(self) -> None:\n        # doesn't get any simpler than this\n        keypair = get_optional(elgamal_keypair_from_secret(TWO_MOD_Q))\n        nonce = ONE_MOD_Q\n        proof = make_schnorr_proof(keypair, nonce)\n        self.assertTrue(proof.is_valid())\n\n    @given(elgamal_keypairs(), elements_mod_q())\n    def test_schnorr_proofs_valid(\n        self, keypair: ElGamalKeyPair, nonce: ElementModQ\n    ) -> None:\n        proof = make_schnorr_proof(keypair, nonce)\n        self.assertTrue(proof.is_valid())\n\n    # Now, we introduce errors in the proofs and make sure that they fail to verify\n    @given(elgamal_keypairs(), elements_mod_q(), elements_mod_q())\n    def test_schnorr_proofs_invalid_u(\n        self, keypair: ElGamalKeyPair, nonce: ElementModQ, other: ElementModQ\n    ) -> None:\n        proof = make_schnorr_proof(keypair, nonce)\n        assume(other != proof.response)\n        proof2 = SchnorrProof(  # pylint: disable=unreachable\n            proof.public_key, proof.commitment, proof.challenge, other\n        )\n        self.assertFalse(proof2.is_valid())\n\n    @given(elgamal_keypairs(), elements_mod_q(), elements_mod_p())\n    def test_schnorr_proofs_invalid_h(\n        self, keypair: ElGamalKeyPair, nonce: ElementModQ, other: ElementModP\n    ) -> None:\n        proof = make_schnorr_proof(keypair, nonce)\n        assume(other != proof.commitment)\n        proof_bad = SchnorrProof(  # pylint: disable=unreachable\n            proof.public_key, other, proof.challenge, proof.response\n        )\n        self.assertFalse(proof_bad.is_valid())\n\n    @given(elgamal_keypairs(), elements_mod_q(), elements_mod_p_no_zero())\n    def test_schnorr_proofs_invalid_public_key(\n        self, keypair: ElGamalKeyPair, nonce: ElementModQ, other: ElementModP\n    ) -> None:\n        proof = make_schnorr_proof(keypair, nonce)\n        assume(other != proof.public_key)\n        proof2 = SchnorrProof(  # pylint: disable=unreachable\n            other, proof.commitment, proof.challenge, proof.response\n        )\n        self.assertFalse(proof2.is_valid())\n\n    @given(elgamal_keypairs(), elements_mod_q())\n    def test_schnorr_proofs_bounds_checking(\n        self, keypair: ElGamalKeyPair, nonce: ElementModQ\n    ) -> None:\n        proof = make_schnorr_proof(keypair, nonce)\n        proof2 = SchnorrProof(\n            ZERO_MOD_P, proof.commitment, proof.challenge, proof.response\n        )\n        proof3 = SchnorrProof(\n            ElementModP(get_large_prime(), False),\n            proof.commitment,\n            proof.challenge,\n            proof.response,\n        )\n        self.assertFalse(proof2.is_valid())\n        self.assertFalse(proof3.is_valid())\n"
  },
  {
    "path": "tests/property/test_tally.py",
    "content": "from datetime import timedelta\nfrom typing import Dict\n\nfrom hypothesis import given, HealthCheck, settings, Phase\nfrom hypothesis.strategies import integers\n\nfrom tests.base_test_case import BaseTestCase\n\nfrom electionguard.ballot import (\n    BallotBoxState,\n    SubmittedBallot,\n)\nfrom electionguard.ballot_box import cast_ballot, spoil_ballot\nfrom electionguard.data_store import DataStore\nfrom electionguard.elgamal import ElGamalSecretKey\nfrom electionguard.encrypt import encrypt_ballot\nfrom electionguard.group import ONE_MOD_Q\nfrom electionguard.tally import CiphertextTally, tally_ballots, tally_ballot\n\n\nfrom electionguard_tools.strategies.election import (\n    elections_and_ballots,\n    ElectionsAndBallotsTupleType,\n)\nfrom electionguard_tools.factories.election_factory import ElectionFactory\nfrom electionguard_tools.helpers.tally_accumulate import accumulate_plaintext_ballots\n\n\nclass TestTally(BaseTestCase):\n    \"\"\"Tally tests\"\"\"\n\n    @settings(\n        deadline=timedelta(milliseconds=10000),\n        suppress_health_check=[HealthCheck.too_slow],\n        max_examples=3,\n        # disabling the \"shrink\" phase, because it runs very slowly\n        phases=[Phase.explicit, Phase.reuse, Phase.generate, Phase.target],\n    )\n    @given(integers(2, 5).flatmap(lambda n: elections_and_ballots(n)))\n    def test_tally_cast_ballots_accumulates_valid_tally(\n        self, everything: ElectionsAndBallotsTupleType\n    ):\n        # Arrange\n        (\n            _election_description,\n            internal_manifest,\n            ballots,\n            secret_key,\n            context,\n        ) = everything\n        # Tally the plaintext ballots for comparison later\n        plaintext_tallies = accumulate_plaintext_ballots(ballots)\n\n        # encrypt each ballot\n        store = DataStore()\n        encryption_seed = ElectionFactory.get_encryption_device().get_hash()\n        for ballot in ballots:\n            encrypted_ballot = encrypt_ballot(\n                ballot,\n                internal_manifest,\n                context,\n                encryption_seed,\n                should_verify_proofs=True,\n            )\n            encryption_seed = encrypted_ballot.code\n            self.assertIsNotNone(encrypted_ballot)\n            # add to the ballot store\n            store.set(\n                encrypted_ballot.object_id,\n                cast_ballot(encrypted_ballot),\n            )\n\n        # act\n        result = tally_ballots(store, internal_manifest, context)\n        self.assertIsNotNone(result)\n\n        # Assert\n        decrypted_tallies = self._decrypt_with_secret(result, secret_key)\n        self.assertEqual(plaintext_tallies, decrypted_tallies)\n\n    @settings(\n        deadline=timedelta(milliseconds=10000),\n        suppress_health_check=[HealthCheck.too_slow],\n        max_examples=3,\n        # disabling the \"shrink\" phase, because it runs very slowly\n        phases=[Phase.explicit, Phase.reuse, Phase.generate, Phase.target],\n    )\n    @given(integers(1, 3).flatmap(lambda n: elections_and_ballots(n)))\n    def test_tally_spoiled_ballots_accumulates_valid_tally(\n        self, everything: ElectionsAndBallotsTupleType\n    ):\n        # Arrange\n        (\n            _election_description,\n            internal_manifest,\n            ballots,\n            secret_key,\n            context,\n        ) = everything\n        # Tally the plaintext ballots for comparison later\n        plaintext_tallies = accumulate_plaintext_ballots(ballots)\n\n        # encrypt each ballot\n        store = DataStore()\n        encryption_seed = ElectionFactory.get_encryption_device().get_hash()\n        for ballot in ballots:\n            encrypted_ballot = encrypt_ballot(\n                ballot,\n                internal_manifest,\n                context,\n                encryption_seed,\n                should_verify_proofs=True,\n            )\n            encryption_seed = encrypted_ballot.code\n            self.assertIsNotNone(encrypted_ballot)\n            # add to the ballot store\n            store.set(\n                encrypted_ballot.object_id,\n                spoil_ballot(encrypted_ballot),\n            )\n\n        # act\n        tally = tally_ballots(store, internal_manifest, context)\n        self.assertIsNotNone(tally)\n\n        # Assert\n        decrypted_tallies = self._decrypt_with_secret(tally, secret_key)\n        self.assertCountEqual(plaintext_tallies, decrypted_tallies)\n        for value in decrypted_tallies.values():\n            self.assertEqual(0, value)\n        self.assertEqual(len(ballots), tally.spoiled())\n\n    @settings(\n        deadline=timedelta(milliseconds=10000),\n        suppress_health_check=[HealthCheck.too_slow],\n        max_examples=3,\n        # disabling the \"shrink\" phase, because it runs very slowly\n        phases=[Phase.explicit, Phase.reuse, Phase.generate, Phase.target],\n    )\n    @given(integers(1, 3).flatmap(lambda n: elections_and_ballots(n)))\n    def test_tally_ballot_invalid_input_fails(\n        self, everything: ElectionsAndBallotsTupleType\n    ):\n\n        # Arrange\n        (\n            _election_description,\n            internal_manifest,\n            ballots,\n            _secret_key,\n            context,\n        ) = everything\n\n        # encrypt each ballot\n        store = DataStore()\n        encryption_seed = ElectionFactory.get_encryption_device().get_hash()\n        for ballot in ballots:\n            encrypted_ballot = encrypt_ballot(\n                ballot, internal_manifest, context, encryption_seed\n            )\n            encryption_seed = encrypted_ballot.code\n            self.assertIsNotNone(encrypted_ballot)\n            # add to the ballot store\n            store.set(\n                encrypted_ballot.object_id,\n                cast_ballot(encrypted_ballot),\n            )\n\n        tally = CiphertextTally(\"my-tally\", internal_manifest, context)\n\n        # act\n        cached_ballots = store.all()\n        first_ballot = cached_ballots[0]\n        first_ballot.state = BallotBoxState.UNKNOWN\n\n        # verify an UNKNOWN state ballot fails\n        self.assertIsNone(tally_ballot(first_ballot, tally))\n        self.assertFalse(tally.append(first_ballot, True))\n\n        # cast a ballot\n        first_ballot.state = BallotBoxState.CAST\n        self.assertTrue(tally.append(first_ballot, False))\n\n        # try to append a spoiled ballot\n        first_ballot.state = BallotBoxState.SPOILED\n        self.assertFalse(tally.append(first_ballot, True))\n\n        # Verify accumulation fails if the selection collection is empty\n        if first_ballot.state == BallotBoxState.CAST:\n            self.assertFalse(\n                tally.contests[first_ballot.object_id].accumulate_contest([])\n            )\n\n        # pop the cast ballot\n        tally.cast_ballot_ids.pop()\n\n        # reset to cast\n        first_ballot.state = BallotBoxState.CAST\n\n        self.assertTrue(\n            self._cannot_erroneously_mutate_state(\n                tally, first_ballot, BallotBoxState.CAST\n            )\n        )\n\n        self.assertTrue(\n            self._cannot_erroneously_mutate_state(\n                tally, first_ballot, BallotBoxState.SPOILED\n            )\n        )\n\n        self.assertTrue(\n            self._cannot_erroneously_mutate_state(\n                tally, first_ballot, BallotBoxState.UNKNOWN\n            )\n        )\n\n        # verify a cast ballot cannot be added twice\n        first_ballot.state = BallotBoxState.CAST\n        self.assertTrue(tally.append(first_ballot, True))\n        self.assertFalse(tally.append(first_ballot, False))\n\n        # verify an already submitted ballot cannot be changed or readded\n        first_ballot.state = BallotBoxState.SPOILED\n        self.assertFalse(tally.append(first_ballot, True))\n\n    @staticmethod\n    def _decrypt_with_secret(\n        tally: CiphertextTally, secret_key: ElGamalSecretKey\n    ) -> Dict[str, int]:\n        \"\"\"\n        Demonstrates how to decrypt a tally with a known secret key\n        \"\"\"\n        plaintext_selections: Dict[str, int] = {}\n        for _, contest in tally.contests.items():\n            for object_id, selection in contest.selections.items():\n                plaintext_tally = selection.ciphertext.decrypt(secret_key)\n                plaintext_selections[object_id] = plaintext_tally\n\n        return plaintext_selections\n\n    def _cannot_erroneously_mutate_state(\n        self,\n        tally: CiphertextTally,\n        ballot: SubmittedBallot,\n        state_to_test: BallotBoxState,\n    ) -> bool:\n\n        input_state = ballot.state\n        ballot.state = state_to_test\n\n        # remove the first selection\n        first_contest = ballot.contests[0]\n        first_selection = first_contest.ballot_selections[0]\n        ballot.contests[0].ballot_selections.remove(first_selection)\n\n        self.assertIsNone(tally_ballot(ballot, tally))\n        self.assertFalse(tally.append(ballot, True))\n\n        # Verify accumulation fails if the selection count does not match\n        if ballot.state == BallotBoxState.CAST:\n            first_tally = tally.contests[first_contest.object_id]\n            self.assertFalse(\n                first_tally.accumulate_contest(ballot.contests[0].ballot_selections)\n            )\n\n            # pylint: disable=protected-access\n            _key, bad_accumulation = first_tally._accumulate_selections(\n                first_selection.object_id,\n                first_tally.selections[first_selection.object_id],\n                ballot.contests[0].ballot_selections,\n            )\n            self.assertIsNone(bad_accumulation)\n\n        ballot.contests[0].ballot_selections.insert(0, first_selection)\n\n        # modify the contest description hash\n        first_contest_hash = ballot.contests[0].description_hash\n        ballot.contests[0].description_hash = ONE_MOD_Q\n        self.assertIsNone(tally_ballot(ballot, tally))\n        self.assertFalse(tally.append(ballot, True))\n\n        ballot.contests[0].description_hash = first_contest_hash\n\n        # modify a contest object id\n        first_contest_object_id = ballot.contests[0].object_id\n        ballot.contests[0].object_id = \"a-bad-object-id\"\n        self.assertIsNone(tally_ballot(ballot, tally))\n        self.assertFalse(tally.append(ballot, True))\n\n        ballot.contests[0].object_id = first_contest_object_id\n\n        # modify a selection object id\n        first_contest_selection_object_id = (\n            ballot.contests[0].ballot_selections[0].object_id\n        )\n        ballot.contests[0].ballot_selections[0].object_id = \"another-bad-object-id\"\n\n        self.assertIsNone(tally_ballot(ballot, tally))\n        self.assertFalse(tally.append(ballot, True))\n\n        # Verify accumulation fails if the selection object id does not match\n        if ballot.state == BallotBoxState.CAST:\n            self.assertFalse(\n                tally.contests[ballot.contests[0].object_id].accumulate_contest(\n                    ballot.contests[0].ballot_selections\n                )\n            )\n\n        ballot.contests[0].ballot_selections[\n            0\n        ].object_id = first_contest_selection_object_id\n\n        # modify the ballot's hash\n        first_ballot_hash = ballot.manifest_hash\n        ballot.manifest_hash = ONE_MOD_Q\n        self.assertIsNone(tally_ballot(ballot, tally))\n        self.assertFalse(tally.append(ballot, True))\n\n        ballot.manifest_hash = first_ballot_hash\n        ballot.state = input_state\n\n        return True\n"
  },
  {
    "path": "tests/property/test_verify.py",
    "content": "# pylint: disable=protected-access\nfrom datetime import timedelta\nfrom typing import Dict\nfrom hypothesis import given, HealthCheck, settings, Phase\nfrom hypothesis.strategies import integers\n\nfrom tests.base_test_case import BaseTestCase\n\nfrom electionguard.ballot_box import spoil_ballot\nfrom electionguard.data_store import DataStore\nfrom electionguard.decryption import compute_decryption_share\nfrom electionguard.decryption_share import DecryptionShare\nfrom electionguard.decrypt_with_shares import decrypt_tally\nfrom electionguard.elgamal import ElGamalKeyPair\nfrom electionguard.encrypt import EncryptionMediator, encrypt_ballot\nfrom electionguard.key_ceremony import CeremonyDetails\nfrom electionguard.key_ceremony_mediator import KeyCeremonyMediator\nfrom electionguard.tally import tally_ballots\nfrom electionguard.type import GuardianId\nfrom electionguard.utils import get_optional\n\nfrom electionguard_verify.verify import (\n    verify_ballot,\n    verify_decryption,\n    verify_aggregation,\n)\n\nimport electionguard_tools.factories.ballot_factory as BallotFactory\nimport electionguard_tools.factories.election_factory as ElectionFactory\nfrom electionguard_tools.strategies.election import (\n    elections_and_ballots,\n    ElectionsAndBallotsTupleType,\n)\nfrom electionguard_tools.strategies.elgamal import elgamal_keypairs\nfrom electionguard_tools.helpers.key_ceremony_orchestrator import (\n    KeyCeremonyOrchestrator,\n)\nfrom electionguard_tools.helpers.election_builder import ElectionBuilder\n\n\nelection_factory = ElectionFactory.ElectionFactory()\nballot_factory = BallotFactory.BallotFactory()\n\n\nclass TestVerify(BaseTestCase):\n    \"\"\"Test ballot verification\"\"\"\n\n    @settings(\n        deadline=timedelta(milliseconds=2000),\n        suppress_health_check=[HealthCheck.too_slow],\n        max_examples=10,\n        # disabling the \"shrink\" phase, because it runs very slowly\n        phases=[Phase.explicit, Phase.reuse, Phase.generate, Phase.target],\n    )\n    @given(elgamal_keypairs())\n    def test_verify_ballot(self, keypair: ElGamalKeyPair):\n        # Arrange\n        manifest = election_factory.get_simple_manifest_from_file()\n        internal_manifest, context = election_factory.get_fake_ciphertext_election(\n            manifest, keypair.public_key\n        )\n\n        data = ballot_factory.get_simple_ballot_from_file()\n        device = election_factory.get_encryption_device()\n        operator = EncryptionMediator(internal_manifest, context, device)\n\n        encrypted_ballot = operator.encrypt(data)\n        self.assertIsNotNone(encrypted_ballot)\n\n        # Act\n        verification = verify_ballot(encrypted_ballot, manifest, context)\n\n        # Assert\n        self.assertIsNotNone(verification)\n        self.assertTrue(verification.verified)\n\n    def test_verify_decryption(self):\n        # Arrange\n        NUMBER_OF_GUARDIANS = 3\n        QUORUM = 2\n        CEREMONY_DETAILS = CeremonyDetails(NUMBER_OF_GUARDIANS, QUORUM)\n\n        key_ceremony_mediator = KeyCeremonyMediator(\n            \"key_ceremony_mediator_mediator\", CEREMONY_DETAILS\n        )\n        guardians = KeyCeremonyOrchestrator.create_guardians(CEREMONY_DETAILS)\n        KeyCeremonyOrchestrator.perform_full_ceremony(guardians, key_ceremony_mediator)\n        joint_public_key = key_ceremony_mediator.publish_joint_key()\n        election_public_keys = key_ceremony_mediator._election_public_keys\n\n        # Setup the election\n        manifest = election_factory.get_fake_manifest()\n        builder = ElectionBuilder(NUMBER_OF_GUARDIANS, QUORUM, manifest)\n        builder.set_public_key(joint_public_key.joint_public_key)\n        builder.set_commitment_hash(joint_public_key.commitment_hash)\n        internal_manifest, context = get_optional(builder.build())\n\n        # generate encrypted tally\n        ballot_store = DataStore()\n        ciphertext_tally = tally_ballots(ballot_store, internal_manifest, context)\n\n        # precompute decryption shares for specific selection for the guardians\n        shares: Dict[GuardianId, DecryptionShare] = {\n            guardian.id: compute_decryption_share(\n                guardian._election_keys,\n                ciphertext_tally,\n                context,\n            )\n            for guardian in guardians\n        }\n\n        plaintext_tally = decrypt_tally(\n            ciphertext_tally, shares, context.crypto_extended_base_hash, manifest\n        )\n\n        # Act\n        verification = verify_decryption(plaintext_tally, election_public_keys, context)\n\n        # Assert\n        self.assertIsNotNone(verification)\n        self.assertTrue(verification.verified)\n\n    @settings(\n        deadline=timedelta(milliseconds=10000),\n        suppress_health_check=[HealthCheck.too_slow],\n        max_examples=10,\n        # disabling the \"shrink\" phase, because it runs very slowly\n        phases=[Phase.explicit, Phase.reuse, Phase.generate, Phase.target],\n    )\n    @given(integers(1, 3).flatmap(lambda n: elections_and_ballots(n)))\n    def test_verify_aggregation(self, election_details: ElectionsAndBallotsTupleType):\n        # Arrange\n        (\n            manifest,\n            internal_manifest,\n            ballots,\n            _secret_key,\n            context,\n        ) = election_details\n\n        # encrypt each ballot\n        store = DataStore()\n        encryption_seed = election_factory.get_encryption_device().get_hash()\n        for ballot in ballots:\n            encrypted_ballot = encrypt_ballot(\n                ballot,\n                internal_manifest,\n                context,\n                encryption_seed,\n                should_verify_proofs=True,\n            )\n            encryption_seed = encrypted_ballot.code\n            self.assertIsNotNone(encrypted_ballot)\n            # add to the ballot store\n            store.set(\n                encrypted_ballot.object_id,\n                spoil_ballot(encrypted_ballot),\n            )\n\n        # Generate Tally\n        submitted_ballots = store.all()\n        tally = tally_ballots(store, internal_manifest, context)\n        self.assertIsNotNone(tally)\n\n        # Act\n        verification = verify_aggregation(submitted_ballots, tally, manifest, context)\n\n        # Assert\n        self.assertIsNotNone(verification)\n        self.assertTrue(verification.verified)\n"
  },
  {
    "path": "tests/unit/__init__.py",
    "content": ""
  },
  {
    "path": "tests/unit/electionguard/__init__.py",
    "content": ""
  },
  {
    "path": "tests/unit/electionguard/test_ballot.py",
    "content": "from tests.base_test_case import BaseTestCase\n\nfrom electionguard.manifest import (\n    ContestDescriptionWithPlaceholders,\n    SelectionDescription,\n    VoteVariationType,\n)\nfrom electionguard.encrypt import contest_from\nfrom electionguard.utils import NullVoteException, OverVoteException, UnderVoteException\n\n\nNUMBER_ELECTED = 2\n\n\ndef get_sample_contest_description() -> ContestDescriptionWithPlaceholders:\n    ballot_selections = [\n        SelectionDescription(\"option-1-id\", 1, \"luke-skywalker-id\"),\n        SelectionDescription(\"option-2-id\", 2, \"darth-vader-id\"),\n        SelectionDescription(\"option-3-id\", 3, \"obi-wan-kenobi-id\"),\n    ]\n    placeholder_selections = [\n        SelectionDescription(\"placeholder-1-id\", 4, \"placeholder-id\"),\n        SelectionDescription(\"placeholder-2-id\", 5, \"placeholder-id\"),\n    ]\n    description = ContestDescriptionWithPlaceholders(\n        \"favorite-character-id\",\n        1,\n        \"dagobah-id\",\n        VoteVariationType.n_of_m,\n        NUMBER_ELECTED,\n        None,\n        \"favorite-star-wars-character\",\n        ballot_selections,\n        None,\n        None,\n        placeholder_selections,\n    )\n    return description\n\n\nclass TestBallot(BaseTestCase):\n    \"\"\"Ballot tests\"\"\"\n\n    def test_contest_valid(self) -> None:\n        # Arrange.\n        contest_description = get_sample_contest_description()\n        contest = contest_from(contest_description)\n\n        # Add Votes\n        for i in range(NUMBER_ELECTED):\n            contest.ballot_selections[i].vote = 1\n\n        # Act & Assert.\n        try:\n            contest.valid(contest_description)\n        except (NullVoteException, OverVoteException, UnderVoteException):\n            self.fail(\"No exceptions should be thrown.\")\n\n    def test_contest_valid_with_null_vote(self) -> None:\n        # Arrange.\n        contest_description = get_sample_contest_description()\n        null_vote = contest_from(contest_description)\n\n        # Act & Assert.\n        with self.assertRaises(NullVoteException):\n            null_vote.valid(contest_description)\n\n    def test_contest_valid_with_under_vote(self) -> None:\n        # Arrange.\n        contest_description = get_sample_contest_description()\n        under_vote = contest_from(contest_description)\n\n        # Add Votes\n        for i in range(NUMBER_ELECTED - 1):\n            under_vote.ballot_selections[i].vote = 1\n\n        # Act & Assert.\n        with self.assertRaises(UnderVoteException):\n            under_vote.valid(contest_description)\n\n    def test_contest_valid_with_over_vote(self) -> None:\n        # Arrange.\n        contest_description = get_sample_contest_description()\n        over_vote = contest_from(contest_description)\n\n        # Add Votes\n        for i in range(NUMBER_ELECTED + 1):\n            over_vote.ballot_selections[i].vote = 1\n\n        # Act & Assert.\n        with self.assertRaises(OverVoteException):\n            over_vote.valid(contest_description)\n"
  },
  {
    "path": "tests/unit/electionguard/test_ballot_box.py",
    "content": "from tests.base_test_case import BaseTestCase\n\nfrom electionguard.ballot import BallotBoxState\nfrom electionguard.ballot_box import (\n    BallotBox,\n    submit_ballot_to_box,\n    cast_ballot,\n    spoil_ballot,\n    submit_ballot,\n)\nfrom electionguard.data_store import DataStore\nfrom electionguard.elgamal import elgamal_keypair_from_secret\nfrom electionguard.encrypt import encrypt_ballot\nfrom electionguard.group import TWO_MOD_Q\nfrom electionguard.utils import get_optional\n\nimport electionguard_tools.factories.election_factory as ElectionFactory\n\n\nclass TestBallotBox(BaseTestCase):\n    \"\"\"Ballot box tests\"\"\"\n\n    def setUp(self) -> None:\n        \"\"\"Setup ballot box tests by creating a mock ballot, manifest, and encryption context.\"\"\"\n\n        election_factory = ElectionFactory.ElectionFactory()\n        self.seed = election_factory.get_encryption_device().get_hash()\n        keypair = get_optional(elgamal_keypair_from_secret(TWO_MOD_Q))\n        manifest = election_factory.get_fake_manifest()\n        (\n            self.internal_manifest,\n            self.context,\n        ) = election_factory.get_fake_ciphertext_election(manifest, keypair.public_key)\n        self.ballot = election_factory.get_fake_ballot(manifest)\n\n    def test_ballot_box_cast_ballot(self) -> None:\n        # Arrange\n        encrypted_ballot = get_optional(\n            encrypt_ballot(\n                self.ballot,\n                self.internal_manifest,\n                self.context,\n                self.seed,\n            )\n        )\n        store: DataStore = DataStore()\n\n        # Act\n        ballot_box = BallotBox(self.internal_manifest, self.context, store)\n        submitted_ballot = ballot_box.cast(encrypted_ballot)\n\n        # Assert\n        # Test returned ballot\n        self.assertIsNotNone(submitted_ballot)\n        self.assertEqual(submitted_ballot.state, BallotBoxState.CAST)\n\n        # Test ballot in box\n        ballot_in_box = store.get(encrypted_ballot.object_id)\n        self.assertIsNotNone(ballot_in_box)\n        self.assertEqual(ballot_in_box.state, BallotBoxState.CAST)\n        self.assertEqual(ballot_in_box.object_id, submitted_ballot.object_id)\n\n        # Test failure modes\n        self.assertIsNone(ballot_box.cast(encrypted_ballot))  # cannot cast again\n        self.assertIsNone(\n            ballot_box.spoil(encrypted_ballot)\n        )  # cannot spoil a ballot already cast\n\n    def test_ballot_box_spoil_ballot(self) -> None:\n        # Arrange\n        encrypted_ballot = get_optional(\n            encrypt_ballot(\n                self.ballot,\n                self.internal_manifest,\n                self.context,\n                self.seed,\n            )\n        )\n        store: DataStore = DataStore()\n\n        # Act\n        ballot_box = BallotBox(self.internal_manifest, self.context, store)\n        submitted_ballot = ballot_box.spoil(encrypted_ballot)\n\n        # Assert\n        # Test returned ballot\n        self.assertIsNotNone(submitted_ballot)\n        self.assertEqual(submitted_ballot.state, BallotBoxState.SPOILED)\n\n        # Test ballot in box\n        ballot_in_box = store.get(encrypted_ballot.object_id)\n        self.assertIsNotNone(ballot_in_box)\n        self.assertEqual(ballot_in_box.state, BallotBoxState.SPOILED)\n        self.assertEqual(ballot_in_box.object_id, submitted_ballot.object_id)\n\n        # Test failure modes\n        self.assertIsNone(ballot_box.cast(encrypted_ballot))  # cannot cast again\n        self.assertIsNone(\n            ballot_box.spoil(encrypted_ballot)\n        )  # cannot spoil a ballot already cast\n\n    def test_submit_ballot_to_box(self) -> None:\n        # Arrange\n        encrypted_ballot = get_optional(\n            encrypt_ballot(\n                self.ballot,\n                self.internal_manifest,\n                self.context,\n                self.seed,\n            )\n        )\n        store: DataStore = DataStore()\n\n        # Act\n        submitted_ballot = submit_ballot_to_box(\n            encrypted_ballot,\n            BallotBoxState.CAST,\n            self.internal_manifest,\n            self.context,\n            store,\n        )\n\n        # Assert\n        # Test returned ballot\n        self.assertIsNotNone(submitted_ballot)\n        self.assertEqual(submitted_ballot.state, BallotBoxState.CAST)\n\n        # Test ballot in box\n        ballot_in_box = store.get(encrypted_ballot.object_id)\n        self.assertIsNotNone(ballot_in_box)\n        self.assertEqual(ballot_in_box.state, BallotBoxState.CAST)\n        self.assertEqual(ballot_in_box.object_id, submitted_ballot.object_id)\n\n        # Test failure modes\n        self.assertIsNone(\n            submit_ballot_to_box(\n                encrypted_ballot,\n                BallotBoxState.CAST,\n                self.internal_manifest,\n                self.context,\n                store,\n            )\n        )  # cannot cast again\n        self.assertIsNone(\n            submit_ballot_to_box(\n                encrypted_ballot,\n                BallotBoxState.SPOILED,\n                self.internal_manifest,\n                self.context,\n                store,\n            )\n        )  # cannot spoil a ballot already cast\n\n    def test_cast_ballot(self) -> None:\n        # Arrange\n        encrypted_ballot = get_optional(\n            encrypt_ballot(\n                self.ballot,\n                self.internal_manifest,\n                self.context,\n                self.seed,\n            )\n        )\n\n        # Act\n        submitted_ballot = cast_ballot(encrypted_ballot)\n\n        # Assert\n        self.assertIsNotNone(submitted_ballot)\n        self.assertEqual(submitted_ballot.state, BallotBoxState.CAST)\n        self.assertEqual(encrypted_ballot.object_id, submitted_ballot.object_id)\n\n    def test_spoil_ballot(self) -> None:\n        # Arrange\n        encrypted_ballot = get_optional(\n            encrypt_ballot(\n                self.ballot,\n                self.internal_manifest,\n                self.context,\n                self.seed,\n            )\n        )\n\n        # Act\n        submitted_ballot = spoil_ballot(encrypted_ballot)\n\n        # Assert\n        self.assertIsNotNone(submitted_ballot)\n        self.assertEqual(submitted_ballot.state, BallotBoxState.SPOILED)\n        self.assertEqual(encrypted_ballot.object_id, submitted_ballot.object_id)\n\n    def test_submit_ballot(self) -> None:\n        # Arrange\n        encrypted_ballot = get_optional(\n            encrypt_ballot(\n                self.ballot,\n                self.internal_manifest,\n                self.context,\n                self.seed,\n            )\n        )\n\n        # Act\n        submitted_ballot = submit_ballot(encrypted_ballot, BallotBoxState.CAST)\n\n        # Assert\n        self.assertIsNotNone(submitted_ballot)\n        self.assertEqual(submitted_ballot.state, BallotBoxState.CAST)\n        self.assertEqual(encrypted_ballot.object_id, submitted_ballot.object_id)\n"
  },
  {
    "path": "tests/unit/electionguard/test_ballot_code.py",
    "content": "from tests.base_test_case import BaseTestCase\n\nfrom electionguard.group import ZERO_MOD_Q, ONE_MOD_Q, TWO_MOD_Q\nfrom electionguard.ballot_code import (\n    get_ballot_code,\n    get_hash_for_device,\n)\n\nfrom electionguard_tools.factories.election_factory import ElectionFactory\n\n\nclass TestBallotCode(BaseTestCase):\n    \"\"\"Ballot code tests\"\"\"\n\n    def test_rotate_ballot_code(self):\n        # Arrange\n        device = ElectionFactory.get_encryption_device()\n        ballot_hash_1 = ONE_MOD_Q\n        ballot_hash_2 = TWO_MOD_Q\n        timestamp_1 = 1000\n        timestamp_2 = 2000\n\n        # Act\n        device_hash = get_hash_for_device(\n            device.device_id, device.session_id, device.launch_code, device.location\n        )\n        ballot_code_1 = get_ballot_code(device_hash, timestamp_1, ballot_hash_1)\n        ballot_code_2 = get_ballot_code(device_hash, timestamp_2, ballot_hash_2)\n\n        # Assert\n        self.assertIsNotNone(device_hash)\n        self.assertIsNotNone(ballot_code_1)\n        self.assertIsNotNone(ballot_code_2)\n\n        self.assertNotEqual(device_hash, ZERO_MOD_Q)\n        self.assertNotEqual(ballot_code_1, device_hash)\n        self.assertNotEqual(ballot_code_2, device_hash)\n        self.assertNotEqual(ballot_code_1, ballot_code_2)\n"
  },
  {
    "path": "tests/unit/electionguard/test_ballot_compact.py",
    "content": "from tests.base_test_case import BaseTestCase\n\nfrom electionguard.ballot import (\n    PlaintextBallot,\n    SubmittedBallot,\n)\nfrom electionguard.ballot_box import cast_ballot\nfrom electionguard.ballot_compact import (\n    compress_plaintext_ballot,\n    compress_submitted_ballot,\n    expand_compact_plaintext_ballot,\n    expand_compact_submitted_ballot,\n)\nfrom electionguard.election import CiphertextElectionContext\nfrom electionguard.elgamal import elgamal_keypair_from_secret\nfrom electionguard.encrypt import encrypt_ballot\nfrom electionguard.group import TWO_MOD_Q, ElementModQ\nfrom electionguard.manifest import InternalManifest\n\nfrom electionguard_tools.factories.election_factory import ElectionFactory\n\n\nclass TestCompactBallot(BaseTestCase):\n    \"\"\"Test Compact Ballot Variations\"\"\"\n\n    plaintext_ballot: PlaintextBallot\n    ballot_nonce: ElementModQ\n    submitted_ballot: SubmittedBallot\n    internal_manifest: InternalManifest\n    context: CiphertextElectionContext\n\n    def setUp(self) -> None:\n        # Election setup\n        election_factory = ElectionFactory()\n        keypair = elgamal_keypair_from_secret(TWO_MOD_Q)\n        manifest = election_factory.get_fake_manifest()\n        (\n            self.internal_manifest,\n            self.context,\n        ) = election_factory.get_fake_ciphertext_election(manifest, keypair.public_key)\n        device_hash = ElectionFactory.get_encryption_device().get_hash()\n\n        # Arrange ballots\n        self.plaintext_ballot = election_factory.get_fake_ballot(self.internal_manifest)\n        ciphertext_ballot = encrypt_ballot(\n            self.plaintext_ballot, self.internal_manifest, self.context, device_hash\n        )\n        self.ballot_nonce = ciphertext_ballot.nonce\n        self.submitted_ballot = cast_ballot(ciphertext_ballot)\n\n    def test_compact_plaintext_ballot(self) -> None:\n        # Act\n        compact_ballot = compress_plaintext_ballot(self.plaintext_ballot)\n\n        # Assert\n        self.assertIsNotNone(compact_ballot)\n        self.assertEqual(self.plaintext_ballot.object_id, compact_ballot.object_id)\n\n        # Act\n        expanded_ballot = expand_compact_plaintext_ballot(\n            compact_ballot, self.internal_manifest\n        )\n\n        # Assert\n        self.assertIsNotNone(expanded_ballot)\n        self.assertEqual(self.plaintext_ballot, expanded_ballot)\n\n    def test_compact_submitted_ballot(self) -> None:\n        # Act\n        compact_ballot = compress_submitted_ballot(\n            self.submitted_ballot, self.plaintext_ballot, self.ballot_nonce\n        )\n\n        # Assert\n        self.assertIsNotNone(compact_ballot)\n        self.assertEqual(\n            self.submitted_ballot.object_id,\n            compact_ballot.compact_plaintext_ballot.object_id,\n        )\n\n        # Act\n        expanded_ballot = expand_compact_submitted_ballot(\n            compact_ballot, self.internal_manifest, self.context\n        )\n\n        # Assert\n        self.assertIsNotNone(expanded_ballot)\n        self.assertEqual(self.submitted_ballot, expanded_ballot)\n        self.assertEqual(self.submitted_ballot.crypto_hash, expanded_ballot.crypto_hash)\n"
  },
  {
    "path": "tests/unit/electionguard/test_constants.py",
    "content": "import os\nfrom unittest.mock import patch\n\nfrom tests.base_test_case import BaseTestCase\n\n\nfrom electionguard.constants import (\n    PrimeOption,\n    LARGE_TEST_CONSTANTS,\n    get_constants,\n    STANDARD_CONSTANTS,\n)\n\nfrom electionguard.constants import (\n    get_small_prime,\n    get_large_prime,\n    get_cofactor,\n    get_generator,\n)\n\n\nclass TestConstants(BaseTestCase):\n    \"\"\"Election constant tests.\"\"\"\n\n    @patch.dict(os.environ, {\"PRIME_OPTION\": PrimeOption.Standard.value})\n    def test_get_standard_primes(self):\n        \"\"\"Test getting standard constants with large primes.\"\"\"\n        # Act\n        constants = get_constants()\n\n        # Assert\n        self.assertIsNotNone(constants)\n        self.assertEqual(constants, STANDARD_CONSTANTS)\n        self.assertEqual(constants.large_prime, get_large_prime())\n        self.assertEqual(constants.small_prime, get_small_prime())\n        self.assertEqual(constants.cofactor, get_cofactor())\n        self.assertEqual(constants.generator, get_generator())\n\n    @patch.dict(os.environ, {\"PRIME_OPTION\": PrimeOption.TestOnly.value})\n    def test_get_test_primes(self):\n        \"\"\"Test getting test only constants with small primes.\"\"\"\n        # Act\n        constants = get_constants()\n\n        # Assert\n        self.assertIsNotNone(constants)\n        self.assertEqual(constants, LARGE_TEST_CONSTANTS)\n        self.assertEqual(constants.large_prime, get_large_prime())\n        self.assertEqual(constants.small_prime, get_small_prime())\n        self.assertEqual(constants.cofactor, get_cofactor())\n        self.assertEqual(constants.generator, get_generator())\n"
  },
  {
    "path": "tests/unit/electionguard/test_decrypt_with_shares.py",
    "content": "# pylint: disable=protected-access\n# pylint: disable=too-many-instance-attributes\n# pylint: disable=unnecessary-comprehension\n\nfrom typing import Dict, List, Tuple\n\nfrom tests.base_test_case import BaseTestCase\n\nfrom electionguard.ballot_box import BallotBox, BallotBoxState, get_ballots\nfrom electionguard.data_store import DataStore\nfrom electionguard.decrypt_with_shares import (\n    decrypt_selection_with_decryption_shares,\n    decrypt_ballot,\n)\nfrom electionguard.decryption import (\n    compute_decryption_share,\n    compute_decryption_share_for_ballot,\n    compute_compensated_decryption_share_for_ballot,\n    compute_lagrange_coefficients_for_guardians,\n    reconstruct_decryption_share_for_ballot,\n)\nfrom electionguard.decryption_share import DecryptionShare\nfrom electionguard.encrypt import EncryptionMediator\nfrom electionguard.group import ElementModP\nfrom electionguard.guardian import Guardian\nfrom electionguard.key_ceremony import CeremonyDetails\nfrom electionguard.key_ceremony_mediator import KeyCeremonyMediator\nfrom electionguard.tally import tally_ballots\nfrom electionguard.type import GuardianId\nfrom electionguard.utils import get_optional\n\nimport electionguard_tools.factories.ballot_factory as BallotFactory\nimport electionguard_tools.factories.election_factory as ElectionFactory\nfrom electionguard_tools.helpers.key_ceremony_orchestrator import (\n    KeyCeremonyOrchestrator,\n)\nfrom electionguard_tools.helpers.tally_accumulate import accumulate_plaintext_ballots\nfrom electionguard_tools.helpers.election_builder import ElectionBuilder\n\n\nelection_factory = ElectionFactory.ElectionFactory()\nballot_factory = BallotFactory.BallotFactory()\n\n\nclass TestDecryptWithShares(BaseTestCase):\n    \"\"\"Test decrypt with shares methods\"\"\"\n\n    NUMBER_OF_GUARDIANS = 3\n    QUORUM = 2\n    CEREMONY_DETAILS = CeremonyDetails(NUMBER_OF_GUARDIANS, QUORUM)\n\n    def setUp(self):\n\n        # Key Ceremony\n        self.key_ceremony_mediator = KeyCeremonyMediator(\n            \"key_ceremony_mediator_mediator\", self.CEREMONY_DETAILS\n        )\n        self.guardians: List[Guardian] = KeyCeremonyOrchestrator.create_guardians(\n            self.CEREMONY_DETAILS\n        )\n        KeyCeremonyOrchestrator.perform_full_ceremony(\n            self.guardians, self.key_ceremony_mediator\n        )\n        self.joint_public_key = self.key_ceremony_mediator.publish_joint_key()\n\n        # Setup the election\n        self.manifest = election_factory.get_fake_manifest()\n        builder = ElectionBuilder(self.NUMBER_OF_GUARDIANS, self.QUORUM, self.manifest)\n        builder.set_public_key(self.joint_public_key.joint_public_key)\n        builder.set_commitment_hash(self.joint_public_key.commitment_hash)\n        self.internal_manifest, self.context = get_optional(builder.build())\n\n        self.encryption_device = election_factory.get_encryption_device()\n        self.ballot_marking_device = EncryptionMediator(\n            self.internal_manifest, self.context, self.encryption_device\n        )\n\n        # get some fake ballots\n        self.fake_cast_ballot = ballot_factory.get_fake_ballot(\n            self.internal_manifest, \"some-unique-ballot-id-cast\"\n        )\n        self.more_fake_ballots = []\n        for i in range(10):\n            self.more_fake_ballots.append(\n                ballot_factory.get_fake_ballot(\n                    self.internal_manifest, f\"some-unique-ballot-id-cast{i}\"\n                )\n            )\n        self.fake_spoiled_ballot = ballot_factory.get_fake_ballot(\n            self.internal_manifest, \"some-unique-ballot-id-spoiled\"\n        )\n        self.more_fake_spoiled_ballots = []\n        for i in range(2):\n            self.more_fake_spoiled_ballots.append(\n                ballot_factory.get_fake_ballot(\n                    self.internal_manifest, f\"some-unique-ballot-id-spoiled{i}\"\n                )\n            )\n        self.assertTrue(\n            self.fake_cast_ballot.is_valid(\n                self.internal_manifest.ballot_styles[0].object_id\n            )\n        )\n        self.assertTrue(\n            self.fake_spoiled_ballot.is_valid(\n                self.internal_manifest.ballot_styles[0].object_id\n            )\n        )\n        self.expected_plaintext_tally = accumulate_plaintext_ballots(\n            [self.fake_cast_ballot] + self.more_fake_ballots\n        )\n\n        # Fill in the expected values with any missing selections\n        # that were not made on any ballots\n        selection_ids = {\n            selection.object_id\n            for contest in self.internal_manifest.contests\n            for selection in contest.ballot_selections\n        }\n\n        missing_selection_ids = selection_ids.difference(\n            set(self.expected_plaintext_tally)\n        )\n\n        for id in missing_selection_ids:\n            self.expected_plaintext_tally[id] = 0\n\n        # Encrypt\n        self.encrypted_fake_cast_ballot = self.ballot_marking_device.encrypt(\n            self.fake_cast_ballot\n        )\n        self.encrypted_fake_spoiled_ballot = self.ballot_marking_device.encrypt(\n            self.fake_spoiled_ballot\n        )\n\n        # encrypt some more fake ballots\n        self.more_fake_encrypted_ballots = []\n        for fake_ballot in self.more_fake_ballots:\n            self.more_fake_encrypted_ballots.append(\n                self.ballot_marking_device.encrypt(fake_ballot)\n            )\n        # encrypt some more fake ballots\n        self.more_fake_encrypted_spoiled_ballots = []\n        for fake_ballot in self.more_fake_spoiled_ballots:\n            self.more_fake_encrypted_spoiled_ballots.append(\n                self.ballot_marking_device.encrypt(fake_ballot)\n            )\n\n        # configure the ballot box\n        ballot_store = DataStore()\n        ballot_box = BallotBox(self.internal_manifest, self.context, ballot_store)\n        ballot_box.cast(self.encrypted_fake_cast_ballot)\n        ballot_box.spoil(self.encrypted_fake_spoiled_ballot)\n\n        # Cast some more fake ballots\n        for fake_ballot in self.more_fake_encrypted_ballots:\n            ballot_box.cast(fake_ballot)\n        # Spoil some more fake ballots\n        for fake_ballot in self.more_fake_encrypted_spoiled_ballots:\n            ballot_box.spoil(fake_ballot)\n\n        # generate encrypted tally\n        self.ciphertext_tally = tally_ballots(\n            ballot_store, self.internal_manifest, self.context\n        )\n        self.ciphertext_ballots = get_ballots(ballot_store, BallotBoxState.SPOILED)\n\n    def tearDown(self):\n        self.key_ceremony_mediator.reset(\n            CeremonyDetails(self.NUMBER_OF_GUARDIANS, self.QUORUM)\n        )\n\n    def test_decrypt_selection_with_all_guardians_present(self):\n        # Arrange\n        available_guardians = self.guardians\n\n        # find the first selection\n        first_contest = list(self.ciphertext_tally.contests.values())[0]\n        first_selection = list(first_contest.selections.values())[0]\n\n        print(first_contest.object_id)\n        print(first_selection.object_id)\n\n        # precompute decryption shares for specific selection for the guardians\n        shares: Dict[GuardianId, Tuple[ElementModP, DecryptionShare]] = {\n            guardian.id: (\n                guardian.share_key().key,\n                compute_decryption_share(\n                    guardian._election_keys,\n                    self.ciphertext_tally,\n                    self.context,\n                )\n                .contests[first_contest.object_id]\n                .selections[first_selection.object_id],\n            )\n            for guardian in available_guardians\n        }\n\n        # Act\n        result = decrypt_selection_with_decryption_shares(\n            first_selection, shares, self.context.crypto_extended_base_hash\n        )\n\n        # Assert\n        self.assertIsNotNone(result)\n        self.assertEqual(\n            self.expected_plaintext_tally[first_selection.object_id], result.tally\n        )\n\n    def test_decrypt_ballot_with_all_guardians_present(self):\n        # Arrange\n        # precompute decryption shares for the guardians\n        available_guardians = self.guardians\n        plaintext_ballot = self.fake_cast_ballot\n        encrypted_ballot = self.encrypted_fake_cast_ballot\n        shares = {\n            available_guardian.id: compute_decryption_share_for_ballot(\n                available_guardian._election_keys,\n                encrypted_ballot,\n                self.context,\n            )\n            for available_guardian in available_guardians\n        }\n\n        # act\n        result = decrypt_ballot(\n            encrypted_ballot,\n            shares,\n            self.context.crypto_extended_base_hash,\n            self.manifest,\n        )\n\n        # assert\n        self.assertIsNotNone(result)\n\n        for contest in plaintext_ballot.contests:\n            for selection in contest.ballot_selections:\n                expected_tally = selection.vote\n                actual_tally = (\n                    result.contests[contest.object_id]\n                    .selections[selection.object_id]\n                    .tally\n                )\n                self.assertEqual(expected_tally, actual_tally)\n\n    def test_decrypt_ballot_with_missing_guardians(self):\n        # Arrange\n        # precompute decryption shares for the guardians\n        plaintext_ballot = self.fake_cast_ballot\n        encrypted_ballot = self.encrypted_fake_cast_ballot\n        available_guardians = self.guardians[0:2]\n        missing_guardian = self.guardians[2]\n\n        available_shares = {\n            available_guardian.id: compute_decryption_share_for_ballot(\n                available_guardian._election_keys,\n                encrypted_ballot,\n                self.context,\n            )\n            for available_guardian in available_guardians\n        }\n\n        compensated_shares = {\n            available_guardian.id: compute_compensated_decryption_share_for_ballot(\n                available_guardian.decrypt_backup(\n                    available_guardian._guardian_election_partial_key_backups.get(\n                        missing_guardian.id\n                    )\n                ),\n                missing_guardian.share_key(),\n                available_guardian.share_key(),\n                encrypted_ballot,\n                self.context,\n            )\n            for available_guardian in available_guardians\n        }\n\n        lagrange_coefficients = compute_lagrange_coefficients_for_guardians(\n            [guardian.share_key() for guardian in available_guardians]\n        )\n\n        reconstructed_share = reconstruct_decryption_share_for_ballot(\n            missing_guardian.share_key(),\n            encrypted_ballot,\n            compensated_shares,\n            lagrange_coefficients,\n        )\n\n        all_shares = {**available_shares, missing_guardian.id: reconstructed_share}\n\n        # act\n        result = decrypt_ballot(\n            encrypted_ballot,\n            all_shares,\n            self.context.crypto_extended_base_hash,\n            self.manifest,\n        )\n\n        # assert\n        self.assertIsNotNone(result)\n\n        for contest in plaintext_ballot.contests:\n            for selection in contest.ballot_selections:\n                expected_tally = selection.vote\n                actual_tally = (\n                    result.contests[contest.object_id]\n                    .selections[selection.object_id]\n                    .tally\n                )\n                self.assertEqual(expected_tally, actual_tally)\n"
  },
  {
    "path": "tests/unit/electionguard/test_decryption.py",
    "content": "# pylint: disable=protected-access\n# pylint: disable=too-many-instance-attributes\n# pylint: disable=unnecessary-comprehension\n\nfrom typing import Dict, List\nfrom tests.base_test_case import BaseTestCase\n\nfrom electionguard.ballot import SubmittedBallot\nfrom electionguard.ballot_box import BallotBox, BallotBoxState, get_ballots\nfrom electionguard.data_store import DataStore\nfrom electionguard.decrypt_with_shares import decrypt_selection_with_decryption_shares\nfrom electionguard.decryption import (\n    compute_compensated_decryption_share,\n    compute_compensated_decryption_share_for_ballot,\n    compute_decryption_share,\n    compute_decryption_share_for_selection,\n    compute_compensated_decryption_share_for_selection,\n    compute_lagrange_coefficients_for_guardians,\n    compute_recovery_public_key,\n    reconstruct_decryption_share,\n    reconstruct_decryption_share_for_ballot,\n)\nfrom electionguard.decryption_share import (\n    CompensatedDecryptionShare,\n    create_ciphertext_decryption_selection,\n)\nfrom electionguard.election_polynomial import compute_lagrange_coefficient\nfrom electionguard.elgamal import ElGamalKeyPair\nfrom electionguard.group import (\n    ZERO_MOD_Q,\n    mult_p,\n    pow_p,\n)\nfrom electionguard.encrypt import EncryptionMediator\nfrom electionguard.guardian import Guardian\nfrom electionguard.key_ceremony import (\n    CeremonyDetails,\n    ElectionKeyPair,\n)\nfrom electionguard.key_ceremony_mediator import KeyCeremonyMediator\nfrom electionguard.tally import tally_ballots\nfrom electionguard.type import BallotId, GuardianId\nfrom electionguard.utils import get_optional\n\nimport electionguard_tools.factories.ballot_factory as BallotFactory\nimport electionguard_tools.factories.election_factory as ElectionFactory\nfrom electionguard_tools.helpers.key_ceremony_orchestrator import (\n    KeyCeremonyOrchestrator,\n)\nfrom electionguard_tools.helpers.tally_accumulate import accumulate_plaintext_ballots\nfrom electionguard_tools.helpers.election_builder import ElectionBuilder\n\n\nelection_factory = ElectionFactory.ElectionFactory()\nballot_factory = BallotFactory.BallotFactory()\n\n\nclass TestDecryption(BaseTestCase):\n    \"\"\"Test decryption methods\"\"\"\n\n    NUMBER_OF_GUARDIANS = 3\n    QUORUM = 2\n    CEREMONY_DETAILS = CeremonyDetails(NUMBER_OF_GUARDIANS, QUORUM)\n\n    def setUp(self):\n\n        # Key Ceremony\n        self.key_ceremony_mediator = KeyCeremonyMediator(\n            \"key_ceremony_mediator_mediator\", self.CEREMONY_DETAILS\n        )\n        self.guardians: List[Guardian] = KeyCeremonyOrchestrator.create_guardians(\n            self.CEREMONY_DETAILS\n        )\n        KeyCeremonyOrchestrator.perform_full_ceremony(\n            self.guardians, self.key_ceremony_mediator\n        )\n        self.joint_public_key = self.key_ceremony_mediator.publish_joint_key()\n\n        # Setup the election\n        manifest = election_factory.get_fake_manifest()\n        builder = ElectionBuilder(self.NUMBER_OF_GUARDIANS, self.QUORUM, manifest)\n        builder.set_public_key(self.joint_public_key.joint_public_key)\n        builder.set_commitment_hash(self.joint_public_key.commitment_hash)\n        self.internal_manifest, self.context = get_optional(builder.build())\n\n        self.encryption_device = election_factory.get_encryption_device()\n        self.ballot_marking_device = EncryptionMediator(\n            self.internal_manifest, self.context, self.encryption_device\n        )\n\n        # get some fake ballots\n        self.fake_cast_ballot = ballot_factory.get_fake_ballot(\n            self.internal_manifest, \"some-unique-ballot-id-cast\"\n        )\n        self.more_fake_ballots = []\n        for i in range(10):\n            self.more_fake_ballots.append(\n                ballot_factory.get_fake_ballot(\n                    self.internal_manifest, f\"some-unique-ballot-id-cast{i}\"\n                )\n            )\n        self.fake_spoiled_ballot = ballot_factory.get_fake_ballot(\n            self.internal_manifest, \"some-unique-ballot-id-spoiled\"\n        )\n        self.more_fake_spoiled_ballots = []\n        for i in range(2):\n            self.more_fake_spoiled_ballots.append(\n                ballot_factory.get_fake_ballot(\n                    self.internal_manifest, f\"some-unique-ballot-id-spoiled{i}\"\n                )\n            )\n        self.assertTrue(\n            self.fake_cast_ballot.is_valid(\n                self.internal_manifest.ballot_styles[0].object_id\n            )\n        )\n        self.assertTrue(\n            self.fake_spoiled_ballot.is_valid(\n                self.internal_manifest.ballot_styles[0].object_id\n            )\n        )\n        self.expected_plaintext_tally = accumulate_plaintext_ballots(\n            [self.fake_cast_ballot] + self.more_fake_ballots\n        )\n\n        # Fill in the expected values with any missing selections\n        # that were not made on any ballots\n        selection_ids = {\n            selection.object_id\n            for contest in self.internal_manifest.contests\n            for selection in contest.ballot_selections\n        }\n\n        missing_selection_ids = selection_ids.difference(\n            set(self.expected_plaintext_tally)\n        )\n\n        for id in missing_selection_ids:\n            self.expected_plaintext_tally[id] = 0\n\n        # Encrypt\n        self.encrypted_fake_cast_ballot = self.ballot_marking_device.encrypt(\n            self.fake_cast_ballot\n        )\n        self.encrypted_fake_spoiled_ballot = self.ballot_marking_device.encrypt(\n            self.fake_spoiled_ballot\n        )\n\n        # encrypt some more fake ballots\n        self.more_fake_encrypted_ballots = []\n        for fake_ballot in self.more_fake_ballots:\n            self.more_fake_encrypted_ballots.append(\n                self.ballot_marking_device.encrypt(fake_ballot)\n            )\n        # encrypt some more fake ballots\n        self.more_fake_encrypted_spoiled_ballots = []\n        for fake_ballot in self.more_fake_spoiled_ballots:\n            self.more_fake_encrypted_spoiled_ballots.append(\n                self.ballot_marking_device.encrypt(fake_ballot)\n            )\n\n        # configure the ballot box\n        ballot_store = DataStore()\n        ballot_box = BallotBox(self.internal_manifest, self.context, ballot_store)\n        ballot_box.cast(self.encrypted_fake_cast_ballot)\n        ballot_box.spoil(self.encrypted_fake_spoiled_ballot)\n\n        # Cast some more fake ballots\n        for fake_ballot in self.more_fake_encrypted_ballots:\n            ballot_box.cast(fake_ballot)\n        # Spoil some more fake ballots\n        for fake_ballot in self.more_fake_encrypted_spoiled_ballots:\n            ballot_box.spoil(fake_ballot)\n\n        # generate encrypted tally\n        self.ciphertext_tally = tally_ballots(\n            ballot_store, self.internal_manifest, self.context\n        )\n        self.ciphertext_ballots: Dict[BallotId, SubmittedBallot] = get_ballots(\n            ballot_store, BallotBoxState.SPOILED\n        )\n\n    def tearDown(self):\n        self.key_ceremony_mediator.reset(\n            CeremonyDetails(self.NUMBER_OF_GUARDIANS, self.QUORUM)\n        )\n\n    # SHARE\n    def test_compute_decryption_share(self):\n        # Arrange\n        guardian = self.guardians[0]\n\n        # Act\n        # Guardian doesn't give keys\n        broken_secret_key = ZERO_MOD_Q\n        broken_guardian_key_pair = ElectionKeyPair(\n            guardian.id,\n            guardian.sequence_order,\n            ElGamalKeyPair(\n                broken_secret_key, guardian._election_keys.key_pair.public_key\n            ),\n            guardian._election_keys.polynomial,\n        )\n\n        broken_share = compute_decryption_share(\n            broken_guardian_key_pair,\n            self.ciphertext_tally,\n            self.context,\n        )\n\n        # Assert\n        self.assertIsNone(broken_share)\n\n        # Act\n        # Normal use case\n        share = compute_decryption_share(\n            guardian._election_keys,\n            self.ciphertext_tally,\n            self.context,\n        )\n\n        # Assert\n        self.assertIsNotNone(share)\n\n    def test_compute_compensated_decryption_share(self):\n        # Arrange\n        guardian = self.guardians[0]\n        missing_guardian = self.guardians[2]\n\n        missing_guardian_public_key = missing_guardian.share_key()\n        missing_guardian_backup = missing_guardian._backups_to_share.get(guardian.id)\n\n        # Act\n        missing_guardian_coordinate = guardian.decrypt_backup(missing_guardian_backup)\n        share = compute_compensated_decryption_share(\n            missing_guardian_coordinate,\n            guardian.share_key(),\n            missing_guardian_public_key,\n            self.ciphertext_tally,\n            self.context,\n        )\n\n        # Assert\n        self.assertIsNotNone(share)\n\n    # SELECTION\n    def test_compute_selection(self):\n        # Arrange\n        first_selection = [\n            selection\n            for contest in self.ciphertext_tally.contests.values()\n            for selection in contest.selections.values()\n        ][0]\n\n        # act\n        result = compute_decryption_share_for_selection(\n            self.guardians[0]._election_keys, first_selection, self.context\n        )\n\n        # assert\n        self.assertIsNotNone(result)\n\n    def test_compute_compensated_selection(self):\n        \"\"\"\n        demonstrates the complete workflow for computing a compensated decryption share\n        For one selection. It is useful for verifying that the workflow is correct\n        \"\"\"\n        # Arrange\n        available_guardian_1 = self.guardians[0]\n        available_guardian_2 = self.guardians[1]\n        missing_guardian = self.guardians[2]\n        available_guardian_1_key = available_guardian_1.share_key()\n        available_guardian_2_key = available_guardian_2.share_key()\n        missing_guardian_key = missing_guardian.share_key()\n\n        first_selection = [\n            selection\n            for contest in self.ciphertext_tally.contests.values()\n            for selection in contest.selections.values()\n        ][0]\n\n        # Compute lagrange coefficients for the guardians that are present\n        lagrange_0 = compute_lagrange_coefficient(\n            available_guardian_1.sequence_order,\n            *[available_guardian_2.sequence_order],\n        )\n        lagrange_1 = compute_lagrange_coefficient(\n            available_guardian_2.sequence_order,\n            *[available_guardian_1.sequence_order],\n        )\n\n        print(\n            (\n                f\"lagrange: sequence_orders: ({available_guardian_1.sequence_order}, \"\n                f\"{available_guardian_2.sequence_order}, {missing_guardian.sequence_order})\\n\"\n            )\n        )\n\n        print(lagrange_0)\n        print(lagrange_1)\n\n        # compute their shares\n        share_0 = compute_decryption_share_for_selection(\n            available_guardian_1._election_keys, first_selection, self.context\n        )\n\n        share_1 = compute_decryption_share_for_selection(\n            available_guardian_2._election_keys, first_selection, self.context\n        )\n\n        self.assertIsNotNone(share_0)\n        self.assertIsNotNone(share_1)\n\n        # compute compensations shares for the missing guardian\n        g3s_encrypted_backup_for_g1 = (\n            missing_guardian.share_election_partial_key_backup(available_guardian_1.id)\n        )\n        g1s_copy_of_g3s_coordinate = available_guardian_1.decrypt_backup(\n            g3s_encrypted_backup_for_g1\n        )\n        compensation_0 = compute_compensated_decryption_share_for_selection(\n            g1s_copy_of_g3s_coordinate,\n            available_guardian_1.share_key(),\n            missing_guardian.share_key(),\n            first_selection,\n            self.context,\n        )\n\n        g3s_encrypted_backup_for_g2 = (\n            missing_guardian.share_election_partial_key_backup(available_guardian_2.id)\n        )\n        g2s_copy_of_g3s_coordinate = available_guardian_2.decrypt_backup(\n            g3s_encrypted_backup_for_g2\n        )\n        compensation_1 = compute_compensated_decryption_share_for_selection(\n            g2s_copy_of_g3s_coordinate,\n            available_guardian_2.share_key(),\n            missing_guardian.share_key(),\n            first_selection,\n            self.context,\n        )\n\n        self.assertIsNotNone(compensation_0)\n        self.assertIsNotNone(compensation_1)\n\n        print(\"\\nSHARES:\")\n        print(compensation_0)\n        print(compensation_1)\n\n        # Check the share proofs\n        self.assertTrue(\n            compensation_0.proof.is_valid(\n                first_selection.ciphertext,\n                compute_recovery_public_key(\n                    available_guardian_1_key, missing_guardian_key\n                ),\n                compensation_0.share,\n                self.context.crypto_extended_base_hash,\n            )\n        )\n\n        self.assertTrue(\n            compensation_1.proof.is_valid(\n                first_selection.ciphertext,\n                compute_recovery_public_key(\n                    available_guardian_2_key, missing_guardian_key\n                ),\n                compensation_1.share,\n                self.context.crypto_extended_base_hash,\n            )\n        )\n\n        share_pow_p = [\n            pow_p(compensation_0.share, lagrange_0),\n            pow_p(compensation_1.share, lagrange_1),\n        ]\n\n        print(\"\\nSHARE_POW_P\")\n        print(share_pow_p)\n\n        # reconstruct the missing share from the compensation shares\n        reconstructed_share = mult_p(\n            *[\n                pow_p(compensation_0.share, lagrange_0),\n                pow_p(compensation_1.share, lagrange_1),\n            ]\n        )\n\n        print(\"\\nRECONSTRUCTED SHARE\\n\")\n        print(reconstructed_share)\n\n        share_2 = create_ciphertext_decryption_selection(\n            first_selection.object_id,\n            missing_guardian.id,\n            reconstructed_share,\n            {\n                available_guardian_1.id: compensation_0,\n                available_guardian_2.id: compensation_1,\n            },\n        )\n\n        # Decrypt the result\n        result = decrypt_selection_with_decryption_shares(\n            first_selection,\n            {\n                available_guardian_1.id: (\n                    available_guardian_1.share_key().key,\n                    share_0,\n                ),\n                available_guardian_2.id: (\n                    available_guardian_2.share_key().key,\n                    share_1,\n                ),\n                missing_guardian.id: (\n                    missing_guardian.share_key().key,\n                    share_2,\n                ),\n            },\n            self.context.crypto_extended_base_hash,\n        )\n\n        print(result)\n\n        self.assertIsNotNone(result)\n        self.assertEqual(\n            result.tally, self.expected_plaintext_tally[first_selection.object_id]\n        )\n\n    def test_compute_compensated_selection_failure(self):\n        # Arrange\n        available_guardian = self.guardians[0]\n        missing_guardian = self.guardians[2]\n\n        first_selection = [\n            selection\n            for contest in self.ciphertext_tally.contests.values()\n            for selection in contest.selections.values()\n        ][0]\n\n        # Act\n        # Get backup for missing guardian instead of one sent by guardian\n        incorrect_backup_encrypted = (\n            available_guardian.share_election_partial_key_backup(missing_guardian.id)\n        )\n        incorrect_backup_decrypted = missing_guardian.decrypt_backup(\n            incorrect_backup_encrypted\n        )\n        result = compute_compensated_decryption_share_for_selection(\n            incorrect_backup_decrypted,\n            available_guardian.share_key(),\n            missing_guardian.share_key(),\n            first_selection,\n            self.context,\n        )\n\n        # Assert\n        self.assertIsNone(result)\n\n    def test_reconstruct_decryption_share(self):\n        # Arrange\n        available_guardians = self.guardians[0:2]\n        available_guardians_keys = [\n            guardian.share_key() for guardian in available_guardians\n        ]\n        missing_guardian = self.guardians[2]\n        missing_guardian_key = missing_guardian.share_key()\n        missing_guardian_backups = {\n            backup.designated_id: backup\n            for backup in missing_guardian.share_election_partial_key_backups()\n        }\n        tally = self.ciphertext_tally\n\n        # Act\n        compensated_shares: Dict[GuardianId, CompensatedDecryptionShare] = {\n            available_guardian.id: compute_compensated_decryption_share(\n                available_guardian.decrypt_backup(\n                    missing_guardian_backups[available_guardian.id]\n                ),\n                available_guardian.share_key(),\n                missing_guardian_key,\n                tally,\n                self.context,\n            )\n            for available_guardian in available_guardians\n        }\n\n        lagrange_coefficients = compute_lagrange_coefficients_for_guardians(\n            available_guardians_keys\n        )\n\n        share = reconstruct_decryption_share(\n            missing_guardian_key, tally, compensated_shares, lagrange_coefficients\n        )\n\n        # Assert\n        self.assertEqual(self.QUORUM, len(compensated_shares))\n        self.assertEqual(self.QUORUM, len(lagrange_coefficients))\n        self.assertIsNotNone(share)\n\n    def test_reconstruct_decryption_shares_for_ballot(self):\n        # Arrange\n        available_guardians = self.guardians[0:2]\n        available_guardians_keys = [\n            guardian.share_key() for guardian in available_guardians\n        ]\n        missing_guardian = self.guardians[2]\n        missing_guardian_key = missing_guardian.share_key()\n        missing_guardian_backups = {\n            backup.designated_id: backup\n            for backup in missing_guardian.share_election_partial_key_backups()\n        }\n        ballot = list(self.ciphertext_ballots.values())[0]\n\n        # Act\n        compensated_ballot_shares: Dict[GuardianId, CompensatedDecryptionShare] = {}\n        for available_guardian in available_guardians:\n            backup = available_guardian.decrypt_backup(\n                missing_guardian_backups[available_guardian.id]\n            )\n            compensated_share = compute_compensated_decryption_share_for_ballot(\n                backup,\n                missing_guardian_key,\n                available_guardian.share_key(),\n                ballot,\n                self.context,\n            )\n            if compensated_share:\n                compensated_ballot_shares[available_guardian.id] = compensated_share\n\n        lagrange_coefficients = compute_lagrange_coefficients_for_guardians(\n            available_guardians_keys\n        )\n\n        missing_ballot_share = reconstruct_decryption_share_for_ballot(\n            missing_guardian_key,\n            ballot,\n            compensated_ballot_shares,\n            lagrange_coefficients,\n        )\n\n        # Assert\n        self.assertEqual(self.QUORUM, len(lagrange_coefficients))\n        self.assertEqual(len(available_guardians), len(compensated_ballot_shares))\n        self.assertEqual(len(available_guardians), len(lagrange_coefficients))\n        self.assertIsNotNone(missing_ballot_share)\n\n    def test_reconstruct_decryption_share_for_ballot(self):\n        # Arrange\n        available_guardians = self.guardians[0:2]\n        available_guardians_keys = [\n            guardian.share_key() for guardian in available_guardians\n        ]\n        missing_guardian = self.guardians[2]\n        missing_guardian_key = missing_guardian.share_key()\n        missing_guardian_backups = {\n            backup.designated_id: backup\n            for backup in missing_guardian.share_election_partial_key_backups()\n        }\n        ballot = self.ciphertext_ballots[self.fake_spoiled_ballot.object_id]\n\n        # Act\n        compensated_shares: Dict[GuardianId, CompensatedDecryptionShare] = {\n            available_guardian.id: get_optional(\n                compute_compensated_decryption_share_for_ballot(\n                    available_guardian.decrypt_backup(\n                        missing_guardian_backups[available_guardian.id]\n                    ),\n                    missing_guardian_key,\n                    available_guardian.share_key(),\n                    ballot,\n                    self.context,\n                )\n            )\n            for available_guardian in available_guardians\n        }\n\n        lagrange_coefficients = compute_lagrange_coefficients_for_guardians(\n            available_guardians_keys\n        )\n\n        share = reconstruct_decryption_share_for_ballot(\n            missing_guardian_key, ballot, compensated_shares, lagrange_coefficients\n        )\n\n        # Assert\n        self.assertEqual(self.QUORUM, len(compensated_shares))\n        self.assertEqual(self.QUORUM, len(lagrange_coefficients))\n        self.assertIsNotNone(share)\n"
  },
  {
    "path": "tests/unit/electionguard/test_election_polynomial.py",
    "content": "from tests.base_test_case import BaseTestCase\nfrom electionguard.schnorr import make_schnorr_proof\nfrom electionguard.elgamal import ElGamalKeyPair\nfrom electionguard.group import rand_q\nfrom electionguard.election_polynomial import (\n    Coefficient,\n    compute_polynomial_coordinate,\n    ElectionPolynomial,\n    generate_polynomial,\n    verify_polynomial_coordinate,\n)\n\nfrom electionguard.group import ONE_MOD_P, ONE_MOD_Q, TWO_MOD_P, TWO_MOD_Q\n\nTEST_EXPONENT_MODIFIER = 1\nTEST_POLYNOMIAL_DEGREE = 3\n\n\nclass TestElectionPolynomial(BaseTestCase):\n    \"\"\"Election polynomial tests\"\"\"\n\n    def test_generate_polynomial(self):\n        # Act\n        polynomial = generate_polynomial(TEST_POLYNOMIAL_DEGREE)\n\n        # Assert\n        self.assertIsNotNone(polynomial)\n\n    def test_compute_polynomial_coordinate(self):\n        # create proofs\n        proof_one = make_schnorr_proof(ElGamalKeyPair(ONE_MOD_Q, ONE_MOD_P), rand_q())\n        proof_two = make_schnorr_proof(ElGamalKeyPair(TWO_MOD_Q, TWO_MOD_P), rand_q())\n\n        # Arrange\n        polynomial = ElectionPolynomial(\n            [\n                Coefficient(ONE_MOD_Q, ONE_MOD_P, proof_one),\n                Coefficient(TWO_MOD_Q, TWO_MOD_P, proof_two),\n            ]\n        )\n        # Act\n        value = compute_polynomial_coordinate(TEST_EXPONENT_MODIFIER, polynomial)\n\n        # Assert\n        self.assertIsNotNone(value)\n\n    def test_verify_polynomial_coordinate(self):\n        # Arrange\n        polynomial = generate_polynomial(TEST_POLYNOMIAL_DEGREE)\n\n        # Act\n        value = compute_polynomial_coordinate(TEST_EXPONENT_MODIFIER, polynomial)\n\n        # Assert\n        self.assertTrue(\n            verify_polynomial_coordinate(\n                value, TEST_EXPONENT_MODIFIER, polynomial.get_commitments()\n            )\n        )\n"
  },
  {
    "path": "tests/unit/electionguard/test_elgamal.py",
    "content": "from electionguard import ElGamalKeyPair\nfrom electionguard.elgamal import (\n    ElGamalPublicKey,\n    ElGamalSecretKey,\n    hashed_elgamal_encrypt,\n)\nfrom electionguard.group import ElementModQ\nfrom electionguard.byte_padding import add_padding, remove_padding\nfrom electionguard.utils import get_optional\nfrom tests.base_test_case import BaseTestCase\n\n\nclass TestElgamal(BaseTestCase):\n    \"\"\"Test decryption methods\"\"\"\n\n    def test_hashed_elgamal_with_session_key_that_starts_with_0(self) -> None:\n        kp = ElGamalKeyPair(\n            secret_key=ElGamalSecretKey(\"02\"),\n            public_key=ElGamalPublicKey(\"A147CA31DE0F48C1\"),\n        )\n        plaintext = b\"\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00\"\n\n        # these values produce a session_key 2B00, which produces encrypted\n        # data that begins with a 0 byte. When hashed_elgamal_encrypt() returns that\n        # data it calls bytes_to_hex() which truncates leading zero's. That produces\n        # 255 bytes instead of 256 bytes and decrypt() much pad a byte to account for it.\n        hmac_nonce = ElementModQ(\"D8E1\")\n        hmac_seed = ElementModQ(\"02F1\")\n\n        self.do_hashed_elgamal(kp, plaintext, hmac_nonce, hmac_seed)\n\n    def test_hashed_elgamal_with_session_key_that_starts_with_1(self) -> None:\n        kp = ElGamalKeyPair(\n            secret_key=ElGamalSecretKey(\"02\"),\n            public_key=ElGamalPublicKey(\"A147CA31DE0F48C1\"),\n        )\n        plaintext = b\"\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00\"\n\n        # these values produce a session_key EE19 which produces encrypted data that\n        # begins with a 1 byte and bytes_to_hex() does not truncate anything\n        hmac_nonce = ElementModQ(\"AB29\")\n        hmac_seed = ElementModQ(\"2179\")\n\n        self.do_hashed_elgamal(kp, plaintext, hmac_nonce, hmac_seed)\n\n    def do_hashed_elgamal(\n        self,\n        kp: ElGamalKeyPair,\n        plaintext: bytes,\n        hmac_nonce: ElementModQ,\n        hmac_seed: ElementModQ,\n    ) -> None:\n        padded_plaintext = add_padding(plaintext)\n        hmac = hashed_elgamal_encrypt(\n            padded_plaintext, hmac_nonce, kp.public_key, hmac_seed\n        )\n        decryption_bytes_padded = hmac.decrypt(kp.secret_key, hmac_seed)\n        self.assertIsNotNone(decryption_bytes_padded)\n\n        decryption_bytes = remove_padding(get_optional(decryption_bytes_padded))\n        self.assertEqual(plaintext, decryption_bytes)\n"
  },
  {
    "path": "tests/unit/electionguard/test_encrypt.py",
    "content": "import os\nfrom unittest.mock import patch\nfrom unittest import TestCase\nfrom electionguard import PrimeOption\n\nfrom electionguard.byte_padding import TruncationError\nfrom electionguard.elgamal import (\n    HashedElGamalCiphertext,\n    elgamal_keypair_from_secret,\n)\nfrom electionguard.encrypt import (\n    ContestData,\n    ContestErrorType,\n    contest_from,\n    encrypt_contest,\n)\nfrom electionguard.group import ONE_MOD_Q, TWO_MOD_Q, ElementModP, ElementModQ, rand_q\nfrom electionguard.manifest import (\n    SelectionDescription,\n    VoteVariationType,\n    ContestDescriptionWithPlaceholders,\n)\nfrom electionguard.serialize import to_raw\nfrom electionguard.utils import get_optional\n\n\ndef get_sample_contest_description() -> ContestDescriptionWithPlaceholders:\n    ballot_selections = [\n        SelectionDescription(\n            \"some-object-id-affirmative\", 0, \"some-candidate-id-affirmative\"\n        ),\n        SelectionDescription(\n            \"some-object-id-negative\", 1, \"some-candidate-id-negative\"\n        ),\n    ]\n    placeholder_selections = [\n        SelectionDescription(\n            \"some-object-id-placeholder\", 2, \"some-candidate-id-placeholder\"\n        )\n    ]\n    metadata = ContestDescriptionWithPlaceholders(\n        \"some-contest-object-id\",\n        0,\n        \"some-electoral-district-id\",\n        VoteVariationType.one_of_m,\n        1,\n        1,\n        \"some-referendum-contest-name\",\n        ballot_selections,\n        None,\n        None,\n        placeholder_selections,\n    )\n    return metadata\n\n\nclass TestEncrypt(TestCase):\n    \"\"\"Test encryption\"\"\"\n\n    def test_contest_data_conversion(self) -> None:\n        \"\"\"Test contest data encoding to padded to bytes then decoding.\"\"\"\n\n        # Arrange\n        error = ContestErrorType.OverVote\n        error_data = [\"overvote-id-1\", \"overvote-id-2\", \"overvote-id-3\"]\n        write_ins = {\n            \"writein-id-1\": \"Teri Dactyl\",\n            \"writein-id-2\": \"Allie Grater\",\n            \"writein-id-3\": \"Anna Littlical\",\n            \"writein-id-4\": \"Polly Wannakrakouer\",\n        }\n        overflow_error_data = [\"overflow-id\" * 50]\n\n        empty_contest_data = ContestData()\n        write_in_contest_data = ContestData(write_ins=write_ins)\n        overvote_contest_data = ContestData(error, error_data)\n        overvote_and_write_in_contest_data = ContestData(error, error_data, write_ins)\n        overflow_contest_data = ContestData(error, overflow_error_data, write_ins)\n\n        # Act & Assert\n        self._padding_cycle(empty_contest_data)\n        self._padding_cycle(write_in_contest_data)\n        self._padding_cycle(overvote_contest_data)\n        self._padding_cycle(overvote_and_write_in_contest_data)\n        self._padding_cycle(overflow_contest_data)\n\n    def _padding_cycle(self, data: ContestData) -> None:\n        \"\"\"Run full cycle of padding and unpadding.\"\"\"\n        EXPECTED_PADDED_LENGTH = 512\n\n        try:\n            padded = data.to_bytes()\n            unpadded = ContestData.from_bytes(padded)\n\n            self.assertEqual(EXPECTED_PADDED_LENGTH, len(padded))\n            self.assertEqual(data, unpadded)\n\n        except TruncationError:\n            # Validate JSON exceeds allowed length\n            json = to_raw(data)\n            self.assertLess(EXPECTED_PADDED_LENGTH, len(json))\n\n    def test_encrypt_simple_contest_referendum_succeeds(self) -> None:\n        # Arrange\n        keypair = get_optional(elgamal_keypair_from_secret(TWO_MOD_Q))\n        nonce = rand_q()\n        encryption_seed = ONE_MOD_Q\n        contest_description = get_sample_contest_description()\n        contest = contest_from(contest_description)\n        contest_hash = contest_description.crypto_hash()\n\n        # Act\n        encrypted_contest = encrypt_contest(\n            contest,\n            contest_description,\n            keypair.public_key,\n            encryption_seed,\n            nonce,\n            should_verify_proofs=True,\n        )\n\n        # Assert\n        self.assertIsNotNone(encrypted_contest)\n        if encrypted_contest is not None:\n            self.assertTrue(\n                encrypted_contest.is_valid_encryption(\n                    contest_hash, keypair.public_key, encryption_seed\n                )\n            )\n\n    def test_contest_encrypt_with_overvotes(self) -> None:\n\n        # Arrange\n        keypair = get_optional(elgamal_keypair_from_secret(TWO_MOD_Q))\n        nonce = rand_q()\n        encryption_seed = ONE_MOD_Q\n        contest_description = get_sample_contest_description()\n        contest = contest_from(contest_description)\n        contest_hash = contest_description.crypto_hash()\n\n        # Add Overvotes\n        for selection in contest.ballot_selections:\n            selection.vote = 1\n\n        # Act\n        encrypted_contest = encrypt_contest(\n            contest,\n            contest_description,\n            keypair.public_key,\n            encryption_seed,\n            nonce,\n            should_verify_proofs=True,\n        )\n\n        # Assert\n        self.assertIsNotNone(encrypted_contest)\n        self.assertIsNotNone(encrypted_contest.extended_data)\n        self.assertTrue(\n            encrypted_contest.is_valid_encryption(\n                contest_hash, keypair.public_key, encryption_seed\n            )\n        )\n\n        # Act\n        decrypted_data = get_optional(\n            encrypted_contest.extended_data.decrypt(keypair.secret_key, encryption_seed)\n        )\n        contest_data = ContestData.from_bytes(decrypted_data)\n\n        # Assert\n        self.assertIsNotNone(contest_data)\n        self.assertIsNotNone(contest_data.error)\n        self.assertIsNotNone(contest_data.error_data)\n        self.assertEqual(contest_data.error, ContestErrorType.OverVote)\n        self.assertGreater(len(contest_data.error_data), 0)\n\n    def test_contest_encrypt_with_write_ins(self):\n\n        # Arrange\n        keypair = get_optional(elgamal_keypair_from_secret(TWO_MOD_Q))\n        nonce = rand_q()\n        encryption_seed = ONE_MOD_Q\n        contest_description = get_sample_contest_description()\n        contest = contest_from(contest_description)\n        contest_hash = contest_description.crypto_hash()\n        write_in_value = \"write_in\"\n\n        # Add Write-ins\n        for selection in contest.ballot_selections:\n            selection.write_in = write_in_value\n\n        # Act\n        encrypted_contest = encrypt_contest(\n            contest,\n            contest_description,\n            keypair.public_key,\n            encryption_seed,\n            nonce,\n            should_verify_proofs=True,\n        )\n\n        # Assert\n        self.assertIsNotNone(encrypted_contest)\n        self.assertIsNotNone(encrypted_contest.extended_data)\n        self.assertTrue(\n            encrypted_contest.is_valid_encryption(\n                contest_hash, keypair.public_key, encryption_seed\n            )\n        )\n\n        # Act\n        decrypted_data = get_optional(\n            encrypted_contest.extended_data.decrypt(keypair.secret_key, encryption_seed)\n        )\n        contest_data = ContestData.from_bytes(decrypted_data)\n\n        # Assert\n        self.assertIsNotNone(contest_data)\n        self.assertIsNotNone(contest_data.write_ins)\n        if contest_data is not None and contest_data.write_ins is not None:\n            self.assertGreater(len(contest_data.write_ins), 0)\n            for write_in in contest_data.write_ins.values():\n                self.assertEqual(write_in, write_in_value)\n\n    @patch.dict(os.environ, {\"PRIME_OPTION\": PrimeOption.Standard.value})\n    def test_contest_data_integration(self) -> None:\n        \"\"\"Contest data encryption done with production primes to match other repositories.\"\"\"\n\n        # Arrange\n        keypair = get_optional(\n            elgamal_keypair_from_secret(\n                ElementModQ(\n                    \"094CDA6CEB3332D62438B6D37BBA774D23C420FA019368671AD330AD50456603\"\n                )\n            )\n        )\n        encryption_seed = ElementModQ(\n            \"6E418518C6C244CA58399C0F47A9C761BAE7B876F8F5360D8D15FCFF26A42BAA\"\n        )\n\n        # pylint: disable=line-too-long\n        encrypted_contest_data = HashedElGamalCiphertext(\n            ElementModP(\n                \"C102BAB526517D74FE5D5C249E7F422993C0306C40A9398FBAD01A0D3547B50BDFD77C6EFC187C7B1FD7918A0B3C2A2FB0A3776A7240F9A75410569379B3D16877B547F52E79542C1129F6E369F2D006D0A1AA3919F0228CA07F5C9A4DFD1118A606AA4B7000F9EDC65963F130663FD4F7246F7CFE7A38F1E1DC9BC0698CAB881DCD5A75E6D7165B329C28D80B719D7A2ED50031A2448A4528275FF161F541CFE304A28CBE7193A4BF8676B2D4F2DE68F175C5B4BFD14B4B1D9868D00E0BD95B6491C96460159DEABF85239B10A7C86B3D975EF58BBF833C6ABFFF223DAF78C1AE4C6F64D084C4118F3B5A2618628FA18852BAB55DCE95C04FFCBBAF582D75C7B8B830424C74A8F8EACD154300FD67CF753EE14FCE94DDED95F1DD2C1386D92B3FF03A9D6EDEE0F67EC80C72E6425B4EA1C17D7B9CC5B2165905373A4E304496462CE2BA077F195302A39C52F0077CA682BC718074F928040D1A36F585AC187A741F51C843C5ED88BC5FB8B86ED96C42BCF84EDF833489D7D3AC407C6D0740CC94BA1D5B885EB430CE8C6017F8660A6C72F4378BF133AA663DBA36CAB967AAC0F7738478110ECEABAE3E914CB7A796C5394F7DF150940BEA43264765B34851ADE4E5F1F213C25DCF66D35BE92611555D8C05ACFDF1AC5CA82B7D7F0D9BE49596F8B7F3269D9887F40B4BAB5C3D2BA7049B6D2119C3D0D01501836203412869E0\"\n            ),\n            \"F8E994D157A065A1DB2DA5E38645C283F7CCB339E13F0DE29B83A4EFA2F4366C626FC8E318AF81DCB2E6083A598F8916A5FEEC3C1A1B8EBEB4081F3CB92FA86E000B4994B77EE173072D796D21EE771F4D8F50E7DC50A7945E35059F893DD0A67C53DF5A3439A89E990C5B7568912CD2655B39E943511E1B0DF8A8E1FEF4EAC3923A5B5DDF1A658335E97AA6EB12E4EEE1394D91548F3E8446E9BBF4207D873F54298B446A7D689FF60A6F60B3FC6B8319EC17FA424F0461949CD49B764C6360AC0D492696E43EE83A6A7CE7AEA4DDBA206F365AA81E918F63709DE796F0338CCD311360D97CDC821506D3EDB434922264966B8AF7E304A403E18384DDCF53AEF1FFC19A66FBCD9C2D04EFC8F2D456BE52DB9C460E3CA10AC4ABFE0B726E19A715546F1CD9CA89C57ED52DA9D78C30BEFE5FE99A8BDEA33B7C06EDFD4E92D514661CD55B99B54E5C468118E16F4827F78FB381845B093F202111E3B84CFCF8DAEE7948BA57698475F3EBC3729559835BF63AAB0F5659019965A2F0CF55E953B1CD37BCBED8EA0D5F161D461E03031BA7D0B042B978F7F6776DDFBCAA7145DE30BA24C29BDFA05C7CCF54D7DD58E75143A16F8619053FCF4DE7BDCCA031F0873A65ACCC56FE78F32B8FC192D2106CF1A1E5339A5C5657E6703D7F30F908CEEF05A84C67C426B187CBC1599FB334307146EAECB16774C5CB7630F4CB093E840086\",\n            \"BBCDE57B7E92BB8607696E09FE629A2B9665D809649B751333023983C001C191\",\n        )\n\n        # Act\n        decrypted_contest_data = encrypted_contest_data.decrypt(\n            keypair.secret_key, encryption_seed\n        )\n\n        contest_data = ContestData.from_bytes(decrypted_contest_data)\n\n        self.assertIsNotNone(contest_data)\n        self.assertEqual(contest_data.error, ContestErrorType.OverVote)\n        self.assertEqual(len(contest_data.error_data), 3)\n        self.assertEqual(len(contest_data.write_ins), 1)\n        self.assertEqual(\n            contest_data.write_ins[\"write-in-selection\"], \"Susan B. Anthony\"\n        )\n"
  },
  {
    "path": "tests/unit/electionguard/test_guardian.py",
    "content": "# pylint: disable=too-many-public-methods\n\n\nfrom tests.base_test_case import BaseTestCase\n\nfrom electionguard.guardian import Guardian\n\nNUMBER_OF_GUARDIANS = 2\nQUORUM = 2\n\nSENDER_GUARDIAN_ID = \"Test Guardian 1\"\nSENDER_SEQUENCE_ORDER = 1\n\nRECIPIENT_GUARDIAN_ID = \"Test Guardian 2\"\nRECIPIENT_SEQUENCE_ORDER = 2\n\nALTERNATE_VERIFIER_ID = \"Test Verifier\"\nALTERNATE_VERIFIER_SEQUENCE_ORDER = 3\n\nELECTION_PUBLIC_KEY = \"\"\n\n\nclass TestGuardian(BaseTestCase):\n    \"\"\"Guardian tests\"\"\"\n\n    def test_import_from_guardian_private_record(self) -> None:\n        # Arrange\n        guardian_expected = Guardian.from_nonce(\n            SENDER_GUARDIAN_ID, SENDER_SEQUENCE_ORDER, NUMBER_OF_GUARDIANS, QUORUM\n        )\n        private_guardian_record = guardian_expected.export_private_data()\n\n        # Act\n        guardian_actual = Guardian.from_private_record(\n            private_guardian_record, NUMBER_OF_GUARDIANS, QUORUM\n        )\n\n        # Assert\n        # pylint: disable=protected-access\n        self.assertEqual(\n            guardian_actual._election_keys, guardian_expected._election_keys\n        )\n        self.assertEqual(\n            guardian_actual._guardian_election_public_keys,\n            guardian_expected._guardian_election_public_keys,\n        )\n        self.assertEqual(\n            guardian_actual._guardian_election_partial_key_backups,\n            guardian_expected._guardian_election_partial_key_backups,\n        )\n\n    def test_set_ceremony_details(self) -> None:\n        # Arrange\n        guardian = Guardian.from_nonce(\n            SENDER_GUARDIAN_ID, SENDER_SEQUENCE_ORDER, NUMBER_OF_GUARDIANS, QUORUM\n        )\n        expected_number_of_guardians = 10\n        expected_quorum = 4\n\n        # Act\n        guardian.set_ceremony_details(expected_number_of_guardians, expected_quorum)\n\n        # Assert\n        self.assertEqual(\n            expected_number_of_guardians, guardian.ceremony_details.number_of_guardians\n        )\n        self.assertEqual(expected_quorum, guardian.ceremony_details.quorum)\n\n    def test_share_key(self) -> None:\n        # Arrange\n        guardian = Guardian.from_nonce(\n            SENDER_GUARDIAN_ID, SENDER_SEQUENCE_ORDER, NUMBER_OF_GUARDIANS, QUORUM\n        )\n\n        # Act\n        key = guardian.share_key()\n\n        # Assert\n        self.assertIsNotNone(key)\n        self.assertIsNotNone(key.key)\n        self.assertEqual(key.owner_id, SENDER_GUARDIAN_ID)\n        for proof in key.coefficient_proofs:\n            self.assertTrue(proof.is_valid())\n\n    def test_save_guardian_key(self) -> None:\n        # Arrange\n        guardian = Guardian.from_nonce(\n            SENDER_GUARDIAN_ID, SENDER_SEQUENCE_ORDER, NUMBER_OF_GUARDIANS, QUORUM\n        )\n        other_guardian = Guardian.from_nonce(\n            RECIPIENT_GUARDIAN_ID, RECIPIENT_SEQUENCE_ORDER, NUMBER_OF_GUARDIANS, QUORUM\n        )\n        key = other_guardian.share_key()\n\n        # Act\n        guardian.save_guardian_key(key)\n\n        # Assert\n        self.assertTrue(guardian.all_guardian_keys_received())\n\n    def test_all_guardian_keys_received(self) -> None:\n        # Arrange\n        guardian = Guardian.from_nonce(\n            SENDER_GUARDIAN_ID, SENDER_SEQUENCE_ORDER, NUMBER_OF_GUARDIANS, QUORUM\n        )\n        other_guardian = Guardian.from_nonce(\n            RECIPIENT_GUARDIAN_ID, RECIPIENT_SEQUENCE_ORDER, NUMBER_OF_GUARDIANS, QUORUM\n        )\n        key = other_guardian.share_key()\n\n        # Act\n        self.assertFalse(guardian.all_guardian_keys_received())\n        guardian.save_guardian_key(key)\n\n        # Assert\n        self.assertTrue(guardian.all_guardian_keys_received())\n\n    def test_share_backups(self) -> None:\n        # Arrange\n        guardian = Guardian.from_nonce(\n            SENDER_GUARDIAN_ID, SENDER_SEQUENCE_ORDER, NUMBER_OF_GUARDIANS, QUORUM\n        )\n        other_guardian = Guardian.from_nonce(\n            RECIPIENT_GUARDIAN_ID, RECIPIENT_SEQUENCE_ORDER, NUMBER_OF_GUARDIANS, QUORUM\n        )\n        guardian.save_guardian_key(other_guardian.share_key())\n\n        # Act\n        empty_key_backup = guardian.share_election_partial_key_backup(other_guardian.id)\n\n        # Assert\n        self.assertIsNone(empty_key_backup)\n\n        # Act\n        guardian.generate_election_partial_key_backups()\n        key_backup = guardian.share_election_partial_key_backup(other_guardian.id)\n\n        # Assert\n        self.assertIsNotNone(key_backup)\n        self.assertIsNotNone(key_backup.encrypted_coordinate)\n        self.assertEqual(key_backup.owner_id, guardian.id)\n        self.assertEqual(key_backup.designated_id, other_guardian.id)\n\n    def test_save_election_partial_key_backup(self) -> None:\n        # Arrange\n        guardian = Guardian.from_nonce(\n            SENDER_GUARDIAN_ID, SENDER_SEQUENCE_ORDER, NUMBER_OF_GUARDIANS, QUORUM\n        )\n        other_guardian = Guardian.from_nonce(\n            RECIPIENT_GUARDIAN_ID, RECIPIENT_SEQUENCE_ORDER, NUMBER_OF_GUARDIANS, QUORUM\n        )\n        guardian.save_guardian_key(other_guardian.share_key())\n        guardian.generate_election_partial_key_backups()\n        key_backup = guardian.share_election_partial_key_backup(other_guardian.id)\n\n        # Act\n        other_guardian.save_election_partial_key_backup(key_backup)\n\n        # Assert\n        self.assertTrue(other_guardian.all_election_partial_key_backups_received())\n\n    def test_all_election_partial_key_backups_received(self) -> None:\n        # Arrange\n        # Round 1\n        guardian = Guardian.from_nonce(\n            SENDER_GUARDIAN_ID, SENDER_SEQUENCE_ORDER, NUMBER_OF_GUARDIANS, QUORUM\n        )\n        other_guardian = Guardian.from_nonce(\n            RECIPIENT_GUARDIAN_ID, RECIPIENT_SEQUENCE_ORDER, NUMBER_OF_GUARDIANS, QUORUM\n        )\n        guardian.save_guardian_key(other_guardian.share_key())\n        other_guardian.save_guardian_key(guardian.share_key())\n\n        # Round 2\n        guardian.generate_election_partial_key_backups()\n        key_backup = guardian.share_election_partial_key_backup(other_guardian.id)\n\n        # Assert\n        self.assertFalse(other_guardian.all_election_partial_key_backups_received())\n        other_guardian.save_election_partial_key_backup(key_backup)\n        self.assertTrue(other_guardian.all_election_partial_key_backups_received())\n\n    def test_verify_election_partial_key_backup(self) -> None:\n        # Arrange\n        # Round 1\n        guardian = Guardian.from_nonce(\n            SENDER_GUARDIAN_ID, SENDER_SEQUENCE_ORDER, NUMBER_OF_GUARDIANS, QUORUM\n        )\n        other_guardian = Guardian.from_nonce(\n            RECIPIENT_GUARDIAN_ID, RECIPIENT_SEQUENCE_ORDER, NUMBER_OF_GUARDIANS, QUORUM\n        )\n        guardian.save_guardian_key(other_guardian.share_key())\n        other_guardian.save_guardian_key(guardian.share_key())\n\n        # Round 2\n        guardian.generate_election_partial_key_backups()\n        key_backup = guardian.share_election_partial_key_backup(other_guardian.id)\n        other_guardian.save_election_partial_key_backup(key_backup)\n\n        # Act\n        verification = other_guardian.verify_election_partial_key_backup(\n            guardian.id,\n        )\n\n        # Assert\n        self.assertIsNotNone(verification)\n        self.assertEqual(verification.owner_id, guardian.id)\n        self.assertEqual(verification.designated_id, other_guardian.id)\n        self.assertEqual(verification.verifier_id, other_guardian.id)\n        self.assertTrue(verification.verified)\n\n    def test_verify_election_partial_key_challenge(self) -> None:\n        # Arrange\n        guardian = Guardian.from_nonce(\n            SENDER_GUARDIAN_ID, SENDER_SEQUENCE_ORDER, NUMBER_OF_GUARDIANS, QUORUM\n        )\n        other_guardian = Guardian.from_nonce(\n            RECIPIENT_GUARDIAN_ID, RECIPIENT_SEQUENCE_ORDER, NUMBER_OF_GUARDIANS, QUORUM\n        )\n        alternate_verifier = Guardian.from_nonce(\n            ALTERNATE_VERIFIER_ID,\n            ALTERNATE_VERIFIER_SEQUENCE_ORDER,\n            NUMBER_OF_GUARDIANS,\n            QUORUM,\n        )\n        guardian.save_guardian_key(other_guardian.share_key())\n        guardian.generate_election_partial_key_backups()\n        challenge = guardian.publish_election_backup_challenge(other_guardian.id)\n\n        # Act\n        verification = alternate_verifier.verify_election_partial_key_challenge(\n            challenge\n        )\n\n        # Assert\n        self.assertIsNotNone(verification)\n        self.assertEqual(verification.owner_id, guardian.id)\n        self.assertEqual(verification.designated_id, other_guardian.id)\n        self.assertEqual(verification.verifier_id, alternate_verifier.id)\n        self.assertTrue(verification.verified)\n\n    def test_publish_election_backup_challenge(self) -> None:\n        # Arrange\n        guardian = Guardian.from_nonce(\n            SENDER_GUARDIAN_ID, SENDER_SEQUENCE_ORDER, NUMBER_OF_GUARDIANS, QUORUM\n        )\n        other_guardian = Guardian.from_nonce(\n            RECIPIENT_GUARDIAN_ID, RECIPIENT_SEQUENCE_ORDER, NUMBER_OF_GUARDIANS, QUORUM\n        )\n        guardian.save_guardian_key(other_guardian.share_key())\n        guardian.generate_election_partial_key_backups()\n\n        # Act\n        challenge = guardian.publish_election_backup_challenge(other_guardian.id)\n\n        # Assert\n        self.assertIsNotNone(challenge)\n        self.assertIsNotNone(challenge.value)\n        self.assertEqual(challenge.owner_id, guardian.id)\n        self.assertEqual(challenge.designated_id, other_guardian.id)\n        self.assertEqual(len(challenge.coefficient_commitments), QUORUM)\n        self.assertEqual(len(challenge.coefficient_proofs), QUORUM)\n        for proof in challenge.coefficient_proofs:\n            proof.is_valid()\n\n    def test_save_election_partial_key_verification(self) -> None:\n        # Arrange\n        # Round 1\n        guardian = Guardian.from_nonce(\n            SENDER_GUARDIAN_ID, SENDER_SEQUENCE_ORDER, NUMBER_OF_GUARDIANS, QUORUM\n        )\n        other_guardian = Guardian.from_nonce(\n            RECIPIENT_GUARDIAN_ID, RECIPIENT_SEQUENCE_ORDER, NUMBER_OF_GUARDIANS, QUORUM\n        )\n        guardian.save_guardian_key(other_guardian.share_key())\n        other_guardian.save_guardian_key(guardian.share_key())\n\n        # Round 2\n        guardian.generate_election_partial_key_backups()\n        key_backup = guardian.share_election_partial_key_backup(RECIPIENT_GUARDIAN_ID)\n        other_guardian.save_election_partial_key_backup(key_backup)\n        verification = other_guardian.verify_election_partial_key_backup(\n            SENDER_GUARDIAN_ID,\n        )\n\n        # Act\n        guardian.save_election_partial_key_verification(verification)\n\n        # Assert\n        self.assertTrue(guardian.all_election_partial_key_backups_verified)\n\n    def test_all_election_partial_key_backups_verified(self) -> None:\n        # Arrange\n        # Round 1\n        guardian = Guardian.from_nonce(\n            SENDER_GUARDIAN_ID, SENDER_SEQUENCE_ORDER, NUMBER_OF_GUARDIANS, QUORUM\n        )\n        other_guardian = Guardian.from_nonce(\n            RECIPIENT_GUARDIAN_ID, RECIPIENT_SEQUENCE_ORDER, NUMBER_OF_GUARDIANS, QUORUM\n        )\n        guardian.save_guardian_key(other_guardian.share_key())\n        other_guardian.save_guardian_key(guardian.share_key())\n\n        # Round 2\n        guardian.generate_election_partial_key_backups()\n        key_backup = guardian.share_election_partial_key_backup(other_guardian.id)\n        other_guardian.save_election_partial_key_backup(key_backup)\n        verification = other_guardian.verify_election_partial_key_backup(\n            guardian.id,\n        )\n        guardian.save_election_partial_key_verification(verification)\n\n        # Act\n        all_saved = guardian.all_election_partial_key_backups_verified()\n\n        # Assert\n        self.assertTrue(all_saved)\n\n    def test_publish_joint_key(self) -> None:\n        # Arrange\n        # Round 1\n        guardian = Guardian.from_nonce(\n            SENDER_GUARDIAN_ID, SENDER_SEQUENCE_ORDER, NUMBER_OF_GUARDIANS, QUORUM\n        )\n        other_guardian = Guardian.from_nonce(\n            RECIPIENT_GUARDIAN_ID, RECIPIENT_SEQUENCE_ORDER, NUMBER_OF_GUARDIANS, QUORUM\n        )\n        guardian.save_guardian_key(other_guardian.share_key())\n        other_guardian.save_guardian_key(guardian.share_key())\n\n        # Round 2\n        guardian.generate_election_partial_key_backups()\n        key_backup = guardian.share_election_partial_key_backup(other_guardian.id)\n        other_guardian.save_election_partial_key_backup(key_backup)\n        verification = other_guardian.verify_election_partial_key_backup(\n            guardian.id,\n        )\n\n        # Act\n        joint_key = guardian.publish_joint_key()\n\n        # Assert\n        self.assertIsNone(joint_key)\n\n        # Act\n        guardian.save_guardian_key(other_guardian.share_key())\n        joint_key = guardian.publish_joint_key()\n\n        # Assert\n        self.assertIsNone(joint_key)\n\n        # Act\n        guardian.save_election_partial_key_verification(verification)\n        joint_key = guardian.publish_joint_key()\n\n        # Assert\n        self.assertIsNotNone(joint_key)\n        self.assertNotEqual(joint_key, guardian.share_key().key)\n"
  },
  {
    "path": "tests/unit/electionguard/test_hmac.py",
    "content": "from electionguard.hmac import get_hmac\n\nfrom tests.base_test_case import BaseTestCase\n\n\nclass TestHmac(BaseTestCase):\n    \"\"\"HMAC tests\"\"\"\n\n    def test_get_hmac(self) -> None:\n        \"\"\"\n        Validate that hmac can be generated correctly.\n        \"\"\"\n\n        # Arrange\n        key = b\"mock_key\"\n        message = b\"mock_message\"\n        length = 256\n        start = 1\n\n        # Act\n        hmac_1 = get_hmac(key, message)\n        hmac_2 = get_hmac(key, message, length)\n        hmac_3 = get_hmac(key, message, length, start)\n\n        # Assert\n        self.assertIsNotNone(hmac_1)\n        self.assertIsNotNone(hmac_2)\n        self.assertIsNotNone(hmac_3)\n"
  },
  {
    "path": "tests/unit/electionguard/test_key_ceremony.py",
    "content": "import os\nfrom unittest.mock import patch\nfrom tests.base_test_case import BaseTestCase\n\nfrom electionguard.constants import (\n    PrimeOption,\n)\n\nfrom electionguard.key_ceremony import (\n    generate_election_key_pair,\n    generate_election_partial_key_backup,\n    verify_election_partial_key_backup,\n    generate_election_partial_key_challenge,\n    verify_election_partial_key_challenge,\n    combine_election_public_keys,\n)\n\nNUMBER_OF_GUARDIANS = 5\nQUORUM = 3\n\nSENDER_GUARDIAN_ID = \"Test Guardian 1\"\nSENDER_SEQUENCE_ORDER = 1\n\nRECIPIENT_GUARDIAN_ID = \"Test Guardian 2\"\nRECIPIENT_SEQUENCE_ORDER = 2\nRECIPIENT_KEY_PAIR = generate_election_key_pair(\n    RECIPIENT_GUARDIAN_ID, RECIPIENT_SEQUENCE_ORDER, QUORUM\n)\nRECIPIENT_KEY = RECIPIENT_KEY_PAIR.share()\n\nALTERNATE_VERIFIER_GUARDIAN_ID = \"Test Guardian 3\"\n\n\n@patch.dict(os.environ, {\"PRIME_OPTION\": PrimeOption.Standard.value})\nclass TestKeyCeremony(BaseTestCase):\n    \"\"\"Key ceremony tests\"\"\"\n\n    def test_generate_election_key_pair(self) -> None:\n        # Act\n        election_key_pair = generate_election_key_pair(\n            SENDER_GUARDIAN_ID, SENDER_SEQUENCE_ORDER, QUORUM\n        )\n\n        # Assert\n        self.assertIsNotNone(election_key_pair)\n        self.assertIsNotNone(election_key_pair.key_pair.public_key)\n        self.assertIsNotNone(election_key_pair.key_pair.secret_key)\n        self.assertIsNotNone(election_key_pair.polynomial)\n\n        self.assertEqual(len(election_key_pair.polynomial.coefficients), QUORUM)\n        for coefficient in election_key_pair.polynomial.coefficients:\n            self.assertTrue(coefficient.proof.is_valid())\n\n    def test_generate_election_partial_key_backup(self) -> None:\n        # Arrange\n        election_key_pair = generate_election_key_pair(\n            SENDER_GUARDIAN_ID, SENDER_SEQUENCE_ORDER, QUORUM\n        )\n\n        # Act\n        backup = generate_election_partial_key_backup(\n            SENDER_GUARDIAN_ID,\n            election_key_pair.polynomial,\n            RECIPIENT_KEY,\n        )\n\n        # Assert\n        self.assertIsNotNone(backup)\n        self.assertEqual(backup.designated_id, RECIPIENT_GUARDIAN_ID)\n        self.assertEqual(backup.designated_sequence_order, RECIPIENT_SEQUENCE_ORDER)\n        self.assertIsNotNone(backup.encrypted_coordinate)\n\n    def test_encrypt_then_verify_coordinate(self) -> None:\n        # Arrange\n        sender_key_pair = generate_election_key_pair(\n            SENDER_GUARDIAN_ID, SENDER_SEQUENCE_ORDER, QUORUM\n        )\n\n        # Act\n        partial_key_backup = generate_election_partial_key_backup(\n            SENDER_GUARDIAN_ID,\n            sender_key_pair.polynomial,\n            RECIPIENT_KEY,\n        )\n        verification = verify_election_partial_key_backup(\n            RECIPIENT_GUARDIAN_ID,\n            partial_key_backup,\n            sender_key_pair.share(),\n            RECIPIENT_KEY_PAIR,\n        )\n\n        # Assert\n        self.assertTrue(verification.verified)\n\n    def test_verify_election_partial_key_backup(self) -> None:\n        # Arrange\n        sender_election_key_pair = generate_election_key_pair(\n            SENDER_GUARDIAN_ID, SENDER_SEQUENCE_ORDER, QUORUM\n        )\n\n        partial_key_backup = generate_election_partial_key_backup(\n            SENDER_GUARDIAN_ID,\n            sender_election_key_pair.polynomial,\n            RECIPIENT_KEY,\n        )\n\n        # Act\n        verification = verify_election_partial_key_backup(\n            RECIPIENT_GUARDIAN_ID,\n            partial_key_backup,\n            sender_election_key_pair.share(),\n            RECIPIENT_KEY_PAIR,\n        )\n\n        # Assert\n        self.assertIsNotNone(verification)\n        self.assertEqual(verification.owner_id, SENDER_GUARDIAN_ID)\n        self.assertEqual(verification.designated_id, RECIPIENT_GUARDIAN_ID)\n        self.assertEqual(verification.verifier_id, RECIPIENT_GUARDIAN_ID)\n        self.assertTrue(verification.verified)\n\n    def test_generate_election_partial_key_challenge(self) -> None:\n        # Arrange\n        sender_election_key_pair = generate_election_key_pair(\n            SENDER_GUARDIAN_ID, SENDER_SEQUENCE_ORDER, QUORUM\n        )\n        partial_key_backup = generate_election_partial_key_backup(\n            SENDER_GUARDIAN_ID,\n            sender_election_key_pair.polynomial,\n            RECIPIENT_KEY,\n        )\n\n        # Act\n        challenge = generate_election_partial_key_challenge(\n            partial_key_backup, sender_election_key_pair.polynomial\n        )\n\n        # Assert\n        self.assertIsNotNone(challenge)\n        self.assertEqual(challenge.designated_id, RECIPIENT_GUARDIAN_ID)\n        self.assertEqual(challenge.designated_sequence_order, RECIPIENT_SEQUENCE_ORDER)\n        self.assertIsNotNone(challenge.value)\n        self.assertEqual(len(challenge.coefficient_commitments), QUORUM)\n        self.assertEqual(len(challenge.coefficient_proofs), QUORUM)\n        for proof in challenge.coefficient_proofs:\n            self.assertTrue(proof.is_valid())\n\n    def test_verify_election_partial_key_challenge(self) -> None:\n        # Arrange\n        sender_election_key_pair = generate_election_key_pair(\n            SENDER_GUARDIAN_ID, SENDER_SEQUENCE_ORDER, QUORUM\n        )\n        partial_key_backup = generate_election_partial_key_backup(\n            SENDER_GUARDIAN_ID,\n            sender_election_key_pair.polynomial,\n            RECIPIENT_KEY,\n        )\n        challenge = generate_election_partial_key_challenge(\n            partial_key_backup, sender_election_key_pair.polynomial\n        )\n\n        # Act\n        verification = verify_election_partial_key_challenge(\n            ALTERNATE_VERIFIER_GUARDIAN_ID, challenge\n        )\n\n        # Assert\n        self.assertIsNotNone(verification)\n        self.assertEqual(verification.owner_id, SENDER_GUARDIAN_ID)\n        self.assertEqual(verification.designated_id, RECIPIENT_GUARDIAN_ID)\n        self.assertEqual(verification.verifier_id, ALTERNATE_VERIFIER_GUARDIAN_ID)\n        self.assertTrue(verification.verified)\n\n    def test_combine_election_public_keys(self) -> None:\n        # Arrange\n        random_key = generate_election_key_pair(\n            RECIPIENT_GUARDIAN_ID, SENDER_SEQUENCE_ORDER, QUORUM\n        ).share()\n        random_key_two = generate_election_key_pair(\n            SENDER_GUARDIAN_ID, RECIPIENT_SEQUENCE_ORDER, QUORUM\n        ).share()\n\n        # Act\n        joint_key = combine_election_public_keys([random_key, random_key_two])\n\n        # Assert\n        self.assertIsNotNone(joint_key)\n        self.assertNotEqual(joint_key.joint_public_key, random_key.key)\n        self.assertNotEqual(joint_key.joint_public_key, random_key_two.key)\n"
  },
  {
    "path": "tests/unit/electionguard/test_key_ceremony_mediator.py",
    "content": "from typing import List\nfrom tests.base_test_case import BaseTestCase\n\nfrom electionguard.guardian import Guardian\nfrom electionguard.key_ceremony import (\n    CeremonyDetails,\n    ElectionPartialKeyVerification,\n)\nfrom electionguard.key_ceremony_mediator import KeyCeremonyMediator, GuardianPair\nfrom electionguard_tools.helpers.key_ceremony_orchestrator import (\n    KeyCeremonyOrchestrator,\n)\n\nNUMBER_OF_GUARDIANS = 2\nQUORUM = 2\nCEREMONY_DETAILS = CeremonyDetails(NUMBER_OF_GUARDIANS, QUORUM)\nGUARDIAN_1_ID = \"Guardian 1\"\nGUARDIAN_2_ID = \"Guardian 2\"\n\n\nclass TestKeyCeremonyMediator(BaseTestCase):\n    \"\"\"Key ceremony mediator tests\"\"\"\n\n    GUARDIAN_1: Guardian\n    GUARDIAN_2: Guardian\n    GUARDIANS: List[Guardian] = []\n\n    def setUp(self) -> None:\n        super().setUp()\n        self.GUARDIAN_1 = Guardian.from_nonce(\n            GUARDIAN_1_ID, 1, NUMBER_OF_GUARDIANS, QUORUM\n        )\n        self.GUARDIAN_2 = Guardian.from_nonce(\n            GUARDIAN_2_ID, 2, NUMBER_OF_GUARDIANS, QUORUM\n        )\n        self.GUARDIANS = [self.GUARDIAN_1, self.GUARDIAN_2]\n\n    def test_reset(self) -> None:\n        # Arrange\n        mediator = KeyCeremonyMediator(\"mediator_reset\", CEREMONY_DETAILS)\n        new_ceremony_details = CeremonyDetails(3, 3)\n\n        mediator.reset(new_ceremony_details)\n        self.assertEqual(mediator.ceremony_details, new_ceremony_details)\n\n    def test_take_attendance(self) -> None:\n        \"\"\"Round 1: Mediator takes attendance and guardians announce\"\"\"\n\n        # Arrange\n        mediator = KeyCeremonyMediator(\"mediator_attendance\", CEREMONY_DETAILS)\n\n        # Act\n        mediator.announce(self.GUARDIAN_1.share_key())\n\n        # Assert\n        self.assertFalse(mediator.all_guardians_announced())\n\n        # Act\n        mediator.announce(self.GUARDIAN_2.share_key())\n\n        # Assert\n        self.assertTrue(mediator.all_guardians_announced())\n\n        # Act\n        guardian_key_sets = mediator.share_announced()\n\n        # Assert\n        self.assertIsNotNone(guardian_key_sets)\n        self.assertEqual(len(guardian_key_sets), NUMBER_OF_GUARDIANS)\n\n    def test_exchange_of_backups(self) -> None:\n        \"\"\"Round 2: Exchange of election partial key backups\"\"\"\n\n        # Arrange\n        mediator = KeyCeremonyMediator(\"mediator_backups_exchange\", CEREMONY_DETAILS)\n        KeyCeremonyOrchestrator.perform_round_1(self.GUARDIANS, mediator)\n\n        # Round 2 - Guardians Only\n        self.GUARDIAN_1.generate_election_partial_key_backups()\n        self.GUARDIAN_2.generate_election_partial_key_backups()\n        backup_from_1_for_2 = self.GUARDIAN_1.share_election_partial_key_backup(\n            GUARDIAN_2_ID\n        )\n        backup_from_2_for_1 = self.GUARDIAN_2.share_election_partial_key_backup(\n            GUARDIAN_1_ID\n        )\n\n        # Act\n        mediator.receive_backups([backup_from_1_for_2])\n\n        # Assert\n        self.assertFalse(mediator.all_backups_available())\n\n        # Act\n        mediator.receive_backups([backup_from_2_for_1])\n\n        # Assert\n        self.assertTrue(mediator.all_backups_available())\n\n        # Act\n        guardian1_backups = mediator.share_backups(GUARDIAN_1_ID)\n        guardian2_backups = mediator.share_backups(GUARDIAN_2_ID)\n\n        # Assert\n        self.assertIsNotNone(guardian1_backups)\n        self.assertIsNotNone(guardian2_backups)\n        self.assertEqual(len(guardian1_backups), 1)\n        self.assertEqual(len(guardian2_backups), 1)\n        for backup in guardian1_backups:\n            self.assertEqual(backup.designated_id, GUARDIAN_1_ID)\n        for backup in guardian2_backups:\n            self.assertEqual(backup.designated_id, GUARDIAN_2_ID)\n        self.assertEqual(guardian1_backups[0], backup_from_2_for_1)\n        self.assertEqual(guardian2_backups[0], backup_from_1_for_2)\n\n    # Partial Key Verifications\n    def test_partial_key_backup_verification_success(self) -> None:\n        \"\"\"\n        Test for the happy path of the verification process where each key is successfully verified and no bad actors.\n        \"\"\"\n        # Arrange\n        mediator = KeyCeremonyMediator(\"mediator_verification\", CEREMONY_DETAILS)\n        KeyCeremonyOrchestrator.perform_round_1(self.GUARDIANS, mediator)\n        KeyCeremonyOrchestrator.perform_round_2(self.GUARDIANS, mediator)\n\n        # Round 3 - Guardians only\n        verification1 = self.GUARDIAN_1.verify_election_partial_key_backup(\n            GUARDIAN_2_ID,\n        )\n        verification2 = self.GUARDIAN_2.verify_election_partial_key_backup(\n            GUARDIAN_1_ID,\n        )\n\n        # Act\n        mediator.receive_backup_verifications([verification1])\n\n        # Assert\n        self.assertFalse(mediator.get_verification_state().all_sent)\n        self.assertFalse(mediator.all_backups_verified())\n        self.assertIsNone(mediator.publish_joint_key())\n\n        # Act\n        mediator.receive_backup_verifications([verification2])\n        joint_key = mediator.publish_joint_key()\n\n        # Assert\n        self.assertTrue(mediator.get_verification_state().all_sent)\n        self.assertTrue(mediator.all_backups_verified())\n        self.assertIsNotNone(joint_key)\n\n    def test_partial_key_backup_verification_failure(self) -> None:\n        \"\"\"\n        In this case, the recipient guardian does not correctly verify the sent key backup.\n        This failed verificaton requires the sender create a challenge and a new verifier\n        aka another guardian must verify this challenge.\n        \"\"\"\n        # Arrange\n        mediator = KeyCeremonyMediator(\"mediator_challenge\", CEREMONY_DETAILS)\n        KeyCeremonyOrchestrator.perform_round_1(self.GUARDIANS, mediator)\n        KeyCeremonyOrchestrator.perform_round_2(self.GUARDIANS, mediator)\n\n        # Round 3 - Guardians only\n        verification1 = self.GUARDIAN_1.verify_election_partial_key_backup(\n            GUARDIAN_2_ID,\n        )\n\n        # Act\n        failed_verification2 = ElectionPartialKeyVerification(\n            GUARDIAN_1_ID,\n            GUARDIAN_2_ID,\n            GUARDIAN_2_ID,\n            False,\n        )\n        mediator.receive_backup_verifications([verification1, failed_verification2])\n\n        state = mediator.get_verification_state()\n\n        # Assert\n        self.assertTrue(state.all_sent)\n        self.assertFalse(state.all_verified)\n        self.assertIsNone(mediator.publish_joint_key())\n        self.assertEqual(len(state.failed_verifications), 1)\n        self.assertEqual(\n            state.failed_verifications[0], GuardianPair(GUARDIAN_1_ID, GUARDIAN_2_ID)\n        )\n\n        # Act\n        challenge = self.GUARDIAN_1.publish_election_backup_challenge(GUARDIAN_2_ID)\n        mediator.verify_challenge(challenge)\n        new_state = mediator.get_verification_state()\n        all_verified = mediator.all_backups_verified()\n        joint_key = mediator.publish_joint_key()\n\n        # Assert\n        self.assertTrue(new_state.all_sent)\n        self.assertTrue(new_state.all_verified)\n        self.assertEqual(len(new_state.failed_verifications), 0)\n        self.assertTrue(all_verified)\n        self.assertIsNotNone(joint_key)\n"
  },
  {
    "path": "tests/unit/electionguard/test_logs.py",
    "content": "import logging\nfrom tests.base_test_case import BaseTestCase\n\nfrom electionguard.logs import (\n    get_stream_handler,\n    log_add_handler,\n    log_remove_handler,\n    log_handlers,\n    log_debug,\n    log_error,\n    log_info,\n    log_warning,\n)\n\n\nclass TestLogs(BaseTestCase):\n    \"\"\"Logging tests\"\"\"\n\n    def test_log_methods(self):\n        # Arrange\n        message = \"test log message\"\n\n        # Act\n        log_debug(message)\n        log_error(message)\n        log_info(message)\n        log_warning(message)\n\n        # Assert\n        self.assertIsNotNone(message)\n\n    def test_log_handlers(self):\n        # Arrange\n\n        # Act\n        handlers = log_handlers()\n\n        # Assert\n        self.assertEqual(len(handlers), 1)\n\n        # Act\n        log_remove_handler(handlers[0])\n        empty_handlers = log_handlers()\n\n        # Assert\n        self.assertEqual(len(empty_handlers), 0)\n\n        # Act\n        log_add_handler(get_stream_handler(logging.INFO))\n        added_handlers = log_handlers()\n\n        # Assert\n        self.assertEqual(len(added_handlers), 1)\n"
  },
  {
    "path": "tests/unit/electionguard/test_manifest.py",
    "content": "from dataclasses import dataclass\nfrom datetime import datetime\n\nfrom tests.base_test_case import BaseTestCase\n\nfrom electionguard.manifest import (\n    Candidate,\n    ContestDescription,\n    ContestDescriptionWithPlaceholders,\n    InternationalizedText,\n    Language,\n    Manifest,\n    InternalManifest,\n    SelectionDescription,\n    VoteVariationType,\n)\nfrom electionguard.serialize import from_raw, to_raw\nimport electionguard_tools.factories.election_factory as ElectionFactory\nimport electionguard_tools.factories.ballot_factory as BallotFactory\n\n\nelection_factory = ElectionFactory.ElectionFactory()\nballot_factory = BallotFactory.BallotFactory()\n\n\nclass TestManifest(BaseTestCase):\n    \"\"\"Manifest tests\"\"\"\n\n    @staticmethod\n    def _set_selection(\n        manifest: Manifest, selection_id: str, candidate_id: str\n    ) -> None:\n        selection = SelectionDescription(selection_id, 1, candidate_id)\n        contest = ContestDescription(\n            \"contest1\",\n            1,\n            \"e1\",\n            VoteVariationType.approval,\n            1,\n            1,\n            \"contest\",\n            [selection],\n        )\n        manifest.contests = [contest]\n\n    @staticmethod\n    def _set_candidate(\n        manifest: Manifest,\n        candidate_id: str,\n        name: str,\n        lang: str,\n        write_in: bool = False,\n    ) -> None:\n        text_name = InternationalizedText([Language(name, lang)])\n        candidate = Candidate(candidate_id, text_name, is_write_in=write_in)\n        manifest.candidates = [candidate]\n\n    def test_get_selection_names_with_valid_selection(self) -> None:\n        # arrange\n        manifest = election_factory.get_simple_manifest_from_file()\n        TestManifest._set_selection(manifest, \"selection1\", \"candidate1\")\n        TestManifest._set_candidate(manifest, \"candidate1\", \"My Candidate\", \"en\")\n\n        # act\n        selection_names = manifest.get_selection_names(\"en\")\n\n        # assert\n        self.assertEqual(1, len(selection_names.keys()))\n        self.assertEqual(\"My Candidate\", selection_names[\"selection1\"])\n\n    def test_get_selection_names_with_missing_language(self) -> None:\n        # arrange\n        manifest = election_factory.get_simple_manifest_from_file()\n        TestManifest._set_selection(manifest, \"selection1\", \"candidate1\")\n        TestManifest._set_candidate(manifest, \"candidate1\", \"My Candidate\", \"en\")\n\n        # act\n        selection_names = manifest.get_selection_names(\"es\")\n\n        # assert\n        self.assertEqual(1, len(selection_names.keys()))\n        self.assertEqual(\"candidate1\", selection_names[\"selection1\"])\n\n    def test_get_selection_names_with_missing_candidate(self) -> None:\n        # arrange\n        manifest = election_factory.get_simple_manifest_from_file()\n        TestManifest._set_selection(manifest, \"selection1\", \"candidate1\")\n        manifest.candidates = []\n\n        # act\n        selection_names = manifest.get_selection_names(\"es\")\n\n        # assert\n        self.assertEqual(1, len(selection_names.keys()))\n        self.assertEqual(\"candidate1\", selection_names[\"selection1\"])\n\n    def test_get_selection_names_with_write_in(self) -> None:\n        # arrange\n        manifest = election_factory.get_simple_manifest_from_file()\n        TestManifest._set_selection(manifest, \"selection1\", \"candidate1\")\n        TestManifest._set_candidate(manifest, \"candidate1\", \"\", \"en\", write_in=True)\n\n        # act\n        selection_names = manifest.get_selection_names(\"es\")\n\n        # assert\n        self.assertEqual(1, len(selection_names.keys()))\n        self.assertEqual(\"Write-In\", selection_names[\"selection1\"])\n\n    def test_simple_manifest_is_valid(self) -> None:\n\n        # Act\n        subject = election_factory.get_simple_manifest_from_file()\n\n        # Assert\n        self.assertIsNotNone(subject.election_scope_id)\n        self.assertEqual(subject.election_scope_id, \"jefferson-county-primary\")\n        self.assertTrue(subject.is_valid())\n\n    def test_simple_manifest_can_serialize(self) -> None:\n        # Arrange\n        subject = election_factory.get_simple_manifest_from_file()\n        intermediate = to_raw(subject)\n\n        # Act\n        result = from_raw(Manifest, intermediate)\n\n        # Assert\n        self.assertIsNotNone(result.election_scope_id)\n        self.assertEqual(result.election_scope_id, \"jefferson-county-primary\")\n\n    def test_manifest_has_deterministic_hash(self) -> None:\n\n        # Act\n        subject1 = election_factory.get_simple_manifest_from_file()\n        subject2 = election_factory.get_simple_manifest_from_file()\n\n        # Assert\n        self.assertEqual(subject1.crypto_hash(), subject2.crypto_hash())\n\n    def test_manifest_hash_is_consistent_regardless_of_format(self) -> None:\n\n        # Act\n        @dataclass\n        class DateType:\n            \"\"\"Temp date class for testing\"\"\"\n\n            date: datetime\n\n        subject1 = election_factory.get_simple_manifest_from_file()\n        subject1.start_date = from_raw(\n            DateType, '{\"date\":\"2020-03-01T08:00:00-05:00\"}'\n        ).date\n\n        subject2 = election_factory.get_simple_manifest_from_file()\n        subject2.start_date = from_raw(\n            DateType, '{\"date\":\"2020-03-01T13:00:00-00:00\"}'\n        ).date\n\n        subject3 = election_factory.get_simple_manifest_from_file()\n        subject3.start_date = from_raw(\n            DateType, '{\"date\":\"2020-03-01T13:00:00.000-00:00\"}'\n        ).date\n\n        subject4 = election_factory.get_simple_manifest_from_file()\n        subject4.start_date = from_raw(DateType, '{\"date\":\"2020-03-01T13:00:00Z\"}').date\n\n        subjects = [subject1, subject2, subject3, subject4]\n\n        # Assert\n        hashes = [subject.crypto_hash() for subject in subjects]\n        for other_hash in hashes[1:]:\n            self.assertEqual(hashes[0], other_hash)\n\n    def test_manifest_from_file_generates_consistent_internal_description_contest_hashes(\n        self,\n    ) -> None:\n        # Arrange\n        comparator = election_factory.get_simple_manifest_from_file()\n        subject = InternalManifest(comparator)\n\n        self.assertEqual(len(comparator.contests), len(subject.contests))\n\n        for expected in comparator.contests:\n            for actual in subject.contests:\n                if expected.object_id == actual.object_id:\n                    self.assertEqual(expected.crypto_hash(), actual.crypto_hash())\n\n    def test_contest_description_valid_input_succeeds(self) -> None:\n        description = ContestDescriptionWithPlaceholders(\n            object_id=\"0@A.com-contest\",\n            electoral_district_id=\"0@A.com-gp-unit\",\n            sequence_order=1,\n            vote_variation=VoteVariationType.n_of_m,\n            number_elected=1,\n            votes_allowed=1,\n            name=\"\",\n            ballot_selections=[\n                SelectionDescription(\n                    \"0@A.com-selection\",\n                    0,\n                    \"0@A.com\",\n                ),\n                SelectionDescription(\n                    \"0@B.com-selection\",\n                    1,\n                    \"0@B.com\",\n                ),\n            ],\n            ballot_title=None,\n            ballot_subtitle=None,\n            placeholder_selections=[\n                SelectionDescription(\n                    \"0@A.com-contest-2-placeholder\",\n                    2,\n                    \"0@A.com-contest-2-candidate\",\n                )\n            ],\n        )\n\n        self.assertTrue(description.is_valid())\n\n    def test_contest_description_invalid_input_fails(self) -> None:\n\n        description = ContestDescriptionWithPlaceholders(\n            object_id=\"0@A.com-contest\",\n            electoral_district_id=\"0@A.com-gp-unit\",\n            sequence_order=1,\n            vote_variation=VoteVariationType.n_of_m,\n            number_elected=1,\n            votes_allowed=1,\n            name=\"\",\n            ballot_selections=[\n                SelectionDescription(\n                    \"0@A.com-selection\",\n                    0,\n                    \"0@A.com\",\n                ),\n                # simulate a bad selection description input\n                SelectionDescription(\n                    \"0@A.com-selection\",\n                    1,\n                    \"0@A.com\",\n                ),\n            ],\n            ballot_title=None,\n            ballot_subtitle=None,\n            placeholder_selections=[\n                SelectionDescription(\n                    \"0@A.com-contest-2-placeholder\",\n                    2,\n                    \"0@A.com-contest-2-candidate\",\n                )\n            ],\n        )\n\n        self.assertFalse(description.is_valid())\n"
  },
  {
    "path": "tests/unit/electionguard/test_scheduler.py",
    "content": "# pylint: disable=consider-using-with\nfrom multiprocessing import Pool\nfrom typing import List\n\nfrom tests.base_test_case import BaseTestCase\n\nfrom electionguard.scheduler import Scheduler\n\n\ndef _callable(data: int):\n    return data\n\n\ndef _exception_callable(something: int):\n    raise Exception\n\n\nclass TestScheduler(BaseTestCase):\n    \"\"\"Scheduler tests\"\"\"\n\n    def test_schedule_callable_throws(self):\n        # Arrange\n        subject = Scheduler()\n\n        # Act\n        result = subject.schedule(_exception_callable, [list([1]), list([2])])\n\n        # Assert\n        self.assertIsNotNone(result)\n        self.assertIsInstance(result, List)\n        subject.close()\n\n    def test_safe_map(self):\n        # Arrange\n        process_pool = Pool(1)\n        subject = Scheduler()\n\n        # Act\n        result = subject.safe_map(process_pool, _callable, [1])\n        self.assertEqual(result, [1])\n\n        # verify exceptions are caught\n        result = subject.safe_map(process_pool, _exception_callable, [1])\n        self.assertIsNotNone(result)\n        self.assertIsInstance(result, List)\n\n        # verify closing the poll catches the value error\n        process_pool.close()\n\n        result = subject.safe_map(process_pool, _callable, [1])\n        self.assertIsNotNone(result)\n        self.assertIsInstance(result, List)\n\n        subject.close()\n"
  },
  {
    "path": "tests/unit/electionguard/test_singleton.py",
    "content": "from tests.base_test_case import BaseTestCase\nfrom electionguard.singleton import Singleton\n\n\nclass TestSingleton(BaseTestCase):\n    \"\"\"Singleton tests\"\"\"\n\n    def test_singleton(self):\n        singleton = Singleton()\n        same_instance = singleton.get_instance()\n        self.assertIsNotNone(singleton)\n        self.assertIsNotNone(same_instance)\n\n    def test_singleton_when_not_initialized(self):\n        instance = Singleton.get_instance()\n        self.assertIsNotNone(instance)\n"
  },
  {
    "path": "tests/unit/electionguard/test_utils.py",
    "content": "from datetime import datetime, timezone\n\nfrom tests.base_test_case import BaseTestCase\n\nfrom electionguard.utils import to_ticks\n\n\nclass TestUtils(BaseTestCase):\n    \"\"\"Utility tests\"\"\"\n\n    def test_conversion_to_ticks_from_utc(self):\n        # Act\n        ticks = to_ticks(datetime.now(timezone.utc))\n\n        self.assertIsNotNone(ticks)\n        self.assertGreater(ticks, 0)\n\n    def test_conversion_to_ticks_from_local(self):\n        # Act\n        ticks = to_ticks(datetime.now())\n\n        self.assertIsNotNone(ticks)\n        self.assertGreater(ticks, 0)\n\n    def test_conversion_to_ticks_with_tz(self):\n        # Arrange\n        now = datetime.now()\n        now_with_tz = (now).astimezone()\n        now_utc_with_tz = now_with_tz.astimezone(timezone.utc)\n\n        # Act\n        ticks_now = to_ticks(now)\n        ticks_local = to_ticks(now_with_tz)\n        ticks_utc = to_ticks(now_utc_with_tz)\n\n        # Assert\n        self.assertIsNotNone(ticks_now)\n        self.assertIsNotNone(ticks_local)\n        self.assertIsNotNone(ticks_utc)\n\n        # Ensure all three tick values are the same\n        unique_ticks = set([ticks_now, ticks_local, ticks_utc])\n        self.assertEqual(1, len(unique_ticks))\n        self.assertTrue(ticks_now in unique_ticks)\n\n    def test_conversion_to_ticks_produces_valid_epoch(self):\n        # Arrange\n        now = datetime.now()\n\n        # Act\n        ticks_now = to_ticks(now)\n        now_from_ticks = datetime.fromtimestamp(ticks_now)\n\n        # Assert\n        # Values below seconds are dropped from the epoch\n        self.assertEqual(now.replace(microsecond=0), now_from_ticks)\n"
  },
  {
    "path": "tests/unit/electionguard_gui/__init__.py",
    "content": ""
  },
  {
    "path": "tests/unit/electionguard_gui/test_decryption_dto.py",
    "content": "from datetime import datetime, timezone\n\nfrom unittest.mock import MagicMock, patch\n\nfrom electionguard_gui.models.decryption_dto import DecryptionDto\nfrom tests.base_test_case import BaseTestCase\n\n\nclass TestDecryptionDto(BaseTestCase):\n    \"\"\"Test the DecryptionDto class\"\"\"\n\n    def test_no_spoiled_ballots(self) -> None:\n        # ARRANGE\n        decryption_dto = DecryptionDto({})\n\n        # ACT\n        spoiled_ballots = decryption_dto.get_plaintext_spoiled_ballots()\n\n        # ASSERT\n        self.assertEqual(0, len(spoiled_ballots))\n\n    def test_get_status_with_no_guardians(self) -> None:\n        # ARRANGE\n        decryption_dto = DecryptionDto(\n            {\n                \"guardians_joined\": [],\n                \"guardians\": 2,\n            }\n        )\n\n        # ACT\n        status = decryption_dto.get_status()\n\n        # ASSERT\n        self.assertEqual(status, \"waiting for all guardians to join\")\n\n    def test_get_status_with_all_guardians_joined_but_not_completed(self) -> None:\n        # ARRANGE\n        decryption_dto = DecryptionDto(\n            {\"guardians_joined\": [\"g1\", \"g2\"], \"guardians\": 2, \"completed_at_utc\": None}\n        )\n\n        # ACT\n        status = decryption_dto.get_status()\n\n        # ASSERT\n        self.assertEqual(status, \"performing decryption\")\n\n    def test_get_status_with_all_guardians_joined_and_completed(self) -> None:\n        # ARRANGE\n        decryption_dto = DecryptionDto(\n            {\n                \"guardians_joined\": [\"g1\"],\n                \"guardians\": 1,\n                \"completed_at\": datetime.now(timezone.utc),\n            }\n        )\n\n        # ACT\n        status = decryption_dto.get_status()\n\n        # ASSERT\n        self.assertEqual(status, \"decryption complete\")\n\n    @patch(\"electionguard_gui.services.authorization_service.AuthorizationService\")\n    def test_admins_can_not_join_key_ceremony(self, auth_service: MagicMock) -> None:\n        # ARRANGE\n        decryption_dto = DecryptionDto({\"guardians_joined\": []})\n\n        auth_service.configure_mock(\n            **{\"get_user_id.return_value\": \"admin1\", \"is_admin.return_value\": True}\n        )\n\n        # ACT\n        decryption_dto.set_can_join(auth_service)\n\n        # ASSERT\n        self.assertFalse(decryption_dto.can_join)\n\n    @patch(\"electionguard_gui.services.authorization_service.AuthorizationService\")\n    def test_users_can_join_key_ceremony_if_not_already_joined(\n        self, auth_service: MagicMock\n    ) -> None:\n        # ARRANGE\n        decryption_dto = DecryptionDto({\"guardians_joined\": []})\n\n        auth_service.configure_mock(\n            **{\"get_user_id.return_value\": \"user1\", \"is_admin.return_value\": False}\n        )\n\n        # ACT\n        decryption_dto.set_can_join(auth_service)\n\n        # ASSERT\n        self.assertTrue(decryption_dto.can_join)\n\n    @patch(\"electionguard_gui.services.authorization_service.AuthorizationService\")\n    def test_users_cant_join_twice(self, auth_service: MagicMock) -> None:\n        # ARRANGE\n        decryption_dto = DecryptionDto({\"guardians_joined\": [\"user1\"]})\n\n        auth_service.configure_mock(\n            **{\"get_user_id.return_value\": \"user1\", \"is_admin.return_value\": False}\n        )\n\n        # ACT\n        decryption_dto.set_can_join(auth_service)\n\n        # ASSERT\n        self.assertFalse(decryption_dto.can_join)\n"
  },
  {
    "path": "tests/unit/electionguard_gui/test_eel_utils.py",
    "content": "from datetime import datetime, timezone\nfrom electionguard_gui.eel_utils import utc_to_str\nfrom tests.base_test_case import BaseTestCase\n\n\nclass TestEelUtils(BaseTestCase):\n    \"\"\"Tests eel utils\"\"\"\n\n    def test_utc_to_str_with_valid_utc_date(self):\n        date = datetime(2020, 2, 3, 7, 10, 10, 0, tzinfo=timezone.utc)\n        result = utc_to_str(date)\n        # this test may be run in different timezones, so we can't test for exact time\n        self.assertRegex(result, \"Feb 3, 2020 [0-9]:10 AM\")\n\n    def test_utc_to_str_with_empty(self):\n        result = utc_to_str(None)\n        self.assertEqual(result, \"\")\n"
  },
  {
    "path": "tests/unit/electionguard_gui/test_election_dto.py",
    "content": "from datetime import datetime, timezone\n\nfrom electionguard_gui.models.election_dto import ElectionDto\nfrom tests.base_test_case import BaseTestCase\n\n\nclass TestElectionDto(BaseTestCase):\n    \"\"\"Test the ElectionDto class\"\"\"\n\n    def test_get_status_with_no_guardians(self) -> None:\n        # ARRANGE\n        self.mocker.patch(\n            \"electionguard_gui.models.election_dto.utc_to_str\",\n            return_value=\"Feb 3, 2022 2:10 PM\",\n        )\n        election_dto = ElectionDto(\n            {\n                \"_id\": \"ABC\",\n                \"created_at\": datetime(2020, 2, 3, 7, 10, 10, 0, tzinfo=timezone.utc),\n            }\n        )\n\n        # ACT\n        result = election_dto.to_dict()\n\n        # ASSERT\n        self.assertEqual(\"ABC\", result[\"id\"])\n        self.assertEqual(\"Feb 3, 2022 2:10 PM\", result[\"created_at\"])\n\n    def test_sum_two_ballots(self) -> None:\n        # ARRANGE\n        election_dto = ElectionDto(\n            {\n                \"ballot_uploads\": [\n                    {\n                        \"ballot_count\": 1,\n                        \"ballot_type\": \"ballot_type_1\",\n                    },\n                    {\n                        \"ballot_count\": 2,\n                        \"ballot_type\": \"ballot_type_2\",\n                    },\n                ]\n            }\n        )\n\n        # ACT\n        result = election_dto.sum_ballots()\n\n        # ASSERT\n        self.assertEqual(3, result)\n\n    def test_sum_zero_ballots(self) -> None:\n        # ARRANGE\n        election_dto = ElectionDto({\"ballot_uploads\": []})\n\n        # ACT\n        result = election_dto.sum_ballots()\n\n        # ASSERT\n        self.assertEqual(0, result)\n"
  },
  {
    "path": "tests/unit/electionguard_gui/test_plaintext_ballot_service.py",
    "content": "from unittest.mock import MagicMock, patch\nfrom electionguard.tally import PlaintextTally, PlaintextTallySelection\nfrom electionguard_gui.services.plaintext_ballot_service import (\n    _get_contest_details,\n    _get_tally_report,\n)\nfrom tests.base_test_case import BaseTestCase\n\n\nclass TestPlaintextBallotService(BaseTestCase):\n    \"\"\"Test the ElectionDto class\"\"\"\n\n    def test_get_tally_report_with_no_contests(self) -> None:\n        # ARRANGE\n        plaintext_ballot = PlaintextTally(\"tally\", {})\n        selection_names: dict[str, str] = {}\n        selection_write_ins: dict[str, bool] = {}\n        parties: dict[str, str] = {}\n        contest_names: dict[str, str] = {}\n\n        # ACT\n        result = _get_tally_report(\n            plaintext_ballot,\n            selection_names,\n            contest_names,\n            selection_write_ins,\n            parties,\n        )\n\n        # ASSERT\n        self.assertEqual(0, len(result))\n\n    @patch(\"electionguard.tally.PlaintextTallySelection\")\n    def test_given_one_contest_with_valid_name_when_get_tally_report_then_name_returned(\n        self, plaintext_tally_selection: MagicMock\n    ) -> None:\n        # ARRANGE\n        plaintext_tally_selection.object_id = \"c-1\"\n        plaintext_ballot = PlaintextTally(\"tally\", {\"c-1\": plaintext_tally_selection})\n        selection_names: dict[str, str] = {}\n        selection_write_ins: dict[str, bool] = {}\n        parties: dict[str, str] = {}\n        contest_names: dict[str, str] = {\"c-1\": \"Contest 1\"}\n\n        # ACT\n        result = _get_tally_report(\n            plaintext_ballot,\n            selection_names,\n            contest_names,\n            selection_write_ins,\n            parties,\n        )\n\n        # ASSERT\n        self.assertEqual(1, len(result))\n        self.assertEqual(\"Contest 1\", result[0][\"name\"])\n\n    @patch(\"electionguard.tally.PlaintextTallySelection\")\n    def test_given_one_contest_with_invalid_name_when_get_tally_report_then_name_is_na(\n        self, plaintext_tally_selection: MagicMock\n    ) -> None:\n        # ARRANGE\n        plaintext_tally_selection.object_id = \"c-1\"\n        plaintext_ballot = PlaintextTally(\"tally\", {\"c-1\": plaintext_tally_selection})\n        selection_names: dict[str, str] = {}\n        selection_write_ins: dict[str, bool] = {}\n        parties: dict[str, str] = {}\n        contest_names: dict[str, str] = {}\n\n        # ACT\n        result = _get_tally_report(\n            plaintext_ballot,\n            selection_names,\n            contest_names,\n            selection_write_ins,\n            parties,\n        )\n\n        # ASSERT\n        self.assertEqual(1, len(result))\n        self.assertEqual(\"n/a\", list(result)[0][\"name\"])\n\n    @patch(\"electionguard.tally.PlaintextTallySelection\")\n    @patch(\"electionguard.tally.PlaintextTallySelection\")\n    def test_given_two_contests_with_duplicate_names_when_get_tally_report_then_both_names_returned(\n        self,\n        plaintext_tally_selection1: MagicMock,\n        plaintext_tally_selection2: MagicMock,\n    ) -> None:\n        # ARRANGE\n        plaintext_tally_selection1.object_id = \"c-1\"\n        plaintext_tally_selection2.object_id = \"c-2\"\n        plaintext_ballot = PlaintextTally(\n            \"tally\",\n            {\n                \"c-1\": plaintext_tally_selection1,\n                \"c-2\": plaintext_tally_selection2,\n            },\n        )\n        selection_names: dict[str, str] = {}\n        selection_write_ins: dict[str, bool] = {}\n        parties: dict[str, str] = {}\n        contest_names: dict[str, str] = {\"c-1\": \"My Contest\", \"c-2\": \"My Contest\"}\n\n        # ACT\n        result = _get_tally_report(\n            plaintext_ballot,\n            selection_names,\n            contest_names,\n            selection_write_ins,\n            parties,\n        )\n\n        # ASSERT\n        self.assertEqual(2, len(result))\n        self.assertEqual(\"My Contest\", list(result)[0][\"name\"])\n        self.assertEqual(\"My Contest\", list(result)[1][\"name\"])\n\n    def test_zero_sections(self) -> None:\n        # ARRANGE\n        selections: list[PlaintextTallySelection] = []\n        selection_names: dict[str, str] = {}\n        selection_write_ins: dict[str, bool] = {}\n        parties: dict[str, str] = {}\n\n        # ACT\n        result = _get_contest_details(\n            selections, selection_names, selection_write_ins, parties\n        )\n\n        # ASSERT\n        self.assertEqual(0, result[\"nonWriteInTotal\"])\n        self.assertEqual(None, result[\"writeInTotal\"])\n        self.assertEqual(0, len(result[\"selections\"]))\n\n    @patch(\"electionguard.tally.PlaintextTallySelection\")\n    def test_one_non_write_in(self, plaintext_tally_selection: MagicMock) -> None:\n        # ARRANGE\n        plaintext_tally_selection.object_id = \"AL\"\n        plaintext_tally_selection.tally = 2\n        selections: list[PlaintextTallySelection] = [plaintext_tally_selection]\n        selection_names: dict[str, str] = {\n            \"AL\": \"Abraham Lincoln\",\n        }\n        selection_write_ins: dict[str, bool] = {\n            \"AL\": False,\n        }\n        parties: dict[str, str] = {\n            \"AL\": \"National Union Party\",\n        }\n\n        # ACT\n        result = _get_contest_details(\n            selections, selection_names, selection_write_ins, parties\n        )\n\n        # ASSERT\n        self.assertEqual(2, result[\"nonWriteInTotal\"])\n        self.assertEqual(None, result[\"writeInTotal\"])\n        self.assertEqual(1, len(result[\"selections\"]))\n        selection = result[\"selections\"][0]\n        self.assertEqual(\"Abraham Lincoln\", selection[\"name\"])\n        self.assertEqual(2, selection[\"tally\"])\n        self.assertEqual(\"National Union Party\", selection[\"party\"])\n        self.assertEqual(1, selection[\"percent\"])\n\n    @patch(\"electionguard.tally.PlaintextTallySelection\")\n    @patch(\"electionguard.tally.PlaintextTallySelection\")\n    def test_duplicate_section_names(\n        self,\n        plaintext_tally_selection1: MagicMock,\n        plaintext_tally_selection2: MagicMock,\n    ) -> None:\n        # ARRANGE\n        plaintext_tally_selection1.object_id = \"S1\"\n        plaintext_tally_selection1.tally = 1\n        plaintext_tally_selection2.object_id = \"S2\"\n        plaintext_tally_selection2.tally = 9\n        selections: list[PlaintextTallySelection] = [\n            plaintext_tally_selection1,\n            plaintext_tally_selection2,\n        ]\n        selection_names: dict[str, str] = {\n            \"S1\": \"Abraham Lincoln\",\n            \"S2\": \"Abraham Lincoln\",\n        }\n        selection_write_ins: dict[str, bool] = {\n            \"S1\": False,\n            \"S2\": False,\n        }\n        parties: dict[str, str] = {\n            \"S1\": \"National Union Party\",\n            \"S2\": \"National Union Party\",\n        }\n\n        # ACT\n        result = _get_contest_details(\n            selections, selection_names, selection_write_ins, parties\n        )\n\n        # ASSERT\n        self.assertEqual(10, result[\"nonWriteInTotal\"])\n        self.assertEqual(None, result[\"writeInTotal\"])\n        self.assertEqual(2, len(result[\"selections\"]))\n\n        selection = result[\"selections\"][0]\n        self.assertEqual(\"Abraham Lincoln\", selection[\"name\"])\n        self.assertEqual(1, selection[\"tally\"])\n        self.assertEqual(\"National Union Party\", selection[\"party\"])\n        self.assertEqual(0.1, selection[\"percent\"])\n\n        selection = result[\"selections\"][1]\n        self.assertEqual(\"Abraham Lincoln\", selection[\"name\"])\n        self.assertEqual(9, selection[\"tally\"])\n        self.assertEqual(\"National Union Party\", selection[\"party\"])\n        self.assertEqual(0.9, selection[\"percent\"])\n\n    @patch(\"electionguard.tally.PlaintextTallySelection\")\n    def test_one_write_in(self, plaintext_tally_selection: MagicMock) -> None:\n        # ARRANGE\n        plaintext_tally_selection.object_id = \"ST\"\n        plaintext_tally_selection.tally = 1\n        selections: list[PlaintextTallySelection] = [plaintext_tally_selection]\n        selection_names: dict[str, str] = {}\n        selection_write_ins: dict[str, bool] = {\n            \"ST\": True,\n        }\n        parties: dict[str, str] = {}\n\n        # ACT\n        result = _get_contest_details(\n            selections, selection_names, selection_write_ins, parties\n        )\n\n        # ASSERT\n        self.assertEqual(0, result[\"nonWriteInTotal\"])\n        self.assertEqual(1, result[\"writeInTotal\"])\n        self.assertEqual(0, len(result[\"selections\"]))\n\n    @patch(\"electionguard.tally.PlaintextTallySelection\")\n    def test_zero_write_in(self, plaintext_tally_selection: MagicMock) -> None:\n        # ARRANGE\n        plaintext_tally_selection.object_id = \"ST\"\n        plaintext_tally_selection.tally = 0\n        selections: list[PlaintextTallySelection] = [plaintext_tally_selection]\n        selection_names: dict[str, str] = {}\n        selection_write_ins: dict[str, bool] = {\n            \"ST\": True,\n        }\n        parties: dict[str, str] = {}\n\n        # ACT\n        result = _get_contest_details(\n            selections, selection_names, selection_write_ins, parties\n        )\n\n        # ASSERT\n        self.assertEqual(0, result[\"nonWriteInTotal\"])\n        self.assertEqual(0, result[\"writeInTotal\"])\n        self.assertEqual(0, len(result[\"selections\"]))\n"
  }
]