├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── documentation.md │ └── feature_request.md ├── pull_request_template.md └── workflows │ ├── main.yml │ ├── pull_request.yml │ └── push.yml ├── .gitignore ├── CITATION.cff ├── Cargo.toml ├── LICENSE ├── README.md ├── benches ├── README.md ├── benchmarks.rs ├── pfdh.rs └── regev.rs └── src ├── construction.rs ├── construction ├── hash.rs ├── hash │ ├── sha256.rs │ └── sis.rs ├── identity_based_encryption.rs ├── identity_based_encryption │ └── dual_regev_ibe.rs ├── pk_encryption.rs ├── pk_encryption │ ├── ccs_from_ibe.rs │ ├── ccs_from_ibe │ │ └── dual_regev_ibe_pfdh.rs │ ├── dual_regev.rs │ ├── dual_regev_discrete_gauss.rs │ ├── lpr.rs │ ├── regev.rs │ ├── regev_discrete_gauss.rs │ └── ring_lpr.rs ├── signature.rs └── signature │ ├── fdh.rs │ ├── fdh │ ├── gpv.rs │ ├── gpv_ring.rs │ └── serialize.rs │ ├── pfdh.rs │ └── pfdh │ ├── gpv.rs │ └── serialize.rs ├── lib.rs ├── primitive.rs ├── primitive ├── psf.rs └── psf │ ├── gpv.rs │ └── gpv_ring.rs ├── sample.rs ├── sample ├── g_trapdoor.rs └── g_trapdoor │ ├── gadget_classical.rs │ ├── gadget_default.rs │ ├── gadget_parameters.rs │ ├── gadget_ring.rs │ ├── short_basis_classical.rs │ ├── short_basis_ring.rs │ └── trapdoor_distribution.rs ├── utils.rs └── utils ├── common_moduli.rs └── rotation_matrix.rs /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | @qfall/pg 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | 18 | 19 | **Describe the bug** 20 | 21 | 22 | **To Reproduce** 23 | 24 | 25 | ```rust 26 | // write your code here 27 | ``` 28 | 29 | **Expected behavior** 30 | 32 | 33 | 34 | **Screenshots** 35 | 36 | 37 | 38 | **Desktop (please complete the following information):** 39 | - OS: 40 | - Version of qFALL-crypto: 41 | 42 | **Additional context** 43 | 44 | 45 | **Solution** 46 | 47 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Documentation 3 | about: Report a problem with the documentation 4 | title: '' 5 | labels: "documentation" 6 | assignees: '' 7 | 8 | --- 9 | 10 | 15 | 16 | **Documentation** 17 | 18 | Type of documentation issue: 19 | 21 | 22 | **Where can we find it** 23 | 24 | 25 | **Please describe what the current documentation is lacking** 26 | 27 | 28 | **Solution** 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | 17 | **Description** 18 | 19 | 20 | **Motivation** 21 | 22 | 23 | **Best available solution** 24 | 28 | ```rust 29 | // write your API call to the new feature here 30 | ``` 31 | 32 | **Additional context** 33 | 34 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | **Description** 2 | 3 | 7 | 8 | This PR implements... 9 | - [ ] feature/ revision/ hotfix/ optimisation/ ... 10 | 11 | for/ of `Component`. 12 | 13 | 17 | 18 | **Testing** 19 | 20 | 21 | 22 | 23 | - [ ] I added basic working examples (possibly in doc-comment) 24 | - [ ] I triggered all possible errors in my test in every possible way 25 | - [ ] I included tests for all reasonable edge cases 26 | - [ ] I provided an intuition regarding how certain inputs have to be set 27 | 28 | 29 | **Checklist:** 30 | 31 | 32 | 33 | - [ ] I have performed a self-review of my own code 34 | - [ ] The code provides good readability and maintainability s.t. it fulfills best practices like talking code, modularity, ... 35 | - [ ] The chosen implementation is not more complex than it has to be 36 | - [ ] My code should work as intended and no side effects occur (e.g. memory leaks) 37 | - [ ] The doc comments fit our style guide 38 | - [ ] I have credited related sources if needed 39 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Pipeline 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | 7 | env: 8 | CARGO_TERM_COLOR: always 9 | RUSTDOCFLAGS: "-Dwarnings" 10 | 11 | jobs: 12 | full_pipeline: 13 | name: Full Pipeline 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Setup dtolnay/rust-toolchain 20 | uses: dtolnay/rust-toolchain@stable 21 | with: 22 | toolchain: stable 23 | components: clippy, rustfmt 24 | 25 | # load project cache to reduce compilation time 26 | - name: Setup project cache 27 | uses: actions/cache@v3 28 | continue-on-error: false 29 | with: 30 | path: | 31 | ~/.cargo/bin/ 32 | ~/.cargo/registry/index/ 33 | ~/.cargo/registry/cache/ 34 | ~/.cargo/git/db/ 35 | target/ 36 | key: release-${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }} 37 | restore-keys: release-${{ runner.os }}-cargo- 38 | 39 | - name: Set environment variables 40 | run: | 41 | echo "PROJECT_NAME=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0] | [ .name ] | join("")')" >> $GITHUB_ENV 42 | echo "PROJECT_VERSION=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0] | [ .version ] | join("")')" >> $GITHUB_ENV 43 | 44 | - name: Update dependencies 45 | run: cargo update 46 | - name: Build 47 | run: cargo build --release 48 | - name: Generate docs 49 | run: cargo doc 50 | - name: Run doc tests # Unit tests are run by tarpaulin 51 | run: cargo test --doc --verbose 52 | 53 | - name: Install cargo-tarpaulin 54 | uses: baptiste0928/cargo-install@v2 55 | with: 56 | crate: cargo-tarpaulin 57 | - name: Calculate test coverage 58 | run: cargo tarpaulin --out Html 59 | - name: Archive code coverage results 60 | uses: actions/upload-artifact@v4 61 | with: 62 | name: ${{ env.PROJECT_NAME }}-code_coverage_report-v${{ env.PROJECT_VERSION }} 63 | path: tarpaulin-report.html 64 | 65 | # Lints: Clippy and Fmt 66 | - name: Clippy 67 | run: cargo clippy -- -D warnings 68 | - name: Format 69 | run: cargo fmt --all -- --check 70 | 71 | # Cargo check for security issues 72 | - name: Install cargo-audit 73 | uses: baptiste0928/cargo-install@v2 74 | with: 75 | crate: cargo-audit 76 | - name: Security audit 77 | run: cargo audit 78 | 79 | # Check for outdated dependencies 80 | - name: Install cargo-outdated 81 | uses: dtolnay/install@cargo-outdated 82 | - name: Outdated dependencies 83 | run: cargo outdated --exit-code 1 84 | 85 | - name: Archive release build 86 | uses: actions/upload-artifact@v4 87 | with: 88 | name: ${{ env.PROJECT_NAME }}-release_build-v${{ env.PROJECT_VERSION }} 89 | path: target/release/${{ env.PROJECT_NAME }} 90 | -------------------------------------------------------------------------------- /.github/workflows/pull_request.yml: -------------------------------------------------------------------------------- 1 | name: Pipeline 2 | # consistency regarding formatting and idiomatic Rust 3 | 4 | on: 5 | push: 6 | branches: 7 | - dev 8 | pull_request: 9 | branches: 10 | - "**" 11 | 12 | env: 13 | CARGO_TERM_COLOR: always 14 | RUSTDOCFLAGS: "-Dwarnings" 15 | 16 | jobs: 17 | pipeline: 18 | name: Pipeline - code coverage + dependency check 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | - uses: actions/checkout@v3 23 | 24 | - name: Setup dtolnay/rust-toolchain 25 | uses: dtolnay/rust-toolchain@stable 26 | with: 27 | toolchain: stable 28 | components: clippy, rustfmt 29 | 30 | # load project cache to reduce compilation time 31 | - name: Setup project cache 32 | uses: actions/cache@v3 33 | continue-on-error: false 34 | with: 35 | path: | 36 | ~/.cargo/bin/ 37 | ~/.cargo/registry/index/ 38 | ~/.cargo/registry/cache/ 39 | ~/.cargo/git/db/ 40 | target/ 41 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }} 42 | restore-keys: ${{ runner.os }}-cargo- 43 | 44 | - name: Set environment variables 45 | run: | 46 | echo "PROJECT_NAME=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0] | [ .name ] | join("")')" >> $GITHUB_ENV 47 | echo "PROJECT_VERSION=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0] | [ .version ] | join("")')" >> $GITHUB_ENV 48 | 49 | - name: Update dependencies 50 | run: cargo update 51 | - name: Build 52 | run: cargo build 53 | - name: Generate docs 54 | run: cargo doc 55 | - name: Run doc tests # Unit tests are run by tarpaulin 56 | run: cargo test --doc --verbose 57 | 58 | - name: Install cargo-tarpaulin 59 | uses: baptiste0928/cargo-install@v2 60 | with: 61 | crate: cargo-tarpaulin 62 | - name: Calculate test coverage 63 | run: cargo tarpaulin --out Html 64 | - name: Archive code coverage results 65 | uses: actions/upload-artifact@v4 66 | with: 67 | name: ${{ env.PROJECT_NAME }}-code_coverage_report-v${{ env.PROJECT_VERSION }} 68 | path: tarpaulin-report.html 69 | 70 | # Lints: Clippy and Fmt 71 | - name: Clippy 72 | run: cargo clippy -- -D warnings 73 | - name: Format 74 | run: cargo fmt --all -- --check 75 | 76 | # Cargo check for security issues 77 | - name: Install cargo-audit 78 | uses: baptiste0928/cargo-install@v2 79 | with: 80 | crate: cargo-audit 81 | - name: Security audit 82 | run: cargo audit 83 | 84 | # Check for outdated dependencies 85 | - name: Install cargo-outdated 86 | uses: dtolnay/install@cargo-outdated 87 | - name: Outdated dependencies 88 | run: cargo outdated --exit-code 1 89 | -------------------------------------------------------------------------------- /.github/workflows/push.yml: -------------------------------------------------------------------------------- 1 | name: Pipeline 2 | # consistency regarding formatting and idiomatic Rust 3 | 4 | on: 5 | push: 6 | branches-ignore: 7 | - main 8 | - dev 9 | 10 | env: 11 | CARGO_TERM_COLOR: always 12 | RUSTDOCFLAGS: "-Dwarnings" 13 | 14 | jobs: 15 | pipeline: 16 | name: Pipeline - test and lints 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v3 21 | 22 | - name: Setup dtolnay/rust-toolchain 23 | uses: dtolnay/rust-toolchain@stable 24 | with: 25 | toolchain: stable 26 | components: clippy, rustfmt 27 | 28 | # load project cache to reduce compilation time 29 | - name: Setup project cache 30 | uses: actions/cache@v3 31 | continue-on-error: false 32 | with: 33 | path: | 34 | ~/.cargo/bin/ 35 | ~/.cargo/registry/index/ 36 | ~/.cargo/registry/cache/ 37 | ~/.cargo/git/db/ 38 | target/ 39 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }} 40 | restore-keys: ${{ runner.os }}-cargo- 41 | 42 | - name: Update dependencies 43 | run: cargo update 44 | - name: Build 45 | run: cargo build 46 | - name: Generate docs 47 | run: cargo doc 48 | 49 | - name: Test 50 | run: cargo test --verbose 51 | 52 | # Lints: Clippy and Fmt 53 | - name: Clippy 54 | run: cargo clippy -- -D warnings 55 | - name: Format 56 | run: cargo fmt --all -- --check 57 | 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | # ignore files generated while using flamegraph 13 | perf.* 14 | flamegraph.svg 15 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | # This CITATION.cff file was generated with cffinit. 2 | # Visit https://bit.ly/cffinit to generate yours today! 3 | 4 | cff-version: 1.2.0 5 | title: qFALL-crypto 6 | message: 'University Paderborn, Codes and Cryptography' 7 | type: software 8 | authors: 9 | - given-names: Laurens 10 | family-names: Porzenheim 11 | - given-names: Marvin 12 | family-names: Beckmann 13 | - given-names: Paul 14 | family-names: Kramer 15 | - given-names: Phil 16 | family-names: Milewski 17 | - given-names: Sven 18 | family-names: Moog 19 | - given-names: Marcel 20 | family-names: Schmidt 21 | - given-names: Niklas 22 | family-names: Siemer 23 | repository-code: 'https://github.com/qfall/crypto' 24 | license: MPL-2.0 25 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "qfall-crypto" 3 | version = "0.1.0" 4 | edition = "2021" 5 | autobenches = false 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | qfall-math = { git = "https://github.com/qfall/math", rev="25163dce852c488af602cddda752ff4c74b0d2c9" } 11 | sha2 = "0.10.6" 12 | serde = {version="1.0", features=["derive"]} 13 | serde_json = "1.0" 14 | typetag = "0.2" 15 | criterion = { version = "0.5", features = ["html_reports"] } 16 | 17 | [profile.bench] 18 | debug = true 19 | 20 | [[bench]] 21 | name = "benchmarks" 22 | harness = false 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # qFALL-crypto 2 | [![made-with-rust](https://img.shields.io/badge/Made%20with-Rust-1f425f.svg)](https://www.rust-lang.org/) 3 | [![CI](https://github.com/qfall/crypto/actions/workflows/push.yml/badge.svg?branch=dev)](https://github.com/qfall/crypto/actions/workflows/pull_request.yml) 4 | [![License: MPL 2.0](https://img.shields.io/badge/License-MPL_2.0-brightgreen.svg)](https://opensource.org/licenses/MPL-2.0) 5 | 6 | This repository is currently being developed by the project group [qFALL - quantum resistant fast lattice library](https://cs.uni-paderborn.de/cuk/lehre/veranstaltungen/ws-2022-23/project-group-qfall) in the winter term 2022 and summer term 2023 by the Codes and Cryptography research group in Paderborn. 7 | 8 | The main objective of this project is to provide researchers and students with the possibility to easily and quickly prototype (lattice-based) cryptography. 9 | 10 | ## Disclaimer 11 | Currently, we are in the development phase and interfaces might change. 12 | Feel free to check out the current progress, but be aware, that the content will 13 | change in the upcoming weeks and months. An official release will most likely be published in the second half of 2023. 14 | 15 | ## Quick-Start 16 | 17 | Please refer to [our website](https://qfall.github.io/) as central information point. 18 | 19 | To install and add our library to your project, please refer to [our tutorial](https://qfall.github.io/book/index.html). 20 | It provides a step-by-step guide to install the required libraries and gives further insights in the usage of our crates. 21 | 22 | ## What does qFALL-crypto offer? 23 | 24 | qFALL-crypto offers a variety of implementations of cryptographic schemes, constructions, and primitives. 25 | We provide a brief overview in the following list. 26 | For a more detailed description, please refer to [our tutorial section](https://qfall.github.io/book/crypto/features.html). 27 | 28 | Full-fledged Cryptographic Features 29 | - [Public Key Encryption](https://github.com/qfall/crypto/blob/dev/src/construction/pk_encryption.rs) 30 | - [LWE Encryption](https://github.com/qfall/crypto/blob/dev/src/construction/pk_encryption/regev.rs) 31 | - [Dual LWE Encryption](https://github.com/qfall/crypto/blob/dev/src/construction/pk_encryption/dual_regev.rs) 32 | - [LPR Encryption](https://github.com/qfall/crypto/blob/dev/src/construction/pk_encryption/lpr.rs) 33 | - [Ring-based LPR Encryption](https://github.com/qfall/crypto/blob/dev/src/construction/pk_encryption/ring_lpr.rs) 34 | - [CCA-secure Encryption](https://github.com/qfall/crypto/blob/dev/src/construction/pk_encryption/ccs_from_ibe.rs) 35 | - [Signatures](https://github.com/qfall/crypto/blob/dev/src/construction/signature.rs) 36 | - [Full-Domain Hash (FDH)](https://github.com/qfall/crypto/blob/dev/src/construction/signature/fdh.rs) 37 | - [Probabilistic FDH (PFDH)](https://github.com/qfall/crypto/blob/dev/src/construction/signature/pfdh.rs) 38 | - [Ring-based FDH](https://github.com/qfall/crypto/blob/dev/src/construction/signature/fdh/gpv_ring.rs) 39 | - [Identity Based Encryption](https://github.com/qfall/crypto/blob/dev/src/construction/identity_based_encryption.rs) 40 | - [From Dual LWE Encryption](https://github.com/qfall/crypto/blob/dev/src/construction/identity_based_encryption/dual_regev_ibe.rs) 41 | - [Hash Functions](https://github.com/qfall/crypto/blob/dev/src/construction/hash.rs) 42 | - [SIS-Hash Function](https://github.com/qfall/crypto/blob/dev/src/construction/hash/sis.rs) 43 | - [SHA-256-based Hash](https://github.com/qfall/crypto/blob/dev/src/construction/hash/sha256.rs) 44 | 45 | Building Blocks and Primitives 46 | - [Preimage Samplable Functions (PSF)](https://github.com/qfall/crypto/blob/dev/src/primitive/psf.rs) 47 | - [Trapdoors](https://github.com/qfall/crypto/blob/dev/src/sample/g_trapdoor.rs) 48 | - [G-trapdoor incl. short basis](https://github.com/qfall/crypto/blob/dev/src/sample/g_trapdoor/gadget_classical.rs) 49 | - [Ring-based G-trapdoor incl. short basis](https://github.com/qfall/crypto/blob/dev/src/sample/g_trapdoor/gadget_ring.rs) 50 | 51 | ## License 52 | This library is distributed under the **Mozilla Public License Version 2.0** which can be found here [License](https://github.com/qfall/crypto/blob/dev/LICENSE). 53 | Permissions of this weak copyleft license are conditioned on making available source code of licensed files and modifications of those files under the same license (or in certain cases, one of the GNU licenses). Copyright and license notices must be preserved. Contributors provide an express grant of patent rights. However, a larger work using the licensed work may be distributed under different terms and without source code for files added in the larger work. 54 | 55 | ## Citing 56 | 57 | Please use the following bibtex entry to cite [qFALL-crypto](https://github.com/qfall/crypto): 58 | 59 | ```text 60 | @misc{qFALL-crypto, 61 | author = {Porzenheim, Laurens and Beckmann, Marvin and Kramer, Paul and Milewski, Phil and Moog, Sven and Schmidt, Marcel and Siemer, Niklas}, 62 | title = {qFALL-crypto v0.0}, 63 | howpublished = {Online: \url{https://github.com/qfall/crypto}}, 64 | month = Mar, 65 | year = 2023, 66 | note = {University Paderborn, Codes and Cryptography} 67 | } 68 | ``` 69 | 70 | ## Get in Touch 71 | One can contact the members of the project group with our mailing list `pg-qfall(at)lists.upb.de`. 72 | -------------------------------------------------------------------------------- /benches/README.md: -------------------------------------------------------------------------------- 1 | 10 | 11 | # How to run benchmarks: 12 | ## Criterion 13 | We use criterion for statistical analysis. A plotting library has to be installed to generate graphs. You can find more information and help here: 14 | - [Criterion-rs GitHub](https://github.com/bheisler/criterion.rs) 15 | - [Cargo-criterion GitHub](https://github.com/bheisler/cargo-criterion) 16 | - [Criterion Book](https://bheisler.github.io/criterion.rs/book/criterion_rs.html) (!Watchout for the criterion version, as of writing this the book is not on the latest version!) 17 | 18 | 19 | ### Commands 20 | a) ```cargo criterion ``` 21 | Has to be installed with `cargo install cargo-criterion`. 22 | Pros: 23 | - You can remove `features = ["html_reports"]` from the `Cargo.toml` leading to a (slightly) faster compile times. 24 | - Criterion aims to move to just using cargo criterion 25 | - The large Probability Density Function graph shows the samples and marks the outlier categorization boarders. 26 | - Can use either [gnuplot](http://www.gnuplot.info/) or [plotters](https://crates.io/crates/plotters) 27 | 28 | b) ```cargo bench ``` 29 | Pros: 30 | - Can visualize the change in performance compared to previous run or other baseline 31 | Cons: 32 | - Can only use [gnuplot](http://www.gnuplot.info/) 33 | 34 | ## Flamegraph 35 | You can also run the benchmarks using the profiler flamegraph. Details can be found here: 36 | - [Flamegraph GitHub](https://github.com/flamegraph-rs/flamegraph). 37 | This provides insights on the execution time of the executed functions and their subroutines. 38 | 39 | Note: Flamegraph does not work in WSL 40 | 41 | ### Command 42 | ```cargo flamegraph --freq 63300 --bench benchmarks -- --bench --profile-time 5 ``` 43 | Generates a flamegraph that allows to approximate how long each function executes. The accuracy of the approximation is better the more samples are produced. This can be improved by 44 | - increasing the sample frequency (`--freq 63300`), This frequency is throttled to the highest possible frequency which depends on the cpu, cpu-temperature, power settings and much more... 45 | - increasing `profile-time` (in seconds). This is how long the benchmark code will be executed. 46 | This parameter also disables the statistical analysis of criterion which prevents it from showing up in the graph. 47 | This parameter is optional, but suggested. 48 | 49 | The flamegraph can be overwhelming since it exposes a lot of internal workings of rust, criterion, and more. 50 | The easiest way to find the function you are looking for is to search for it with `Ctrl + F`. 51 | You have to enter a part of the rust function name or regex (not the benchmark name). 52 | 53 | # How to create benchmarks 54 | 55 | ## No appropriate file exists so far: 56 | 1. create the file 57 | 2. Insert in new file: 58 | ``` rust 59 | use criterion::*; 60 | 61 | criterion_group!(benches); 62 | ``` 63 | 3. Insert in [benchmarks.rs](/benches/benchmarks.rs): 64 | ``` rust 65 | pub mod ; 66 | ``` 67 | and `::benches` in the `criterion_main!` macro. 68 | 69 | ## Appropriately named benchmark file exists in `/benches` (e.g. `integer.rs`) 70 | 1. Create a function that performs the functionality that should be benchmarked (called `do_stuff` below). 71 | 2. Add a function to handle the interaction with criterion. 72 | e.g.: 73 | ``` rust 74 | /// Add Comment describing the benchmark here 75 | pub fn bench_do_stuff(c: &mut Criterion) { 76 | c.bench_function("", |b| b.iter(|| do_stuff())); 77 | } 78 | ``` 79 | The benchmark name specified here is later used to select which benchmark to run and also displayed in the output. 80 | This function can also look differently, for example, because it uses [criterion groups](https://docs.rs/criterion/latest/criterion/struct.BenchmarkGroup.html). 81 | 3. Add function created in step 2 in the `criterion_group!` macro (bottom of file). 82 | 83 | -------------------------------------------------------------------------------- /benches/benchmarks.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Sven Moog 2 | // 3 | // This file is part of qFALL-crypto. 4 | // 5 | // qFALL-crypto is free software: you can redistribute it and/or modify it under 6 | // the terms of the Mozilla Public License Version 2.0 as published by the 7 | // Mozilla Foundation. See . 8 | //! This file collects the benchmarks from other files. 9 | 10 | use criterion::criterion_main; 11 | 12 | pub mod pfdh; 13 | pub mod regev; 14 | 15 | criterion_main! {regev::benches, pfdh::benches} 16 | -------------------------------------------------------------------------------- /benches/pfdh.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Marvin Beckmann 2 | // 3 | // This file is part of qFALL-crypto. 4 | // 5 | // qFALL-crypto is free software: you can redistribute it and/or modify it under 6 | // the terms of the Mozilla Public License Version 2.0 as published by the 7 | // Mozilla Foundation. See . 8 | 9 | use criterion::{criterion_group, Criterion}; 10 | use qfall_crypto::construction::signature::{SignatureScheme, PFDH}; 11 | 12 | /// Performs a full instantiation with an additional signing and verifying of a signature. 13 | fn pfdh_cycle(n: i64) { 14 | let mut pfdh = PFDH::init_gpv(n, 113, 17, 128); 15 | 16 | let m = "Hello World!"; 17 | 18 | let (pk, sk) = pfdh.gen(); 19 | let sigma = pfdh.sign(m.to_owned(), &sk, &pk); 20 | 21 | pfdh.vfy(m.to_owned(), &sigma, &pk); 22 | } 23 | 24 | /// Benchmark [bench_pfdh_full_cycle] with `n = 8`. 25 | /// 26 | /// This benchmark can be run with for example: 27 | /// - `cargo criterion Full\ Cycle\ PFDH\ n=8` 28 | /// - `cargo bench --bench benchmarks Full\ Cycle\ PFDH\ n=8` 29 | /// - `cargo flamegraph --bench benchmarks -- --bench Full\ Cycle\ PFDH\ n=8` 30 | /// 31 | /// Shorter variants or regex expressions can also be used to specify the 32 | /// benchmark name. The `\ ` is used to escape the space, alternatively, 33 | /// quotation marks can be used. 34 | fn bench_pfdh_full_cycle(c: &mut Criterion) { 35 | c.bench_function("Full Cycle PFDH n=8", |b| b.iter(|| pfdh_cycle(8))); 36 | } 37 | 38 | /// Benchmark [bench_pfdh_signature] with `n = 8`. 39 | /// 40 | /// This benchmark can be run with for example: 41 | /// - `cargo criterion Signing\ PFDH\ n=8` 42 | /// - `cargo bench --bench benchmarks Signing\ PFDH\ n=8` 43 | /// - `cargo flamegraph --bench benchmarks -- --bench Signing\ PFDH\ n=8` 44 | /// 45 | /// Shorter variants or regex expressions can also be used to specify the 46 | /// benchmark name. The `\ ` is used to escape the space, alternatively, 47 | /// quotation marks can be used. 48 | fn bench_pfdh_signature(c: &mut Criterion) { 49 | let mut pfdh = PFDH::init_gpv(8, 113, 17, 128); 50 | 51 | let m = "Hello World!"; 52 | 53 | let (pk, sk) = pfdh.gen(); 54 | 55 | c.bench_function("Signing PFDH n=8", |b| { 56 | b.iter(|| pfdh.sign(m.to_owned(), &sk, &pk)) 57 | }); 58 | } 59 | 60 | criterion_group!(benches, bench_pfdh_full_cycle, bench_pfdh_signature); 61 | -------------------------------------------------------------------------------- /benches/regev.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Sven Moog 2 | // 3 | // This file is part of qFALL-crypto. 4 | // 5 | // qFALL-crypto is free software: you can redistribute it and/or modify it under 6 | // the terms of the Mozilla Public License Version 2.0 as published by the 7 | // Mozilla Foundation. See . 8 | 9 | use criterion::*; 10 | use qfall_crypto::construction::pk_encryption::PKEncryptionScheme; 11 | use qfall_crypto::construction::pk_encryption::Regev; 12 | use qfall_math::integer::Z; 13 | 14 | /// Performs a full-cycle of gen, enc, dec with regev. 15 | fn regev_cycle(n: i64) { 16 | let msg = Z::ONE; 17 | let regev = Regev::new_from_n(n); 18 | 19 | let (pk, sk) = regev.gen(); 20 | let cipher = regev.enc(&pk, &msg); 21 | let _ = regev.dec(&sk, &cipher); 22 | } 23 | 24 | /// Benchmark [regev_cycle] with `n = 50`. 25 | /// 26 | /// This benchmark can be run with for example: 27 | /// - `cargo criterion Regev\ n=50` 28 | /// - `cargo bench --bench benchmarks Regev\ n=50` 29 | /// - `cargo flamegraph --bench benchmarks -- --bench Regev\ n=50` 30 | /// 31 | /// Shorter variants or regex expressions can also be used to specify the 32 | /// benchmark name. The `\ ` is used to escape the space, alternatively, 33 | /// quotation marks can be used. 34 | fn bench_regev_cycle(c: &mut Criterion) { 35 | c.bench_function("Regev n=50", |b| b.iter(|| regev_cycle(50))); 36 | } 37 | 38 | /// Benchmark [regev_cycle] with `n = 10, 20, 30, 40, 50, 60` 39 | /// 40 | /// This benchmark can be run with for example: 41 | /// - `cargo criterion "Regev\ n\ sweep"` 42 | /// - `cargo criterion Regev\ n\ sweep/n=20` (only run the n=20 benchmark). 43 | /// - `cargo criterion 'Regev.*n=20'` (only run the n=20 benchmark). 44 | /// - `cargo bench --bench benchmarks Regev\ n\ sweep` 45 | /// 46 | /// Shorter variants or regex expressions can also be used to specify the 47 | /// benchmark name. The `\ ` is used to escape the space, alternatively, 48 | /// quotation marks can be used. 49 | fn bench_regev_cycle_n_sweep(c: &mut Criterion) { 50 | let mut group = c.benchmark_group("Regev n sweep"); 51 | 52 | for n in [10, 20, 30, 40, 50, 60].iter() { 53 | group.bench_function(format!("n={n}"), |b| b.iter(|| regev_cycle(*n))); 54 | } 55 | 56 | group.finish(); 57 | } 58 | 59 | criterion_group!(benches, bench_regev_cycle, bench_regev_cycle_n_sweep); 60 | -------------------------------------------------------------------------------- /src/construction.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Niklas Siemer, Marvin Beckmann 2 | // 3 | // This file is part of qFALL-crypto. 4 | // 5 | // qFALL-crypto is free software: you can redistribute it and/or modify it under 6 | // the terms of the Mozilla Public License Version 2.0 as published by the 7 | // Mozilla Foundation. See . 8 | 9 | //! This module contains fundamental cryptographic constructions, on which other 10 | //! constructions can be build on. 11 | //! 12 | //! Among others, these include encryption schemes and signature schemes. 13 | //! A construction is always build the same way: 14 | //! 15 | //! 1. A trait that combines the common feature, e.g. 16 | //! [`public key encryption`](pk_encryption::PKEncryptionScheme). 17 | //! 2. Explicit implementations of the trait, e.g. 18 | //! [`RingLPR`](pk_encryption::RingLPR). 19 | 20 | pub mod hash; 21 | pub mod identity_based_encryption; 22 | pub mod pk_encryption; 23 | pub mod signature; 24 | -------------------------------------------------------------------------------- /src/construction/hash.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Niklas Siemer 2 | // 3 | // This file is part of qFALL-crypto. 4 | // 5 | // qFALL-crypto is free software: you can redistribute it and/or modify it under 6 | // the terms of the Mozilla Public License Version 2.0 as published by the 7 | // Mozilla Foundation. See . 8 | 9 | //! This module contains implementations of hash functions. 10 | //! 11 | //! The main references are listed in the following: 12 | //! - \[1\] Peikert, Chris (2016). 13 | //! A decade of lattice cryptography. 14 | //! In: Theoretical Computer Science 10.4. 15 | //! 16 | 17 | pub mod sha256; 18 | mod sis; 19 | 20 | pub use sis::SISHash; 21 | 22 | /// This trait should be implemented by hashes with domain [`str`]. 23 | pub trait HashInto { 24 | /// Hashes a given String literal. 25 | /// 26 | /// Paramters: 27 | /// - `m`: specifies the string message to be hashed 28 | /// 29 | /// Returns a hash of type Domain. 30 | fn hash(&self, m: &str) -> DigestSpace; 31 | } 32 | -------------------------------------------------------------------------------- /src/construction/hash/sha256.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Phil Milewski 2 | // 3 | // This file is part of qFALL-crypto. 4 | // 5 | // qFALL-crypto is free software: you can redistribute it and/or modify it under 6 | // the terms of the Mozilla Public License Version 2.0 as published by the 7 | // Mozilla Foundation. See . 8 | 9 | //! This module contains sha256 hashes into different domains. 10 | 11 | use super::HashInto; 12 | use qfall_math::traits::FromCoefficientEmbedding; 13 | use qfall_math::utils::index::evaluate_indices; 14 | use qfall_math::{ 15 | integer::{MatPolyOverZ, Z}, 16 | integer_mod_q::{MatPolynomialRingZq, MatZq, Modulus, ModulusPolynomialRingZq, Zq}, 17 | traits::MatrixSetEntry, 18 | }; 19 | use serde::{Deserialize, Serialize}; 20 | use sha2::{Digest, Sha256}; 21 | use std::fmt::Display; 22 | 23 | /// Computes the sha256 hash value of a given String literal. 24 | /// 25 | /// Parameters: 26 | /// - `string`: specifies the value that is hashed. 27 | /// 28 | /// Returns the sha256 value of the given string as a hex string. 29 | /// 30 | /// # Examples 31 | /// ``` 32 | /// use qfall_crypto::construction::hash::sha256::sha256; 33 | /// 34 | /// let string = "Hello World!"; 35 | /// let hash = sha256(string); 36 | /// assert_eq!("7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069", hash); 37 | /// ``` 38 | pub fn sha256(string: &str) -> String { 39 | let mut hasher = Sha256::new(); 40 | hasher.update(string); 41 | let result = hasher.finalize(); 42 | format!("{:x}", result) 43 | } 44 | 45 | /// Hashes a given String literal into a [`Zq`] using sha256. 46 | /// 47 | /// Parameters: 48 | /// - `string`: specifies the value that is hashed. 49 | /// - `modulus`: specifies the modulus of the returned [`Zq`] value 50 | /// 51 | /// Returns a [`Zq`] as a hash value for the given string. 52 | /// 53 | /// # Examples 54 | /// ``` 55 | /// use qfall_crypto::construction::hash::sha256::hash_to_zq_sha256; 56 | /// use qfall_math::integer_mod_q::Zq; 57 | /// 58 | /// let string = "Hello World!"; 59 | /// 60 | /// let hash: Zq = hash_to_zq_sha256("Hello World!", 7); 61 | /// assert_eq!(Zq::from((2, 7)), hash) 62 | /// ``` 63 | /// 64 | /// # Panics ... 65 | /// - if `modulus <= 1`. 66 | pub fn hash_to_zq_sha256(string: &str, modulus: impl Into) -> Zq { 67 | let modulus = modulus.into(); 68 | let modulus_new = Z::from(&modulus); 69 | let bitsize = modulus_new.bits(); 70 | let mut hex = "".to_string(); 71 | let string2 = format!("{modulus_new} {string}"); 72 | 73 | for i in 0..=bitsize / 128 74 | // hashing into e.g. Zq with 256 bit length of q from 256 bit will result in 75 | // lower values to be up to two times as likely as higher values. 76 | // Doubling the bit size of the hashed number will 77 | // reduce this difference to 1/2^n which is negligible. 78 | // https://crypto.stackexchange.com/questions/37305/how-can-i-instantiate-a-generalized-hash-function 79 | { 80 | hex = hex + &sha256(&format!("{i} {string2}")); 81 | } 82 | 83 | Zq::from((Z::from_str_b(&hex, 16).unwrap(), modulus)) 84 | } 85 | 86 | /// Hashes a given String literal into a [`MatZq`] using sha256. 87 | /// 88 | /// Parameters: 89 | /// - `string`: specifies the value that is hashed 90 | /// - `num_rows`: specifies the number of rows of the result 91 | /// - `num_cols`: specifies the number of columns of the result 92 | /// - `modulus`: specifies the modulus of the returned [`MatZq`] value 93 | /// 94 | /// Returns a [`MatZq`] as a hash for the given string. 95 | /// 96 | /// # Examples 97 | /// ``` 98 | /// use qfall_crypto::construction::hash::sha256::hash_to_mat_zq_sha256; 99 | /// use qfall_math::integer_mod_q::MatZq; 100 | /// use std::str::FromStr; 101 | /// 102 | /// let string = "Hello World!"; 103 | /// 104 | /// let hash: MatZq = hash_to_mat_zq_sha256(string, 2, 2, 7); 105 | /// assert_eq!(MatZq::from_str("[[6, 3],[5, 2]] mod 7").unwrap(), hash); 106 | /// ``` 107 | /// 108 | /// # Panics ... 109 | /// - if `modulus <= 1`. 110 | /// - if the number of rows or columns is less or equal to `0` or does not fit into an [`i64`]. 111 | pub fn hash_to_mat_zq_sha256( 112 | string: &str, 113 | num_rows: impl TryInto + Display, 114 | num_cols: impl TryInto + Display, 115 | modulus: impl Into, 116 | ) -> MatZq { 117 | let modulus = modulus.into(); 118 | let (num_rows_new, num_cols_new) = evaluate_indices(num_rows, num_cols).unwrap(); 119 | let mut matrix = MatZq::new(num_rows_new, num_cols_new, modulus.clone()); 120 | 121 | let new_string = format!("{num_rows_new} {num_cols_new} {string}"); 122 | for i in 0..num_rows_new { 123 | for j in 0..num_cols_new { 124 | matrix 125 | .set_entry( 126 | i, 127 | j, 128 | hash_to_zq_sha256(&format!("{i} {j} {new_string}"), &modulus), 129 | ) 130 | .unwrap(); 131 | } 132 | } 133 | matrix 134 | } 135 | 136 | /// Object for hashing Strings into a [`MatZq`]. 137 | /// The object fixes the modulus and the corresponding dimensions. 138 | /// 139 | /// Parameters: 140 | /// - `modulus`: Defines the range in which each entry is hashed 141 | /// - `rows`: Defines the number of rows of the hash value 142 | /// - `cols`: Defines the number of columns of the hash value 143 | /// 144 | /// Returns a [`MatZq`] as a hash for the given string. 145 | /// 146 | /// # Examples 147 | /// ``` 148 | /// use qfall_crypto::construction::hash::{HashInto, sha256::{HashMatZq, hash_to_mat_zq_sha256}}; 149 | /// use qfall_math::integer_mod_q::{Modulus, MatZq}; 150 | /// use std::str::FromStr; 151 | /// 152 | /// let modulus = Modulus::from(7); 153 | /// 154 | /// let hasher = HashMatZq { 155 | /// modulus, 156 | /// rows: 17, 157 | /// cols: 3, 158 | /// }; 159 | /// let hash_val = hasher.hash("Hello"); 160 | /// ``` 161 | #[derive(Serialize, Deserialize)] 162 | pub struct HashMatZq { 163 | pub modulus: Modulus, 164 | pub rows: i64, 165 | pub cols: i64, 166 | } 167 | 168 | impl HashInto for HashMatZq { 169 | /// Hashes a given String literal into a [`MatZq`] using sha256. 170 | /// The dimensions and the modulus is fixed by the hash object. 171 | /// 172 | /// Parameters: 173 | /// - `string`: specifies the value that is hashed 174 | /// 175 | /// Returns a [`MatZq`] as a hash for the given string. 176 | /// 177 | /// # Examples 178 | /// ``` 179 | /// use qfall_crypto::construction::hash::{HashInto, sha256::{HashMatZq, hash_to_mat_zq_sha256}}; 180 | /// use qfall_math::integer_mod_q::{Modulus, MatZq}; 181 | /// use std::str::FromStr; 182 | /// 183 | /// let modulus = Modulus::from(7); 184 | /// 185 | /// let hasher = HashMatZq { 186 | /// modulus, 187 | /// rows: 17, 188 | /// cols: 3, 189 | /// }; 190 | /// let hash_val = hasher.hash("Hello"); 191 | /// ``` 192 | fn hash(&self, m: &str) -> MatZq { 193 | hash_to_mat_zq_sha256(m, self.rows, self.cols, &self.modulus) 194 | } 195 | } 196 | 197 | /// Object for hashing Strings into a [`MatPolynomialRingZq`]. 198 | /// The object fixes the modulus and the corresponding dimensions. 199 | /// 200 | /// Parameters: 201 | /// - `modulus`: Defines the range in which each entry is hashed 202 | /// - `rows`: Defines the number of rows of the hash value 203 | /// - `cols`: Defines the number of columns of the hash value 204 | /// 205 | /// Returns a [`MatZq`] as a hash for the given string. 206 | /// 207 | /// # Examples 208 | /// ``` 209 | /// use qfall_crypto::construction::hash::{HashInto, sha256::HashMatPolynomialRingZq}; 210 | /// use qfall_crypto::sample::g_trapdoor::gadget_parameters::GadgetParametersRing; 211 | /// 212 | /// let gp = GadgetParametersRing::init_default(10, 99); 213 | /// 214 | /// let hasher = HashMatPolynomialRingZq { 215 | /// modulus: gp.modulus, 216 | /// rows: 17, 217 | /// cols: 3, 218 | /// }; 219 | /// let hash_val = hasher.hash("Hello"); 220 | /// ``` 221 | #[derive(Serialize, Deserialize)] 222 | pub struct HashMatPolynomialRingZq { 223 | pub modulus: ModulusPolynomialRingZq, 224 | pub rows: i64, 225 | pub cols: i64, 226 | } 227 | 228 | impl HashInto for HashMatPolynomialRingZq { 229 | /// Hashes a given String literal into a [`MatPolynomialRingZq`] using sha256. 230 | /// The dimensions and the modulus is fixed by the hash object. 231 | /// 232 | /// Parameters: 233 | /// - `string`: specifies the value that is hashed 234 | /// 235 | /// Returns a [`MatPolynomialRingZq`] as a hash for the given string. 236 | /// 237 | /// # Examples 238 | /// ``` 239 | /// use qfall_crypto::construction::hash::{HashInto, sha256::{HashMatPolynomialRingZq}}; 240 | /// use qfall_crypto::sample::g_trapdoor::gadget_parameters::GadgetParametersRing; 241 | /// 242 | /// let gp = GadgetParametersRing::init_default(10, 99); 243 | /// 244 | /// let hasher = HashMatPolynomialRingZq { 245 | /// modulus: gp.modulus, 246 | /// rows: 17, 247 | /// cols: 3, 248 | /// }; 249 | /// let hash_val = hasher.hash("Hello"); 250 | /// ``` 251 | fn hash(&self, m: &str) -> MatPolynomialRingZq { 252 | let highest_deg = self.modulus.get_degree(); 253 | let embedding = 254 | hash_to_mat_zq_sha256(m, self.rows * highest_deg, self.cols, self.modulus.get_q()) 255 | .get_representative_least_nonnegative_residue(); 256 | let poly_mat = MatPolyOverZ::from_coefficient_embedding((&embedding, highest_deg - 1)); 257 | MatPolynomialRingZq::from((&poly_mat, &self.modulus)) 258 | } 259 | } 260 | 261 | #[cfg(test)] 262 | mod tests_sha { 263 | use super::{hash_to_mat_zq_sha256, hash_to_zq_sha256, sha256, Z}; 264 | use qfall_math::{ 265 | integer_mod_q::{MatZq, Zq}, 266 | traits::{Distance, Pow}, 267 | }; 268 | use std::str::FromStr; 269 | 270 | /// Ensure sha256 works. 271 | #[test] 272 | fn test_sha256() { 273 | let str1 = "Hello World!"; 274 | let str2 = "qfall"; 275 | 276 | let hash1 = sha256(str1); 277 | let hash2 = sha256(str2); 278 | 279 | assert_eq!( 280 | "7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069", 281 | hash1 282 | ); 283 | assert_eq!( 284 | "eb6ed1369a670050bd04b24036e8c29144b0f6b10166dc9c8b4987a6026c715f", 285 | hash2 286 | ); 287 | } 288 | 289 | /// Ensure hashing into [`Zq`] works as intended. 290 | #[test] 291 | fn test_hash_to_zq_sha256() { 292 | let str1 = "Hello World!"; 293 | let str2 = "qfall"; 294 | 295 | let hash1 = hash_to_zq_sha256(str1, 256); 296 | let hash2 = hash_to_zq_sha256(str2, 16); 297 | 298 | assert_eq!(Zq::from((150, 256)), hash1); 299 | assert_eq!(Zq::from((12, 16)), hash2); 300 | } 301 | 302 | /// Ensure hashing into [`Zq`] hits the whole domain not just the first 256 bit. 303 | #[test] 304 | fn test_hash_to_zq_sha256_large() { 305 | let str1 = "Hello World!"; 306 | 307 | let mut large = false; 308 | for i in 0..5 { 309 | if hash_to_zq_sha256(&(i.to_string() + str1), Z::from(271).pow(100).unwrap()) 310 | .get_representative_least_nonnegative_residue() 311 | .distance(Z::ZERO) 312 | > Z::from(u64::MAX) 313 | { 314 | large = true; 315 | } 316 | } 317 | 318 | assert!(large); 319 | } 320 | 321 | /// Ensure hashing into [`MatZq`] works as intended. 322 | #[test] 323 | fn test_hash_to_mat_zq_sha256() { 324 | let str1 = "Hello World!"; 325 | let str2 = "qfall"; 326 | 327 | let hash1 = hash_to_mat_zq_sha256(str1, 2, 2, 256); 328 | let hash2 = hash_to_mat_zq_sha256(str2, 2, 2, 16); 329 | 330 | assert_eq!( 331 | MatZq::from_str("[[159, 26],[249, 141]] mod 256").unwrap(), 332 | hash1 333 | ); 334 | assert_eq!(MatZq::from_str("[[3, 12],[9, 12]] mod 16").unwrap(), hash2); 335 | } 336 | 337 | /// Ensure hashing into [`MatZq`] works as intended. 338 | #[test] 339 | #[should_panic] 340 | fn test_hash_to_mat_zq_sha256_negative_dimensions() { 341 | let str1 = "Hello World!"; 342 | 343 | let _ = hash_to_mat_zq_sha256(str1, 0, 0, 16); 344 | } 345 | } 346 | 347 | #[cfg(test)] 348 | mod hash_into_mat_polynomial_ring_zq { 349 | use super::{HashInto, HashMatPolynomialRingZq}; 350 | use crate::sample::g_trapdoor::gadget_parameters::GadgetParametersRing; 351 | use qfall_math::{integer::PolyOverZ, traits::*}; 352 | 353 | /// Ensure that the hash function maps into the correct dimension and it is also 354 | /// static, i.e. the same value is returned, when the same value is hashed. 355 | #[test] 356 | fn correct_dimensions() { 357 | let gp = GadgetParametersRing::init_default(10, 99); 358 | 359 | let hasher = HashMatPolynomialRingZq { 360 | modulus: gp.modulus, 361 | rows: 17, 362 | cols: 3, 363 | }; 364 | let hash_val = hasher.hash("Hello"); 365 | let hash_val_2 = hasher.hash("Hello"); 366 | let entry: PolyOverZ = hash_val.get_entry(0, 0).unwrap(); 367 | 368 | assert_eq!(hasher.rows, hash_val.get_num_rows()); 369 | assert_eq!(hasher.cols, hash_val.get_num_columns()); 370 | assert_eq!(hash_val, hash_val_2); 371 | assert_eq!(9, entry.get_degree()) 372 | } 373 | } 374 | -------------------------------------------------------------------------------- /src/construction/hash/sis.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Niklas Siemer 2 | // 3 | // This file is part of qFALL-crypto. 4 | // 5 | // qFALL-crypto is free software: you can redistribute it and/or modify it under 6 | // the terms of the Mozilla Public License Version 2.0 as published by the 7 | // Mozilla Foundation. See . 8 | 9 | //! This module contains an implementation of the collision-resistant 10 | //! SIS-based hash function. 11 | 12 | use qfall_math::{error::MathError, integer::Z, integer_mod_q::MatZq, traits::MatrixDimensions}; 13 | use serde::{Deserialize, Serialize}; 14 | 15 | /// This struct keeps an instance of the [`SISHash`] including 16 | /// its key and public parameters implicitly stored as `n = key.#rows()`, 17 | /// `m = key.#columns`, and `q = key.modulus`. 18 | /// 19 | /// This construction is implemented according to the description in [\[1\]](). 20 | /// 21 | /// Attributes: 22 | /// - `n`: specifies the security parameter, which is not equal to the bit-security level 23 | /// - `m`: defines the number of columns of `A` defining this SIS instance 24 | /// - `q`: specifies the modulus 25 | /// 26 | /// # Examples 27 | /// ``` 28 | /// use qfall_crypto::construction::hash::SISHash; 29 | /// use qfall_math::integer_mod_q::MatZq; 30 | /// // setup public parameters and key pair 31 | /// let hash = SISHash::gen(5, 18, 11).unwrap(); 32 | /// 33 | /// // check provable collision-resistance of hash 34 | /// assert!(hash.check_security().is_ok()); 35 | /// 36 | /// // generate something to hash 37 | /// let msg = MatZq::sample_uniform(18, 1, 11); 38 | /// 39 | /// // hash the message 40 | /// let result = hash.hash(&msg); 41 | /// ``` 42 | #[derive(Debug, Serialize, Deserialize)] 43 | pub struct SISHash { 44 | key: MatZq, // implicitly contains n = nrows, m = ncols, q = modulus 45 | } 46 | 47 | impl SISHash { 48 | /// Generates a new secret key for an [`SISHash`] instance. 49 | /// 50 | /// Parameters: 51 | /// - `n`: specifies the security parameter and number of rows 52 | /// of the uniform at random instantiated matrix `A` 53 | /// - `m`: specifies the number of columns of matrix `A` 54 | /// - `q`: specifies the modulus 55 | /// 56 | /// Returns a new instance of a [`SISHash`] function with freshly 57 | /// chosen secret key `A` of type [`MatZq`], dimensions `n x m`, 58 | /// and modulus `q`. Otherwise, a [`MathError`] is returned, if `n <= 0`. 59 | /// 60 | /// # Examples 61 | /// ``` 62 | /// use qfall_crypto::construction::hash::SISHash; 63 | /// 64 | /// let hash = SISHash::gen(5, 18, 11).unwrap(); 65 | /// ``` 66 | /// 67 | /// # Errors and Failures 68 | /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) 69 | /// if `n <= 0`. 70 | pub fn gen(n: impl Into, m: impl Into, q: impl Into) -> Result { 71 | let n: Z = n.into(); 72 | let m: Z = m.into(); 73 | let q: Z = q.into(); 74 | 75 | if n < Z::ONE { 76 | return Err(MathError::InvalidIntegerInput(String::from( 77 | "n must be chosen bigger than 0.", 78 | ))); 79 | } 80 | 81 | let mat_a = MatZq::sample_uniform(&n, &m, q); 82 | 83 | Ok(Self { key: mat_a }) 84 | } 85 | 86 | /// Checks whether the [`SISHash`] instance is provably collision-resistant. 87 | /// 88 | /// Returns an empty result if the instance is provably secure. 89 | /// Otherwise, a [`MathError`] is returned, if or `m < n log q`, 90 | /// or `q <= ⌈sqrt(n log q)⌉` as collision-resistance 91 | /// would otherwise not be ensured. 92 | /// 93 | /// # Examples 94 | /// ``` 95 | /// use qfall_crypto::construction::hash::SISHash; 96 | /// let hash = SISHash::gen(5, 18, 11).unwrap(); 97 | /// 98 | /// assert!(hash.check_security().is_ok()); 99 | /// ``` 100 | /// 101 | /// # Errors and Failures 102 | /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) 103 | /// if `m < n log q`, or `q <= ⌈sqrt(n log q)⌉` 104 | /// as collision-resistance would otherwise not be ensured. 105 | pub fn check_security(&self) -> Result<(), MathError> { 106 | let n: Z = self.key.get_num_rows().into(); 107 | let m: Z = self.key.get_num_columns().into(); 108 | let q: Z = self.key.get_mod().into(); 109 | 110 | // computed according to bullet point 3 of section 4.1.1 in Decade 111 | let m_bar = (&n * q.log(2).unwrap()).ceil(); 112 | 113 | // m >= m_bar according to bullet point 3 of section 4.1.1 in Decade 114 | if m < m_bar { 115 | return Err(MathError::InvalidIntegerInput(String::from( 116 | "m was chosen smaller than n log q, but it must be larger to satisfy the pigeonhole principle.", 117 | ))); 118 | } 119 | // q > ⌈sqrt(m_bar)⌉ according to bullet point 3 + 1 of section 4.1.1 in Decade 120 | if q <= m_bar.sqrt().ceil() { 121 | return Err(MathError::InvalidIntegerInput(String::from( 122 | "q was chosen smaller than ⌈sqrt(n log q)⌉, but it must be larger to satisfy the pigeonhole principle.", 123 | ))); 124 | } 125 | 126 | Ok(()) 127 | } 128 | 129 | /// Applies f_A to `value`, i.e. computes `A * value`. 130 | /// 131 | /// Parameters: 132 | /// - `value`: specifies an element from the domain, 133 | /// i.e. a column vector of length `m` with modulus `q` 134 | /// 135 | /// Returns the hash digest of dimension `n`. 136 | /// 137 | /// # Examples 138 | /// ``` 139 | /// use qfall_crypto::construction::hash::SISHash; 140 | /// use qfall_math::integer_mod_q::MatZq; 141 | /// use std::str::FromStr; 142 | /// let hash = SISHash::gen(1, 3, 7).unwrap(); 143 | /// let value = MatZq::from_str("[[1],[2],[3]] mod 7").unwrap(); 144 | /// 145 | /// hash.hash(&value); 146 | /// ``` 147 | /// 148 | /// # Panics ... 149 | /// - if `value` isn't a column vector. 150 | /// - if `value` has a different modulus than the [`SISHash`] instance. 151 | /// - if `value` has mismatching dimensions, i.e. the vector length isn't `m`. 152 | pub fn hash(&self, value: &MatZq) -> MatZq { 153 | if !value.is_column_vector() { 154 | panic!("The hashed value has to be a column vector!"); 155 | } 156 | 157 | &self.key * value 158 | } 159 | } 160 | 161 | #[cfg(test)] 162 | mod test_gen { 163 | use super::{SISHash, Z}; 164 | use qfall_math::traits::MatrixDimensions; 165 | 166 | /// Checks whether too small chosen `n` results in an error. 167 | #[test] 168 | fn invalid_n() { 169 | let res_0 = SISHash::gen(0, 2, 2); 170 | let res_1 = SISHash::gen(-1, 2, 2); 171 | let res_2 = SISHash::gen(i64::MIN, 2, 2); 172 | 173 | assert!(res_0.is_err()); 174 | assert!(res_1.is_err()); 175 | assert!(res_2.is_err()); 176 | } 177 | 178 | /// Checks whether too small chosen `m` results in an error in the security check. 179 | #[test] 180 | fn insecure_m() { 181 | let res_0 = SISHash::gen(1, 1, 4).unwrap(); 182 | let res_1 = SISHash::gen(2, 2, 2).unwrap(); 183 | let res_2 = SISHash::gen(4, 5, i64::MAX).unwrap(); 184 | 185 | assert!(res_0.check_security().is_err()); 186 | assert!(res_1.check_security().is_err()); 187 | assert!(res_2.check_security().is_err()); 188 | } 189 | 190 | /// Checks whether too small chosen `q` results in an error in the security check. 191 | #[test] 192 | fn insecure_q() { 193 | let res_0 = SISHash::gen(10, 50, 6).unwrap(); 194 | let res_1 = SISHash::gen(5, 50, 4).unwrap(); 195 | 196 | assert!(res_0.check_security().is_err()); 197 | assert!(res_1.check_security().is_err()); 198 | } 199 | 200 | /// Ensures that a working example returns a proper instance. 201 | #[test] 202 | fn working_example() { 203 | let hash = SISHash::gen(5, 18, 11).unwrap(); 204 | 205 | assert!(hash.check_security().is_ok()); 206 | assert_eq!(5, hash.key.get_num_rows()); 207 | assert_eq!(18, hash.key.get_num_columns()); 208 | assert_eq!(Z::from(11), Z::from(hash.key.get_mod())); 209 | } 210 | 211 | /// Ensures that the expected availability is provided. 212 | #[test] 213 | fn availability() { 214 | let _ = SISHash::gen(4i8, 4i8, 4i8); 215 | let _ = SISHash::gen(4i8, 4i16, 4i32); 216 | let _ = SISHash::gen(4u8, 4i64, 4u16); 217 | let _ = SISHash::gen(4u64, 4u32, 4); 218 | let _ = SISHash::gen(Z::ONE, 4i64, 4u16); 219 | let _ = SISHash::gen(Z::ONE, Z::from(2), Z::from(2)); 220 | } 221 | } 222 | 223 | #[cfg(test)] 224 | mod test_hash { 225 | use super::{MatZq, SISHash, Z}; 226 | use qfall_math::traits::MatrixDimensions; 227 | 228 | /// Ensures that non-column-vectors result in a panic. 229 | #[should_panic] 230 | #[test] 231 | fn not_column_vec() { 232 | let hash = SISHash::gen(1, 3, 7).unwrap(); 233 | let value = MatZq::new(1, 3, 7); 234 | 235 | hash.hash(&value); 236 | } 237 | 238 | /// Ensures that mismatching dimensions result in a panic. 239 | #[should_panic] 240 | #[test] 241 | fn mismatching_dimensions() { 242 | let hash = SISHash::gen(1, 3, 7).unwrap(); 243 | let value = MatZq::new(4, 1, 7); 244 | 245 | hash.hash(&value); 246 | } 247 | 248 | /// Ensures that mismatching moduli result in a panic. 249 | #[should_panic] 250 | #[test] 251 | fn mismatching_moduli() { 252 | let hash = SISHash::gen(1, 3, 7).unwrap(); 253 | let value = MatZq::new(3, 1, 8); 254 | 255 | hash.hash(&value); 256 | } 257 | 258 | /// Ensures that a working example returns a proper instance. 259 | #[test] 260 | fn working_example() { 261 | let hash = SISHash::gen(5, 18, 11).unwrap(); 262 | let value = MatZq::new(18, 1, 11); 263 | 264 | let res = hash.hash(&value); 265 | 266 | assert_eq!(5, res.get_num_rows()); 267 | assert_eq!(1, res.get_num_columns()); 268 | assert_eq!(Z::from(11), Z::from(res.get_mod())); 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /src/construction/identity_based_encryption.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Phil Milewski 2 | // 3 | // This file is part of qFALL-crypto. 4 | // 5 | // qFALL-crypto is free software: you can redistribute it and/or modify it under 6 | // the terms of the Mozilla Public License Version 2.0 as published by the 7 | // Mozilla Foundation. See . 8 | 9 | //! This module provides the trait a struct should implement if it is an 10 | //! instance of a identity based public key encryption scheme. Furthermore, 11 | //! it contains cryptographic schemes implementing the [`IBEScheme`] trait. 12 | //! 13 | //! The main references are listed in the following 14 | //! and will be further referenced in submodules by these numbers: 15 | //! - \[1\] Gentry, Craig and Peikert, Chris and Vaikuntanathan, Vinod (2008). 16 | //! Trapdoors for hard lattices and new cryptographic constructions. 17 | //! In: Proceedings of the fortieth annual ACM symposium on Theory of computing. 18 | //! 19 | //! - \[2\] Regev, Oded (2009). 20 | //! On lattices, learning with errors, random linear codes, and cryptography. 21 | //! In: Journal of the ACM 6. 22 | //! 23 | 24 | mod dual_regev_ibe; 25 | 26 | pub use dual_regev_ibe::DualRegevIBE; 27 | use qfall_math::integer::Z; 28 | 29 | /// This trait should be implemented by every identity-based encryption scheme. 30 | /// It offers a simple interface to use and implements the main functions supported by 31 | /// IBEs. 32 | pub trait IBEScheme { 33 | type MasterPublicKey; 34 | type MasterSecretKey; 35 | type SecretKey; 36 | type Cipher; 37 | type Identity; 38 | 39 | /// Generates a master public key pair `(mpk, msk)` suitable for the specific identity-based encryption scheme (IBE). 40 | /// 41 | /// Returns a tuple `(mpk, msk)` consisting of [`Self::MasterPublicKey`] and [`Self::MasterSecretKey`]. 42 | fn setup(&self) -> (Self::MasterPublicKey, Self::MasterSecretKey); 43 | 44 | /// Extracts a secret key corresponding to the specified `identity` using the master secret key `msk`. 45 | /// 46 | /// Parameters: 47 | /// - `master_pk`: specifies the master public key 48 | /// - `master_sk`: specifies the master secret key used for extracting the secret of `identity` 49 | /// - `identity`: specifies the identity for which the secret key should be extracted 50 | /// 51 | /// Returns a secret key for the specified `identity` as a [`Self::SecretKey`]. 52 | fn extract( 53 | &mut self, 54 | master_pk: &Self::MasterPublicKey, 55 | master_sk: &Self::MasterSecretKey, 56 | identity: &Self::Identity, 57 | ) -> Self::SecretKey; 58 | 59 | /// Encrypts the provided `message` using the master public key `mpk` and `identity` of the recipient. 60 | /// 61 | /// Parameters: 62 | /// - `master_pk`: specifies the master public key used for this IBE 63 | /// - `identity`: specifies the recipient that should be able to decrypt the encrypted message 64 | /// - `message`: specifies the message to be encrypted 65 | /// 66 | /// Returns the encryption of `message` as a [`Self::Cipher`] instance. 67 | fn enc( 68 | &self, 69 | master_pk: &Self::MasterPublicKey, 70 | identity: &Self::Identity, 71 | message: impl Into, 72 | ) -> Self::Cipher; 73 | 74 | /// Decrypts the provided `cipher` using the extracted secret key `sk`. 75 | /// 76 | /// Parameters: 77 | /// - `sk`: specifies the extracted secret key used for decryption 78 | /// - `cipher`: specifies the ciphertext to be decrypted 79 | /// 80 | /// Returns the decryption of `cipher` as a [`Z`] instance. 81 | fn dec(&self, sk: &Self::SecretKey, cipher: &Self::Cipher) -> Z; 82 | } 83 | -------------------------------------------------------------------------------- /src/construction/pk_encryption.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Niklas Siemer 2 | // 3 | // This file is part of qFALL-crypto. 4 | // 5 | // qFALL-crypto is free software: you can redistribute it and/or modify it under 6 | // the terms of the Mozilla Public License Version 2.0 as published by the 7 | // Mozilla Foundation. See . 8 | 9 | //! This module provides the trait a struct should implement if it is an 10 | //! instance of a public key encryption scheme. Furthermore, it contains 11 | //! cryptographic schemes implementing the [`PKEncryptionScheme`] or [`PKEncryptionSchemeMut`] trait. 12 | //! 13 | //! The main references are listed in the following 14 | //! and will be further referenced in submodules by these numbers: 15 | //! - \[1\] Peikert, Chris (2016). 16 | //! A decade of lattice cryptography. 17 | //! In: Theoretical Computer Science 10.4. 18 | //! 19 | //! - \[2\] Gentry, Craig and Peikert, Chris and Vaikuntanathan, Vinod (2008). 20 | //! Trapdoors for hard lattices and new cryptographic constructions. 21 | //! In: Proceedings of the fortieth annual ACM symposium on Theory of computing. 22 | //! 23 | //! - \[3\] Regev, Oded (2009). 24 | //! On lattices, learning with errors, random linear codes, and cryptography. 25 | //! In: Journal of the ACM 6. 26 | //! 27 | //! - \[4\] Lindner, R., and C. Peikert (2011). 28 | //! Better key sizes (and attacks) for LWE-based encryption. 29 | //! In: Topics in Cryptology - RSA Conference 2011, Springer. 30 | //! 31 | //! - \[5\] Canetti, R., Halevi, S., and Katz, J. (2004). 32 | //! Chosen-ciphertext security from identity-based encryption. 33 | //! In: Advances in Cryptology - EUROCRYPT 2004. 34 | //! 35 | 36 | mod ccs_from_ibe; 37 | mod dual_regev; 38 | mod dual_regev_discrete_gauss; 39 | mod lpr; 40 | mod regev; 41 | mod regev_discrete_gauss; 42 | mod ring_lpr; 43 | 44 | pub use ccs_from_ibe::CCSfromIBE; 45 | pub use dual_regev::DualRegev; 46 | pub use dual_regev_discrete_gauss::DualRegevWithDiscreteGaussianRegularity; 47 | pub use lpr::LPR; 48 | use qfall_math::integer::Z; 49 | pub use regev::Regev; 50 | pub use regev_discrete_gauss::RegevWithDiscreteGaussianRegularity; 51 | pub use ring_lpr::RingLPR; 52 | 53 | /// This trait should be implemented by every public key encryption scheme. 54 | /// It offers a simple interface to use and implement PKEs. 55 | pub trait PKEncryptionScheme { 56 | type PublicKey; 57 | type SecretKey; 58 | type Cipher; 59 | 60 | /// Generates a public key pair `(pk, sk)` suitable for the specific scheme. 61 | /// 62 | /// Returns a tuple `(pk, sk)` consisting of [`Self::PublicKey`] and [`Self::SecretKey`]. 63 | fn gen(&self) -> (Self::PublicKey, Self::SecretKey); 64 | 65 | /// Encrypts the provided `message` using the public key `pk`. 66 | /// 67 | /// Parameters: 68 | /// - `pk`: specifies the public key used for encryption 69 | /// - `message`: specifies the message to be encrypted 70 | /// 71 | /// Returns the encryption of `message` as a [`Self::Cipher`] instance. 72 | fn enc(&self, pk: &Self::PublicKey, message: impl Into) -> Self::Cipher; 73 | 74 | /// Decrypts the provided `cipher` using the secret key `sk`. 75 | /// 76 | /// Parameters: 77 | /// - `sk`: specifies the secret key used for decryption 78 | /// - `cipher`: specifies the ciphertext to be decrypted 79 | /// 80 | /// Returns the decryption of `cipher` as a [`Z`] instance. 81 | fn dec(&self, sk: &Self::SecretKey, cipher: &Self::Cipher) -> Z; 82 | } 83 | 84 | /// This trait just exists s.t. we can pass `self` in as mutable for more advanced constructions, which use a storage. 85 | /// Otherwise, it does exactly the same as [`PKEncryptionScheme`]. 86 | pub trait PKEncryptionSchemeMut { 87 | type PublicKey; 88 | type SecretKey; 89 | type Cipher; 90 | 91 | /// Generates a public key pair `(pk, sk)` suitable for the specific scheme. 92 | /// 93 | /// Returns a tuple `(pk, sk)` consisting of [`Self::PublicKey`] and [`Self::SecretKey`]. 94 | fn gen(&mut self) -> (Self::PublicKey, Self::SecretKey); 95 | 96 | /// Encrypts the provided `message` using the public key `pk`. 97 | /// 98 | /// Parameters: 99 | /// - `pk`: specifies the public key used for encryption 100 | /// - `message`: specifies the message to be encrypted 101 | /// 102 | /// Returns the encryption of `message` as a [`Self::Cipher`] instance. 103 | fn enc(&mut self, pk: &Self::PublicKey, message: impl Into) -> Self::Cipher; 104 | 105 | /// Decrypts the provided `cipher` using the secret key `sk`. 106 | /// 107 | /// Parameters: 108 | /// - `sk`: specifies the secret key used for decryption 109 | /// - `cipher`: specifies the ciphertext to be decrypted 110 | /// 111 | /// Returns the decryption of `cipher` as a [`Z`] instance. 112 | fn dec(&mut self, sk: &Self::SecretKey, cipher: &Self::Cipher) -> Z; 113 | } 114 | 115 | /// This trait generically implements multi-bit encryption 116 | /// for any scheme implementing the [`PKEncryptionScheme`] trait. 117 | /// 118 | /// It splits the given ciphertext up into its bits and 119 | /// stores the individual encrypted bits as a vector of ciphertexts. 120 | pub trait GenericMultiBitEncryption: PKEncryptionScheme { 121 | /// Encrypts multiple bits by appending several encryptions of single bits. 122 | /// The order of single ciphers is `[c0, c1, ..., cn]`, where `c0` is the least significant bit. 123 | /// Negative values are not allowed. Hence, the absolute value is being encrypted. 124 | /// 125 | /// Parameters: 126 | /// - `pk`: specifies the public key 127 | /// - `message`: specifies the message that should be encryted 128 | /// 129 | /// Returns a cipher of type [`Vec`] containing [`PKEncryptionScheme::Cipher`]. 130 | fn enc_multiple_bits(&self, pk: &Self::PublicKey, message: impl Into) -> Vec { 131 | let message: Z = message.into().abs(); 132 | 133 | let bits = message.to_bits(); 134 | let mut out = vec![]; 135 | for bit in bits { 136 | if bit { 137 | out.push(self.enc(pk, Z::ONE)); 138 | } else { 139 | out.push(self.enc(pk, Z::ZERO)); 140 | } 141 | } 142 | 143 | out 144 | } 145 | 146 | /// Decrypts a multiple bit ciphertext. 147 | /// 148 | /// Parameters: 149 | /// - `sk`: specifies the secret key used for decryption 150 | /// - `cipher`: specifies a slice of ciphers containing several [`PKEncryptionScheme::Cipher`] instances 151 | /// to be decrypted 152 | /// 153 | /// Returns the decryption of `cipher` as a [`Z`] instance. 154 | fn dec_multiple_bits(&self, sk: &Self::SecretKey, cipher: &[Self::Cipher]) -> Z { 155 | let mut bits = vec![]; 156 | 157 | for item in cipher { 158 | if self.dec(sk, item) == Z::ZERO { 159 | bits.push(false); 160 | } else { 161 | bits.push(true); 162 | } 163 | } 164 | 165 | Z::from_bits(&bits) 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/construction/pk_encryption/ccs_from_ibe.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Niklas Siemer 2 | // 3 | // This file is part of qFALL-crypto. 4 | // 5 | // qFALL-crypto is free software: you can redistribute it and/or modify it under 6 | // the terms of the Mozilla Public License Version 2.0 as published by the 7 | // Mozilla Foundation. See . 8 | 9 | //! This module contains a general implementation of an IND-CCA secure 10 | //! public key encryption scheme constructed 11 | //! via an [`IBEScheme`] and a [`SignatureScheme`]. 12 | 13 | use super::PKEncryptionSchemeMut; 14 | use crate::construction::{identity_based_encryption::IBEScheme, signature::SignatureScheme}; 15 | use qfall_math::integer::Z; 16 | use serde::{Deserialize, Serialize}; 17 | 18 | pub mod dual_regev_ibe_pfdh; 19 | 20 | /// This struct manages and stores the public parameters of an [`CCSfromIBE`] 21 | /// public key encryption construction based on [\[5\]](). 22 | /// 23 | /// Attributes: 24 | /// - `ibe`: specifies the IBE scheme used in this construction 25 | /// - `signature`: specifies the signature scheme used in this construction 26 | /// 27 | /// # Examples 28 | /// ``` 29 | /// use qfall_crypto::construction::pk_encryption::{CCSfromIBE, PKEncryptionSchemeMut}; 30 | /// use qfall_math::integer::Z; 31 | /// let mut scheme = CCSfromIBE::init_dr_pfdh_from_n(4); 32 | /// 33 | /// let (pk, sk) = scheme.gen(); 34 | /// let cipher = scheme.enc(&pk, 0); 35 | /// let m = scheme.dec(&sk, &cipher); 36 | /// 37 | /// assert_eq!(Z::ZERO, m); 38 | /// ``` 39 | #[derive(Serialize, Deserialize, Clone)] 40 | pub struct CCSfromIBE 41 | where 42 | IBE::Cipher: ToString, 43 | { 44 | pub ibe: IBE, 45 | pub signature: Signature, 46 | } 47 | 48 | impl PKEncryptionSchemeMut for CCSfromIBE 49 | where 50 | IBE: IBEScheme, 51 | Signature: SignatureScheme, 52 | IBE::Cipher: ToString, 53 | IBE::MasterPublicKey: Clone, 54 | Signature::PublicKey: Into + Clone, 55 | { 56 | type Cipher = (Signature::PublicKey, IBE::Cipher, Signature::Signature); 57 | type PublicKey = IBE::MasterPublicKey; 58 | type SecretKey = (IBE::MasterPublicKey, IBE::MasterSecretKey); 59 | 60 | /// Generates a (pk, sk) pair for the CCS construction 61 | /// by following these steps: 62 | /// - (mpk, msk) = ibe.setup() 63 | /// 64 | /// Then, `pk = mpk` and `sk = (mpk, msk)` are returned. 65 | /// 66 | /// # Examples 67 | /// ``` 68 | /// use qfall_crypto::construction::pk_encryption::{CCSfromIBE, PKEncryptionSchemeMut}; 69 | /// let mut scheme = CCSfromIBE::init_dr_pfdh_from_n(4); 70 | /// 71 | /// let (pk, sk) = scheme.gen(); 72 | /// ``` 73 | fn gen(&mut self) -> (Self::PublicKey, Self::SecretKey) { 74 | let (pk, sk) = self.ibe.setup(); 75 | (pk.clone(), (pk, sk)) 76 | } 77 | 78 | /// Generates an encryption of `message` for the provided public key by following these steps: 79 | /// - (vrfy_key, sign_key) = signature.gen() 80 | /// - c = ibe.enc(mpk, vrfy_key, message), i.e. encrypt `message` with respect to identity `vrfy_key` 81 | /// - sigma = signature.sign(c, sign_key, vrfy_key), i.e. sign message `c` 82 | /// 83 | /// Then, the ciphertext `(vrfy_key, c, sigma)` is returned. 84 | /// 85 | /// Parameters: 86 | /// - `pk`: specifies the public key `pk = A` 87 | /// - `message`: specifies the message that should be encrypted 88 | /// 89 | /// Returns a cipher consisting of a tuple `cipher = (vrfy_key, c, sigma)`. 90 | /// 91 | /// # Examples 92 | /// ``` 93 | /// use qfall_crypto::construction::pk_encryption::{CCSfromIBE, PKEncryptionSchemeMut}; 94 | /// let mut scheme = CCSfromIBE::init_dr_pfdh_from_n(4); 95 | /// 96 | /// let (pk, sk) = scheme.gen(); 97 | /// let cipher = scheme.enc(&pk, 1); 98 | /// ``` 99 | fn enc(&mut self, pk: &Self::PublicKey, message: impl Into) -> Self::Cipher { 100 | let (vrfy_key, sign_key) = self.signature.gen(); 101 | 102 | let c = self.ibe.enc(pk, &vrfy_key.clone().into(), message); 103 | let sigma = self.signature.sign(c.to_string(), &sign_key, &vrfy_key); 104 | (vrfy_key, c, sigma) 105 | } 106 | 107 | /// Decrypts the provided `cipher` using the secret key `sk` by following these steps: 108 | /// - if signature.vrfy(c, sigma, vrfy_key) is not successful, output -1, otherwise proceed 109 | /// - secret_key = ibe.extract(mpk, msk, vrfy_key), i.e. extract the secret key for identity `vrfy_key` 110 | /// - ibe.dec(secret_key, c) 111 | /// 112 | /// Then, the resulting decryption is returned. 113 | /// 114 | /// Parameters: 115 | /// - `sk`: specifies the secret key `sk = (mpk, msk)` 116 | /// - `cipher`: specifies the cipher containing `cipher = (vrfy_key, c, sigma)` 117 | /// 118 | /// Returns the decryption of `cipher` as a [`Z`] instance. 119 | /// 120 | /// # Examples 121 | /// ``` 122 | /// use qfall_crypto::construction::pk_encryption::{CCSfromIBE, PKEncryptionSchemeMut}; 123 | /// use qfall_math::integer::Z; 124 | /// let mut scheme = CCSfromIBE::init_dr_pfdh_from_n(4); 125 | /// 126 | /// let (pk, sk) = scheme.gen(); 127 | /// let cipher = scheme.enc(&pk, 1); 128 | /// let m = scheme.dec(&sk, &cipher); 129 | /// 130 | /// assert_eq!(Z::ONE, m); 131 | /// ``` 132 | fn dec(&mut self, sk: &Self::SecretKey, cipher: &Self::Cipher) -> Z { 133 | if !self 134 | .signature 135 | .vfy(cipher.1.to_string(), &cipher.2, &cipher.0) 136 | { 137 | return Z::MINUS_ONE; 138 | } 139 | 140 | let secret = self.ibe.extract(&sk.0, &sk.1, &cipher.0.clone().into()); 141 | self.ibe.dec(&secret, &cipher.1) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/construction/pk_encryption/ccs_from_ibe/dual_regev_ibe_pfdh.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Niklas Siemer 2 | // 3 | // This file is part of qFALL-crypto. 4 | // 5 | // qFALL-crypto is free software: you can redistribute it and/or modify it under 6 | // the terms of the Mozilla Public License Version 2.0 as published by the 7 | // Mozilla Foundation. See . 8 | 9 | //! A classical implementation of the [`CCSfromIBE`] scheme using 10 | //! the [`DualRegevIBE`] and [`PFDH`]. 11 | 12 | use super::CCSfromIBE; 13 | use crate::{ 14 | construction::{ 15 | hash::sha256::HashMatZq, identity_based_encryption::DualRegevIBE, signature::PFDH, 16 | }, 17 | primitive::psf::PSFGPV, 18 | }; 19 | use qfall_math::{ 20 | integer::{MatZ, Z}, 21 | integer_mod_q::{MatZq, Modulus}, 22 | rational::{MatQ, Q}, 23 | }; 24 | 25 | impl CCSfromIBE> { 26 | /// Initializes a [`CCSfromIBE`] PK encryption scheme from a [`DualRegevIBE`] and a [`PFDH`] signature. 27 | /// 28 | /// Parameters: 29 | /// - `n`: specifies the security parameter 30 | /// - `q`: specifies the modulus 31 | /// - `r`: specifies the Gaussian parameter used for the [`PSFGPV`] for the [`PFDH`] 32 | /// - `randomness_length`: specifies the number of bits added to the message before signing 33 | /// - `alpha`: specifies the Gaussian parameter used for encryption in 34 | /// [`DualRegev`](crate::construction::pk_encryption::DualRegev) in the [`DualRegevIBE`] 35 | /// 36 | /// Returns an explicit implementation of an IND-CCA-secure public key encryption scheme. 37 | /// 38 | /// # Example 39 | /// ``` 40 | /// use qfall_crypto::construction::pk_encryption::CCSfromIBE; 41 | /// 42 | /// let mut scheme = CCSfromIBE::init_dr_pfdh(4, 13933, 4, 10.77, 0.0021); 43 | /// ``` 44 | /// 45 | /// # Panics ... 46 | /// - if `q <= 1`. 47 | /// - if `n < 1` or `n` does not fit into an [`i64`]. 48 | pub fn init_dr_pfdh( 49 | n: impl Into, // security parameter 50 | q: impl Into, 51 | randomness_length: impl Into, // added to the message before signing 52 | r: impl Into, // Gaussian parameter for PSF 53 | alpha: impl Into, // Gaussian parameter for Dual Regev Encryption 54 | ) -> Self { 55 | let n = n.into(); 56 | let q = q.into(); 57 | let r = r.into(); 58 | 59 | let dr_ibe = DualRegevIBE::new(&n, &q, &r, alpha); 60 | let pfdh = PFDH::init_gpv(n, q, r, randomness_length); 61 | 62 | Self { 63 | ibe: dr_ibe, 64 | signature: pfdh, 65 | } 66 | } 67 | 68 | /// Initializes a [`CCSfromIBE`] PK encryption scheme from a [`DualRegevIBE`] and a [`PFDH`] signature 69 | /// from a given `n > 0`. 70 | /// 71 | /// Parameters: 72 | /// - `n`: specifies the security parameter 73 | /// 74 | /// Returns an explicit implementation of an IND-CCA-secure public key encryption scheme 75 | /// chosen with appropriate parameters for given `n`.. 76 | /// 77 | /// # Example 78 | /// ``` 79 | /// use qfall_crypto::construction::pk_encryption::CCSfromIBE; 80 | /// 81 | /// let mut scheme = CCSfromIBE::init_dr_pfdh_from_n(4); 82 | /// ``` 83 | /// 84 | /// # Panics ... 85 | /// - if `n < 4` or `n` does not fit into an [`i64`]. 86 | pub fn init_dr_pfdh_from_n(n: impl Into) -> Self { 87 | let n = n.into(); 88 | assert!( 89 | n > 3, 90 | "n needs to be chosen larger than 3 for this function to work properly." 91 | ); 92 | 93 | let ibe = DualRegevIBE::new_from_n(&n); 94 | let pfdh = PFDH::init_gpv(&n, &ibe.dual_regev.q, &ibe.psf.s, &n); 95 | 96 | Self { 97 | ibe, 98 | signature: pfdh, 99 | } 100 | } 101 | } 102 | 103 | #[cfg(test)] 104 | mod test_ccs_from_ibe { 105 | use super::CCSfromIBE; 106 | use crate::construction::pk_encryption::PKEncryptionSchemeMut; 107 | use qfall_math::integer::Z; 108 | 109 | /// Checks whether the full-cycle of gen, enc, dec works properly 110 | /// for message 0 and small n. 111 | #[test] 112 | fn cycle_zero() { 113 | let msg = Z::ZERO; 114 | let mut scheme = CCSfromIBE::init_dr_pfdh_from_n(4); 115 | 116 | let (pk, sk) = scheme.gen(); 117 | let cipher = scheme.enc(&pk, &msg); 118 | let m = scheme.dec(&sk, &cipher); 119 | assert_eq!(msg, m); 120 | } 121 | 122 | /// Checks whether the full-cycle of gen, enc, dec works properly 123 | /// for message 1 and small n. 124 | #[test] 125 | fn cycle_one() { 126 | let msg = Z::ONE; 127 | let mut scheme = CCSfromIBE::init_dr_pfdh_from_n(4); 128 | 129 | let (pk, sk) = scheme.gen(); 130 | let cipher = scheme.enc(&pk, &msg); 131 | let m = scheme.dec(&sk, &cipher); 132 | assert_eq!(msg, m); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/construction/signature.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Marcel Luca Schmidt, Marvin Beckmann 2 | // 3 | // This file is part of qFALL-crypto. 4 | // 5 | // qFALL-crypto is free software: you can redistribute it and/or modify it under 6 | // the terms of the Mozilla Public License Version 2.0 as published by the 7 | // Mozilla Foundation. See . 8 | 9 | //! This module provides the trait a struct should implement if it is an 10 | //! instance of a signature scheme. Furthermore, it contains cryptographic signatures 11 | //! implementing the [`SignatureScheme`] trait. 12 | //! 13 | //! - \[1\] Gentry, Craig, Chris Peikert, and Vinod Vaikuntanathan. 14 | //! "Trapdoors for hard lattices and new cryptographic constructions." 15 | //! Proceedings of the fortieth annual ACM symposium on Theory of computing. 2008. 16 | //! 17 | 18 | mod fdh; 19 | mod pfdh; 20 | 21 | pub use fdh::FDH; 22 | pub use pfdh::PFDH; 23 | 24 | /// This trait should be implemented by every signature scheme. 25 | /// It captures the essential functionalities each signature scheme has to support. 26 | /// 27 | /// Note: The gen does not take in the parameter `1^n`, as this is a public parameter, 28 | /// which shall be defined by the struct implementing this trait. 29 | pub trait SignatureScheme { 30 | /// The type of the secret key. 31 | type SecretKey; 32 | /// The type of the public key. 33 | type PublicKey; 34 | /// The type of the signature. 35 | type Signature; 36 | 37 | /// Generates a public key and a secret key from the attributes the 38 | /// struct has, which implements this trait. 39 | /// 40 | /// Returns the public key and the secret key. 41 | fn gen(&mut self) -> (Self::PublicKey, Self::SecretKey); 42 | 43 | /// Signs a message using the secret key (and potentially the public key). 44 | /// 45 | /// Returns the resulting signature. 46 | fn sign(&mut self, m: String, sk: &Self::SecretKey, pk: &Self::PublicKey) -> Self::Signature; 47 | 48 | /// Verifies that a signature is valid for a message by using the public key. 49 | /// 50 | /// Returns the result of the verification as a boolean. 51 | fn vfy(&self, m: String, sigma: &Self::Signature, pk: &Self::PublicKey) -> bool; 52 | } 53 | -------------------------------------------------------------------------------- /src/construction/signature/fdh.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Marvin Beckmann 2 | // 3 | // This file is part of qFALL-crypto. 4 | // 5 | // qFALL-crypto is free software: you can redistribute it and/or modify it under 6 | // the terms of the Mozilla Public License Version 2.0 as published by the 7 | // Mozilla Foundation. See . 8 | 9 | //! This Module contains a general implementation of the [`FDH`] scheme, 10 | //! which only has to be instantiated with a corresponding PSF, a storage and 11 | //! a corresponding hash function. 12 | //! 13 | //! Implementation of a [`FDH`]-signature scheme are thereby fairly easy, 14 | //! see [`FDH::init_gpv`] that works with every PSF and a corresponding hash function 15 | 16 | use super::SignatureScheme; 17 | use crate::{construction::hash::HashInto, primitive::psf::PSF}; 18 | use serde::{Deserialize, Serialize}; 19 | use std::{collections::HashMap, marker::PhantomData}; 20 | 21 | pub mod gpv; 22 | pub mod gpv_ring; 23 | pub mod serialize; 24 | 25 | /// This struct captures the general definition of a hash-then-sign signature scheme 26 | /// that uses a hash function as in [\[1\]]() and a PSF. 27 | /// An explicit instantiation for defined types makes understanding this struct much 28 | /// easier, compare [`FDH::init_gpv`]. 29 | /// This signature scheme uses a storage, so it is stateful. 30 | /// 31 | /// Implementing a function for a specific set of types(replacing the generic types) 32 | /// allows for easy implementation of the signature scheme. Any PSF and a corresponding 33 | /// hash-function can be directly translated to an implementation of this signature 34 | /// scheme. 35 | /// 36 | /// Attributes 37 | /// - `psf`: The PSF which has to implement the [`PSF`] trait and must also be 38 | /// (de-)serializable. 39 | /// - `storage`: A Hashmap that safes all previously signed messages and their signature 40 | /// - `hash`: The hash-function which has to map a string into the correct domain 41 | /// 42 | /// # Example 43 | /// ## Signature Scheme from [`PSFGPV`](crate::primitive::psf::PSFGPV) 44 | /// ``` 45 | /// use qfall_crypto::construction::signature::{FDH, SignatureScheme}; 46 | /// 47 | /// let mut fdh = FDH::init_gpv(4, 113, 17); 48 | /// 49 | /// let m = "Hello World!"; 50 | /// 51 | /// let (pk, sk) = fdh.gen(); 52 | /// let sigma = fdh.sign(m.to_owned(), &sk, &pk); 53 | /// 54 | /// assert!(fdh.vfy(m.to_owned(), &sigma, &pk)); 55 | /// ``` 56 | #[derive(Serialize)] 57 | pub struct FDH< 58 | A, 59 | Trapdoor, 60 | Domain: Serialize + for<'a> Deserialize<'a>, 61 | Range, 62 | T: PSF + Serialize + for<'a> Deserialize<'a>, 63 | Hash: HashInto + Serialize + for<'a> Deserialize<'a>, 64 | > { 65 | pub psf: Box, 66 | pub storage: HashMap, 67 | pub hash: Box, 68 | 69 | // The parameters below can be ignored, they are just there for generic usage 70 | #[serde(skip_serializing)] 71 | pub _a_type: PhantomData, 72 | #[serde(skip_serializing)] 73 | pub _trapdoor_type: PhantomData, 74 | #[serde(skip_serializing)] 75 | pub _range_type: PhantomData, 76 | } 77 | 78 | impl SignatureScheme 79 | for FDH 80 | where 81 | Domain: Clone + Serialize + for<'a> Deserialize<'a>, 82 | Range: PartialEq, 83 | T: PSF + Serialize + for<'a> Deserialize<'a>, 84 | Hash: HashInto + Serialize + for<'a> Deserialize<'a>, 85 | { 86 | type SecretKey = Trapdoor; 87 | type PublicKey = A; 88 | type Signature = Domain; 89 | 90 | /// Generates a trapdoor by calling the `trap_gen` of the psf 91 | fn gen(&mut self) -> (Self::PublicKey, Self::SecretKey) { 92 | self.psf.trap_gen() 93 | } 94 | 95 | /// Firstly checks if the message has been signed before, and if, return that 96 | /// signature, else it continues. 97 | /// It hashes the message into the domain and then computes a signature using 98 | /// `samp_p` from the psf with the trapdoor. 99 | fn sign(&mut self, m: String, sk: &Self::SecretKey, pk: &Self::PublicKey) -> Self::Signature { 100 | // check if it is in the HashMap 101 | if let Some(sigma) = self.storage.get(&m) { 102 | return sigma.clone(); 103 | } 104 | 105 | let u = (self.hash).hash(&m); 106 | let signature = self.psf.samp_p(pk, sk, &u); 107 | 108 | // insert signature in HashMap 109 | self.storage.insert(m, signature.clone()); 110 | signature 111 | } 112 | 113 | /// Checks if a signature is firstly within D_n, and then checks if 114 | /// the signature is actually a valid preimage under `fa` of `hash(m)`. 115 | fn vfy(&self, m: String, sigma: &Self::Signature, pk: &Self::PublicKey) -> bool { 116 | if !self.psf.check_domain(sigma) { 117 | return false; 118 | } 119 | 120 | let u = (self.hash).hash(&m); 121 | 122 | self.psf.f_a(pk, sigma) == u 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/construction/signature/fdh/gpv.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Marvin Beckmann 2 | // 3 | // This file is part of qFALL-crypto. 4 | // 5 | // qFALL-crypto is free software: you can redistribute it and/or modify it under 6 | // the terms of the Mozilla Public License Version 2.0 as published by the 7 | // Mozilla Foundation. See . 8 | 9 | //! A classical implementation of the [`FDH`] scheme using the [`PSFGPV`] 10 | //! according to [\[1\]](<../index.html#:~:text=[1]>). 11 | 12 | use super::FDH; 13 | use crate::{ 14 | construction::hash::sha256::HashMatZq, primitive::psf::PSFGPV, 15 | sample::g_trapdoor::gadget_parameters::GadgetParameters, 16 | }; 17 | use qfall_math::{ 18 | integer::{MatZ, Z}, 19 | integer_mod_q::{MatZq, Modulus}, 20 | rational::{MatQ, Q}, 21 | }; 22 | use std::{collections::HashMap, marker::PhantomData}; 23 | 24 | impl FDH { 25 | /// Initializes an FDH signature scheme from a [`PSFGPV`]. 26 | /// 27 | /// This function corresponds to an implementation of an FDH-signature 28 | /// scheme with the explicit PSF [`PSFGPV`] which is generated using 29 | /// the default of [`GadgetParameters`]. 30 | /// 31 | /// Parameters: 32 | /// - `n`: The security parameter 33 | /// - `q`: The modulus used for the G-Trapdoors 34 | /// - `s`: The Gaussian parameter with which is sampled 35 | /// 36 | /// Returns an explicit implementation of a FDH-signature scheme. 37 | /// 38 | /// # Example 39 | /// ``` 40 | /// use qfall_crypto::construction::signature::{FDH, SignatureScheme}; 41 | /// 42 | /// let m = "Hello World!"; 43 | /// 44 | /// let mut fdh = FDH::init_gpv(4, 113, 17); 45 | /// let (pk, sk) = fdh.gen(); 46 | /// 47 | /// let sigma = fdh.sign(m.to_string(), &sk, &pk); 48 | /// 49 | /// assert!(fdh.vfy(m.to_string(), &sigma, &pk)); 50 | /// ``` 51 | /// 52 | /// # Panics ... 53 | /// - if `q <= 1`. 54 | pub fn init_gpv(n: impl Into, q: impl Into, s: impl Into) -> Self { 55 | let n = n.into(); 56 | let n_i64 = i64::try_from(&n).unwrap(); 57 | let q = q.into(); 58 | let psf = PSFGPV { 59 | gp: GadgetParameters::init_default(&n, &q), 60 | s: s.into(), 61 | }; 62 | Self { 63 | psf: Box::new(psf), 64 | storage: HashMap::new(), 65 | hash: Box::new(HashMatZq { 66 | modulus: q, 67 | rows: n_i64, 68 | cols: 1, 69 | }), 70 | _a_type: PhantomData, 71 | _trapdoor_type: PhantomData, 72 | _range_type: PhantomData, 73 | } 74 | } 75 | } 76 | 77 | #[cfg(test)] 78 | mod test_fdh { 79 | use super::{HashMatZq, FDH, PSFGPV}; 80 | use crate::construction::signature::SignatureScheme; 81 | use qfall_math::{ 82 | integer::{MatZ, Z}, 83 | integer_mod_q::MatZq, 84 | rational::{MatQ, Q}, 85 | traits::Pow, 86 | }; 87 | 88 | /// Ensure that the generated signature is valid. 89 | #[test] 90 | fn ensure_valid_signature_is_generated() { 91 | let n = Z::from(4); 92 | let k = Z::from(6); 93 | // `s >= ||\tilde short_base|| * omega(sqrt{log m})`, 94 | // here `log(2*n*k) = omega(sqrt{log m}))` (Theorem 4.1 - GPV08) 95 | let s: Q = ((&n * &k).sqrt() + 1) * Q::from(2) * (Z::from(2) * &n * &k).log(2).unwrap(); 96 | let q = Z::from(2).pow(&k).unwrap(); 97 | 98 | let mut fdh = FDH::init_gpv(n, &q, &s); 99 | let (pk, sk) = fdh.gen(); 100 | 101 | for i in 0..10 { 102 | let m = format!("Hello World! {}", i); 103 | 104 | let sigma = fdh.sign(m.to_owned(), &sk, &pk); 105 | 106 | assert_eq!(&sigma, &fdh.sign(m.to_owned(), &sk, &pk)); 107 | assert!(fdh.vfy(m.to_owned(), &sigma, &pk)) 108 | } 109 | } 110 | 111 | /// Ensure that an entry is actually added to the local storage. 112 | #[test] 113 | fn storage_filled() { 114 | let mut fdh = FDH::init_gpv(5, 1024, 10); 115 | 116 | let m = "Hello World!"; 117 | let (pk, sk) = fdh.gen(); 118 | let _ = fdh.sign(m.to_owned(), &sk, &pk); 119 | 120 | assert!(fdh.storage.contains_key(m)) 121 | } 122 | 123 | /// Ensure that after deserialization the HashMap still contains all entries. 124 | #[test] 125 | fn reload_hashmap() { 126 | let mut fdh = FDH::init_gpv(5, 1024, 10); 127 | 128 | // fill one entry in the HashMap 129 | let m = "Hello World!"; 130 | let (pk, sk) = fdh.gen(); 131 | let _ = fdh.sign(m.to_owned(), &sk, &pk); 132 | 133 | let fdh_string = serde_json::to_string(&fdh).expect("Unable to create a json object"); 134 | let fdh_2: FDH = 135 | serde_json::from_str(&fdh_string).unwrap(); 136 | 137 | assert_eq!(fdh.storage, fdh_2.storage); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/construction/signature/fdh/gpv_ring.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Marvin Beckmann 2 | // 3 | // This file is part of qFALL-crypto. 4 | // 5 | // qFALL-crypto is free software: you can redistribute it and/or modify it under 6 | // the terms of the Mozilla Public License Version 2.0 as published by the 7 | // Mozilla Foundation. See . 8 | 9 | //! A ring implementation of the [`FDH`] scheme using the [`PSFGPVRing`] 10 | //! according to [\[1\]](<../index.html#:~:text=[1]>). 11 | 12 | use super::FDH; 13 | use crate::{ 14 | construction::hash::sha256::HashMatPolynomialRingZq, primitive::psf::PSFGPVRing, 15 | sample::g_trapdoor::gadget_parameters::GadgetParametersRing, 16 | }; 17 | use qfall_math::{ 18 | integer::{MatPolyOverZ, Z}, 19 | integer_mod_q::{MatPolynomialRingZq, Modulus}, 20 | rational::Q, 21 | }; 22 | use std::{collections::HashMap, marker::PhantomData}; 23 | 24 | impl 25 | FDH< 26 | MatPolynomialRingZq, 27 | (MatPolyOverZ, MatPolyOverZ), 28 | MatPolyOverZ, 29 | MatPolynomialRingZq, 30 | PSFGPVRing, 31 | HashMatPolynomialRingZq, 32 | > 33 | { 34 | /// Initializes an FDH signature scheme from a [`PSFGPVRing`]. 35 | /// The trapdoor is sampled with a Gaussian parameter of 1.005 36 | /// as done in [\[3\]]() who derived it from 37 | /// [\[5\]](). 38 | /// 39 | /// This function corresponds to an implementation of an FDH-signature 40 | /// scheme with the explicit PSF [`PSFGPVRing`] which is generated using 41 | /// the default of [`GadgetParametersRing`]. 42 | /// 43 | /// Parameters: 44 | /// - `n`: The security parameter 45 | /// - `q`: The modulus used for the G-Trapdoors 46 | /// - `s`: The Gaussian parameter with which is sampled 47 | /// 48 | /// Returns an explicit implementation of an FDH-signature scheme. 49 | /// 50 | /// # Example 51 | /// ``` 52 | /// use qfall_crypto::construction::signature::{FDH, SignatureScheme}; 53 | /// 54 | /// let mut fdh = FDH::init_gpv_ring(8, 512, 100); 55 | /// let (pk, sk) = fdh.gen(); 56 | /// 57 | /// let m = &format!("Hello World!"); 58 | /// 59 | /// let sigma = fdh.sign(m.to_owned(), &sk, &pk); 60 | /// assert!(fdh.vfy(m.to_owned(), &sigma, &pk)); 61 | /// ``` 62 | /// 63 | /// # Panics ... 64 | /// - if `q <= 1`. 65 | pub fn init_gpv_ring(n: impl Into, q: impl Into, s: impl Into) -> Self { 66 | let n = n.into(); 67 | let q = q.into(); 68 | let s = s.into(); 69 | let psf = PSFGPVRing { 70 | gp: GadgetParametersRing::init_default(&n, &q), 71 | s, 72 | s_td: Q::from(1.005_f64), 73 | }; 74 | let modulus = psf.gp.modulus.clone(); 75 | Self { 76 | psf: Box::new(psf), 77 | storage: HashMap::new(), 78 | hash: Box::new(HashMatPolynomialRingZq { 79 | modulus, 80 | rows: 1, 81 | cols: 1, 82 | }), 83 | _a_type: PhantomData, 84 | _trapdoor_type: PhantomData, 85 | _range_type: PhantomData, 86 | } 87 | } 88 | } 89 | 90 | #[cfg(test)] 91 | mod test_fdh { 92 | use super::{PSFGPVRing, FDH}; 93 | use crate::{ 94 | construction::hash::sha256::HashMatPolynomialRingZq, 95 | construction::signature::SignatureScheme, 96 | }; 97 | use qfall_math::{integer::MatPolyOverZ, integer_mod_q::MatPolynomialRingZq, rational::Q}; 98 | 99 | const MODULUS: i64 = 512; 100 | const N: i64 = 8; 101 | fn compute_s() -> Q { 102 | ((2 * 2 * Q::from(1.005_f64) * Q::from(N).sqrt() + 1) * 2) * 4 103 | } 104 | 105 | /// Ensure that the generated signature is valid. 106 | #[test] 107 | fn ensure_valid_signature_is_generated() { 108 | let mut fdh = FDH::init_gpv_ring(N, MODULUS, compute_s()); 109 | let (pk, sk) = fdh.gen(); 110 | 111 | for i in 0..10 { 112 | let m = &format!("Hello World! {i}"); 113 | 114 | let sigma = fdh.sign(m.to_owned(), &sk, &pk); 115 | 116 | assert!( 117 | fdh.vfy(m.to_owned(), &sigma, &pk), 118 | "This is a probabilistic test and may fail with negligible probability. \ 119 | As n is rather small here, try to rerun the test and check whether the \ 120 | test fails again." 121 | ) 122 | } 123 | } 124 | 125 | /// Ensure that an entry is actually added to the local storage. 126 | #[test] 127 | fn storage_filled() { 128 | let mut fdh = FDH::init_gpv_ring(N, MODULUS, compute_s()); 129 | 130 | let m = "Hello World!"; 131 | let (pk, sk) = fdh.gen(); 132 | let sign_1 = fdh.sign(m.to_owned(), &sk, &pk); 133 | let sign_2 = fdh.sign(m.to_owned(), &sk, &pk); 134 | 135 | assert!(fdh.storage.contains_key(m)); 136 | assert_eq!(sign_1, sign_2); 137 | } 138 | 139 | /// Ensure that after deserialization the HashMap still contains all entries. 140 | #[test] 141 | fn reload_hashmap() { 142 | let mut fdh = FDH::init_gpv_ring(N, MODULUS, compute_s()); 143 | 144 | // fill one entry in the HashMap 145 | let m = "Hello World!"; 146 | let (pk, sk) = fdh.gen(); 147 | let _ = fdh.sign(m.to_owned(), &sk, &pk); 148 | 149 | let fdh_string = serde_json::to_string(&fdh).expect("Unable to create a json object"); 150 | 151 | let fdh_2: FDH< 152 | MatPolynomialRingZq, 153 | (MatPolyOverZ, MatPolyOverZ), 154 | MatPolyOverZ, 155 | MatPolynomialRingZq, 156 | PSFGPVRing, 157 | HashMatPolynomialRingZq, 158 | > = serde_json::from_str(&fdh_string).unwrap(); 159 | 160 | assert_eq!(fdh.storage, fdh_2.storage); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/construction/signature/fdh/serialize.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Marvin Beckmann 2 | // 3 | // This file is part of qFALL-crypto. 4 | // 5 | // qFALL-crypto is free software: you can redistribute it and/or modify it under 6 | // the terms of the Mozilla Public License Version 2.0 as published by the 7 | // Mozilla Foundation. See . 8 | 9 | //! Allows to Deserialize an arbitrary [`FDH`] instantiation 10 | 11 | use super::FDH; 12 | use crate::{construction::hash::HashInto, primitive::psf::PSF}; 13 | use serde::{ 14 | de::{Error, MapAccess, Visitor}, 15 | Deserialize, Serialize, 16 | }; 17 | use std::{fmt, marker::PhantomData}; 18 | 19 | impl<'de, A, Trapdoor, Domain, Range, T, Hash> Deserialize<'de> 20 | for FDH 21 | where 22 | Domain: Serialize + for<'a> Deserialize<'a>, 23 | T: PSF + Serialize + for<'a> Deserialize<'a>, 24 | Hash: HashInto + Serialize + for<'a> Deserialize<'a>, 25 | { 26 | fn deserialize(deserializer: D) -> Result 27 | where 28 | D: serde::Deserializer<'de>, 29 | { 30 | /// This enum defines the content of the struct to be generated using [`Deserialize`] 31 | const FIELDS: &[&str] = &["psf", "storage", "hash"]; 32 | #[derive(Deserialize)] 33 | #[serde(field_identifier, rename_all = "lowercase")] 34 | enum Field { 35 | Psf, 36 | Storage, 37 | Hash, 38 | } 39 | 40 | /// This visitor iterates over the strings content and collects all possible fields. 41 | /// It sets the corresponding values of the struct based on the values found. 42 | struct StructVisitor { 43 | a: PhantomData, 44 | trapdoor: PhantomData, 45 | domain: PhantomData, 46 | range: PhantomData, 47 | t: PhantomData, 48 | hash: PhantomData, 49 | } 50 | impl<'de, A, Trapdoor, Domain, Range, T, Hash> Visitor<'de> 51 | for StructVisitor 52 | where 53 | Domain: Serialize + for<'a> Deserialize<'a>, 54 | T: PSF + Serialize + for<'a> Deserialize<'a>, 55 | Hash: HashInto + Serialize + for<'a> Deserialize<'a>, 56 | { 57 | type Value = FDH; 58 | 59 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 60 | formatter.write_str("struct $type") 61 | } 62 | 63 | fn visit_map(self, mut map: V) -> Result 64 | where 65 | V: MapAccess<'de>, 66 | { 67 | let mut psf = None; 68 | let mut storage = None; 69 | let mut hash = None; 70 | while let Some(key) = map.next_key()? { 71 | match key { 72 | Field::Psf => { 73 | if psf.is_some() { 74 | return Err(Error::duplicate_field("psf")); 75 | } 76 | psf = Some(map.next_value()?); 77 | } 78 | Field::Storage => { 79 | if storage.is_some() { 80 | return Err(Error::duplicate_field("storage")); 81 | } 82 | storage = Some(map.next_value()?); 83 | } 84 | Field::Hash => { 85 | if hash.is_some() { 86 | return Err(Error::duplicate_field("hash")); 87 | } 88 | hash = Some(map.next_value()?); 89 | } 90 | } 91 | } 92 | 93 | Ok(FDH { 94 | psf: Box::new(psf.unwrap()), 95 | storage: storage.unwrap(), 96 | hash: Box::new(hash.unwrap()), 97 | _a_type: PhantomData, 98 | _trapdoor_type: PhantomData, 99 | _range_type: PhantomData, 100 | }) 101 | } 102 | } 103 | 104 | let struct_visitor: StructVisitor = StructVisitor { 105 | a: PhantomData, 106 | trapdoor: PhantomData, 107 | domain: PhantomData, 108 | range: PhantomData, 109 | t: PhantomData, 110 | hash: PhantomData, 111 | }; 112 | deserializer.deserialize_struct("FDH", FIELDS, struct_visitor) 113 | } 114 | } 115 | 116 | #[cfg(test)] 117 | mod test_deserialization { 118 | use crate::{ 119 | construction::{ 120 | hash::sha256::HashMatZq, 121 | signature::{SignatureScheme, FDH}, 122 | }, 123 | primitive::psf::PSFGPV, 124 | }; 125 | use qfall_math::{integer::MatZ, integer_mod_q::MatZq, rational::MatQ}; 126 | 127 | /// Ensure that deserialization works. 128 | #[allow(clippy::type_complexity)] 129 | #[test] 130 | fn deserialize_gpv() { 131 | let mut fdh = FDH::init_gpv(2, 127, 20); 132 | 133 | // fill one entry in the HashMap 134 | let m = "Hello World!"; 135 | let (pk, sk) = fdh.gen(); 136 | let _ = fdh.sign(m.to_owned(), &sk, &pk); 137 | 138 | let fdh_string = serde_json::to_string(&fdh).expect("Unable to create a json object"); 139 | let fdh_2: Result, _> = 140 | serde_json::from_str(&fdh_string); 141 | 142 | assert!(fdh_2.is_ok()); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/construction/signature/pfdh.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Phil Milewski 2 | // 3 | // This file is part of qFALL-crypto. 4 | // 5 | // qFALL-crypto is free software: you can redistribute it and/or modify it under 6 | // the terms of the Mozilla Public License Version 2.0 as published by the 7 | // Mozilla Foundation. See . 8 | 9 | //! This Module contains a general implementation of the [`PFDH`] scheme. 10 | //! 11 | //! Implementation of a [`PFDH`]-signature scheme are thereby fairly easy, 12 | //! see [`PFDH::init_gpv`](crate::construction::signature::pfdh::gpv) that 13 | //! works with every PSF and a corresponding hash function. 14 | 15 | use super::SignatureScheme; 16 | use crate::{construction::hash::HashInto, primitive::psf::PSF}; 17 | use qfall_math::{integer::Z, traits::Pow}; 18 | use serde::{Deserialize, Serialize}; 19 | use std::marker::PhantomData; 20 | 21 | pub mod gpv; 22 | pub mod serialize; 23 | 24 | /// This struct captures the general definition of a hash-then-sign signature scheme 25 | /// that uses a hash function as in [\[1\]]() and a PSF. 26 | /// An explicit instantiation for defined types makes understanding this struct much 27 | /// easier, compare [`PFDH::init_gpv`]. 28 | /// This signature scheme also includes randomness into the hashed strings rather than 29 | /// using a storage, so it is stateless. 30 | /// 31 | /// Implementing a function for a specific set of types(replacing the generic types) 32 | /// allows for easy implementation of the signature scheme. Any PSF and a corresponding 33 | /// hash-function can be directly translated to an implementation of this signature 34 | /// scheme. 35 | /// 36 | /// Attributes 37 | /// - `psf`: The PSF which has to implement the [`PSF`] trait and must also be 38 | /// (de-)serializable. 39 | /// - `hash`: The hash-function which has to map a string into the correct domain. 40 | /// - `randomness_length`: The length of the salt that is added to the string before 41 | /// hashing. 42 | /// 43 | /// # Example 44 | /// ## Signature Scheme from [`PSFGPV`](crate::primitive::psf::PSFGPV) 45 | /// ``` 46 | /// use qfall_crypto::construction::signature::{PFDH, SignatureScheme}; 47 | /// 48 | /// let mut pfdh = PFDH::init_gpv(4, 113, 17, 128); 49 | /// 50 | /// let m = "Hello World!"; 51 | /// 52 | /// let (pk, sk) = pfdh.gen(); 53 | /// let sigma = pfdh.sign(m.to_owned(), &sk, &pk); 54 | /// 55 | /// assert!(pfdh.vfy(m.to_owned(), &sigma, &pk)); 56 | /// ``` 57 | #[derive(Serialize)] 58 | pub struct PFDH< 59 | A, 60 | Trapdoor, 61 | Domain: Serialize + for<'a> Deserialize<'a>, 62 | Range, 63 | T: PSF + Serialize + for<'a> Deserialize<'a>, 64 | Hash: HashInto + Serialize + for<'a> Deserialize<'a>, 65 | > { 66 | pub psf: Box, 67 | pub hash: Box, 68 | pub randomness_length: Z, 69 | 70 | // The parameters below can be ignored, they are just there for generic usage 71 | #[serde(skip_serializing)] 72 | pub _a_type: PhantomData, 73 | #[serde(skip_serializing)] 74 | pub _trapdoor_type: PhantomData, 75 | #[serde(skip_serializing)] 76 | pub _domain_type: PhantomData, 77 | #[serde(skip_serializing)] 78 | pub _range_type: PhantomData, 79 | } 80 | 81 | impl SignatureScheme 82 | for PFDH 83 | where 84 | Domain: Clone + Serialize + for<'a> Deserialize<'a>, 85 | Range: PartialEq, 86 | T: PSF + Serialize + for<'a> Deserialize<'a>, 87 | Hash: HashInto + Serialize + for<'a> Deserialize<'a>, 88 | { 89 | type SecretKey = Trapdoor; 90 | type PublicKey = A; 91 | type Signature = (Domain, Z); 92 | 93 | /// Generates a trapdoor by calling the `trap_gen` of the psf 94 | fn gen(&mut self) -> (Self::PublicKey, Self::SecretKey) { 95 | self.psf.trap_gen() 96 | } 97 | 98 | /// Firstly generate randomness 99 | /// It hashes the message and randomness into the domain and then computes a signature using 100 | /// `samp_p` from the psf with the trapdoor. 101 | fn sign(&mut self, m: String, sk: &Self::SecretKey, pk: &Self::PublicKey) -> Self::Signature { 102 | let randomness = 103 | Z::sample_uniform(0, Z::from(2).pow(&self.randomness_length).unwrap()).unwrap(); 104 | let u = (self.hash).hash(&format!("{m} {randomness} {}", &self.randomness_length)); 105 | let signature_part1 = self.psf.samp_p(pk, sk, &u); 106 | 107 | (signature_part1, randomness) 108 | } 109 | 110 | /// Checks if a signature is firstly within D_n, and then checks if 111 | /// the signature is actually a valid preimage under `fa` of `hash(m||r)`. 112 | fn vfy(&self, m: String, sigma: &Self::Signature, pk: &Self::PublicKey) -> bool { 113 | if !self.psf.check_domain(&sigma.0) { 114 | return false; 115 | } 116 | 117 | let u = (self.hash).hash(&format!("{m} {} {}", sigma.1, &self.randomness_length)); 118 | 119 | self.psf.f_a(pk, &sigma.0) == u 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/construction/signature/pfdh/gpv.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Phil Milewski 2 | // 3 | // This file is part of qFALL-crypto. 4 | // 5 | // qFALL-crypto is free software: you can redistribute it and/or modify it under 6 | // the terms of the Mozilla Public License Version 2.0 as published by the 7 | // Mozilla Foundation. See . 8 | 9 | //! A classical implementation of the [`PFDH`] scheme using the [`PSFGPV`] 10 | //! according to [\[1\]](<../index.html#:~:text=[1]>). 11 | 12 | use super::PFDH; 13 | use crate::{ 14 | construction::hash::sha256::HashMatZq, primitive::psf::PSFGPV, 15 | sample::g_trapdoor::gadget_parameters::GadgetParameters, 16 | }; 17 | use qfall_math::{ 18 | integer::{MatZ, Z}, 19 | integer_mod_q::{MatZq, Modulus}, 20 | rational::{MatQ, Q}, 21 | }; 22 | use std::marker::PhantomData; 23 | 24 | impl PFDH { 25 | /// Initializes an PFDH signature scheme from a [`PSFGPV`]. 26 | /// 27 | /// This function corresponds to an implementation of an PFDH-signature 28 | /// scheme with the explicit PSF [`PSFGPV`] which is generated using 29 | /// the default of [`GadgetParameters`]. 30 | /// 31 | /// Parameters: 32 | /// - `n`: The security parameter 33 | /// - `q`: The modulus used for the G-Trapdoors 34 | /// - `s`: The Gaussian parameter with which is sampled 35 | /// - `randomness_length`: the number of bits used for the randomness 36 | /// 37 | /// Returns an explicit implementation of a PFDH-signature scheme. 38 | /// 39 | /// # Example 40 | /// ``` 41 | /// use qfall_crypto::construction::signature::{PFDH, SignatureScheme}; 42 | /// 43 | /// let mut pfdh = PFDH::init_gpv(4, 113, 17, 128); 44 | /// 45 | /// let m = "Hello World!"; 46 | /// 47 | /// let (pk, sk) = pfdh.gen(); 48 | /// let sigma = pfdh.sign(m.to_owned(), &sk, &pk); 49 | /// 50 | /// assert!(pfdh.vfy(m.to_owned(), &sigma, &pk)); 51 | /// ``` 52 | /// 53 | /// # Panics ... 54 | /// - if `q <= 1`. 55 | pub fn init_gpv( 56 | n: impl Into, 57 | q: impl Into, 58 | s: impl Into, 59 | randomness_length: impl Into, 60 | ) -> Self { 61 | let q = q.into(); 62 | let n = n.into(); 63 | let s = s.into(); 64 | let psf = PSFGPV { 65 | gp: GadgetParameters::init_default(&n, &q), 66 | s, 67 | }; 68 | let n = i64::try_from(&n).unwrap(); 69 | Self { 70 | psf: Box::new(psf), 71 | hash: Box::new(HashMatZq { 72 | modulus: q, 73 | rows: n, 74 | cols: 1, 75 | }), 76 | randomness_length: randomness_length.into(), 77 | _a_type: PhantomData, 78 | _trapdoor_type: PhantomData, 79 | _domain_type: PhantomData, 80 | _range_type: PhantomData, 81 | } 82 | } 83 | } 84 | 85 | #[cfg(test)] 86 | mod test_pfdh { 87 | use super::PFDH; 88 | use crate::construction::signature::SignatureScheme; 89 | use qfall_math::{integer::Z, rational::Q, traits::Pow}; 90 | 91 | /// Ensure that the generated signature is valid. 92 | #[test] 93 | fn ensure_valid_signature_is_generated() { 94 | let n = Z::from(4); 95 | let k = Z::from(6); 96 | // `s >= ||\tilde short_base|| * omega(sqrt{log m})`, 97 | // here `log(2*n*k) = omega(sqrt{log m}))` (Theorem 4.1 - GPV08) 98 | let s: Q = ((&n * &k).sqrt() + 1) * Q::from(2) * (Z::from(2) * &n * &k).log(2).unwrap(); 99 | let q = Z::from(2).pow(&k).unwrap(); 100 | 101 | let mut pfdh = PFDH::init_gpv(n, &q, &s, 128); 102 | let (pk, sk) = pfdh.gen(); 103 | 104 | for i in 0..10 { 105 | let m = format!("Hello World! {}", i); 106 | 107 | let sigma = pfdh.sign(m.to_owned(), &sk, &pk); 108 | 109 | assert!(pfdh.vfy(m.to_owned(), &sigma, &pk)) 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/construction/signature/pfdh/serialize.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Phil Milewski 2 | // 3 | // This file is part of qFALL-crypto. 4 | // 5 | // qFALL-crypto is free software: you can redistribute it and/or modify it under 6 | // the terms of the Mozilla Public License Version 2.0 as published by the 7 | // Mozilla Foundation. See . 8 | 9 | //! Allows to Deserialize an arbitrary [`PFDH`] instantiation 10 | 11 | use super::PFDH; 12 | use crate::{construction::hash::HashInto, primitive::psf::PSF}; 13 | use serde::{ 14 | de::{Error, MapAccess, Visitor}, 15 | Deserialize, Serialize, 16 | }; 17 | use std::{fmt, marker::PhantomData}; 18 | 19 | impl<'de, A, Trapdoor, Domain, Range, T, Hash> Deserialize<'de> 20 | for PFDH 21 | where 22 | Domain: Serialize + for<'a> Deserialize<'a>, 23 | T: PSF + Serialize + for<'a> Deserialize<'a>, 24 | Hash: HashInto + Serialize + for<'a> Deserialize<'a>, 25 | { 26 | #[allow(non_camel_case_types)] 27 | fn deserialize(deserializer: D) -> Result 28 | where 29 | D: serde::Deserializer<'de>, 30 | { 31 | /// This enum defines the content of the struct to be generated using [`Deserialize`] 32 | const FIELDS: &[&str] = &["psf", "hash", "randomness_length"]; 33 | #[derive(Deserialize)] 34 | #[serde(field_identifier, rename_all = "lowercase")] 35 | enum Field { 36 | Psf, 37 | Hash, 38 | Randomness_Length, 39 | } 40 | 41 | /// This visitor iterates over the strings content and collects all possible fields. 42 | /// It sets the corresponding values of the struct based on the values found. 43 | struct StructVisitor { 44 | a: PhantomData, 45 | trapdoor: PhantomData, 46 | domain: PhantomData, 47 | range: PhantomData, 48 | t: PhantomData, 49 | hash: PhantomData, 50 | } 51 | impl<'de, A, Trapdoor, Domain, Range, T, Hash> Visitor<'de> 52 | for StructVisitor 53 | where 54 | Domain: Serialize + for<'a> Deserialize<'a>, 55 | T: PSF + Serialize + for<'a> Deserialize<'a>, 56 | Hash: HashInto + Serialize + for<'a> Deserialize<'a>, 57 | { 58 | type Value = PFDH; 59 | 60 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 61 | formatter.write_str("struct $type") 62 | } 63 | 64 | fn visit_map(self, mut map: V) -> Result 65 | where 66 | V: MapAccess<'de>, 67 | { 68 | let mut psf = None; 69 | let mut hash = None; 70 | let mut randomness_length = None; 71 | while let Some(key) = map.next_key()? { 72 | match key { 73 | Field::Psf => { 74 | if psf.is_some() { 75 | return Err(Error::duplicate_field("psf")); 76 | } 77 | psf = Some(map.next_value()?); 78 | } 79 | Field::Hash => { 80 | if hash.is_some() { 81 | return Err(Error::duplicate_field("hash")); 82 | } 83 | hash = Some(map.next_value()?); 84 | } 85 | Field::Randomness_Length => { 86 | if randomness_length.is_some() { 87 | return Err(Error::duplicate_field("randomness_length")); 88 | } 89 | randomness_length = Some(map.next_value()?); 90 | } 91 | } 92 | } 93 | 94 | Ok(PFDH { 95 | psf: Box::new(psf.unwrap()), 96 | hash: Box::new(hash.unwrap()), 97 | randomness_length: randomness_length.unwrap(), 98 | _a_type: PhantomData, 99 | _trapdoor_type: PhantomData, 100 | _range_type: PhantomData, 101 | _domain_type: PhantomData, 102 | }) 103 | } 104 | } 105 | 106 | let struct_visitor: StructVisitor = StructVisitor { 107 | a: PhantomData, 108 | trapdoor: PhantomData, 109 | domain: PhantomData, 110 | range: PhantomData, 111 | t: PhantomData, 112 | hash: PhantomData, 113 | }; 114 | deserializer.deserialize_struct("PFDH", FIELDS, struct_visitor) 115 | } 116 | } 117 | 118 | #[cfg(test)] 119 | mod test_deserialization { 120 | use crate::{ 121 | construction::{ 122 | hash::sha256::HashMatZq, 123 | signature::{SignatureScheme, PFDH}, 124 | }, 125 | primitive::psf::PSFGPV, 126 | }; 127 | use qfall_math::{integer::MatZ, integer_mod_q::MatZq, rational::MatQ}; 128 | 129 | /// Ensure that deserialization works. 130 | #[allow(clippy::type_complexity)] 131 | #[test] 132 | fn deserialize_gpv() { 133 | let mut pfdh = PFDH::init_gpv(2, 127, 20, 1233); 134 | 135 | let m = "Hello World!"; 136 | let (pk, sk) = pfdh.gen(); 137 | let signature = pfdh.sign(m.to_owned(), &sk, &pk); 138 | 139 | let pfdh_string = serde_json::to_string(&pfdh).expect("Unable to create a json object"); 140 | let pfdh_2: Result, _> = 141 | serde_json::from_str(&pfdh_string); 142 | 143 | assert!(pfdh_2.is_ok()); 144 | 145 | //ensure signing still works 146 | let mut pfdh_2 = pfdh_2.unwrap(); 147 | let signature_2 = pfdh_2.sign(m.to_owned(), &sk, &pk); 148 | 149 | //ensure verification still works 150 | assert!(pfdh_2.vfy(m.to_string(), &signature, &pk)); 151 | assert!(pfdh_2.vfy(m.to_string(), &signature_2, &pk)); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Niklas Siemer, Marvin Beckmann 2 | // 3 | // This file is part of qFALL-crypto. 4 | // 5 | // qFALL-crypto is free software: you can redistribute it and/or modify it under 6 | // the terms of the Mozilla Public License Version 2.0 as published by the 7 | // Mozilla Foundation. See . 8 | 9 | //! # What is qFALL-crypto? 10 | //! qFall-crypto provides cryptographic basics such as mathematical primitives, 11 | //! fundamental lattice-based cryptographic constructions, and samplable distributions/ 12 | //! possibilities to sample instances of lattice problems to prototype 13 | //! lattice-based cryptographic constructions and more. 14 | //! 15 | //! Currently qFALL-crypto supports 3 main construction types: 16 | //! - [Identity-Based Encryptions](construction::identity_based_encryption::IBEScheme) 17 | //! - [Public-Key Encryptions](construction::pk_encryption::PKEncryptionScheme) 18 | //! - [Signatures](construction::signature::SignatureScheme) 19 | //! 20 | //! These are identified by traits and then implemented for specific constructions, e.g. 21 | //! [`RingLPR`](construction::pk_encryption::RingLPR). 22 | //! Our library has further primitives useful for prototyping such as 23 | //! [`PSFs`](primitive::psf::PSF) that can be used to implement constructions. 24 | //! 25 | //! qFALL-crypto is free software: you can redistribute it and/or modify it under 26 | //! the terms of the Mozilla Public License Version 2.0 as published by the 27 | //! Mozilla Foundation. See . 28 | //! 29 | //! ## Tutorial + Website 30 | //! You can find a dedicated [tutorial](https://qfall.github.io/book/index.html) to qFALL-crypto on our [website](https://qfall.github.io/). 31 | //! The tutorial explains the basic steps starting from installation and 32 | //! continues with basic usage. 33 | //! qFALL-crypto is co-developed together with qFALL-math which provides the basic 34 | //! foundation that is used to implement the cryptographic constructions. 35 | 36 | pub mod construction; 37 | pub mod primitive; 38 | pub mod sample; 39 | pub mod utils; 40 | -------------------------------------------------------------------------------- /src/primitive.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Niklas Siemer 2 | // 3 | // This file is part of qFALL-crypto. 4 | // 5 | // qFALL-crypto is free software: you can redistribute it and/or modify it under 6 | // the terms of the Mozilla Public License Version 2.0 as published by the 7 | // Mozilla Foundation. See . 8 | 9 | //! This module contains mathematical primitives that are useful for cryptographic 10 | //! constructions/purposes, but themselves do not provide security guarantees like 11 | //! confidentiality, integrity, ... 12 | 13 | pub mod psf; 14 | -------------------------------------------------------------------------------- /src/primitive/psf.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Marcel Luca Schmidt, Marvin Beckmann 2 | // 3 | // This file is part of qFALL-crypto. 4 | // 5 | // qFALL-crypto is free software: you can redistribute it and/or modify it under 6 | // the terms of the Mozilla Public License Version 2.0 as published by the 7 | // Mozilla Foundation. See . 8 | 9 | //! Contains the [`PSF`] trait, which is then subsequently implemented by 10 | //! explicit constructions such as [`PSFGPV`]. 11 | //! 12 | //! The main references are listed in the following 13 | //! and will be further referenced in submodules by these numbers: 14 | //! - \[1\] Micciancio, D., Peikert, C. (2012). 15 | //! Trapdoors for Lattices: Simpler, Tighter, Faster, Smaller. 16 | //! In: Pointcheval, D., Johansson, T. (eds) Advances in Cryptology – EUROCRYPT 2012. 17 | //! EUROCRYPT 2012. Lecture Notes in Computer Science, vol 7237. 18 | //! Springer, Berlin, Heidelberg. 19 | //! - \[2\] Gür, K.D., Polyakov, Y., Rohloff, K., Ryan, G.W. and Savas, E., 2018, 20 | //! January. Implementation and evaluation of improved Gaussian sampling for lattice 21 | //! trapdoors. In Proceedings of the 6th Workshop on Encrypted Computing & Applied 22 | //! Homomorphic Cryptography (pp. 61-71). 23 | 24 | mod gpv; 25 | mod gpv_ring; 26 | 27 | pub use gpv::PSFGPV; 28 | pub use gpv_ring::PSFGPVRing; 29 | 30 | /// This trait should be implemented by all constructions that are 31 | /// actual implementations of a preimage sampleable function. 32 | /// A formal definition for these PSFs can be found in 33 | /// [\[1\]]() 34 | pub trait PSF { 35 | /// Samples a parity-check matrix and a trapdoor for that matrix. 36 | /// 37 | /// Returns the parity-check matrix and the trapdoor. 38 | fn trap_gen(&self) -> (A, Trapdoor); 39 | 40 | /// Samples an element in the domain according to a specified distribution. 41 | /// 42 | /// Returns the sampled element. 43 | fn samp_d(&self) -> Domain; 44 | 45 | /// Samples an element `e` in the domain according to a specified distribution 46 | /// conditioned on `f_a(a, e) = u`. 47 | /// 48 | /// Parameters: 49 | /// - `a`: The parity-check matrix 50 | /// - `r`: The G-Trapdoor for `a` 51 | /// - `u`: The syndrome from the range 52 | /// 53 | /// Returns a sample `e` from the domain on the conditioned discrete 54 | /// Gaussian distribution `f_a(a,e) = u`. 55 | fn samp_p(&self, a: &A, r: &Trapdoor, u: &Range) -> Domain; 56 | 57 | /// Implements the efficiently computable function `f_a`, 58 | /// which is uniquely classified by `a`. 59 | /// 60 | /// Parameters: 61 | /// - `a`: The parity-check matrix of dimensions `n x m` 62 | /// - `sigma`: A column vector of length `m` 63 | /// 64 | /// Returns the result of `f_a`. 65 | fn f_a(&self, a: &A, sigma: &Domain) -> Range; 66 | 67 | /// Checks whether an element is in the correct domain (and not just the correct type). 68 | /// 69 | /// Returns the result of the check as a boolean. 70 | fn check_domain(&self, sigma: &Domain) -> bool; 71 | } 72 | -------------------------------------------------------------------------------- /src/primitive/psf/gpv.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Marvin Beckmann 2 | // 3 | // This file is part of qFALL-crypto. 4 | // 5 | // qFALL-crypto is free software: you can redistribute it and/or modify it under 6 | // the terms of the Mozilla Public License Version 2.0 as published by the 7 | // Mozilla Foundation. See . 8 | 9 | //! Implements a GPV PSF according to [\[1\]](<../index.html#:~:text=[1]>) 10 | //! using G-Trapdoors to generate a short basis and corresponding trapdoor. 11 | 12 | use super::PSF; 13 | use crate::sample::g_trapdoor::{ 14 | gadget_classical::gen_trapdoor, gadget_parameters::GadgetParameters, 15 | short_basis_classical::gen_short_basis_for_trapdoor, 16 | }; 17 | use qfall_math::{ 18 | integer::MatZ, 19 | integer_mod_q::MatZq, 20 | rational::{MatQ, Q}, 21 | traits::{MatrixDimensions, Pow}, 22 | }; 23 | use serde::{Deserialize, Serialize}; 24 | 25 | /// A lattice-based implementation of a [`PSF`] according to 26 | /// [\[1\]]() using 27 | /// G-Trapdoors where D_n = {e ∈ Z^m | |e| <= s sqrt(m)} 28 | /// and R_n = Z_q^n. 29 | /// 30 | /// Attributes 31 | /// - `gp`: Describes the gadget parameters with which the G-Trapdoor is generated 32 | /// - `s`: The Gaussian parameter with which is sampled 33 | /// 34 | /// # Examples 35 | /// ``` 36 | /// use qfall_crypto::primitive::psf::PSFGPV; 37 | /// use qfall_crypto::sample::g_trapdoor::gadget_parameters::GadgetParameters; 38 | /// use qfall_math::rational::Q; 39 | /// use qfall_crypto::primitive::psf::PSF; 40 | /// 41 | /// let psf = PSFGPV { 42 | /// gp: GadgetParameters::init_default(8, 64), 43 | /// s: Q::from(12), 44 | /// }; 45 | /// 46 | /// let (a, td) = psf.trap_gen(); 47 | /// let domain_sample = psf.samp_d(); 48 | /// let range_fa = psf.f_a(&a, &domain_sample); 49 | /// let preimage = psf.samp_p(&a, &td, &range_fa); 50 | /// 51 | /// assert!(psf.check_domain(&preimage)); 52 | /// ``` 53 | #[derive(Serialize, Deserialize)] 54 | pub struct PSFGPV { 55 | pub gp: GadgetParameters, 56 | pub s: Q, 57 | } 58 | 59 | impl PSF for PSFGPV { 60 | /// Computes a G-Trapdoor according to the [`GadgetParameters`] 61 | /// defined in the struct. 62 | /// It returns a matrix `A` together with a short base and its GSO. 63 | /// 64 | /// # Examples 65 | /// ``` 66 | /// use qfall_crypto::primitive::psf::PSFGPV; 67 | /// use qfall_crypto::sample::g_trapdoor::gadget_parameters::GadgetParameters; 68 | /// use qfall_math::rational::Q; 69 | /// use qfall_crypto::primitive::psf::PSF; 70 | /// 71 | /// let psf = PSFGPV { 72 | /// gp: GadgetParameters::init_default(8, 64), 73 | /// s: Q::from(12), 74 | /// }; 75 | /// 76 | /// let (a, (sh_b, sh_b_gso)) = psf.trap_gen(); 77 | /// ``` 78 | fn trap_gen(&self) -> (MatZq, (MatZ, MatQ)) { 79 | let a_bar = MatZq::sample_uniform(&self.gp.n, &self.gp.m_bar, &self.gp.q); 80 | 81 | let tag = MatZq::identity(&self.gp.n, &self.gp.n, &self.gp.q); 82 | 83 | let (a, r) = gen_trapdoor(&self.gp, &a_bar, &tag).unwrap(); 84 | 85 | let short_base = gen_short_basis_for_trapdoor(&self.gp, &tag, &a, &r); 86 | let short_base_gso = MatQ::from(&short_base).gso(); 87 | 88 | (a, (short_base, short_base_gso)) 89 | } 90 | 91 | /// Samples in the domain using SampleD with the standard basis and center `0`. 92 | /// 93 | /// # Examples 94 | /// ``` 95 | /// use qfall_crypto::primitive::psf::PSFGPV; 96 | /// use qfall_crypto::sample::g_trapdoor::gadget_parameters::GadgetParameters; 97 | /// use qfall_math::rational::Q; 98 | /// use qfall_crypto::primitive::psf::PSF; 99 | /// 100 | /// let psf = PSFGPV { 101 | /// gp: GadgetParameters::init_default(8, 64), 102 | /// s: Q::from(12), 103 | /// }; 104 | /// let (a, td) = psf.trap_gen(); 105 | /// 106 | /// let domain_sample = psf.samp_d(); 107 | /// ``` 108 | fn samp_d(&self) -> MatZ { 109 | let m = &self.gp.n * &self.gp.k + &self.gp.m_bar; 110 | MatZ::sample_d_common(&m, &self.gp.n, &self.s).unwrap() 111 | } 112 | 113 | /// Samples an `e` in the domain using SampleD with a short basis that is generated 114 | /// from the G-Trapdoor from the conditioned conditioned 115 | /// discrete Gaussian with `f_a(a,e) = u` for a provided syndrome `u`. 116 | /// 117 | /// *Note*: the provided parameters `a,r,u` must fit together, 118 | /// otherwise unexpected behavior such as panics may occur. 119 | /// 120 | /// Parameters: 121 | /// - `a`: The parity-check matrix 122 | /// - `short_base`: The short base for `Λ^⟂(A)` 123 | /// - `short_base_gso`: The precomputed GSO of the short_base 124 | /// - `u`: The syndrome from the range 125 | /// 126 | /// Returns a sample `e` from the domain on the conditioned discrete 127 | /// Gaussian distribution `f_a(a,e) = u`. 128 | /// 129 | /// # Examples 130 | /// ``` 131 | /// use qfall_crypto::primitive::psf::PSFGPV; 132 | /// use qfall_crypto::sample::g_trapdoor::gadget_parameters::GadgetParameters; 133 | /// use qfall_math::rational::Q; 134 | /// use qfall_crypto::primitive::psf::PSF; 135 | /// 136 | /// let psf = PSFGPV { 137 | /// gp: GadgetParameters::init_default(8, 64), 138 | /// s: Q::from(12), 139 | /// }; 140 | /// let (a, td) = psf.trap_gen(); 141 | /// let domain_sample = psf.samp_d(); 142 | /// let range_fa = psf.f_a(&a, &domain_sample); 143 | /// 144 | /// let preimage = psf.samp_p(&a, &td, &range_fa); 145 | /// assert_eq!(range_fa, psf.f_a(&a, &preimage)) 146 | /// ``` 147 | fn samp_p(&self, a: &MatZq, (short_base, short_base_gso): &(MatZ, MatQ), u: &MatZq) -> MatZ { 148 | let sol: MatZ = a 149 | .solve_gaussian_elimination(u) 150 | .unwrap() 151 | .get_representative_least_nonnegative_residue(); 152 | 153 | let center = MatQ::from(&(-1 * &sol)); 154 | 155 | sol + MatZ::sample_d_precomputed_gso( 156 | short_base, 157 | short_base_gso, 158 | &self.gp.n, 159 | ¢er, 160 | &self.s, 161 | ) 162 | .unwrap() 163 | } 164 | 165 | /// Implements the efficiently computable function `f_a` which here corresponds to 166 | /// `a*sigma`. The sigma must be from the domain, i.e. D_n. 167 | /// 168 | /// Parameters: 169 | /// - `a`: The parity-check matrix of dimensions `n x m` 170 | /// - `sigma`: A column vector of length `m` 171 | /// 172 | /// Returns `a*sigma` 173 | /// 174 | /// # Examples 175 | /// ``` 176 | /// use qfall_crypto::primitive::psf::PSFGPV; 177 | /// use qfall_crypto::sample::g_trapdoor::gadget_parameters::GadgetParameters; 178 | /// use qfall_math::rational::Q; 179 | /// use qfall_crypto::primitive::psf::PSF; 180 | /// 181 | /// let psf = PSFGPV { 182 | /// gp: GadgetParameters::init_default(8, 64), 183 | /// s: Q::from(12), 184 | /// }; 185 | /// let (a, td) = psf.trap_gen(); 186 | /// let domain_sample = psf.samp_d(); 187 | /// let range_fa = psf.f_a(&a, &domain_sample); 188 | /// ``` 189 | /// 190 | /// # Panics ... 191 | /// - if `sigma` is not in the domain. 192 | fn f_a(&self, a: &MatZq, sigma: &MatZ) -> MatZq { 193 | assert!(self.check_domain(sigma)); 194 | a * sigma 195 | } 196 | 197 | /// Checks whether a value `sigma` is in D_n = {e ∈ Z^m | |e| <= s sqrt(m)}. 198 | /// 199 | /// Parameters: 200 | /// - `sigma`: The value for which is checked, if it is in the domain 201 | /// 202 | /// Returns true, if `sigma` is in D_n. 203 | /// 204 | /// # Examples 205 | /// ``` 206 | /// use qfall_crypto::primitive::psf::PSF; 207 | /// use qfall_crypto::primitive::psf::PSFGPV; 208 | /// use qfall_crypto::sample::g_trapdoor::gadget_parameters::GadgetParameters; 209 | /// use qfall_math::rational::Q; 210 | /// 211 | /// let psf = PSFGPV { 212 | /// gp: GadgetParameters::init_default(8, 64), 213 | /// s: Q::from(12), 214 | /// }; 215 | /// let (a, td) = psf.trap_gen(); 216 | /// 217 | /// let vector = psf.samp_d(); 218 | /// 219 | /// assert!(psf.check_domain(&vector)); 220 | /// ``` 221 | fn check_domain(&self, sigma: &MatZ) -> bool { 222 | let m = &self.gp.n * &self.gp.k + &self.gp.m_bar; 223 | sigma.is_column_vector() 224 | && m == sigma.get_num_rows() 225 | && sigma.norm_eucl_sqrd().unwrap() <= self.s.pow(2).unwrap() * &m 226 | } 227 | } 228 | 229 | #[cfg(test)] 230 | mod test_gpv_psf { 231 | use super::super::gpv::PSFGPV; 232 | use super::PSF; 233 | use crate::sample::g_trapdoor::gadget_parameters::GadgetParameters; 234 | use qfall_math::integer::MatZ; 235 | use qfall_math::rational::Q; 236 | use qfall_math::traits::*; 237 | 238 | /// Ensures that `samp_d` actually computes values that are in D_n. 239 | #[test] 240 | fn samp_d_samples_from_dn() { 241 | for (n, q) in [(5, 256), (10, 128), (15, 157)] { 242 | let psf = PSFGPV { 243 | gp: GadgetParameters::init_default(n, q), 244 | s: Q::from(10), 245 | }; 246 | 247 | for _ in 0..5 { 248 | assert!(psf.check_domain(&psf.samp_d())); 249 | } 250 | } 251 | } 252 | 253 | /// Ensures that `samp_p` actually computes preimages that are also in the correct 254 | /// domain. 255 | #[test] 256 | fn samp_p_preimage_and_domain() { 257 | for (n, q) in [(5, 256), (6, 128)] { 258 | let psf = PSFGPV { 259 | gp: GadgetParameters::init_default(n, q), 260 | s: Q::from(10), 261 | }; 262 | let (a, r) = psf.trap_gen(); 263 | let domain_sample = psf.samp_d(); 264 | let range_fa = psf.f_a(&a, &domain_sample); 265 | 266 | let preimage = psf.samp_p(&a, &r, &range_fa); 267 | assert_eq!(range_fa, psf.f_a(&a, &preimage)); 268 | assert!(psf.check_domain(&preimage)); 269 | } 270 | } 271 | 272 | /// Ensures that `f_a` returns `a*sigma`. 273 | #[test] 274 | fn f_a_works_as_expected() { 275 | for (n, q) in [(5, 256), (6, 128)] { 276 | let psf = PSFGPV { 277 | gp: GadgetParameters::init_default(n, q), 278 | s: Q::from(10), 279 | }; 280 | let (a, _) = psf.trap_gen(); 281 | let domain_sample = psf.samp_d(); 282 | 283 | assert_eq!(&a * &domain_sample, psf.f_a(&a, &domain_sample)); 284 | } 285 | } 286 | 287 | /// Ensures that `f_a` panics if a value is provided, that is not within the domain. 288 | /// Sigma is not a vector. 289 | #[test] 290 | #[should_panic] 291 | fn f_a_sigma_not_in_domain_matrix() { 292 | let psf = PSFGPV { 293 | gp: GadgetParameters::init_default(8, 128), 294 | s: Q::from(10), 295 | }; 296 | let (a, _) = psf.trap_gen(); 297 | let not_in_domain = MatZ::new(a.get_num_columns(), 2); 298 | 299 | let _ = psf.f_a(&a, ¬_in_domain); 300 | } 301 | 302 | /// Ensures that `f_a` panics if a value is provided, that is not within the domain. 303 | /// Sigma is not of the correct length. 304 | #[test] 305 | #[should_panic] 306 | fn f_a_sigma_not_in_domain_incorrect_length() { 307 | let psf = PSFGPV { 308 | gp: GadgetParameters::init_default(8, 128), 309 | s: Q::from(10), 310 | }; 311 | let (a, _) = psf.trap_gen(); 312 | let not_in_domain = MatZ::new(a.get_num_columns() - 1, 1); 313 | 314 | let _ = psf.f_a(&a, ¬_in_domain); 315 | } 316 | 317 | /// Ensures that `f_a` panics if a value is provided, that is not within the domain. 318 | /// Sigma is too long. 319 | #[test] 320 | #[should_panic] 321 | fn f_a_sigma_not_in_domain_too_long() { 322 | let psf = PSFGPV { 323 | gp: GadgetParameters::init_default(8, 128), 324 | s: Q::from(10), 325 | }; 326 | let (a, _) = psf.trap_gen(); 327 | let not_in_domain = 328 | psf.s.round() * a.get_num_columns() * MatZ::identity(a.get_num_columns(), 1); 329 | 330 | let _ = psf.f_a(&a, ¬_in_domain); 331 | } 332 | 333 | /// Ensures that `check_domain` works for vectors with the correct length. 334 | #[test] 335 | fn check_domain_as_expected() { 336 | let psf = PSFGPV { 337 | gp: GadgetParameters::init_default(8, 128), 338 | s: Q::from(10), 339 | }; 340 | let (a, _) = psf.trap_gen(); 341 | let value = psf.s.round(); 342 | let mut in_domain = MatZ::new(a.get_num_columns(), 1); 343 | for i in 0..in_domain.get_num_rows() { 344 | in_domain.set_entry(i, 0, &value).unwrap(); 345 | } 346 | 347 | assert!(psf.check_domain(&MatZ::new(a.get_num_columns(), 1))); 348 | assert!(psf.check_domain(&in_domain)); 349 | } 350 | 351 | /// Ensures that `check_domain` returns false for values that are not in the domain. 352 | #[test] 353 | fn check_domain_not_in_dn() { 354 | let psf = PSFGPV { 355 | gp: GadgetParameters::init_default(8, 128), 356 | s: Q::from(10), 357 | }; 358 | let (a, _) = psf.trap_gen(); 359 | 360 | let matrix = MatZ::new(a.get_num_columns(), 2); 361 | let too_short = MatZ::new(a.get_num_columns() - 1, 1); 362 | let too_long = MatZ::new(a.get_num_columns() + 1, 1); 363 | let entry_too_large = 364 | psf.s.round() * a.get_num_columns() * MatZ::identity(a.get_num_columns(), 1); 365 | 366 | assert!(!psf.check_domain(&matrix)); 367 | assert!(!psf.check_domain(&too_long)); 368 | assert!(!psf.check_domain(&too_short)); 369 | assert!(!psf.check_domain(&entry_too_large)); 370 | } 371 | } 372 | -------------------------------------------------------------------------------- /src/primitive/psf/gpv_ring.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Marvin Beckmann 2 | // 3 | // This file is part of qFALL-crypto. 4 | // 5 | // qFALL-crypto is free software: you can redistribute it and/or modify it under 6 | // the terms of the Mozilla Public License Version 2.0 as published by the 7 | // Mozilla Foundation. See . 8 | 9 | //! Implements a GPV PSF over the polynomial ring according to 10 | //! [\[1\]](<../index.html#:~:text=[1]>) and [\[2\]](<../index.html#:~:text=[2]>) 11 | //! using G-Trapdoors to generate a short basis and corresponding trapdoor. 12 | 13 | use super::PSF; 14 | use crate::{ 15 | sample::g_trapdoor::{ 16 | gadget_parameters::GadgetParametersRing, gadget_ring::gen_trapdoor_ring_lwe, 17 | short_basis_ring::gen_short_basis_for_trapdoor_ring, 18 | }, 19 | utils::rotation_matrix::rot_minus_matrix, 20 | }; 21 | use qfall_math::{ 22 | integer::{MatPolyOverZ, MatZ, PolyOverZ}, 23 | integer_mod_q::{MatPolynomialRingZq, MatZq}, 24 | rational::{MatQ, PolyOverQ, Q}, 25 | traits::{ 26 | FromCoefficientEmbedding, IntoCoefficientEmbedding, MatrixDimensions, MatrixGetSubmatrix, 27 | Pow, 28 | }, 29 | }; 30 | use serde::{Deserialize, Serialize}; 31 | 32 | /// A lattice-based implementation of a [`PSF`] according to 33 | /// [\[1\]]() and [\[2\]]() 34 | /// using G-Trapdoors where D_n = {e ∈ R^m | |ι(e)| <= s sqrt(m*n) } 35 | /// and R_n = R_q. 36 | /// 37 | /// Attributes 38 | /// - `gp`: Describes the gadget parameters with which the G-Trapdoor is generated 39 | /// - `s`: The Gaussian parameter with which elements from the domain are sampled 40 | /// - `s:td`: The Gaussian parameter with which the trapdoor is sampled 41 | /// 42 | /// # Examples 43 | /// ``` 44 | /// use qfall_crypto::primitive::psf::PSFGPVRing; 45 | /// use qfall_crypto::sample::g_trapdoor::gadget_parameters::GadgetParametersRing; 46 | /// use qfall_math::rational::Q; 47 | /// use qfall_crypto::primitive::psf::PSF; 48 | /// 49 | /// let psf = PSFGPVRing { 50 | /// gp: GadgetParametersRing::init_default(8, 512), 51 | /// s: Q::from(100), 52 | /// s_td: Q::from(1.005_f64), 53 | /// }; 54 | /// 55 | /// let (a, (r, e)) = psf.trap_gen(); 56 | /// let domain_sample = psf.samp_d(); 57 | /// let range_fa = psf.f_a(&a, &domain_sample); 58 | /// let preimage = psf.samp_p(&a, &(r,e), &range_fa); 59 | /// 60 | /// assert!(psf.check_domain(&preimage)); 61 | /// ``` 62 | #[derive(Serialize, Deserialize)] 63 | pub struct PSFGPVRing { 64 | pub gp: GadgetParametersRing, 65 | pub s: Q, 66 | pub s_td: Q, 67 | } 68 | 69 | impl PSF 70 | for PSFGPVRing 71 | { 72 | /// Computes a G-Trapdoor according to the [`GadgetParametersRing`]. 73 | /// 74 | /// # Examples 75 | /// ``` 76 | /// use qfall_crypto::primitive::psf::PSFGPVRing; 77 | /// use qfall_crypto::sample::g_trapdoor::gadget_parameters::GadgetParametersRing; 78 | /// use qfall_math::rational::Q; 79 | /// use qfall_crypto::primitive::psf::PSF; 80 | /// 81 | /// let psf = PSFGPVRing { 82 | /// gp: GadgetParametersRing::init_default(8, 512), 83 | /// s: Q::from(100), 84 | /// s_td: Q::from(1.005_f64), 85 | /// }; 86 | /// let (a, (r, e)) = psf.trap_gen(); 87 | /// ``` 88 | fn trap_gen(&self) -> (MatPolynomialRingZq, (MatPolyOverZ, MatPolyOverZ)) { 89 | let a_bar = 90 | PolyOverZ::sample_uniform(self.gp.modulus.get_degree() - 1, 0, self.gp.modulus.get_q()) 91 | .unwrap(); 92 | let (a, r, e) = gen_trapdoor_ring_lwe(&self.gp, &a_bar, &self.s_td).unwrap(); 93 | 94 | (a, (r, e)) 95 | } 96 | 97 | /// Samples in the domain using SampleD with the standard basis and center `0`. 98 | /// 99 | /// # Examples 100 | /// ``` 101 | /// use qfall_crypto::primitive::psf::PSFGPVRing; 102 | /// use qfall_crypto::sample::g_trapdoor::gadget_parameters::GadgetParametersRing; 103 | /// use qfall_math::rational::Q; 104 | /// use qfall_crypto::primitive::psf::PSF; 105 | /// 106 | /// let psf = PSFGPVRing { 107 | /// gp: GadgetParametersRing::init_default(8, 512), 108 | /// s: Q::from(100), 109 | /// s_td: Q::from(1.005_f64), 110 | /// }; 111 | /// let (a, (r, e)) = psf.trap_gen(); 112 | /// 113 | /// let domain_sample = psf.samp_d(); 114 | /// ``` 115 | fn samp_d(&self) -> MatPolyOverZ { 116 | let dimension = self.gp.modulus.get_degree() * (&self.gp.k + 2); 117 | let sample = MatZ::sample_d_common(dimension, &self.gp.n, &self.s).unwrap(); 118 | MatPolyOverZ::from_coefficient_embedding((&sample, self.gp.modulus.get_degree() - 1)) 119 | } 120 | 121 | /// Samples an `e` in the domain using SampleD with a short basis that is generated 122 | /// from the G-Trapdoor from the conditioned discrete Gaussian with 123 | /// `f_a(a,e) = u` for a provided syndrome `u`. 124 | /// 125 | /// *Note*: the provided parameters `a, r, e, u` must fit together, 126 | /// otherwise unexpected behavior such as panics may occur. 127 | /// 128 | /// Parameters: 129 | /// - `a`: The parity-check matrix 130 | /// - `r`: Together with `e` builds a G-Trapdoor for `a` 131 | /// - `e`: Together with `r` builds a G-Trapdoor for `a` 132 | /// - `u`: The syndrome from the range 133 | /// 134 | /// Returns a sample `e` from the domain on the conditioned discrete 135 | /// Gaussian distribution `f_a(a,e) = u`. 136 | /// 137 | /// # Examples 138 | /// ``` 139 | /// use qfall_crypto::primitive::psf::PSFGPVRing; 140 | /// use qfall_crypto::sample::g_trapdoor::gadget_parameters::GadgetParametersRing; 141 | /// use qfall_math::rational::Q; 142 | /// use qfall_crypto::primitive::psf::PSF; 143 | /// 144 | /// let psf = PSFGPVRing { 145 | /// gp: GadgetParametersRing::init_default(8, 512), 146 | /// s: Q::from(100), 147 | /// s_td: Q::from(1.005_f64), 148 | /// }; 149 | /// let (a, (r, e)) = psf.trap_gen(); 150 | /// 151 | /// let domain_sample = psf.samp_d(); 152 | /// let range_fa = psf.f_a(&a, &domain_sample); 153 | /// 154 | /// let preimage = psf.samp_p(&a, &(r,e), &range_fa); 155 | /// assert_eq!(range_fa, psf.f_a(&a, &preimage)) 156 | /// ``` 157 | fn samp_p( 158 | &self, 159 | a: &MatPolynomialRingZq, 160 | (r, e): &(MatPolyOverZ, MatPolyOverZ), 161 | u: &MatPolynomialRingZq, 162 | ) -> MatPolyOverZ { 163 | // compute solution to `a*x = u` 164 | // the same as `Rot^-(ι(a)) ι(x) = ι(u)` 165 | 166 | let short_basis = gen_short_basis_for_trapdoor_ring(&self.gp, a, r, e); 167 | 168 | // solve `rot^-(ι(a)) ι(x) = ι(u)` to get solution 169 | let u_embedded = u 170 | .get_representative_least_nonnegative_residue() 171 | .into_coefficient_embedding(self.gp.modulus.get_degree()); 172 | let a_embedded = a 173 | .get_representative_least_nonnegative_residue() 174 | .into_coefficient_embedding(self.gp.modulus.get_degree()); 175 | let rot_a = rot_minus_matrix(&a_embedded); 176 | 177 | let u_embedded = MatZq::from((&u_embedded, &self.gp.modulus.get_q())); 178 | let rot_a = MatZq::from((&rot_a, &self.gp.modulus.get_q())); 179 | let sol: MatZ = rot_a 180 | .solve_gaussian_elimination(&u_embedded) 181 | .unwrap() 182 | .get_representative_least_nonnegative_residue(); 183 | 184 | // turn center into a vector of polynomials over Q with maximal degree as the 185 | // modulus 186 | let center = MatQ::from(&(-1 * &sol)); 187 | let mut center_embedded = Vec::new(); 188 | for block in 0..(center.get_num_rows() / (self.gp.modulus.get_degree())) { 189 | let sub_mat = center 190 | .get_submatrix( 191 | block * self.gp.modulus.get_degree(), 192 | (block + 1) * self.gp.modulus.get_degree() - 1, 193 | 0, 194 | 0, 195 | ) 196 | .unwrap(); 197 | let embedded_sub_mat = PolyOverQ::from_coefficient_embedding(&sub_mat); 198 | center_embedded.push(embedded_sub_mat); 199 | } 200 | 201 | MatPolyOverZ::from_coefficient_embedding((&sol, self.gp.modulus.get_degree() - 1)) 202 | + MatPolyOverZ::sample_d( 203 | &short_basis, 204 | self.gp.modulus.get_degree(), 205 | &self.gp.n, 206 | ¢er_embedded, 207 | &self.s, 208 | ) 209 | .unwrap() 210 | } 211 | 212 | /// Implements the efficiently computable function `f_a` which here corresponds to 213 | /// `a*sigma`. 214 | /// 215 | /// Parameters: 216 | /// - `a`: The parity-check matrix of dimensions `n x m` 217 | /// - `sigma`: A column vector of length `m` 218 | /// 219 | /// Returns `a*sigma` 220 | /// 221 | /// # Examples 222 | /// ``` 223 | /// use qfall_crypto::primitive::psf::PSFGPVRing; 224 | /// use qfall_crypto::sample::g_trapdoor::gadget_parameters::GadgetParametersRing; 225 | /// use qfall_math::rational::Q; 226 | /// use qfall_crypto::primitive::psf::PSF; 227 | /// 228 | /// let psf = PSFGPVRing { 229 | /// gp: GadgetParametersRing::init_default(8, 512), 230 | /// s: Q::from(100), 231 | /// s_td: Q::from(1.005_f64), 232 | /// }; 233 | /// let (a, (r, e)) = psf.trap_gen(); 234 | /// 235 | /// let domain_sample = psf.samp_d(); 236 | /// let range_fa = psf.f_a(&a, &domain_sample); 237 | /// ``` 238 | /// 239 | /// # Panics ... 240 | /// - if `sigma` is not in the domain. 241 | fn f_a(&self, a: &MatPolynomialRingZq, sigma: &MatPolyOverZ) -> MatPolynomialRingZq { 242 | assert!(self.check_domain(sigma)); 243 | let sigma = MatPolynomialRingZq::from((sigma, &a.get_mod())); 244 | a * sigma 245 | } 246 | 247 | /// Checks whether a value `sigma` is in D_n = {e ∈ R^m | |ι(e)| <= s sqrt(m*n) }. 248 | /// 249 | /// Parameters: 250 | /// - `sigma`: The value for which is checked, if it is in the domain 251 | /// 252 | /// Returns true, if `sigma` is in D_n. 253 | /// 254 | /// # Examples 255 | /// ``` 256 | /// use qfall_crypto::primitive::psf::PSFGPVRing; 257 | /// use qfall_crypto::sample::g_trapdoor::gadget_parameters::GadgetParametersRing; 258 | /// use qfall_math::rational::Q; 259 | /// use qfall_crypto::primitive::psf::PSF; 260 | /// 261 | /// let psf = PSFGPVRing { 262 | /// gp: GadgetParametersRing::init_default(8, 512), 263 | /// s: Q::from(100), 264 | /// s_td: Q::from(1.005_f64), 265 | /// }; 266 | /// let (a, (r, e)) = psf.trap_gen(); 267 | /// 268 | /// let vector = psf.samp_d(); 269 | /// 270 | /// assert!(psf.check_domain(&vector)); 271 | /// ``` 272 | fn check_domain(&self, sigma: &MatPolyOverZ) -> bool { 273 | let m = &self.gp.k + 2; 274 | let nr_coeffs = self.gp.modulus.get_degree(); 275 | let sigma_embedded = sigma.into_coefficient_embedding(nr_coeffs); 276 | 277 | sigma.is_column_vector() 278 | && m == sigma.get_num_rows() 279 | && sigma_embedded.norm_eucl_sqrd().unwrap() 280 | <= self.s.pow(2).unwrap() * sigma_embedded.get_num_rows() 281 | } 282 | } 283 | 284 | #[cfg(test)] 285 | mod test_gpv_psf { 286 | use super::super::gpv_ring::PSFGPVRing; 287 | use super::PSF; 288 | use crate::sample::g_trapdoor::gadget_parameters::GadgetParametersRing; 289 | use qfall_math::integer::{MatPolyOverZ, PolyOverZ}; 290 | use qfall_math::integer_mod_q::MatPolynomialRingZq; 291 | use qfall_math::rational::Q; 292 | use qfall_math::traits::*; 293 | 294 | fn compute_s(n: i64) -> Q { 295 | ((2 * 2 * Q::from(1.005_f64) * Q::from(n).sqrt() + 1) * 2) * 4 296 | } 297 | 298 | /// Ensures that `samp_d` actually computes values that are in D_n. 299 | #[test] 300 | fn samp_d_samples_from_dn() { 301 | let (n, q) = (5, 123456789); 302 | let psf = PSFGPVRing { 303 | gp: GadgetParametersRing::init_default(n, q), 304 | s: Q::from(1000), 305 | s_td: Q::from(1.005_f64), 306 | }; 307 | 308 | for _ in 0..5 { 309 | assert!(psf.check_domain(&psf.samp_d())); 310 | } 311 | } 312 | 313 | /// Ensures that `samp_p` actually computes preimages that are also in the correct 314 | /// domain. 315 | #[test] 316 | fn samp_p_preimage_and_domain() { 317 | for (n, q) in [(5, i32::MAX - 57), (6, i32::MAX)] { 318 | let psf = PSFGPVRing { 319 | gp: GadgetParametersRing::init_default(n, q), 320 | s: compute_s(n), 321 | s_td: Q::from(1.005_f64), 322 | }; 323 | let (a, r) = psf.trap_gen(); 324 | let domain_sample = psf.samp_d(); 325 | let range_fa = psf.f_a(&a, &domain_sample); 326 | 327 | let preimage = psf.samp_p(&a, &r, &range_fa); 328 | 329 | assert_eq!(range_fa, psf.f_a(&a, &preimage)); 330 | assert!(psf.check_domain(&preimage)); 331 | } 332 | } 333 | 334 | /// Ensures that `f_a` returns `a*sigma`. 335 | #[test] 336 | fn f_a_works_as_expected() { 337 | for (n, q) in [(5, 256), (6, 128)] { 338 | let psf = PSFGPVRing { 339 | gp: GadgetParametersRing::init_default(n, q), 340 | s: compute_s(n), 341 | s_td: Q::from(1.005_f64), 342 | }; 343 | let (a, _) = psf.trap_gen(); 344 | let domain_sample = psf.samp_d(); 345 | 346 | let domain_sample_2 = MatPolynomialRingZq::from((&domain_sample, &a.get_mod())); 347 | assert_eq!(&a * &domain_sample_2, psf.f_a(&a, &domain_sample)); 348 | } 349 | } 350 | 351 | /// Ensures that `f_a` panics if a value is provided, that is not within the domain. 352 | /// Sigma is not a vector. 353 | #[test] 354 | #[should_panic] 355 | fn f_a_sigma_not_in_domain_matrix() { 356 | let psf = PSFGPVRing { 357 | gp: GadgetParametersRing::init_default(8, 1024), 358 | s: compute_s(8), 359 | s_td: Q::from(1.005_f64), 360 | }; 361 | let (a, _) = psf.trap_gen(); 362 | let not_in_domain = MatPolyOverZ::new(a.get_num_columns(), 2); 363 | 364 | let _ = psf.f_a(&a, ¬_in_domain); 365 | } 366 | 367 | /// Ensures that `f_a` panics if a value is provided, that is not within the domain. 368 | /// Sigma is not of the correct length. 369 | #[test] 370 | #[should_panic] 371 | fn f_a_sigma_not_in_domain_incorrect_length() { 372 | let psf = PSFGPVRing { 373 | gp: GadgetParametersRing::init_default(8, 1024), 374 | s: compute_s(8), 375 | s_td: Q::from(1.005_f64), 376 | }; 377 | let (a, _) = psf.trap_gen(); 378 | let not_in_domain = MatPolyOverZ::new(a.get_num_columns() - 1, 1); 379 | 380 | let _ = psf.f_a(&a, ¬_in_domain); 381 | } 382 | 383 | /// Ensures that `f_a` panics if a value is provided, that is not within the domain. 384 | /// Sigma is too long. 385 | #[test] 386 | #[should_panic] 387 | fn f_a_sigma_not_in_domain_too_long() { 388 | let psf = PSFGPVRing { 389 | gp: GadgetParametersRing::init_default(8, 1024), 390 | s: compute_s(8), 391 | s_td: Q::from(1.005_f64), 392 | }; 393 | let (a, _) = psf.trap_gen(); 394 | let not_in_domain = psf.s.round() 395 | * a.get_num_columns() 396 | * 8 397 | * MatPolyOverZ::identity(a.get_num_columns(), 1); 398 | 399 | let _ = psf.f_a(&a, ¬_in_domain); 400 | } 401 | 402 | /// Ensures that `check_domain` works for vectors with the correct length. 403 | #[test] 404 | fn check_domain_as_expected() { 405 | let psf = PSFGPVRing { 406 | gp: GadgetParametersRing::init_default(9, 1024), 407 | s: compute_s(9), 408 | s_td: Q::from(1.005_f64), 409 | }; 410 | let (a, _) = psf.trap_gen(); 411 | let value = PolyOverZ::from(psf.s.round() * 3); 412 | let mut in_domain = MatPolyOverZ::new(a.get_num_columns(), 1); 413 | for i in 0..in_domain.get_num_rows() { 414 | in_domain.set_entry(i, 0, &value).unwrap(); 415 | } 416 | 417 | assert!(psf.check_domain(&MatPolyOverZ::new(a.get_num_columns(), 1))); 418 | assert!(psf.check_domain(&in_domain)); 419 | } 420 | 421 | /// Ensures that `check_domain` returns false for values that are not in the domain. 422 | #[test] 423 | fn check_domain_not_in_dn() { 424 | let psf = PSFGPVRing { 425 | gp: GadgetParametersRing::init_default(8, 1024), 426 | s: compute_s(8), 427 | s_td: Q::from(1.005_f64), 428 | }; 429 | let (a, _) = psf.trap_gen(); 430 | 431 | let matrix = MatPolyOverZ::new(a.get_num_columns(), 2); 432 | let too_short = MatPolyOverZ::new(a.get_num_columns() - 1, 1); 433 | let too_long = MatPolyOverZ::new(a.get_num_columns() + 1, 1); 434 | let entry_too_large = psf.s.round() 435 | * a.get_num_columns() 436 | * 8 437 | * MatPolyOverZ::identity(a.get_num_columns(), 1); 438 | 439 | assert!(!psf.check_domain(&matrix)); 440 | assert!(!psf.check_domain(&too_long)); 441 | assert!(!psf.check_domain(&too_short)); 442 | assert!(!psf.check_domain(&entry_too_large)); 443 | } 444 | } 445 | -------------------------------------------------------------------------------- /src/sample.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Niklas Siemer 2 | // 3 | // This file is part of qFALL-crypto. 4 | // 5 | // qFALL-crypto is free software: you can redistribute it and/or modify it under 6 | // the terms of the Mozilla Public License Version 2.0 as published by the 7 | // Mozilla Foundation. See . 8 | 9 | //! This module contains anything that should be easily samplable for lattice-based 10 | //! cryptography. 11 | 12 | pub mod g_trapdoor; 13 | -------------------------------------------------------------------------------- /src/sample/g_trapdoor.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Marvin Beckmann 2 | // 3 | // This file is part of qFALL-crypto. 4 | // 5 | // qFALL-crypto is free software: you can redistribute it and/or modify it under 6 | // the terms of the Mozilla Public License Version 2.0 as published by the 7 | // Mozilla Foundation. See . 8 | 9 | //! A G-Trapdoor is a form of a trapdoor for lattices 10 | //! that allows for very efficient sampling. 11 | //! This module contains implementations for G-Trapdoors in the classical and 12 | //! in the ring setting. 13 | //! 14 | //! The main references are listed in the following 15 | //! and will be further referenced in submodules by these numbers: 16 | //! - \[1\] Micciancio, D., Peikert, C. (2012). 17 | //! Trapdoors for Lattices: Simpler, Tighter, Faster, Smaller. 18 | //! In: Pointcheval, D., Johansson, T. (eds) Advances in Cryptology – EUROCRYPT 2012. 19 | //! EUROCRYPT 2012. Lecture Notes in Computer Science, vol 7237. 20 | //! Springer, Berlin, Heidelberg. 21 | //! - \[2\] El Bansarkhani, R., Buchmann, J. (2014). Improvement and Efficient 22 | //! Implementation of a Lattice-Based Signature Scheme. In: Lange, T., Lauter, K., 23 | //! Lisoněk, P. (eds) Selected Areas in Cryptography -- SAC 2013. SAC 2013. Lecture Notes 24 | //! in Computer Science(), vol 8282. Springer, Berlin, Heidelberg. 25 | //! 26 | //! - \[3\] Gür, K.D., Polyakov, Y., Rohloff, K., Ryan, G.W. and Savas, E., 2018, 27 | //! January. Implementation and evaluation of improved Gaussian sampling for lattice 28 | //! trapdoors. In Proceedings of the 6th Workshop on Encrypted Computing & Applied 29 | //! Homomorphic Cryptography (pp. 61-71). 30 | //! - \[4\] Cash, D., Hofheinz, D., Kiltz, E., & Peikert, C. (2012). 31 | //! Bonsai trees, or how to delegate a lattice basis. Journal of cryptology, 25, 601-639. 32 | //! 33 | //! - \[5\] Chen, Yuanmi, and Phong Q. Nguyen. "BKZ 2.0: Better lattice security 34 | //! estimates." International Conference on the Theory and Application of Cryptology and 35 | //! Information Security. Berlin, Heidelberg: Springer Berlin Heidelberg, 2011. 36 | 37 | pub mod gadget_classical; 38 | pub mod gadget_default; 39 | pub mod gadget_parameters; 40 | pub mod gadget_ring; 41 | pub mod short_basis_classical; 42 | pub mod short_basis_ring; 43 | pub mod trapdoor_distribution; 44 | -------------------------------------------------------------------------------- /src/sample/g_trapdoor/gadget_classical.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Marvin Beckmann 2 | // 3 | // This file is part of qFALL-crypto. 4 | // 5 | // qFALL-crypto is free software: you can redistribute it and/or modify it under 6 | // the terms of the Mozilla Public License Version 2.0 as published by the 7 | // Mozilla Foundation. See . 8 | 9 | //! This module contains an implementation to generate a gadget trapdoor in a 10 | //! classical setting. 11 | 12 | use super::gadget_parameters::GadgetParameters; 13 | use qfall_math::{ 14 | error::MathError, 15 | integer::{MatZ, Z}, 16 | integer_mod_q::{MatZq, Zq}, 17 | traits::*, 18 | }; 19 | use std::fmt::Display; 20 | 21 | /// Generates a trapdoor according to Algorithm 1 in [\[1\]](<../index.html#:~:text=[1]>). 22 | /// - Generates the gadget matrix: `G` 23 | /// - Samples the trapdoor `R` from the specified distribution in `params` 24 | /// - Outputs `([a_bar | tag * g - a_bar * r], r)` as a tuple of `(A,R)`, 25 | /// where `R` defines a trapdoor for `A`. 26 | /// 27 | /// Parameters: 28 | /// - `params`: all gadget parameters which are required to generate the trapdoor 29 | /// - `a_bar`: the matrix defining the first part of the G-Trapdoor 30 | /// - `tag`: the tag which is hidden within the matrix `A` 31 | /// 32 | /// Returns a a parity-check matrix `a` derived from `a_bar` and its gadget-trapdoor `r` 33 | /// under a give tag `h`. 34 | /// 35 | /// # Examples 36 | /// ``` 37 | /// use qfall_crypto::sample::g_trapdoor::{gadget_parameters::GadgetParameters, gadget_classical::gen_trapdoor}; 38 | /// use qfall_math::integer_mod_q::MatZq; 39 | /// 40 | /// let params = GadgetParameters::init_default(42, 42); 41 | /// let a_bar = MatZq::sample_uniform(42, ¶ms.m_bar, ¶ms.q); 42 | /// let tag = MatZq::identity(42, 42, ¶ms.q); 43 | /// 44 | /// let (a,r) = gen_trapdoor(¶ms, &a_bar, &tag).unwrap(); 45 | /// ``` 46 | /// 47 | /// # Errors and Failures 48 | /// - Returns a [`MathError`] of type [`MismatchingMatrixDimension`](MathError::MismatchingMatrixDimension) 49 | /// if the matrices can not be concatenated due to mismatching dimensions. 50 | /// - Returns a [`MathError`] of type [`MismatchingModulus`](MathError::MismatchingModulus) 51 | /// if the matrices can not be concatenated due to mismatching moduli. 52 | /// 53 | /// # Panics ... 54 | /// - if `params.k < 1` or it does not fit into an [`i64`]. 55 | /// - if `params.n < 1`. 56 | pub fn gen_trapdoor( 57 | params: &GadgetParameters, 58 | a_bar: &MatZq, 59 | tag: &MatZq, 60 | ) -> Result<(MatZq, MatZ), MathError> { 61 | let g = gen_gadget_mat(¶ms.n, ¶ms.k, ¶ms.base); 62 | let r = params 63 | .distribution 64 | .sample(¶ms.m_bar, &(¶ms.n * ¶ms.k)); 65 | // set A = [A_bar | HG - A_bar R] 66 | let a = a_bar.concat_horizontal(&(tag * g - a_bar * &r))?; 67 | Ok((a, r)) 68 | } 69 | 70 | /// Generates a gadget matrix based on its definition in [\[1\]](<../index.html#:~:text=[1]>). 71 | /// This corresponds to `I_n \oplus g^t` where `g` is a gadget vector for the `base`. 72 | /// 73 | /// Parameters: 74 | /// - `n`: the size of the identity matrix, with which the tensor product is defined 75 | /// - `k`: the size of the gadget vector 76 | /// - `base`: the base with which the entries in the gadget vector are defined 77 | /// 78 | /// Returns a gadget matrix of size `n*nk` with `base` as the base for the gadget vector. 79 | /// 80 | /// # Examples 81 | /// ``` 82 | /// use qfall_crypto::sample::g_trapdoor::gadget_classical::gen_gadget_mat; 83 | /// use qfall_math::integer::Z; 84 | /// 85 | /// let g = gen_gadget_mat(4, 4, &Z::from(2)); 86 | /// ``` 87 | /// 88 | /// # Panics ... 89 | /// - if `k < 1` or it does not fit into an [`i64`]. 90 | /// - if `n < 1`. 91 | pub fn gen_gadget_mat( 92 | n: impl TryInto + Display + Clone, 93 | k: impl TryInto + Display, 94 | base: &Z, 95 | ) -> MatZ { 96 | let gadget_vec = gen_gadget_vec(k, base); 97 | let identity = MatZ::identity(n.clone(), n); 98 | identity.tensor_product(&gadget_vec.transpose()) 99 | } 100 | 101 | /// Generates a gadget vector based on its definition in [\[1\]](<../index.html#:~:text=[1]>). 102 | /// This corresponds to a vector `(base ^0, base^1, ..., base^{k-1})` 103 | /// 104 | /// Parameters: 105 | /// - `k`: the size of the gadget vector 106 | /// - `base`: the base with which the entries in the gadget vector are defined 107 | /// 108 | /// Returns a gadget vector of length `k` with `base` as its base. 109 | /// 110 | /// # Examples 111 | /// ``` 112 | /// use qfall_crypto::sample::g_trapdoor::gadget_classical::gen_gadget_vec; 113 | /// use qfall_math::integer::Z; 114 | /// 115 | /// let g = gen_gadget_vec(4, &Z::from(2)); 116 | /// ``` 117 | /// 118 | /// # Panics ... 119 | /// - if `k < 1` or it does not fit into an [`i64`]. 120 | pub fn gen_gadget_vec(k: impl TryInto + Display, base: &Z) -> MatZ { 121 | let mut out = MatZ::new(k, 1); 122 | for i in 0..out.get_num_rows() { 123 | out.set_entry(i, 0, &base.pow(i).unwrap()).unwrap(); 124 | } 125 | out 126 | } 127 | 128 | /// Computes an arbitrary solution for `g^t x = value mod q`. 129 | /// 130 | /// Parameters: 131 | /// - `value`: the matrix for which a solution has to be computed 132 | /// - `k`: the length of a gadget vector 133 | /// - `base`: the base with which the gadget vector is defined 134 | /// 135 | /// Returns an arbitrary solution for `g^tx = value mod q` 136 | /// 137 | /// # Examples 138 | /// ``` 139 | /// use qfall_math::{integer::{Z, MatZ}, integer_mod_q::Zq, traits::MatrixGetEntry}; 140 | /// use qfall_crypto::sample::g_trapdoor::gadget_classical::{find_solution_gadget_vec, gen_gadget_vec}; 141 | /// use std::str::FromStr; 142 | /// 143 | /// let k = Z::from(5); 144 | /// let base = Z::from(3); 145 | /// let value = Zq::from((29,125)); 146 | /// 147 | /// let sol = find_solution_gadget_vec(&value, &k, &base); 148 | /// 149 | /// assert_eq!( 150 | /// value.get_representative_least_absolute_residue(), 151 | /// (gen_gadget_vec(&k, &base).transpose() * sol) 152 | /// .get_entry(0, 0) 153 | /// .unwrap() 154 | /// ) 155 | /// ``` 156 | /// 157 | /// # Panics ... 158 | /// - if the modulus of the value is greater than `base^k`. 159 | pub fn find_solution_gadget_vec(value: &Zq, k: &Z, base: &Z) -> MatZ { 160 | if base.pow(k).unwrap() < value.get_mod() { 161 | panic!("The modulus is too large, the value is potentially not representable."); 162 | } 163 | 164 | let mut value = value.get_representative_least_nonnegative_residue(); 165 | let mut out = MatZ::new(k, 1); 166 | for i in 0..out.get_num_rows() { 167 | let val_i = &value % base; 168 | out.set_entry(i, 0, &val_i).unwrap(); 169 | value = (value - val_i).div_exact(base).unwrap(); 170 | } 171 | out 172 | } 173 | 174 | /// Computes an arbitrary solution for `GX = value mod q`. 175 | /// 176 | /// Computes a entrywise solution using the structure of the gadget matrix to its 177 | /// advantage and utilizing `find_solution_gadget_vec`. 178 | /// 179 | /// Parameters: 180 | /// - `value`: the matrix for which a solution has to be computed 181 | /// - `k`: the length of a gadget vector 182 | /// - `base`: the base with which the gadget vector is defined 183 | /// 184 | /// Returns an arbitrary solution for `GX = value mod q`. 185 | /// 186 | /// # Examples 187 | /// ``` 188 | /// use qfall_math::integer::Z; 189 | /// use qfall_math::integer::MatZ; 190 | /// use qfall_math::integer_mod_q::MatZq; 191 | /// use qfall_crypto::sample::g_trapdoor::gadget_classical::find_solution_gadget_mat; 192 | /// use qfall_crypto::sample::g_trapdoor::gadget_classical::gen_gadget_mat; 193 | /// use std::str::FromStr; 194 | /// 195 | /// let k = Z::from(5); 196 | /// let base = Z::from(3); 197 | /// let value = MatZq::from_str("[[1, 42],[2, 30],[3, 12]] mod 125").unwrap(); 198 | /// 199 | /// let sol = find_solution_gadget_mat(&value, &k, &base); 200 | /// 201 | /// assert_eq!( 202 | /// value.get_representative_least_absolute_residue(), 203 | /// gen_gadget_mat(3, &k, &base) * sol 204 | /// ) 205 | /// ``` 206 | /// 207 | /// # Panics ... 208 | /// - if the modulus of the value is greater than `base^k`. 209 | pub fn find_solution_gadget_mat(value: &MatZq, k: &Z, base: &Z) -> MatZ { 210 | let mut out = MatZ::new(k * value.get_num_rows(), value.get_num_columns()); 211 | for i in 0..value.get_num_columns() as usize { 212 | let mut _out: MatZ = find_solution_gadget_vec(&value.get_entry(0, i).unwrap(), k, base); 213 | for j in 1..value.get_num_rows() as usize { 214 | let sol_j = find_solution_gadget_vec(&value.get_entry(j, i).unwrap(), k, base); 215 | _out = _out.concat_vertical(&sol_j).unwrap(); 216 | } 217 | out.set_column(i, &_out, 0).unwrap(); 218 | } 219 | out 220 | } 221 | 222 | #[cfg(test)] 223 | mod test_gen_gadget_vec { 224 | use crate::sample::g_trapdoor::gadget_classical::gen_gadget_vec; 225 | use qfall_math::integer::{MatZ, Z}; 226 | use std::str::FromStr; 227 | 228 | /// Assure that the gadget vector with base `2` and length `5` works correctly. 229 | #[test] 230 | fn correctness_base_2() { 231 | let gadget_vec = gen_gadget_vec(5, &Z::from(2)); 232 | 233 | let vec = MatZ::from_str("[[1],[2],[4],[8],[16]]").unwrap(); 234 | assert_eq!(vec, gadget_vec); 235 | } 236 | 237 | /// Assure that the gadget vector with base `5` and length `4` works correctly. 238 | #[test] 239 | fn correctness_base_5() { 240 | let gadget_vec = gen_gadget_vec(4, &Z::from(5)); 241 | 242 | let vec = MatZ::from_str("[[1],[5],[25],[125]]").unwrap(); 243 | assert_eq!(vec, gadget_vec); 244 | } 245 | } 246 | 247 | #[cfg(test)] 248 | mod test_gen_gadget_mat { 249 | use super::gen_gadget_mat; 250 | use qfall_math::integer::{MatZ, Z}; 251 | use std::str::FromStr; 252 | 253 | /// Assure that the gadget matrix with gadget vector `[1, 2, 4]^t`(base 3) and 254 | /// `I_3` works correctly. 255 | #[test] 256 | fn correctness_base_2_3x3() { 257 | let gadget_mat = gen_gadget_mat(3, 3, &Z::from(2)); 258 | 259 | let mat_str = "[[1, 2, 4, 0, 0, 0, 0, 0, 0],\ 260 | [0, 0, 0, 1, 2, 4, 0, 0, 0],\ 261 | [0, 0, 0, 0, 0, 0, 1, 2, 4]]"; 262 | 263 | let mat = MatZ::from_str(mat_str).unwrap(); 264 | assert_eq!(mat, gadget_mat); 265 | } 266 | 267 | /// Assure that the gadget matrix with gadget vector `[1, 3, 9, 27, 81]^t`(base 3) and 268 | /// `I_2` works correctly. 269 | #[test] 270 | fn correctness_base_3_2x5() { 271 | let gadget_mat = gen_gadget_mat(2, 5, &Z::from(3)); 272 | 273 | let mat_str = "[[1, 3, 9, 27, 81, 0, 0, 0, 0, 0],\ 274 | [ 0, 0, 0, 0, 0, 1, 3, 9, 27, 81]]"; 275 | 276 | let mat = MatZ::from_str(mat_str).unwrap(); 277 | assert_eq!(mat, gadget_mat); 278 | } 279 | } 280 | 281 | #[cfg(test)] 282 | mod test_gen_trapdoor { 283 | use super::gen_trapdoor; 284 | use crate::sample::g_trapdoor::{ 285 | gadget_classical::gen_gadget_mat, gadget_parameters::GadgetParameters, 286 | }; 287 | use qfall_math::{ 288 | integer::{MatZ, Z}, 289 | integer_mod_q::{MatZq, Modulus}, 290 | traits::*, 291 | }; 292 | 293 | /// Assure that the trapdoor `r` returned from [`gen_trapdoor`] is actually a 294 | /// trapdoor for `a`. 295 | #[test] 296 | fn is_trapdoor_without_tag() { 297 | let params = GadgetParameters::init_default(42, 32); 298 | let a_bar = MatZq::sample_uniform(42, ¶ms.m_bar, ¶ms.q); 299 | let tag = MatZq::identity(42, 42, ¶ms.q); 300 | 301 | // call gen_trapdoor to get matrix a and its 'trapdoor' r 302 | let (a, r) = gen_trapdoor(¶ms, &a_bar, &tag).unwrap(); 303 | 304 | // generate the trapdoor for a from r as trapdoor = [[r],[I]] 305 | let trapdoor = r 306 | .concat_vertical(&MatZ::identity( 307 | a.get_num_columns() - r.get_num_rows(), 308 | r.get_num_columns(), 309 | )) 310 | .unwrap(); 311 | 312 | // ensure G = A*trapdoor (definition of a trapdoor) 313 | let gadget_mat = gen_gadget_mat(¶ms.n, ¶ms.k, &Z::from(2)); 314 | assert_eq!( 315 | MatZq::from((&gadget_mat, ¶ms.q)), 316 | a * MatZq::from((&trapdoor, ¶ms.q)) 317 | ); 318 | } 319 | 320 | /// Assure that the trapdoor `r` returned from [`gen_trapdoor`] is actually a 321 | /// trapdoor for `a`. 322 | #[test] 323 | fn is_trapdoor_with_tag() { 324 | let modulus = Modulus::from(32); 325 | let params = GadgetParameters::init_default(42, &modulus); 326 | let a_bar = MatZq::sample_uniform(42, ¶ms.m_bar, ¶ms.q); 327 | // calculate an invertible tag in Z_q^{n × n} 328 | let tag = calculate_invertible_tag(42, &modulus); 329 | 330 | // call gen_trapdoor to get matrix a and its 'trapdoor' r 331 | let (a, r) = gen_trapdoor(¶ms, &a_bar, &tag).unwrap(); 332 | 333 | // generate the trapdoor for a from r as trapdoor = [[r],[I]] 334 | let trapdoor = r 335 | .concat_vertical(&MatZ::identity( 336 | a.get_num_columns() - r.get_num_rows(), 337 | r.get_num_columns(), 338 | )) 339 | .unwrap(); 340 | 341 | // ensure tag*G = A*trapdoor (definition of a trapdoor) 342 | let gadget_mat = gen_gadget_mat(¶ms.n, ¶ms.k, &Z::from(2)); 343 | assert_eq!( 344 | tag * MatZq::from((&gadget_mat, &modulus)), 345 | a * MatZq::from((&trapdoor, &modulus)) 346 | ); 347 | } 348 | 349 | /// Generates an invertible tag matrix (generates a diagonal matrix) 350 | fn calculate_invertible_tag(size: i64, modulus: &Modulus) -> MatZq { 351 | let max_value = Z::from(modulus); 352 | let mut out = MatZq::identity(size, size, modulus); 353 | // create a diagonal matrix with random values (because it is a diagonal matrix 354 | // with `1` on the diagonal, it is always invertible) 355 | for row in 0..size { 356 | for column in 0..size { 357 | if row < column { 358 | out.set_entry(row, column, Z::sample_uniform(0, &max_value).unwrap()) 359 | .unwrap(); 360 | } 361 | } 362 | } 363 | out 364 | } 365 | } 366 | 367 | #[cfg(test)] 368 | mod test_find_solution_gadget { 369 | use super::find_solution_gadget_vec; 370 | use crate::sample::g_trapdoor::gadget_classical::{ 371 | find_solution_gadget_mat, gen_gadget_mat, gen_gadget_vec, 372 | }; 373 | use qfall_math::{ 374 | integer::Z, 375 | integer_mod_q::{MatZq, Zq}, 376 | traits::MatrixGetEntry, 377 | }; 378 | use std::str::FromStr; 379 | 380 | /// Ensure that the found solution is actually correct. 381 | #[test] 382 | fn returns_correct_solution_vec() { 383 | let k = Z::from(5); 384 | let base = Z::from(3); 385 | for i in 0..124 { 386 | let value = Zq::from((i, 125)); 387 | 388 | let sol = find_solution_gadget_vec(&value, &k, &base); 389 | 390 | assert_eq!( 391 | value.get_representative_least_nonnegative_residue(), 392 | (gen_gadget_vec(&k, &base).transpose() * sol) 393 | .get_entry(0, 0) 394 | .unwrap() 395 | ) 396 | } 397 | } 398 | 399 | /// Ensure that the found solution is actually correct. 400 | #[test] 401 | fn returns_correct_solution_mat() { 402 | let k = Z::from(5); 403 | let base = Z::from(3); 404 | let value = MatZq::from_str("[[1, 42],[2, 40],[3, 90]] mod 125").unwrap(); 405 | 406 | let sol = find_solution_gadget_mat(&value, &k, &base); 407 | 408 | assert_eq!( 409 | value.get_representative_least_nonnegative_residue(), 410 | gen_gadget_mat(3, &k, &base) * sol 411 | ) 412 | } 413 | } 414 | -------------------------------------------------------------------------------- /src/sample/g_trapdoor/gadget_default.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Marvin Beckmann 2 | // 3 | // This file is part of qFALL-crypto. 4 | // 5 | // qFALL-crypto is free software: you can redistribute it and/or modify it under 6 | // the terms of the Mozilla Public License Version 2.0 as published by the 7 | // Mozilla Foundation. See . 8 | 9 | //! This module contains functions to generate G-Trapdoors with default parameters. 10 | 11 | use crate::sample::g_trapdoor::{ 12 | gadget_classical::gen_trapdoor, 13 | gadget_parameters::{GadgetParameters, GadgetParametersRing}, 14 | gadget_ring::gen_trapdoor_ring_lwe, 15 | }; 16 | use qfall_math::{ 17 | integer::{MatPolyOverZ, MatZ, PolyOverZ, Z}, 18 | integer_mod_q::{MatPolynomialRingZq, MatZq, Modulus}, 19 | rational::Q, 20 | }; 21 | 22 | /// Computes a trapdoor with default values. 23 | /// 24 | /// - `params` is computed using [`GadgetParameters::init_default`]. 25 | /// - `tag = I_n` is taken from [\[1\]](): Algorithm 1 26 | /// 27 | /// Parameters: 28 | /// - `n`: the security parameter 29 | /// - `q`: the modulus for the trapdoor 30 | /// 31 | /// Returns a matrix `a` and its gadget-trapdoor `r` as in [\[1\]](): Algorithm 1 for some fixed set of parameters [`GadgetParameters::init_default`]. 32 | /// 33 | /// # Examples 34 | /// ``` 35 | /// use qfall_crypto::sample::g_trapdoor::gadget_default::gen_trapdoor_default; 36 | /// 37 | /// let (a,r) = gen_trapdoor_default(42, 101); 38 | /// ``` 39 | /// 40 | /// # Panics ... 41 | /// - if the security parameter `n` is not in `[1, i64::MAX]`. 42 | /// - if `q <= 1`. 43 | pub fn gen_trapdoor_default(n: impl Into, q: impl Into) -> (MatZq, MatZ) { 44 | // panic if n < 1 (security parameter must be positive) 45 | let n = n.into(); 46 | assert!(n >= Z::ONE); 47 | 48 | let params = GadgetParameters::init_default(n, q); 49 | 50 | // a_bar <-$ Z_q^{n * m_bar} 51 | let a_bar = MatZq::sample_uniform(¶ms.n, ¶ms.m_bar, ¶ms.q); 52 | 53 | // tag = I_n 54 | let tag = MatZq::identity(¶ms.n, ¶ms.n, ¶ms.q); 55 | 56 | // we can unwrap, as we compute the parameters on our own and 57 | // they should always work 58 | gen_trapdoor(¶ms, &a_bar, &tag).unwrap() 59 | } 60 | 61 | /// Computes a trapdoor with default values in a ring setting. 62 | /// 63 | /// - `params` is computed using [`GadgetParametersRing::init_default`]. 64 | /// 65 | /// Parameters: 66 | /// - `n`: the security parameter 67 | /// - `q`: the modulus for the trapdoor 68 | /// 69 | /// Returns a matrix `a` and its gadget-trapdoor `(r,e)` as in [\[2\]](): 70 | /// Construction 1 for some fixed set of parameters [`GadgetParameters::init_default`]. 71 | /// 72 | /// # Examples 73 | /// ``` 74 | /// use qfall_crypto::sample::g_trapdoor::gadget_default::gen_trapdoor_ring_default; 75 | /// 76 | /// let (a,r, e) = gen_trapdoor_ring_default(100, 29, 10);; 77 | /// ``` 78 | /// 79 | /// # Panics... 80 | /// - if the security parameter `n` is not in `[1, i64::MAX]`. 81 | /// - if `q <= 1`. 82 | pub fn gen_trapdoor_ring_default( 83 | n: impl Into, 84 | q: impl Into, 85 | s: impl Into, 86 | ) -> (MatPolynomialRingZq, MatPolyOverZ, MatPolyOverZ) { 87 | // panic if n < 1 (security parameter must be positive) 88 | let n = n.into(); 89 | assert!(n >= Z::ONE); 90 | let s = s.into(); 91 | 92 | let params = GadgetParametersRing::init_default(n, q); 93 | 94 | // a_bar <-$ Zq[X]^n 95 | let a_bar = PolyOverZ::sample_uniform(¶ms.n, 0, params.modulus.get_q()).unwrap(); 96 | 97 | // we can unwrap, as we compute the parameters on our own and 98 | // they should always work 99 | gen_trapdoor_ring_lwe(¶ms, &a_bar, &s).unwrap() 100 | } 101 | 102 | #[cfg(test)] 103 | mod test_gen_trapdoor_default { 104 | use super::gen_trapdoor_default; 105 | use crate::sample::g_trapdoor::gadget_classical::gen_gadget_mat; 106 | use qfall_math::{ 107 | integer::{MatZ, Z}, 108 | traits::{Concatenate, MatrixDimensions, Pow}, 109 | }; 110 | 111 | /// Ensures that the default parameters are used correctly and the expected 112 | /// dimensions are returned. 113 | #[test] 114 | fn correct_default_dimensions() { 115 | for n in [5, 10, 50] { 116 | for k in [5, 10] { 117 | let q = 2_i64.pow(k); 118 | 119 | let n_log_2_pow_2 = Z::from(n).log_ceil(2).unwrap().pow(2).unwrap(); 120 | let m_bar = n * k + n_log_2_pow_2; 121 | let m = &m_bar + n * k; 122 | 123 | let (a, r) = gen_trapdoor_default(n, q); 124 | 125 | assert_eq!(n as i64, a.get_num_rows()); 126 | assert_eq!(m, Z::from(a.get_num_columns())); 127 | 128 | assert_eq!(m_bar, Z::from(r.get_num_rows())); 129 | assert_eq!((n * k) as i64, r.get_num_columns()); 130 | } 131 | } 132 | } 133 | 134 | /// Ensures that for several parameter choices the generated G-Trapdoor is 135 | /// actually a trapdoor. 136 | #[test] 137 | fn ensure_is_trapdoor() { 138 | for n in [5, 10, 25] { 139 | for k in [5, 10] { 140 | let q = 2_i64.pow(k); 141 | 142 | let (a, r) = gen_trapdoor_default(n, q); 143 | 144 | let trapdoor = r.concat_vertical(&MatZ::identity(n * k, n * k)).unwrap(); 145 | 146 | assert_eq!( 147 | gen_gadget_mat(n, k, &Z::from(2)), 148 | (a * trapdoor).get_representative_least_nonnegative_residue() 149 | ) 150 | } 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/sample/g_trapdoor/gadget_parameters.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Marvin Beckmann 2 | // 3 | // This file is part of qFALL-crypto. 4 | // 5 | // qFALL-crypto is free software: you can redistribute it and/or modify it under 6 | // the terms of the Mozilla Public License Version 2.0 as published by the 7 | // Mozilla Foundation. See . 8 | 9 | //! This module contains the [`GadgetParameters`] object, 10 | //! which contains all parameters which are needed to generate a classical G-Trapdoor. 11 | 12 | use super::trapdoor_distribution::{ 13 | PlusMinusOneZero, TrapdoorDistribution, TrapdoorDistributionRing, 14 | }; 15 | use crate::{ 16 | sample::g_trapdoor::trapdoor_distribution::SampleZ, utils::common_moduli::new_anticyclic, 17 | }; 18 | use qfall_math::{ 19 | integer::Z, 20 | integer_mod_q::{Modulus, ModulusPolynomialRingZq}, 21 | traits::Pow, 22 | }; 23 | use serde::{Deserialize, Serialize}; 24 | 25 | /// Collects all parameters which are necessary to compute a G-trapdoor. 26 | /// You can either use [`GadgetParameters::init_default`] or set all values 27 | /// and distributions yourself. 28 | /// 29 | /// Attributes: 30 | /// - `n`: the security parameter 31 | /// - `k`: the size of the gadget vector: mostly taken as `log_base(q)` 32 | /// - `m_bar`: has to be chose appropriately for regularity and for 33 | /// the distribution to be sub-Gaussian 34 | /// - `base`: the base with which the gadget-vector and matrix are generated 35 | /// - `q`: the modulus 36 | /// - `distribution`: the distribution from which the matrix `A_bar` is sampled 37 | /// 38 | /// # Examples 39 | /// ``` 40 | /// use qfall_crypto::sample::g_trapdoor::gadget_parameters::GadgetParameters; 41 | /// 42 | /// let params = GadgetParameters::init_default(42, 42); 43 | /// ``` 44 | #[derive(Serialize, Deserialize)] 45 | pub struct GadgetParameters { 46 | pub n: Z, 47 | pub k: Z, 48 | pub m_bar: Z, 49 | pub base: Z, 50 | pub q: Modulus, 51 | pub distribution: Box, 52 | } 53 | 54 | /// Collects all parameters which are necessary to compute a ring-based G-trapdoor. 55 | /// You can either use [`GadgetParametersRing::init_default`] or set all values 56 | /// and distributions yourself. 57 | /// 58 | /// Attributes: 59 | /// - `n`: the security parameter 60 | /// - `k`: the size of the gadget vector: mostly taken as `log_base(q)` 61 | /// - `m_bar`: has to be chose appropriately for regularity and for 62 | /// the distribution to be sub-Gaussian 63 | /// - `base`: the base with which the gadget-vector and matrix are generated 64 | /// - `modulus`: holds the modulus q and the polynomial that is used for reduction 65 | /// - `distribution`: the distribution from which the matrix `A_bar` is sampled 66 | /// 67 | /// # Examples 68 | /// ``` 69 | /// use qfall_crypto::sample::g_trapdoor::gadget_parameters::GadgetParametersRing; 70 | /// 71 | /// let params = GadgetParametersRing::init_default(42, 42); 72 | /// ``` 73 | #[derive(Serialize, Deserialize)] 74 | pub struct GadgetParametersRing { 75 | pub n: Z, 76 | pub k: Z, 77 | pub m_bar: Z, 78 | pub base: Z, 79 | pub modulus: ModulusPolynomialRingZq, 80 | pub distribution: Box, 81 | } 82 | 83 | impl GadgetParameters { 84 | /// Initializes default values for [`GadgetParameters`] to create a classical 85 | /// G-trapdoor. 86 | /// 87 | /// - `base = 2` is taken from [\[1\]](<../index.html#:~:text=[1]>): Theorem 1. 88 | /// - `k = log_2_ceil(q)` is taken from [\[1\]](<../index.html#:~:text=[1]>): 89 | /// Theorem 1. 90 | /// - `w = n * k`: As it is required to match the dimension of the gadget matrix, 91 | /// hence it has to equal to `n * size of gadget_vec` 92 | /// - `m_bar = n * k + log(n)^2`: is taken from [\[1\]](<../index.html#:~:text=[1]>) 93 | /// as a function satisfying `m_bar = n log q + ω(log n)` 94 | /// - the distribution is taken as [`PlusMinusOneZero`], 95 | /// see the example from [\[1\]](<../index.html#:~:text=[1]>): after statistical instantiation in section 3.2 96 | /// 97 | /// Parameters: 98 | /// - `n`: the security parameter for the generation 99 | /// - `q`: the modulus over which the TrapGen operates 100 | /// 101 | /// Returns an instantiation of default GadgetParameters based on the references mentioned above 102 | /// 103 | /// # Examples 104 | /// ``` 105 | /// use qfall_crypto::sample::g_trapdoor::gadget_parameters::GadgetParameters; 106 | /// 107 | /// let params = GadgetParameters::init_default(42, 42); 108 | /// ``` 109 | /// 110 | /// # Panics ... 111 | /// - if the security parameter `n` is not in `[1, i64::MAX]`. 112 | /// - if `q <= 1`. 113 | pub fn init_default(n: impl Into, q: impl Into) -> Self { 114 | // panic if n < 1 (security parameter must be positive) and not larger than 115 | // [`i64`] because downstream matrices can be at most that size 116 | let q = q.into(); 117 | let n: Z = n.into(); 118 | assert!(n >= Z::ONE && n <= i64::MAX); 119 | 120 | let base = Z::from(2); 121 | let log_q = Z::from(&q).log_ceil(&base).unwrap(); 122 | let n_log_q = &n * &log_q; 123 | let log_n = n.log_ceil(&base).unwrap(); 124 | let m_bar = &n_log_q + &log_n.pow(2).unwrap(); 125 | Self { 126 | n, 127 | k: log_q, 128 | m_bar, 129 | base, 130 | q, 131 | distribution: Box::new(PlusMinusOneZero), 132 | } 133 | } 134 | } 135 | 136 | impl GadgetParametersRing { 137 | /// Initializes default values for [`GadgetParametersRing`] to create a ring-based 138 | /// G-trapdoor. The parameters follow the ones in 139 | /// [\[3\]](<../index.html#:~:text=[3]>): Algorithm 1. 140 | /// 141 | /// - `base = 2` is taken from [\[3\]](<../index.html#:~:text=[3]>) 142 | /// - `k = log_2_ceil(q)` is taken from [\[3\]](<../index.html#:~:text=[3]>): 143 | /// Algorithm 1. 144 | /// - `m_bar = 2 + k`: is taken from [\[3\]](<../index.html#:~:text=[3]>) 145 | /// - the distribution is taken as [`SampleZ`], 146 | /// as in [\[3\]](<../index.html#:~:text=[3]>) 147 | /// - the modulus is defined by `X^n +1 mod q` as in [\[3\]](<../index.html#:~:text=[3]>) 148 | /// 149 | /// Parameters: 150 | /// - `n`: the security parameter for the generation 151 | /// - `q`: the modulus over which the TrapGen operates 152 | /// 153 | /// Returns an instantiation of default GadgetParameters based on the references mentioned above 154 | /// 155 | /// # Examples 156 | /// ``` 157 | /// use qfall_crypto::sample::g_trapdoor::gadget_parameters::GadgetParametersRing; 158 | /// 159 | /// let params = GadgetParametersRing::init_default(42, 42); 160 | /// ``` 161 | /// 162 | /// # Panics ... 163 | /// - if the security parameter `n` is not in `[1, i64::MAX]`. 164 | /// - if `q <= 1`. 165 | pub fn init_default(n: impl Into, q: impl Into) -> Self { 166 | // panic if n < 1 (security parameter must be positive) and not larger than 167 | // [`i64`] because downstream matrices can be at most that size 168 | let q = q.into(); 169 | let n = n.into(); 170 | assert!(n >= Z::ONE && n <= i64::MAX); 171 | 172 | let base = Z::from(2); 173 | let log_q = Z::from(&q).log_ceil(&base).unwrap(); 174 | 175 | let poly_mod = new_anticyclic(&n, &q).unwrap(); 176 | 177 | Self { 178 | n, 179 | k: log_q.clone(), 180 | m_bar: log_q + 2, 181 | base, 182 | modulus: poly_mod, 183 | distribution: Box::new(SampleZ), 184 | } 185 | } 186 | } 187 | 188 | #[cfg(test)] 189 | mod test_default_parameter { 190 | use crate::sample::g_trapdoor::gadget_parameters::GadgetParameters; 191 | use qfall_math::{integer::Z, integer_mod_q::Modulus, traits::Pow}; 192 | 193 | /// Ensure that this test fails, if the default parameters are changed. 194 | #[test] 195 | fn default_unchanged() { 196 | for n in [5, 10, 50, 100] { 197 | for k in [5, 10, 25] { 198 | let q = 2_i64.pow(k); 199 | 200 | let n_log_2_pow_2 = Z::from(n).log_ceil(2).unwrap().pow(2).unwrap(); 201 | let m_bar = n * k + n_log_2_pow_2; 202 | 203 | let gp = GadgetParameters::init_default(n, q); 204 | 205 | assert_eq!(Z::from(2), gp.base); 206 | assert_eq!(Z::from(k), gp.k); 207 | assert_eq!(m_bar, gp.m_bar); 208 | assert_eq!(Z::from(n), gp.n); 209 | assert_eq!(Modulus::from(q), gp.q); 210 | } 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/sample/g_trapdoor/gadget_ring.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Marvin Beckmann 2 | // 3 | // This file is part of qFALL-crypto. 4 | // 5 | // qFALL-crypto is free software: you can redistribute it and/or modify it under 6 | // the terms of the Mozilla Public License Version 2.0 as published by the 7 | // Mozilla Foundation. See . 8 | 9 | //! This module contains an implementation to generate a gadget trapdoor in a 10 | //! ring-lwe setting. 11 | 12 | use super::{gadget_classical::find_solution_gadget_mat, gadget_parameters::GadgetParametersRing}; 13 | use qfall_math::{ 14 | error::MathError, 15 | integer::{MatPolyOverZ, PolyOverZ, Z}, 16 | integer_mod_q::{MatPolynomialRingZq, MatZq, PolynomialRingZq}, 17 | rational::Q, 18 | traits::{ 19 | Concatenate, IntoCoefficientEmbedding, MatrixGetEntry, MatrixSetEntry, Pow, SetCoefficient, 20 | }, 21 | }; 22 | use std::fmt::Display; 23 | 24 | /// Generates a trapdoor according to Construction 1 in [\[2\]](<../index.html#:~:text=[2]>). 25 | /// Namely: 26 | /// - Generates the gadget matrix: `G` 27 | /// - Samples the trapdoor `R` from the specified distribution in `params` 28 | /// - Outputs 29 | /// `([1 | a_bar | g_1 - (a_bar * r_1 + e_1) | ... | g_k - (a_bar * r_k + e_k) ], r, e)` 30 | /// as a tuple of `(A,R)`, where `R` defines a trapdoor for `A`. 31 | /// 32 | /// Parameters: 33 | /// - `params`: all gadget parameters which are required to generate the trapdoor 34 | /// - `a_bar`: the matrix defining the second part of the G-Trapdoor 35 | /// - `tag`: the tag which is hidden within the matrix `A` 36 | /// - `s`: defining the deviation of the distribution from which `r` and `e` is sampled 37 | /// 38 | /// Returns a a parity-check matrix `a` derived from `a_bar` and its gadget-trapdoor 39 | /// `r` under a give tag `h`. 40 | /// 41 | /// # Examples 42 | /// ``` 43 | /// use qfall_crypto::sample::g_trapdoor::{gadget_parameters::GadgetParametersRing, gadget_ring::gen_trapdoor_ring_lwe}; 44 | /// use qfall_math::integer::PolyOverZ; 45 | /// 46 | /// let params = GadgetParametersRing::init_default(8, 17); 47 | /// let a_bar = PolyOverZ::sample_uniform(¶ms.n, 0, ¶ms.modulus.get_q()).unwrap(); 48 | /// 49 | /// let (a, r, e) = gen_trapdoor_ring_lwe(¶ms, &a_bar, 10).unwrap(); 50 | /// ``` 51 | /// 52 | /// # Errors and Failures 53 | /// - Returns a [`MathError`] of type [`MismatchingMatrixDimension`](MathError::MismatchingMatrixDimension) 54 | /// if the matrices can not be concatenated due to mismatching dimensions. 55 | /// - Returns a [`MathError`] of type [`OutOfBounds`](MathError::OutOfBounds) 56 | /// if row or column are greater than the matrix size. 57 | /// 58 | /// # Panics ... 59 | /// - if `params.k < 1` or it does not fit into an [`i64`]. 60 | /// - if `params.n < 1`. 61 | pub fn gen_trapdoor_ring_lwe( 62 | params: &GadgetParametersRing, 63 | a_bar: &PolyOverZ, 64 | s: impl Into, 65 | ) -> Result<(MatPolynomialRingZq, MatPolyOverZ, MatPolyOverZ), MathError> { 66 | let s = s.into(); 67 | // Sample `r` and `e` using a provided distribution 68 | let r = params.distribution.sample(¶ms.n, ¶ms.k, &s); 69 | let e = params.distribution.sample(¶ms.n, ¶ms.k, &s); 70 | 71 | // compute the parity check matrix 72 | // `A = [1 | a | g^t - ar + e]` 73 | let mut big_a = MatPolyOverZ::new(1, 2); 74 | big_a.set_entry(0, 0, &PolyOverZ::from(1))?; 75 | big_a.set_entry(0, 1, a_bar)?; 76 | let g = gen_gadget_ring(¶ms.k, ¶ms.base); 77 | big_a = big_a.concat_horizontal(&(g.transpose() - (a_bar * &r + &e)))?; 78 | 79 | Ok((MatPolynomialRingZq::from((&big_a, ¶ms.modulus)), r, e)) 80 | } 81 | 82 | /// Generates a gadget vector based on its definition in [\[3\]](<../index.html#:~:text=[3]>). 83 | /// This corresponds to a vector `(base ^0, base^1, ..., base^{k-1})` where each entry 84 | /// is a constant polynomial. 85 | /// 86 | /// Parameters: 87 | /// - `k`: the size of the gadget vector 88 | /// - `base`: the base with which the entries in the gadget vector are defined 89 | /// 90 | /// Returns a gadget vector of length `k` with `base` as its base. 91 | /// 92 | /// # Examples 93 | /// ``` 94 | /// use qfall_crypto::sample::g_trapdoor::gadget_ring::gen_gadget_ring; 95 | /// use qfall_math::integer::Z; 96 | /// 97 | /// let g = gen_gadget_ring(4, &Z::from(2)); 98 | /// ``` 99 | /// 100 | /// # Panics ... 101 | /// - if `k < 1` or it does not fit into an [`i64`]. 102 | pub fn gen_gadget_ring(k: impl TryInto + Display, base: &Z) -> MatPolyOverZ { 103 | let mut out = MatPolyOverZ::new(k, 1); 104 | let mut i: i64 = 0; 105 | while out 106 | .set_entry(i, 0, &PolyOverZ::from(base.pow(i).unwrap())) 107 | .is_ok() 108 | { 109 | i += 1; 110 | } 111 | out 112 | } 113 | 114 | /// Computes an arbitrary solution for ` = value/(Modulus)`. 115 | /// 116 | /// Parameters: 117 | /// - `u`: the element for which a solution has to be computed 118 | /// - `k`: the length of a gadget vector 119 | /// - `base`: the base with which the gadget vector is defined 120 | /// 121 | /// Returns an arbitrary solution for ` = value/(Modulus)` 122 | /// 123 | /// # Examples 124 | /// ``` 125 | /// use qfall_math::integer::PolyOverZ; 126 | /// use qfall_math::integer_mod_q::PolynomialRingZq; 127 | /// use qfall_crypto::sample::g_trapdoor::gadget_ring::gen_gadget_ring; 128 | /// use qfall_crypto::sample::g_trapdoor::gadget_ring::find_solution_gadget_ring; 129 | /// use qfall_crypto::sample::g_trapdoor::gadget_parameters::GadgetParametersRing; 130 | /// use qfall_math::integer_mod_q::MatPolynomialRingZq; 131 | /// use std::str::FromStr; 132 | /// 133 | /// let gp = GadgetParametersRing::init_default(10, 128); 134 | /// 135 | /// let gadget = gen_gadget_ring(&gp.k, &gp.base); 136 | /// let gadget = MatPolynomialRingZq::from((&gadget, &gp.modulus)); 137 | /// 138 | /// let u = PolyOverZ::from_str("10 5 124 12 14 14 1 2 4 1 5").unwrap(); 139 | /// let u = PolynomialRingZq::from((&u, &gp.modulus)); 140 | /// 141 | /// let solution = find_solution_gadget_ring(&u, &gp.k, &gp.base); 142 | /// let solution = MatPolynomialRingZq::from((&solution, &gp.modulus)); 143 | /// assert_eq!(u, gadget.dot_product(&solution).unwrap()) 144 | /// ``` 145 | /// 146 | /// # Panics ... 147 | /// - if the modulus of the value is greater than `base^k`. 148 | pub fn find_solution_gadget_ring(u: &PolynomialRingZq, k: &Z, base: &Z) -> MatPolyOverZ { 149 | let k_i64 = i64::try_from(k).unwrap(); 150 | let modulus = u.get_mod(); 151 | let size = modulus.get_degree(); 152 | let value = u 153 | .get_representative_least_nonnegative_residue() 154 | .into_coefficient_embedding(size); 155 | let value = MatZq::from((&value, modulus.get_q())); 156 | 157 | let classical_sol = find_solution_gadget_mat(&value, k, base); 158 | 159 | let mut out = MatPolyOverZ::new(1, k); 160 | for i in 0..k_i64 { 161 | let mut poly = PolyOverZ::default(); 162 | for j in 0..size { 163 | let entry = classical_sol.get_entry(i + j * k_i64, 0).unwrap(); 164 | poly.set_coeff(j, &entry).unwrap(); 165 | } 166 | out.set_entry(0, i, &poly).unwrap(); 167 | } 168 | out 169 | } 170 | 171 | #[cfg(test)] 172 | mod test_gen_trapdoor_ring { 173 | 174 | use crate::sample::g_trapdoor::{ 175 | gadget_parameters::GadgetParametersRing, gadget_ring::gen_trapdoor_ring_lwe, 176 | }; 177 | use qfall_math::{ 178 | integer::{MatPolyOverZ, PolyOverZ, Z}, 179 | integer_mod_q::MatPolynomialRingZq, 180 | traits::{Concatenate, GetCoefficient, MatrixDimensions, MatrixGetEntry, Pow}, 181 | }; 182 | 183 | /// Computes a trapdoor using the given secrets `(r,e)` 184 | fn compute_trapdoor(r: &MatPolyOverZ, e: &MatPolyOverZ, k: &Z) -> MatPolyOverZ { 185 | let i_k = MatPolyOverZ::identity(k, k); 186 | 187 | e.concat_vertical(r).unwrap().concat_vertical(&i_k).unwrap() 188 | } 189 | 190 | /// Assure that the trapdoor `r` returned from [`gen_trapdoor`] is actually a 191 | /// trapdoor for `a`. 192 | #[test] 193 | fn is_trapdoor() { 194 | let params = GadgetParametersRing::init_default(6, 32); 195 | let a_bar = PolyOverZ::sample_uniform(¶ms.n, 0, params.modulus.get_q()).unwrap(); 196 | 197 | // call gen_trapdoor to get matrix a and its 'trapdoor' r 198 | let (a, r, e) = gen_trapdoor_ring_lwe(¶ms, &a_bar, 10).unwrap(); 199 | 200 | // generate the trapdoor for a from r as trapdoor = [[e],[r],[I]] 201 | let trapdoor = 202 | MatPolynomialRingZq::from((&compute_trapdoor(&r, &e, ¶ms.k), ¶ms.modulus)); 203 | 204 | // ensure G = A*trapdoor (definition of a trapdoor) 205 | let res: MatPolynomialRingZq = &a * &trapdoor; 206 | 207 | assert_eq!(params.k, Z::from(res.get_num_columns())); 208 | assert_eq!(1, res.get_num_rows()); 209 | 210 | for i in 0..i64::try_from(¶ms.k).unwrap() { 211 | let res_entry: PolyOverZ = res.get_entry(0, i).unwrap(); 212 | assert_eq!(res_entry.get_coeff(0).unwrap(), params.base.pow(i).unwrap()) 213 | } 214 | } 215 | } 216 | 217 | #[cfg(test)] 218 | mod test_find_solution_gadget_ring { 219 | use super::{find_solution_gadget_ring, gen_gadget_ring}; 220 | use crate::sample::g_trapdoor::gadget_parameters::GadgetParametersRing; 221 | use qfall_math::{ 222 | integer::PolyOverZ, 223 | integer_mod_q::{MatPolynomialRingZq, PolynomialRingZq}, 224 | }; 225 | use std::str::FromStr; 226 | 227 | /// Ensures that the algorithm finds a correct solution such that ` = u`. 228 | #[test] 229 | fn is_correct_solution() { 230 | let gp = GadgetParametersRing::init_default(3, 32); 231 | 232 | let gadget = gen_gadget_ring(&gp.k, &gp.base); 233 | let gadget = MatPolynomialRingZq::from((&gadget, &gp.modulus)); 234 | 235 | let u = PolyOverZ::from_str("10 5 124 12 14 14 1 2 4 1 5").unwrap(); 236 | let u = PolynomialRingZq::from((&u, &gp.modulus)); 237 | 238 | let solution = find_solution_gadget_ring(&u, &gp.k, &gp.base); 239 | let solution = MatPolynomialRingZq::from((&solution, &gp.modulus)); 240 | 241 | assert_eq!(u, gadget.dot_product(&solution).unwrap()) 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/sample/g_trapdoor/trapdoor_distribution.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Marvin Beckmann 2 | // 3 | // This file is part of qFALL-crypto. 4 | // 5 | // qFALL-crypto is free software: you can redistribute it and/or modify it under 6 | // the terms of the Mozilla Public License Version 2.0 as published by the 7 | // Mozilla Foundation. See . 8 | 9 | //! This module contains all implementation of TrapdoorDistributions from 10 | //! which the matrix `A_bar` is sampled in the trapdoor generation algorithm. 11 | 12 | use qfall_math::{ 13 | integer::{MatPolyOverZ, MatZ, PolyOverZ, Z}, 14 | rational::Q, 15 | traits::MatrixSetEntry, 16 | }; 17 | use serde::{Deserialize, Serialize}; 18 | 19 | /// This trait should be implemented by all distributions which should be 20 | /// used to generate a trapdoor. 21 | #[typetag::serde] 22 | pub trait TrapdoorDistribution { 23 | /// Sample from a matrix according to a predefined distribution. 24 | /// 25 | /// Parameters: 26 | /// - `m_bar`: number of rows of the matrix that is sampled 27 | /// - `w`: number of columns of the matrix that is sampled 28 | /// 29 | /// Returns a matrix which is sampled according to the defined distribution. 30 | fn sample(&self, m_bar: &Z, w: &Z) -> MatZ; 31 | } 32 | 33 | /// This trait should be implemented by all distributions which should be 34 | /// used to generate a trapdoor over a ring. 35 | #[typetag::serde] 36 | pub trait TrapdoorDistributionRing { 37 | /// Sample a matrix of polynomials of length `n` with entries sampled 38 | /// using a predefined distribution. 39 | /// 40 | /// Parameters: 41 | /// - `n`: length of the polynomial 42 | /// - `nr_cols`: number of columns of the matrix 43 | /// - `s`: the Gaussian parameter used for SampleZ 44 | /// 45 | /// Returns a matrix where each entry is a polynomials of length `n`, sampled 46 | /// using the defined distribution. 47 | fn sample(&self, n: &Z, nr_cols: &Z, s: &Q) -> MatPolyOverZ; 48 | } 49 | 50 | /// A distribution which samples a matrix of type [`MatZ`] with entries in `\{-1,0,1\}` 51 | /// with probability `1/4` for `-1` and `1` an probability `1/2` for `0` 52 | #[derive(Serialize, Deserialize)] 53 | pub struct PlusMinusOneZero; 54 | 55 | /// A distribution which samples a row vector of type [`MatPolyOverZ`] where each 56 | /// coefficient is a polynomial of degree `n-1` and each coefficient of the polynomial 57 | /// is sampled using [`Z::sample_discrete_gauss`] 58 | #[derive(Serialize, Deserialize)] 59 | pub struct SampleZ; 60 | 61 | #[typetag::serde] 62 | impl TrapdoorDistribution for PlusMinusOneZero { 63 | /// Sample a matrix where each entry has probability `1/2` for being `0` 64 | /// and `1/4` each for `+/-1`. 65 | /// 66 | /// Parameters: 67 | /// - `m_bar`: number of columns of the matrix 68 | /// - `w`: number of rows of the matrix 69 | /// 70 | /// Returns a matrix where each entry is sampled independently with probability 71 | /// `1/2` for `0` and `1/4` each for `+/-1`. 72 | /// 73 | /// # Examples 74 | /// ``` 75 | /// use qfall_crypto::sample::g_trapdoor::trapdoor_distribution::{PlusMinusOneZero, TrapdoorDistribution}; 76 | /// 77 | /// let mat = PlusMinusOneZero.sample(&42.into(), &24.into()); 78 | /// ``` 79 | /// 80 | /// # Panics... 81 | /// - if `m_bar` or `w` does not fit into in `i64` or is smaller than `1`. 82 | fn sample(&self, m_bar: &Z, w: &Z) -> MatZ { 83 | let mat_1 = MatZ::sample_uniform(m_bar, w, 0, 2).unwrap(); 84 | let mat_2 = MatZ::sample_uniform(m_bar, w, 0, 2).unwrap(); 85 | mat_1 - mat_2 86 | } 87 | } 88 | 89 | #[typetag::serde] 90 | impl TrapdoorDistributionRing for SampleZ { 91 | /// Sample a matrix of polynomials of length `n` with entries sampled 92 | /// using [`Z::sample_discrete_gauss`]. 93 | /// 94 | /// Parameters: 95 | /// - `n`: length of the polynomial 96 | /// - `nr_cols`: number of columns of the matrix 97 | /// - `s`: the Gaussian parameter used for SampleZ 98 | /// 99 | /// Returns a matrix where each entry is a polynomials of length `n`, sampled 100 | /// using [`Z::sample_discrete_gauss`]. 101 | /// 102 | /// # Examples 103 | /// ``` 104 | /// use qfall_crypto::sample::g_trapdoor::trapdoor_distribution::{SampleZ, TrapdoorDistributionRing}; 105 | /// 106 | /// let mat = SampleZ.sample(&42.into(), &24.into(), &3.into()); 107 | /// ``` 108 | /// 109 | /// # Panics... 110 | /// - if `n`, `nr_rows` or `nr_cols` does not fit into in `i64` 111 | /// or is smaller than `1`. 112 | fn sample(&self, n: &Z, nr_cols: &Z, s: &Q) -> MatPolyOverZ { 113 | let n = i64::try_from(n).unwrap(); 114 | let nr_cols = i64::try_from(nr_cols).unwrap(); 115 | let mut out_mat = MatPolyOverZ::new(1, nr_cols); 116 | for j in 0..nr_cols { 117 | let sample = PolyOverZ::sample_discrete_gauss(n - 1, n, 0, s).unwrap(); 118 | out_mat.set_entry(0, j, &sample).unwrap(); 119 | } 120 | 121 | out_mat 122 | } 123 | } 124 | 125 | #[cfg(test)] 126 | mod test_pm_one_zero { 127 | use super::PlusMinusOneZero; 128 | use super::TrapdoorDistribution; 129 | use qfall_math::integer::Z; 130 | use qfall_math::traits::*; 131 | 132 | /// Ensure that the distribution samples in its correct range. 133 | #[test] 134 | fn correct_range() { 135 | let sample = PlusMinusOneZero.sample(&10.into(), &5.into()); 136 | 137 | for i in 0..10 { 138 | for j in 0..5 { 139 | assert!( 140 | Z::MINUS_ONE <= sample.get_entry(i, j).unwrap() 141 | && Z::ONE >= sample.get_entry(i, j).unwrap() 142 | ); 143 | } 144 | } 145 | } 146 | } 147 | 148 | #[cfg(test)] 149 | mod test_sample_z { 150 | use super::{SampleZ, TrapdoorDistributionRing}; 151 | use qfall_math::{rational::Q, traits::*}; 152 | 153 | /// Ensure that the distribution samples are in the correct range, 154 | #[test] 155 | fn correct_range_high_prob() { 156 | for _ in 0..20 { 157 | let s = 5.into(); 158 | let sample = SampleZ.sample(&10.into(), &15.into(), &s); 159 | 160 | // it should be the same as sampling a vector with 10*15 entries 161 | let coeff_embedding = sample.transpose().into_coefficient_embedding(10); 162 | 163 | // test for concentration bound 164 | assert!( 165 | Q::from(coeff_embedding.norm_eucl_sqrd().unwrap()) 166 | <= s.pow(2).unwrap() * coeff_embedding.get_num_rows() 167 | ); 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Marvin Beckmann 2 | // 3 | // This file is part of qFALL-crypto. 4 | // 5 | // qFALL-crypto is free software: you can redistribute it and/or modify it under 6 | // the terms of the Mozilla Public License Version 2.0 as published by the 7 | // Mozilla Foundation. See . 8 | 9 | //! This module contains common functions that are used by sub functions. 10 | //! 11 | //! This can include specialized implementations for certain parameter sets, such as rotation matrices. 12 | 13 | pub mod common_moduli; 14 | pub mod rotation_matrix; 15 | -------------------------------------------------------------------------------- /src/utils/common_moduli.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Niklas Siemer 2 | // 3 | // This file is part of qFALL-crypto. 4 | // 5 | // qFALL-crypto is free software: you can redistribute it and/or modify it under 6 | // the terms of the Mozilla Public License Version 2.0 as published by the 7 | // Mozilla Foundation. See . 8 | 9 | //! This module contains functions to quickly instantiate 10 | //! common moduli for ring-based lattice cryptography. 11 | 12 | use qfall_math::{ 13 | error::MathError, 14 | integer_mod_q::{Modulus, ModulusPolynomialRingZq, PolyOverZq}, 15 | traits::SetCoefficient, 16 | }; 17 | use std::fmt::Display; 18 | 19 | /// Outputs a [`ModulusPolynomialRingZq`] of the form `X^n + 1 mod q`. 20 | /// 21 | /// Parameters: 22 | /// - `n`: specifies the degree of the modulus polynomial 23 | /// - `q`: specifies the modulus of the modulus polynomial 24 | /// 25 | /// Returns a [`ModulusPolynomialRingZq`] of the form `X^n + 1 mod q` or 26 | /// a [`MathError`] if `q <= 1`, `n < 0`, or `n` does not into an [`i64`]. 27 | /// 28 | /// # Examples 29 | /// ``` 30 | /// use qfall_crypto::utils::common_moduli::new_anticyclic; 31 | /// 32 | /// let poly_mod = new_anticyclic(8, 17); 33 | /// ``` 34 | /// 35 | /// # Errors and Failures 36 | /// - Returns a [`MathError`] of type [`OutOfBounds`](MathError::OutOfBounds) if 37 | /// the `n` is negative or it does not fit into an [`i64`]. 38 | /// 39 | /// # Panics ... 40 | /// - if the `q` is not larger than `1`. 41 | pub fn new_anticyclic( 42 | n: impl TryInto + Display, 43 | q: impl Into, 44 | ) -> Result { 45 | let mut poly = PolyOverZq::from((1, q)); 46 | poly.set_coeff(n, 1)?; 47 | Ok(ModulusPolynomialRingZq::from(&poly)) 48 | } 49 | 50 | /// Outputs a [`ModulusPolynomialRingZq`] of the form `X^n - 1 mod q`. 51 | /// 52 | /// Parameters: 53 | /// - `n`: specifies the degree of the modulus polynomial 54 | /// - `q`: specifies the modulus of the modulus polynomial 55 | /// 56 | /// Returns a [`ModulusPolynomialRingZq`] of the form `X^n - 1 mod q` or 57 | /// a [`MathError`] if `q <= 1`, `n < 0`, or `n` does not into an [`i64`]. 58 | /// 59 | /// # Examples 60 | /// ``` 61 | /// use qfall_crypto::utils::common_moduli::new_cyclic; 62 | /// 63 | /// let poly_mod = new_cyclic(8, 17); 64 | /// ``` 65 | /// 66 | /// # Errors and Failures 67 | /// - Returns a [`MathError`] of type [`OutOfBounds`](MathError::OutOfBounds) if 68 | /// the `n` is negative or it does not fit into an [`i64`]. 69 | /// 70 | /// # Panics ... 71 | /// - if the `q` is not larger than `1`. 72 | pub fn new_cyclic( 73 | n: impl TryInto + Display, 74 | q: impl Into, 75 | ) -> Result { 76 | let mut poly = PolyOverZq::from((-1, q)); 77 | poly.set_coeff(n, 1)?; 78 | Ok(ModulusPolynomialRingZq::from(&poly)) 79 | } 80 | 81 | #[cfg(test)] 82 | mod test_new_anticyclic { 83 | use super::new_anticyclic; 84 | use qfall_math::{integer::Z, integer_mod_q::PolyOverZq, traits::GetCoefficient}; 85 | 86 | /// Ensure that the modulus polynomial has the specified degree. 87 | #[test] 88 | fn degree() { 89 | let degrees = [1, 4, 7, 16, 32, 128]; 90 | for degree in degrees { 91 | let poly_mod = new_anticyclic(degree, 7).unwrap(); 92 | 93 | assert_eq!(degree, poly_mod.get_degree()); 94 | } 95 | } 96 | 97 | /// Check whether the method outputs the correct polynomial. 98 | #[test] 99 | fn correct_polynomial() { 100 | let degrees = [1, 4, 7, 16, 32, 128]; 101 | for degree in degrees { 102 | let poly_mod = new_anticyclic(degree, 7).unwrap(); 103 | let poly_zq = PolyOverZq::from(&poly_mod); 104 | 105 | assert_eq!( 106 | Z::ONE, 107 | GetCoefficient::::get_coeff(&poly_zq, degree).unwrap() 108 | ); 109 | assert_eq!(Z::ONE, GetCoefficient::::get_coeff(&poly_zq, 0).unwrap()); 110 | for i in 1..degree { 111 | assert_eq!( 112 | Z::ZERO, 113 | GetCoefficient::::get_coeff(&poly_zq, i).unwrap() 114 | ); 115 | } 116 | } 117 | } 118 | 119 | /// Ensures that the correct modulus is set as 120 | /// the integer modulus of the output modulus polynomial. 121 | #[test] 122 | fn correct_modulus() { 123 | let moduli = [7, 10, i64::MAX]; 124 | for modulus in moduli { 125 | let poly_mod = new_anticyclic(2, modulus).unwrap(); 126 | 127 | assert_eq!(Z::from(modulus), poly_mod.get_q()); 128 | } 129 | } 130 | 131 | /// Ensures that an invalid degree for the modulus polynomial results in an error. 132 | #[test] 133 | fn invalid_n() { 134 | let res = new_anticyclic(-1, 7); 135 | 136 | assert!(res.is_err()); 137 | } 138 | 139 | /// Ensures that an invalid modulus for the modulus polynomial results in a panic. 140 | #[test] 141 | #[should_panic] 142 | fn invalid_modulus() { 143 | let _ = new_anticyclic(2, 0); 144 | } 145 | } 146 | 147 | #[cfg(test)] 148 | mod test_new_cyclic { 149 | use super::new_cyclic; 150 | use qfall_math::{integer::Z, integer_mod_q::PolyOverZq, traits::GetCoefficient}; 151 | 152 | /// Ensure that the modulus polynomial has the specified degree. 153 | #[test] 154 | fn degree() { 155 | let degrees = [1, 4, 7, 16, 32, 128]; 156 | for degree in degrees { 157 | let poly_mod = new_cyclic(degree, 7).unwrap(); 158 | 159 | assert_eq!(degree, poly_mod.get_degree()); 160 | } 161 | } 162 | 163 | /// Check whether the method outputs the correct polynomial. 164 | #[test] 165 | fn correct_polynomial() { 166 | let degrees = [1, 4, 7, 16, 32, 128]; 167 | for degree in degrees { 168 | let poly_mod = new_cyclic(degree, 7).unwrap(); 169 | let poly_zq = PolyOverZq::from(&poly_mod); 170 | 171 | assert_eq!( 172 | Z::ONE, 173 | GetCoefficient::::get_coeff(&poly_zq, degree).unwrap() 174 | ); 175 | assert_eq!( 176 | Z::from(6), 177 | GetCoefficient::::get_coeff(&poly_zq, 0).unwrap() 178 | ); 179 | for i in 1..degree { 180 | assert_eq!( 181 | Z::ZERO, 182 | GetCoefficient::::get_coeff(&poly_zq, i).unwrap() 183 | ); 184 | } 185 | } 186 | } 187 | 188 | /// Ensures that the correct modulus is set as 189 | /// the integer modulus of the output modulus polynomial. 190 | #[test] 191 | fn correct_modulus() { 192 | let moduli = [7, 10, i64::MAX]; 193 | for modulus in moduli { 194 | let poly_mod = new_cyclic(2, modulus).unwrap(); 195 | 196 | assert_eq!(Z::from(modulus), poly_mod.get_q()); 197 | } 198 | } 199 | 200 | /// Ensures that an invalid degree for the modulus polynomial results in an error. 201 | #[test] 202 | fn invalid_n() { 203 | let res = new_cyclic(-1, 7); 204 | 205 | assert!(res.is_err()); 206 | } 207 | 208 | /// Ensures that an invalid modulus for the modulus polynomial results in a panic. 209 | #[test] 210 | #[should_panic] 211 | fn invalid_modulus() { 212 | let _ = new_cyclic(2, 0); 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/utils/rotation_matrix.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Marvin Beckmann 2 | // 3 | // This file is part of qFALL-crypto. 4 | // 5 | // qFALL-crypto is free software: you can redistribute it and/or modify it under 6 | // the terms of the Mozilla Public License Version 2.0 as published by the 7 | // Mozilla Foundation. See . 8 | 9 | //! This module includes a specialized implementation called rotation matrices, 10 | //! which find application in ring-based implementations for the special rings of the form 11 | //! `Z[X]/(X^n + 1)`. 12 | 13 | use qfall_math::{integer::MatZ, traits::*}; 14 | 15 | /// Takes in a vector and computes and the rotation matrix as follows. 16 | /// For a vector `[[a_1],[a_2],...,[a_n]]` it computes the rotation matrix as 17 | /// `[[a_1, -a_n, ..., -a_2],[a_2, a_1, ..., -a_3],...,[a_n, a_{n-1}, ..., a_1]]` 18 | /// 19 | /// It takes in both a column vector or a row vector, but the format of the matrix 20 | /// remains as described above. 21 | /// 22 | /// Parameters: 23 | /// - `vec`: The vector for which the rotation matrix will be computed. 24 | /// 25 | /// Returns the rotation matrix of `vec` as a [`MatZ`]. 26 | /// 27 | /// # Examples 28 | /// ``` 29 | /// use qfall_crypto::utils::rotation_matrix::rot_minus; 30 | /// use qfall_math::integer::MatZ; 31 | /// use std::str::FromStr; 32 | /// 33 | /// let vec = MatZ::from_str("[[1],[5],[-1],[9]]").unwrap(); 34 | /// let row_vec = MatZ::from_str("[[1,5,-1,9]]").unwrap(); 35 | /// 36 | /// let rot_col = rot_minus(&vec); 37 | /// let rot_row = rot_minus(&row_vec); 38 | /// ``` 39 | /// 40 | /// # Panics ... 41 | /// - if the provided matrix is not a vector. 42 | pub fn rot_minus(vec: &MatZ) -> MatZ { 43 | let vec = if vec.is_column_vector() { 44 | vec.clone() 45 | } else if vec.is_row_vector() { 46 | vec.transpose() 47 | } else { 48 | panic!("The input must be a vector.") 49 | }; 50 | 51 | let mut out = MatZ::new(vec.get_num_rows(), vec.get_num_rows()); 52 | 53 | for i in 0..out.get_num_rows() { 54 | let entry = vec.get_entry(i, 0).unwrap(); 55 | for j in 0..out.get_num_columns() { 56 | let (row, sign) = match i + j { 57 | k if k >= out.get_num_rows() => ((k) % out.get_num_rows(), -1), 58 | k => (k, 1), 59 | }; 60 | out.set_entry(row, j, sign * &entry).unwrap(); 61 | } 62 | } 63 | out 64 | } 65 | 66 | /// Takes in a matrix, splits the matrix into separate columns and concatenates the 67 | /// rotation matrices as: 68 | /// `[rot^-(a_1) | rot^-(a_2) | ... | rot^-(a_m)]` 69 | /// 70 | /// It takes in both a column vector or a row vector, but the format of the matrix 71 | /// remains as described above. 72 | /// 73 | /// Parameters: 74 | /// - `vec`: The vector for which the rotation matrix will be computed. 75 | /// 76 | /// # Examples 77 | /// ``` 78 | /// use qfall_crypto::utils::rotation_matrix::rot_minus_matrix; 79 | /// use qfall_math::integer::MatZ; 80 | /// use std::str::FromStr; 81 | /// 82 | /// let mat = MatZ::from_str("[[1,5,-1,9],[2,3,4,5]]").unwrap(); 83 | /// 84 | /// let rot_mat = rot_minus_matrix(&mat); 85 | /// ``` 86 | pub fn rot_minus_matrix(matrix: &MatZ) -> MatZ { 87 | let mut vec = Vec::new(); 88 | for i in 0..matrix.get_num_columns() { 89 | vec.push(rot_minus(&matrix.get_column(i).unwrap())); 90 | } 91 | 92 | let mut out = vec.first().unwrap().clone(); 93 | for mat in vec.iter().skip(1) { 94 | out = out.concat_horizontal(mat).unwrap(); 95 | } 96 | out 97 | } 98 | 99 | #[cfg(test)] 100 | mod test_rot_minus { 101 | use crate::utils::rotation_matrix::{rot_minus, rot_minus_matrix}; 102 | use qfall_math::integer::MatZ; 103 | use std::str::FromStr; 104 | 105 | /// Ensures that the rotation minus matrix works correctly for a vector. 106 | #[test] 107 | fn correct_rotation_matrix_vec() { 108 | let vec = MatZ::from_str("[[1],[5],[-1],[9]]").unwrap(); 109 | let row_vec = MatZ::from_str("[[1,5,-1,9]]").unwrap(); 110 | 111 | let rot_col = rot_minus(&vec); 112 | let rot_row = rot_minus(&row_vec); 113 | 114 | let cmp_rot = 115 | MatZ::from_str("[[1, -9, 1, -5],[5, 1, -9, 1],[-1, 5, 1, -9],[9, -1, 5, 1]]").unwrap(); 116 | assert_eq!(rot_col, rot_row); 117 | assert_eq!(cmp_rot, rot_col) 118 | } 119 | 120 | /// Ensures that the rotation minus matrix works correctly for matrices. 121 | #[test] 122 | fn correct_rotation_matrix_mat() { 123 | let mat = MatZ::from_str(&format!("[[1,5,-1,9],[{}, 1, 2, 3]]", u64::MAX)).unwrap(); 124 | 125 | let rot_mat = rot_minus_matrix(&mat); 126 | 127 | let cmp_rot = MatZ::from_str(&format!( 128 | "[[1, -{}, 5, -1, -1, -2, 9, -3],\ 129 | [{}, 1, 1, 5, 2, -1, 3, 9]]", 130 | u64::MAX, 131 | u64::MAX 132 | )) 133 | .unwrap(); 134 | assert_eq!(cmp_rot, rot_mat); 135 | } 136 | 137 | /// Ensures that the minus rotation matrix for vectors panics if a matrix is provided. 138 | #[test] 139 | #[should_panic] 140 | fn not_vector() { 141 | let mat = MatZ::from_str("[[1,5,-1,9],[1,2,3,4]]").unwrap(); 142 | 143 | let _ = rot_minus(&mat); 144 | } 145 | } 146 | --------------------------------------------------------------------------------