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) +}