├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── documentation.md │ ├── feature_request.md │ └── bug_report.md ├── pull_request_template.md └── workflows │ ├── push.yml │ ├── pull_request.yml │ └── main.yml ├── .gitignore ├── benches ├── benchmarks.rs ├── psf.rs └── README.md ├── src ├── utils.rs ├── sample.rs ├── primitive.rs ├── compression.rs ├── lib.rs ├── sample │ ├── g_trapdoor.rs │ └── g_trapdoor │ │ ├── gadget_default.rs │ │ ├── trapdoor_distribution.rs │ │ ├── gadget_parameters.rs │ │ ├── gadget_ring.rs │ │ ├── short_basis_classical.rs │ │ └── gadget_classical.rs ├── primitive │ ├── psf.rs │ └── psf │ │ ├── gpv.rs │ │ ├── gpv_ring.rs │ │ └── mp_perturbation.rs ├── utils │ ├── rotation_matrix.rs │ ├── common_moduli.rs │ └── common_encodings.rs └── compression │ └── lossy_compression_fips203.rs ├── CITATION.cff ├── Cargo.toml ├── CONTRIBUTING.md ├── README.md └── LICENSE /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | @qfall/pg 2 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /benches/benchmarks.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Sven Moog 2 | // 3 | // This file is part of qFALL-tools. 4 | // 5 | // qFALL-tools 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 psf; 13 | 14 | criterion_main! {psf::benches} 15 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Marvin Beckmann 2 | // 3 | // This file is part of qFALL-tools. 4 | // 5 | // qFALL-tools 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 commonly functions in lattice-based cryptography. 10 | 11 | pub mod common_encodings; 12 | pub mod common_moduli; 13 | pub mod rotation_matrix; 14 | -------------------------------------------------------------------------------- /src/sample.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Niklas Siemer 2 | // 3 | // This file is part of qFALL-tools. 4 | // 5 | // qFALL-tools 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 anything that should be easily samplable for lattice-based 10 | //! cryptography, which is more complex than sampling from a distribution. 11 | 12 | pub mod g_trapdoor; 13 | -------------------------------------------------------------------------------- /src/primitive.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Niklas Siemer 2 | // 3 | // This file is part of qFALL-tools. 4 | // 5 | // qFALL-tools 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 primitives that are useful for cryptographic 10 | //! constructions, but are solely targeted to be used in other constructions. 11 | //! 12 | //! More specifically, this module contains primitives that do not provide security 13 | //! guarantees targeted at end-users such as confidentiality, integrity, etc. 14 | 15 | pub mod psf; 16 | -------------------------------------------------------------------------------- /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-tools 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 | -------------------------------------------------------------------------------- /src/compression.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2025 Niklas Siemer 2 | // 3 | // This file is part of qFALL-tools. 4 | // 5 | // qFALL-tools 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 commonly used compression techniques in lattice-based cryptography. 10 | //! 11 | //! References: 12 | //! - \[1\] National Institute of Standards and Technology (2024). 13 | //! Module-Lattice-Based Key-Encapsulation Mechanism Standard. 14 | //! Federal Information Processing Standards Publication (FIPS 203). 15 | //! 16 | 17 | mod lossy_compression_fips203; 18 | 19 | pub use lossy_compression_fips203::LossyCompressionFIPS203; 20 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "qfall-tools" 3 | version = "0.1.0" 4 | edition = "2024" 5 | rust-version = "1.85" # due to rand and rand_distr dependency 6 | description = "Common sub-modules and procedures in lattice-based constructions" 7 | readme = "README.md" 8 | homepage = "https://qfall.github.io" 9 | repository = "https://github.com/qfall/tools" 10 | license = "MPL-2.0" 11 | keywords = ["prototype", "lattice", "cryptography"] 12 | categories = ["cryptography", "mathematics", "development-tools::build-utils", "development-tools::testing", "development-tools::profiling"] 13 | autobenches = false 14 | 15 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 16 | 17 | [dependencies] 18 | qfall-math = { git = "https://github.com/qfall/math", branch = "dev" } 19 | flint-sys = "0.7.3" 20 | sha2 = "0.10" 21 | serde = {version="1.0", features=["derive"]} 22 | serde_json = "1.0" 23 | typetag = "0.2" 24 | criterion = { version = "0.8", features = ["html_reports"] } 25 | 26 | [profile.bench] 27 | debug = true 28 | 29 | [[bench]] 30 | name = "benchmarks" 31 | harness = false 32 | -------------------------------------------------------------------------------- /.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/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 | 17 | 18 | **Describe the bug** 19 | 20 | 21 | 22 | **To Reproduce** 23 | 24 | 25 | 26 | 27 | ```rust 28 | // write your code here 29 | ``` 30 | 31 | **Expected behavior** 32 | 33 | 35 | 36 | **Screenshots** 37 | 38 | 39 | 40 | **Desktop (please complete the following information):** 41 | 42 | - OS: 43 | - Version of qFALL-tools: 44 | 45 | **Additional context** 46 | 47 | 48 | 49 | **Solution** 50 | 51 | 52 | -------------------------------------------------------------------------------- /.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/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 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Niklas Siemer, Marvin Beckmann 2 | // 3 | // This file is part of qFALL-tools. 4 | // 5 | // qFALL-tools 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 | //! `qFALL` is a prototyping library for lattice-based cryptography. 10 | //! `qFALL-tools` collects common sub-modules and features used by lattice-based constructions 11 | //! to simplify and accelerate the development of such. 12 | //! Among these are: 13 | //! - [Compression techniques](crate::compression), 14 | //! - [Primitives such as Preimage Samplable Functions (PSF)](crate::primitive), 15 | //! - [Sampling algorithm using trapdoors](crate::sample), and 16 | //! - [common functions for efficient prototyping](crate::utils) such as 17 | //! - [common message encodings for encryption](crate::utils::common_encodings), 18 | //! - [quick instantiations of common moduli for rings](crate::utils::common_moduli), as well as 19 | //! - [rotation matrices](crate::utils::rotation_matrix). 20 | //! 21 | //! The `qFALL` project contains two more crates called [`qFALL-math`](https://crates.io/crates/qfall-math) 22 | //! and [`qFALL-schemes`](https://crates.io/crates/qfall-schemes) to support prototyping. 23 | //! - Find further information on [our website](https://qfall.github.io/). 24 | //! - We recommend [our tutorial](https://qfall.github.io/book) to start working with qFALL. 25 | //! 26 | //! ## Quick Example 27 | //! ``` 28 | //! use qfall_tools::utils::{common_moduli::new_anticyclic, common_encodings::encode_value_in_polynomialringzq}; 29 | //! use qfall_math::integer::Z; 30 | //! 31 | //! // Create X^256 + 1 mod 3329 32 | //! let poly_mod = new_anticyclic(256, 3329).unwrap(); 33 | //! // Generate integer from string 34 | //! let message = Z::from_utf8("Hello!"); 35 | //! // Turn string into encoding q/2 and 0 for each 1 and 0 bit respectively 36 | //! let mu_q_half = encode_value_in_polynomialringzq(message, 2, &poly_mod).unwrap(); 37 | //! ``` 38 | 39 | pub mod compression; 40 | pub mod primitive; 41 | pub mod sample; 42 | pub mod utils; 43 | -------------------------------------------------------------------------------- /src/sample/g_trapdoor.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Marvin Beckmann 2 | // 3 | // This file is part of qFALL-tools. 4 | // 5 | // qFALL-tools 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 implementations for G-trapdoors in the classical and 10 | //! ring setting, enabling efficient preimage sampling. 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\] El Bansarkhani, R., Buchmann, J. (2014). Improvement and Efficient 20 | //! Implementation of a Lattice-Based Signature Scheme. In: Lange, T., Lauter, K., 21 | //! Lisoněk, P. (eds) Selected Areas in Cryptography -- SAC 2013. SAC 2013. Lecture Notes 22 | //! in Computer Science(), vol 8282. Springer, Berlin, Heidelberg. 23 | //! 24 | //! - \[3\] Gür, K.D., Polyakov, Y., Rohloff, K., Ryan, G.W. and Savas, E., 2018, 25 | //! January. Implementation and evaluation of improved Gaussian sampling for lattice 26 | //! trapdoors. In Proceedings of the 6th Workshop on Encrypted Computing & Applied 27 | //! Homomorphic Cryptography (pp. 61-71). 28 | //! - \[4\] Cash, D., Hofheinz, D., Kiltz, E., & Peikert, C. (2012). 29 | //! Bonsai trees, or how to delegate a lattice basis. Journal of cryptology, 25, 601-639. 30 | //! 31 | //! - \[5\] Chen, Yuanmi, and Phong Q. Nguyen. "BKZ 2.0: Better lattice security 32 | //! estimates." International Conference on the Theory and Application of Cryptology and 33 | //! Information Security. Berlin, Heidelberg: Springer Berlin Heidelberg, 2011. 34 | 35 | pub mod gadget_classical; 36 | pub mod gadget_default; 37 | pub mod gadget_parameters; 38 | pub mod gadget_ring; 39 | pub mod short_basis_classical; 40 | pub mod short_basis_ring; 41 | pub mod trapdoor_distribution; 42 | -------------------------------------------------------------------------------- /.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/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 | -------------------------------------------------------------------------------- /src/primitive/psf.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Marcel Luca Schmidt, Marvin Beckmann 2 | // 3 | // This file is part of qFALL-tools. 4 | // 5 | // qFALL-tools 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 implementations of Preimage Samplable Functions, short [`PSF`]. 10 | //! 11 | //! The main references are listed in the following 12 | //! and will be further referenced in submodules by these numbers: 13 | //! - \[1\] Micciancio, D., Peikert, C. (2012). 14 | //! Trapdoors for Lattices: Simpler, Tighter, Faster, Smaller. 15 | //! In: Pointcheval, D., Johansson, T. (eds) Advances in Cryptology – EUROCRYPT 2012. 16 | //! EUROCRYPT 2012. Lecture Notes in Computer Science, vol 7237. 17 | //! Springer, Berlin, Heidelberg. 18 | //! - \[2\] Gür, K.D., Polyakov, Y., Rohloff, K., Ryan, G.W. and Savas, E., 2018, 19 | //! January. Implementation and evaluation of improved Gaussian sampling for lattice 20 | //! trapdoors. In Proceedings of the 6th Workshop on Encrypted Computing & Applied 21 | //! Homomorphic Cryptography (pp. 61-71). 22 | //! - \[3\] Peikert, Chris. 23 | //! An efficient and parallel Gaussian sampler for lattices. 24 | //! In: Annual Cryptology Conference - CRYPTO 2010. 25 | //! Springer, Berlin, Heidelberg. 26 | 27 | mod gpv; 28 | mod gpv_ring; 29 | mod mp_perturbation; 30 | 31 | pub use gpv::PSFGPV; 32 | pub use gpv_ring::PSFGPVRing; 33 | pub use mp_perturbation::PSFPerturbation; 34 | 35 | /// This trait should be implemented by all constructions that are 36 | /// actual implementations of a preimage sampleable function. 37 | /// A formal definition for these PSFs can be found in 38 | /// [\[1\]]() 39 | pub trait PSF { 40 | type A; 41 | type Trapdoor; 42 | type Domain; 43 | type Range; 44 | 45 | /// Samples a parity-check matrix and a trapdoor for that matrix. 46 | /// 47 | /// Returns the parity-check matrix and the trapdoor. 48 | fn trap_gen(&self) -> (Self::A, Self::Trapdoor); 49 | 50 | /// Samples an element in the domain according to a specified distribution. 51 | /// 52 | /// Returns the sampled element. 53 | fn samp_d(&self) -> Self::Domain; 54 | 55 | /// Samples an element `e` in the domain according to a specified distribution 56 | /// conditioned on `f_a(a, e) = u`. 57 | /// 58 | /// Parameters: 59 | /// - `a`: The parity-check matrix 60 | /// - `r`: The G-Trapdoor for `a` 61 | /// - `u`: The syndrome from the range 62 | /// 63 | /// Returns a sample `e` from the domain on the conditioned discrete 64 | /// Gaussian distribution `f_a(a,e) = u`. 65 | fn samp_p(&self, a: &Self::A, r: &Self::Trapdoor, u: &Self::Range) -> Self::Domain; 66 | 67 | /// Implements the efficiently computable function `f_a`, 68 | /// which is uniquely classified by `a`. 69 | /// 70 | /// Parameters: 71 | /// - `a`: The parity-check matrix of dimensions `n x m` 72 | /// - `sigma`: A column vector of length `m` 73 | /// 74 | /// Returns the result of `f_a`. 75 | fn f_a(&self, a: &Self::A, sigma: &Self::Domain) -> Self::Range; 76 | 77 | /// Checks whether an element is in the correct domain (and not just the correct type). 78 | /// 79 | /// Returns the result of the check as a boolean. 80 | fn check_domain(&self, sigma: &Self::Domain) -> bool; 81 | } 82 | -------------------------------------------------------------------------------- /benches/psf.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2025 Niklas Siemer 2 | // 3 | // This file is part of qFALL-tools. 4 | // 5 | // qFALL-tools 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, criterion_group}; 10 | use qfall_math::{integer_mod_q::MatZq, rational::Q}; 11 | use qfall_tools::{ 12 | primitive::psf::{PSF, PSFGPV, PSFPerturbation}, 13 | sample::g_trapdoor::gadget_parameters::GadgetParameters, 14 | }; 15 | 16 | /// Benchmark [bench_psf] with `n = 8`. 17 | /// 18 | /// This benchmark can be run with for example: 19 | /// - `cargo criterion PSF\ GPV\ n=8` 20 | /// - `cargo bench --bench benchmarks PSF\ GPV\ n=8` 21 | /// - `cargo flamegraph --bench benchmarks -- --bench PSF\ GPV\ n=8` 22 | /// 23 | /// Shorter variants or regex expressions can also be used to specify the 24 | /// benchmark name. The `\ ` is used to escape the space, alternatively, 25 | /// quotation marks can be used. 26 | fn bench_psf(c: &mut Criterion) { 27 | let (n, q) = (8, 128); 28 | 29 | let psf = PSFGPV { 30 | gp: GadgetParameters::init_default(n, q), 31 | // multiply with the rounding parameter from next test to have the same samplign parameter 32 | s: Q::from(30) * Q::from(n).log(2).unwrap(), 33 | }; 34 | 35 | let target = MatZq::sample_uniform(n, 1, q); 36 | let (a, r) = psf.trap_gen(); 37 | 38 | c.bench_function("PSF GPV n=8", |b| b.iter(|| psf.samp_p(&a, &r, &target))); 39 | } 40 | 41 | /// Benchmark [bench_psf_perturbation] with `n = 8`. 42 | /// 43 | /// This benchmark can be run with for example: 44 | /// - `cargo criterion PSF\ Perturbation\ n=8` 45 | /// - `cargo bench --bench benchmarks PSF\ Perturbation\ n=8` 46 | /// - `cargo flamegraph --bench benchmarks -- --bench PSF\ Perturbation\ n=8` 47 | /// 48 | /// Shorter variants or regex expressions can also be used to specify the 49 | /// benchmark name. The `\ ` is used to escape the space, alternatively, 50 | /// quotation marks can be used. 51 | fn bench_psf_perturbation(c: &mut Criterion) { 52 | let (n, q) = (8, 128); 53 | 54 | let psf = PSFPerturbation { 55 | gp: GadgetParameters::init_default(n, q), 56 | s: Q::from(30), 57 | r: Q::from(n).log(2).unwrap(), 58 | }; 59 | 60 | let target = MatZq::sample_uniform(n, 1, q); 61 | let (a, r) = psf.trap_gen(); 62 | 63 | c.bench_function("PSF Perturbation n=8", |b| { 64 | b.iter(|| psf.samp_p(&a, &r, &target)) 65 | }); 66 | } 67 | 68 | /// Benchmark [bench_psf_perturbation] with `n = 64`. 69 | /// 70 | /// This benchmark can be run with for example: 71 | /// - `cargo criterion PSF\ Perturbation\ n=64` 72 | /// - `cargo bench --bench benchmarks PSF\ Perturbation\ n=64` 73 | /// - `cargo flamegraph --bench benchmarks -- --bench PSF\ Perturbation\ n=64` 74 | /// 75 | /// Shorter variants or regex expressions can also be used to specify the 76 | /// benchmark name. The `\ ` is used to escape the space, alternatively, 77 | /// quotation marks can be used. 78 | fn bench_psf_perturbation_larger(c: &mut Criterion) { 79 | let (n, q) = (64, 128); 80 | 81 | let psf = PSFPerturbation { 82 | gp: GadgetParameters::init_default(n, q), 83 | s: Q::from(100), 84 | r: Q::from(n).log(2).unwrap(), 85 | }; 86 | 87 | let target = MatZq::sample_uniform(n, 1, q); 88 | let (a, r) = psf.trap_gen(); 89 | 90 | c.bench_function("PSF Perturbation n=64", |b| { 91 | b.iter(|| psf.samp_p(&a, &r, &target)) 92 | }); 93 | } 94 | 95 | criterion_group!( 96 | benches, 97 | bench_psf, 98 | bench_psf_perturbation, 99 | bench_psf_perturbation_larger, 100 | ); 101 | -------------------------------------------------------------------------------- /benches/README.md: -------------------------------------------------------------------------------- 1 | 10 | 11 | # How to run benchmarks: 12 | 13 | ## Criterion 14 | 15 | We use criterion for statistical analysis. A plotting library has to be installed to generate graphs. You can find more information and help here: 16 | 17 | - [Criterion-rs GitHub](https://github.com/bheisler/criterion.rs) 18 | - [Cargo-criterion GitHub](https://github.com/bheisler/cargo-criterion) 19 | - [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!) 20 | 21 | ### Commands 22 | 23 | a) `cargo criterion ` 24 | Has to be installed with `cargo install cargo-criterion`. 25 | Pros: 26 | 27 | - You can remove `features = ["html_reports"]` from the `Cargo.toml` leading to a (slightly) faster compile times. 28 | - Criterion aims to move to just using cargo criterion 29 | - The large Probability Density Function graph shows the samples and marks the outlier categorization boarders. 30 | - Can use either [gnuplot](http://www.gnuplot.info/) or [plotters](https://crates.io/crates/plotters) 31 | 32 | b) `cargo bench ` 33 | Pros: 34 | 35 | - Can visualize the change in performance compared to previous run or other baseline 36 | Cons: 37 | - Can only use [gnuplot](http://www.gnuplot.info/) 38 | 39 | ## Flamegraph 40 | 41 | You can also run the benchmarks using the profiler flamegraph. Details can be found here: 42 | 43 | - [Flamegraph GitHub](https://github.com/flamegraph-rs/flamegraph). 44 | This provides insights on the execution time of the executed functions and their subroutines. 45 | 46 | Note: Flamegraph does not work in WSL 47 | 48 | ### Command 49 | 50 | `cargo flamegraph --freq 63300 --bench benchmarks -- --bench --profile-time 5 ` 51 | 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 52 | 53 | - 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... 54 | - increasing `profile-time` (in seconds). This is how long the benchmark code will be executed. 55 | This parameter also disables the statistical analysis of criterion which prevents it from showing up in the graph. 56 | This parameter is optional, but suggested. 57 | 58 | The flamegraph can be overwhelming since it exposes a lot of internal workings of rust, criterion, and more. 59 | The easiest way to find the function you are looking for is to search for it with `Ctrl + F`. 60 | You have to enter a part of the rust function name or regex (not the benchmark name). 61 | 62 | # How to create benchmarks 63 | 64 | ## No appropriate file exists so far: 65 | 66 | 1. create the file 67 | 2. Insert in new file: 68 | 69 | ```rust 70 | use criterion::*; 71 | 72 | criterion_group!(benches); 73 | ``` 74 | 75 | 3. Insert in [benchmarks.rs](/benches/benchmarks.rs): 76 | ```rust 77 | pub mod ; 78 | ``` 79 | and `::benches` in the `criterion_main!` macro. 80 | 81 | ## Appropriately named benchmark file exists in `/benches` (e.g. `integer.rs`) 82 | 83 | 1. Create a function that performs the functionality that should be benchmarked (called `do_stuff` below). 84 | 2. Add a function to handle the interaction with criterion. 85 | e.g.: 86 | ```rust 87 | /// Add Comment describing the benchmark here 88 | pub fn bench_do_stuff(c: &mut Criterion) { 89 | c.bench_function("", |b| b.iter(|| do_stuff())); 90 | } 91 | ``` 92 | The benchmark name specified here is later used to select which benchmark to run and also displayed in the output. 93 | This function can also look differently, for example, because it uses [criterion groups](https://docs.rs/criterion/latest/criterion/struct.BenchmarkGroup.html). 94 | 3. Add function created in step 2 in the `criterion_group!` macro (bottom of file). 95 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | This library is designed to prototype lattice-based cryptography. Our intent for this library is to be maintained by the community. We encourage anyone to add missing, frequently used features for lattice-based prototyping to this library, and we are happy to help with that process. 3 | 4 | More generally, all contributions such as bugfixes, documentation and tests are welcome. Please go ahead and submit your pull requests. 5 | 6 | ## Choosing the right location 7 | The qFALL library is divided into three repositories: [qFALL-math](https://github.com/qfall/math), [qFALL-tools](https://github.com/qfall/tools) and [qFALL-schemes](https://github.com/qfall/schemes). 8 | 9 | Please add new features to one of these repositories, roughly following these guidelines. 10 | - If your feature implements a general mathematical function, then add your code to [qFALL-math](https://github.com/qfall/math). 11 | - If your feature implements a fundamental primitive or shortcut that is commonly used in the construction of lattice-based schemes, e.g., G-trapdoors, then add your code to [qFALL-tools](https://github.com/qfall/tools). 12 | - If you implement a construction, e.g., Kyber, then add your code to [qFALL-schemes](https://github.com/qfall/schemes). 13 | 14 | When in doubt, just submit your pull request to the repository you feel is best suited for your code. We will sort it. 15 | 16 | ## Style Guide 17 | Our style guide is based on the [rust standard](https://github.com/rust-lang/rfcs/blob/master/text/0505-api-comment-conventions.md). These rules summarise our style guidelines. 18 | - Every function should be documented. A doc-comment includes a concise description of the function and an example. In case it receives parameters other than `self`, it also includes a description of every parameter, the output type, and behavior. If applicable, it also includes Error and Panic behavior and references to scientific literature. 19 | - If the code of your function is not self-explanatory from your doc-comment, use inline-comments `//` to briefly describe the steps. 20 | - A file should always have the copyright notice at the top, followed by a very brief inner doc-comment to summarise the purpose of this file, grouped up imports, implementations of all features, and finally tests of each feature in a separate test-module with a brief doc-comment for each test. 21 | - Overall, any feature should get a descriptive but concise name s.t. it can be discovered intuitively. 22 | - Code in our library needs to be formatted using `cargo fmt` and satisfy `clippy`'s standards. 23 | - We aim for multiple tests per function, its unforeseen behavior, panic or error-cases to boost confidence in our implementations and ensure that modifications of a function only introduce intended changes of behavior. 24 | - Last but not least, we would like to minimise the number of dependencies of all crates to keep them as slim and quickly compilable as possible. 25 | 26 | ## Documentation 27 | The documentation for each crate is available online and it can be generated locally by running the following command in the root directory of this repository. 28 | ```bash 29 | cargo doc --open 30 | ``` 31 | 32 | Furthermore, here is an example of a doc-comment of a function that follows our guidelines. 33 | ```rust 34 | impl Z { 35 | /// Chooses a [`Z`] instance according to the discrete Gaussian distribution 36 | /// in `[center - ⌈6 * s⌉ , center + ⌊6 * s⌋ ]`. 37 | /// 38 | /// This function samples discrete Gaussians according to the definition of 39 | /// SampleZ in [GPV08](https://citeseerx.ist.psu.edu/document?repid=rep1&type=pdf&doi=d9f54077d568784c786f7b1d030b00493eb3ae35). 40 | /// 41 | /// Parameters: 42 | /// - `n`: specifies the range from which is sampled 43 | /// - `center`: specifies the position of the center with peak probability 44 | /// - `s`: specifies the Gaussian parameter, which is proportional 45 | /// to the standard deviation `sigma * sqrt(2 * pi) = s` 46 | /// 47 | /// Returns new [`Z`] sample chosen according to the specified discrete Gaussian 48 | /// distribution or a [`MathError`] if the specified parameters were not chosen 49 | /// appropriately, i.e. `s < 0`. 50 | /// 51 | /// # Examples 52 | /// ``` 53 | /// use qfall_math::integer::Z; 54 | /// 55 | /// let sample = Z::sample_discrete_gauss(0, 1).unwrap(); 56 | /// ``` 57 | /// 58 | /// # Errors and Failures 59 | /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) 60 | /// if `s < 0`. 61 | /// 62 | /// This function implements SampleZ according to: 63 | /// - \[1\] Gentry, Craig and Peikert, Chris and Vaikuntanathan, Vinod (2008). 64 | /// Trapdoors for hard lattices and new cryptographic constructions. 65 | /// In: Proceedings of the fortieth annual ACM symposium on Theory of computing. 66 | /// 67 | pub fn sample_discrete_gauss(center: impl Into, s: impl Into) -> Result {...} 68 | } 69 | ``` 70 | -------------------------------------------------------------------------------- /src/utils/rotation_matrix.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Marvin Beckmann 2 | // 3 | // This file is part of qFALL-tools. 4 | // 5 | // qFALL-tools 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 functions for generating rotation matrices used 10 | //! when working over the ring `Z[X]/(X^n + 1)`. 11 | 12 | use qfall_math::{integer::MatZ, traits::*}; 13 | 14 | /// Takes in a vector and computes and the rotation matrix as follows. 15 | /// For a vector `[[a_1],[a_2],...,[a_n]]` it computes the rotation matrix as 16 | /// `[[a_1, -a_n, ..., -a_2],[a_2, a_1, ..., -a_3],...,[a_n, a_{n-1}, ..., a_1]]` 17 | /// 18 | /// It takes in both a column vector or a row vector, but the format of the matrix 19 | /// remains as described above. 20 | /// 21 | /// Parameters: 22 | /// - `vec`: The vector for which the rotation matrix will be computed. 23 | /// 24 | /// Returns the rotation matrix of `vec` as a [`MatZ`]. 25 | /// 26 | /// # Examples 27 | /// ``` 28 | /// use qfall_tools::utils::rotation_matrix::rot_minus; 29 | /// use qfall_math::integer::MatZ; 30 | /// use std::str::FromStr; 31 | /// 32 | /// let vec = MatZ::from_str("[[1],[5],[-1],[9]]").unwrap(); 33 | /// let row_vec = MatZ::from_str("[[1,5,-1,9]]").unwrap(); 34 | /// 35 | /// let rot_col = rot_minus(&vec); 36 | /// let rot_row = rot_minus(&row_vec); 37 | /// ``` 38 | /// 39 | /// # Panics ... 40 | /// - if the provided matrix is not a vector. 41 | pub fn rot_minus(vec: &MatZ) -> MatZ { 42 | let vec = if vec.is_column_vector() { 43 | vec.clone() 44 | } else if vec.is_row_vector() { 45 | vec.transpose() 46 | } else { 47 | panic!("The input must be a vector.") 48 | }; 49 | 50 | let mut out = MatZ::new(vec.get_num_rows(), vec.get_num_rows()); 51 | 52 | for i in 0..out.get_num_rows() { 53 | let entry = vec.get_entry(i, 0).unwrap(); 54 | for j in 0..out.get_num_columns() { 55 | let (row, sign) = match i + j { 56 | k if k >= out.get_num_rows() => ((k) % out.get_num_rows(), -1), 57 | k => (k, 1), 58 | }; 59 | out.set_entry(row, j, sign * &entry).unwrap(); 60 | } 61 | } 62 | out 63 | } 64 | 65 | /// Takes in a matrix, splits the matrix into separate columns and concatenates the 66 | /// rotation matrices as: 67 | /// `[rot^-(a_1) | rot^-(a_2) | ... | rot^-(a_m)]` 68 | /// 69 | /// It takes in both a column vector or a row vector, but the format of the matrix 70 | /// remains as described above. 71 | /// 72 | /// Parameters: 73 | /// - `vec`: The vector for which the rotation matrix will be computed. 74 | /// 75 | /// # Examples 76 | /// ``` 77 | /// use qfall_tools::utils::rotation_matrix::rot_minus_matrix; 78 | /// use qfall_math::integer::MatZ; 79 | /// use std::str::FromStr; 80 | /// 81 | /// let mat = MatZ::from_str("[[1,5,-1,9],[2,3,4,5]]").unwrap(); 82 | /// 83 | /// let rot_mat = rot_minus_matrix(&mat); 84 | /// ``` 85 | pub fn rot_minus_matrix(matrix: &MatZ) -> MatZ { 86 | let mut vec = Vec::new(); 87 | for i in 0..matrix.get_num_columns() { 88 | vec.push(rot_minus(&matrix.get_column(i).unwrap())); 89 | } 90 | 91 | let mut out = vec.first().unwrap().clone(); 92 | for mat in vec.iter().skip(1) { 93 | out = out.concat_horizontal(mat).unwrap(); 94 | } 95 | out 96 | } 97 | 98 | #[cfg(test)] 99 | mod test_rot_minus { 100 | use crate::utils::rotation_matrix::{rot_minus, rot_minus_matrix}; 101 | use qfall_math::integer::MatZ; 102 | use std::str::FromStr; 103 | 104 | /// Ensures that the rotation minus matrix works correctly for a vector. 105 | #[test] 106 | fn correct_rotation_matrix_vec() { 107 | let vec = MatZ::from_str("[[1],[5],[-1],[9]]").unwrap(); 108 | let row_vec = MatZ::from_str("[[1,5,-1,9]]").unwrap(); 109 | 110 | let rot_col = rot_minus(&vec); 111 | let rot_row = rot_minus(&row_vec); 112 | 113 | let cmp_rot = 114 | MatZ::from_str("[[1, -9, 1, -5],[5, 1, -9, 1],[-1, 5, 1, -9],[9, -1, 5, 1]]").unwrap(); 115 | assert_eq!(rot_col, rot_row); 116 | assert_eq!(cmp_rot, rot_col) 117 | } 118 | 119 | /// Ensures that the rotation minus matrix works correctly for matrices. 120 | #[test] 121 | fn correct_rotation_matrix_mat() { 122 | let mat = MatZ::from_str(&format!("[[1,5,-1,9],[{}, 1, 2, 3]]", u64::MAX)).unwrap(); 123 | 124 | let rot_mat = rot_minus_matrix(&mat); 125 | 126 | let cmp_rot = MatZ::from_str(&format!( 127 | "[[1, -{}, 5, -1, -1, -2, 9, -3],\ 128 | [{}, 1, 1, 5, 2, -1, 3, 9]]", 129 | u64::MAX, 130 | u64::MAX 131 | )) 132 | .unwrap(); 133 | assert_eq!(cmp_rot, rot_mat); 134 | } 135 | 136 | /// Ensures that the minus rotation matrix for vectors panics if a matrix is provided. 137 | #[test] 138 | #[should_panic] 139 | fn not_vector() { 140 | let mat = MatZ::from_str("[[1,5,-1,9],[1,2,3,4]]").unwrap(); 141 | 142 | let _ = rot_minus(&mat); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/sample/g_trapdoor/gadget_default.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Marvin Beckmann 2 | // 3 | // This file is part of qFALL-tools. 4 | // 5 | // qFALL-tools 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_tools::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_tools::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 [5i64, 10, 25] { 139 | for k in [5, 10] { 140 | let q = 2_i64.pow(k as u32); 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/trapdoor_distribution.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Marvin Beckmann 2 | // 3 | // This file is part of qFALL-tools. 4 | // 5 | // qFALL-tools 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 trapdoor distributions 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_tools::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_tools::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, 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::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 | coeff_embedding.norm_eucl_sqrd().unwrap() 166 | <= s.pow(2).unwrap() * coeff_embedding.get_num_rows() 167 | ); 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # qFALL-tools 2 | [github](https://github.com/qfall/tools) 3 | [crates.io](https://crates.io/crates/qfall-tools) 4 | [docs.rs](https://docs.rs/qfall-tools) 5 | [tutorial](https://qfall.github.io/book) 6 | [build](https://github.com/qfall/tools/actions/workflows/push.yml) 7 | [license](https://github.com/qfall/tools/blob/dev/LICENSE) 8 | 9 | `qFALL` is a prototyping library for lattice-based cryptography. 10 | This `tools`-crate collects common sub-modules and features used by lattice-based constructions to simplify and accelerate the development of such. 11 | 12 | ## Quick-Start 13 | First, ensure that you use a Unix-like distribution (Linux or MacOS). Setup [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) if you're using Windows. This is required due to this crate's dependency on FLINT. 14 | Then, make sure your `rustc --version` is `1.85` or newer. 15 | 16 | Furthermore, it's required that `m4`, a C-compiler such as `gcc`, and `make` are installed. 17 | ```bash 18 | sudo apt-get install m4 gcc make 19 | ``` 20 | Then, add you can add this crate to your project by executing the following command. 21 | ```bash 22 | cargo add qfall-tools 23 | ``` 24 | - Find further information on [our website](https://qfall.github.io/). Also check out [`qfall-math`](https://crates.io/crates/qfall-math) and [`qfall-schemes`](https://crates.io/crates/qfall-schemes). 25 | - Read the [documentation of this crate](https://docs.rs/qfall-tools). 26 | - We recommend [our tutorial](https://qfall.github.io/book) to start working with qFALL. 27 | 28 | ## What does qFALL-tools offer? 29 | qFALL-tools offers several commonly used sub-modules specific to lattice-based cryptography. 30 | - [Compression Techniques](https://docs.rs/qfall-tools/latest/qfall_tools/compression/index.html) 31 | - [Lossy Compression according to FIPS 203](https://docs.rs/qfall-tools/latest/qfall_tools/compression/trait.LossyCompressionFIPS203.html) 32 | - [Preimage Samplable Functions (PSF)](https://docs.rs/qfall-tools/latest/qfall_tools/primitive/psf/index.html) 33 | - [GPV-based PSF over Z_q](https://docs.rs/qfall-tools/latest/qfall_tools/primitive/psf/struct.PSFGPV.html) 34 | - [GPV-based PSF over R_q](https://docs.rs/qfall-tools/latest/qfall_tools/primitive/psf/struct.PSFGPVRing.html) 35 | - [MP12 / Perturbation-based PSF over Z_q](https://docs.rs/qfall-tools/latest/qfall_tools/primitive/psf/struct.PSFPerturbation.html) 36 | - [Trapdoors](https://docs.rs/qfall-tools/latest/qfall_tools/sample/g_trapdoor/index.html) 37 | - [G-trapdoor incl. short basis](https://docs.rs/qfall-tools/latest/qfall_tools/sample/g_trapdoor/gadget_classical/index.html) 38 | - [Ring-based G-trapdoor incl. short basis](https://docs.rs/qfall-tools/latest/qfall_tools/sample/g_trapdoor/gadget_ring/index.html) 39 | 40 | Furthermore, this crate simplifies the implementation of your prototype by supporting a range of [utility functions](https://docs.rs/qfall-tools/latest/qfall_tools/utils/index.html) to quickly instantiate [commonly used moduli](https://docs.rs/qfall-tools/latest/qfall_tools/utils/common_moduli/index.html), [rotation matrices](https://docs.rs/qfall-tools/latest/qfall_tools/utils/rotation_matrix/index.html), and [encodings](https://docs.rs/qfall-tools/latest/qfall_tools/utils/common_encodings/index.html). 41 | 42 | ## Quick Examples 43 | From String to Encoding for Encryption 44 | ```rust 45 | use qfall_tools::utils::{common_moduli::new_anticyclic, common_encodings::encode_value_in_polynomialringzq}; 46 | use qfall_math::integer::Z; 47 | 48 | // Create X^256 + 1 mod 3329 49 | let poly_mod = new_anticyclic(256, 3329).unwrap(); 50 | // Generate integer from string 51 | let message = Z::from_utf8("Hello!"); 52 | // Turn string into encoding q/2 and 0 for each 1 and 0 bit respectively 53 | let mu_q_half = encode_value_in_polynomialringzq(message, 2, &poly_mod).unwrap(); 54 | ``` 55 | 56 | Preimage Sampling using a PSF 57 | ```rust 58 | use qfall_tools::primitive::psf::{PSF, PSFPerturbation}; 59 | use qfall_tools::sample::g_trapdoor::gadget_parameters::GadgetParameters; 60 | use qfall_math::rational::Q; 61 | 62 | let psf = PSFPerturbation { 63 | gp: GadgetParameters::init_default(8, 64), 64 | r: Q::from(3), 65 | s: Q::from(25), 66 | }; 67 | 68 | // Generate matrix A with a trapdoor 69 | let (a, td) = psf.trap_gen(); 70 | // Choose a random target 71 | let domain_sample = psf.samp_d(); 72 | let target = psf.f_a(&a, &domain_sample); 73 | // Sample a preimage for the given target 74 | let preimage = psf.samp_p(&a, &td, &target); 75 | 76 | assert!(psf.check_domain(&preimage)); 77 | assert_eq!(a * preimage, target); 78 | ``` 79 | 80 | ## Bugs 81 | Please report bugs through the [GitHub issue tracker](https://github.com/qfall/tools/issues). 82 | 83 | ## Contributions 84 | Contributors are: 85 | - Marvin Beckmann 86 | - Jan Niklas Siemer 87 | 88 | See [Contributing](https://github.com/qfall/tools/blob/dev/CONTRIBUTING.md) for details on how to contribute. 89 | 90 | ## Citing 91 | 92 | Please use the following bibtex entry to cite [qFALL](https://qfall.github.io). 93 | 94 | ```text 95 | TODO: Update to eprint 96 | ``` 97 | 98 | ## Dependencies 99 | This project is based on [qfall-math](https://crates.io/crates/qfall-math), which builds on top of the C-based, optimised math-library [FLINT](https://flintlib.org/). We utilise [serde](https://crates.io/crates/serde) and [serde_json](https://crates.io/crates/serde_json) to (de-)serialize objects to and from JSON. This crate relies on [criterion](https://crates.io/crates/criterion) for benchmarking purposes. An extensive list can be found in our `Cargo.toml` file. 100 | 101 | ## License 102 | 103 | This library is distributed under the [Mozilla Public License Version 2.0](https://github.com/qfall/tools/blob/dev/LICENSE). 104 | Permissions of this weak copyleft license are conditioned on making the source code of licensed files and modifications of those files available under the same license (or in certain cases, under 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 to the larger work. 105 | -------------------------------------------------------------------------------- /src/utils/common_moduli.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Niklas Siemer 2 | // 3 | // This file is part of qFALL-tools. 4 | // 5 | // qFALL-tools 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 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_tools::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_tools::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/sample/g_trapdoor/gadget_parameters.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Marvin Beckmann 2 | // 3 | // This file is part of qFALL-tools. 4 | // 5 | // qFALL-tools 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_tools::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_tools::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_tools::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_tools::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-tools. 4 | // 5 | // qFALL-tools 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, MatrixDimensions, MatrixGetEntry, MatrixSetEntry, 20 | Pow, SetCoefficient, 21 | }, 22 | }; 23 | use std::fmt::Display; 24 | 25 | /// Generates a trapdoor according to Construction 1 in [\[2\]](<../index.html#:~:text=[2]>). 26 | /// Namely: 27 | /// - Generates the gadget matrix: `G` 28 | /// - Samples the trapdoor `R` from the specified distribution in `params` 29 | /// - Outputs 30 | /// `([1 | a_bar | g_1 - (a_bar * r_1 + e_1) | ... | g_k - (a_bar * r_k + e_k) ], r, e)` 31 | /// as a tuple of `(A,R)`, where `R` defines a trapdoor for `A`. 32 | /// 33 | /// Parameters: 34 | /// - `params`: all gadget parameters which are required to generate the trapdoor 35 | /// - `a_bar`: the matrix defining the second part of the G-Trapdoor 36 | /// - `tag`: the tag which is hidden within the matrix `A` 37 | /// - `s`: defining the deviation of the distribution from which `r` and `e` is sampled 38 | /// 39 | /// Returns a a parity-check matrix `a` derived from `a_bar` and its gadget-trapdoor 40 | /// `r` under a give tag `h`. 41 | /// 42 | /// # Examples 43 | /// ``` 44 | /// use qfall_tools::sample::g_trapdoor::{gadget_parameters::GadgetParametersRing, gadget_ring::gen_trapdoor_ring_lwe}; 45 | /// use qfall_math::integer::PolyOverZ; 46 | /// 47 | /// let params = GadgetParametersRing::init_default(8, 17); 48 | /// let a_bar = PolyOverZ::sample_uniform(¶ms.n, 0, ¶ms.modulus.get_q()).unwrap(); 49 | /// 50 | /// let (a, r, e) = gen_trapdoor_ring_lwe(¶ms, &a_bar, 10).unwrap(); 51 | /// ``` 52 | /// 53 | /// # Errors and Failures 54 | /// - Returns a [`MathError`] of type [`MismatchingMatrixDimension`](MathError::MismatchingMatrixDimension) 55 | /// if the matrices can not be concatenated due to mismatching dimensions. 56 | /// - Returns a [`MathError`] of type [`OutOfBounds`](MathError::OutOfBounds) 57 | /// if row or column are greater than the matrix size. 58 | /// 59 | /// # Panics ... 60 | /// - if `params.k < 1` or it does not fit into an [`i64`]. 61 | /// - if `params.n < 1`. 62 | pub fn gen_trapdoor_ring_lwe( 63 | params: &GadgetParametersRing, 64 | a_bar: &PolyOverZ, 65 | s: impl Into, 66 | ) -> Result<(MatPolynomialRingZq, MatPolyOverZ, MatPolyOverZ), MathError> { 67 | let s = s.into(); 68 | // Sample `r` and `e` using a provided distribution 69 | let r = params.distribution.sample(¶ms.n, ¶ms.k, &s); 70 | let e = params.distribution.sample(¶ms.n, ¶ms.k, &s); 71 | 72 | // compute the parity check matrix 73 | // `A = [1 | a | g^t - ar + e]` 74 | let mut big_a = MatPolyOverZ::new(1, 2); 75 | big_a.set_entry(0, 0, &PolyOverZ::from(1))?; 76 | big_a.set_entry(0, 1, a_bar)?; 77 | let g = gen_gadget_ring(¶ms.k, ¶ms.base); 78 | big_a = big_a.concat_horizontal(&(g.transpose() - (a_bar * &r + &e)))?; 79 | 80 | Ok((MatPolynomialRingZq::from((&big_a, ¶ms.modulus)), r, e)) 81 | } 82 | 83 | /// Generates a gadget vector based on its definition in [\[3\]](<../index.html#:~:text=[3]>). 84 | /// This corresponds to a vector `(base ^0, base^1, ..., base^{k-1})` where each entry 85 | /// is a constant polynomial. 86 | /// 87 | /// Parameters: 88 | /// - `k`: the size of the gadget vector 89 | /// - `base`: the base with which the entries in the gadget vector are defined 90 | /// 91 | /// Returns a gadget vector of length `k` with `base` as its base. 92 | /// 93 | /// # Examples 94 | /// ``` 95 | /// use qfall_tools::sample::g_trapdoor::gadget_ring::gen_gadget_ring; 96 | /// use qfall_math::integer::Z; 97 | /// 98 | /// let g = gen_gadget_ring(4, &Z::from(2)); 99 | /// ``` 100 | /// 101 | /// # Panics ... 102 | /// - if `k < 1` or it does not fit into an [`i64`]. 103 | pub fn gen_gadget_ring(k: impl TryInto + Display, base: &Z) -> MatPolyOverZ { 104 | let mut out = MatPolyOverZ::new(k, 1); 105 | for j in 0..out.get_num_rows() { 106 | unsafe { out.set_entry_unchecked(j, 0, &PolyOverZ::from(base.pow(j).unwrap())) }; 107 | } 108 | out 109 | } 110 | 111 | /// Computes an arbitrary solution for ` = value/(Modulus)`. 112 | /// 113 | /// Parameters: 114 | /// - `u`: the element for which a solution has to be computed 115 | /// - `k`: the length of a gadget vector 116 | /// - `base`: the base with which the gadget vector is defined 117 | /// 118 | /// Returns an arbitrary solution for ` = value/(Modulus)` 119 | /// 120 | /// # Examples 121 | /// ``` 122 | /// use qfall_math::integer::PolyOverZ; 123 | /// use qfall_math::integer_mod_q::PolynomialRingZq; 124 | /// use qfall_tools::sample::g_trapdoor::gadget_ring::gen_gadget_ring; 125 | /// use qfall_tools::sample::g_trapdoor::gadget_ring::find_solution_gadget_ring; 126 | /// use qfall_tools::sample::g_trapdoor::gadget_parameters::GadgetParametersRing; 127 | /// use qfall_math::integer_mod_q::MatPolynomialRingZq; 128 | /// use std::str::FromStr; 129 | /// 130 | /// let gp = GadgetParametersRing::init_default(10, 128); 131 | /// 132 | /// let gadget = gen_gadget_ring(&gp.k, &gp.base); 133 | /// let gadget = MatPolynomialRingZq::from((&gadget, &gp.modulus)); 134 | /// 135 | /// let u = PolyOverZ::from_str("10 5 124 12 14 14 1 2 4 1 5").unwrap(); 136 | /// let u = PolynomialRingZq::from((&u, &gp.modulus)); 137 | /// 138 | /// let solution = find_solution_gadget_ring(&u, &gp.k, &gp.base); 139 | /// let solution = MatPolynomialRingZq::from((&solution, &gp.modulus)); 140 | /// assert_eq!(u, gadget.dot_product(&solution).unwrap()) 141 | /// ``` 142 | /// 143 | /// # Panics ... 144 | /// - if the modulus of the value is greater than `base^k`. 145 | pub fn find_solution_gadget_ring(u: &PolynomialRingZq, k: &Z, base: &Z) -> MatPolyOverZ { 146 | let k_i64 = i64::try_from(k).unwrap(); 147 | let modulus = u.get_mod(); 148 | let size = modulus.get_degree(); 149 | let value = u 150 | .get_representative_least_nonnegative_residue() 151 | .into_coefficient_embedding(size); 152 | let value = MatZq::from((&value, modulus.get_q())); 153 | 154 | let classical_sol = find_solution_gadget_mat(&value, k, base); 155 | 156 | let mut out = MatPolyOverZ::new(1, k); 157 | for i in 0..k_i64 { 158 | let mut poly = PolyOverZ::default(); 159 | for j in 0..size { 160 | let entry = classical_sol.get_entry(i + j * k_i64, 0).unwrap(); 161 | poly.set_coeff(j, &entry).unwrap(); 162 | } 163 | out.set_entry(0, i, &poly).unwrap(); 164 | } 165 | out 166 | } 167 | 168 | #[cfg(test)] 169 | mod test_gen_trapdoor_ring { 170 | 171 | use crate::sample::g_trapdoor::{ 172 | gadget_parameters::GadgetParametersRing, gadget_ring::gen_trapdoor_ring_lwe, 173 | }; 174 | use qfall_math::{ 175 | integer::{MatPolyOverZ, PolyOverZ, Z}, 176 | integer_mod_q::MatPolynomialRingZq, 177 | traits::{Concatenate, GetCoefficient, MatrixDimensions, MatrixGetEntry, Pow}, 178 | }; 179 | 180 | /// Computes a trapdoor using the given secrets `(r,e)` 181 | fn compute_trapdoor(r: &MatPolyOverZ, e: &MatPolyOverZ, k: &Z) -> MatPolyOverZ { 182 | let i_k = MatPolyOverZ::identity(k, k); 183 | 184 | e.concat_vertical(r).unwrap().concat_vertical(&i_k).unwrap() 185 | } 186 | 187 | /// Assure that the trapdoor `r` returned from [`gen_trapdoor`] is actually a 188 | /// trapdoor for `a`. 189 | #[test] 190 | fn is_trapdoor() { 191 | let params = GadgetParametersRing::init_default(6, 32); 192 | let a_bar = PolyOverZ::sample_uniform(¶ms.n, 0, params.modulus.get_q()).unwrap(); 193 | 194 | // call gen_trapdoor to get matrix a and its 'trapdoor' r 195 | let (a, r, e) = gen_trapdoor_ring_lwe(¶ms, &a_bar, 10).unwrap(); 196 | 197 | // generate the trapdoor for a from r as trapdoor = [[e],[r],[I]] 198 | let trapdoor = 199 | MatPolynomialRingZq::from((&compute_trapdoor(&r, &e, ¶ms.k), ¶ms.modulus)); 200 | 201 | // ensure G = A*trapdoor (definition of a trapdoor) 202 | let res: MatPolynomialRingZq = &a * &trapdoor; 203 | 204 | assert_eq!(params.k, Z::from(res.get_num_columns())); 205 | assert_eq!(1, res.get_num_rows()); 206 | 207 | for i in 0..i64::try_from(¶ms.k).unwrap() { 208 | let res_entry: PolyOverZ = res.get_entry(0, i).unwrap(); 209 | assert_eq!(res_entry.get_coeff(0).unwrap(), params.base.pow(i).unwrap()) 210 | } 211 | } 212 | } 213 | 214 | #[cfg(test)] 215 | mod test_find_solution_gadget_ring { 216 | use super::{find_solution_gadget_ring, gen_gadget_ring}; 217 | use crate::sample::g_trapdoor::gadget_parameters::GadgetParametersRing; 218 | use qfall_math::{ 219 | integer::PolyOverZ, 220 | integer_mod_q::{MatPolynomialRingZq, PolynomialRingZq}, 221 | }; 222 | use std::str::FromStr; 223 | 224 | /// Ensures that the algorithm finds a correct solution such that ` = u`. 225 | #[test] 226 | fn is_correct_solution() { 227 | let gp = GadgetParametersRing::init_default(3, 32); 228 | 229 | let gadget = gen_gadget_ring(&gp.k, &gp.base); 230 | let gadget = MatPolynomialRingZq::from((&gadget, &gp.modulus)); 231 | 232 | let u = PolyOverZ::from_str("10 5 124 12 14 14 1 2 4 1 5").unwrap(); 233 | let u = PolynomialRingZq::from((&u, &gp.modulus)); 234 | 235 | let solution = find_solution_gadget_ring(&u, &gp.k, &gp.base); 236 | let solution = MatPolynomialRingZq::from((&solution, &gp.modulus)); 237 | 238 | assert_eq!(u, gadget.dot_product(&solution).unwrap()) 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/utils/common_encodings.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2025 Niklas Siemer 2 | // 3 | // This file is part of qFALL-tools. 4 | // 5 | // qFALL-tools 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 message encoding and decoding functions commonly used in lattice-based 10 | //! encryption schemes. 11 | 12 | use qfall_math::{ 13 | error::MathError, 14 | integer::{PolyOverZ, Z}, 15 | integer_mod_q::{ModulusPolynomialRingZq, PolynomialRingZq}, 16 | traits::{GetCoefficient, SetCoefficient}, 17 | }; 18 | 19 | /// Takes any non-negative integer value, represents it with respect to `base` 20 | /// and generates a [`PolynomialRingZq`] containing the previous representation w.r.t. `base` 21 | /// across its coefficients multiplied by `q/base`. 22 | /// This function is commonly used in encryption algorithms of lattice-based PKE schemes 23 | /// and described as `⌊q/base * μ⌋`, where `μ ∈ R_{base}^n`. 24 | /// 25 | /// Parameters: 26 | /// - `value`: the non-negative integer value to encode 27 | /// - `base`: defines the encoded representation, usually chosen as 2 for binary representation 28 | /// - `modulus`: specifies the modulus of the returned struct and `q` 29 | /// 30 | /// Returns a [`PolynomialRingZq`] containing `⌊q/base * μ⌋` as described above or a [`MathError`] 31 | /// if `base < 2`, `value < 0`, or `value` represented w.r.t. `base` requires more than `modulus.get_degree()` coefficients. 32 | /// 33 | /// # Examples 34 | /// ``` 35 | /// use qfall_tools::utils::common_encodings::encode_value_in_polynomialringzq; 36 | /// use qfall_tools::utils::common_moduli::new_anticyclic; 37 | /// 38 | /// let modulus = new_anticyclic(16, 257).unwrap(); 39 | /// let base = 2; 40 | /// let value = u16::MAX; 41 | /// 42 | /// let encoded = encode_value_in_polynomialringzq(value, base, &modulus).unwrap(); 43 | /// ``` 44 | /// 45 | /// # Errors and Failures 46 | /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) 47 | /// if `value` is negative, `value` represented w.r.t. `base` has more digits than coefficients available in `modulus.get_degree()`, 48 | /// or `base < 2`. 49 | pub fn encode_value_in_polynomialringzq( 50 | value: impl Into, 51 | base: impl Into, 52 | modulus: &ModulusPolynomialRingZq, 53 | ) -> Result { 54 | let mut value = value.into(); 55 | let base = base.into(); 56 | let modulus: ModulusPolynomialRingZq = modulus.into(); 57 | 58 | if value < Z::ZERO { 59 | return Err(MathError::InvalidIntegerInput(format!( 60 | "The given value {value} needs to be non-negative." 61 | ))); 62 | } 63 | 64 | let min_req_degree = u64::try_from((&value + Z::ONE).log_ceil(&base)?)?; 65 | if min_req_degree > modulus.get_degree() as u64 { 66 | return Err(MathError::InvalidIntegerInput(format!( 67 | "The given value requires {min_req_degree} digits represented w.r.t. base {base}. Your modulus only provides space for {} digits.", 68 | modulus.get_degree() 69 | ))); 70 | } 71 | 72 | // get representation of value w.r.t. base as base 73 | let mut base_repr = Vec::with_capacity(min_req_degree as usize); 74 | while value > Z::ZERO { 75 | let digit = &value % &base; 76 | base_repr.push(digit); 77 | value = value.div_floor(&base); 78 | } 79 | 80 | let mut res = PolyOverZ::default(); 81 | for (i, digit) in base_repr.iter().enumerate() { 82 | if digit != &Z::ZERO { 83 | unsafe { res.set_coeff_unchecked(i as i64, digit) }; 84 | } 85 | } 86 | 87 | // spread out each represented value by factor `q / base` 88 | let q_div_base = modulus.get_q().div_floor(&base); 89 | res *= q_div_base; 90 | 91 | Ok(PolynomialRingZq::from((res, modulus))) 92 | } 93 | 94 | /// Takes an encoded [`PolynomialRingZq`] and decodes it w.r.t. `base` and `q`, effectively performing 95 | /// `μ = ⌈base/q * poly⌋ mod base` for `poly ∈ R_q^n`. Then, it takes any value of `μ ∈ R_{base}^n` and 96 | /// turns it into a non-negative integer of type [`Z`]. 97 | /// This function is commonly used in decryption algorithms of lattice-based PKE schemes and invers to 98 | /// [`encode_value_in_polynomialringzq`]. 99 | /// 100 | /// Parameters: 101 | /// - `poly`: the [`PolynomialRingZq`] containing the encoded value 102 | /// - `base`: defines the encoded representation, usually chosen as 2 for binary representation 103 | /// 104 | /// Returns a [`Z`] containing the value of the vector `⌈base/q * poly⌋ mod base ∈ R_{base}^n` as a decimal number 105 | /// as described above or a [`MathError`] if `base < 2`. 106 | /// 107 | /// # Examples 108 | /// ``` 109 | /// use qfall_tools::utils::common_encodings::{encode_value_in_polynomialringzq, decode_value_from_polynomialringzq}; 110 | /// use qfall_tools::utils::common_moduli::new_anticyclic; 111 | /// 112 | /// let modulus = new_anticyclic(16, 257).unwrap(); 113 | /// let base = 2; 114 | /// let value = u16::MAX; 115 | /// 116 | /// let encoded = encode_value_in_polynomialringzq(value, base, &modulus).unwrap(); 117 | /// let decoded = decode_value_from_polynomialringzq(&encoded, base).unwrap(); 118 | /// 119 | /// assert_eq!(value, decoded); 120 | /// ``` 121 | /// 122 | /// # Errors and Failures 123 | /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) 124 | /// if `base < 2`. 125 | pub fn decode_value_from_polynomialringzq( 126 | poly: &PolynomialRingZq, 127 | base: impl Into, 128 | ) -> Result { 129 | let base = base.into(); 130 | let q = poly.get_mod().get_q(); 131 | let q_div_2base = q.div_floor(2 * &base); 132 | 133 | if base <= Z::ONE { 134 | return Err(MathError::InvalidIntegerInput(format!( 135 | "The given base {base} is smaller than 2, which does not allow the encoding of any information." 136 | ))); 137 | } 138 | 139 | let mut poly = poly.get_representative_least_nonnegative_residue(); 140 | poly *= &base; 141 | 142 | let mut out = Z::default(); 143 | 144 | for i in (0..=poly.get_degree()).rev() { 145 | let mut coeff = unsafe { poly.get_coeff_unchecked(i) }; 146 | coeff += &q_div_2base; 147 | let res = coeff.div_floor(&q) % &base; 148 | out *= &base; 149 | out += res; 150 | } 151 | 152 | Ok(out) 153 | } 154 | 155 | #[cfg(test)] 156 | mod test_encode_value_in_polynomialringzq { 157 | use crate::utils::{ 158 | common_encodings::encode_value_in_polynomialringzq, common_moduli::new_anticyclic, 159 | }; 160 | use qfall_math::{integer::Z, traits::GetCoefficient}; 161 | 162 | /// Ensures that [`encode_value_in_polynomialringzq`] works properly for `base = 2`. 163 | #[test] 164 | fn binary() { 165 | let q = 257; 166 | let q_half = q / 2; 167 | let modulus = new_anticyclic(16, q).unwrap(); 168 | 169 | let res0 = encode_value_in_polynomialringzq(1, 2, &modulus).unwrap(); 170 | let res1 = encode_value_in_polynomialringzq(2, 2, &modulus).unwrap(); 171 | let res2 = encode_value_in_polynomialringzq(3, 2, &modulus).unwrap(); 172 | 173 | assert_eq!(GetCoefficient::::get_coeff(&res0, 0).unwrap(), q_half); 174 | assert_eq!(res0.get_degree(), 0); 175 | 176 | assert_eq!(GetCoefficient::::get_coeff(&res1, 0).unwrap(), 0); 177 | assert_eq!(GetCoefficient::::get_coeff(&res1, 1).unwrap(), q_half); 178 | assert_eq!(res1.get_degree(), 1); 179 | 180 | assert_eq!(GetCoefficient::::get_coeff(&res2, 0).unwrap(), q_half); 181 | assert_eq!(GetCoefficient::::get_coeff(&res2, 1).unwrap(), q_half); 182 | assert_eq!(res2.get_degree(), 1); 183 | } 184 | 185 | /// Ensures that [`encode_value_in_polynomialringzq`] works properly for `base = 3`. 186 | #[test] 187 | fn ternary() { 188 | let q = 257; 189 | let q_third = q / 3; 190 | let modulus = new_anticyclic(16, q).unwrap(); 191 | 192 | let res0 = encode_value_in_polynomialringzq(1, 3, &modulus).unwrap(); 193 | let res1 = encode_value_in_polynomialringzq(2, 3, &modulus).unwrap(); 194 | let res2 = encode_value_in_polynomialringzq(3, 3, &modulus).unwrap(); 195 | 196 | assert_eq!(GetCoefficient::::get_coeff(&res0, 0).unwrap(), q_third); 197 | assert_eq!(res0.get_degree(), 0); 198 | 199 | assert_eq!( 200 | GetCoefficient::::get_coeff(&res1, 0).unwrap(), 201 | 2 * q_third 202 | ); 203 | assert_eq!(res1.get_degree(), 0); 204 | 205 | assert_eq!(GetCoefficient::::get_coeff(&res2, 0).unwrap(), 0); 206 | assert_eq!(GetCoefficient::::get_coeff(&res2, 1).unwrap(), q_third); 207 | assert_eq!(res2.get_degree(), 1); 208 | } 209 | 210 | /// Ensures that [`encode_value_in_polynomialringzq`] returns an error if there is not enough space due to modulus size constraints. 211 | #[test] 212 | fn not_enough_space() { 213 | let modulus = new_anticyclic(16, 257).unwrap(); 214 | 215 | let res = encode_value_in_polynomialringzq(u16::MAX as u32 + 1, 2, &modulus); 216 | 217 | assert!(res.is_err()); 218 | } 219 | 220 | /// Ensures that [`encode_value_in_polynomialringzq`] returns an error if `base < 2`. 221 | #[test] 222 | fn too_small_base() { 223 | let modulus = new_anticyclic(16, 257).unwrap(); 224 | 225 | let res = encode_value_in_polynomialringzq(4, 1, &modulus); 226 | 227 | assert!(res.is_err()); 228 | } 229 | 230 | /// Ensures that [`encode_value_in_polynomialringzq`] returns an error if `value < 0`. 231 | #[test] 232 | fn neagive_value() { 233 | let modulus = new_anticyclic(16, 257).unwrap(); 234 | 235 | let res = encode_value_in_polynomialringzq(-1, 1, &modulus); 236 | 237 | assert!(res.is_err()); 238 | } 239 | } 240 | 241 | #[cfg(test)] 242 | mod test_decode_value_from_polynomialringzq { 243 | use crate::utils::{ 244 | common_encodings::{decode_value_from_polynomialringzq, encode_value_in_polynomialringzq}, 245 | common_moduli::new_anticyclic, 246 | }; 247 | use qfall_math::{integer::Z, integer_mod_q::PolynomialRingZq}; 248 | 249 | /// Ensures that encoded information can be decoded without losing information for `base = 2`. 250 | #[test] 251 | fn round_trip_binary() { 252 | let q = 257; 253 | let base = 2; 254 | let modulus = new_anticyclic(17, q).unwrap(); 255 | let msg = Z::sample_uniform(0, u16::MAX).unwrap(); 256 | 257 | let encoding = encode_value_in_polynomialringzq(&msg, base, &modulus).unwrap(); 258 | let decoding = decode_value_from_polynomialringzq(&encoding, base).unwrap(); 259 | 260 | assert_eq!(msg, decoding); 261 | } 262 | 263 | /// Ensures that encoded information can be decoded without losing information for `base = 3`. 264 | #[test] 265 | fn round_trip_ternary() { 266 | let q = 257; 267 | let base = 3; 268 | let modulus = new_anticyclic(16, q).unwrap(); 269 | let msg = Z::sample_uniform(0, u16::MAX).unwrap(); 270 | 271 | let encoding = encode_value_in_polynomialringzq(&msg, base, &modulus).unwrap(); 272 | let decoding = decode_value_from_polynomialringzq(&encoding, base).unwrap(); 273 | 274 | assert_eq!(msg, decoding); 275 | } 276 | 277 | /// Ensures that [`decode_value_from_polynomialringzq`] returns an error if `base < 2`. 278 | #[test] 279 | fn too_small_base() { 280 | let modulus = new_anticyclic(16, 257).unwrap(); 281 | let poly = PolynomialRingZq::sample_uniform(&modulus); 282 | 283 | let res = decode_value_from_polynomialringzq(&poly, 1); 284 | 285 | assert!(res.is_err()); 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /src/primitive/psf/gpv.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Marvin Beckmann 2 | // 3 | // This file is part of qFALL-tools. 4 | // 5 | // qFALL-tools 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 trapdoors. 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_tools::primitive::psf::PSFGPV; 37 | /// use qfall_tools::sample::g_trapdoor::gadget_parameters::GadgetParameters; 38 | /// use qfall_math::rational::Q; 39 | /// use qfall_tools::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 | type A = MatZq; 61 | type Trapdoor = (MatZ, MatQ); 62 | type Domain = MatZ; 63 | type Range = MatZq; 64 | 65 | /// Computes a G-Trapdoor according to the [`GadgetParameters`] 66 | /// defined in the struct. 67 | /// It returns a matrix `A` together with a short base and its GSO. 68 | /// 69 | /// # Examples 70 | /// ``` 71 | /// use qfall_tools::primitive::psf::PSFGPV; 72 | /// use qfall_tools::sample::g_trapdoor::gadget_parameters::GadgetParameters; 73 | /// use qfall_math::rational::Q; 74 | /// use qfall_tools::primitive::psf::PSF; 75 | /// 76 | /// let psf = PSFGPV { 77 | /// gp: GadgetParameters::init_default(8, 64), 78 | /// s: Q::from(12), 79 | /// }; 80 | /// 81 | /// let (a, (sh_b, sh_b_gso)) = psf.trap_gen(); 82 | /// ``` 83 | fn trap_gen(&self) -> (MatZq, (MatZ, MatQ)) { 84 | let a_bar = MatZq::sample_uniform(&self.gp.n, &self.gp.m_bar, &self.gp.q); 85 | 86 | let tag = MatZq::identity(&self.gp.n, &self.gp.n, &self.gp.q); 87 | 88 | let (a, r) = gen_trapdoor(&self.gp, &a_bar, &tag).unwrap(); 89 | 90 | let short_base = gen_short_basis_for_trapdoor(&self.gp, &tag, &a, &r); 91 | let short_base_gso = MatQ::from(&short_base).gso(); 92 | 93 | (a, (short_base, short_base_gso)) 94 | } 95 | 96 | /// Samples in the domain using SampleD with the standard basis and center `0`. 97 | /// 98 | /// # Examples 99 | /// ``` 100 | /// use qfall_tools::primitive::psf::PSFGPV; 101 | /// use qfall_tools::sample::g_trapdoor::gadget_parameters::GadgetParameters; 102 | /// use qfall_math::rational::Q; 103 | /// use qfall_tools::primitive::psf::PSF; 104 | /// 105 | /// let psf = PSFGPV { 106 | /// gp: GadgetParameters::init_default(8, 64), 107 | /// s: Q::from(12), 108 | /// }; 109 | /// let (a, td) = psf.trap_gen(); 110 | /// 111 | /// let domain_sample = psf.samp_d(); 112 | /// ``` 113 | fn samp_d(&self) -> MatZ { 114 | let m = &self.gp.n * &self.gp.k + &self.gp.m_bar; 115 | MatZ::sample_discrete_gauss(&m, 1, 0, &self.s).unwrap() 116 | } 117 | 118 | /// Samples an `e` in the domain using SampleD with a short basis that is generated 119 | /// from the G-Trapdoor from the conditioned conditioned 120 | /// discrete Gaussian with `f_a(a,e) = u` for a provided syndrome `u`. 121 | /// 122 | /// *Note*: the provided parameters `a,r,u` must fit together, 123 | /// otherwise unexpected behavior such as panics may occur. 124 | /// 125 | /// Parameters: 126 | /// - `a`: The parity-check matrix 127 | /// - `short_base`: The short base for `Λ^⟂(A)` 128 | /// - `short_base_gso`: The precomputed GSO of the short_base 129 | /// - `u`: The syndrome from the range 130 | /// 131 | /// Returns a sample `e` from the domain on the conditioned discrete 132 | /// Gaussian distribution `f_a(a,e) = u`. 133 | /// 134 | /// # Examples 135 | /// ``` 136 | /// use qfall_tools::primitive::psf::PSFGPV; 137 | /// use qfall_tools::sample::g_trapdoor::gadget_parameters::GadgetParameters; 138 | /// use qfall_math::rational::Q; 139 | /// use qfall_tools::primitive::psf::PSF; 140 | /// 141 | /// let psf = PSFGPV { 142 | /// gp: GadgetParameters::init_default(8, 64), 143 | /// s: Q::from(12), 144 | /// }; 145 | /// let (a, td) = psf.trap_gen(); 146 | /// let domain_sample = psf.samp_d(); 147 | /// let range_fa = psf.f_a(&a, &domain_sample); 148 | /// 149 | /// let preimage = psf.samp_p(&a, &td, &range_fa); 150 | /// assert_eq!(range_fa, psf.f_a(&a, &preimage)) 151 | /// ``` 152 | fn samp_p(&self, a: &MatZq, (short_base, short_base_gso): &(MatZ, MatQ), u: &MatZq) -> MatZ { 153 | let sol: MatZ = a 154 | .solve_gaussian_elimination(u) 155 | .unwrap() 156 | .get_representative_least_nonnegative_residue(); 157 | 158 | let center = MatQ::from(&(-1 * &sol)); 159 | 160 | sol + MatZ::sample_d_precomputed_gso(short_base, short_base_gso, ¢er, &self.s).unwrap() 161 | } 162 | 163 | /// Implements the efficiently computable function `f_a` which here corresponds to 164 | /// `a*sigma`. The sigma must be from the domain, i.e. D_n. 165 | /// 166 | /// Parameters: 167 | /// - `a`: The parity-check matrix of dimensions `n x m` 168 | /// - `sigma`: A column vector of length `m` 169 | /// 170 | /// Returns `a*sigma` 171 | /// 172 | /// # Examples 173 | /// ``` 174 | /// use qfall_tools::primitive::psf::PSFGPV; 175 | /// use qfall_tools::sample::g_trapdoor::gadget_parameters::GadgetParameters; 176 | /// use qfall_math::rational::Q; 177 | /// use qfall_tools::primitive::psf::PSF; 178 | /// 179 | /// let psf = PSFGPV { 180 | /// gp: GadgetParameters::init_default(8, 64), 181 | /// s: Q::from(12), 182 | /// }; 183 | /// let (a, td) = psf.trap_gen(); 184 | /// let domain_sample = psf.samp_d(); 185 | /// let range_fa = psf.f_a(&a, &domain_sample); 186 | /// ``` 187 | /// 188 | /// # Panics ... 189 | /// - if `sigma` is not in the domain. 190 | fn f_a(&self, a: &MatZq, sigma: &MatZ) -> MatZq { 191 | assert!(self.check_domain(sigma)); 192 | a * sigma 193 | } 194 | 195 | /// Checks whether a value `sigma` is in D_n = {e ∈ Z^m | |e| <= s sqrt(m)}. 196 | /// 197 | /// Parameters: 198 | /// - `sigma`: The value for which is checked, if it is in the domain 199 | /// 200 | /// Returns true, if `sigma` is in D_n. 201 | /// 202 | /// # Examples 203 | /// ``` 204 | /// use qfall_tools::primitive::psf::PSF; 205 | /// use qfall_tools::primitive::psf::PSFGPV; 206 | /// use qfall_tools::sample::g_trapdoor::gadget_parameters::GadgetParameters; 207 | /// use qfall_math::rational::Q; 208 | /// 209 | /// let psf = PSFGPV { 210 | /// gp: GadgetParameters::init_default(8, 64), 211 | /// s: Q::from(12), 212 | /// }; 213 | /// let (a, td) = psf.trap_gen(); 214 | /// 215 | /// let vector = psf.samp_d(); 216 | /// 217 | /// assert!(psf.check_domain(&vector)); 218 | /// ``` 219 | fn check_domain(&self, sigma: &MatZ) -> bool { 220 | let m = &self.gp.n * &self.gp.k + &self.gp.m_bar; 221 | sigma.is_column_vector() 222 | && m == sigma.get_num_rows() 223 | && sigma.norm_eucl_sqrd().unwrap() <= self.s.pow(2).unwrap() * &m 224 | } 225 | } 226 | 227 | #[cfg(test)] 228 | mod test_gpv_psf { 229 | use super::super::gpv::PSFGPV; 230 | use super::PSF; 231 | use crate::sample::g_trapdoor::gadget_parameters::GadgetParameters; 232 | use qfall_math::integer::MatZ; 233 | use qfall_math::rational::Q; 234 | use qfall_math::traits::*; 235 | 236 | /// Ensures that `samp_d` actually computes values that are in D_n. 237 | #[test] 238 | fn samp_d_samples_from_dn() { 239 | for (n, q) in [(5, 256), (10, 128), (15, 157)] { 240 | let psf = PSFGPV { 241 | gp: GadgetParameters::init_default(n, q), 242 | s: Q::from(10), 243 | }; 244 | 245 | for _ in 0..5 { 246 | assert!(psf.check_domain(&psf.samp_d())); 247 | } 248 | } 249 | } 250 | 251 | /// Ensures that `samp_p` actually computes preimages that are also in the correct 252 | /// domain. 253 | #[test] 254 | fn samp_p_preimage_and_domain() { 255 | for (n, q) in [(5, 256), (6, 128)] { 256 | let psf = PSFGPV { 257 | gp: GadgetParameters::init_default(n, q), 258 | s: Q::from(10), 259 | }; 260 | let (a, r) = psf.trap_gen(); 261 | let domain_sample = psf.samp_d(); 262 | let range_fa = psf.f_a(&a, &domain_sample); 263 | 264 | let preimage = psf.samp_p(&a, &r, &range_fa); 265 | assert_eq!(range_fa, psf.f_a(&a, &preimage)); 266 | assert!(psf.check_domain(&preimage)); 267 | } 268 | } 269 | 270 | /// Ensures that `f_a` returns `a*sigma`. 271 | #[test] 272 | fn f_a_works_as_expected() { 273 | for (n, q) in [(5, 256), (6, 128)] { 274 | let psf = PSFGPV { 275 | gp: GadgetParameters::init_default(n, q), 276 | s: Q::from(10), 277 | }; 278 | let (a, _) = psf.trap_gen(); 279 | let domain_sample = psf.samp_d(); 280 | 281 | assert_eq!(&a * &domain_sample, psf.f_a(&a, &domain_sample)); 282 | } 283 | } 284 | 285 | /// Ensures that `f_a` panics if a value is provided, that is not within the domain. 286 | /// Sigma is not a vector. 287 | #[test] 288 | #[should_panic] 289 | fn f_a_sigma_not_in_domain_matrix() { 290 | let psf = PSFGPV { 291 | gp: GadgetParameters::init_default(8, 128), 292 | s: Q::from(10), 293 | }; 294 | let (a, _) = psf.trap_gen(); 295 | let not_in_domain = MatZ::new(a.get_num_columns(), 2); 296 | 297 | let _ = psf.f_a(&a, ¬_in_domain); 298 | } 299 | 300 | /// Ensures that `f_a` panics if a value is provided, that is not within the domain. 301 | /// Sigma is not of the correct length. 302 | #[test] 303 | #[should_panic] 304 | fn f_a_sigma_not_in_domain_incorrect_length() { 305 | let psf = PSFGPV { 306 | gp: GadgetParameters::init_default(8, 128), 307 | s: Q::from(10), 308 | }; 309 | let (a, _) = psf.trap_gen(); 310 | let not_in_domain = MatZ::new(a.get_num_columns() - 1, 1); 311 | 312 | let _ = psf.f_a(&a, ¬_in_domain); 313 | } 314 | 315 | /// Ensures that `f_a` panics if a value is provided, that is not within the domain. 316 | /// Sigma is too long. 317 | #[test] 318 | #[should_panic] 319 | fn f_a_sigma_not_in_domain_too_long() { 320 | let psf = PSFGPV { 321 | gp: GadgetParameters::init_default(8, 128), 322 | s: Q::from(10), 323 | }; 324 | let (a, _) = psf.trap_gen(); 325 | let not_in_domain = 326 | psf.s.round() * a.get_num_columns() * MatZ::identity(a.get_num_columns(), 1); 327 | 328 | let _ = psf.f_a(&a, ¬_in_domain); 329 | } 330 | 331 | /// Ensures that `check_domain` works for vectors with the correct length. 332 | #[test] 333 | fn check_domain_as_expected() { 334 | let psf = PSFGPV { 335 | gp: GadgetParameters::init_default(8, 128), 336 | s: Q::from(10), 337 | }; 338 | let (a, _) = psf.trap_gen(); 339 | let value = psf.s.round(); 340 | let mut in_domain = MatZ::new(a.get_num_columns(), 1); 341 | for i in 0..in_domain.get_num_rows() { 342 | in_domain.set_entry(i, 0, &value).unwrap(); 343 | } 344 | 345 | assert!(psf.check_domain(&MatZ::new(a.get_num_columns(), 1))); 346 | assert!(psf.check_domain(&in_domain)); 347 | } 348 | 349 | /// Ensures that `check_domain` returns false for values that are not in the domain. 350 | #[test] 351 | fn check_domain_not_in_dn() { 352 | let psf = PSFGPV { 353 | gp: GadgetParameters::init_default(8, 128), 354 | s: Q::from(10), 355 | }; 356 | let (a, _) = psf.trap_gen(); 357 | 358 | let matrix = MatZ::new(a.get_num_columns(), 2); 359 | let too_short = MatZ::new(a.get_num_columns() - 1, 1); 360 | let too_long = MatZ::new(a.get_num_columns() + 1, 1); 361 | let entry_too_large = 362 | psf.s.round() * a.get_num_columns() * MatZ::identity(a.get_num_columns(), 1); 363 | 364 | assert!(!psf.check_domain(&matrix)); 365 | assert!(!psf.check_domain(&too_long)); 366 | assert!(!psf.check_domain(&too_short)); 367 | assert!(!psf.check_domain(&entry_too_large)); 368 | } 369 | } 370 | -------------------------------------------------------------------------------- /src/sample/g_trapdoor/short_basis_classical.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Marvin Beckmann 2 | // 3 | // This file is part of qFALL-tools. 4 | // 5 | // qFALL-tools 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 short basis from a G-trapdoor 10 | //! and its parity check matrix. 11 | 12 | use super::{ 13 | gadget_classical::{find_solution_gadget_mat, short_basis_gadget}, 14 | gadget_parameters::GadgetParameters, 15 | }; 16 | use qfall_math::{ 17 | integer::{MatZ, Z}, 18 | integer_mod_q::MatZq, 19 | traits::*, 20 | }; 21 | 22 | /// Generates a short basis according to [\[1\]](<../index.html#:~:text=[1]>). 23 | /// Also refer to Lemma 5.3 in the eprint version . 24 | /// 25 | /// The matrix is generated as `[ I | R, 0 | I ] * [ 0 | I, S' | W ]` 26 | /// where `W` is a solution of `GW = -H^{-1}A [ I | 0 ] mod q` and `S'` is a 27 | /// reordering of `S` (if `base^k=q` then reversed, otherwise the same as before). 28 | /// This corresponds to an appropriate reordering from 29 | /// [\[1\]](<../index.html#:~:text=[1]>) and Lemma 3.2 from 30 | /// [\[4\]](<../index.html#:~:text=[4]>). 31 | /// 32 | /// Parameters: 33 | /// - `params`: the gadget parameters with which the trapdoor was generated 34 | /// - `tag`: the corresponding tag 35 | /// - `a`: the parity check matrix 36 | /// - `r`: the trapdoor for `a` 37 | /// 38 | /// Returns a short basis for the lattice `Λ^⟂(a)` using the trapdoor `r` 39 | /// 40 | /// # Examples 41 | /// ``` 42 | /// use qfall_tools::sample::g_trapdoor::{gadget_parameters::GadgetParameters, 43 | /// gadget_default::gen_trapdoor_default}; 44 | /// use qfall_tools::sample::g_trapdoor::short_basis_classical::gen_short_basis_for_trapdoor; 45 | /// use qfall_math::integer_mod_q::MatZq; 46 | /// 47 | /// let params = GadgetParameters::init_default(10, 127); 48 | /// let (a, r) = gen_trapdoor_default(¶ms.n, 127); 49 | /// 50 | /// let tag = MatZq::identity(¶ms.n, ¶ms.n, 127); 51 | /// 52 | /// let short_basis = gen_short_basis_for_trapdoor(¶ms, &tag, &a, &r); 53 | /// ``` 54 | pub fn gen_short_basis_for_trapdoor( 55 | params: &GadgetParameters, 56 | tag: &MatZq, 57 | a: &MatZq, 58 | r: &MatZ, 59 | ) -> MatZ { 60 | let sa_l = gen_sa_l(r); 61 | let sa_r = gen_sa_r(params, tag, a); 62 | sa_l * sa_r 63 | } 64 | 65 | /// Computes [ I | R, 0 | I ] 66 | fn gen_sa_l(r: &MatZ) -> MatZ { 67 | let r_rows = r.get_num_rows(); 68 | let r_cols = r.get_num_columns(); 69 | let mut sa_l = MatZ::identity(r_rows + r_cols, r_rows + r_cols); 70 | 71 | sa_l.set_submatrix(0, r_rows, r, 0, 0, -1, -1).unwrap(); 72 | 73 | sa_l 74 | } 75 | 76 | /// Computes `[ 0 | I, S' | W ]` 77 | fn gen_sa_r(params: &GadgetParameters, tag: &MatZq, a: &MatZq) -> MatZ { 78 | let mut s = short_basis_gadget(params); 79 | // if `base^k = q`, then the reverse of `S` has a shorter diagonalization 80 | if params.base.pow(¶ms.k).unwrap() == params.q { 81 | s.reverse_columns(); 82 | } 83 | let w = compute_w(params, tag, a); 84 | 85 | let mut sa_r = MatZ::new( 86 | s.get_num_rows() + w.get_num_columns(), 87 | s.get_num_columns() + w.get_num_columns(), 88 | ); 89 | 90 | let offset_identity = s.get_num_columns(); 91 | for diagonal in 0..w.get_num_columns() { 92 | unsafe { sa_r.set_entry_unchecked(diagonal, diagonal + offset_identity, 1) }; 93 | } 94 | 95 | let offset_lower = w.get_num_columns(); 96 | sa_r.set_submatrix(offset_lower, 0, &s, 0, 0, -1, -1) 97 | .unwrap(); 98 | sa_r.set_submatrix(offset_lower, s.get_num_columns(), &w, 0, 0, -1, -1) 99 | .unwrap(); 100 | 101 | sa_r 102 | } 103 | 104 | /// Computes `W` with `GW = -H^{-1}A [ I | 0 ] mod q` 105 | fn compute_w(params: &GadgetParameters, tag: &MatZq, a: &MatZq) -> MatZ { 106 | let tag_inv = tag.inverse().unwrap(); 107 | 108 | let rhs = Z::MINUS_ONE * tag_inv * (a * MatZ::identity(a.get_num_columns(), ¶ms.m_bar)); 109 | find_solution_gadget_mat(&rhs, ¶ms.k, ¶ms.base) 110 | } 111 | 112 | #[cfg(test)] 113 | mod test_gen_short_basis_for_trapdoor { 114 | use super::gen_short_basis_for_trapdoor; 115 | use crate::sample::g_trapdoor::{ 116 | gadget_classical::gen_trapdoor, gadget_default::gen_trapdoor_default, 117 | gadget_parameters::GadgetParameters, 118 | }; 119 | use qfall_math::{ 120 | integer::Z, 121 | integer_mod_q::{MatZq, Modulus}, 122 | rational::{MatQ, Q}, 123 | traits::*, 124 | }; 125 | 126 | /// Ensure that every vector within the returned basis is in `Λ^⟂(A)`. 127 | #[test] 128 | fn is_basis_not_power_tag_identity() { 129 | for n in [1, 5, 10, 12] { 130 | let q = Modulus::from(127 + 3 * n); 131 | let params = GadgetParameters::init_default(n, &q); 132 | let (a, r) = gen_trapdoor_default(¶ms.n, &q); 133 | 134 | let tag = MatZq::identity(¶ms.n, ¶ms.n, &q); 135 | 136 | let short_basis = gen_short_basis_for_trapdoor(¶ms, &tag, &a, &r); 137 | 138 | let zero_vec = MatZq::new(a.get_num_rows(), 1, &q); 139 | 140 | for i in 0..short_basis.get_num_columns() { 141 | assert_eq!(zero_vec, &a * short_basis.get_column(i).unwrap()) 142 | } 143 | } 144 | } 145 | 146 | /// Ensures that the trapdoor generated is actually a base for `Λ^⟂(A)` 147 | /// included with an actual tag, here `a*I_n`. 148 | #[test] 149 | fn is_basis_with_tag_factor_identity() { 150 | for n in [2, 5, 10, 12] { 151 | let q = Modulus::from(124 + 2 * n); 152 | let params = GadgetParameters::init_default(n, &q); 153 | 154 | let tag = 17 * MatZq::identity(n, n, ¶ms.q); 155 | let a_bar = MatZq::sample_uniform(n, ¶ms.m_bar, ¶ms.q); 156 | 157 | let (a, r) = gen_trapdoor(¶ms, &a_bar, &tag).unwrap(); 158 | 159 | let short_basis = gen_short_basis_for_trapdoor(¶ms, &tag, &a, &r); 160 | 161 | let zero_vec = MatZq::new(a.get_num_rows(), 1, &q); 162 | for i in 0..short_basis.get_num_columns() { 163 | assert_eq!(zero_vec, &a * short_basis.get_column(i).unwrap()) 164 | } 165 | } 166 | } 167 | 168 | /// Ensures that the trapdoor generated is actually a base for `Λ^⟂(A)` 169 | /// included with an actual tag. 170 | #[test] 171 | fn is_basis_with_tag_arbitrarily() { 172 | for n in [2, 5, 10, 12] { 173 | let q = Modulus::from(124 + 2 * n); 174 | let params = GadgetParameters::init_default(n, &q); 175 | 176 | let tag = calculate_invertible_tag(n, &q); 177 | let a_bar = MatZq::sample_uniform(n, ¶ms.m_bar, ¶ms.q); 178 | 179 | let (a, r) = gen_trapdoor(¶ms, &a_bar, &tag).unwrap(); 180 | 181 | let short_basis = gen_short_basis_for_trapdoor(¶ms, &tag, &a, &r); 182 | 183 | let zero_vec = MatZq::new(a.get_num_rows(), 1, &q); 184 | for i in 0..short_basis.get_num_columns() { 185 | assert_eq!(zero_vec, &a * short_basis.get_column(i).unwrap()) 186 | } 187 | } 188 | } 189 | 190 | /// Ensure that the orthogonalized short base length is upper bounded by 191 | /// `(s_1(R)+1)*||\tilde S'||`. 192 | #[test] 193 | fn ensure_orthogonalized_length_perfect_power() { 194 | for n in [1, 5, 7] { 195 | let q = Modulus::from(128); 196 | let params = GadgetParameters::init_default(n, &q); 197 | let tag = calculate_invertible_tag(n, &q); 198 | let a_bar = MatZq::sample_uniform(n, ¶ms.m_bar, ¶ms.q); 199 | 200 | let (a, r) = gen_trapdoor(¶ms, &a_bar, &tag).unwrap(); 201 | 202 | let short_basis = gen_short_basis_for_trapdoor(¶ms, &tag, &a, &r); 203 | 204 | let orthogonalized_short_basis = MatQ::from(&short_basis).gso(); 205 | 206 | let s1_r = params.m_bar.sqrt(); 207 | let orth_s_length = 2; 208 | let upper_bound: Q = (s1_r + 1) * orth_s_length; 209 | for i in 0..orthogonalized_short_basis.get_num_columns() { 210 | let b_tilde_i = orthogonalized_short_basis.get_column(i).unwrap(); 211 | 212 | assert!(b_tilde_i.norm_eucl_sqrd().unwrap() <= upper_bound.pow(2).unwrap()) 213 | } 214 | } 215 | } 216 | 217 | /// Ensure that the orthogonalized short base length is upper bounded by 218 | /// `(s_1(R)+1)*||\tilde S'||`. 219 | #[test] 220 | fn ensure_orthogonalized_length_not_perfect_power() { 221 | for n in [1, 5, 7] { 222 | let q = Modulus::from(127); 223 | let params = GadgetParameters::init_default(n, &q); 224 | let tag = calculate_invertible_tag(n, &q); 225 | let a_bar = MatZq::sample_uniform(n, ¶ms.m_bar, ¶ms.q); 226 | 227 | let (a, r) = gen_trapdoor(¶ms, &a_bar, &tag).unwrap(); 228 | 229 | let short_basis = gen_short_basis_for_trapdoor(¶ms, &tag, &a, &r); 230 | 231 | let orthogonalized_short_basis = MatQ::from(&short_basis).gso(); 232 | 233 | let s1_r = params.m_bar.sqrt(); 234 | let orth_s_length = Q::from(5).sqrt(); 235 | let upper_bound: Q = (s1_r + 1) * orth_s_length; 236 | for i in 0..orthogonalized_short_basis.get_num_columns() { 237 | let b_tilde_i = orthogonalized_short_basis.get_column(i).unwrap(); 238 | 239 | assert!(b_tilde_i.norm_eucl_sqrd().unwrap() <= upper_bound.pow(2).unwrap()) 240 | } 241 | } 242 | } 243 | 244 | /// Generates an invertible tag matrix (generates a diagonal matrix) and sets entries 245 | /// above the diagonal uniformly at random. 246 | fn calculate_invertible_tag(size: i64, q: &Modulus) -> MatZq { 247 | let max_value = Z::from(q); 248 | let mut out = MatZq::identity(size, size, q); 249 | // create a diagonal matrix with random values (because it is a diagonal matrix 250 | // with `1` on the diagonal, it is always invertible) 251 | for row in 0..size { 252 | for column in 0..size { 253 | if row < column { 254 | out.set_entry(row, column, Z::sample_uniform(0, &max_value).unwrap()) 255 | .unwrap(); 256 | } 257 | } 258 | } 259 | out 260 | } 261 | } 262 | 263 | #[cfg(test)] 264 | mod test_gen_sa { 265 | use super::gen_sa_l; 266 | use crate::sample::g_trapdoor::{ 267 | gadget_parameters::GadgetParameters, short_basis_classical::gen_sa_r, 268 | }; 269 | use qfall_math::{integer::MatZ, integer_mod_q::MatZq}; 270 | use std::str::FromStr; 271 | 272 | /// Returns a fixed trapdoor and a matrix a for a fixed parameter set 273 | fn get_fixed_trapdoor_for_tag_identity() -> (GadgetParameters, MatZq, MatZ) { 274 | let params = GadgetParameters::init_default(2, 8); 275 | 276 | let a = MatZq::from_str( 277 | "[\ 278 | [2, 6, 2, 5, 3, 0, 1, 1, 1, 6, 5, 0, 6],\ 279 | [6, 0, 3, 1, 5, 6, 2, 7, 0, 3, 7, 7, 0]] mod 8", 280 | ) 281 | .unwrap(); 282 | 283 | let r = MatZ::from_str( 284 | "[[0, 1, 0, 1, 1, 0],\ 285 | [-1, 1, 0, 0, 0, -1],\ 286 | [-1, 0, -1, -1, -1, 0],\ 287 | [-1, 1, 0, 0, 0, 1],\ 288 | [-1, -1, 0, 1, 0, 1],\ 289 | [-1, 0, 0, -1, 0, 1],\ 290 | [0, -1, 0, 0, 0, 0]]", 291 | ) 292 | .unwrap(); 293 | 294 | (params, a, r) 295 | } 296 | 297 | /// Ensure that the left part of the multiplication to get sa is correctly computed. 298 | #[test] 299 | fn working_sa_l() { 300 | let (_, _, r) = get_fixed_trapdoor_for_tag_identity(); 301 | let sa_1 = gen_sa_l(&r); 302 | 303 | let sa_1_cmp = MatZ::from_str( 304 | "[\ 305 | [1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0],\ 306 | [0, 1, 0, 0, 0, 0, 0, -1, 1, 0, 0, 0, -1],\ 307 | [0, 0, 1, 0, 0, 0, 0, -1, 0, -1, -1, -1, 0],\ 308 | [0, 0, 0, 1, 0, 0, 0, -1, 1, 0, 0, 0, 1],\ 309 | [0, 0, 0, 0, 1, 0, 0, -1, -1, 0, 1, 0, 1],\ 310 | [0, 0, 0, 0, 0, 1, 0, -1, 0, 0, -1, 0, 1],\ 311 | [0, 0, 0, 0, 0, 0, 1, 0, -1, 0, 0, 0, 0],\ 312 | [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],\ 313 | [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],\ 314 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],\ 315 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],\ 316 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],\ 317 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]]", 318 | ) 319 | .unwrap(); 320 | assert_eq!(sa_1_cmp, sa_1); 321 | } 322 | 323 | /// Ensure that the right part of the multiplication to get sa is correctly computed 324 | /// with tag as identity. 325 | #[test] 326 | fn working_sa_r_identity() { 327 | let (params, a, _) = get_fixed_trapdoor_for_tag_identity(); 328 | let tag = MatZq::identity(¶ms.n, ¶ms.n, ¶ms.q); 329 | let sa_r = gen_sa_r(¶ms, &tag, &a); 330 | 331 | let sa_r_cmp = MatZ::from_str( 332 | "[\ 333 | [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0],\ 334 | [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],\ 335 | [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],\ 336 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],\ 337 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],\ 338 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],\ 339 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],\ 340 | [0, 0, 0, 0, 0, 2, 0, 0, 0, 1, 1, 0, 1],\ 341 | [0, 0, 0, 0, 2, -1, 1, 1, 1, 1, 0, 0, 1],\ 342 | [0, 0, 0, 2, -1, 0, 1, 0, 1, 0, 1, 0, 1],\ 343 | [0, 0, 2, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0],\ 344 | [0, 2, -1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1],\ 345 | [2, -1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1]]", 346 | ) 347 | .unwrap(); 348 | assert_eq!(sa_r_cmp, sa_r); 349 | } 350 | } 351 | 352 | #[cfg(test)] 353 | mod test_compute_w { 354 | use super::compute_w; 355 | use crate::sample::g_trapdoor::{ 356 | gadget_classical::gen_gadget_mat, gadget_parameters::GadgetParameters, 357 | }; 358 | use qfall_math::{ 359 | integer::{MatZ, Z}, 360 | integer_mod_q::MatZq, 361 | traits::MatrixDimensions, 362 | }; 363 | use std::str::FromStr; 364 | 365 | /// Ensure that `GW = A[I|0] mod q`. 366 | #[test] 367 | fn working_example_tag_identity() { 368 | let params = GadgetParameters::init_default(2, 8); 369 | let tag = MatZq::identity(2, 2, ¶ms.q); 370 | 371 | let a = MatZq::from_str( 372 | "[\ 373 | [2, 6, 2, 5, 3, 0, 1, 1, 1, 6, 5, 0, 6],\ 374 | [6, 0, 3, 1, 5, 6, 2, 7, 0, 3, 7, 7, 0]] mod 8", 375 | ) 376 | .unwrap(); 377 | 378 | let w = compute_w(¶ms, &tag, &a); 379 | let g = gen_gadget_mat((¶ms.n).try_into().unwrap(), ¶ms.k, ¶ms.base); 380 | 381 | let gw = MatZq::from((&(g * w), ¶ms.q)); 382 | let rhs = &a * MatZ::identity(a.get_num_columns(), ¶ms.m_bar); 383 | 384 | assert_eq!(gw, Z::MINUS_ONE * rhs) 385 | } 386 | } 387 | -------------------------------------------------------------------------------- /src/compression/lossy_compression_fips203.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2025 Niklas Siemer 2 | // 3 | // This file is part of qFALL-tools. 4 | // 5 | // qFALL-tools 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 lossy (de-)compression as specified in FIPS 203, i.e. ML-KEM / Kyber. 10 | 11 | use flint_sys::fmpz_poly::fmpz_poly_set_coeff_fmpz; 12 | use qfall_math::{ 13 | integer::{MatPolyOverZ, PolyOverZ, Z}, 14 | integer_mod_q::{MatPolynomialRingZq, ModulusPolynomialRingZq, PolynomialRingZq}, 15 | traits::{GetCoefficient, MatrixDimensions, MatrixGetEntry, MatrixSetEntry, Pow}, 16 | }; 17 | 18 | /// This trait is implemented by data-structures, which may use lossy compression by dropping lower order bits 19 | /// as specified in [\[1\]](). 20 | pub trait LossyCompressionFIPS203 { 21 | /// Defines the datatype that the compressed value will have. 22 | type CompressedType; 23 | /// Defines the type of the modulus object. 24 | type ModulusType; 25 | 26 | /// Compresses by keeping only `d` higher-order bits. 27 | /// This function modifies the value of `self` directly. 28 | /// 29 | /// The function is specified by `Compress_d(x) := ⌈(2^d / q) * x⌋ mod 2^d`. 30 | /// 31 | /// Parameters: 32 | /// - `d`: specifies the number of bits that is kept to represent values 33 | /// 34 | /// Returns a new instance of type [`Self::CompressedType`] containing the compressed coefficients with a loss-factor 35 | /// defined by `q` and `d`. 36 | /// 37 | /// # Panics ... 38 | /// - if `d` is smaller than `1`. 39 | fn lossy_compress(&self, d: impl Into) -> Self::CompressedType; 40 | 41 | /// Decompresses a previously compressed value by mapping it to the closest recoverable value over the ring `Z_q`. 42 | /// This function modifies the value of `self` directly. 43 | /// 44 | /// The function is specified by `Decompress_d(y) := ⌈(q / 2^d) * y⌋`. 45 | /// 46 | /// Parameters: 47 | /// - `d`: specifies the number of bits that was kept during compression 48 | /// 49 | /// Returns a new instance of type [`Self`] with decompressed values according to the loss-factor 50 | /// defined by `q` and `d`. 51 | /// 52 | /// # Panics ... 53 | /// - if `d` is smaller than `1`. 54 | fn lossy_decompress( 55 | compressed: &Self::CompressedType, 56 | d: impl Into, 57 | modulus: &Self::ModulusType, 58 | ) -> Self; 59 | } 60 | 61 | impl LossyCompressionFIPS203 for PolynomialRingZq { 62 | type CompressedType = PolyOverZ; 63 | type ModulusType = ModulusPolynomialRingZq; 64 | 65 | /// Compresses by keeping only `d` higher-order bits. 66 | /// This function modifies the value of `self` directly. 67 | /// 68 | /// The function is specified by `Compress_d(x) := ⌈(2^d / q) * x⌋ mod 2^d`. 69 | /// 70 | /// Parameters: 71 | /// - `d`: specifies the number of bits that is kept to represent each value 72 | /// 73 | /// Returns a [`PolyOverZ`] containing the compressed coefficients with a loss-factor 74 | /// defined by `q` and `d`. 75 | /// 76 | /// # Examples 77 | /// ``` 78 | /// use qfall_math::integer_mod_q::PolynomialRingZq; 79 | /// use qfall_tools::{utils::common_moduli::new_anticyclic, compression::LossyCompressionFIPS203}; 80 | /// 81 | /// let modulus = new_anticyclic(16, 257).unwrap(); 82 | /// let mut poly = PolynomialRingZq::sample_uniform(&modulus); 83 | /// 84 | /// let compressed = poly.lossy_compress(4); 85 | /// ``` 86 | /// 87 | /// # Panics ... 88 | /// - if `d` is smaller than `1`. 89 | fn lossy_compress(&self, d: impl Into) -> Self::CompressedType { 90 | let d = d.into(); 91 | assert!( 92 | d >= Z::ONE, 93 | "Performing this function with d < 1 implies reducing mod 1, leaving no information to recover. Choose a larger parameter d." 94 | ); 95 | let two_pow_d = Z::from(2).pow(d).unwrap(); 96 | let q = self.get_mod().get_q(); 97 | let q_div_2 = q.div_floor(2); 98 | 99 | let mut out = PolyOverZ::default(); 100 | 101 | for coeff_i in 0..=self.get_degree() { 102 | let mut coeff: Z = unsafe { self.get_coeff_unchecked(coeff_i) }; 103 | 104 | coeff *= &two_pow_d; 105 | coeff += &q_div_2; 106 | let mut res = coeff.div_floor(&q) % &two_pow_d; 107 | 108 | unsafe { 109 | fmpz_poly_set_coeff_fmpz(out.get_fmpz_poly_struct(), coeff_i, res.get_fmpz()); 110 | }; 111 | } 112 | 113 | out 114 | } 115 | 116 | /// Decompresses a previously compressed value by mapping it to the closest recoverable value over the ring `Z_q`. 117 | /// This function modifies the value of `self` directly. 118 | /// 119 | /// The function is specified by `Decompress_d(y) := ⌈(q / 2^d) * y⌋`. 120 | /// 121 | /// Parameters: 122 | /// - `compressed`: specifies the compressed value 123 | /// - `d`: specifies the number of bits that was kept during compression 124 | /// - `modulus`: specifies the modulus of the returned value 125 | /// 126 | /// Returns a [`PolynomialRingZq`] with decompressed values according to the loss-factor 127 | /// defined by `q` and `d`. 128 | /// 129 | /// # Examples 130 | /// ``` 131 | /// use qfall_math::integer_mod_q::PolynomialRingZq; 132 | /// use qfall_tools::{utils::common_moduli::new_anticyclic, compression::LossyCompressionFIPS203}; 133 | /// 134 | /// let modulus = new_anticyclic(16, 257).unwrap(); 135 | /// let mut poly = PolynomialRingZq::sample_uniform(&modulus); 136 | /// 137 | /// let compressed = poly.lossy_compress(4); 138 | /// let decompressed = PolynomialRingZq::lossy_decompress(&compressed, 4, &modulus); 139 | /// ``` 140 | /// 141 | /// # Panics ... 142 | /// - if `d` is smaller than `1`. 143 | fn lossy_decompress( 144 | compressed: &Self::CompressedType, 145 | d: impl Into, 146 | modulus: &Self::ModulusType, 147 | ) -> Self { 148 | let d = d.into(); 149 | assert!( 150 | d >= Z::ONE, 151 | "Performing this function with d < 1 implies reducing mod 1, leaving no information to recover. Choose a larger parameter d." 152 | ); 153 | let two_pow_d_minus_1 = Z::from(2).pow(d - 1).unwrap(); 154 | let two_pow_d = &two_pow_d_minus_1 * 2; 155 | let q = modulus.get_q(); 156 | 157 | let mut out = Self::from(modulus); 158 | 159 | for coeff_i in 0..=compressed.get_degree() { 160 | let mut coeff: Z = unsafe { compressed.get_coeff_unchecked(coeff_i) }; 161 | 162 | coeff *= &q; 163 | coeff += &two_pow_d_minus_1; 164 | let mut res = coeff.div_floor(&two_pow_d); 165 | 166 | unsafe { 167 | fmpz_poly_set_coeff_fmpz(out.get_fmpz_poly_struct(), coeff_i, res.get_fmpz()); 168 | }; 169 | } 170 | 171 | out 172 | } 173 | } 174 | 175 | impl LossyCompressionFIPS203 for MatPolynomialRingZq { 176 | type CompressedType = MatPolyOverZ; 177 | type ModulusType = ModulusPolynomialRingZq; 178 | 179 | /// Compresses by keeping only `d` higher-order bits. 180 | /// This function modifies the value of `self` directly. 181 | /// 182 | /// The function is specified by `Compress_d(x) := ⌈(2^d / q) * x⌋ mod 2^d`. 183 | /// 184 | /// Parameters: 185 | /// - `d`: specifies the number of bits that is kept to represent each value 186 | /// 187 | /// Returns a [`MatPolyOverZ`] containing the compressed coefficients with a loss-factor 188 | /// defined by `q` and `d`. 189 | /// 190 | /// # Examples 191 | /// ``` 192 | /// use qfall_math::integer_mod_q::MatPolynomialRingZq; 193 | /// use qfall_tools::{utils::common_moduli::new_anticyclic, compression::LossyCompressionFIPS203}; 194 | /// 195 | /// let modulus = new_anticyclic(16, 257).unwrap(); 196 | /// let mut poly = MatPolynomialRingZq::sample_uniform(2, 3, &modulus); 197 | /// 198 | /// let compressed = poly.lossy_compress(4); 199 | /// ``` 200 | /// 201 | /// # Panics ... 202 | /// - if `d` is smaller than `1`. 203 | fn lossy_compress(&self, d: impl Into) -> Self::CompressedType { 204 | let d = d.into(); 205 | 206 | let mut out = MatPolyOverZ::new(self.get_num_rows(), self.get_num_columns()); 207 | 208 | for row in 0..self.get_num_rows() { 209 | for col in 0..self.get_num_columns() { 210 | let entry: PolynomialRingZq = unsafe { self.get_entry_unchecked(row, col) }; 211 | let res = entry.lossy_compress(&d); 212 | unsafe { out.set_entry_unchecked(row, col, res) }; 213 | } 214 | } 215 | 216 | out 217 | } 218 | 219 | /// Decompresses a previously compressed value by mapping it to the closest recoverable value over the ring `Z_q`. 220 | /// This function modifies the value of `self` directly. 221 | /// 222 | /// The function is specified by `Decompress_d(y) := ⌈(q / 2^d) * y⌋`. 223 | /// 224 | /// Parameters: 225 | /// - `compressed`: specifies the compressed matrix 226 | /// - `d`: specifies the number of bits that was kept during compression 227 | /// - `modulus`: specifies the modulus of the returned value 228 | /// 229 | /// Returns a [`MatPolynomialRingZq`] with decompressed values according to the loss-factor 230 | /// defined by `q` and `d`. 231 | /// 232 | /// # Examples 233 | /// ``` 234 | /// use qfall_math::integer_mod_q::MatPolynomialRingZq; 235 | /// use qfall_tools::{utils::common_moduli::new_anticyclic, compression::LossyCompressionFIPS203}; 236 | /// 237 | /// let modulus = new_anticyclic(16, 257).unwrap(); 238 | /// let mut poly = MatPolynomialRingZq::sample_uniform(2, 3, &modulus); 239 | /// 240 | /// let compressed = poly.lossy_compress(4); 241 | /// let decompressed = MatPolynomialRingZq::lossy_decompress(&compressed, 4, &modulus); 242 | /// ``` 243 | /// 244 | /// # Panics ... 245 | /// - if `d` is smaller than `1`. 246 | fn lossy_decompress( 247 | compressed: &Self::CompressedType, 248 | d: impl Into, 249 | modulus: &Self::ModulusType, 250 | ) -> Self { 251 | let d = d.into(); 252 | 253 | let mut out = MatPolynomialRingZq::new( 254 | compressed.get_num_rows(), 255 | compressed.get_num_columns(), 256 | modulus, 257 | ); 258 | 259 | for row in 0..compressed.get_num_rows() { 260 | for col in 0..compressed.get_num_columns() { 261 | let entry: PolyOverZ = unsafe { compressed.get_entry_unchecked(row, col) }; 262 | let res = PolynomialRingZq::lossy_decompress(&entry, &d, modulus); 263 | unsafe { out.set_entry_unchecked(row, col, res) }; 264 | } 265 | } 266 | 267 | out 268 | } 269 | } 270 | 271 | #[cfg(test)] 272 | mod test_compression_poly { 273 | use crate::{compression::LossyCompressionFIPS203, utils::common_moduli::new_anticyclic}; 274 | use qfall_math::{ 275 | integer::Z, 276 | integer_mod_q::PolynomialRingZq, 277 | traits::{Distance, GetCoefficient, Pow}, 278 | }; 279 | 280 | /// Ensures that decompressing compressed values results in values close to the original for small d. 281 | #[test] 282 | fn round_trip_small_d() { 283 | let d = 4; 284 | let q = Z::from(257); 285 | let modulus = new_anticyclic(16, &q).unwrap(); 286 | let poly = PolynomialRingZq::sample_uniform(&modulus); 287 | 288 | let compressed = poly.lossy_compress(d); 289 | let decompressed = PolynomialRingZq::lossy_decompress(&compressed, d, &modulus); 290 | 291 | for i in 0..modulus.get_degree() { 292 | let orig_coeff: Z = poly.get_coeff(i).unwrap(); 293 | let coeff: Z = decompressed.get_coeff(i).unwrap(); 294 | 295 | let mut distance = orig_coeff.distance(coeff); 296 | if distance > &q / 2 { 297 | distance = &q - distance; 298 | } 299 | 300 | assert!(distance <= Z::from(2).pow(q.log_ceil(2).unwrap() - d - 1).unwrap()); 301 | } 302 | } 303 | 304 | /// Ensures that decompressing compressed values results in values close to the original. 305 | #[test] 306 | fn round_trip() { 307 | let d = 11; 308 | let q = Z::from(3329); 309 | let modulus = new_anticyclic(16, &q).unwrap(); 310 | let poly = PolynomialRingZq::sample_uniform(&modulus); 311 | 312 | let compressed = poly.lossy_compress(d); 313 | let decompressed = PolynomialRingZq::lossy_decompress(&compressed, d, &modulus); 314 | 315 | for i in 0..modulus.get_degree() { 316 | let orig_coeff: Z = poly.get_coeff(i).unwrap(); 317 | let coeff: Z = decompressed.get_coeff(i).unwrap(); 318 | 319 | let mut distance = orig_coeff.distance(coeff); 320 | if distance > &q / 2 { 321 | distance = &q - distance; 322 | } 323 | 324 | assert!(distance <= Z::from(2).pow(q.log_ceil(2).unwrap() - d - 1).unwrap()); 325 | } 326 | } 327 | 328 | /// Ensures that the function panics if `d = 0` or smaller. 329 | #[test] 330 | #[should_panic] 331 | fn too_small_d() { 332 | let d = 0; 333 | let q = Z::from(3329); 334 | let modulus = new_anticyclic(16, &q).unwrap(); 335 | let poly = PolynomialRingZq::sample_uniform(&modulus); 336 | 337 | poly.lossy_compress(d); 338 | } 339 | } 340 | 341 | #[cfg(test)] 342 | mod test_compression_matrix { 343 | use crate::{compression::LossyCompressionFIPS203, utils::common_moduli::new_anticyclic}; 344 | use qfall_math::{ 345 | integer::Z, 346 | integer_mod_q::{MatPolynomialRingZq, PolynomialRingZq}, 347 | traits::{Distance, GetCoefficient, MatrixDimensions, MatrixGetEntry, Pow}, 348 | }; 349 | 350 | /// Ensures that decompressing compressed values results in values close to the original. 351 | #[test] 352 | fn round_trip() { 353 | let d = 11; 354 | let q = Z::from(3329); 355 | let modulus = new_anticyclic(16, &q).unwrap(); 356 | let matrix = MatPolynomialRingZq::sample_uniform(2, 2, &modulus); 357 | 358 | let compressed = matrix.lossy_compress(d); 359 | let decompressed = MatPolynomialRingZq::lossy_decompress(&compressed, d, &modulus); 360 | 361 | for row in 0..matrix.get_num_rows() { 362 | for col in 0..matrix.get_num_columns() { 363 | let orig_entry: PolynomialRingZq = matrix.get_entry(row, col).unwrap(); 364 | let entry: PolynomialRingZq = decompressed.get_entry(row, col).unwrap(); 365 | 366 | for i in 0..modulus.get_degree() { 367 | let orig_coeff: Z = orig_entry.get_coeff(i).unwrap(); 368 | let coeff: Z = entry.get_coeff(i).unwrap(); 369 | 370 | let mut distance = orig_coeff.distance(coeff); 371 | if distance > &q / 2 { 372 | distance = &q - distance; 373 | } 374 | 375 | assert!(distance <= Z::from(2).pow(q.log_ceil(2).unwrap() - d - 1).unwrap()); 376 | } 377 | } 378 | } 379 | } 380 | 381 | /// Ensures that the function panics if `d = 0` or smaller. 382 | #[test] 383 | #[should_panic] 384 | fn too_small_d() { 385 | let d = 0; 386 | let q = Z::from(3329); 387 | let modulus = new_anticyclic(16, &q).unwrap(); 388 | let poly = MatPolynomialRingZq::sample_uniform(2, 3, &modulus); 389 | 390 | poly.lossy_compress(d); 391 | } 392 | } 393 | -------------------------------------------------------------------------------- /src/primitive/psf/gpv_ring.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Marvin Beckmann 2 | // 3 | // This file is part of qFALL-tools. 4 | // 5 | // qFALL-tools 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 trapdoors. 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_tools::primitive::psf::PSFGPVRing; 45 | /// use qfall_tools::sample::g_trapdoor::gadget_parameters::GadgetParametersRing; 46 | /// use qfall_math::rational::Q; 47 | /// use qfall_tools::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 for PSFGPVRing { 70 | type A = MatPolynomialRingZq; 71 | type Trapdoor = (MatPolyOverZ, MatPolyOverZ); 72 | type Domain = MatPolyOverZ; 73 | type Range = MatPolynomialRingZq; 74 | 75 | /// Computes a G-Trapdoor according to the [`GadgetParametersRing`]. 76 | /// 77 | /// # Examples 78 | /// ``` 79 | /// use qfall_tools::primitive::psf::PSFGPVRing; 80 | /// use qfall_tools::sample::g_trapdoor::gadget_parameters::GadgetParametersRing; 81 | /// use qfall_math::rational::Q; 82 | /// use qfall_tools::primitive::psf::PSF; 83 | /// 84 | /// let psf = PSFGPVRing { 85 | /// gp: GadgetParametersRing::init_default(8, 512), 86 | /// s: Q::from(100), 87 | /// s_td: Q::from(1.005_f64), 88 | /// }; 89 | /// let (a, (r, e)) = psf.trap_gen(); 90 | /// ``` 91 | fn trap_gen(&self) -> (MatPolynomialRingZq, (MatPolyOverZ, MatPolyOverZ)) { 92 | let a_bar = 93 | PolyOverZ::sample_uniform(self.gp.modulus.get_degree() - 1, 0, self.gp.modulus.get_q()) 94 | .unwrap(); 95 | let (a, r, e) = gen_trapdoor_ring_lwe(&self.gp, &a_bar, &self.s_td).unwrap(); 96 | 97 | (a, (r, e)) 98 | } 99 | 100 | /// Samples in the domain using SampleD with the standard basis and center `0`. 101 | /// 102 | /// # Examples 103 | /// ``` 104 | /// use qfall_tools::primitive::psf::PSFGPVRing; 105 | /// use qfall_tools::sample::g_trapdoor::gadget_parameters::GadgetParametersRing; 106 | /// use qfall_math::rational::Q; 107 | /// use qfall_tools::primitive::psf::PSF; 108 | /// 109 | /// let psf = PSFGPVRing { 110 | /// gp: GadgetParametersRing::init_default(8, 512), 111 | /// s: Q::from(100), 112 | /// s_td: Q::from(1.005_f64), 113 | /// }; 114 | /// let (a, (r, e)) = psf.trap_gen(); 115 | /// 116 | /// let domain_sample = psf.samp_d(); 117 | /// ``` 118 | fn samp_d(&self) -> MatPolyOverZ { 119 | let dimension = self.gp.modulus.get_degree() * (&self.gp.k + 2); 120 | let sample = MatZ::sample_discrete_gauss(dimension, 1, 0, &self.s).unwrap(); 121 | MatPolyOverZ::from_coefficient_embedding((&sample, self.gp.modulus.get_degree() - 1)) 122 | } 123 | 124 | /// Samples an `e` in the domain using SampleD with a short basis that is generated 125 | /// from the G-Trapdoor from the conditioned discrete Gaussian with 126 | /// `f_a(a,e) = u` for a provided syndrome `u`. 127 | /// 128 | /// *Note*: the provided parameters `a, r, e, u` must fit together, 129 | /// otherwise unexpected behavior such as panics may occur. 130 | /// 131 | /// Parameters: 132 | /// - `a`: The parity-check matrix 133 | /// - `r`: Together with `e` builds a G-Trapdoor for `a` 134 | /// - `e`: Together with `r` builds a G-Trapdoor for `a` 135 | /// - `u`: The syndrome from the range 136 | /// 137 | /// Returns a sample `e` from the domain on the conditioned discrete 138 | /// Gaussian distribution `f_a(a,e) = u`. 139 | /// 140 | /// # Examples 141 | /// ``` 142 | /// use qfall_tools::primitive::psf::PSFGPVRing; 143 | /// use qfall_tools::sample::g_trapdoor::gadget_parameters::GadgetParametersRing; 144 | /// use qfall_math::rational::Q; 145 | /// use qfall_tools::primitive::psf::PSF; 146 | /// 147 | /// let psf = PSFGPVRing { 148 | /// gp: GadgetParametersRing::init_default(8, 512), 149 | /// s: Q::from(100), 150 | /// s_td: Q::from(1.005_f64), 151 | /// }; 152 | /// let (a, (r, e)) = psf.trap_gen(); 153 | /// 154 | /// let domain_sample = psf.samp_d(); 155 | /// let range_fa = psf.f_a(&a, &domain_sample); 156 | /// 157 | /// let preimage = psf.samp_p(&a, &(r,e), &range_fa); 158 | /// assert_eq!(range_fa, psf.f_a(&a, &preimage)) 159 | /// ``` 160 | fn samp_p( 161 | &self, 162 | a: &MatPolynomialRingZq, 163 | (r, e): &(MatPolyOverZ, MatPolyOverZ), 164 | u: &MatPolynomialRingZq, 165 | ) -> MatPolyOverZ { 166 | // compute solution to `a*x = u` 167 | // the same as `Rot^-(ι(a)) ι(x) = ι(u)` 168 | 169 | let short_basis = gen_short_basis_for_trapdoor_ring(&self.gp, a, r, e); 170 | 171 | // solve `rot^-(ι(a)) ι(x) = ι(u)` to get solution 172 | let u_embedded = u 173 | .get_representative_least_nonnegative_residue() 174 | .into_coefficient_embedding(self.gp.modulus.get_degree()); 175 | let a_embedded = a 176 | .get_representative_least_nonnegative_residue() 177 | .into_coefficient_embedding(self.gp.modulus.get_degree()); 178 | let rot_a = rot_minus_matrix(&a_embedded); 179 | 180 | let u_embedded = MatZq::from((&u_embedded, &self.gp.modulus.get_q())); 181 | let rot_a = MatZq::from((&rot_a, &self.gp.modulus.get_q())); 182 | let sol: MatZ = rot_a 183 | .solve_gaussian_elimination(&u_embedded) 184 | .unwrap() 185 | .get_representative_least_nonnegative_residue(); 186 | 187 | // turn center into a vector of polynomials over Q with maximal degree as the 188 | // modulus 189 | let center = MatQ::from(&(-1 * &sol)); 190 | let mut center_embedded = Vec::new(); 191 | for block in 0..(center.get_num_rows() / (self.gp.modulus.get_degree())) { 192 | let sub_mat = center 193 | .get_submatrix( 194 | block * self.gp.modulus.get_degree(), 195 | (block + 1) * self.gp.modulus.get_degree() - 1, 196 | 0, 197 | 0, 198 | ) 199 | .unwrap(); 200 | let embedded_sub_mat = PolyOverQ::from_coefficient_embedding(&sub_mat); 201 | center_embedded.push(embedded_sub_mat); 202 | } 203 | 204 | MatPolyOverZ::from_coefficient_embedding((&sol, self.gp.modulus.get_degree() - 1)) 205 | + MatPolyOverZ::sample_d( 206 | &short_basis, 207 | self.gp.modulus.get_degree(), 208 | ¢er_embedded, 209 | &self.s, 210 | ) 211 | .unwrap() 212 | } 213 | 214 | /// Implements the efficiently computable function `f_a` which here corresponds to 215 | /// `a*sigma`. 216 | /// 217 | /// Parameters: 218 | /// - `a`: The parity-check matrix of dimensions `n x m` 219 | /// - `sigma`: A column vector of length `m` 220 | /// 221 | /// Returns `a*sigma` 222 | /// 223 | /// # Examples 224 | /// ``` 225 | /// use qfall_tools::primitive::psf::PSFGPVRing; 226 | /// use qfall_tools::sample::g_trapdoor::gadget_parameters::GadgetParametersRing; 227 | /// use qfall_math::rational::Q; 228 | /// use qfall_tools::primitive::psf::PSF; 229 | /// 230 | /// let psf = PSFGPVRing { 231 | /// gp: GadgetParametersRing::init_default(8, 512), 232 | /// s: Q::from(100), 233 | /// s_td: Q::from(1.005_f64), 234 | /// }; 235 | /// let (a, (r, e)) = psf.trap_gen(); 236 | /// 237 | /// let domain_sample = psf.samp_d(); 238 | /// let range_fa = psf.f_a(&a, &domain_sample); 239 | /// ``` 240 | /// 241 | /// # Panics ... 242 | /// - if `sigma` is not in the domain. 243 | fn f_a(&self, a: &MatPolynomialRingZq, sigma: &MatPolyOverZ) -> MatPolynomialRingZq { 244 | assert!(self.check_domain(sigma)); 245 | let sigma = MatPolynomialRingZq::from((sigma, &a.get_mod())); 246 | a * sigma 247 | } 248 | 249 | /// Checks whether a value `sigma` is in D_n = {e ∈ R^m | |ι(e)| <= s sqrt(m*n) }. 250 | /// 251 | /// Parameters: 252 | /// - `sigma`: The value for which is checked, if it is in the domain 253 | /// 254 | /// Returns true, if `sigma` is in D_n. 255 | /// 256 | /// # Examples 257 | /// ``` 258 | /// use qfall_tools::primitive::psf::PSFGPVRing; 259 | /// use qfall_tools::sample::g_trapdoor::gadget_parameters::GadgetParametersRing; 260 | /// use qfall_math::rational::Q; 261 | /// use qfall_tools::primitive::psf::PSF; 262 | /// 263 | /// let psf = PSFGPVRing { 264 | /// gp: GadgetParametersRing::init_default(8, 512), 265 | /// s: Q::from(100), 266 | /// s_td: Q::from(1.005_f64), 267 | /// }; 268 | /// let (a, (r, e)) = psf.trap_gen(); 269 | /// 270 | /// let vector = psf.samp_d(); 271 | /// 272 | /// assert!(psf.check_domain(&vector)); 273 | /// ``` 274 | fn check_domain(&self, sigma: &MatPolyOverZ) -> bool { 275 | let m = &self.gp.k + 2; 276 | let nr_coeffs = self.gp.modulus.get_degree(); 277 | let sigma_embedded = sigma.into_coefficient_embedding(nr_coeffs); 278 | 279 | sigma.is_column_vector() 280 | && m == sigma.get_num_rows() 281 | && sigma_embedded.norm_eucl_sqrd().unwrap() 282 | <= self.s.pow(2).unwrap() * sigma_embedded.get_num_rows() 283 | } 284 | } 285 | 286 | #[cfg(test)] 287 | mod test_gpv_psf { 288 | use super::super::gpv_ring::PSFGPVRing; 289 | use super::PSF; 290 | use crate::sample::g_trapdoor::gadget_parameters::GadgetParametersRing; 291 | use qfall_math::integer::{MatPolyOverZ, PolyOverZ}; 292 | use qfall_math::integer_mod_q::MatPolynomialRingZq; 293 | use qfall_math::rational::Q; 294 | use qfall_math::traits::*; 295 | 296 | fn compute_s(n: i64) -> Q { 297 | ((2 * 2 * Q::from(1.005_f64) * Q::from(n).sqrt() + 1) * 2) * 4 298 | } 299 | 300 | /// Ensures that `samp_d` actually computes values that are in D_n. 301 | #[test] 302 | fn samp_d_samples_from_dn() { 303 | let (n, q) = (5, 123456789); 304 | let psf = PSFGPVRing { 305 | gp: GadgetParametersRing::init_default(n, q), 306 | s: Q::from(1000), 307 | s_td: Q::from(1.005_f64), 308 | }; 309 | 310 | for _ in 0..5 { 311 | assert!(psf.check_domain(&psf.samp_d())); 312 | } 313 | } 314 | 315 | /// Ensures that `samp_p` actually computes preimages that are also in the correct 316 | /// domain. 317 | #[test] 318 | fn samp_p_preimage_and_domain() { 319 | for (n, q) in [(5, i32::MAX - 57), (6, i32::MAX)] { 320 | let psf = PSFGPVRing { 321 | gp: GadgetParametersRing::init_default(n, q), 322 | s: compute_s(n), 323 | s_td: Q::from(1.005_f64), 324 | }; 325 | let (a, r) = psf.trap_gen(); 326 | let domain_sample = psf.samp_d(); 327 | let range_fa = psf.f_a(&a, &domain_sample); 328 | 329 | let preimage = psf.samp_p(&a, &r, &range_fa); 330 | 331 | assert_eq!(range_fa, psf.f_a(&a, &preimage)); 332 | assert!(psf.check_domain(&preimage)); 333 | } 334 | } 335 | 336 | /// Ensures that `f_a` returns `a*sigma`. 337 | #[test] 338 | fn f_a_works_as_expected() { 339 | for (n, q) in [(5, 256), (6, 128)] { 340 | let psf = PSFGPVRing { 341 | gp: GadgetParametersRing::init_default(n, q), 342 | s: compute_s(n), 343 | s_td: Q::from(1.005_f64), 344 | }; 345 | let (a, _) = psf.trap_gen(); 346 | let domain_sample = psf.samp_d(); 347 | 348 | let domain_sample_2 = MatPolynomialRingZq::from((&domain_sample, &a.get_mod())); 349 | assert_eq!(&a * &domain_sample_2, psf.f_a(&a, &domain_sample)); 350 | } 351 | } 352 | 353 | /// Ensures that `f_a` panics if a value is provided, that is not within the domain. 354 | /// Sigma is not a vector. 355 | #[test] 356 | #[should_panic] 357 | fn f_a_sigma_not_in_domain_matrix() { 358 | let psf = PSFGPVRing { 359 | gp: GadgetParametersRing::init_default(8, 1024), 360 | s: compute_s(8), 361 | s_td: Q::from(1.005_f64), 362 | }; 363 | let (a, _) = psf.trap_gen(); 364 | let not_in_domain = MatPolyOverZ::new(a.get_num_columns(), 2); 365 | 366 | let _ = psf.f_a(&a, ¬_in_domain); 367 | } 368 | 369 | /// Ensures that `f_a` panics if a value is provided, that is not within the domain. 370 | /// Sigma is not of the correct length. 371 | #[test] 372 | #[should_panic] 373 | fn f_a_sigma_not_in_domain_incorrect_length() { 374 | let psf = PSFGPVRing { 375 | gp: GadgetParametersRing::init_default(8, 1024), 376 | s: compute_s(8), 377 | s_td: Q::from(1.005_f64), 378 | }; 379 | let (a, _) = psf.trap_gen(); 380 | let not_in_domain = MatPolyOverZ::new(a.get_num_columns() - 1, 1); 381 | 382 | let _ = psf.f_a(&a, ¬_in_domain); 383 | } 384 | 385 | /// Ensures that `f_a` panics if a value is provided, that is not within the domain. 386 | /// Sigma is too long. 387 | #[test] 388 | #[should_panic] 389 | fn f_a_sigma_not_in_domain_too_long() { 390 | let psf = PSFGPVRing { 391 | gp: GadgetParametersRing::init_default(8, 1024), 392 | s: compute_s(8), 393 | s_td: Q::from(1.005_f64), 394 | }; 395 | let (a, _) = psf.trap_gen(); 396 | let not_in_domain = psf.s.round() 397 | * a.get_num_columns() 398 | * 8 399 | * MatPolyOverZ::identity(a.get_num_columns(), 1); 400 | 401 | let _ = psf.f_a(&a, ¬_in_domain); 402 | } 403 | 404 | /// Ensures that `check_domain` works for vectors with the correct length. 405 | #[test] 406 | fn check_domain_as_expected() { 407 | let psf = PSFGPVRing { 408 | gp: GadgetParametersRing::init_default(9, 1024), 409 | s: compute_s(9), 410 | s_td: Q::from(1.005_f64), 411 | }; 412 | let (a, _) = psf.trap_gen(); 413 | let value = PolyOverZ::from(psf.s.round() * 3); 414 | let mut in_domain = MatPolyOverZ::new(a.get_num_columns(), 1); 415 | for i in 0..in_domain.get_num_rows() { 416 | in_domain.set_entry(i, 0, &value).unwrap(); 417 | } 418 | 419 | assert!(psf.check_domain(&MatPolyOverZ::new(a.get_num_columns(), 1))); 420 | assert!(psf.check_domain(&in_domain)); 421 | } 422 | 423 | /// Ensures that `check_domain` returns false for values that are not in the domain. 424 | #[test] 425 | fn check_domain_not_in_dn() { 426 | let psf = PSFGPVRing { 427 | gp: GadgetParametersRing::init_default(8, 1024), 428 | s: compute_s(8), 429 | s_td: Q::from(1.005_f64), 430 | }; 431 | let (a, _) = psf.trap_gen(); 432 | 433 | let matrix = MatPolyOverZ::new(a.get_num_columns(), 2); 434 | let too_short = MatPolyOverZ::new(a.get_num_columns() - 1, 1); 435 | let too_long = MatPolyOverZ::new(a.get_num_columns() + 1, 1); 436 | let entry_too_large = psf.s.round() 437 | * a.get_num_columns() 438 | * 8 439 | * MatPolyOverZ::identity(a.get_num_columns(), 1); 440 | 441 | assert!(!psf.check_domain(&matrix)); 442 | assert!(!psf.check_domain(&too_long)); 443 | assert!(!psf.check_domain(&too_short)); 444 | assert!(!psf.check_domain(&entry_too_large)); 445 | } 446 | } 447 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /src/sample/g_trapdoor/gadget_classical.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Marvin Beckmann 2 | // 3 | // This file is part of qFALL-tools. 4 | // 5 | // qFALL-tools 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_tools::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).try_into().unwrap(), ¶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_tools::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(n: i64, k: impl TryInto + Display, base: &Z) -> MatZ { 92 | let gadget_vec = gen_gadget_vec(k, base).transpose(); 93 | let mut out = MatZ::new(n, n * gadget_vec.get_num_columns()); 94 | for j in 0..out.get_num_rows() { 95 | out.set_submatrix( 96 | j, 97 | j * gadget_vec.get_num_columns(), 98 | &gadget_vec, 99 | 0, 100 | 0, 101 | -1, 102 | -1, 103 | ) 104 | .unwrap(); 105 | } 106 | out 107 | } 108 | 109 | /// Generates a gadget vector based on its definition in [\[1\]](<../index.html#:~:text=[1]>). 110 | /// This corresponds to a vector `(base ^0, base^1, ..., base^{k-1})` 111 | /// 112 | /// Parameters: 113 | /// - `k`: the size of the gadget vector 114 | /// - `base`: the base with which the entries in the gadget vector are defined 115 | /// 116 | /// Returns a gadget vector of length `k` with `base` as its base. 117 | /// 118 | /// # Examples 119 | /// ``` 120 | /// use qfall_tools::sample::g_trapdoor::gadget_classical::gen_gadget_vec; 121 | /// use qfall_math::integer::Z; 122 | /// 123 | /// let g = gen_gadget_vec(4, &Z::from(2)); 124 | /// ``` 125 | /// 126 | /// # Panics ... 127 | /// - if `k < 1` or it does not fit into an [`i64`]. 128 | pub fn gen_gadget_vec(k: impl TryInto + Display, base: &Z) -> MatZ { 129 | let mut entry = Z::ONE; 130 | let mut out = MatZ::new(k, 1); 131 | for i in 0..out.get_num_rows() { 132 | out.set_entry(i, 0, &entry).unwrap(); 133 | entry *= base 134 | } 135 | out 136 | } 137 | 138 | /// Computes an arbitrary solution for `g^t x = value mod q`. 139 | /// 140 | /// Parameters: 141 | /// - `value`: the matrix for which a solution has to be computed 142 | /// - `k`: the length of a gadget vector 143 | /// - `base`: the base with which the gadget vector is defined 144 | /// 145 | /// Returns an arbitrary solution for `g^tx = value mod q` 146 | /// 147 | /// # Examples 148 | /// ``` 149 | /// use qfall_math::{integer::{Z, MatZ}, integer_mod_q::Zq, traits::MatrixGetEntry}; 150 | /// use qfall_tools::sample::g_trapdoor::gadget_classical::{find_solution_gadget_vec, gen_gadget_vec}; 151 | /// use std::str::FromStr; 152 | /// 153 | /// let k = Z::from(5); 154 | /// let base = Z::from(3); 155 | /// let value = Zq::from((29,125)); 156 | /// 157 | /// let sol = find_solution_gadget_vec(&value, &k, &base); 158 | /// 159 | /// assert_eq!( 160 | /// value.get_representative_least_absolute_residue(), 161 | /// (gen_gadget_vec(&k, &base).transpose() * sol) 162 | /// .get_entry(0, 0) 163 | /// .unwrap() 164 | /// ) 165 | /// ``` 166 | /// 167 | /// # Panics ... 168 | /// - if the modulus of the value is greater than `base^k`. 169 | pub fn find_solution_gadget_vec(value: &Zq, k: &Z, base: &Z) -> MatZ { 170 | if base.pow(k).unwrap() < value.get_mod() { 171 | panic!("The modulus is too large, the value is potentially not representable."); 172 | } 173 | 174 | let mut value = value.get_representative_least_nonnegative_residue(); 175 | let mut out = MatZ::new(k, 1); 176 | for i in 0..out.get_num_rows() { 177 | let val_i = &value % base; 178 | out.set_entry(i, 0, &val_i).unwrap(); 179 | value = (value - val_i).div_exact(base).unwrap(); 180 | } 181 | out 182 | } 183 | 184 | /// Computes an arbitrary solution for `GX = value mod q`. 185 | /// 186 | /// Computes a entrywise solution using the structure of the gadget matrix to its 187 | /// advantage and utilizing `find_solution_gadget_vec`. 188 | /// 189 | /// Parameters: 190 | /// - `value`: the matrix for which a solution has to be computed 191 | /// - `k`: the length of a gadget vector 192 | /// - `base`: the base with which the gadget vector is defined 193 | /// 194 | /// Returns an arbitrary solution for `GX = value mod q`. 195 | /// 196 | /// # Examples 197 | /// ``` 198 | /// use qfall_math::integer::Z; 199 | /// use qfall_math::integer::MatZ; 200 | /// use qfall_math::integer_mod_q::MatZq; 201 | /// use qfall_tools::sample::g_trapdoor::gadget_classical::find_solution_gadget_mat; 202 | /// use qfall_tools::sample::g_trapdoor::gadget_classical::gen_gadget_mat; 203 | /// use std::str::FromStr; 204 | /// 205 | /// let k = Z::from(5); 206 | /// let base = Z::from(3); 207 | /// let value = MatZq::from_str("[[1, 42],[2, 30],[3, 12]] mod 125").unwrap(); 208 | /// 209 | /// let sol = find_solution_gadget_mat(&value, &k, &base); 210 | /// 211 | /// assert_eq!( 212 | /// value.get_representative_least_absolute_residue(), 213 | /// gen_gadget_mat(3, &k, &base) * sol 214 | /// ) 215 | /// ``` 216 | /// 217 | /// # Panics ... 218 | /// - if the modulus of the value is greater than `base^k`. 219 | pub fn find_solution_gadget_mat(value: &MatZq, k: &Z, base: &Z) -> MatZ { 220 | let mut out = MatZ::new(k * value.get_num_rows(), value.get_num_columns()); 221 | for i in 0..value.get_num_columns() as usize { 222 | for j in 0..value.get_num_rows() as usize { 223 | let sol_j = find_solution_gadget_vec(&value.get_entry(j, i).unwrap(), k, base); 224 | out.set_submatrix(k * j as u64, i, &sol_j, 0, 0, -1, 0) 225 | .unwrap(); 226 | } 227 | } 228 | out 229 | } 230 | 231 | /// Outputs the short basis `S` of any gadget matrix `G`. 232 | /// 233 | /// Parameters: 234 | /// - `params`: The [`GadgetParameters`] that define the gadget matrix `G` 235 | /// 236 | /// Returns a [`MatZ`] a short basis matrix `S` for `G`. 237 | /// 238 | /// # Examples 239 | /// ``` 240 | /// use qfall_tools::sample::g_trapdoor::{gadget_parameters::GadgetParameters, 241 | /// gadget_default::gen_trapdoor_default}; 242 | /// use qfall_tools::sample::g_trapdoor::gadget_classical::short_basis_gadget; 243 | /// 244 | /// let params = GadgetParameters::init_default(10, 127); 245 | /// 246 | /// let short_basis_gadget = short_basis_gadget(¶ms); 247 | /// ``` 248 | pub fn short_basis_gadget(params: &GadgetParameters) -> MatZ { 249 | let mut sk = MatZ::new(¶ms.k, ¶ms.k); 250 | let n: i64 = (¶ms.n).try_into().unwrap(); 251 | let k: i64 = (¶ms.k).try_into().unwrap(); 252 | for j in 0..k { 253 | sk.set_entry(j, j, ¶ms.base).unwrap(); 254 | } 255 | for i in 0..k - 1 { 256 | sk.set_entry(i + 1, i, Z::MINUS_ONE).unwrap(); 257 | } 258 | sk = if params.base.pow(k).unwrap() == params.q { 259 | // compute s in the special case where the modulus is a power of base 260 | // i.e. the last column can remain as it is 261 | sk 262 | } else { 263 | // compute s for any arbitrary modulus 264 | // represent modulus in `base` and set last row accordingly 265 | let mut q = Z::from(¶ms.q); 266 | for i in 0..k { 267 | let q_i = &q % ¶ms.base; 268 | sk.set_entry(i, k - 1, &q_i).unwrap(); 269 | q = (q - q_i).div_exact(¶ms.base).unwrap(); 270 | } 271 | sk 272 | }; 273 | let mut out = MatZ::new(n * k, n * k); 274 | for j in 0..n { 275 | out.set_submatrix( 276 | j * sk.get_num_rows(), 277 | j * sk.get_num_columns(), 278 | &sk, 279 | 0, 280 | 0, 281 | -1, 282 | -1, 283 | ) 284 | .unwrap(); 285 | } 286 | out 287 | } 288 | 289 | #[cfg(test)] 290 | mod test_gen_gadget_vec { 291 | use crate::sample::g_trapdoor::gadget_classical::gen_gadget_vec; 292 | use qfall_math::integer::{MatZ, Z}; 293 | use std::str::FromStr; 294 | 295 | /// Assure that the gadget vector with base `2` and length `5` works correctly. 296 | #[test] 297 | fn correctness_base_2() { 298 | let gadget_vec = gen_gadget_vec(5, &Z::from(2)); 299 | 300 | let vec = MatZ::from_str("[[1],[2],[4],[8],[16]]").unwrap(); 301 | assert_eq!(vec, gadget_vec); 302 | } 303 | 304 | /// Assure that the gadget vector with base `5` and length `4` works correctly. 305 | #[test] 306 | fn correctness_base_5() { 307 | let gadget_vec = gen_gadget_vec(4, &Z::from(5)); 308 | 309 | let vec = MatZ::from_str("[[1],[5],[25],[125]]").unwrap(); 310 | assert_eq!(vec, gadget_vec); 311 | } 312 | } 313 | 314 | #[cfg(test)] 315 | mod test_gen_gadget_mat { 316 | use super::gen_gadget_mat; 317 | use qfall_math::integer::{MatZ, Z}; 318 | use std::str::FromStr; 319 | 320 | /// Assure that the gadget matrix with gadget vector `[1, 2, 4]^t`(base 3) and 321 | /// `I_3` works correctly. 322 | #[test] 323 | fn correctness_base_2_3x3() { 324 | let gadget_mat = gen_gadget_mat(3, 3, &Z::from(2)); 325 | 326 | let mat_str = "[[1, 2, 4, 0, 0, 0, 0, 0, 0],\ 327 | [0, 0, 0, 1, 2, 4, 0, 0, 0],\ 328 | [0, 0, 0, 0, 0, 0, 1, 2, 4]]"; 329 | 330 | let mat = MatZ::from_str(mat_str).unwrap(); 331 | assert_eq!(mat, gadget_mat); 332 | } 333 | 334 | /// Assure that the gadget matrix with gadget vector `[1, 3, 9, 27, 81]^t`(base 3) and 335 | /// `I_2` works correctly. 336 | #[test] 337 | fn correctness_base_3_2x5() { 338 | let gadget_mat = gen_gadget_mat(2, 5, &Z::from(3)); 339 | 340 | let mat_str = "[[1, 3, 9, 27, 81, 0, 0, 0, 0, 0],\ 341 | [ 0, 0, 0, 0, 0, 1, 3, 9, 27, 81]]"; 342 | 343 | let mat = MatZ::from_str(mat_str).unwrap(); 344 | assert_eq!(mat, gadget_mat); 345 | } 346 | } 347 | 348 | #[cfg(test)] 349 | mod test_gen_trapdoor { 350 | use super::gen_trapdoor; 351 | use crate::sample::g_trapdoor::{ 352 | gadget_classical::gen_gadget_mat, gadget_parameters::GadgetParameters, 353 | }; 354 | use qfall_math::{ 355 | integer::{MatZ, Z}, 356 | integer_mod_q::{MatZq, Modulus}, 357 | traits::*, 358 | }; 359 | 360 | /// Assure that the trapdoor `r` returned from [`gen_trapdoor`] is actually a 361 | /// trapdoor for `a`. 362 | #[test] 363 | fn is_trapdoor_without_tag() { 364 | let params = GadgetParameters::init_default(42, 32); 365 | let a_bar = MatZq::sample_uniform(42, ¶ms.m_bar, ¶ms.q); 366 | let tag = MatZq::identity(42, 42, ¶ms.q); 367 | 368 | // call gen_trapdoor to get matrix a and its 'trapdoor' r 369 | let (a, r) = gen_trapdoor(¶ms, &a_bar, &tag).unwrap(); 370 | 371 | // generate the trapdoor for a from r as trapdoor = [[r],[I]] 372 | let trapdoor = r 373 | .concat_vertical(&MatZ::identity( 374 | a.get_num_columns() - r.get_num_rows(), 375 | r.get_num_columns(), 376 | )) 377 | .unwrap(); 378 | 379 | // ensure G = A*trapdoor (definition of a trapdoor) 380 | let gadget_mat = gen_gadget_mat(42, ¶ms.k, &Z::from(2)); 381 | assert_eq!( 382 | MatZq::from((&gadget_mat, ¶ms.q)), 383 | a * MatZq::from((&trapdoor, ¶ms.q)) 384 | ); 385 | } 386 | 387 | /// Assure that the trapdoor `r` returned from [`gen_trapdoor`] is actually a 388 | /// trapdoor for `a`. 389 | #[test] 390 | fn is_trapdoor_with_tag() { 391 | let modulus = Modulus::from(32); 392 | let params = GadgetParameters::init_default(42, &modulus); 393 | let a_bar = MatZq::sample_uniform(42, ¶ms.m_bar, ¶ms.q); 394 | // calculate an invertible tag in Z_q^{n × n} 395 | let tag = calculate_invertible_tag(42, &modulus); 396 | 397 | // call gen_trapdoor to get matrix a and its 'trapdoor' r 398 | let (a, r) = gen_trapdoor(¶ms, &a_bar, &tag).unwrap(); 399 | 400 | // generate the trapdoor for a from r as trapdoor = [[r],[I]] 401 | let trapdoor = r 402 | .concat_vertical(&MatZ::identity( 403 | a.get_num_columns() - r.get_num_rows(), 404 | r.get_num_columns(), 405 | )) 406 | .unwrap(); 407 | 408 | // ensure tag*G = A*trapdoor (definition of a trapdoor) 409 | let gadget_mat = gen_gadget_mat(42, ¶ms.k, &Z::from(2)); 410 | assert_eq!( 411 | tag * MatZq::from((&gadget_mat, &modulus)), 412 | a * MatZq::from((&trapdoor, &modulus)) 413 | ); 414 | } 415 | 416 | /// Generates an invertible tag matrix (generates a diagonal matrix) 417 | fn calculate_invertible_tag(size: i64, modulus: &Modulus) -> MatZq { 418 | let max_value = Z::from(modulus); 419 | let mut out = MatZq::identity(size, size, modulus); 420 | // create a diagonal matrix with random values (because it is a diagonal matrix 421 | // with `1` on the diagonal, it is always invertible) 422 | for row in 0..size { 423 | for column in 0..size { 424 | if row < column { 425 | out.set_entry(row, column, Z::sample_uniform(0, &max_value).unwrap()) 426 | .unwrap(); 427 | } 428 | } 429 | } 430 | out 431 | } 432 | } 433 | 434 | #[cfg(test)] 435 | mod test_find_solution_gadget { 436 | use super::find_solution_gadget_vec; 437 | use crate::sample::g_trapdoor::gadget_classical::{ 438 | find_solution_gadget_mat, gen_gadget_mat, gen_gadget_vec, 439 | }; 440 | use qfall_math::{ 441 | integer::Z, 442 | integer_mod_q::{MatZq, Zq}, 443 | traits::MatrixGetEntry, 444 | }; 445 | use std::str::FromStr; 446 | 447 | /// Ensure that the found solution is actually correct. 448 | #[test] 449 | fn returns_correct_solution_vec() { 450 | let k = Z::from(5); 451 | let base = Z::from(3); 452 | for i in 0..124 { 453 | let value = Zq::from((i, 125)); 454 | 455 | let sol = find_solution_gadget_vec(&value, &k, &base); 456 | 457 | assert_eq!( 458 | value.get_representative_least_nonnegative_residue(), 459 | (gen_gadget_vec(&k, &base).transpose() * sol) 460 | .get_entry(0, 0) 461 | .unwrap() 462 | ) 463 | } 464 | } 465 | 466 | /// Ensure that the found solution is actually correct. 467 | #[test] 468 | fn returns_correct_solution_mat() { 469 | let k = Z::from(5); 470 | let base = Z::from(3); 471 | let value = MatZq::from_str("[[1, 42],[2, 40],[3, 90]] mod 125").unwrap(); 472 | 473 | let sol = find_solution_gadget_mat(&value, &k, &base); 474 | 475 | assert_eq!( 476 | value.get_representative_least_nonnegative_residue(), 477 | gen_gadget_mat(3, &k, &base) * sol 478 | ) 479 | } 480 | } 481 | 482 | #[cfg(test)] 483 | mod test_short_basis_gadget { 484 | use crate::sample::g_trapdoor::{ 485 | gadget_classical::short_basis_gadget, gadget_parameters::GadgetParameters, 486 | }; 487 | use qfall_math::integer::{MatZ, Z}; 488 | use std::str::FromStr; 489 | 490 | /// Ensure that the matrix s is computed correctly for a power-of-two modulus. 491 | #[test] 492 | fn base_2_power_two() { 493 | let params = GadgetParameters::init_default(2, 16); 494 | 495 | let s = short_basis_gadget(¶ms); 496 | 497 | let s_cmp = MatZ::from_str( 498 | "[[2, 0, 0, 0, 0, 0, 0, 0],\ 499 | [-1, 2, 0, 0, 0, 0, 0, 0],\ 500 | [0, -1, 2, 0, 0, 0, 0, 0],\ 501 | [0, 0, -1, 2, 0, 0, 0, 0],\ 502 | [0, 0, 0, 0, 2, 0, 0, 0],\ 503 | [0, 0, 0, 0, -1, 2, 0, 0],\ 504 | [0, 0, 0, 0, 0, -1, 2, 0],\ 505 | [0, 0, 0, 0, 0, 0, -1, 2]]", 506 | ) 507 | .unwrap(); 508 | assert_eq!(s_cmp, s) 509 | } 510 | 511 | /// Ensure that the matrix s is computed correctly for a an arbitrary modulus. 512 | #[test] 513 | fn base_2_arbitrary() { 514 | let q = Z::from(0b1100110); 515 | let params = GadgetParameters::init_default(1, q); 516 | 517 | let s = short_basis_gadget(¶ms); 518 | 519 | let s_cmp = MatZ::from_str( 520 | "[[2, 0, 0, 0, 0, 0, 0],\ 521 | [-1, 2, 0, 0, 0, 0, 1],\ 522 | [0, -1, 2, 0, 0, 0, 1],\ 523 | [0, 0, -1, 2, 0, 0, 0],\ 524 | [0, 0, 0, -1, 2, 0, 0],\ 525 | [0, 0, 0, 0, -1, 2, 1],\ 526 | [0, 0, 0, 0, 0, -1, 1]]", 527 | ) 528 | .unwrap(); 529 | 530 | assert_eq!(s_cmp, s) 531 | } 532 | 533 | /// Ensure that the matrix s is computed correctly for a power-of-5 modulus. 534 | #[test] 535 | fn base_5_power_5() { 536 | let mut params = GadgetParameters::init_default(1, 625); 537 | params.k = Z::from(4); 538 | params.base = Z::from(5); 539 | 540 | let s = short_basis_gadget(¶ms); 541 | 542 | let s_cmp = MatZ::from_str( 543 | "[[5, 0, 0, 0],\ 544 | [-1, 5, 0, 0],\ 545 | [0, -1, 5, 0],\ 546 | [0, 0, -1, 5]]", 547 | ) 548 | .unwrap(); 549 | assert_eq!(s_cmp, s) 550 | } 551 | 552 | /// Ensure that the matrix s is computed correctly for an arbitrary modulus with 553 | /// base 5. 554 | #[test] 555 | fn base_5_arbitrary() { 556 | let q = Z::from_str_b("4123", 5).unwrap(); 557 | let mut params = GadgetParameters::init_default(1, q); 558 | params.k = Z::from(4); 559 | params.base = Z::from(5); 560 | 561 | let s = short_basis_gadget(¶ms); 562 | 563 | let s_cmp = MatZ::from_str( 564 | "[[5, 0, 0, 3],\ 565 | [-1, 5, 0, 2],\ 566 | [0, -1, 5, 1],\ 567 | [0, 0, -1, 4]]", 568 | ) 569 | .unwrap(); 570 | 571 | assert_eq!(s_cmp, s) 572 | } 573 | } 574 | -------------------------------------------------------------------------------- /src/primitive/psf/mp_perturbation.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2025 Niklas Siemer 2 | // 3 | // This file is part of qFALL-tools. 4 | // 5 | // qFALL-tools 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 [`PSF`] based on perturbation sampling desribed in [\[1\]](<../index.html#:~:text=[1]>) 10 | //! using G-trapdoors. 11 | 12 | use super::PSF; 13 | use crate::sample::g_trapdoor::{ 14 | gadget_classical::short_basis_gadget, 15 | gadget_classical::{find_solution_gadget_mat, gen_trapdoor}, 16 | gadget_parameters::GadgetParameters, 17 | }; 18 | use qfall_math::{ 19 | integer::{MatZ, Z}, 20 | integer_mod_q::MatZq, 21 | rational::{MatQ, Q}, 22 | traits::{Concatenate, MatrixDimensions, Pow}, 23 | }; 24 | use serde::{Deserialize, Serialize}; 25 | 26 | /// A lattice-based implementation of a [`PSF`] according to 27 | /// [\[1\]]() using 28 | /// G-Trapdoors where D_n = {e ∈ Z^m | |e| <= s sqrt(m)} 29 | /// and R_n = Z_q^n. 30 | /// 31 | /// Attributes 32 | /// - `gp`: Describes the gadget parameters with which the G-Trapdoor is generated 33 | /// - `r`: The rounding parameter 34 | /// - `s`: The Gaussian parameter with which is sampled 35 | /// 36 | /// # Examples 37 | /// ``` 38 | /// use qfall_tools::primitive::psf::PSFPerturbation; 39 | /// use qfall_tools::sample::g_trapdoor::gadget_parameters::GadgetParameters; 40 | /// use qfall_math::rational::Q; 41 | /// use qfall_tools::primitive::psf::PSF; 42 | /// 43 | /// let psf = PSFPerturbation { 44 | /// gp: GadgetParameters::init_default(8, 64), 45 | /// r: Q::from(3), 46 | /// s: Q::from(25), 47 | /// }; 48 | /// 49 | /// let (a, td) = psf.trap_gen(); 50 | /// let domain_sample = psf.samp_d(); 51 | /// let range_fa = psf.f_a(&a, &domain_sample); 52 | /// let preimage = psf.samp_p(&a, &td, &range_fa); 53 | /// 54 | /// assert!(psf.check_domain(&preimage)); 55 | /// assert_eq!(a * preimage, range_fa); 56 | /// ``` 57 | #[derive(Serialize, Deserialize)] 58 | pub struct PSFPerturbation { 59 | pub gp: GadgetParameters, 60 | pub r: Q, 61 | pub s: Q, 62 | } 63 | 64 | impl PSFPerturbation { 65 | /// Computes √Σ_2 = √(r^2 * (b^2 + 1) * [R^t | I]^t * [R^t | I] - r^2 * I) 66 | /// to perform non-spherical Gaussian sampling according to Algorithm 1 in 67 | /// [\[3\]](). 68 | /// This matrix is the second part of the secret key and needs to be precomputed 69 | /// to execute [PSFPerturbation::samp_p]. 70 | /// [PSFPerturbation::trap_gen] outputs this matrix for `s^2 * I`, i.e. for discrete 71 | /// Gaussian preimages centered around `0`. This function enables changing the 72 | /// covariance matrix to any covariance matrix s.t. Σ_2 is positive definite. 73 | /// 74 | /// Parameters: 75 | /// - `mat_r`: The trapdoor matrix `R` 76 | /// - `mat_sigma`: The covariance matrix `Σ` to sample [`Self::samp_p`] with 77 | /// 78 | /// Returns a [`MatQ`] containing √Σ_2 = √(r^2 * (b^2 + 1) * [R^t | I]^t * [R^t | I] - r^2 * I) 79 | /// if Σ_2 was positive definite. 80 | /// 81 | /// # Examples 82 | /// ``` 83 | /// use qfall_tools::primitive::psf::PSFPerturbation; 84 | /// use qfall_tools::sample::g_trapdoor::gadget_parameters::GadgetParameters; 85 | /// use qfall_math::rational::{Q, MatQ}; 86 | /// use qfall_tools::primitive::psf::PSF; 87 | /// use qfall_math::traits::*; 88 | /// 89 | /// let psf = PSFPerturbation { 90 | /// gp: GadgetParameters::init_default(8, 64), 91 | /// r: Q::from(3), 92 | /// s: Q::from(25), 93 | /// }; 94 | /// let different_s: f64 = 35.0; 95 | /// 96 | /// let (a, td) = psf.trap_gen(); 97 | /// 98 | /// let cov_mat = different_s.powi(2) * MatQ::identity(a.get_num_columns(), a.get_num_columns()); 99 | /// let mat_sqrt_sigma_2 = psf.compute_sqrt_sigma_2(&td.0, &cov_mat); 100 | /// let new_td = (td.0, mat_sqrt_sigma_2, td.2); 101 | /// 102 | /// let domain_sample = psf.samp_d(); 103 | /// let range_fa = psf.f_a(&a, &domain_sample); 104 | /// let preimage = psf.samp_p(&a, &new_td, &range_fa); 105 | /// 106 | /// assert!(psf.check_domain(&preimage)); 107 | /// ``` 108 | /// 109 | /// # Panics ... 110 | /// - if Σ_2 is not positive definite. 111 | pub fn compute_sqrt_sigma_2(&self, mat_r: &MatZ, mat_sigma: &MatQ) -> MatQ { 112 | // Normalization factor according to MP12, Section 2.3 113 | let normalization_factor = 1.0 / (2.0 * Q::PI); 114 | 115 | // full_td = [R^t | I]^t 116 | let full_td = mat_r 117 | .concat_vertical(&MatZ::identity( 118 | mat_sigma.get_num_rows() - mat_r.get_num_rows(), 119 | mat_r.get_num_columns(), 120 | )) 121 | .unwrap(); 122 | 123 | // Assemble Σ_p = Σ - [R^t|I]^t * Σ_G * [R^t|I] with √Σ_G ≥ η (Λ^⊥(G)) 124 | // and assumption Σ_G = (base^2 + 1) * I as ||S|| = √(b^2 + 1), Theorem 4.1, MP12 125 | let mat_sigma_p: MatQ = 126 | mat_sigma - (self.gp.base.pow(2).unwrap() + 1) * &full_td * full_td.transpose(); 127 | 128 | // r^2 * Σ_p <=> r * √Σ_p 129 | // Compute Σ_2 according to the requirements of Algorithm 1 in [3] 130 | // Assume Σ_1 = r^2 * B_1 * B_1^t with B_1 = I as basis for ZZ^n 131 | // Then, Σ_2 = Σ - Σ_1, where Σ = r^2 * Σ_p 132 | let sigma_2: MatQ = normalization_factor 133 | * self.r.pow(2).unwrap() 134 | * (&mat_sigma_p 135 | - MatQ::identity(mat_sigma_p.get_num_rows(), mat_sigma_p.get_num_columns())); 136 | 137 | // Compute √Σ_2 138 | sigma_2.cholesky_decomposition_flint() 139 | } 140 | } 141 | 142 | /// Samples a preimage with respect to the gadget matrix `G` of `vec_u`. 143 | /// 144 | /// Parameters: 145 | /// - `psf`: The [`PSFPerturbation`] that sets the parameters for this function 146 | /// - `vec_u`: The target vector 147 | /// - `short_basis_gadget`: The short basis of the corresponding gadget-matrix - just required to speed up the algorithm 148 | /// - `short_basis_gadget_gso`: The GSO of the short basis of the gadget-matrix - just required to speed up the algorithm 149 | /// 150 | /// Returns a discrete Gaussian sampled preimage of `u` under `G` with Gaussian parameter `r * √(b^2 + 1)`, 151 | /// where `b` is the base used for the G-trapdoor. 152 | /// 153 | /// # Examples 154 | /// ```compile_fail 155 | /// use qfall_tools::primitive::psf::PSFPerturbation; 156 | /// use qfall_tools::sample::g_trapdoor::gadget_parameters::GadgetParameters; 157 | /// use qfall_tools::sample::g_trapdoor::gadget_classical::short_basis_gadget; 158 | /// use qfall_math::rational::Q; 159 | /// use qfall_tools::primitive::psf::PSF; 160 | /// 161 | /// let psf = PSFPerturbation { 162 | /// gp: GadgetParameters::init_default(8, 64), 163 | /// r: Q::from(3), 164 | /// s: Q::from(25), 165 | /// }; 166 | /// 167 | /// let short_basis_g = short_basis_gadget(&psf.gp); 168 | /// let short_basis_g_gso = MatQ::from(&short_basis_g).gso(); 169 | /// 170 | /// let target = MatZq::sample_uniform(8, 1, 64); 171 | /// let preimage = randomized_nearest_plane_gadget(&osf, &target, &short_basis_g, &short_basis_g_gso); 172 | /// ``` 173 | pub(crate) fn randomized_nearest_plane_gadget( 174 | psf: &PSFPerturbation, 175 | vec_u: &MatZq, 176 | short_basis_gadget: &MatZ, 177 | short_basis_gadget_gso: &MatQ, 178 | ) -> MatZ { 179 | // s = r * √(b^2 + 1) according to Algorithm 3 in [1] 180 | let s = &psf.r * (psf.gp.base.pow(2).unwrap() + Z::ONE).sqrt(); 181 | 182 | // find solution s.t. G * long_solution = vec_u 183 | let long_solution = find_solution_gadget_mat(vec_u, &psf.gp.k, &psf.gp.base); 184 | 185 | let center = MatQ::from(&(-1 * &long_solution)); 186 | 187 | // just as PSFGPV::samp_p 188 | long_solution 189 | + MatZ::sample_d_precomputed_gso(short_basis_gadget, short_basis_gadget_gso, ¢er, &s) 190 | .unwrap() 191 | } 192 | 193 | impl PSF for PSFPerturbation { 194 | type A = MatZq; 195 | type Trapdoor = (MatZ, MatQ, (MatZ, MatQ)); 196 | type Domain = MatZ; 197 | type Range = MatZq; 198 | 199 | /// Computes a G-Trapdoor according to the [`GadgetParameters`] defined in the struct. 200 | /// It returns a matrix `A` together with the short trapdoor matrix `R` and a precomputed √Σ_2 201 | /// for covariance matrix `s^2 * I`, i.e. for preimage sampling centered around `0`. 202 | /// The last part of the trapdoor tuple contains a short basis for the gadget matrix `G` and its 203 | /// GSO, which removes the need to recompute the GSO for each iteration and speeds up preimage sampling 204 | /// drastically. 205 | /// 206 | /// # Examples 207 | /// ``` 208 | /// use qfall_tools::primitive::psf::PSFPerturbation; 209 | /// use qfall_tools::sample::g_trapdoor::gadget_parameters::GadgetParameters; 210 | /// use qfall_math::rational::Q; 211 | /// use qfall_tools::primitive::psf::PSF; 212 | /// 213 | /// let psf = PSFPerturbation { 214 | /// gp: GadgetParameters::init_default(8, 64), 215 | /// r: Q::from(3), 216 | /// s: Q::from(25), 217 | /// }; 218 | /// 219 | /// let (mat_a, (mat_r, mat_sqrt_sigma_2, (sh_b_g, sh_b_g_gso))) = psf.trap_gen(); 220 | /// ``` 221 | fn trap_gen(&self) -> (MatZq, (MatZ, MatQ, (MatZ, MatQ))) { 222 | let mat_a_bar = MatZq::sample_uniform(&self.gp.n, &self.gp.m_bar, &self.gp.q); 223 | let tag = MatZq::identity(&self.gp.n, &self.gp.n, &self.gp.q); 224 | 225 | let (mat_a, mat_r) = gen_trapdoor(&self.gp, &mat_a_bar, &tag).unwrap(); 226 | 227 | let mat_sqrt_sigma_2 = self.compute_sqrt_sigma_2( 228 | &mat_r, 229 | &(&self.s.pow(2).unwrap() 230 | * MatQ::identity(mat_a.get_num_columns(), mat_a.get_num_columns())), 231 | ); 232 | 233 | let short_basis_gadget = short_basis_gadget(&self.gp); 234 | let short_basis_gadget_gso = MatQ::from(&short_basis_gadget).gso(); 235 | 236 | ( 237 | mat_a, 238 | ( 239 | mat_r, 240 | mat_sqrt_sigma_2, 241 | (short_basis_gadget, short_basis_gadget_gso), 242 | ), 243 | ) 244 | } 245 | 246 | /// Samples in the domain using SampleD with the standard basis and center `0`. 247 | /// 248 | /// # Examples 249 | /// ``` 250 | /// use qfall_tools::primitive::psf::PSFPerturbation; 251 | /// use qfall_tools::sample::g_trapdoor::gadget_parameters::GadgetParameters; 252 | /// use qfall_math::rational::Q; 253 | /// use qfall_tools::primitive::psf::PSF; 254 | /// 255 | /// let psf = PSFPerturbation { 256 | /// gp: GadgetParameters::init_default(8, 64), 257 | /// r: Q::from(3), 258 | /// s: Q::from(25), 259 | /// }; 260 | /// let (mat_a, td) = psf.trap_gen(); 261 | /// 262 | /// let domain_sample = psf.samp_d(); 263 | /// ``` 264 | fn samp_d(&self) -> MatZ { 265 | let m = &self.gp.n * &self.gp.k + &self.gp.m_bar; 266 | MatZ::sample_discrete_gauss(m, 1, 0, &self.s * &self.r).unwrap() 267 | } 268 | 269 | /// Samples an `e` in the domain using SampleD that is generated 270 | /// from the G-Trapdoor from the conditioned 271 | /// discrete Gaussian with `f_a(a,e) = u` for a provided syndrome `u`. 272 | /// 273 | /// *Note*: the provided parameters `mat_a, mat_r, vec_u` must fit together, 274 | /// otherwise unexpected behavior such as panics may occur. 275 | /// 276 | /// Parameters: 277 | /// - `mat_a`: The parity-check matrix 278 | /// - `mat_r`: The short trapdoor matrix `R` 279 | /// - `mat_sqrt_sigma_2`: The precomputed √Σ_2 280 | /// - `vec_u`: The syndrome from the range 281 | /// 282 | /// Returns a sample `e` from the domain on the conditioned discrete 283 | /// Gaussian distribution `f_a(a,e) = u` with covariance matrix Σ depending on `mat_sqrt_sigma_2`. 284 | /// 285 | /// # Examples 286 | /// ``` 287 | /// use qfall_tools::primitive::psf::PSFPerturbation; 288 | /// use qfall_tools::sample::g_trapdoor::gadget_parameters::GadgetParameters; 289 | /// use qfall_math::rational::Q; 290 | /// use qfall_tools::primitive::psf::PSF; 291 | /// 292 | /// let psf = PSFPerturbation { 293 | /// gp: GadgetParameters::init_default(8, 64), 294 | /// r: Q::from(3), 295 | /// s: Q::from(25), 296 | /// }; 297 | /// let (mat_a, td) = psf.trap_gen(); 298 | /// let domain_sample = psf.samp_d(); 299 | /// let range_fa = psf.f_a(&mat_a, &domain_sample); 300 | /// 301 | /// let preimage = psf.samp_p(&mat_a, &td, &range_fa); 302 | /// assert_eq!(range_fa, psf.f_a(&mat_a, &preimage)) 303 | /// ``` 304 | fn samp_p( 305 | &self, 306 | mat_a: &MatZq, 307 | (mat_r, mat_sqrt_sigma_2, (short_basis_gadget, short_basis_gadget_gso)): &( 308 | MatZ, 309 | MatQ, 310 | (MatZ, MatQ), 311 | ), 312 | vec_u: &MatZq, 313 | ) -> MatZ { 314 | // Sample perturbation p <- D_{ZZ^m, r * √Σ_2} 315 | let vec_p = MatZ::sample_d_common_non_spherical(mat_sqrt_sigma_2, &self.r).unwrap(); 316 | 317 | // v = u - A * p 318 | let vec_v = vec_u - mat_a * &vec_p; 319 | 320 | // z <- D_{Λ_v^⊥(G), r * √Σ_G} 321 | let vec_z = randomized_nearest_plane_gadget( 322 | self, 323 | &vec_v, 324 | short_basis_gadget, 325 | short_basis_gadget_gso, 326 | ); 327 | 328 | let full_td = mat_r 329 | .concat_vertical(&MatZ::identity( 330 | mat_r.get_num_columns(), 331 | mat_r.get_num_columns(), 332 | )) 333 | .unwrap(); 334 | 335 | vec_p + full_td * vec_z 336 | } 337 | 338 | /// Implements the efficiently computable function `f_a` which here corresponds to 339 | /// `mat_a * sigma`. The sigma must be from the domain, i.e. D_n. 340 | /// 341 | /// Parameters: 342 | /// - `mat_a`: The parity-check matrix of dimensions `n x m` 343 | /// - `sigma`: A column vector of length `m` 344 | /// 345 | /// Returns `mat_a * sigma`. 346 | /// 347 | /// # Examples 348 | /// ``` 349 | /// use qfall_tools::primitive::psf::PSFPerturbation; 350 | /// use qfall_tools::sample::g_trapdoor::gadget_parameters::GadgetParameters; 351 | /// use qfall_math::rational::Q; 352 | /// use qfall_tools::primitive::psf::PSF; 353 | /// 354 | /// let psf = PSFPerturbation { 355 | /// gp: GadgetParameters::init_default(8, 64), 356 | /// r: Q::from(3), 357 | /// s: Q::from(25), 358 | /// }; 359 | /// let (mat_a, td) = psf.trap_gen(); 360 | /// let domain_sample = psf.samp_d(); 361 | /// let range_fa = psf.f_a(&mat_a, &domain_sample); 362 | /// ``` 363 | /// 364 | /// # Panics ... 365 | /// - if `sigma` is not in the domain. 366 | fn f_a(&self, mat_a: &MatZq, sigma: &MatZ) -> MatZq { 367 | assert!(self.check_domain(sigma)); 368 | mat_a * sigma 369 | } 370 | 371 | /// Checks whether a value `sigma` is in D_n = {e ∈ Z^m | |e| <= s * r * sqrt(m)}. 372 | /// 373 | /// Parameters: 374 | /// - `sigma`: The value for which is checked, if it is in the domain 375 | /// 376 | /// Returns true, if `sigma` is in D_n. 377 | /// 378 | /// # Examples 379 | /// ``` 380 | /// use qfall_tools::primitive::psf::PSF; 381 | /// use qfall_tools::primitive::psf::PSFPerturbation; 382 | /// use qfall_tools::sample::g_trapdoor::gadget_parameters::GadgetParameters; 383 | /// use qfall_math::rational::Q; 384 | /// 385 | /// let psf = PSFPerturbation { 386 | /// gp: GadgetParameters::init_default(8, 64), 387 | /// r: Q::from(3), 388 | /// s: Q::from(25), 389 | /// }; 390 | /// let (mat_a, td) = psf.trap_gen(); 391 | /// 392 | /// let vector = psf.samp_d(); 393 | /// 394 | /// assert!(psf.check_domain(&vector)); 395 | /// ``` 396 | fn check_domain(&self, sigma: &MatZ) -> bool { 397 | let m = &self.gp.n * &self.gp.k + &self.gp.m_bar; 398 | sigma.is_column_vector() 399 | && m == sigma.get_num_rows() 400 | && sigma.norm_eucl_sqrd().unwrap() 401 | <= self.s.pow(2).unwrap() * &m * &self.r.pow(2).unwrap() 402 | } 403 | } 404 | 405 | #[cfg(test)] 406 | mod test_psf_perturbation { 407 | use super::PSF; 408 | use super::PSFPerturbation; 409 | use crate::sample::g_trapdoor::gadget_parameters::GadgetParameters; 410 | use qfall_math::integer::MatZ; 411 | use qfall_math::rational::Q; 412 | use qfall_math::traits::*; 413 | 414 | /// Ensures that `samp_d` actually computes values that are in D_n. 415 | #[test] 416 | fn samp_d_samples_from_dn() { 417 | for (n, q) in [(5, 256), (10, 128), (15, 157)] { 418 | let psf = PSFPerturbation { 419 | gp: GadgetParameters::init_default(n, q), 420 | r: Q::from(n).log(2).unwrap(), 421 | s: Q::from(25), 422 | }; 423 | 424 | for _ in 0..5 { 425 | assert!(psf.check_domain(&psf.samp_d())); 426 | } 427 | } 428 | } 429 | 430 | /// Ensures that `samp_p` actually computes preimages that are also in the correct 431 | /// domain. 432 | #[test] 433 | fn samp_p_preimage_and_domain() { 434 | for (n, q) in [(5, 256), (6, 128)] { 435 | let psf = PSFPerturbation { 436 | gp: GadgetParameters::init_default(n, q), 437 | r: Q::from(n).log(2).unwrap(), 438 | s: Q::from(25), 439 | }; 440 | let (a, r) = psf.trap_gen(); 441 | let domain_sample = psf.samp_d(); 442 | let range_fa = psf.f_a(&a, &domain_sample); 443 | 444 | let preimage = psf.samp_p(&a, &r, &range_fa); 445 | assert_eq!(range_fa, psf.f_a(&a, &preimage)); 446 | assert!(psf.check_domain(&preimage)); 447 | } 448 | } 449 | 450 | /// Ensures that `f_a` returns `a * sigma`. 451 | #[test] 452 | fn f_a_works_as_expected() { 453 | for (n, q) in [(5, 256), (6, 128)] { 454 | let psf = PSFPerturbation { 455 | gp: GadgetParameters::init_default(n, q), 456 | r: Q::from(n).log(2).unwrap(), 457 | s: Q::from(25), 458 | }; 459 | let (a, _) = psf.trap_gen(); 460 | let domain_sample = psf.samp_d(); 461 | 462 | assert_eq!(&a * &domain_sample, psf.f_a(&a, &domain_sample)); 463 | } 464 | } 465 | 466 | /// Ensures that `f_a` panics if a value is provided, that is not within the domain. 467 | /// Sigma is not a vector. 468 | #[test] 469 | #[should_panic] 470 | fn f_a_sigma_not_in_domain_matrix() { 471 | let psf = PSFPerturbation { 472 | gp: GadgetParameters::init_default(8, 128), 473 | r: Q::from(8).log(2).unwrap(), 474 | s: Q::from(25), 475 | }; 476 | let (a, _) = psf.trap_gen(); 477 | let not_in_domain = MatZ::new(a.get_num_columns(), 2); 478 | 479 | let _ = psf.f_a(&a, ¬_in_domain); 480 | } 481 | 482 | /// Ensures that `f_a` panics if a value is provided, that is not within the domain. 483 | /// Sigma is not of the correct length. 484 | #[test] 485 | #[should_panic] 486 | fn f_a_sigma_not_in_domain_incorrect_length() { 487 | let psf = PSFPerturbation { 488 | gp: GadgetParameters::init_default(8, 128), 489 | r: Q::from(8).log(2).unwrap(), 490 | s: Q::from(25), 491 | }; 492 | let (a, _) = psf.trap_gen(); 493 | let not_in_domain = MatZ::new(a.get_num_columns() - 1, 1); 494 | 495 | let _ = psf.f_a(&a, ¬_in_domain); 496 | } 497 | 498 | /// Ensures that `f_a` panics if a value is provided, that is not within the domain. 499 | /// Sigma is too long. 500 | #[test] 501 | #[should_panic] 502 | fn f_a_sigma_not_in_domain_too_long() { 503 | let psf = PSFPerturbation { 504 | gp: GadgetParameters::init_default(8, 128), 505 | r: Q::from(8).log(2).unwrap(), 506 | s: Q::from(25), 507 | }; 508 | let (a, _) = psf.trap_gen(); 509 | let not_in_domain = 510 | psf.s.round() * a.get_num_columns() * MatZ::identity(a.get_num_columns(), 1); 511 | 512 | let _ = psf.f_a(&a, ¬_in_domain); 513 | } 514 | 515 | /// Ensures that `check_domain` works for vectors with the correct length. 516 | #[test] 517 | fn check_domain_as_expected() { 518 | let psf = PSFPerturbation { 519 | gp: GadgetParameters::init_default(8, 128), 520 | r: Q::from(8).log(2).unwrap(), 521 | s: Q::from(25), 522 | }; 523 | let (a, _) = psf.trap_gen(); 524 | let value = psf.s.round(); 525 | let mut in_domain = MatZ::new(a.get_num_columns(), 1); 526 | for i in 0..in_domain.get_num_rows() { 527 | in_domain.set_entry(i, 0, &value).unwrap(); 528 | } 529 | 530 | assert!(psf.check_domain(&MatZ::new(a.get_num_columns(), 1))); 531 | assert!(psf.check_domain(&in_domain)); 532 | } 533 | 534 | /// Ensures that `check_domain` returns false for values that are not in the domain. 535 | #[test] 536 | fn check_domain_not_in_dn() { 537 | let psf = PSFPerturbation { 538 | gp: GadgetParameters::init_default(8, 128), 539 | r: Q::from(8).log(2).unwrap(), 540 | s: Q::from(25), 541 | }; 542 | let (a, _) = psf.trap_gen(); 543 | 544 | let matrix = MatZ::new(a.get_num_columns(), 2); 545 | let too_short = MatZ::new(a.get_num_columns() - 1, 1); 546 | let too_long = MatZ::new(a.get_num_columns() + 1, 1); 547 | let entry_too_large = 548 | psf.s.round() * a.get_num_columns() * MatZ::identity(a.get_num_columns(), 1); 549 | 550 | assert!(!psf.check_domain(&matrix)); 551 | assert!(!psf.check_domain(&too_long)); 552 | assert!(!psf.check_domain(&too_short)); 553 | assert!(!psf.check_domain(&entry_too_large)); 554 | } 555 | } 556 | --------------------------------------------------------------------------------