├── .github └── workflows │ ├── build-docs │ ├── build_test.yml │ ├── clear-target-files │ ├── docs.yml │ ├── linuxbuildwheels │ ├── macosbuildwheel │ └── wheels.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── bench └── bench.py ├── cryptomite ├── CMakeLists.txt ├── __init__.py ├── circulant.py ├── dodis.py ├── pycryptomite.cpp ├── toeplitz.py ├── trevisan.py └── utils.py ├── docs ├── Makefile ├── _static │ ├── .DS_Store │ └── images │ │ ├── cryptomite_logo_blur.png │ │ ├── cryptomite_logo_full_blur.png │ │ └── favicon.png ├── bibliography.rst ├── conf.py ├── cryptomite.rst ├── examples │ └── example.ipynb ├── figures │ ├── Table.png │ ├── extractor_flow_chart.png │ └── performance.png ├── gettingstarted.rst ├── glossary.rst ├── index.rst ├── intro.rst ├── make.bat ├── notebooks.rst ├── performance.rst └── requirements.txt ├── na_set.txt ├── pyproject.toml ├── setup.cfg ├── setup.py ├── src ├── .gitignore ├── .gitmodules ├── .travis.yml ├── CMakeLists.txt ├── bigntt.cpp ├── bigntt.h ├── irreducible_poly.cpp ├── irreducible_poly.h ├── ntt.cpp ├── ntt.h ├── trevisan.cpp └── trevisan.h └── test ├── CMakeLists.txt ├── requirements.txt ├── test_circulant.py ├── test_dodis.py ├── test_ntt.py ├── test_toeplitz.py ├── test_vn.py └── tests.cpp /.github/workflows/build-docs: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd docs 4 | make clean 5 | make html 6 | 7 | -------------------------------------------------------------------------------- /.github/workflows/build_test.yml: -------------------------------------------------------------------------------- 1 | name: Build, lint and test package 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | pull_request: 8 | 9 | env: 10 | SRC_DIR: cryptomite 11 | TEST_DIR: test 12 | 13 | jobs: 14 | lint: 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | python-version: [ 3.9, "3.10", 3.11, 3.12 ] 19 | outputs: 20 | error-check: ${{ steps.error-check.conclusion }} 21 | steps: 22 | - uses: actions/checkout@v3 23 | - name: Setup Python ${{ matrix.python-version }} 24 | uses: actions/setup-python@v4 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | - name: Install linter 28 | run: python -m pip install flake8 flake8-broken-line flake8-bugbear flake8-copyright flake8-import-order flake8-quotes 29 | - name: Check for errors 30 | id: error-check 31 | run: 32 | # stop the build if there are Python syntax errors or undefined names 33 | flake8 ${{ env.SRC_DIR }} --count --isolated --select=E9,F63,F7,F82 --show-source --statistics 34 | - name: Lint with flake8 35 | run: 36 | flake8 ${{ env.SRC_DIR }} 37 | - name: Check doc line lengths and complexity 38 | run: 39 | # exit-zero treats all errors as warnings 40 | flake8 ${{ env.SRC_DIR }} --count --exit-zero --isolated --max-complexity=10 --max-doc-length=72 --select=C901,W505 --statistics 41 | - name: Install package 42 | run: pip install . 43 | - name: Install test requirements 44 | run: pip install -r test/requirements.txt 45 | - name: Test package 46 | run: | 47 | pip install pytest 48 | pytest ${{ env.TEST_DIR }} 49 | -------------------------------------------------------------------------------- /.github/workflows/clear-target-files: -------------------------------------------------------------------------------- 1 | **/* 2 | !.git 3 | !.nojekyll 4 | 5 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Build and deploy documentation 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | pull_request: 8 | 9 | env: 10 | WORKFLOWS_DIR: .github/workflows 11 | 12 | jobs: 13 | docs: 14 | name: Build and deploy documentation 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | with: 19 | fetch-depth: 0 # fetches tags, required for version info 20 | - name: Set up Python 21 | uses: actions/setup-python@v4 22 | with: 23 | python-version: 3.8 24 | - name: Build library 25 | run: pip install . 26 | - name: Install documentation dependencies 27 | run: | 28 | sudo apt-get install graphviz pandoc 29 | pip install -r docs/requirements.txt 30 | - name: Build documentation 31 | run: ${{ env.WORKFLOWS_DIR }}/build-docs 32 | - name: Deploy documentation 33 | if: ${{ github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'release') }} 34 | uses: s0/git-publish-subdir-action@develop 35 | env: 36 | REPO: self 37 | BRANCH: docs 38 | FOLDER: docs/_build/html/ 39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | CLEAR_GLOBS_FILE: ${{ env.WORKFLOWS_DIR }}/clear-target-files 41 | -------------------------------------------------------------------------------- /.github/workflows/linuxbuildwheels: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -evu 4 | 5 | cd /cryptomite 6 | mkdir wheelhouse 7 | mkdir audited 8 | 9 | for pyX in $PY_VERS 10 | do 11 | cd /cryptomite 12 | export PYEX=/opt/python/${pyX}/bin/python 13 | ${PYEX} -m pip install -U pip wheel scikit-build 14 | ${PYEX} setup.py bdist_wheel -d "tmpwheel_${pyX}" 15 | auditwheel repair "tmpwheel_${pyX}/cryptomite-"*".whl" -w "audited/" 16 | done 17 | -------------------------------------------------------------------------------- /.github/workflows/macosbuildwheel: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -evu 4 | 5 | cd ${GITHUB_WORKSPACE} 6 | 7 | PYVER=`python -c 'import sys; print(".".join(map(str, sys.version_info[:3])))'` 8 | python -m pip install -U pip wheel scikit-build delocate 9 | python setup.py bdist_wheel -d "${GITHUB_WORKSPACE}/tmpwheel_${PYVER}" 10 | delocate-wheel -v -w "${GITHUB_WORKSPACE}/wheelhouse/" "${GITHUB_WORKSPACE}/tmpwheel_${PYVER}/cryptomite-"*".whl" 11 | -------------------------------------------------------------------------------- /.github/workflows/wheels.yml: -------------------------------------------------------------------------------- 1 | name: wheels 2 | on: 3 | push: 4 | branches: 5 | - 'build-wheel' 6 | jobs: 7 | build_linux_wheels: 8 | name: build linux wheels 9 | runs-on: ubuntu-latest 10 | env: 11 | PY_VERS: cp38-cp38 cp39-cp39 cp310-cp310 cp311-cp311 cp312-cp312 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: set up container 15 | run: | 16 | docker create --name linux_build -i -v /:/host quay.io/pypa/manylinux2014_x86_64:latest /bin/bash 17 | docker cp . linux_build:/cryptomite/ 18 | - name: run build 19 | run: | 20 | docker start linux_build 21 | docker exec -e PY_VERS="${PY_VERS}" linux_build /bin/bash -c "/cryptomite/.github/workflows/linuxbuildwheels" 22 | mkdir wheelhouse 23 | docker cp linux_build:/cryptomite/audited/. wheelhouse/ 24 | - uses: actions/upload-artifact@v4 25 | with: 26 | name: linux_wheels 27 | path: wheelhouse/ 28 | build_macos_wheels: 29 | name: build macos wheels 30 | runs-on: macos-13 31 | steps: 32 | - uses: actions/checkout@v2 33 | - name: set up python 3.9 34 | uses: actions/setup-python@v2 35 | with: 36 | python-version: '3.9' 37 | - name: build wheel (3.9) 38 | run: .github/workflows/macosbuildwheel 39 | - name: set up python 3.10 40 | uses: actions/setup-python@v2 41 | with: 42 | python-version: '3.10' 43 | - name: build wheel (3.10) 44 | run: .github/workflows/macosbuildwheel 45 | - name: set up python 3.11 46 | uses: actions/setup-python@v2 47 | with: 48 | python-version: '3.11' 49 | - name: build wheel (3.11) 50 | run: .github/workflows/macosbuildwheel 51 | - name: set up python 3.12 52 | uses: actions/setup-python@v2 53 | with: 54 | python-version: '3.12' 55 | - name: build wheel (3.12) 56 | run: .github/workflows/macosbuildwheel 57 | - uses: actions/checkout@v2 58 | - name: set up python '3.13' 59 | uses: actions/setup-python@v2 60 | with: 61 | python-version: '3.13' 62 | - name: build wheel (3.13) 63 | run: .github/workflows/macosbuildwheel 64 | - uses: actions/upload-artifact@v4 65 | with: 66 | name: macos_wheels 67 | path: wheelhouse/ 68 | build_macos_m1_wheels: 69 | name: build macos M1 wheels 70 | runs-on: macos-latest-xlarge 71 | steps: 72 | - uses: actions/checkout@v2 73 | - name: set up python 3.10 74 | uses: actions/setup-python@v2 75 | with: 76 | python-version: '3.10' 77 | - name: build wheel (3.10) 78 | run: .github/workflows/macosbuildwheel 79 | - name: set up python 3.11 80 | uses: actions/setup-python@v2 81 | with: 82 | python-version: '3.11' 83 | - name: build wheel (3.11) 84 | run: .github/workflows/macosbuildwheel 85 | - name: set up python 3.12 86 | uses: actions/setup-python@v2 87 | with: 88 | python-version: '3.12' 89 | - name: build wheel (3.12) 90 | run: .github/workflows/macosbuildwheel 91 | - name: set up python 3.13 92 | uses: actions/setup-python@v2 93 | with: 94 | python-version: '3.13' 95 | - name: build wheel (3.13) 96 | run: .github/workflows/macosbuildwheel 97 | - uses: actions/upload-artifact@v4 98 | with: 99 | name: macos_m1_wheels 100 | path: wheelhouse/ 101 | build_windows_wheels: 102 | name: build windows wheels 103 | runs-on: windows-2019 104 | steps: 105 | - uses: actions/checkout@v2 106 | - name: set up python 3.8 107 | uses: actions/setup-python@v2 108 | with: 109 | python-version: '3.8' 110 | - name: build wheel (3.8) 111 | run: | 112 | python -m pip install -U pip wheel scikit-build 113 | python setup.py bdist_wheel -d "${{ github.workspace }}/wheelhouse" 114 | - name: set up python 3.9 115 | uses: actions/setup-python@v2 116 | with: 117 | python-version: '3.9' 118 | - name: build wheel (3.9) 119 | run: | 120 | python -m pip install -U pip wheel scikit-build 121 | python setup.py bdist_wheel -d "${{ github.workspace }}/wheelhouse" 122 | - name: set up python 3.10 123 | uses: actions/setup-python@v2 124 | with: 125 | python-version: '3.10' 126 | - name: build wheel (3.10) 127 | run: | 128 | python -m pip install -U pip wheel scikit-build 129 | python setup.py bdist_wheel -d "${{ github.workspace }}/wheelhouse" 130 | - name: set up python 3.11 131 | uses: actions/setup-python@v2 132 | with: 133 | python-version: '3.11' 134 | - name: build wheel (3.11) 135 | run: | 136 | python -m pip install -U pip wheel scikit-build 137 | python setup.py bdist_wheel -d "${{ github.workspace }}/wheelhouse" 138 | - name: set up python 3.12 139 | uses: actions/setup-python@v2 140 | with: 141 | python-version: '3.12' 142 | - name: build wheel (3.12) 143 | run: | 144 | python -m pip install -U pip wheel scikit-build 145 | python setup.py bdist_wheel -d "${{ github.workspace }}/wheelhouse" 146 | - name: set up python 3.13 147 | uses: actions/setup-python@v2 148 | with: 149 | python-version: '3.13' 150 | - name: build wheel (3.13) 151 | run: | 152 | python -m pip install -U pip wheel scikit-build 153 | python setup.py bdist_wheel -d "${{ github.workspace }}/wheelhouse" 154 | - uses: actions/upload-artifact@v4 155 | with: 156 | name: windows_wheels 157 | path: wheelhouse/ 158 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # `cryptomite/version.py` is generated automatically by setuptools-scm 2 | cryptomite/version.py 3 | 4 | # development files 5 | *.egg-info/ 6 | .coverage 7 | .ipynb_checkpoints/ 8 | __pycache__/ 9 | build/ 10 | dist/ 11 | htmlcov/ 12 | .idea/ 13 | cmake-build-*/ 14 | 15 | # ignore the built documentation 16 | docs/_* 17 | !docs/_static/ 18 | 19 | # ignore scikit-build stuff 20 | _skbuild/ 21 | */*.so 22 | cryptomite/lib 23 | cryptomite/include 24 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22.1) 2 | 3 | project(cryptomite) 4 | 5 | include(CTest) 6 | 7 | set(CMAKE_CXX_STANDARD 20) 8 | 9 | set(_SKBUILD_FORCE_MSVC FALSE) 10 | 11 | add_subdirectory(src) 12 | 13 | add_subdirectory(cryptomite) 14 | 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | LICENSE AGREEMENT FOR USE, REPRODUCTION, AND DISTRIBUTION 2 | 3 | This License is made between Quantinuum Limited (the “Licensor”) and you (“You” or the “Licensee”) regarding the Licensor’s library of extractor software and algorithms for generating and verifying highly entropic data. 4 | 5 | Wherein it is agreed as follows 6 | 7 | 1. Definitions. 8 | 9 | “Academic Research Purposes” shall mean for purposes of academic studies to investigate scientific characteristics of software and algorithms of the Work; such scientific characteristics include, for example, reliability of entropy of data strings generated using the algorithms. 10 | 11 | “Commercial Use” shall mean any use other than use for Academic Research Purposes. 12 | 13 | “Contribution” shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to the Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, “submitted” means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as “Not a Contribution”. 14 | 15 | “Contributor” shall mean the Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by the Licensor and subsequently incorporated within the Work. 16 | 17 | “Derivative Works” shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 18 | 19 | “License” shall mean the terms and conditions for use, reproduction, and distribution as set out in this document. 20 | 21 | “Licensor” shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 22 | 23 | “Legal Entity” shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, “control” means (i) the power, direct or indirect, to cause the direction or 24 | 25 | management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 26 | 27 | “Object” form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 28 | 29 | “Source” form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 30 | 31 | “Work” shall mean the work made available under the License, namely the compilation of extractor software and algorithms for generating and verifying highly entropic data. 32 | 33 | “You” (or “Your”) shall mean an individual or Legal Entity exercising permissions granted by this License. 34 | 35 | 2. Grant of Copyright License. 36 | 37 | Subject to the terms and conditions of this License, the Licensor hereby grants to You a worldwide, non-exclusive, no-charge, royalty-free, revocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form, for Academic Research Purposes only, wherein Commercial Use of the Work is strictly prohibited. 38 | 39 | 3. Redistribution. 40 | 41 | You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that: 42 | 43 | a) Redistribution is for Academic Research Purposes only, wherein Commercial Use of the Work is strictly prohibited; 44 | 45 | b) You provide to recipients of the Work or Derivative Works, at the same time as the redistribution, a copy of this License; 46 | 47 | c) You cause any modified files to carry prominent notices stating that You changed the files of the Work; 48 | 49 | d) You retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, acknowledgement (see the APPENDIX) and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 50 | 51 | e) If the Work includes a “NOTICE” text file as part of its distribution, any Derivative Works that You distribute include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: 52 | 53 | 1. within a NOTICE text file distributed as part of the Derivative Works; 54 | 55 | 2. within the Source form or documentation, if provided along with the Derivative Works; or 56 | 57 | 3. within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. 58 | 59 | The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. 60 | 61 | You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 62 | 63 | 4. Submission of Contributions. 64 | 65 | Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 66 | 67 | 5. Trademarks. 68 | 69 | This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 70 | 71 | 6. Disclaimer of Warranty. 72 | 73 | Unless required by applicable law or agreed to in writing, the Licensor provides the Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 74 | 75 | 7. Limitation of Liability. 76 | 77 | In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall the Licensor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 78 | 79 | 8. Accepting Warranty or Additional Liability. 80 | 81 | While redistributing the Work or Derivative Works thereof for Academic Research Purposes, You may choose to offer acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 82 | 83 | 9. Termination and Liability for Breach 84 | 85 | Upon any breach of the terms of this License, in particular, but without limitation, to those relating to the prohibition of Commercial Use by the Licensee at clause 2. above, or relating to the prohibition of reproduction or redistribution for the purposes of Commercial Use at clause 3. above, the permission granted in this License shall immediately terminate and you shall cease to use the Work for any purpose without prejudice to the ability of the Licensor, at its sole election, to bring such legal proceedings as may be necessary to prohibit further breaches of its terms or to claim for recovery of any loss or damage suffered as a result of the breach in any jurisdiction as may be required to give effect to this clause 9. or to the other terms of this License, as appropriate. 86 | 87 | 10. Acceptance 88 | 89 | By downloading and accessing the Work, You enter into this License with the Licensor and agree to be bound by its terms. 90 | 91 | APPENDIX 92 | 93 | For copyright acknowledgment purposes, the following software products have been used in setting up the Work: 94 | 95 | - OpenMP, Apache 2.0 ,https://github.com/llvm-mirror/openmp/blob/master/LICENSE.txt 96 | - cffi, MIT, https://pypi.org/project/cffi/ 97 | - setuptools, MIT, https://pypi.org/project/setuptools/ 98 | - cmake, Apache / BSD, https://pypi.org/project/cmake/ 99 | - wheel, MIT, https://pypi.org/project/wheel/ 100 | - scikit-build, MIT, https://pypi.org/project/scikit-build/ 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cryptomite 2 | 3 | ![Build Status](https://github.com/CQCL/cryptomite/actions/workflows/build_test.yml/badge.svg) 4 | [![PyPI version](https://img.shields.io/pypi/v/cryptomite)](//pypi.org/project/cryptomite) 5 | [![Downloads](https://static.pepy.tech/badge/cryptomite)](https://pepy.tech/project/cryptomite) 6 | [![Downloads](https://static.pepy.tech/badge/cryptomite/month)](https://pepy.tech/project/cryptomite) 7 | [![arXiv](https://img.shields.io/badge/arXiv-2402.09481-green)](//arxiv.org/abs/2402.09481) 8 | 9 | 10 | ![image](https://github.com/CQCL/cryptomite/assets/13847804/671c8eec-0f2a-46b0-92ba-3a0c040492e8) 11 | 12 | `cryptomite` is a modular, extensible high-level Python library 13 | of randomness extractors, created by Quantinuum's Quantum Cryptography team. 14 | At a high level, the library offers state-of-the-art randomness extractors that are easy to use, optimized for performance and numerically precise 15 | providing a trade-off of features that suit numerous practical use cases. Find more information in our accompanying [paper](https://quantum-journal.org/papers/q-2025-01-08-1584/). 16 | For additional examples of usage and guidance on getting started with Cryptomite, see our related [blog post](https://medium.com/quantinuum/introducing-cryptomite-randomness-extraction-simplified-857fc2f87673) 17 | and repository [documentation](https://cqcl.github.io/cryptomite/). 18 | 19 | 20 | The library is available for non-commercial use only; see the [license](https://github.com/CQCL/cryptomite/blob/main/LICENSE) for details. 21 | 22 | The performance-critical parts of the library (e.g. the number theoretic transform) are implemented in C++, while the rest of the library (e.g. parameter estimation) is implemented in Python for accessibility and ease of installation. 23 | 24 | The package is available for Python 3.8 and higher on Mac, Windows and Linux. To install, type: 25 | 26 | ```bash 27 | pip install cryptomite 28 | ``` 29 | 30 | 31 | 32 | ## Example Usage 33 | 34 | ```python 35 | from cryptomite.trevisan import Trevisan 36 | from random import randint 37 | 38 | n, m, max_eps = 1000, 200, 0.01 39 | 40 | ext = Trevisan(n, m, max_eps) 41 | 42 | input_bits = [randint(0, 1) for _ in range(n)] 43 | seed_bits = [randint(0, 1) for _ in range(ext.ext.get_seed_length())] 44 | 45 | output_bits = ext.extract(input_bits, seed_bits) 46 | ``` 47 | 48 | ## Documentation 49 | 50 | To build the docs, run 51 | 52 | ```bash 53 | cd docs 54 | pip install -r requirements.txt 55 | make clean 56 | make html 57 | ``` 58 | 59 | ## Testing 60 | 61 | Install `pytest`, then run `pytest test`. 62 | 63 | To run the C++ tests, run 64 | 65 | ```bash 66 | cmake . 67 | make 68 | test/runTest 69 | ``` 70 | 71 | ## How to Cite 72 | If you use `cryptomite` in your research, please cite the accompanying [paper](https://arxiv.org/abs/2402.09481): 73 | 74 | ``` 75 | @article{Foreman2025cryptomite, 76 | doi = {10.22331/q-2025-01-08-1584}, 77 | url = {https://doi.org/10.22331/q-2025-01-08-1584}, 78 | title = {Cryptomite: {A} versatile and user-friendly library of randomness extractors}, 79 | author = {Foreman, Cameron and Yeung, Richie and Edgington, Alec and Curchod, Florian J.}, 80 | journal = {{Quantum}}, 81 | issn = {2521-327X}, 82 | publisher = {{Verein zur F{\"{o}}rderung des Open Access Publizierens in den Quantenwissenschaften}}, 83 | volume = {9}, 84 | pages = {1584}, 85 | month = jan, 86 | year = {2025} 87 | } 88 | ``` 89 | -------------------------------------------------------------------------------- /bench/bench.py: -------------------------------------------------------------------------------- 1 | from cryptomite.dodis import Dodis 2 | from cryptomite.toeplitz import Toeplitz 3 | from cryptomite.utils import von_neumann 4 | from cryptomite.trevisan import Trevisan 5 | from numpy.random import randint 6 | import numpy as np 7 | 8 | from time import time 9 | 10 | import pandas as pd 11 | 12 | 13 | cache1 = dict() 14 | cache2 = dict() 15 | 16 | N_REPEATS = 5 17 | 18 | seed_caches = [dict() for _ in range(N_REPEATS)] 19 | cache3 = dict() 20 | def get_random(cache, n): 21 | if n not in cache: 22 | cache[n] = randint(0, 2, size=n) 23 | 24 | return cache[n] 25 | 26 | # compare with naive 27 | 28 | inp_lens = [x * 10 ** n for x in [10, 18, 32, 56] for n in range(1, 10)] 29 | # inp_lens = [10 ** 8] 30 | names = ["Extractor", "Input Length", "Ratio"] 31 | #ratios = (0.5, 0.75) 32 | ratios = [0.5] 33 | columns = ["Wait (s)"] 34 | indices = [] 35 | rows = [] 36 | 37 | # Dodis 38 | print("\nDodis: ", end="", flush=True) 39 | for inp_len in inp_lens: 40 | inp1 = get_random(cache1, inp_len) 41 | inp2 = get_random(cache2, inp_len) 42 | 43 | for ratio in ratios: 44 | waits = [] 45 | for i in range(N_REPEATS): 46 | inp2 = get_random(seed_caches[i], inp_len) 47 | out_len = int(inp_len * ratio) 48 | ext = Dodis(inp_len, out_len) 49 | start = time() 50 | ext.extract(inp1, inp2) 51 | wait = time() - start 52 | waits.append(wait) 53 | print(".", end="", flush=True) 54 | print("|", end="", flush=True) 55 | rows.append(sum(waits) / len(waits)) 56 | indices.append(('Dodis', inp_len, ratio)) 57 | 58 | # VN 59 | print("\nVN: ", end="", flush=True) 60 | for inp_len in inp_lens: 61 | waits = [] 62 | for i in range(N_REPEATS): 63 | inp1 = get_random(seed_caches[i], inp_len) 64 | start = time() 65 | von_neumann(inp1) 66 | wait = time() - start 67 | waits.append(wait) 68 | print(".", end="", flush=True) 69 | print("|", end="", flush=True) 70 | rows.append(sum(waits) / len(waits)) 71 | indices.append(('Von Neumann', inp_len, np.nan)) 72 | 73 | # Toeplitz 74 | print("\nToeplitz", end="", flush=True) 75 | for inp_len in inp_lens: 76 | inp1 = get_random(cache1, inp_len) 77 | 78 | for ratio in ratios: 79 | out_len = int(inp_len * ratio) 80 | waits = [] 81 | for i in range(N_REPEATS): 82 | seed = get_random(seed_caches[i], inp_len + out_len - 1) 83 | ext = Toeplitz(inp_len, out_len) 84 | start = time() 85 | ext.extract(inp1, seed) 86 | wait = time() - start 87 | waits.append(wait) 88 | print(".", end="", flush=True) 89 | print("|", end="", flush=True) 90 | rows.append(sum(waits) / len(waits)) 91 | indices.append(('Toeplitz', inp_len, ratio)) 92 | 93 | # Trevisan 94 | print("\nTrevisan: ", end="", flush=True) 95 | for inp_len in inp_lens: 96 | if inp_len > 10000: 97 | continue 98 | inp1 = get_random(cache1, inp_len) 99 | 100 | for ratio in ratios: 101 | min_ent = int(inp_len * ratio) 102 | ext = Trevisan(inp_len, min_ent, 2 ** -20) 103 | seed_length = ext.ext.get_seed_length() 104 | waits = [] 105 | for i in range(N_REPEATS): 106 | seed = get_random(seed_caches[i], seed_length) 107 | start = time() 108 | ext.extract(inp1, seed) 109 | wait = time() - start 110 | waits.append(wait) 111 | print(".", end="", flush=True) 112 | rows.append(sum(waits) / len(waits)) 113 | indices.append(('Trevisan', inp_len, ratio)) 114 | print("") 115 | 116 | index = pd.MultiIndex.from_tuples(indices, names=names) 117 | 118 | df = pd.DataFrame(rows, index=index, columns=columns) 119 | 120 | print(df) 121 | 122 | ndf = df.copy().reset_index() 123 | ndf.loc[ndf["Extractor"] == "Von Neumann", "Ratio"] = 0.5 124 | ndf["Rate"] = ndf["Input Length"] * ndf["Ratio"] / ndf["Wait (s)"] 125 | -------------------------------------------------------------------------------- /cryptomite/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(GNUInstallDirs) 2 | 3 | include(FetchContent) 4 | FetchContent_Declare( 5 | pybind11 6 | URL https://github.com/pybind/pybind11/archive/refs/tags/v2.10.4.tar.gz 7 | ) 8 | FetchContent_MakeAvailable(pybind11) 9 | pybind11_add_module(_cryptomite MODULE pycryptomite.cpp) 10 | 11 | target_link_libraries(_cryptomite PUBLIC trevisan) 12 | 13 | install(TARGETS _cryptomite DESTINATION .) 14 | 15 | set(ignoreMe "${SKBUILD}") 16 | -------------------------------------------------------------------------------- /cryptomite/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | __all__ = [ 3 | 'circulant', 4 | 'dodis', 5 | 'toeplitz', 6 | 'trevisan', 7 | 'utils', 8 | 'Circulant', 9 | 'Dodis', 10 | 'Toeplitz', 11 | 'Trevisan', 12 | 'von_neumann' 13 | ] 14 | 15 | from cryptomite import circulant, dodis, toeplitz, trevisan, utils 16 | from cryptomite.circulant import Circulant 17 | from cryptomite.dodis import Dodis 18 | from cryptomite.toeplitz import Toeplitz 19 | from cryptomite.trevisan import Trevisan 20 | from cryptomite.utils import von_neumann 21 | 22 | __version__ = '0.2.3' 23 | -------------------------------------------------------------------------------- /cryptomite/circulant.py: -------------------------------------------------------------------------------- 1 | """ 2 | The Circulant extractor [For2024]_ takes an input of `n_1' bits and 3 | a (weak) seed of `n_1 + 1' bits, where `n_1 + 1' is prime. 4 | """ 5 | from __future__ import annotations 6 | 7 | from math import floor 8 | from typing import cast 9 | 10 | from cryptomite.utils import BitsT, closest_prime, conv, log_2 11 | 12 | __all__ = ['Circulant'] 13 | 14 | 15 | class Circulant: 16 | """ 17 | Circulant extractor based on [For2024]_. 18 | """ 19 | def __init__(self, n_1: int, m: int): 20 | """ 21 | Initialize a Circulant extractor. 22 | 23 | Parameters 24 | ---------- 25 | n_1 : int 26 | The length of the first input (in bits). 27 | **Note:** n_1 + 1 must be prime. 28 | m : int 29 | The length of the extractor output (in bits). 30 | """ 31 | self.n_1, self.m = n_1, m 32 | 33 | def extract(self, input1: BitsT, input2: BitsT) -> BitsT: 34 | """ 35 | Perform randomness extraction. 36 | 37 | Parameters 38 | ---------- 39 | input1 : list of bits (0s and 1s) 40 | The first input (the 'weak input'), consisting of n_1 bits. 41 | input2 : list of bits (0s and 1s) 42 | The second input (the '(weak) seed'), consisting 43 | of n_2 = n_1 + 1 bits. 44 | 45 | Returns 46 | ------- 47 | list of bits (0s and 1s) 48 | The extractor output bits, of length m. 49 | """ 50 | n_1, m = self.n_1, self.m 51 | assert len(input1) == len(input2) - 1 == n_1 52 | assert n_1 >= m 53 | input1 = input1 + [0] 54 | n_1 += 1 55 | input1, input2 = list(input1), list(input2) 56 | l = log_2(2 * n_1 - 2) 57 | L = 1 << l 58 | input1 = input1[0:1] + input1[1:][::-1] + [0] * (L - n_1) 59 | input2 = input2 + [0] * (L - len(input2)) 60 | conv_output = conv(l, input1, input2) 61 | output: BitsT = cast(BitsT, [ 62 | (conv_output[i] + conv_output[i + n_1]) & 1 for i in range(m)]) 63 | return output 64 | 65 | @staticmethod 66 | def from_params( 67 | n_1: int, 68 | k_1: float, 69 | n_2: int, 70 | k_2: float, 71 | log2_error: float, 72 | q_proof: bool, 73 | verbose: bool = True) -> Circulant: 74 | """ 75 | Generate a Circulant extractor with valid parameters 76 | based on input constraints. 77 | 78 | Parameters 79 | ---------- 80 | n_1 : int 81 | The length of the first input (in bits). 82 | k_1 : float 83 | The min-entropy of the first input. 84 | n_2 : int 85 | The length of the second input (in bits). 86 | k_2 : float 87 | The min-entropy of the second input. 88 | log2_error : float 89 | The logarithm (base 2) of the acceptable extractor error. 90 | Must be negative, as the extractor error is 2^log2_error. 91 | q_proof : bool 92 | If True, adjusts parameters to ensure quantum-proof extraction 93 | in the Markov and product sources models (see [For2024]_). 94 | verbose : bool 95 | If True, prints the parameters used for extraction (default: True). 96 | 97 | Returns 98 | ------- 99 | Circulant 100 | A configured Circulant extractor. 101 | 102 | Raises 103 | ------ 104 | ValueError 105 | If the output length is non-positive. 106 | 107 | Notes 108 | ----- 109 | - If n_2 is not prime, the function selects the closest prime and 110 | adjusts the other parameters accordingly. 111 | - For this extractor, the output length remains the same when it is 112 | classical-proof, quantum-proof in the product sources model, and 113 | quantum-proof in the Markov model (see [For2024]_). 114 | """ 115 | assert log2_error <= 0 116 | 117 | # Find the closest prime to the average of input lengths. 118 | n_2_adjusted = closest_prime((n_1 + n_2) // 2) 119 | 120 | # Adjust min-entropy values if input lengths exceed the computed prime. 121 | k_1_adjusted = k_1 - max(0, n_1 - (n_2_adjusted - 1)) 122 | k_2_adjusted = k_2 - max(0, n_2 - n_2_adjusted) 123 | 124 | # Compute the output length based on entropy constraints and 125 | # extraction error. 126 | if q_proof: 127 | m = floor(k_1_adjusted + (k_2_adjusted - n_2_adjusted) 128 | + 2 * log2_error) 129 | else: 130 | m = floor(k_1_adjusted + (k_2_adjusted - n_2_adjusted) 131 | + 2 * log2_error) 132 | 133 | # Ensure the output length is valid. 134 | if m <= 0: 135 | raise ValueError( 136 | 'Cannot extract with these parameters. ' 137 | 'Increase k_1, k_2, or log2_error.' 138 | ) 139 | 140 | # Print parameter details (if verbose). 141 | if verbose: 142 | print( 143 | f'--- New Circulant Extractor Parameters ---\n' 144 | f'Input Length 1 (n_1): {n_2_adjusted-1}, ' 145 | f'Min Entropy of Input 1 (k_1): {k_1_adjusted}, ' 146 | f'Input Length 2 (n_2): {n_2_adjusted}, ' 147 | f'Min Entropy of Input 2 (k_2): {k_2_adjusted}, ' 148 | f'Output Length (m): {m}, ' 149 | f'Extraction Error (log2_error): {log2_error}. ' 150 | ) 151 | print("""Adjust the length of the input 152 | and (weak) seed accordingly.""") 153 | 154 | return Circulant(n_2_adjusted - 1, m) 155 | -------------------------------------------------------------------------------- /cryptomite/dodis.py: -------------------------------------------------------------------------------- 1 | """ 2 | The Dodis et al. extractor [Dodis2004]_ takes two equal length inputs, 3 | each of length `n = n_1 = n_2'. This implementation is based on the 4 | improved cyclic shift matrices construction from [For2020]_, which 5 | requires n to be prime with primitive root 2. 6 | """ 7 | from __future__ import annotations 8 | 9 | from math import floor, log2 10 | from typing import cast 11 | 12 | from cryptomite.utils import BitsT, closest_na_set, conv, log_2 13 | 14 | __all__ = ['Dodis'] 15 | 16 | 17 | class Dodis: 18 | """ 19 | Dodis et al. extractor [Dodis2004]_, with implementation 20 | based on [For2020, For2024]_. 21 | """ 22 | def __init__(self, n: int, m: int): 23 | """ 24 | Initialize a Dodis extractor. 25 | 26 | Parameters 27 | ---------- 28 | n : int 29 | The length of the first input (in bits). 30 | **Note:** n must be prime with primitive root 2. 31 | m : int 32 | The length of the extractor output (in bits). 33 | """ 34 | self.n, self.m = n, m 35 | 36 | def extract(self, input1: BitsT, input2: BitsT) -> BitsT: 37 | """ 38 | Perform randomness extraction. 39 | 40 | Parameters 41 | ---------- 42 | input1 : list of bits (0s and 1s) 43 | The first input (the 'weak input'), consisting of n bits. 44 | input2 : list of bits (0s and 1s) 45 | The second input (the '(weak) seed'), consisting of n bits. 46 | 47 | Returns 48 | ------- 49 | list of bits (0s and 1s) 50 | The extractor output bits, of length m. 51 | """ 52 | n, m = self.n, self.m 53 | assert len(input1) == len(input2) == n 54 | assert n >= m 55 | l = log_2(2 * n - 2) 56 | L = 1 << l 57 | input1, input2 = list(input1), list(input2) 58 | input1 = input1[0:1] + input1[1:][::-1] + [0] * (L - n) 59 | input2 = input2 + [0] * (L - len(input2)) 60 | conv_output = conv(l, input1, input2) 61 | output: BitsT = cast(BitsT, [ 62 | (conv_output[i] + conv_output[i + n]) & 1 for i in range(m)]) 63 | return output 64 | 65 | @staticmethod 66 | def from_params( 67 | n_1: int, 68 | k_1: float, 69 | n_2: int, 70 | k_2: float, 71 | log2_error: float, 72 | q_proof: bool, 73 | verbose: bool = True) -> Dodis: 74 | """ 75 | Generate a Dodis et al. extractor with valid parameters 76 | based on input constraints. 77 | 78 | Parameters 79 | ---------- 80 | n_1 : int 81 | The length of the first input (in bits). 82 | k_1 : float 83 | The min-entropy of the first input. 84 | n_2 : int 85 | The length of the second input (in bits). 86 | k_2 : float 87 | The min-entropy of the second input. 88 | log2_error : float 89 | The logarithm (base 2) of the acceptable extractor error. 90 | Must be negative, as the extractor error is 2^log2_error. 91 | q_proof : bool 92 | If True, adjusts parameters to ensure quantum-proof extraction 93 | in the Markov and product sources models (see [For2024]_). 94 | verbose : bool 95 | If True, prints the parameters used for extraction (default: True). 96 | 97 | Returns 98 | ------- 99 | Dodis 100 | A configured Dodis et al. extractor. 101 | 102 | Raises 103 | ------ 104 | ValueError 105 | If the output length is non-positive. 106 | 107 | Notes 108 | ----- 109 | - If n is not prime with primitive root 2, the function 110 | selects the closest prime with primitive root 2 and 111 | adjusts the other parameters accordingly. 112 | """ 113 | assert log2_error <= 0 114 | 115 | # Find the closest prime with primitive root 2 116 | # to the average of input lengths. 117 | n_adjusted = closest_na_set((n_1 + n_2) // 2) 118 | 119 | # Adjust min-entropy values if input lengths exceed the 120 | # computed prime with primitive root 2. 121 | k_1_adjusted = k_1 - max(0, n_1 - n_adjusted) 122 | k_2_adjusted = k_2 - max(0, n_2 - n_adjusted) 123 | 124 | # Compute the output length based on entropy constraints and 125 | # extraction error. 126 | if q_proof: 127 | m = floor(0.2 * (k_1_adjusted + (k_2_adjusted 128 | - n_adjusted) + 8 * log2_error 129 | + 9 - 4 * log2(3))) 130 | else: 131 | m = floor(k_1 + (k_2 - n_adjusted) 132 | + 1 + 2 * log2_error) 133 | 134 | # Ensure the output length is valid. 135 | if m <= 0: 136 | raise ValueError( 137 | 'Cannot extract with these parameters. ' 138 | 'Increase k_1, k_2, or log2_error.' 139 | ) 140 | 141 | # Print parameter details (if verbose). 142 | if verbose: 143 | print( 144 | f'--- New Dodis et al. Extractor Parameters ---\n' 145 | f'Input Length 1 (n_1): {n_adjusted}, ' 146 | f'Min Entropy of Input 1 (k_1): {k_1_adjusted}, ' 147 | f'Input Length 2 (n_2): {n_adjusted}, ' 148 | f'Min Entropy of Input 2 (k_2): {k_2_adjusted}, ' 149 | f'Output Length (m): {m}, ' 150 | f'Extraction Error (log2_error): {log2_error}. ' 151 | ) 152 | print("""Adjust the length of the input 153 | and (weak) seed accordingly.""") 154 | return Dodis(n_adjusted, m) 155 | -------------------------------------------------------------------------------- /cryptomite/pycryptomite.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | namespace py = pybind11; 9 | 10 | // PYBIND11_MAKE_OPAQUE(std::vector); 11 | 12 | PYBIND11_MODULE(_cryptomite, m) { 13 | // optional module docstring 14 | m.doc() = "C++ Implementation of Randomness Extractors"; 15 | 16 | py::class_(m, "TrevisanConfig") 17 | .def(py::init()) 18 | .def_readonly("n", &TrevisanConfig::n) 19 | .def_readonly("m", &TrevisanConfig::m) 20 | .def_readonly("t", &TrevisanConfig::t) 21 | .def_readonly("l", &TrevisanConfig::l); 22 | 23 | py::class_(m, "Trevisan") 24 | .def(py::init()) 25 | .def("get_seed_length", &Trevisan::get_seed_length) 26 | .def("load_source", &Trevisan::load_source) 27 | .def("extract_bit", &Trevisan::extract_bit); 28 | 29 | py::class_(m, "NTT") 30 | .def(py::init()) 31 | .def("ntt", &NTT::ntt) 32 | .def("mul_vec", &NTT::mul_vec) 33 | .def("conv", &NTT::conv); 34 | 35 | py::class_(m, "BigNTT") 36 | .def(py::init()) 37 | .def("ntt", &BigNTT::ntt) 38 | .def("mul_vec", &BigNTT::mul_vec) 39 | .def("conv", &BigNTT::conv); 40 | } 41 | -------------------------------------------------------------------------------- /cryptomite/toeplitz.py: -------------------------------------------------------------------------------- 1 | """ 2 | The Toeplitz extractor takes an input of `n_1' bits and 3 | a (weak) seed of `n_2 = n_1 + m - 1' bits, where `m' is output length. 4 | This implementation is based on the efficient implementation 5 | presented in [For2024]_. 6 | """ 7 | from __future__ import annotations 8 | 9 | from math import floor 10 | from typing import cast 11 | 12 | from cryptomite.utils import BitT, BitsT, conv, log_2 13 | 14 | __all__ = ['Toeplitz'] 15 | 16 | 17 | class Toeplitz: 18 | """ 19 | Toeplitz extractor with implementation 20 | based on [For2024]_. 21 | """ 22 | def __init__(self, n_1: int, m: int): 23 | """ 24 | Initialize a Toeplitz extractor. 25 | 26 | Parameters 27 | ---------- 28 | n_1 : int 29 | The length of the first input (in bits). 30 | m : int 31 | The length of the extractor output (in bits). 32 | """ 33 | self.n_1, self.m = n_1, m 34 | 35 | def extract(self, input1: BitsT, input2: BitsT) -> BitsT: 36 | """ 37 | Perform randomness extraction. 38 | 39 | Parameters 40 | ---------- 41 | input1 : list of bits (0s and 1s) 42 | The first input (the 'weak input'), consisting of n_1 bits. 43 | input2 : list of bits (0s and 1s) 44 | The second input (the '(weak) seed'), consisting 45 | of n_2 = n_1 + m - 1 bits. 46 | 47 | Returns 48 | ------- 49 | list of bits (0s and 1s) 50 | The extractor output bits, of length m. 51 | """ 52 | n_1, m = self.n_1, self.m 53 | assert len(input1) == n_1 54 | assert len(input2) == n_1 + m - 1 55 | assert n_1 >= m 56 | l = log_2(2 * n_1) 57 | L = 1 << l 58 | input1, input2 = list(input1), list(input2) 59 | input1 = input1 + [0] * (L - n_1) 60 | input2 = input2[:m] + [0] * (L - (m + n_1 - 1)) + input2[m:] 61 | conv_output = conv(l, input1, input2) 62 | output: BitsT = [cast(BitT, x & 1) for x in conv_output[:m]] 63 | return output 64 | 65 | @staticmethod 66 | def from_params( 67 | n_1: int, 68 | k_1: float, 69 | n_2: int, 70 | k_2: float, 71 | log2_error: float, 72 | q_proof: bool, 73 | verbose: bool = True) -> Toeplitz: 74 | """ 75 | Generate a Toeplitz extractor with valid parameters 76 | based on input constraints. 77 | 78 | Parameters 79 | ---------- 80 | n_1 : int 81 | The length of the first input (in bits). 82 | k_1 : float 83 | The min-entropy of the first input. 84 | n_2 : int 85 | The length of the second input (in bits). 86 | k_2 : float 87 | The min-entropy of the second input. 88 | log2_error : float 89 | The logarithm (base 2) of the acceptable extractor error. 90 | Must be negative, as the extractor error is 2^log2_error. 91 | q_proof : bool 92 | If True, adjusts parameters to ensure quantum-proof extraction 93 | in the Markov and product sources models (see [For2024]_). 94 | verbose : bool 95 | If True, prints the parameters used for extraction (default: True). 96 | 97 | Returns 98 | ------- 99 | Toeplitz 100 | A configured Toeplitz extractor. 101 | 102 | Raises 103 | ------ 104 | ValueError 105 | If the length of the first source is great than or equal to 106 | that of the second. 107 | ValueError 108 | If the output length is non-positive. 109 | 110 | Notes 111 | ----- 112 | - For this extractor, the output length remains the same when it is 113 | classical-proof, quantum-proof in the product sources model, and 114 | quantum-proof in the Markov model (see [For2024]_). 115 | """ 116 | assert log2_error <= 0 117 | 118 | # Ensure the second input is longer than the first. 119 | if n_2 <= n_1: 120 | raise ValueError( 121 | 'The second input must be longer than the first.' 122 | 'Re-order the inputs or increase n_2.' 123 | ) 124 | 125 | n_2_adjusted = n_2 126 | k_2_adjusted = k_2 127 | 128 | if q_proof: 129 | m_max = floor(k_1 + (k_2 - n_2) + 2 * log2_error) 130 | else: 131 | m_max = floor(k_1 + (k_2 - n_2) + 2 * log2_error) 132 | 133 | # Compute the output length. 134 | if n_2 > n_1 + m_max - 1: 135 | n_2_adjusted = n_1 + m_max - 1 136 | m = m_max 137 | if n_2 < n_1 + m_max - 1: 138 | m = n_2 - n_1 + 1 139 | 140 | # Ensure the output length is valid. 141 | if m <= 0: 142 | raise ValueError( 143 | 'Cannot extract with these parameters. ' 144 | 'Increase k_1, k_2, or log2_error.' 145 | ) 146 | 147 | # Adjust the relevant parameters. 148 | n_2_adjusted = n_1 + m - 1 149 | k_2_adjusted = k_2 - max(0, n_2 - n_2_adjusted) 150 | 151 | # Print parameter details (if verbose). 152 | if verbose: 153 | print( 154 | f'--- New Circulant Extractor Parameters ---\n' 155 | f'Input Length 1 (n_1): {n_1}, ' 156 | f'Min Entropy of Input 1 (k_1): {k_1}, ' 157 | f'Input Length 2 (n_2): {n_2_adjusted}, ' 158 | f'Min Entropy of Input 2 (k_2): {k_2_adjusted}, ' 159 | f'Output Length (m): {m}, ' 160 | f'Extraction Error (log2_error): {log2_error}. ' 161 | ) 162 | print("""Adjust the length of the input 163 | and (weak) seed accordingly.""") 164 | 165 | return Toeplitz(n_1, m) 166 | -------------------------------------------------------------------------------- /cryptomite/trevisan.py: -------------------------------------------------------------------------------- 1 | """ 2 | The Trevisan extractor [Trev2001]_ takes an input of `n_1' bits and 3 | a seed of `n_2 = O(log n_1)' bits. 4 | This implementation is based on the implementation in [Mauer2012]_ 5 | and [For2024]_. 6 | """ 7 | from __future__ import annotations 8 | 9 | __all__ = ['Trevisan'] 10 | 11 | from cryptomite import _cryptomite 12 | 13 | 14 | class Trevisan: 15 | """ 16 | Trevisan extractor [Trev2001]_ with implementation 17 | based on [Mauer2012]_ and [For2024]_. 18 | """ 19 | def __init__(self, n: int, k: float, error: float): 20 | """Initialize a Trevisan Extractor. 21 | 22 | Parameters 23 | ---------- 24 | n : int 25 | The length of the input bits. 26 | k : float 27 | The min-entropy of the input bits. 28 | error : float 29 | The maximum acceptable extractor error. 30 | """ 31 | self.config = _cryptomite.TrevisanConfig(n, k, error) 32 | self.ext = _cryptomite.Trevisan(self.config) 33 | 34 | def extract(self, input1: list[bool], input2: list[bool]) -> list[bool]: 35 | """ 36 | Extract randomness. 37 | 38 | Parameters 39 | ---------- 40 | input1 : list of bits (0s and 1s) 41 | The first input (the 'weak input'), consisting of n bits. 42 | input2 : list of bits (0s and 1s) 43 | The second input (the '(weak) seed'). 44 | 45 | Returns 46 | ------- 47 | list of bits (0s and 1s) 48 | The extractor output bits, of length m. 49 | """ 50 | self.ext.load_source(input1, input2) 51 | 52 | m = self.config.m 53 | bits = [] 54 | for i in range(m): 55 | bit = self.ext.extract_bit(i) 56 | bits.append(bit) 57 | return bits 58 | -------------------------------------------------------------------------------- /cryptomite/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is a module. 3 | """ 4 | from __future__ import annotations 5 | 6 | from math import sqrt 7 | from typing import Literal, Sequence 8 | 9 | from cryptomite._cryptomite import BigNTT, NTT 10 | 11 | __all__ = ['is_prime', 'prime_facto', 'previous_prime', 'next_prime', 12 | 'closest_prime', 'previous_na_set', 'next_na_set', 13 | 'closest_na_set', 'suggest_extractor', 'von_neumann'] 14 | 15 | 16 | BitT = Literal[0, 1] 17 | BitsT = Sequence[BitT] 18 | 19 | 20 | def log_2(n: int) -> int: 21 | """ 22 | Take the ceiling of the base 2 logarithm of an integer. 23 | 24 | Parameters 25 | ---------- 26 | n : int 27 | The input integer. 28 | 29 | Returns 30 | ------- 31 | int 32 | The ceiling of the base 2 logarithm of n. 33 | """ 34 | x = 0 35 | while n > 0: 36 | n >>= 1 37 | x += 1 38 | return x 39 | 40 | 41 | def conv(l: int, source1: Sequence[int], source2: Sequence[int]) -> list[int]: 42 | """ 43 | Perform a cyclic convolution of size 2^l. 44 | 45 | Parameters 46 | ---------- 47 | l : int 48 | The base 2 logarithm of the size of the convolution. 49 | source1: list of int 50 | The first input vector. 51 | source2: list of int 52 | The second input vector. 53 | 54 | Returns 55 | ------- 56 | list[int] 57 | The output of the convolution. 58 | """ 59 | L = 1 << l 60 | assert len(source1) == len(source2) == L 61 | ntt = BigNTT(l) if l > 30 else NTT(l) 62 | # ntt_source1 = ntt.ntt(source1, False) 63 | # ntt_source2 = ntt.ntt(source2, False) 64 | # mul_source = ntt.mul_vec(ntt_source1, ntt_source2) 65 | # conv_output = ntt.ntt(mul_source, True) 66 | return ntt.conv(source1, source2) 67 | 68 | 69 | def is_prime(n: int) -> bool: 70 | """ 71 | Checks whether an integer is prime. 72 | 73 | Parameters 74 | ---------- 75 | n : int 76 | The integer to check for primality. 77 | 78 | Returns 79 | ------- 80 | bool 81 | Whether n is prime. 82 | """ 83 | for i in range(2, round(sqrt(n)) + 1): 84 | if n % i == 0: 85 | return False 86 | return True 87 | 88 | 89 | def prime_facto(n: int) -> tuple[list[int], list[int]]: 90 | """ 91 | Factorizes an integer into its prime factors and their powers. 92 | It is required for the later function: na_set. 93 | 94 | Parameters 95 | ---------- 96 | n : int 97 | The integer to check. 98 | 99 | Returns 100 | ------- 101 | list 102 | A list representing the prime factorization of n. 103 | """ 104 | factors = [] 105 | i = 2 106 | while i <= round(sqrt(n)) + 1: 107 | if n % i == 0: 108 | factors.append(i) 109 | n = n // i 110 | else: 111 | i = i + 1 112 | if n != 1: 113 | factors.append(n) 114 | factors2 = [factors[0]] 115 | powers = [1] 116 | p = 0 117 | # this will put 24 = 2 * 2 * 2 * 3 in the form 118 | # factors=(2,3) and powers=(3,1) for (2^3) * (3^1) 119 | for i in range(1, len(factors)): 120 | if factors[i] == factors[i - 1]: 121 | powers[p] = powers[p] + 1 122 | if factors[i] != factors[i - 1]: 123 | powers.append(1) 124 | p = p + 1 125 | factors2.append(factors[i]) 126 | return factors2, powers 127 | 128 | 129 | def previous_prime(k: int) -> int: 130 | """ 131 | Finds the largest prime number less than or equal to the given input. 132 | 133 | Parameters 134 | ---------- 135 | k : int 136 | The upper limit for checking prime numbers. 137 | 138 | Returns 139 | ------- 140 | int 141 | The largest prime number less than or equal to k. 142 | """ 143 | k -= 1 144 | if k % 2 != 0: 145 | k = k - 1 146 | stop = False 147 | while not stop: 148 | stop = True 149 | while not is_prime(k + 1): 150 | k = k - 2 151 | return k + 1 152 | 153 | 154 | def next_prime(k: int) -> int: 155 | """ 156 | Finds the smallest prime number greater than or equal to the given input. 157 | 158 | Parameters 159 | ---------- 160 | k : int 161 | The lower limit for checking prime numbers. 162 | 163 | Returns 164 | ------- 165 | int 166 | The smallest prime number greater than or equal to k. 167 | """ 168 | k -= 1 169 | if k % 2 != 0: 170 | k = k + 1 171 | stop = False 172 | while not stop: 173 | stop = True 174 | while not is_prime(k + 1): 175 | k = k + 2 176 | return k + 1 177 | 178 | 179 | def closest_prime(k: int) -> int: 180 | """ 181 | Finds the closest prime number to the given input. 182 | 183 | k : int 184 | The input value to find the closest prime number to. 185 | 186 | Returns 187 | ------- 188 | int 189 | The closest prime number to k. 190 | """ 191 | next_p = next_prime(k) 192 | previous_p = previous_prime(k) 193 | if next_p - k >= k - previous_p: 194 | out = previous_p 195 | else: 196 | out = next_p 197 | return out 198 | 199 | 200 | def previous_na_set(k: int) -> int: 201 | """ 202 | Finds the largest prime number with primitive root 2 203 | less than or equal to the given input. 204 | 205 | Parameters 206 | ---------- 207 | k : int 208 | The upper limit for checking prime numbers with 209 | primitive root 2. 210 | 211 | Returns 212 | ------- 213 | int 214 | The largest prime number with primitive root 2 215 | less than or equal to k. 216 | """ 217 | k -= 1 218 | if k % 2 != 0: 219 | k = k - 1 220 | stop = False 221 | while not stop: 222 | stop = True 223 | while not is_prime(k + 1): 224 | k = k - 2 225 | primes, _ = prime_facto(k) 226 | for prime in primes: 227 | if pow(2, k // prime, k + 1) == 1: 228 | stop = False 229 | k = k - 2 230 | break 231 | return k + 1 232 | 233 | 234 | def next_na_set(k: int) -> int: 235 | """ 236 | Finds the smallest prime number with primitive root 2 237 | greater than or equal to the given input. 238 | 239 | Parameters 240 | ---------- 241 | k : int 242 | The lower limit for checking prime numbers with 243 | primitive root 2. 244 | 245 | Returns 246 | ------- 247 | int 248 | The smallest prime number with primitive root 2 249 | greater than or equal to k. 250 | """ 251 | k -= 1 252 | if k % 2 != 0: 253 | k = k + 1 254 | stop = False 255 | while not stop: 256 | stop = True 257 | while not is_prime(k + 1): 258 | k = k + 2 259 | primes, _ = prime_facto(k) 260 | for prime in primes: 261 | if pow(2, k // prime, k + 1) == 1: 262 | stop = False 263 | k = k + 2 264 | break 265 | return k + 1 266 | 267 | 268 | def closest_na_set(k: int) -> int: 269 | """ 270 | Finds the closest prime number to the given input. 271 | 272 | k : int 273 | The input value to find the closest prime 274 | with primitive root 2 to. 275 | 276 | Returns 277 | ------- 278 | int 279 | The closest prime number with primitive root 2 280 | to k. 281 | """ 282 | next_p = next_na_set(k) 283 | previous_p = previous_na_set(k) 284 | if next_p - k >= k - previous_p: 285 | out = previous_p 286 | else: 287 | out = next_p 288 | return out 289 | 290 | 291 | def suggest_extractor(n_1: int, exchangeable_sequence: bool, 292 | efficiency_required: bool) -> str: 293 | """ 294 | Suggests the best extractor for a user, based on Fig.2 295 | from the technical paper. 296 | 297 | Parameters 298 | ---------- 299 | n_1 : int 300 | The length of the first input (in bits). 301 | exchangeable_sequence : bool 302 | Boolean input indicating whether the source forms an 303 | exchangeable sequence. 304 | efficiency_required : bool 305 | Boolean input indicating whether the user requires efficient 306 | extraction. 307 | 308 | Returns 309 | ------- 310 | string 311 | The suggested extractor. 312 | """ 313 | if exchangeable_sequence: 314 | out = 'Von Neumann' 315 | else: 316 | if n_1 <= 10**6 or efficiency_required: 317 | out = 'Circulant' 318 | else: 319 | out = 'Trevisan' 320 | return out 321 | 322 | 323 | def von_neumann(bits: BitsT) -> BitsT: 324 | """ 325 | Perform extraction using Von-Neumann extractor. 326 | """ 327 | return [x for x, y in zip(bits[::2], bits[1::2]) if x != y] 328 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/_static/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CQCL/cryptomite/8d140d71fce63c5a7a167dfd2d4cebfe0882362f/docs/_static/.DS_Store -------------------------------------------------------------------------------- /docs/_static/images/cryptomite_logo_blur.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CQCL/cryptomite/8d140d71fce63c5a7a167dfd2d4cebfe0882362f/docs/_static/images/cryptomite_logo_blur.png -------------------------------------------------------------------------------- /docs/_static/images/cryptomite_logo_full_blur.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CQCL/cryptomite/8d140d71fce63c5a7a167dfd2d4cebfe0882362f/docs/_static/images/cryptomite_logo_full_blur.png -------------------------------------------------------------------------------- /docs/_static/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CQCL/cryptomite/8d140d71fce63c5a7a167dfd2d4cebfe0882362f/docs/_static/images/favicon.png -------------------------------------------------------------------------------- /docs/bibliography.rst: -------------------------------------------------------------------------------- 1 | .. _sec-bibliography: 2 | 3 | Bibliography 4 | ============ 5 | 6 | .. [Jea2022] N. Jain, H. M. Chin, H. Mani et al. `Practical continuous-variable quantum key distribution with composable security `_, Nature communications (2022) 7 | 8 | .. [ATVV2021] M. Avesani, H. Tebyanian, P. Villoresi, G. Vallone. `Semi-device-independent heterodyne-based quantum random-number generator `_, Physical Review Applied (2021) 9 | 10 | .. [Gea2010] C. Gabriel, C. Wittmann, D. Sych et al. `A generator for unique quantum random numbers based on vacuum states `_, Nature Photonics (2010) 11 | 12 | .. [For2020] C. Foreman, S. Wright, A. Edgington et al. `Practical randomness amplification and privatisation with implementations on quantum computers `_, Quantum 7, 969 (2023). 13 | 14 | .. [Trev2001] L. Trevisan, `Extractors and Pseudorandom Generators `_, J. ACM, vol. 48, pp. 860–879 (2001) 15 | 16 | .. [Mauer2012] W. Mauerer, C. Portmann, and V. B. Scholz, `A modular framework for randomness extraction based on Trevisan’s construction `_, Pre-print (2012) 17 | 18 | .. [Dodis2004] Y. Dodis, A. Elbaz, R. Oliveira, and R. Raz, `Improved randomness extraction from two independent sources `_ in Proceedings RANDOM, vol. 3122, pp. 334–344 (2004) 19 | 20 | .. [For2024] C. Foreman, R. Yeung, A. Edgington et al. `Cryptomite: A versatile and user-friendly library of randomness extractors `_, Quantum 9, 1584 (2025). -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | # import os 14 | # import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | from cryptomite import __version__ 21 | 22 | release = version = __version__ 23 | 24 | 25 | project = 'cryptomite' 26 | copyright = '2025 Quantinuum Limited' 27 | author = 'Quantinuum Quantum Cryptography Team' 28 | 29 | # -- General configuration --------------------------------------------------- 30 | 31 | # Add any Sphinx extension module names here, as strings. They can be 32 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 33 | # ones. 34 | extensions = [ 35 | 'm2r2', 36 | 'nbsphinx', 37 | 'numpydoc', 38 | 'sphinx.ext.autodoc', 39 | 'sphinx.ext.viewcode', 40 | 'sphinx.ext.graphviz', 41 | 'sphinx.ext.inheritance_diagram', 42 | 'sphinxarg.ext', 43 | ] 44 | 45 | autodoc_default_options = { 46 | 'members': True, 47 | 'inherited-members': True, 48 | 'undoc-members': True, 49 | 'special-members': '__init__, __call__', 50 | } 51 | 52 | # This disables the need to document methods in the class docstring. 53 | numpydoc_show_class_members = False 54 | 55 | # Add any paths that contain templates here, relative to this directory. 56 | templates_path = ['_templates'] 57 | 58 | # List of patterns, relative to source directory, that match files and 59 | # directories to ignore when looking for source files. 60 | # This pattern also affects html_static_path and html_extra_path. 61 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 62 | 63 | 64 | # -- Options for HTML output ------------------------------------------------- 65 | 66 | # The theme to use for HTML and HTML Help pages. See the documentation for 67 | # a list of builtin themes. 68 | # 69 | html_theme = 'sphinx_rtd_theme' 70 | html_context = { 71 | 'conf_py_path': '/docs/' 72 | } 73 | 74 | # Add any paths that contain custom static files (such as style sheets) here, 75 | # relative to this directory. They are copied after the builtin static files, 76 | # so a file named "default.css" will overwrite the builtin "default.css". 77 | html_static_path = ['_static'] 78 | html_logo = '_static/images/cryptomite_logo_blur.png' 79 | html_favicon = '_static/images/favicon.png' 80 | 81 | numfig = True 82 | nbsphinx_allow_errors = True 83 | 84 | -------------------------------------------------------------------------------- /docs/cryptomite.rst: -------------------------------------------------------------------------------- 1 | API Reference 2 | ============= 3 | 4 | Submodules 5 | ---------- 6 | 7 | cryptomite.circulant module 8 | --------------------------- 9 | 10 | .. automodule:: cryptomite.circulant 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | cryptomite.dodis module 16 | ----------------------- 17 | 18 | .. automodule:: cryptomite.dodis 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | cryptomite.toeplitz module 24 | -------------------------- 25 | 26 | .. automodule:: cryptomite.toeplitz 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | cryptomite.trevisan module 32 | -------------------------- 33 | 34 | .. automodule:: cryptomite.trevisan 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | cryptomite.utils module 40 | ----------------------- 41 | 42 | .. automodule:: cryptomite.utils 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | Module contents 48 | --------------- 49 | 50 | .. automodule:: cryptomite 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | -------------------------------------------------------------------------------- /docs/examples/example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "attachments": {}, 5 | "cell_type": "markdown", 6 | "id": "512571e4", 7 | "metadata": {}, 8 | "source": [ 9 | "# Basic Usage\n", 10 | "\n", 11 | "In this notebook, we give a few examples of how to use ``Cryptomite`` and accompanying utility functions. \n", 12 | "A similar, more introductory, guide to getting started can be found in this [blog post](https://medium.com/quantinuum/introducing-cryptomite-randomness-extraction-simplified-857fc2f87673)." 13 | ] 14 | }, 15 | { 16 | "cell_type": "markdown", 17 | "id": "1a031d29", 18 | "metadata": {}, 19 | "source": [ 20 | "## Quick Start\n", 21 | "\n", 22 | "``pip install cryptomite``" 23 | ] 24 | }, 25 | { 26 | "cell_type": "markdown", 27 | "id": "4b899d5b", 28 | "metadata": {}, 29 | "source": [ 30 | "## Initializing an extractor\n", 31 | "\n", 32 | "Using ``Cryptomite``, extractors can be initialized by giving the input length (in bits) ``n_1`` and output length (in bits) ``m``, or in the case of the Trevisan extractor, giving the input length ``n``, output length ``m`` and maximum acceptable extractor error,``error``.\n", 33 | "We generate all the extractors of ``Cryptomite`` below, for an example ``n_1``, ``m``, ``error``:\n" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": null, 39 | "id": "5f168725", 40 | "metadata": {}, 41 | "outputs": [], 42 | "source": [ 43 | "import cryptomite\n", 44 | "n_1, m, error = 100, 90, 0.0001\n", 45 | "circulant = cryptomite.Circulant(n_1, m)\n", 46 | "dodis = cryptomite.Dodis(n_1, m)\n", 47 | "toeplitz = cryptomite.Toeplitz(n_1, m)\n", 48 | "trevisan = cryptomite.Trevisan(n_1, m, error)" 49 | ] 50 | }, 51 | { 52 | "cell_type": "markdown", 53 | "id": "23b75513", 54 | "metadata": {}, 55 | "source": [ 56 | "## Performing extraction\n", 57 | "\n", 58 | "Now the extractors have been initialized, we demonstrate how to perform extraction. \n", 59 | "The extractors take a list of input bits and a list of (weak) seed bits as input and output a new list of bits.\n", 60 | "Each extractor of ``Cryptomite`` requires a different amount of (weak) seed bits and the calculation of this is left to the user (either manually or using our ``from_params`` utility functions). \n", 61 | "In the following example, we generate both the input string and the (weak) seed using ``randint`` function from the ``random`` Python library, which users would replace with their desired generation methods, and use ``n_1`` and ``n_2`` to represent the input and (weak) seed lengths, respectively." 62 | ] 63 | }, 64 | { 65 | "cell_type": "code", 66 | "execution_count": null, 67 | "id": "0d28807c", 68 | "metadata": {}, 69 | "outputs": [], 70 | "source": [ 71 | "from random import randint\n", 72 | "input_bits = [randint(0, 1) for _ in range(n_1)]\n", 73 | "circulant.extract(input_bits, [randint(0, 1) for _ in range(n_2)])\n", 74 | "dodis.extract(input_bits, [randint(0, 1) for _ in range(n_2)])\n", 75 | "toeplitz.extract(input_bits, [randint(0, 1) for _ in range(n_2)])\n", 76 | "trevisan.extract(input_bits, [randint(0, 1) for _ in range(n_2)])" 77 | ] 78 | }, 79 | { 80 | "cell_type": "markdown", 81 | "id": "42f7f24f", 82 | "metadata": { 83 | "raw_mimetype": "text/restructuredtext" 84 | }, 85 | "source": [ 86 | "## Using the from_params utility functions\n", 87 | "\n", 88 | "Alternatively, all ``Cryptomite`` extractors (except Trevisan) can be generated without manually calculating the output or seed length, by using the ``from_params`` utility functions. \n", 89 | "Simply provide the input length, input min-entropy, (weak) seed length, (weak) seed min-entropy, maximum acceptable extractor error and specify whether to calculate the quantum-proof output length or not. \n", 90 | "For example:" 91 | ] 92 | }, 93 | { 94 | "cell_type": "code", 95 | "execution_count": null, 96 | "id": "19ba95c7", 97 | "metadata": {}, 98 | "outputs": [], 99 | "source": [ 100 | "import cryptomite\n", 101 | "from math import log2\n", 102 | "n_1, k_1 = 100, 60\n", 103 | "n_2, k_2 = 100, 80\n", 104 | "error = 0.00001\n", 105 | "q_proof = False\n", 106 | "log2_error = log2(error)\n", 107 | "circulant = cryptomite.Circulant.from_params(n_1, k_1, n_2, k_2, log2_error, q_proof)\n", 108 | "dodis = cryptomite.Dodis.from_params(n_1, k_1, n_2, k_2, log2_error, q_proof)\n", 109 | "toeplitz = cryptomite.Toeplitz.from_params(n_1, k_1, n_2, k_2, log2_error, q_proof)" 110 | ] 111 | }, 112 | { 113 | "cell_type": "markdown", 114 | "id": "fb5cbab8", 115 | "metadata": {}, 116 | "source": [ 117 | "In this case, a **valid** extractor is generated from the specified parameters (i.e. one that adheres to the required input and seed length criteria for that extractor).\n", 118 | "Any changes to input length, seed length, input entropy and seed entropy printed." 119 | ] 120 | } 121 | ], 122 | "metadata": { 123 | "kernelspec": { 124 | "display_name": "Python 3 (ipykernel)", 125 | "language": "python", 126 | "name": "python3" 127 | }, 128 | "language_info": { 129 | "codemirror_mode": { 130 | "name": "ipython", 131 | "version": 3 132 | }, 133 | "file_extension": ".py", 134 | "mimetype": "text/x-python", 135 | "name": "python", 136 | "nbconvert_exporter": "python", 137 | "pygments_lexer": "ipython3", 138 | "version": "3.9.16" 139 | } 140 | }, 141 | "nbformat": 4, 142 | "nbformat_minor": 5 143 | } 144 | -------------------------------------------------------------------------------- /docs/figures/Table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CQCL/cryptomite/8d140d71fce63c5a7a167dfd2d4cebfe0882362f/docs/figures/Table.png -------------------------------------------------------------------------------- /docs/figures/extractor_flow_chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CQCL/cryptomite/8d140d71fce63c5a7a167dfd2d4cebfe0882362f/docs/figures/extractor_flow_chart.png -------------------------------------------------------------------------------- /docs/figures/performance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CQCL/cryptomite/8d140d71fce63c5a7a167dfd2d4cebfe0882362f/docs/figures/performance.png -------------------------------------------------------------------------------- /docs/gettingstarted.rst: -------------------------------------------------------------------------------- 1 | Selecting a Randomness Extractor 2 | ================================ 3 | In general, the choice of randomness extractor depends on the scenario in which it is used, and it is not always clear which extractor is best suited to a given situation. 4 | Here, we informally summarize the information from the 'Overview of Extractor Library' in [For2024]_, which assists a user in selecting the best randomness extractor for their task. 5 | 6 | If a user begins with some extractor input of length :math:`n_1` with :term:`min-entropy` :math:`k_1`, the flowchart below provides a useful guide for selecting an appropriate extractor. 7 | 8 | .. image:: figures/extractor_flow_chart.png 9 | :width: 600 10 | 11 | This flowchart gives a good general approach to follow, however, some improvements can be obtained by analyzing each extractor individually. 12 | The maximum output length for each extractor in different security models is detailed in the following table, 13 | where :math:`n_2` and :math:`k_2` denote the length and min-entropy of the (weak) seed, respectively and :math:`\epsilon` the extractor error. 14 | 15 | .. image:: figures/Table.png 16 | :width: 600 17 | 18 | The derivation of these parameters and all relevant proofs can be found in [For2024]_. -------------------------------------------------------------------------------- /docs/glossary.rst: -------------------------------------------------------------------------------- 1 | Glossary 2 | ======== 3 | 4 | .. glossary:: 5 | 6 | min-entropy 7 | The min-entropy, :math:`k`, of a random variable, :math:`X \in \{ 0,1\}^n`, is defined as 8 | 9 | .. math:: 10 | k = H_{\infty}(X) = -\log _{ \max x \in \{ 0,1\}^n} \Pr (X = x | \lambda) 11 | 12 | where :math:`\lambda` is any additional side information a physical observer may have. 13 | This can be interpreted as the minimum amount of random bits a random variable :math:`X` has, conditioned on the side information :math:`\lambda`. 14 | 15 | block min-entropy 16 | A set of random variables :math:`X_i`` for :math:`i \in \mathbb{Z}` is said to have block min entropy :math:`k`, if 17 | 18 | .. math:: 19 | k = H_{\infty}(X_i) = -\log _{ \max x \in \{ 0,1\}^n} \Pr (X_i = x | X_0, X_1, ..., X_{i-1}, \lambda) 20 | 21 | for all :math:`i`, where :math:`\lambda` is any additional side information a physical observer may have. 22 | This can be interpreted as the minimum amount of random bits a random variable :math:`X_i` has, even when conditioned on all previous random variables in the set and side information :math:`\lambda`. 23 | 24 | statistical distance 25 | The statistical distance, :math:`\Delta`, between two random variables, :math:`X` and :math:`Z` :math:`\in \{0,1\}^n` is defined as 26 | 27 | .. math:: 28 | \Delta(X,Z) = \frac{1}{2} \sum_{v \in \{ 0,1 \}^n} | \Pr(X=v | \lambda) - \Pr(Z=v | \lambda)| 29 | 30 | where :math:`\lambda` is any additional side information a physical observer may have. 31 | This is a measure of how close, or indistinguishable, two distributions are to one another. 32 | 33 | perfect randomness 34 | A distribution :math:`X` on :math:`\{0,1\}^n` is said to be perfectly random, if, 35 | 36 | .. math:: 37 | \Delta(X, U_n) = 0 38 | 39 | where :math:`U_n` is the uniform variable on :math:`\{0,1\}^n`. 40 | This definition is equivalent to saying that the variable :math:`X` is completely indistinguishable from the uniform distribution to a physical observer. 41 | Note: This is a composable definition, any random variable :math:`X` satisfying this definition can be safely used in practical applications. 42 | 43 | near-perfect randomness 44 | A random variable :math:`X`` on :math:`\{0,1\}^n` is said to be near-perfectly random, if, 45 | 46 | .. math:: 47 | \Delta(X, U_n) \leq \epsilon 48 | 49 | where :math:`U_n` is the uniform variable on :math:`\{0,1\}^n`. 50 | This definition is equivalent to saying that the variable :math:`X` is :math:`\epsilon` close to indistinguishable from the uniform distribution to a physical observer. 51 | Note: This is a composable definition, any random variable :math:`X` satisfying this definition can be safely used in practical applications. 52 | 53 | deterministic randomness extractor 54 | A :math:`(k, \epsilon, n, m)`-deterministic randomness extractor is a function 55 | 56 | .. math:: 57 | \mathrm{Ext}_d: \{ 0,1\}^n \rightarrow \{0,1\}^m 58 | 59 | such that, for every random variable :math:`X` on :math:`\{ 0,1\}^n` with min-entropy :math:`H_{\infty}(X) \geq k`, then, 60 | 61 | .. math:: 62 | \Delta(\mathrm{Ext}_d(X), U_m) \leq \epsilon 63 | 64 | where :math:`U_m` is the uniform variable on :math:`\{0,1\}^m`. 65 | In words, a deterministic extractor is a deterministic function that maps a random variable :math:`X` to a new variable :math:`\mathrm{Ext}_d(X)` that is near-perfect, as defined in :term:`near-perfect randomness`. 66 | 67 | seeded randomness extractor 68 | A :math:`(k, \epsilon, n, d, m)`-seeded randomness extractor is a function 69 | 70 | .. math:: 71 | \mathrm{Ext}_s: \{ 0,1\}^{n} \times \{0,1\}^{d} \rightarrow \{0,1\}^m 72 | 73 | such that, for every random variable :math:`X` on :math:`\{ 0,1\}^{n}` with min-entropy :math:`H_{\infty}(X) \geq k`, and every :math:`S` on :math:`\{ 0,1\}^d` with min-entropy :math:`H_{\infty}(Y) = d` then, 74 | 75 | .. math:: 76 | \Delta(\mathrm{Ext}_s(X, S), U_m) \leq \epsilon 77 | 78 | where :math:`U_m` is the uniform distribution on :math:`\{0,1\}^m`. 79 | In words, a seeded extractor is a randomized function that maps a random variable :math:`X` to a new variable :math:`\mathrm{Ext}_s(X, S)` that is near-perfect, as defined in :term:`near-perfect randomness`. 80 | 81 | 2-source randomness extractor 82 | A :math:`(k_1, k_2, \epsilon, n_1, n_2, m)`-2-weak-source randomness extractor is a function 83 | 84 | .. math:: 85 | \mathrm{Ext}_2: \{ 0,1\}^{n_1} \times \{0,1\}^{n_2} \rightarrow \{0,1\}^m 86 | 87 | such that, for every independent random variable :math:`X` on :math:`\{ 0,1\}^{n_1}` with min-entropy :math:`H_{\infty}(X) \geq k_1`, and :math:`Y` on :math:`\{ 0,1\}^{n_2}` with min-entropy :math:`H_{\infty}(Y) \geq k_2` then, 88 | 89 | .. math:: 90 | \Delta(\mathrm{Ext}_2(X, Y), U_m) \leq \epsilon 91 | 92 | where :math:`U_m` is the uniform variable on :math:`\{0,1\}^m`. 93 | In words, a 2-weak-source extractor is a weakly randomized function that maps a random variable :math:`X` to a new variable :math:`\mathrm{Ext}_2(X, Y)` that is near-perfect, as defined in :term:`near-perfect randomness`. 94 | 95 | strong seeded extractor 96 | A strong seeded randomness extractor is any :math:`\mathrm{Ext}_s` s.t. 97 | 98 | .. math:: 99 | \Delta( (\mathbf{Ext}_s(X, S), S), (U_m, S) ) \leq \epsilon 100 | 101 | where :math:`U_m` is the uniform variable on :math:`\{0,1\}^m`. 102 | Note, we use bold font to denote strong extractors. 103 | In words, a strong seeded extractor is a randomized function that maps a random variable :math:`X` to a new variable :math:`\mathbf{Ext}_s(X, S)` that is near-perfect, as defined in :term:`near-perfect randomness` and where :math:`\mathrm{Ext}_s(X, S)` is (near-) independent of :math:`X`. 104 | 105 | Intuitively, this has 3 main implications: 106 | 107 | #. The seed :math:`S` can be made public without compromising the uniformity of the extractor output. 108 | #. The seed :math:`S` can be concatenated with the output :math:`\mathbf{Ext_s}(X,S)` to get a longer, (near-)perfect output. 109 | #. The seed :math:`S` can be re-used with different input sources. -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | ========== 2 | Cryptomite 3 | ========== 4 | 5 | :py:mod:`Cryptomite` is a Python library of randomness extractors, created by Quantinuum's Quantum Cryptography team. 6 | At a high level, the library offers state-of-the-art randomness extractors that are easy to use, optimized and numerically precise -- 7 | providing a trade-off of features suitable for a wide variety of practical applications. 8 | 9 | The performance critical parts of the library (e.g. NTT) are implemented in C++, but the rest of the 10 | library (e.g. parameter estimation) is implemented in Python for accessibility and ease of use. 11 | 12 | The package is available for Python 3.8 and higher on Mac, Windows and Linux. To install, type: 13 | 14 | .. code-block:: bash 15 | 16 | pip install cryptomite 17 | 18 | To see the example notebooks, go to Examples. 19 | 20 | User Support 21 | ============ 22 | If you need help with :py:mod:`Cryptomite`, or think you have found a bug, please email 23 | qcrypto@quantinuum.com. 24 | 25 | Licence 26 | ======= 27 | See `license `_ here. 28 | In summary, you are free to use, modify and distribute to :py:mod:`Cryptomite` 29 | for academic purposes. If you wish to use it for commercial use, contact 30 | qcrypto@quantinuum.com 31 | 32 | How to Cite 33 | =========== 34 | If you use :py:mod:`Cryptomite` in your research, please cite the accompanying paper: 35 | `Cryptomite: A versatile and user-friendly library of randomness extractors `_. 36 | 37 | 38 | .. toctree:: 39 | :caption: Toolkit 40 | :maxdepth: 4 41 | 42 | intro 43 | gettingstarted 44 | cryptomite 45 | performance 46 | notebooks 47 | glossary 48 | bibliography 49 | -------------------------------------------------------------------------------- /docs/intro.rst: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | Randomness extractors are functions that distil near-uniform randomness from weakly random inputs. 4 | These functions have extensive applications, including derandomization in computational complexity theory, list-decodable error-correcting codes, and most notably, quantum cryptography. 5 | In quantum cryptography, tasks like key distribution and random number generation involve several critical (classical) post-processing steps. 6 | Randomness extractors are crucial in these processes, allowing to purify a shared raw secret key (known as privacy amplification) and to extract near-perfect randomness from a weakly random entropy source (known as randomness extraction) in the presence of an adversary. 7 | Recent advancements in theoretical and experimental quantum cryptography have led to exciting proof-of-concept demonstrations and even commercial products. 8 | However, all of these require an appropriate randomness extractor with suitable implementation complexity, loss/error rates, and security. 9 | To address this, we developed :code:`Cryptomite`, a software library with multiple state-of-the-art randomness extractors that are easy to use, highly optimized and numerically precise (using the number-theoretic transform where possible to reduce complexity, whilst not needing to use floating point arithmetic like the fast Fourier transform). -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/notebooks.rst: -------------------------------------------------------------------------------- 1 | .. _sec-examples: 2 | 3 | Example Usage 4 | ============= 5 | 6 | Here's how to get started with ``cryptomite``: 7 | 8 | .. toctree:: 9 | 10 | ../examples/example.ipynb 11 | -------------------------------------------------------------------------------- /docs/performance.rst: -------------------------------------------------------------------------------- 1 | Performance 2 | =========== 3 | 4 | To demonstrate the capabilities of :py:mod:`Cryptomite`, we conducted benchmarking on a MacBook Pro with a 2 GHz quad-core Intel i5 processor and 16GB RAM 5 | The code for this benchmarking is available in `cryptomite/bench/bench.py`, enabling users to reproduce the results on their own machines. 6 | This testing assumes that the min-entropy of the first source (the weak input) is :math:`k_1 = n_1 / 2`. 7 | 8 | .. image:: figures/performance.png 9 | :width: 600 -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | ipykernel==6.9.2 2 | m2r2==0.3.2 3 | nbformat==5.1.3 4 | nbsphinx==0.8.8 5 | numpydoc==1.1.0 6 | Sphinx==4.4.0 7 | sphinx-argparse==0.3.1 8 | sphinx-rtd-theme==1.0.0 9 | lxml_html_clean==0.1.0 10 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | "wheel", 5 | "scikit-build", 6 | "cmake", 7 | ] 8 | build-backend = "setuptools.build_meta" 9 | 10 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = cryptomite 3 | description = An awesome extractor library 4 | long_description = file: README.md 5 | long_description_content_type = text/markdown 6 | version = 0.2.3 7 | author = Cambridge Quantum 8 | author_email = qcrypto@cambridgequantum.com 9 | license_files = file: LICENSE 10 | keywords = 11 | quantum 12 | quantum computing 13 | classifiers = 14 | Intended Audience :: Developers 15 | Intended Audience :: Science/Research 16 | Operating System :: OS Independent 17 | Programming Language :: Python :: 3 18 | Programming Language :: Python :: 3.8 19 | Programming Language :: Python :: 3.9 20 | Programming Language :: Python :: 3.10 21 | Topic :: Scientific/Engineering 22 | 23 | [options] 24 | packages = 25 | cryptomite 26 | install_requires = 27 | python_requires = >=3.8 28 | 29 | [flake8] 30 | ignore = 31 | # The default list except for W504 (line break after binary operator) 32 | # and E741 (ambiguous variable name 'l') 33 | E121,E123,E126,E226,E24,E704,E741,W503 34 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from skbuild import setup 4 | 5 | 6 | setup( 7 | zip_safe=False, 8 | packages=["cryptomite"], 9 | cmake_install_dir="cryptomite", 10 | ) 11 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | venv/ 3 | .cache/ 4 | __pycache__/ 5 | -------------------------------------------------------------------------------- /src/.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "pybind11"] 2 | path = pybind11 3 | url = https://github.com/pybind/pybind11.git 4 | -------------------------------------------------------------------------------- /src/.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | sudo: false 4 | 5 | matrix: 6 | include: 7 | - os: linux 8 | compiler: gcc 9 | addons: &gcc49 10 | apt: 11 | sources: ['ubuntu-toolchain-r-test'] 12 | packages: ['g++-4.9', 'gcc-4.9'] 13 | env: 14 | - CXX='g++-4.9' 15 | - CC='gcc-4.9' 16 | python: 2.7 17 | - os: linux 18 | compiler: gcc 19 | addons: &gcc49 20 | apt: 21 | sources: ['ubuntu-toolchain-r-test'] 22 | packages: ['g++-4.9', 'gcc-4.9'] 23 | env: 24 | - CXX='g++-4.9' 25 | - CC='gcc-4.9' 26 | python: 3.4 27 | - os: osx 28 | osx_image: xcode7.3 29 | compiler: gcc 30 | sudo: required 31 | language: generic 32 | 33 | install: 34 | - | 35 | if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then 36 | # manually install python on osx 37 | brew update 38 | brew install python3 39 | brew reinstall gcc 40 | virtualenv venv 41 | source venv/bin/activate 42 | pip install -r requirements.txt --upgrade 43 | fi 44 | - pip install -r requirements.txt --upgrade 45 | - python --version 46 | 47 | script: 48 | - mkdir build 49 | - cd build 50 | - cmake .. 51 | - make 52 | - ls 53 | # - python -c 'import example' 54 | - cd .. 55 | - pep8 --ignore E501 test.py 56 | # - PYTHONPATH=build pytest -vv test.py 57 | 58 | notifications: 59 | email: false 60 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(trevisan trevisan.cpp irreducible_poly.cpp ntt.cpp bigntt.cpp) 2 | 3 | target_include_directories(trevisan PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) 4 | 5 | set_property(TARGET trevisan PROPERTY POSITION_INDEPENDENT_CODE ON) 6 | -------------------------------------------------------------------------------- /src/bigntt.cpp: -------------------------------------------------------------------------------- 1 | #include "bigntt.h" 2 | 3 | #include 4 | #include 5 | 6 | #define P ((9ull<<42) + 1) 7 | #define G 5 // primitive root mod P 8 | 9 | uint64_t add(uint64_t a, uint64_t b) { 10 | uint64_t c = a + b; 11 | if (c >= P) { 12 | c -= P; 13 | } 14 | 15 | return c; 16 | } 17 | 18 | /** 19 | * Subtraction: a-b mod P 20 | * 21 | * @pre a,b < P 22 | */ 23 | static uint64_t sub(uint64_t a, uint64_t b) { 24 | uint64_t c = a - b; 25 | if (a < b) { 26 | c += P; 27 | } 28 | 29 | return c; 30 | } 31 | 32 | uint64_t mul(uint64_t a, uint64_t b) { 33 | // correct if a,b,P < 2^57 34 | uint64_t c = (double)a * b / P; 35 | int64_t ans = int64_t(a * b - c * P) % int64_t(P); 36 | if (ans < 0) 37 | ans += P; 38 | return ans; 39 | } 40 | 41 | /** 42 | * Modular exponentiation: a^e mod P 43 | */ 44 | static uint64_t modexp(uint64_t a, uint64_t e) { 45 | // e is not secret, no need to make constant time 46 | uint64_t r = 1; 47 | while (e) { 48 | if (e&1) { 49 | r = mul(r, a); 50 | } 51 | e >>= 1; 52 | a = mul(a, a); 53 | } 54 | return r; 55 | } 56 | 57 | /** 58 | * Reverse the bits of x (an l-bit number) 59 | */ 60 | static uint64_t reverse_bits(unsigned l, uint64_t x) { 61 | uint64_t y = 0; 62 | while (l) { 63 | l--; 64 | y |= ((x&1) << l); 65 | x >>= 1; 66 | } 67 | return y; 68 | } 69 | 70 | BigNTT::BigNTT(unsigned l) : L(1ll< 40) { 72 | throw std::runtime_error("Must have 1 <= l <= 40."); 73 | } 74 | 75 | Linv = modexp(L, P-2); 76 | 77 | uint64_t half_L = L/2; 78 | 79 | R = std::vector(half_L); 80 | Rinv = std::vector(half_L); 81 | revbits = std::vector(L); 82 | 83 | uint64_t r = modexp(G, (P - 1) >> l); // primitive L'th root of unity 84 | 85 | { 86 | { 87 | uint64_t t = 1; 88 | for (uint64_t i = 0; i < half_L; i++) { 89 | R[i] = t; 90 | t = mul(t, r); 91 | } 92 | } 93 | 94 | { 95 | // r^(L/2) = -1 96 | uint64_t t = P - 1; 97 | for (uint64_t i = 1; i <= half_L; i++) { 98 | t = mul(t, r); 99 | Rinv[half_L - i] = t; 100 | } 101 | } 102 | } 103 | 104 | for (uint64_t i = 0; i < L; i++) { 105 | revbits[i] = reverse_bits(l, i); 106 | } 107 | 108 | } 109 | 110 | std::vector BigNTT::ntt(const std::vector &x, bool inverse) { 111 | const std::vector& U = inverse ? Rinv : R; 112 | 113 | std::vector y(L, 0); 114 | 115 | // Bit inversion 116 | for (uint64_t i = 0; i < L; i++) { 117 | y[revbits[i]] = x[i]; 118 | } 119 | 120 | // Main loop 121 | for ( 122 | uint64_t h = 2, k = 1, u = L/2; 123 | h <= L; 124 | k = h, h <<= 1, u >>= 1) 125 | { 126 | for (uint64_t i = 0; i < L; i += h) { 127 | for (uint64_t j = 0, v = 0; j < k; j++, v += u) { 128 | uint64_t r = i + j; 129 | uint64_t s = r + k; 130 | uint64_t a = y[r]; 131 | uint64_t b = mul(y[s], U[v]); 132 | y[r] = add(a, b); 133 | y[s] = sub(a, b); 134 | } 135 | } 136 | } 137 | 138 | // Normalization for inverse 139 | if (inverse) { 140 | for (uint64_t i = 0; i < L; i++) { 141 | y[i] = mul(Linv, y[i]); 142 | } 143 | } 144 | return y; 145 | } 146 | 147 | std::vector BigNTT::mul_vec(const std::vector &a, const std::vector &b) { 148 | std::vector c(a.size()); 149 | for (uint64_t i = 0; i < a.size(); i++) { 150 | c[i] = mul(a[i], b[i]); 151 | } 152 | return c; 153 | } 154 | 155 | std::vector BigNTT::conv(const std::vector &a, const std::vector &b) { 156 | std::vector c = mul_vec(ntt(a, false), ntt(b, false)); 157 | return ntt(c, true); 158 | } -------------------------------------------------------------------------------- /src/bigntt.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class BigNTT { 7 | private: 8 | /** Sequence length (power of 2) */ 9 | uint64_t L; 10 | 11 | /** Inverse of L mod p */ 12 | uint64_t Linv; 13 | 14 | /** 15 | * Powers 1, r, r^2, ..., r^(L/2-1) mod p, where r is a primitive L'th 16 | * root of unity mod p 17 | */ 18 | std::vector R; 19 | 20 | /** 21 | * Inverse powers 1, r^{-1}, r^{-2}, ..., r{-^(L/2-1)} mod p 22 | */ 23 | std::vector Rinv; 24 | 25 | /** 26 | * Lookup table for bit reversals 27 | */ 28 | std::vector revbits; 29 | 30 | public: 31 | explicit BigNTT(unsigned l); 32 | 33 | std::vector ntt(const std::vector &x, bool inverse); 34 | 35 | std::vector mul_vec(const std::vector &a, const std::vector &b); 36 | std::vector conv(const std::vector &a, const std::vector &b); 37 | }; 38 | -------------------------------------------------------------------------------- /src/irreducible_poly.cpp: -------------------------------------------------------------------------------- 1 | #include "irreducible_poly.h" 2 | 3 | #include 4 | 5 | // Minimal weight primitive polynomials over Z/2Z 6 | // In addition the coefficients/bits (apart from the 7 | // constant and the leading term) are as close to 8 | // the low end as possible. 9 | // 10 | // Generated by Joerg Arndt, 2003-January-30 11 | const std::vector> minweight_primpoly_coeffs { 12 | // coeffs // (deg) [weight] 13 | {0}, // (0) [1] 14 | {1, 0}, // (1) [2] 15 | {2,1,0}, // (2) [3] 16 | {3,1,0}, // (3) [3] 17 | {4,1,0}, // (4) [3] 18 | {5,2,0}, // (5) [3] 19 | {6,1,0}, // (6) [3] 20 | {7,1,0}, // (7) [3] 21 | {8,4,3,2,0}, // (8) [5] 22 | {9,4,0}, // (9) [3] 23 | {10,3,0}, // (10) [3] 24 | {11,2,0}, // (11) [3] 25 | {12,6,4,1,0}, // (12) [5] 26 | {13,4,3,1,0}, // (13) [5] 27 | {14,5,3,1,0}, // (14) [5] 28 | {15,1,0}, // (15) [3] 29 | {16,5,3,2,0}, // (16) [5] 30 | {17,3,0}, // (17) [3] 31 | {18,7,0}, // (18) [3] 32 | {19,5,2,1,0}, // (19) [5] 33 | {20,3,0}, // (20) [3] 34 | {21,2,0}, // (21) [3] 35 | {22,1,0}, // (22) [3] 36 | {23,5,0}, // (23) [3] 37 | {24,4,3,1,0}, // (24) [5] 38 | {25,3,0}, // (25) [3] 39 | {26,6,2,1,0}, // (26) [5] 40 | {27,5,2,1,0}, // (27) [5] 41 | {28,3,0}, // (28) [3] 42 | {29,2,0}, // (29) [3] 43 | {30,6,4,1,0}, // (30) [5] 44 | {31,3,0}, // (31) [3] 45 | {32,7,6,2,0}, // (32) [5] 46 | {33,13,0}, // (33) [3] 47 | {34,8,4,3,0}, // (34) [5] 48 | {35,2,0}, // (35) [3] 49 | {36,11,0}, // (36) [3] 50 | {37,6,4,1,0}, // (37) [5] 51 | {38,6,5,1,0}, // (38) [5] 52 | {39,4,0}, // (39) [3] 53 | {40,5,4,3,0}, // (40) [5] 54 | {41,3,0}, // (41) [3] 55 | {42,7,4,3,0}, // (42) [5] 56 | {43,6,4,3,0}, // (43) [5] 57 | {44,6,5,2,0}, // (44) [5] 58 | {45,4,3,1,0}, // (45) [5] 59 | {46,8,7,6,0}, // (46) [5] 60 | {47,5,0}, // (47) [3] 61 | {48,9,7,4,0}, // (48) [5] 62 | {49,9,0}, // (49) [3] 63 | {50,4,3,2,0}, // (50) [5] 64 | {51,6,3,1,0}, // (51) [5] 65 | {52,3,0}, // (52) [3] 66 | {53,6,2,1,0}, // (53) [5] 67 | {54,8,6,3,0}, // (54) [5] 68 | {55,24,0}, // (55) [3] 69 | {56,7,4,2,0}, // (56) [5] 70 | {57,7,0}, // (57) [3] 71 | {58,19,0}, // (58) [3] 72 | {59,7,4,2,0}, // (59) [5] 73 | {60,1,0}, // (60) [3] 74 | {61,5,2,1,0}, // (61) [5] 75 | {62,6,5,3,0}, // (62) [5] 76 | {63,1,0}, // (63) [3] 77 | {64,4,3,1,0}, // (64) [5] 78 | {65,18,0}, // (65) [3] 79 | {66,9,8,6,0}, // (66) [5] 80 | {67,5,2,1,0}, // (67) [5] 81 | {68,9,0}, // (68) [3] 82 | {69,6,5,2,0}, // (69) [5] 83 | {70,5,3,1,0}, // (70) [5] 84 | {71,6,0}, // (71) [3] 85 | {72,10,9,3,0}, // (72) [5] 86 | {73,25,0}, // (73) [3] 87 | {74,7,4,3,0}, // (74) [5] 88 | {75,6,3,1,0}, // (75) [5] 89 | {76,5,4,2,0}, // (76) [5] 90 | {77,6,5,2,0}, // (77) [5] 91 | {78,7,2,1,0}, // (78) [5] 92 | {79,9,0}, // (79) [3] 93 | {80,9,4,2,0}, // (80) [5] 94 | {81,4,0}, // (81) [3] 95 | {82,9,6,4,0}, // (82) [5] 96 | {83,7,4,2,0}, // (83) [5] 97 | {84,13,0}, // (84) [3] 98 | {85,8,2,1,0}, // (85) [5] 99 | {86,6,5,2,0}, // (86) [5] 100 | {87,13,0}, // (87) [3] 101 | {88,11,9,8,0}, // (88) [5] 102 | {89,38,0}, // (89) [3] 103 | {90,5,3,2,0}, // (90) [5] 104 | {91,8,5,1,0}, // (91) [5] 105 | {92,6,5,2,0}, // (92) [5] 106 | {93,2,0}, // (93) [3] 107 | {94,21,0}, // (94) [3] 108 | {95,11,0}, // (95) [3] 109 | {96,10,9,6,0}, // (96) [5] 110 | {97,6,0}, // (97) [3] 111 | {98,11,0}, // (98) [3] 112 | {99,7,5,4,0}, // (99) [5] 113 | {100,37,0}, // (100) [3] 114 | {101,7,6,1,0}, // (101) [5] 115 | {102,6,5,3,0}, // (102) [5] 116 | {103,9,0}, // (103) [3] 117 | {104,11,10,1,0}, // (104) [5] 118 | {105,16,0}, // (105) [3] 119 | {106,15,0}, // (106) [3] 120 | {107,9,7,4,0}, // (107) [5] 121 | {108,31,0}, // (108) [3] 122 | {109,5,4,2,0}, // (109) [5] 123 | {110,6,4,1,0}, // (110) [5] 124 | {111,10,0}, // (111) [3] 125 | {112,11,6,4,0}, // (112) [5] 126 | {113,9,0}, // (113) [3] 127 | {114,11,2,1,0}, // (114) [5] 128 | {115,8,7,5,0}, // (115) [5] 129 | {116,6,5,2,0}, // (116) [5] 130 | {117,5,2,1,0}, // (117) [5] 131 | {118,33,0}, // (118) [3] 132 | {119,8,0}, // (119) [3] 133 | {120,9,6,2,0}, // (120) [5] 134 | {121,18,0}, // (121) [3] 135 | {122,6,2,1,0}, // (122) [5] 136 | {123,2,0}, // (123) [3] 137 | {124,37,0}, // (124) [3] 138 | {125,7,6,5,0}, // (125) [5] 139 | {126,7,4,2,0}, // (126) [5] 140 | {127,1,0}, // (127) [3] 141 | {128,7,2,1,0}, // (128) [5] 142 | {129,5,0}, // (129) [3] 143 | {130,3,0}, // (130) [3] 144 | {131,8,3,2,0}, // (131) [5] 145 | {132,29,0}, // (132) [3] 146 | {133,9,8,2,0}, // (133) [5] 147 | {134,57,0}, // (134) [3] 148 | {135,11,0}, // (135) [3] 149 | {136,8,3,2,0}, // (136) [5] 150 | {137,21,0}, // (137) [3] 151 | {138,8,7,1,0}, // (138) [5] 152 | {139,8,5,3,0}, // (139) [5] 153 | {140,29,0}, // (140) [3] 154 | {141,13,6,1,0}, // (141) [5] 155 | {142,21,0}, // (142) [3] 156 | {143,5,3,2,0}, // (143) [5] 157 | {144,7,4,2,0}, // (144) [5] 158 | {145,52,0}, // (145) [3] 159 | {146,5,3,2,0}, // (146) [5] 160 | {147,11,4,2,0}, // (147) [5] 161 | {148,27,0}, // (148) [3] 162 | {149,10,9,7,0}, // (149) [5] 163 | {150,53,0}, // (150) [3] 164 | {151,3,0}, // (151) [3] 165 | {152,6,3,2,0}, // (152) [5] 166 | {153,1,0}, // (153) [3] 167 | {154,9,5,1,0}, // (154) [5] 168 | {155,7,5,4,0}, // (155) [5] 169 | {156,9,5,3,0}, // (156) [5] 170 | {157,6,5,2,0}, // (157) [5] 171 | {158,8,6,5,0}, // (158) [5] 172 | {159,31,0}, // (159) [3] 173 | {160,5,3,2,0}, // (160) [5] 174 | {161,18,0}, // (161) [3] 175 | {162,8,7,4,0}, // (162) [5] 176 | {163,7,6,3,0}, // (163) [5] 177 | {164,12,6,5,0}, // (164) [5] 178 | {165,9,8,3,0}, // (165) [5] 179 | {166,10,3,2,0}, // (166) [5] 180 | {167,6,0}, // (167) [3] 181 | {168,16,9,6,0}, // (168) [5] 182 | {169,34,0}, // (169) [3] 183 | {170,23,0}, // (170) [3] 184 | {171,6,5,2,0}, // (171) [5] 185 | {172,7,0}, // (172) [3] 186 | {173,8,5,2,0}, // (173) [5] 187 | {174,13,0}, // (174) [3] 188 | {175,6,0}, // (175) [3] 189 | {176,12,11,9,0}, // (176) [5] 190 | {177,8,0}, // (177) [3] 191 | {178,87,0}, // (178) [3] 192 | {179,4,2,1,0}, // (179) [5] 193 | {180,12,10,7,0}, // (180) [5] 194 | {181,7,6,1,0}, // (181) [5] 195 | {182,8,6,1,0}, // (182) [5] 196 | {183,56,0}, // (183) [3] 197 | {184,9,8,7,0}, // (184) [5] 198 | {185,24,0}, // (185) [3] 199 | {186,9,8,6,0}, // (186) [5] 200 | {187,7,6,5,0}, // (187) [5] 201 | {188,6,5,2,0}, // (188) [5] 202 | {189,6,5,2,0}, // (189) [5] 203 | {190,13,6,2,0}, // (190) [5] 204 | {191,9,0}, // (191) [3] 205 | {192,15,11,5,0}, // (192) [5] 206 | {193,15,0}, // (193) [3] 207 | {194,87,0}, // (194) [3] 208 | {195,8,3,2,0}, // (195) [5] 209 | {196,11,9,2,0}, // (196) [5] 210 | {197,9,4,2,0}, // (197) [5] 211 | {198,65,0}, // (198) [3] 212 | {199,34,0}, // (199) [3] 213 | {200,5,3,2,0}, // (200) [5] 214 | {201,14,0}, // (201) [3] 215 | {202,55,0}, // (202) [3] 216 | {203,8,7,1,0}, // (203) [5] 217 | {204,10,4,3,0}, // (204) [5] 218 | {205,9,5,2,0}, // (205) [5] 219 | {206,10,9,5,0}, // (206) [5] 220 | {207,43,0}, // (207) [3] 221 | {208,9,3,1,0}, // (208) [5] 222 | {209,6,0}, // (209) [3] 223 | {210,12,4,3,0}, // (210) [5] 224 | {211,11,10,8,0}, // (211) [5] 225 | {212,105,0}, // (212) [3] 226 | {213,6,5,2,0}, // (213) [5] 227 | {214,5,3,1,0}, // (214) [5] 228 | {215,23,0}, // (215) [3] 229 | {216,7,3,1,0}, // (216) [5] 230 | {217,45,0}, // (217) [3] 231 | {218,11,0}, // (218) [3] 232 | {219,8,4,1,0}, // (219) [5] 233 | {220,12,10,9,0}, // (220) [5] 234 | {221,8,6,2,0}, // (221) [5] 235 | {222,8,5,2,0}, // (222) [5] 236 | {223,33,0}, // (223) [3] 237 | {224,12,7,2,0}, // (224) [5] 238 | {225,32,0}, // (225) [3] 239 | {226,10,7,3,0}, // (226) [5] 240 | {227,10,9,4,0}, // (227) [5] 241 | {228,12,11,2,0}, // (228) [5] 242 | {229,10,4,1,0}, // (229) [5] 243 | {230,8,7,6,0}, // (230) [5] 244 | {231,26,0}, // (231) [3] 245 | {232,11,9,4,0}, // (232) [5] 246 | {233,74,0}, // (233) [3] 247 | {234,31,0}, // (234) [3] 248 | {235,9,6,1,0}, // (235) [5] 249 | {236,5,0}, // (236) [3] 250 | {237,7,4,1,0}, // (237) [5] 251 | {238,5,2,1,0}, // (238) [5] 252 | {239,36,0}, // (239) [3] 253 | {240,8,5,3,0}, // (240) [5] 254 | {241,70,0}, // (241) [3] 255 | {242,11,6,1,0}, // (242) [5] 256 | {243,8,5,1,0}, // (243) [5] 257 | {244,9,4,1,0}, // (244) [5] 258 | {245,6,4,1,0}, // (245) [5] 259 | {246,11,2,1,0}, // (246) [5] 260 | {247,82,0}, // (247) [3] 261 | {248,15,14,10,0}, // (248) [5] 262 | {249,86,0}, // (249) [3] 263 | {250,103,0}, // (250) [3] 264 | {251,7,4,2,0}, // (251) [5] 265 | {252,67,0}, // (252) [3] 266 | {253,7,3,2,0}, // (253) [5] 267 | {254,7,2,1,0}, // (254) [5] 268 | {255,52,0}, // (255) [3] 269 | {256,10,5,2,0}, // (256) [5] 270 | {257,12,0}, // (257) [3] 271 | {258,83,0}, // (258) [3] 272 | {259,10,6,2,0}, // (259) [5] 273 | {260,10,8,7,0}, // (260) [5] 274 | {261,7,6,4,0}, // (261) [5] 275 | {262,9,8,4,0}, // (262) [5] 276 | {263,93,0}, // (263) [3] 277 | {264,10,9,1,0}, // (264) [5] 278 | {265,42,0}, // (265) [3] 279 | {266,47,0}, // (266) [3] 280 | {267,8,6,3,0}, // (267) [5] 281 | {268,25,0}, // (268) [3] 282 | {269,7,6,1,0}, // (269) [5] 283 | {270,53,0}, // (270) [3] 284 | {271,58,0}, // (271) [3] 285 | {272,9,6,2,0}, // (272) [5] 286 | {273,23,0}, // (273) [3] 287 | {274,67,0}, // (274) [3] 288 | {275,11,10,9,0}, // (275) [5] 289 | {276,6,3,1,0}, // (276) [5] 290 | {277,12,6,3,0}, // (277) [5] 291 | {278,5,0}, // (278) [3] 292 | {279,5,0}, // (279) [3] 293 | {280,9,5,2,0}, // (280) [5] 294 | {281,93,0}, // (281) [3] 295 | {282,35,0}, // (282) [3] 296 | {283,12,7,5,0}, // (283) [5] 297 | {284,119,0}, // (284) [3] 298 | {285,10,7,5,0}, // (285) [5] 299 | {286,69,0}, // (286) [3] 300 | {287,71,0}, // (287) [3] 301 | {288,11,10,1,0}, // (288) [5] 302 | {289,21,0}, // (289) [3] 303 | {290,5,3,2,0}, // (290) [5] 304 | {291,12,11,5,0}, // (291) [5] 305 | {292,97,0}, // (292) [3] 306 | {293,11,6,1,0}, // (293) [5] 307 | {294,61,0}, // (294) [3] 308 | {295,48,0}, // (295) [3] 309 | {296,11,9,4,0}, // (296) [5] 310 | {297,5,0}, // (297) [3] 311 | {298,11,8,4,0}, // (298) [5] 312 | {299,11,6,4,0}, // (299) [5] 313 | {300,7,0}, // (300) [3] 314 | {301,9,5,2,0}, // (301) [5] 315 | {302,41,0}, // (302) [3] 316 | {303,13,12,6,0}, // (303) [5] 317 | {304,11,2,1,0}, // (304) [5] 318 | {305,102,0}, // (305) [3] 319 | {306,7,3,1,0}, // (306) [5] 320 | {307,8,4,2,0}, // (307) [5] 321 | {308,15,9,2,0}, // (308) [5] 322 | {309,10,6,4,0}, // (309) [5] 323 | {310,8,5,1,0}, // (310) [5] 324 | {311,7,5,3,0}, // (311) [5] 325 | {312,11,10,5,0}, // (312) [5] 326 | {313,79,0}, // (313) [3] 327 | {314,15,0}, // (314) [3] 328 | {315,10,9,1,0}, // (315) [5] 329 | {316,135,0}, // (316) [3] 330 | {317,7,4,2,0}, // (317) [5] 331 | {318,8,6,5,0}, // (318) [5] 332 | {319,36,0}, // (319) [3] 333 | {320,4,3,1,0}, // (320) [5] 334 | {321,31,0}, // (321) [3] 335 | {322,67,0}, // (322) [3] 336 | {323,10,3,1,0}, // (323) [5] 337 | {324,6,4,3,0}, // (324) [5] 338 | {325,10,5,2,0}, // (325) [5] 339 | {326,10,3,1,0}, // (326) [5] 340 | {327,34,0}, // (327) [3] 341 | {328,9,7,5,0}, // (328) [5] 342 | {329,50,0}, // (329) [3] 343 | {330,8,7,2,0}, // (330) [5] 344 | {331,10,6,2,0}, // (331) [5] 345 | {332,123,0}, // (332) [3] 346 | {333,2,0}, // (333) [3] 347 | {334,7,4,1,0}, // (334) [5] 348 | {335,10,7,2,0}, // (335) [5] 349 | {336,7,4,1,0}, // (336) [5] 350 | {337,55,0}, // (337) [3] 351 | {338,6,3,2,0}, // (338) [5] 352 | {339,16,10,7,0}, // (339) [5] 353 | {340,11,4,3,0}, // (340) [5] 354 | {341,14,11,5,0}, // (341) [5] 355 | {342,125,0}, // (342) [3] 356 | {343,75,0}, // (343) [3] 357 | {344,11,10,6,0}, // (344) [5] 358 | {345,22,0}, // (345) [3] 359 | {346,11,7,2,0}, // (346) [5] 360 | {347,11,10,3,0}, // (347) [5] 361 | {348,8,7,4,0}, // (348) [5] 362 | {349,6,5,2,0}, // (349) [5] 363 | {350,53,0}, // (350) [3] 364 | {351,34,0}, // (351) [3] 365 | {352,13,11,6,0}, // (352) [5] 366 | {353,69,0}, // (353) [3] 367 | {354,14,13,5,0}, // (354) [5] 368 | {355,6,5,1,0}, // (355) [5] 369 | {356,10,9,7,0}, // (356) [5] 370 | {357,11,10,2,0}, // (357) [5] 371 | {358,14,8,7,0}, // (358) [5] 372 | {359,68,0}, // (359) [3] 373 | {360,26,25,1,0}, // (360) [5] 374 | {361,7,4,1,0}, // (361) [5] 375 | {362,63,0}, // (362) [3] 376 | {363,8,5,3,0}, // (363) [5] 377 | {364,67,0}, // (364) [3] 378 | {365,9,6,5,0}, // (365) [5] 379 | {366,29,0}, // (366) [3] 380 | {367,21,0}, // (367) [3] 381 | {368,17,9,7,0}, // (368) [5] 382 | {369,91,0}, // (369) [3] 383 | {370,139,0}, // (370) [3] 384 | {371,8,3,2,0}, // (371) [5] 385 | {372,15,7,3,0}, // (372) [5] 386 | {373,8,7,2,0}, // (373) [5] 387 | {374,8,6,5,0}, // (374) [5] 388 | {375,16,0}, // (375) [3] 389 | {376,8,7,5,0}, // (376) [5] 390 | {377,41,0}, // (377) [3] 391 | {378,43,0}, // (378) [3] 392 | {379,10,8,5,0}, // (379) [5] 393 | {380,47,0}, // (380) [3] 394 | {381,5,2,1,0}, // (381) [5] 395 | {382,81,0}, // (382) [3] 396 | {383,90,0}, // (383) [3] 397 | {384,16,15,6,0}, // (384) [5] 398 | {385,6,0}, // (385) [3] 399 | {386,83,0}, // (386) [3] 400 | {387,9,8,2,0}, // (387) [5] 401 | {388,14,3,1,0}, // (388) [5] 402 | {389,10,9,5,0}, // (389) [5] 403 | {390,89,0}, // (390) [3] 404 | {391,28,0}, // (391) [3] 405 | {392,13,10,6,0}, // (392) [5] 406 | {393,7,0}, // (393) [3] 407 | {394,135,0}, // (394) [3] 408 | {395,11,6,5,0}, // (395) [5] 409 | {396,25,0}, // (396) [3] 410 | {397,12,7,6,0}, // (397) [5] 411 | {398,14,6,5,0}, // (398) [5] 412 | {399,86,0}, // (399) [3] 413 | {400,5,3,2,0}, // (400) [5] 414 | }; -------------------------------------------------------------------------------- /src/irreducible_poly.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | extern const std::vector> minweight_primpoly_coeffs; 6 | -------------------------------------------------------------------------------- /src/ntt.cpp: -------------------------------------------------------------------------------- 1 | #include "ntt.h" 2 | 3 | #include 4 | #include 5 | 6 | #define P ((3u<<30) + 1) 7 | #define G 5 // primitive root mod P 8 | 9 | uint32_t add(uint32_t a, uint32_t b) { 10 | uint64_t c = a; 11 | c += b; 12 | uint64_t d = c - P; 13 | uint64_t e = d >> 32; 14 | // return d if d >= 0 else c 15 | return (c&e) | (d&~e); 16 | } 17 | 18 | /** 19 | * Subtraction: a-b mod P 20 | * 21 | * @pre a,b < P 22 | */ 23 | static uint32_t sub(uint32_t a, uint32_t b) { 24 | uint64_t c = a; 25 | c -= b; 26 | uint64_t d = c + P; 27 | uint64_t e = c >> 32; 28 | // return c if c >= 0 else d 29 | return (c&~e) | (d&e); 30 | } 31 | 32 | uint32_t mul(uint32_t a, uint32_t b) { 33 | uint64_t n = a; n*= b; 34 | return n % P; 35 | } 36 | 37 | /** 38 | * Modular exponentiation: a^e mod P 39 | */ 40 | static uint32_t modexp(uint32_t a, uint32_t e) { 41 | // e is not secret, no need to make constant time 42 | uint32_t r = 1; 43 | while (e) { 44 | if (e&1) { 45 | r = mul(r, a); 46 | } 47 | e >>= 1; 48 | a = mul(a, a); 49 | } 50 | return r; 51 | } 52 | 53 | /** 54 | * Reverse the bits of x (an l-bit number) 55 | */ 56 | static uint32_t reverse_bits(unsigned l, uint32_t x) { 57 | uint32_t y = 0; 58 | while (l) { 59 | l--; 60 | y |= ((x&1) << l); 61 | x >>= 1; 62 | } 63 | return y; 64 | } 65 | 66 | NTT::NTT(unsigned l) : L(1< 30) { 68 | throw std::runtime_error("Must have 1 <= l <= 30."); 69 | } 70 | 71 | Linv = modexp(L, P-2); 72 | 73 | uint32_t half_L = L/2; 74 | 75 | R = std::vector(half_L); 76 | Rinv = std::vector(half_L); 77 | revbits = std::vector(L); 78 | 79 | uint32_t r = modexp(G, (P - 1) >> l); // primitive L'th root of unity 80 | 81 | { 82 | { 83 | uint64_t t = 1; 84 | for (uint32_t i = 0; i < half_L; i++) { 85 | R[i] = t; 86 | t = mul(t, r); 87 | } 88 | } 89 | 90 | { 91 | // r^(L/2) = -1 92 | uint32_t t = P - 1; 93 | for (uint32_t i = 1; i <= half_L; i++) { 94 | t = mul(t, r); 95 | Rinv[half_L - i] = t; 96 | } 97 | } 98 | } 99 | 100 | for (uint32_t i = 0; i < L; i++) { 101 | revbits[i] = reverse_bits(l, i); 102 | } 103 | 104 | } 105 | 106 | std::vector NTT::ntt(const std::vector &x, bool inverse) { 107 | const std::vector& U = inverse ? Rinv : R; 108 | 109 | std::vector y(L, 0); 110 | 111 | // Bit inversion 112 | for (uint32_t i = 0; i < L; i++) { 113 | y[revbits[i]] = x[i]; 114 | } 115 | 116 | // Main loop 117 | for ( 118 | uint32_t h = 2, k = 1, u = L/2; 119 | h <= L; 120 | k = h, h <<= 1, u >>= 1) 121 | { 122 | for (uint32_t i = 0; i < L; i += h) { 123 | for (uint32_t j = 0, v = 0; j < k; j++, v += u) { 124 | uint32_t r = i + j; 125 | uint32_t s = r + k; 126 | uint32_t a = y[r]; 127 | uint32_t b = mul(y[s], U[v]); 128 | y[r] = add(a, b); 129 | y[s] = sub(a, b); 130 | } 131 | } 132 | } 133 | 134 | // Normalization for inverse 135 | if (inverse) { 136 | for (uint32_t i = 0; i < L; i++) { 137 | y[i] = mul(Linv, y[i]); 138 | } 139 | } 140 | return y; 141 | } 142 | 143 | std::vector NTT::mul_vec(const std::vector &a, const std::vector &b) { 144 | std::vector c(a.size()); 145 | for (uint32_t i = 0; i < a.size(); i++) { 146 | c[i] = mul(a[i], b[i]); 147 | } 148 | return c; 149 | } 150 | 151 | std::vector NTT::conv(const std::vector &a, const std::vector &b) { 152 | std::vector c = mul_vec(ntt(a, false), ntt(b, false)); 153 | return ntt(c, true); 154 | } -------------------------------------------------------------------------------- /src/ntt.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class NTT { 7 | private: 8 | /** Sequence length (power of 2) */ 9 | uint32_t L; 10 | 11 | /** Inverse of L mod p */ 12 | uint32_t Linv; 13 | 14 | /** 15 | * Powers 1, r, r^2, ..., r^(L/2-1) mod p, where r is a primitive L'th 16 | * root of unity mod p 17 | */ 18 | std::vector R; 19 | 20 | /** 21 | * Inverse powers 1, r^{-1}, r^{-2}, ..., r{-^(L/2-1)} mod p 22 | */ 23 | std::vector Rinv; 24 | 25 | /** 26 | * Lookup table for bit reversals 27 | */ 28 | std::vector revbits; 29 | 30 | public: 31 | explicit NTT(unsigned l); 32 | 33 | std::vector ntt(const std::vector &x, bool inverse); 34 | 35 | std::vector mul_vec(const std::vector &a, const std::vector &b); 36 | std::vector conv(const std::vector &a, const std::vector &b); 37 | }; 38 | -------------------------------------------------------------------------------- /src/trevisan.cpp: -------------------------------------------------------------------------------- 1 | #include "irreducible_poly.h" 2 | #include "trevisan.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace std; 11 | 12 | GF2Poly::GF2Poly(int log_t) : log_t(log_t) { 13 | if (log_t <= 0 || log_t >= 256 ) { 14 | cout << "log_t " << log_t << endl; 15 | throw runtime_error("log_t should be in the range (0, 256)."); 16 | } 17 | 18 | // select irred_poly 19 | irred_poly = poly_bits(); 20 | vector coeffs = minweight_primpoly_coeffs[log_t]; 21 | for (int coeff : coeffs) { 22 | irred_poly[coeff] = 1; 23 | } 24 | 25 | mask = 1ULL << log_t; 26 | } 27 | 28 | poly_bits GF2Poly::poly_mul(poly_bits x, poly_bits y) const { 29 | poly_bits result = poly_bits(); 30 | for (size_t i = 0; i < 256; i++) { 31 | if (y[i]) { 32 | result ^= x; 33 | } 34 | x <<= 1; 35 | if (x[log_t]) { 36 | x ^= irred_poly; 37 | } 38 | } 39 | 40 | return result; 41 | } 42 | 43 | poly_bits GF2Poly::poly_add(poly_bits x, poly_bits y) { 44 | return x ^ y; 45 | } 46 | 47 | // Evaluates the polynomial at `x` in a linear number of operations 48 | poly_bits GF2Poly::horner_method(const vector &coeffs, poly_bits x) const { 49 | poly_bits res = poly_bits(); 50 | size_t n_coeffs = coeffs.size(); 51 | for (size_t i = 0; i < n_coeffs; i++) { 52 | poly_bits coeff = coeffs[n_coeffs - i - 1]; 53 | res = poly_mul(res, x); 54 | res = poly_add(res, coeff); 55 | } 56 | 57 | return res; 58 | } 59 | 60 | 61 | HRWeakDesign::HRWeakDesign(int m, int log_t) : log_t(log_t), field(log_t), m(m) { 62 | int log_m = 0; 63 | while ((1 << log_m) < m) log_m += 1; 64 | mask = (1 << log_t) - 1; 65 | 66 | t = 1 << log_t; 67 | d = t * t; 68 | 69 | c = (log_m + log_t - 1) / log_t; 70 | } 71 | 72 | // Returns the ith subset of the HR weak design 73 | vector HRWeakDesign::get_s(int i) { 74 | if (i < 0 || i >= m) { 75 | cerr << "Index: " << i << ", HR weak design size: " << m << endl; 76 | throw runtime_error("Index out of bounds for HR weak design."); 77 | } 78 | 79 | auto alphas = vector(c); 80 | for (int j = 0; j < c; j++) { 81 | alphas[j] = poly_bits((i >> j*log_t) & mask); 82 | } 83 | auto s = vector(t); 84 | for (int a = 0; a < t; a++) { 85 | uint64_t b = field.horner_method(alphas, a).to_ullong(); 86 | uint64_t pair = (a << log_t) + b; 87 | s[a] = pair; 88 | } 89 | 90 | return s; 91 | } 92 | 93 | BlockWeakDesign::BlockWeakDesign(int m, int log_t) 94 | : base(max((int) ceil((double) m / HRWeakDesign::r - 1), 1 << log_t), log_t), m(m) { 95 | double base_r = HRWeakDesign::r; 96 | t = 1ULL << log_t; 97 | l = max(1, (int)ceil( (log((double) m-base_r) - log((double) t-base_r)) / (log(base_r) - log(base_r - 1)) )); 98 | 99 | // compute size of subweak designs 100 | auto ns = vector(l); 101 | auto sum_ns = vector(l); 102 | double acc = 0; 103 | for (int i = 0; i < l; i++) { 104 | ns[i] = pow(1 - 1/base_r, i) * ((double) m/base_r - 1); 105 | acc += ns[i]; 106 | sum_ns[i] = ceil(acc); 107 | } 108 | 109 | // sum_ms[i] stores sum of ms from 0 to i -> sum_ms[0] = 0 110 | sum_ms.push_back(0); 111 | for (int i = 0; i < l; i++) { 112 | ms.push_back(sum_ns[i] - sum_ms.back()); 113 | sum_ms.push_back(ms.back() + sum_ms.back()); 114 | } 115 | ms.push_back(m - sum_ms.back()); 116 | sum_ms.push_back(ms.back() + sum_ms.back()); 117 | 118 | d = (l+1) * t * t; 119 | 120 | // base = HRWeakDesign(max(ms[0], t), log_t); 121 | } 122 | 123 | // Returns the ith subset of the block weak design. 124 | vector BlockWeakDesign::get_s(int i) { 125 | if (i < 0 || i >= m) { 126 | cerr << "Index: " << i << ", block weak design size: " << m << endl; 127 | throw runtime_error("Index out of bounds for block weak design."); 128 | } 129 | 130 | // find correct index using binary search 131 | int ind = 0; 132 | int step = 1 << 30; 133 | while (step > 0) { 134 | // invariant: sum_ms[ind] <= i 135 | if (ind + step <= l and sum_ms[ind + step] <= i) { 136 | ind += step; 137 | } 138 | step >>= 1; 139 | } 140 | 141 | int base_i = i - sum_ms[ind]; 142 | int base_inc = ind * t * t; 143 | vector base_s = base.get_s(base_i); 144 | auto s = vector(); 145 | for (uint64_t elem : base_s) { 146 | s.push_back(elem + base_inc); 147 | } 148 | 149 | return s; 150 | } 151 | 152 | RSHExtractor::RSHExtractor(int n, int l) : field(l), n(n), l(l) { 153 | s = (n + l - 1) / l; 154 | } 155 | 156 | poly_bits RSHExtractor::reed_solomon_step(const vector &r_input, const vector &alpha_bits) { 157 | // reverse bits to do conversion using big-endian convention 158 | // Example: 0101 -> 5 instead of 0101 -> 10 159 | // the coefficients of RS are reversed 160 | auto coeffs = vector(s); 161 | for (size_t i = 0; i < s; i++) { 162 | for (size_t j = 0; j < l; j++) { 163 | coeffs[s - i - 1][j] = r_input[i*l + j]; 164 | } 165 | } 166 | 167 | auto alpha = poly_bits(); 168 | for (size_t i = 0; i < alpha_bits.size(); i++) { 169 | alpha[i] = alpha_bits[i]; 170 | } 171 | poly_bits r = field.horner_method(coeffs, alpha); 172 | return r; 173 | } 174 | 175 | bool RSHExtractor::hadamard_step(poly_bits r_bits, vector beta) { 176 | bool b = 0; 177 | for (size_t i = 0; i < r_bits.size(); i++) { 178 | bool r_bit = r_bits[i], beta_bit = beta[i]; 179 | b ^= r_bit & beta_bit; 180 | } 181 | return b; 182 | } 183 | 184 | bool RSHExtractor::extract(vector &r_input, const vector &r_seed) { 185 | if (r_input.size() != n && r_input.size() != s*l) { 186 | cerr << "Actual: " << r_input.size() << " Expected: " << n << endl; 187 | throw runtime_error("Input length doesn't match extractor parameters"); 188 | } 189 | if (r_seed.size() != 2*l) { 190 | cerr << "Actual: " << r_seed.size() << " Expected: " << 2*l << endl; 191 | throw runtime_error("Seed length doesn't match extractor parameters"); 192 | } 193 | 194 | // split seed into two halves, alpha and beta 195 | auto alpha_bits = vector(r_seed.begin(), r_seed.begin() + l); 196 | auto beta_bits = vector(r_seed.begin() + l, r_seed.end()); 197 | 198 | // right pad input bits with zeros 199 | while (r_input.size() < s*l) { 200 | r_input.push_back(0); 201 | } 202 | poly_bits r = reed_solomon_step(r_input, alpha_bits); 203 | 204 | // extract the bits of r 205 | bool b = hadamard_step(r, beta_bits); 206 | return b; 207 | } 208 | 209 | TrevisanConfig::TrevisanConfig(int n, int k, double max_eps) : n(n) { 210 | double r = BlockWeakDesign::r; 211 | // choose largest m s.t. m*eps <= max_eps 212 | m = 0; 213 | int step = 1 << 30; 214 | double log_max_eps = log2(max_eps); 215 | while (step > 0) { 216 | // eps = 2 ** (((m+step)*r - k + 6) / 4) 217 | double try_log_eps = ((double) (m+step)*r - k + 6) / 4; 218 | if ((log2(m+step) + try_log_eps) <= log_max_eps) { 219 | m += step; 220 | log_eps = try_log_eps; 221 | } 222 | step >>= 1; 223 | } 224 | 225 | // l = ceil(log(n) + 2 * log(2 / eps)) 226 | l = (int) ceil(log2(n) + 2 * (1 - log_eps)); 227 | int t_req = 2*l; 228 | // make t a power of 2 for weak design 229 | log_t = (int) ceil(log2(t_req)); 230 | t = 1 << log_t; 231 | } 232 | 233 | Trevisan::Trevisan(TrevisanConfig config) 234 | : wd(config.m, config.log_t), ext(config.n, config.l), 235 | n(config.n), m(config.m), l(config.l) {} 236 | 237 | int Trevisan::get_seed_length() const { 238 | return wd.d; 239 | } 240 | 241 | void Trevisan::load_source(const vector &source_inp, const vector &source_seed) { 242 | if (source_inp.size() != n) { 243 | cerr << "Actual: " << source_inp.size() << " Expected: " << n << endl; 244 | throw runtime_error("Input length doesn't match extractor parameters"); 245 | } 246 | if (source_seed.size() != get_seed_length()){ 247 | cerr << "Actual: " << source_seed.size() << " Expected: " << get_seed_length() << endl; 248 | throw runtime_error("Seed length doesn't match extractor parameters"); 249 | } 250 | source_loaded = true; 251 | this->source_inp = source_inp; 252 | this->source_seed = source_seed; 253 | } 254 | 255 | vector Trevisan::extract() { 256 | auto bits = vector(m); 257 | for (int i = 0; i < m; i++) { 258 | bits[i] = extract_bit(i); 259 | cout << bits[i]; 260 | } 261 | cout << endl; 262 | 263 | return bits; 264 | } 265 | 266 | bool Trevisan::extract_bit(int i) { 267 | if (!source_loaded) { 268 | throw runtime_error("Load source with load_source(input, seed)."); 269 | } 270 | 271 | auto design_set = wd.get_s(i); 272 | auto selected_bits = vector(2*l); 273 | for (int j = 0; j < 2*l; j++) { 274 | selected_bits[j] = source_seed[design_set[j]]; 275 | } 276 | 277 | bool bit = ext.extract(source_inp, selected_bits); 278 | return bit; 279 | } 280 | -------------------------------------------------------------------------------- /src/trevisan.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | typedef std::bitset<256> poly_bits; 8 | 9 | // Implements ring operations in the Galois field (GF(t)) 10 | class GF2Poly { 11 | public: 12 | // The order of the Galois field `t` must be a power of two. log(t) can 13 | // be up to 63. 14 | int log_t; 15 | poly_bits data, mask, irred_poly; 16 | 17 | explicit GF2Poly(int log_t); 18 | 19 | poly_bits poly_mul(poly_bits x, poly_bits y) const; 20 | static poly_bits poly_add(poly_bits x, poly_bits y); 21 | 22 | // Evaluates the polynomial at `x` in a linear number of operations 23 | poly_bits horner_method(const std::vector &coeffs, poly_bits x) const; 24 | }; 25 | 26 | // Implements the weak design by Hartman and Raz. 27 | class HRWeakDesign { 28 | private: 29 | int c, log_t, mask; 30 | GF2Poly field; 31 | 32 | public: 33 | static constexpr double r = 5.43656365691809; // 2 * e 34 | int m, t, d; 35 | 36 | HRWeakDesign(int m, int log_t); 37 | 38 | // Returns the ith subset of the HR weak design 39 | std::vector get_s(int i); 40 | }; 41 | 42 | // Implements the block weak design from Maurer et al. 43 | class BlockWeakDesign { 44 | private: 45 | std::vector ms, sum_ms; 46 | int l; 47 | HRWeakDesign base; 48 | 49 | public: 50 | static constexpr double r = 1.0; 51 | int m, d, t; 52 | 53 | BlockWeakDesign(int m, int log_t); 54 | 55 | // Returns the ith subset of the block weak design. 56 | std::vector get_s(int i); 57 | }; 58 | 59 | // Implements the Reed-Solomon-Hadamard extractor. 60 | class RSHExtractor { 61 | private: 62 | GF2Poly field; 63 | public: 64 | int n, l, s; 65 | 66 | RSHExtractor(int n, int l); 67 | 68 | poly_bits reed_solomon_step(const std::vector &r_input, const std::vector &alpha_bits); 69 | 70 | static bool hadamard_step(poly_bits r_bits, std::vector beta); 71 | 72 | bool extract(std::vector &r_input, const std::vector &r_seed); 73 | }; 74 | 75 | class TrevisanConfig { 76 | public: 77 | int m, log_t, l; 78 | int n, t; 79 | double log_eps; 80 | 81 | TrevisanConfig(int n, int k, double max_eps); 82 | }; 83 | 84 | class Trevisan { 85 | private: 86 | BlockWeakDesign wd; 87 | RSHExtractor ext; 88 | std::vector source_inp; 89 | std::vector source_seed; 90 | public: 91 | int n, m, l; 92 | bool source_loaded = false; 93 | 94 | explicit Trevisan(TrevisanConfig config); 95 | 96 | int get_seed_length() const; 97 | 98 | void load_source(const std::vector &source_inp, const std::vector &source_seed); 99 | 100 | std::vector extract(); 101 | bool extract_bit(int i); 102 | }; 103 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.9) 2 | set (CMAKE_CXX_STANDARD 17) 3 | project(trevisan-extractor) 4 | 5 | # Fetch GTest 6 | include(FetchContent) 7 | FetchContent_Declare( 8 | googletest 9 | URL https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip 10 | ) 11 | 12 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 13 | FetchContent_MakeAvailable(googletest) 14 | 15 | # Link runTests with what we want to test and the GTest and pthread library 16 | add_executable(runTests tests.cpp) 17 | target_link_libraries(runTests trevisan gtest_main) 18 | 19 | # Add to CTest 20 | add_test(NAME runTests COMMAND $ --gtest_output=xml:${CMAKE_CURRENT_BINARY_DIR}/) 21 | -------------------------------------------------------------------------------- /test/requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | -------------------------------------------------------------------------------- /test/test_circulant.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from cryptomite.circulant import Circulant 3 | 4 | 5 | circulant_testcases = [ 6 | (2, 1, [0, 1], [1, 1, 1], [1]), 7 | (2, 2, [1, 0], [1, 1, 0], [1, 1]), 8 | (2, 2, [0, 1], [0, 0, 0], [0, 0]), 9 | (5, 5, [1, 0, 1, 0, 0], [1, 1, 1, 0, 1, 0], [0, 1, 0, 0, 0]), 10 | ( 11 | 8, 12 | 8, 13 | [0, 0, 1, 1, 0, 0, 0, 1], 14 | [1, 1, 1, 0, 1, 1, 1, 1, 0], 15 | [0, 1, 1, 1, 1, 1, 0, 1], 16 | ), 17 | ] 18 | 19 | 20 | @pytest.mark.parametrize("n,m,x,y,z", circulant_testcases) 21 | def test_circulant(n, m, x, y, z): 22 | assert Circulant(n, m).extract(x, y) == z 23 | -------------------------------------------------------------------------------- /test/test_dodis.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from cryptomite.dodis import Dodis 3 | 4 | 5 | dodis_testcases = [ 6 | (3, 1, [0, 1, 0], [1, 1, 1], [1]), 7 | (3, 2, [1, 0, 0], [1, 1, 0], [1, 1]), 8 | (3, 3, [0, 1, 1], [0, 0, 0], [0, 0, 0]), 9 | (6, 4, [0, 1, 0, 0, 0, 1], [0, 0, 0, 1, 1, 1], [1, 0, 1, 1]), 10 | (6, 5, [1, 0, 0, 0, 0, 1], [1, 0, 1, 1, 1, 0], [1, 1, 1, 0, 0]), 11 | (6, 6, [1, 0, 1, 0, 0, 0], [1, 1, 1, 0, 1, 0], [0, 1, 0, 0, 0, 1]), 12 | ( 13 | 9, 14 | 7, 15 | [0, 1, 1, 1, 0, 1, 1, 1, 1], 16 | [1, 1, 1, 0, 1, 1, 0, 0, 1], 17 | [0, 0, 1, 0, 0, 0, 1], 18 | ), 19 | ( 20 | 9, 21 | 8, 22 | [0, 1, 1, 0, 1, 0, 1, 1, 1], 23 | [0, 1, 1, 0, 1, 0, 0, 1, 0], 24 | [0, 0, 0, 0, 0, 1, 1, 0], 25 | ), 26 | ( 27 | 9, 28 | 9, 29 | [0, 0, 1, 1, 0, 0, 0, 1, 0], 30 | [1, 1, 1, 0, 1, 1, 1, 1, 0], 31 | [0, 1, 1, 1, 1, 1, 0, 1, 1], 32 | ), 33 | ] 34 | 35 | 36 | @pytest.mark.parametrize("n,m,x,y,z", dodis_testcases) 37 | def test_dodis(n, m, x, y, z): 38 | assert Dodis(n, m).extract(x, y) == z 39 | -------------------------------------------------------------------------------- /test/test_ntt.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from cryptomite._cryptomite import BigNTT, NTT 3 | import numpy as np 4 | 5 | test_range = list(range(2, 21)) 6 | 7 | 8 | def slow_conv(a, b): 9 | """ direct implementation """ 10 | c = [0] * len(a) 11 | for i in range(len(a)): 12 | for j in range(len(b)): 13 | c[(i + j) % len(c)] += a[i] * b[j] 14 | return c 15 | 16 | 17 | @pytest.mark.parametrize('n', test_range) 18 | def test_ntt_inv(n): 19 | ntt = NTT(n) 20 | for _ in range(10): 21 | v = np.random.randint(0, 1 << n, 1 << n).tolist() 22 | assert ntt.ntt(ntt.ntt(v, False), True) == v 23 | 24 | 25 | @pytest.mark.parametrize('n', test_range) 26 | def test_big_ntt_inv(n): 27 | ntt = BigNTT(n) 28 | for _ in range(10): 29 | v = np.random.randint(0, 1 << n, 1 << n).tolist() 30 | assert ntt.ntt(ntt.ntt(v, False), True) == v 31 | 32 | 33 | @pytest.mark.parametrize('n', list(range(2, 11))) 34 | def test_ntt_conv(n): 35 | ntt = NTT(n) 36 | a = np.random.randint(0, 2, 1 << n).tolist() 37 | b = np.random.randint(0, 2, 1 << n).tolist() 38 | assert ntt.conv(a, b) == slow_conv(a, b) 39 | 40 | 41 | @pytest.mark.parametrize('n', test_range) 42 | def test_big_ntt_conv(n): 43 | ntt = NTT(n) 44 | big_ntt = BigNTT(n) 45 | a = np.random.randint(0, 2, 1 << n).tolist() 46 | b = np.random.randint(0, 2, 1 << n).tolist() 47 | assert ntt.conv(a, b) == big_ntt.conv(a, b) 48 | -------------------------------------------------------------------------------- /test/test_toeplitz.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from cryptomite.toeplitz import Toeplitz 3 | 4 | 5 | toeplitz_testcases = [ 6 | (3, 1, [1, 0, 0], [0, 0, 0], [0]), 7 | (3, 2, [1, 0, 0], [1, 1, 1, 1], [1, 1]), 8 | (3, 3, [0, 0, 0], [1, 1, 0, 0, 0], [0, 0, 0]), 9 | (6, 4, [1, 1, 0, 0, 0, 1], [1, 1, 0, 1, 0, 1, 0, 0, 1], [0, 1, 1, 1]), 10 | (6, 5, 11 | [1, 1, 0, 0, 0, 1], 12 | [0, 0, 1, 1, 1, 1, 0, 1, 0, 1], 13 | [0, 0, 0, 0, 1]), 14 | (6, 6, 15 | [0, 0, 1, 1, 0, 0], 16 | [0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1], 17 | [1, 1, 1, 0, 1, 0]), 18 | (9, 7, 19 | [1, 1, 1, 1, 1, 1, 0, 1, 1], 20 | [0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1], 21 | [0, 0, 0, 0, 1, 1, 1]), 22 | (9, 8, 23 | [1, 0, 0, 1, 1, 1, 1, 0, 0], 24 | [1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1], 25 | [1, 1, 1, 1, 1, 0, 1, 0]), 26 | (9, 9, 27 | [0, 1, 0, 0, 1, 1, 1, 1, 1], 28 | [0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1], 29 | [0, 0, 1, 0, 0, 1, 0, 1, 1]) 30 | ] 31 | 32 | 33 | 34 | @pytest.mark.parametrize("n,m,x,y,z", toeplitz_testcases) 35 | def test_toeplitz(n, m, x, y, z): 36 | assert Toeplitz(n, m).extract(x, y) == z 37 | -------------------------------------------------------------------------------- /test/test_vn.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from cryptomite.utils import von_neumann 3 | 4 | vn_testcases = [ 5 | ([], []), 6 | ([0, 1, 0, 1], [0, 0]), 7 | ([0, 0, 0, 0], []), 8 | ([0, 0, 1, 1], []), 9 | ([0, 0, 0, 1, 1, 0, 1, 1, 1], [0, 1]), 10 | ([1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0], 11 | [1, 1, 1, 0, 1]) 12 | ] 13 | 14 | 15 | @pytest.mark.parametrize("bits, expect", vn_testcases) 16 | def test_vn(bits, expect): 17 | assert von_neumann(bits) == expect 18 | -------------------------------------------------------------------------------- /test/tests.cpp: -------------------------------------------------------------------------------- 1 | // tests.cpp 2 | #include 3 | #include 4 | 5 | TEST(GF2PolyTest, Example) { 6 | GF2Poly poly(4); 7 | poly_bits x(0b1101); 8 | poly_bits y(0b0101); 9 | 10 | poly_bits xy(0b1100); 11 | 12 | ASSERT_EQ(xy, poly.poly_mul(x, y)); 13 | } 14 | 15 | typedef tuple> HRExample; 16 | 17 | static const vector hr_examples = { 18 | {121, 2, 7, {3, 6, 9, 12}}, 19 | {42, 2, 40, {0, 4, 10, 14}}, 20 | {181, 2, 48, {0, 7, 10, 13}}, 21 | {141, 2, 6, {2, 7, 8, 13}}, 22 | {86, 2, 42, {2, 6, 8, 12}}, 23 | {1585451, 3, 1111387, {3, 13, 16, 30, 33, 43, 53, 63}}, 24 | {14429011, 3, 10924101, {5, 14, 22, 24, 35, 42, 49, 56}}, 25 | {9412952, 3, 536846, {6, 10, 18, 26, 36, 43, 50, 57}}, 26 | {1020535, 3, 957551, {7, 10, 23, 28, 36, 42, 52, 60}}, 27 | {4344458, 3, 3265585, {1, 14, 18, 31, 33, 44, 54, 56}}, 28 | }; 29 | 30 | class HrTest : 31 | public testing::TestWithParam { 32 | }; 33 | 34 | typedef tuple> BWDExample; 35 | 36 | static const vector bwd_examples = { 37 | {50, 3, 7, {7, 15, 23, 31, 39, 47, 55, 63}}, 38 | {50, 3, 32, {384, 392, 400, 408, 416, 424, 432, 440}}, 39 | {50, 3, 4, {4, 12, 20, 28, 36, 44, 52, 60}}, 40 | {50, 3, 20, {133, 141, 149, 157, 165, 173, 181, 189}}, 41 | {50, 3, 9, {64, 72, 80, 88, 96, 104, 112, 120}}, 42 | {500, 4, 16, {0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255}}, 43 | {500, 4, 237, {779, 795, 811, 827, 843, 859, 875, 891, 907, 923, 939, 955, 971, 987, 1003, 1019}}, 44 | {500, 4, 56, {8, 27, 46, 61, 68, 87, 98, 113, 131, 144, 165, 182, 207, 220, 233, 250}}, 45 | {500, 4, 197, {527, 542, 557, 572, 587, 602, 617, 632, 647, 662, 677, 692, 707, 722, 737, 752}}, 46 | {500, 4, 356, {1543, 1559, 1575, 1591, 1607, 1623, 1639, 1655, 1671, 1687, 1703, 1719, 1735, 1751, 1767, 1783}}, 47 | {5000, 5, 4646, {13318, 13350, 13382, 13414, 13446, 13478, 13510, 13542, 13574, 13606, 13638, 13670, 13702, 13734, 13766, 13798, 13830, 13862, 13894, 13926, 13958, 13990, 14022, 14054, 14086, 14118, 14150, 14182, 14214, 14246, 14278, 14310}}, 48 | {5000, 5, 2748, {3091, 3133, 3151, 3169, 3214, 3232, 3282, 3324, 3340, 3362, 3408, 3454, 3473, 3519, 3533, 3555, 3592, 3622, 3668, 3706, 3733, 3771, 3785, 3815, 3863, 3897, 3915, 3941, 3978, 4004, 4054, 4088}}, 49 | {5000, 5, 1787, {2070, 2101, 2128, 2163, 2202, 2233, 2268, 2303, 2318, 2349, 2376, 2411, 2434, 2465, 2500, 2535, 2563, 2592, 2629, 2662, 2703, 2732, 2761, 2794, 2843, 2872, 2909, 2942, 2967, 2996, 3025, 3058}}, 50 | {5000, 5, 1807, {2058, 2094, 2114, 2150, 2202, 2238, 2258, 2294, 2319, 2347, 2375, 2403, 2463, 2491, 2519, 2547, 2560, 2596, 2632, 2668, 2704, 2740, 2776, 2812, 2821, 2849, 2893, 2921, 2965, 2993, 3037, 3065}}, 51 | {5000, 5, 1673, {2052, 2084, 2116, 2148, 2180, 2212, 2244, 2276, 2308, 2340, 2372, 2404, 2436, 2468, 2500, 2532, 2564, 2596, 2628, 2660, 2692, 2724, 2756, 2788, 2820, 2852, 2884, 2916, 2948, 2980, 3012, 3044}}, 52 | {50000, 6, 14285, {4129, 4207, 4283, 4341, 4365, 4419, 4503, 4569, 4633, 4695, 4739, 4813, 4917, 4987, 5039, 5089, 5144, 5206, 5250, 5324, 5428, 5498, 5550, 5600, 5664, 5742, 5818, 5876, 5900, 5954, 6038, 6104, 6200, 6262, 6306, 6380, 6420, 6490, 6542, 6592, 6656, 6734, 6810, 6868, 6956, 7010, 7094, 7160, 7169, 7247, 7323, 7381, 7469, 7523, 7607, 7673, 7737, 7799, 7843, 7917, 7957, 8027, 8079, 8129}}, 53 | {50000, 6, 20916, {8247, 8311, 8369, 8433, 8483, 8547, 8613, 8677, 8764, 8828, 8890, 8954, 9000, 9064, 9134, 9198, 9259, 9323, 9389, 9453, 9535, 9599, 9657, 9721, 9760, 9824, 9894, 9958, 10036, 10100, 10162, 10226, 10279, 10343, 10401, 10465, 10547, 10611, 10677, 10741, 10796, 10860, 10922, 10986, 11064, 11128, 11198, 11262, 11323, 11387, 11453, 11517, 11567, 11631, 11689, 11753, 11824, 11888, 11958, 12022, 12068, 12132, 12194, 12258}}, 54 | {50000, 6, 13820, {4112, 4185, 4228, 4301, 4384, 4457, 4532, 4605, 4624, 4697, 4740, 4813, 4896, 4969, 5044, 5117, 5146, 5203, 5262, 5319, 5418, 5475, 5566, 5623, 5658, 5715, 5774, 5831, 5930, 5987, 6078, 6135, 6188, 6245, 6328, 6385, 6428, 6485, 6536, 6593, 6700, 6757, 6840, 6897, 6940, 6997, 7048, 7105, 7206, 7279, 7346, 7419, 7446, 7519, 7554, 7627, 7718, 7791, 7858, 7931, 7958, 8031, 8066, 8139}}, 55 | {50000, 6, 26657, {12344, 12355, 12429, 12534, 12561, 12650, 12708, 12767, 12841, 12882, 12956, 13031, 13056, 13179, 13237, 13262, 13338, 13409, 13487, 13524, 13619, 13640, 13702, 13821, 13835, 13936, 14014, 14021, 14114, 14169, 14231, 14316, 14399, 14404, 14474, 14577, 14614, 14701, 14755, 14808, 14894, 14933, 15003, 15072, 15111, 15228, 15282, 15305, 15389, 15462, 15528, 15571, 15668, 15695, 15745, 15866, 15884, 15991, 16057, 16066, 16165, 16222, 16272, 16363}}, 56 | {50000, 6, 47551, {57356, 57419, 57474, 57541, 57616, 57687, 57758, 57817, 57908, 57971, 58042, 58109, 58152, 58223, 58278, 58337, 58431, 58488, 58545, 58614, 58659, 58724, 58797, 58858, 58887, 58944, 59017, 59086, 59163, 59228, 59285, 59346, 59433, 59502, 59559, 59616, 59701, 59762, 59835, 59900, 59921, 59990, 60063, 60120, 60173, 60234, 60291, 60356, 60442, 60509, 60564, 60627, 60678, 60737, 60808, 60879, 60962, 61029, 61100, 61163, 61246, 61305, 61360, 61431}} 57 | }; 58 | 59 | class BwdTest : 60 | public testing::TestWithParam { 61 | }; 62 | 63 | typedef tuple, vector, bool> RSHExample; 64 | 65 | static const vector rsh_examples = { 66 | {20, 5, {0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1}, {0, 1, 1, 0, 1, 1, 1, 0, 0, 0}, 1}, 67 | {20, 5, {0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1}, {0, 1, 0, 0, 0, 1, 0, 0, 1, 1}, 1}, 68 | {20, 5, {0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0}, {1, 1, 0, 1, 0, 1, 0, 1, 0, 0}, 0}, 69 | {20, 5, {1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0}, {1, 1, 1, 0, 0, 1, 1, 0, 0, 1}, 1}, 70 | {20, 5, {1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0}, {1, 1, 0, 1, 1, 1, 0, 0, 1, 0}, 0}, 71 | {20, 5, {0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1}, {0, 1, 1, 1, 0, 1, 1, 0, 0, 0}, 0}, 72 | {20, 5, {0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0}, {0, 0, 1, 1, 1, 1, 1, 1, 0, 0}, 1}, 73 | {20, 5, {1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1}, {1, 0, 1, 0, 0, 1, 0, 0, 0, 0}, 0}, 74 | {20, 5, {1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1}, {1, 1, 0, 1, 1, 1, 0, 0, 0, 1}, 1}, 75 | {20, 5, {0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0}, {0, 0, 0, 0, 1, 1, 0, 1, 0, 0}, 1}, 76 | {20, 5, {1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1}, {0, 1, 0, 1, 1, 0, 1, 1, 0, 0}, 0}, 77 | {20, 5, {1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1}, {0, 0, 1, 1, 0, 0, 1, 0, 1, 0}, 0}, 78 | {20, 5, {0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1}, {0, 0, 0, 0, 1, 1, 0, 1, 1, 0}, 0}, 79 | {20, 5, {0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1}, {0, 1, 1, 0, 1, 0, 0, 1, 1, 1}, 1}, 80 | {20, 5, {1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1}, {1, 0, 1, 0, 0, 1, 1, 1, 1, 1}, 1}, 81 | {20, 5, {1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1}, {0, 1, 0, 1, 0, 1, 0, 0, 0, 1}, 1}, 82 | {20, 5, {0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0}, {1, 0, 0, 0, 0, 1, 1, 0, 1, 1}, 1}, 83 | {20, 5, {1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1}, {0, 0, 0, 1, 0, 1, 0, 0, 1, 1}, 1}, 84 | {20, 5, {1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1}, {0, 0, 1, 1, 0, 0, 0, 1, 0, 1}, 0}, 85 | {20, 5, {0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1}, {0, 1, 0, 0, 1, 0, 1, 0, 0, 0}, 1}, 86 | {23, 3, {1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0}, {0, 1, 1, 0, 1, 1}, 1}, 87 | {23, 3, {1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0}, {0, 1, 1, 0, 1, 0}, 1}, 88 | {23, 3, {1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0}, {1, 0, 1, 0, 0, 1}, 0}, 89 | {23, 3, {1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1}, {1, 1, 0, 1, 0, 0}, 1}, 90 | {23, 3, {0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0}, {0, 0, 1, 0, 1, 1}, 0}, 91 | {23, 3, {1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1}, {0, 0, 1, 1, 1, 0}, 1}, 92 | {23, 3, {0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1}, {0, 1, 1, 0, 0, 0}, 0}, 93 | {23, 3, {0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0}, {1, 1, 1, 1, 0, 0}, 0}, 94 | {23, 3, {0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1}, {1, 1, 0, 0, 1, 0}, 1}, 95 | {23, 3, {1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0}, {0, 1, 0, 1, 1, 0}, 0}, 96 | {23, 3, {0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1}, {1, 0, 0, 0, 0, 0}, 0}, 97 | {23, 3, {0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0}, {0, 1, 1, 1, 0, 1}, 0}, 98 | {23, 3, {1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1}, {1, 1, 1, 0, 0, 0}, 0}, 99 | {23, 3, {0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0}, {1, 0, 0, 1, 1, 1}, 0}, 100 | {23, 3, {0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1}, {1, 0, 1, 1, 0, 0}, 0}, 101 | {23, 3, {1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1}, {0, 1, 1, 1, 0, 1}, 0}, 102 | {23, 3, {1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0}, {0, 1, 0, 1, 1, 0}, 1}, 103 | {23, 3, {0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0}, {0, 1, 1, 0, 0, 0}, 0}, 104 | {23, 3, {0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1}, {0, 1, 1, 1, 1, 1}, 1}, 105 | {23, 3, {0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0}, {1, 1, 0, 1, 0, 0}, 0}, 106 | }; 107 | 108 | class RshTest : 109 | public testing::TestWithParam { 110 | }; 111 | 112 | 113 | TEST_P(HrTest, Example) { 114 | int m = get<0>(GetParam()), log_t = get<1>(GetParam()), i = get<2>(GetParam()); 115 | 116 | auto result = get<3>(GetParam()); 117 | ASSERT_EQ(result, HRWeakDesign(m, log_t).get_s(i)); 118 | } 119 | 120 | INSTANTIATE_TEST_SUITE_P(HrTests, 121 | HrTest, 122 | testing::ValuesIn(hr_examples)); 123 | 124 | 125 | TEST_P(BwdTest, Example) { 126 | int m = get<0>(GetParam()), log_t = get<1>(GetParam()), i = get<2>(GetParam()); 127 | 128 | auto result = get<3>(GetParam()); 129 | EXPECT_EQ(result, BlockWeakDesign(m, log_t).get_s(i)); 130 | } 131 | 132 | INSTANTIATE_TEST_SUITE_P(BwdTests, 133 | BwdTest, 134 | testing::ValuesIn(bwd_examples)); 135 | 136 | 137 | TEST_P(RshTest, Example) { 138 | int n = get<0>(GetParam()), l = get<1>(GetParam()); 139 | vector r_input = get<2>(GetParam()), r_seed = get<3>(GetParam()); 140 | 141 | int result = get<4>(GetParam()); 142 | EXPECT_EQ(result, RSHExtractor(n, l).extract(r_input, r_seed)); 143 | } 144 | 145 | INSTANTIATE_TEST_SUITE_P(RshTests, 146 | RshTest, 147 | testing::ValuesIn(rsh_examples)); 148 | 149 | 150 | TEST(TimingTest, Example) { 151 | int n = 1000; 152 | int k = 200; 153 | vector r_input; 154 | vector r_seed; 155 | 156 | auto trevisan = new Trevisan(TrevisanConfig(n, k, 0.01)); 157 | 158 | for (int i = 0; i < n; i++) { 159 | r_input.push_back(rand() % 2); 160 | } 161 | 162 | for (int i = 0; i < trevisan->get_seed_length(); i++) { 163 | r_seed.push_back(rand() % 2); 164 | } 165 | 166 | trevisan->load_source(r_input, r_seed); 167 | for (int i = 0; i < 10; i++) { 168 | auto result = trevisan->extract(); 169 | trevisan->load_source(r_input, r_seed); 170 | } 171 | } 172 | 173 | 174 | int main(int argc, char **argv) { 175 | testing::InitGoogleTest(&argc, argv); 176 | return RUN_ALL_TESTS(); 177 | } 178 | --------------------------------------------------------------------------------