diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs
index 8a0c4be..8ba6809 100644
--- a/benches/benchmarks.rs
+++ b/benches/benchmarks.rs
@@ -9,8 +9,9 @@
use criterion::criterion_main;
+pub mod k_pke;
pub mod pfdh;
pub mod psf;
pub mod regev;
-criterion_main! {regev::benches, pfdh::benches, psf::benches}
+criterion_main! {regev::benches, pfdh::benches, k_pke::benches, psf::benches}
diff --git a/benches/k_pke.rs b/benches/k_pke.rs
new file mode 100644
index 0000000..e42622d
--- /dev/null
+++ b/benches/k_pke.rs
@@ -0,0 +1,145 @@
+// Copyright © 2025 Niklas Siemer
+//
+// This file is part of qFALL-crypto.
+//
+// qFALL-crypto is free software: you can redistribute it and/or modify it under
+// the terms of the Mozilla Public License Version 2.0 as published by the
+// Mozilla Foundation. See .
+
+use criterion::*;
+use qfall_crypto::construction::pk_encryption::PKEncryptionScheme;
+use qfall_crypto::construction::pk_encryption::KPKE;
+
+/// Performs a full-cycle of gen, enc, dec with [`KPKE`].
+fn kpke_cycle(k_pke: &KPKE) {
+ let (pk, sk) = k_pke.gen();
+ let cipher = k_pke.enc(&pk, 1);
+ let _ = k_pke.dec(&sk, &cipher);
+}
+
+/// Benchmark [kpke_cycle] with [KPKE::ml_kem_512].
+///
+/// This benchmark can be run with for example:
+/// - `cargo criterion K-PKE\ cycle\ 512`
+/// - `cargo bench --bench benchmarks K-PKE\ cycle\ 512`
+/// - `cargo flamegraph --bench benchmarks -- --bench K-PKE\ cycle\ 512`
+fn bench_kpke_cycle_512(c: &mut Criterion) {
+ let k_pke = KPKE::ml_kem_512();
+
+ c.bench_function("K-PKE cycle 512", |b| b.iter(|| kpke_cycle(&k_pke)));
+}
+
+/// Benchmark [KPKE::gen] with [KPKE::ml_kem_512].
+fn bench_kpke_gen_512(c: &mut Criterion) {
+ let k_pke = KPKE::ml_kem_512();
+
+ c.bench_function("K-PKE gen 512", |b| b.iter(|| k_pke.gen()));
+}
+
+/// Benchmark [KPKE::enc] with [KPKE::ml_kem_512].
+fn bench_kpke_enc_512(c: &mut Criterion) {
+ let k_pke = KPKE::ml_kem_512();
+ let (pk, _) = k_pke.gen();
+ let msg = i64::MAX;
+
+ c.bench_function("K-PKE enc 512", |b| b.iter(|| k_pke.enc(&pk, msg)));
+}
+
+/// Benchmark [KPKE::dec] with [KPKE::ml_kem_512].
+fn bench_kpke_dec_512(c: &mut Criterion) {
+ let k_pke = KPKE::ml_kem_512();
+ let (pk, sk) = k_pke.gen();
+ let cipher = k_pke.enc(&pk, i64::MAX);
+
+ c.bench_function("K-PKE dec 512", |b| b.iter(|| k_pke.dec(&sk, &cipher)));
+}
+
+/// Benchmark [kpke_cycle] with [KPKE::ml_kem_768].
+///
+/// This benchmark can be run with for example:
+/// - `cargo criterion K-PKE\ cycle\ 768`
+/// - `cargo bench --bench benchmarks K-PKE\ cycle\ 768`
+/// - `cargo flamegraph --bench benchmarks -- --bench K-PKE\ cycle\ 768`
+fn bench_kpke_cycle_768(c: &mut Criterion) {
+ let k_pke = KPKE::ml_kem_768();
+
+ c.bench_function("K-PKE cycle 768", |b| b.iter(|| kpke_cycle(&k_pke)));
+}
+
+/// Benchmark [KPKE::gen] with [KPKE::ml_kem_768].
+fn bench_kpke_gen_768(c: &mut Criterion) {
+ let k_pke = KPKE::ml_kem_768();
+
+ c.bench_function("K-PKE gen 768", |b| b.iter(|| k_pke.gen()));
+}
+
+/// Benchmark [KPKE::enc] with [KPKE::ml_kem_768].
+fn bench_kpke_enc_768(c: &mut Criterion) {
+ let k_pke = KPKE::ml_kem_768();
+ let (pk, _) = k_pke.gen();
+ let msg = i64::MAX;
+
+ c.bench_function("K-PKE enc 768", |b| b.iter(|| k_pke.enc(&pk, msg)));
+}
+
+/// Benchmark [KPKE::dec] with [KPKE::ml_kem_768].
+fn bench_kpke_dec_768(c: &mut Criterion) {
+ let k_pke = KPKE::ml_kem_768();
+ let (pk, sk) = k_pke.gen();
+ let cipher = k_pke.enc(&pk, i64::MAX);
+
+ c.bench_function("K-PKE dec 768", |b| b.iter(|| k_pke.dec(&sk, &cipher)));
+}
+
+/// Benchmark [kpke_cycle] with [KPKE::ml_kem_1024].
+///
+/// This benchmark can be run with for example:
+/// - `cargo criterion K-PKE\ cycle\ 1024`
+/// - `cargo bench --bench benchmarks K-PKE\ cycle\ 1024`
+/// - `cargo flamegraph --bench benchmarks -- --bench K-PKE\ cycle\ 1024`
+fn bench_kpke_cycle_1024(c: &mut Criterion) {
+ let k_pke = KPKE::ml_kem_1024();
+
+ c.bench_function("K-PKE cycle 1024", |b| b.iter(|| kpke_cycle(&k_pke)));
+}
+
+/// Benchmark [KPKE::gen] with [KPKE::ml_kem_1024].
+fn bench_kpke_gen_1024(c: &mut Criterion) {
+ let k_pke = KPKE::ml_kem_1024();
+
+ c.bench_function("K-PKE gen 1024", |b| b.iter(|| k_pke.gen()));
+}
+
+/// Benchmark [KPKE::enc] with [KPKE::ml_kem_1024].
+fn bench_kpke_enc_1024(c: &mut Criterion) {
+ let k_pke = KPKE::ml_kem_1024();
+ let (pk, _) = k_pke.gen();
+ let msg = i64::MAX;
+
+ c.bench_function("K-PKE enc 1024", |b| b.iter(|| k_pke.enc(&pk, msg)));
+}
+
+/// Benchmark [KPKE::dec] with [KPKE::ml_kem_1024].
+fn bench_kpke_dec_1024(c: &mut Criterion) {
+ let k_pke = KPKE::ml_kem_1024();
+ let (pk, sk) = k_pke.gen();
+ let cipher = k_pke.enc(&pk, i64::MAX);
+
+ c.bench_function("K-PKE dec 1024", |b| b.iter(|| k_pke.dec(&sk, &cipher)));
+}
+
+criterion_group!(
+ benches,
+ bench_kpke_cycle_512,
+ bench_kpke_gen_512,
+ bench_kpke_enc_512,
+ bench_kpke_dec_512,
+ bench_kpke_cycle_768,
+ bench_kpke_gen_768,
+ bench_kpke_enc_768,
+ bench_kpke_dec_768,
+ bench_kpke_cycle_1024,
+ bench_kpke_gen_1024,
+ bench_kpke_enc_1024,
+ bench_kpke_dec_1024,
+);
diff --git a/src/construction/pk_encryption.rs b/src/construction/pk_encryption.rs
index db93c44..acb84bc 100644
--- a/src/construction/pk_encryption.rs
+++ b/src/construction/pk_encryption.rs
@@ -32,10 +32,15 @@
//! Chosen-ciphertext security from identity-based encryption.
//! In: Advances in Cryptology - EUROCRYPT 2004.
//!
+//! - \[6\] National Institute of Standards and Technology (2024).
+//! Module-Lattice-Based Key-Encapsulation Mechanism Standard.
+//! Federal Information Processing Standards Publication (FIPS 203).
+//!
mod ccs_from_ibe;
mod dual_regev;
mod dual_regev_discrete_gauss;
+mod k_pke;
mod lpr;
mod regev;
mod regev_discrete_gauss;
@@ -44,6 +49,7 @@ mod ring_lpr;
pub use ccs_from_ibe::CCSfromIBE;
pub use dual_regev::DualRegev;
pub use dual_regev_discrete_gauss::DualRegevWithDiscreteGaussianRegularity;
+pub use k_pke::KPKE;
pub use lpr::LPR;
use qfall_math::integer::Z;
pub use regev::Regev;
diff --git a/src/construction/pk_encryption/k_pke.rs b/src/construction/pk_encryption/k_pke.rs
new file mode 100644
index 0000000..b47ec55
--- /dev/null
+++ b/src/construction/pk_encryption/k_pke.rs
@@ -0,0 +1,276 @@
+// Copyright © 2025 Niklas Siemer
+//
+// This file is part of qFALL-crypto.
+//
+// qFALL-crypto is free software: you can redistribute it and/or modify it under
+// the terms of the Mozilla Public License Version 2.0 as published by the
+// Mozilla Foundation. See .
+
+//! This module contains a naive implementation of the K-PKE scheme
+//! used as foundation for ML-KEM.
+//!
+//! **WARNING:** This implementation is a toy implementation of the basics below
+//! ML-KEM and mostly supposed to showcase the prototyping capabilities of the `qfall`-library.
+
+use crate::{
+ construction::pk_encryption::PKEncryptionScheme,
+ utils::{
+ common_encodings::{
+ decode_z_bitwise_from_polynomialringzq, encode_z_bitwise_in_polynomialringzq,
+ },
+ common_moduli::new_anticyclic,
+ },
+};
+use qfall_math::{
+ integer::Z,
+ integer_mod_q::{MatPolynomialRingZq, ModulusPolynomialRingZq, PolynomialRingZq},
+};
+use serde::{Deserialize, Serialize};
+
+/// This is a naive toy-implementation of the [`PKEncryptionScheme`] used
+/// as a basis for ML-KEM.
+///
+/// This implementation is not supposed to be an implementation of the FIPS 203 standard in [\[6\]](), but
+/// is supposed to showcase the prototyping capabilities of `qfall` and does not cover compression algorithms
+/// as specified in the FIPS 203 document or might deviate for the choice of matrix multiplication algorithms.
+/// Especially, NTT-representation, sampling and multiplication are not part of this prototype.
+///
+/// Attributes:
+/// - `q`: defines the modulus polynomial `(X^n + 1) mod p`
+/// - `k`: defines the width and height of matrix `A`
+/// - `eta_1`: defines that vectors `s`, `e`, and `y` are sampled according to Bin(eta_1, 1/2) centered around 0
+/// - `eta_2`: defines that vector `e_1` and `e_2` are sampled according to Bin(eta_2, 1/2) centered around 0
+///
+/// # Examples
+/// ```
+/// use qfall_crypto::construction::pk_encryption::{KPKE, PKEncryptionScheme};
+///
+/// // setup public parameters
+/// let k_pke = KPKE::ml_kem_512();
+///
+/// // generate (pk, sk) pair
+/// let (pk, sk) = k_pke.gen();
+///
+/// // encrypt a message
+/// let msg = 250;
+/// let cipher = k_pke.enc(&pk, &msg);
+///
+/// // decrypt the ciphertext
+/// let m = k_pke.dec(&sk, &cipher);
+///
+/// assert_eq!(msg, m);
+/// ```
+#[derive(Debug, Serialize, Deserialize)]
+pub struct KPKE {
+ q: ModulusPolynomialRingZq, // modulus (X^n + 1) mod p
+ k: i64, // defines both dimensions of matrix A
+ eta_1: i64, // defines the binomial distribution of the secret and error drawn in `gen`
+ eta_2: i64, // defines the binomial distribution of the error drawn in `enc`
+}
+
+impl KPKE {
+ /// Returns a [`KPKE`] instance with public parameters according to the ML-KEM-512 specification.
+ pub fn ml_kem_512() -> Self {
+ let q = new_anticyclic(256, 3329).unwrap();
+ Self {
+ q,
+ k: 2,
+ eta_1: 3,
+ eta_2: 2,
+ }
+ }
+
+ /// Returns a [`KPKE`] instance with public parameters according to the ML-KEM-768 specification.
+ pub fn ml_kem_768() -> Self {
+ let q = new_anticyclic(256, 3329).unwrap();
+ Self {
+ q,
+ k: 3,
+ eta_1: 2,
+ eta_2: 2,
+ }
+ }
+
+ /// Returns a [`KPKE`] instance with public parameters according to the ML-KEM-1024 specification.
+ pub fn ml_kem_1024() -> Self {
+ let q = new_anticyclic(256, 3329).unwrap();
+ Self {
+ q,
+ k: 4,
+ eta_1: 2,
+ eta_2: 2,
+ }
+ }
+}
+
+impl PKEncryptionScheme for KPKE {
+ type PublicKey = (MatPolynomialRingZq, MatPolynomialRingZq);
+ type SecretKey = MatPolynomialRingZq;
+ type Cipher = (MatPolynomialRingZq, PolynomialRingZq);
+
+ /// Generates a `(pk, sk)` pair by following these steps:
+ /// - A <- R_q^{k x k}
+ /// - s <- Bin(eta_1, 0.5)^k centered around 0
+ /// - e <- Bin(eta_1, 0.5)^k centered around 0
+ /// - t = A * s + e
+ ///
+ /// Then, `pk = (A^T, t)` and `sk = s` are returned.
+ ///
+ /// # Examples
+ /// ```
+ /// use qfall_crypto::construction::pk_encryption::{PKEncryptionScheme, KPKE};
+ /// let k_pke = KPKE::ml_kem_512();
+ ///
+ /// let (pk, sk) = k_pke.gen();
+ /// ```
+ fn gen(&self) -> (Self::PublicKey, Self::SecretKey) {
+ // 5 𝐀[𝑖,𝑗] ← SampleNTT(𝜌‖𝑗‖𝑖)
+ // Reminder: NTT-representation, sampling and multiplication are not part of this prototype
+ let mat_a = MatPolynomialRingZq::sample_uniform(self.k, self.k, &self.q);
+ // 9 𝐬[𝑖] ← SamplePolyCBD_𝜂_1(PRF_𝜂_1 (𝜎, 𝑁))
+ let vec_s = MatPolynomialRingZq::sample_binomial_with_offset(
+ self.k,
+ 1,
+ &self.q,
+ -self.eta_1,
+ 2 * self.eta_1,
+ 0.5,
+ )
+ .unwrap();
+ // 13 𝐞[𝑖] ← SamplePolyCBD_𝜂_1(PRF_𝜂_1 (𝜎, 𝑁))
+ let vec_e = MatPolynomialRingZq::sample_binomial_with_offset(
+ self.k,
+ 1,
+ &self.q,
+ -self.eta_1,
+ 2 * self.eta_1,
+ 0.5,
+ )
+ .unwrap();
+
+ // 18 𝐭 ← 𝐀 ∘ 𝐬 + 𝐞
+ let vec_t = &mat_a * &vec_s + vec_e;
+
+ let pk = (mat_a.transpose(), vec_t);
+ let sk = vec_s;
+ (pk, sk)
+ }
+
+ /// Encrypts a `message` with the provided public key by following these steps:
+ /// - y <- Bin(eta_1, 0.5)^k centered around 0
+ /// - e_1 <- Bin(eta_2, 0.5)^k centered around 0
+ /// - e_2 <- Bin(eta_2, 0.5) centered around 0
+ /// - u = A^T * y + e_1
+ /// - v = t^T * y + e_2 + 𝜇, where 𝜇 is the {q/2, 0} encoding of the bits of `message`
+ ///
+ /// Then, ciphertext `(u, v)` is returned.
+ ///
+ /// Parameters:
+ /// - `pk`: specifies the public key `pk = (A, t)`
+ /// - `message`: specifies the message that should be encrypted, which should not extend 256 bits (and be positive)
+ ///
+ /// Returns a ciphertext `(u, v)` of type [`MatPolynomialRingZq`] and [`PolynomialRingZq`].
+ ///
+ /// # Examples
+ /// ```
+ /// use qfall_crypto::construction::pk_encryption::{PKEncryptionScheme, KPKE};
+ /// let k_pke = KPKE::ml_kem_512();
+ /// let (pk, sk) = k_pke.gen();
+ ///
+ /// let c = k_pke.enc(&pk, 1);
+ /// ```
+ fn enc(&self, pk: &Self::PublicKey, message: impl Into) -> Self::Cipher {
+ // 10 𝐲[𝑖] ← SamplePolyCBD_𝜂_1(PRF_𝜂_1 (𝑟, 𝑁))
+ let vec_y = MatPolynomialRingZq::sample_binomial_with_offset(
+ self.k,
+ 1,
+ &self.q,
+ -self.eta_1,
+ 2 * self.eta_1,
+ 0.5,
+ )
+ .unwrap();
+ // 𝐞_𝟏[𝑖] ← SamplePolyCBD_𝜂_2(PRF_𝜂_2 (𝑟, 𝑁))
+ let vec_e_1 = MatPolynomialRingZq::sample_binomial_with_offset(
+ self.k,
+ 1,
+ &self.q,
+ -self.eta_2,
+ 2 * self.eta_2,
+ 0.5,
+ )
+ .unwrap();
+ // 𝑒_2 ← SamplePolyCBD_𝜂_2(PRF_𝜂_2 (𝑟, 𝑁))
+ let e_2 = PolynomialRingZq::sample_binomial_with_offset(
+ &self.q,
+ -self.eta_2,
+ 2 * self.eta_2,
+ 0.5,
+ )
+ .unwrap();
+
+ // 19 𝐮 ← NTT^−1(𝐀^⊺ ∘ 𝐲) + 𝐞_𝟏
+ let vec_u = &pk.0 * &vec_y + vec_e_1;
+
+ // 20 𝜇 ← Decompress_1(ByteDecode_1(𝑚))
+ let mu = encode_z_bitwise_in_polynomialringzq(&self.q, &message.into());
+
+ // 21 𝑣 ← NTT^−1(𝐭^⊺ ∘ 𝐲) + 𝑒_2 + 𝜇
+ let v = pk.1.dot_product(&vec_y).unwrap() + e_2 + mu;
+
+ (vec_u, v)
+ }
+
+ /// Decrypts the provided `cipher` using the secret key `sk` by following these steps:
+ /// - w = v - s^T * u
+ /// - returns the decoding of `w` with 1 and 0 set in the returned [`Z`] instance
+ /// if the corresponding coefficient was closer to q/2 or 0 respectively
+ ///
+ /// Parameters:
+ /// - `sk`: specifies the secret key `sk = s`
+ /// - `cipher`: specifies the ciphertext containing `cipher = (u, v)`
+ ///
+ /// Returns the decryption of `cipher` as a [`Z`] instance.
+ ///
+ /// # Examples
+ /// ```
+ /// use qfall_crypto::construction::pk_encryption::{PKEncryptionScheme, KPKE};
+ /// let k_pke = KPKE::ml_kem_512();
+ /// let (pk, sk) = k_pke.gen();
+ /// let c = k_pke.enc(&pk, 1);
+ ///
+ /// let m = k_pke.dec(&sk, &c);
+ ///
+ /// assert_eq!(1, m);
+ /// ```
+ fn dec(&self, sk: &Self::SecretKey, (u, v): &Self::Cipher) -> Z {
+ // 6 𝑤 ← 𝑣 − NTT^−1(𝐬^⊺ ∘ NTT(𝐮))
+ let w = v - sk.dot_product(u).unwrap();
+
+ // 7 𝑚 ← ByteEncode_1(Compress_1(𝑤))
+ decode_z_bitwise_from_polynomialringzq(self.q.get_q(), &w)
+ }
+}
+
+#[cfg(test)]
+mod test_kpke {
+ use crate::construction::pk_encryption::{k_pke::KPKE, PKEncryptionScheme};
+
+ /// Ensures that [`KPKE`] works for all ML-KEM specifications by
+ /// performing a round trip of several messages.
+ #[test]
+ fn correctness() {
+ let k_pkes = [KPKE::ml_kem_512(), KPKE::ml_kem_768(), KPKE::ml_kem_1024()];
+ for k_pke in k_pkes {
+ let messages = [0, 1, 13, 255, 2047, 4294967295_u32];
+
+ for message in messages {
+ let (pk, sk) = k_pke.gen();
+ let c = k_pke.enc(&pk, message);
+ let m = k_pke.dec(&sk, &c);
+
+ assert_eq!(message, m);
+ }
+ }
+ }
+}
diff --git a/src/construction/pk_encryption/ring_lpr.rs b/src/construction/pk_encryption/ring_lpr.rs
index 7e6f10e..1ff38ee 100644
--- a/src/construction/pk_encryption/ring_lpr.rs
+++ b/src/construction/pk_encryption/ring_lpr.rs
@@ -10,13 +10,18 @@
//! public key Ring-LPR encryption scheme.
use super::PKEncryptionScheme;
-use crate::utils::common_moduli::new_anticyclic;
+use crate::utils::{
+ common_encodings::{
+ decode_z_bitwise_from_polynomialringzq, encode_z_bitwise_in_polynomialringzq,
+ },
+ common_moduli::new_anticyclic,
+};
use qfall_math::{
error::MathError,
- integer::{PolyOverZ, Z},
- integer_mod_q::{Modulus, ModulusPolynomialRingZq, PolynomialRingZq, Zq},
+ integer::Z,
+ integer_mod_q::{Modulus, ModulusPolynomialRingZq, PolynomialRingZq},
rational::Q,
- traits::{Distance, GetCoefficient, Pow, SetCoefficient},
+ traits::Pow,
};
use serde::{Deserialize, Serialize};
@@ -312,21 +317,6 @@ impl RingLPR {
pub fn secure128() -> Self {
Self::new(512, 92897729, 0.000005)
}
-
- /// Turns a [`Z`] instance into its bit representation, converts this bit representation
- /// into a [`PolynomialRingZq`] with entries q/2 for any 1-bit and 0 as coefficient for any 0-bit.
- fn z_into_polynomialringzq(&self, mu: &Z) -> PolynomialRingZq {
- let bits = mu.to_bits();
- let mut mu_q_half = PolynomialRingZq::from((PolyOverZ::default(), &self.q));
- let q_half = self.q.get_q().div_floor(2);
- for (i, bit) in bits.iter().enumerate() {
- if *bit {
- mu_q_half.set_coeff(i, &q_half).unwrap();
- }
- }
-
- mu_q_half
- }
}
impl Default for RingLPR {
@@ -424,7 +414,7 @@ impl PKEncryptionScheme for RingLPR {
let message: Z = message.into().abs();
let mu = message % Z::from(2).pow(&self.n).unwrap();
// set mu_q_half to polynomial with n {0,1} coefficients
- let mu_q_half = self.z_into_polynomialringzq(&mu);
+ let mu_q_half = encode_z_bitwise_in_polynomialringzq(&self.q, &mu);
// r <- χ
let r = PolynomialRingZq::sample_discrete_gauss(
@@ -490,23 +480,7 @@ impl PKEncryptionScheme for RingLPR {
// res = v - s * u
let result = &cipher.1 - sk * &cipher.0;
- let q_half = self.q.get_q().div_floor(2);
-
- // check for each coefficient whether it's closer to 0 or q/2
- // if closer to q/2 -> add 2^i to result
- let mut vec = vec![];
- for i in 0..self.q.get_degree() {
- let coeff: Zq = result.get_coeff(i).unwrap();
- let coeff: Z = coeff.get_representative_least_absolute_residue().abs();
-
- if coeff.distance(&q_half) < coeff.distance(Z::ZERO) {
- vec.push(true);
- } else {
- vec.push(false);
- }
- }
-
- Z::from_bits(&vec)
+ decode_z_bitwise_from_polynomialringzq(self.q.get_q(), &result)
}
}
diff --git a/src/utils.rs b/src/utils.rs
index d050050..4bfed21 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -10,5 +10,6 @@
//!
//! This can include specialized implementations for certain parameter sets, such as rotation matrices.
+pub mod common_encodings;
pub mod common_moduli;
pub mod rotation_matrix;
diff --git a/src/utils/common_encodings.rs b/src/utils/common_encodings.rs
new file mode 100644
index 0000000..63c80d8
--- /dev/null
+++ b/src/utils/common_encodings.rs
@@ -0,0 +1,58 @@
+// Copyright © 2025 Niklas Siemer
+//
+// This file is part of qFALL-crypto.
+//
+// qFALL-crypto is free software: you can redistribute it and/or modify it under
+// the terms of the Mozilla Public License Version 2.0 as published by the
+// Mozilla Foundation. See .
+
+//! This module contains functions to encode and decode data with commonly used
+//! encodings.
+
+use qfall_math::{
+ integer::{PolyOverZ, Z},
+ integer_mod_q::{ModulusPolynomialRingZq, PolynomialRingZq, Zq},
+ traits::{Distance, GetCoefficient, SetCoefficient},
+};
+
+/// Turns a [`Z`] instance into its bit representation, converts this bit representation
+/// into a [`PolynomialRingZq`] with entries q/2 for any 1-bit and 0 as coefficient for any 0-bit.
+pub fn encode_z_bitwise_in_polynomialringzq(
+ modulus: &ModulusPolynomialRingZq,
+ mu: &Z,
+) -> PolynomialRingZq {
+ let modulus: ModulusPolynomialRingZq = modulus.into();
+ let q_half = modulus.get_q().div_floor(2);
+
+ let bits = mu.to_bits();
+ let mut mu_q_half = PolynomialRingZq::from((PolyOverZ::default(), &modulus));
+ for (i, bit) in bits.iter().enumerate() {
+ if *bit {
+ mu_q_half.set_coeff(i, &q_half).unwrap();
+ }
+ }
+
+ mu_q_half
+}
+
+/// Checks for each coefficient of `poly` whether its value is closer to q/2 or 0
+/// and sets the corresponding bit in the returned [`Z`] value to 1 or 0 respectively.
+pub fn decode_z_bitwise_from_polynomialringzq(modulus: impl Into, poly: &PolynomialRingZq) -> Z {
+ let q_half = modulus.into().div_floor(2);
+
+ // check for each coefficient whether it's closer to 0 or q/2
+ // if closer to q/2 -> add 2^i to result
+ let mut vec = vec![];
+ for i in 0..=poly.get_degree() {
+ let coeff: Zq = poly.get_coeff(i).unwrap();
+ let coeff: Z = coeff.get_representative_least_absolute_residue();
+
+ if coeff.distance(&q_half) < coeff.distance(Z::ZERO) {
+ vec.push(true);
+ } else {
+ vec.push(false);
+ }
+ }
+
+ Z::from_bits(&vec)
+}