mirror of
https://github.com/arnaucube/poulpy.git
synced 2026-02-10 05:06:44 +01:00
Fix seeded glwe encryption
This commit is contained in:
@@ -143,20 +143,21 @@ where
|
|||||||
let mut source_xa = Source::new(seed);
|
let mut source_xa = Source::new(seed);
|
||||||
|
|
||||||
let (mut tmp_pt, scrach_1) = scratch.take_glwe_plaintext(res);
|
let (mut tmp_pt, scrach_1) = scratch.take_glwe_plaintext(res);
|
||||||
for col_i in 0..rank_in {
|
|
||||||
for d_i in 0..dnum {
|
for col_j in 0..rank_in {
|
||||||
|
for row_i in 0..dnum {
|
||||||
// Adds the scalar_znx_pt to the i-th limb of the vec_znx_pt
|
// Adds the scalar_znx_pt to the i-th limb of the vec_znx_pt
|
||||||
tmp_pt.data.zero(); // zeroes for next iteration
|
tmp_pt.data.zero(); // zeroes for next iteration
|
||||||
self.vec_znx_add_scalar_inplace(&mut tmp_pt.data, 0, (dsize - 1) + d_i * dsize, pt, col_i);
|
self.vec_znx_add_scalar_inplace(&mut tmp_pt.data, 0, (dsize - 1) + row_i * dsize, pt, col_j);
|
||||||
self.vec_znx_normalize_inplace(base2k, &mut tmp_pt.data, 0, scrach_1);
|
self.vec_znx_normalize_inplace(base2k, &mut tmp_pt.data, 0, scrach_1);
|
||||||
|
|
||||||
let (seed, mut source_xa_tmp) = source_xa.branch();
|
let (seed, mut source_xa_tmp) = source_xa.branch();
|
||||||
seeds[col_i * dnum + d_i] = seed;
|
seeds[row_i * rank_in + col_j] = seed;
|
||||||
|
|
||||||
self.glwe_encrypt_sk_internal(
|
self.glwe_encrypt_sk_internal(
|
||||||
res.base2k().into(),
|
res.base2k().into(),
|
||||||
res.k().into(),
|
res.k().into(),
|
||||||
&mut res.at_mut(d_i, col_i).data,
|
&mut res.at_mut(row_i, col_j).data,
|
||||||
cols,
|
cols,
|
||||||
true,
|
true,
|
||||||
Some((&tmp_pt, 0)),
|
Some((&tmp_pt, 0)),
|
||||||
|
|||||||
@@ -263,9 +263,8 @@ where
|
|||||||
|
|
||||||
let rank_in: usize = res.rank_in().into();
|
let rank_in: usize = res.rank_in().into();
|
||||||
let dnum: usize = res.dnum().into();
|
let dnum: usize = res.dnum().into();
|
||||||
|
for col_i in 0..rank_in {
|
||||||
for row_i in 0..dnum {
|
for row_i in 0..dnum {
|
||||||
for col_i in 0..rank_in {
|
|
||||||
self.decompress_glwe(&mut res.at_mut(row_i, col_i), &other.at(row_i, col_i));
|
self.decompress_glwe(&mut res.at_mut(row_i, col_i), &other.at(row_i, col_i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use poulpy_hal::{
|
use poulpy_hal::{
|
||||||
api::VecZnxAddScalarInplace,
|
api::VecZnxAddScalarInplace,
|
||||||
layouts::{Backend, DataRef, Module, ScalarZnxToRef, Scratch, Stats, ZnxInfos, ZnxZero},
|
layouts::{Backend, DataRef, Module, ScalarZnxToRef, Scratch, Stats, ZnxZero},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -78,7 +78,6 @@ where
|
|||||||
let dsize: usize = res.dsize().into();
|
let dsize: usize = res.dsize().into();
|
||||||
let (mut pt, scratch_1) = scratch.take_glwe_plaintext(res);
|
let (mut pt, scratch_1) = scratch.take_glwe_plaintext(res);
|
||||||
pt.data_mut().zero();
|
pt.data_mut().zero();
|
||||||
println!("col: {res_col} {}", pt_want.to_ref().cols());
|
|
||||||
self.vec_znx_add_scalar_inplace(
|
self.vec_znx_add_scalar_inplace(
|
||||||
&mut pt.data,
|
&mut pt.data,
|
||||||
0,
|
0,
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ glwe_packer => crate::tests::test_suite::test_glwe_packer,
|
|||||||
// GGLWE Encryption
|
// GGLWE Encryption
|
||||||
gglwe_switching_key_encrypt_sk => crate::tests::test_suite::encryption::test_gglwe_switching_key_encrypt_sk,
|
gglwe_switching_key_encrypt_sk => crate::tests::test_suite::encryption::test_gglwe_switching_key_encrypt_sk,
|
||||||
gglwe_switching_key_compressed_encrypt_sk => crate::tests::test_suite::encryption::test_gglwe_switching_key_compressed_encrypt_sk,
|
gglwe_switching_key_compressed_encrypt_sk => crate::tests::test_suite::encryption::test_gglwe_switching_key_compressed_encrypt_sk,
|
||||||
|
gglwe_compressed_encrypt_sk => crate::tests::test_suite::encryption::test_gglwe_compressed_encrypt_sk,
|
||||||
gglwe_automorphism_key_encrypt_sk => crate::tests::test_suite::encryption::test_gglwe_automorphism_key_encrypt_sk,
|
gglwe_automorphism_key_encrypt_sk => crate::tests::test_suite::encryption::test_gglwe_automorphism_key_encrypt_sk,
|
||||||
gglwe_automorphism_key_compressed_encrypt_sk => crate::tests::test_suite::encryption::test_gglwe_automorphism_key_compressed_encrypt_sk,
|
gglwe_automorphism_key_compressed_encrypt_sk => crate::tests::test_suite::encryption::test_gglwe_automorphism_key_compressed_encrypt_sk,
|
||||||
gglwe_tensor_key_encrypt_sk => crate::tests::test_suite::encryption::test_gglwe_tensor_key_encrypt_sk,
|
gglwe_tensor_key_encrypt_sk => crate::tests::test_suite::encryption::test_gglwe_tensor_key_encrypt_sk,
|
||||||
@@ -98,6 +99,7 @@ glwe_packer => crate::tests::test_suite::test_glwe_packer,
|
|||||||
// GGLWE Encryption
|
// GGLWE Encryption
|
||||||
gglwe_switching_key_encrypt_sk => crate::tests::test_suite::encryption::test_gglwe_switching_key_encrypt_sk,
|
gglwe_switching_key_encrypt_sk => crate::tests::test_suite::encryption::test_gglwe_switching_key_encrypt_sk,
|
||||||
gglwe_switching_key_compressed_encrypt_sk => crate::tests::test_suite::encryption::test_gglwe_switching_key_compressed_encrypt_sk,
|
gglwe_switching_key_compressed_encrypt_sk => crate::tests::test_suite::encryption::test_gglwe_switching_key_compressed_encrypt_sk,
|
||||||
|
gglwe_compressed_encrypt_sk => crate::tests::test_suite::encryption::test_gglwe_compressed_encrypt_sk,
|
||||||
gglwe_automorphism_key_encrypt_sk => crate::tests::test_suite::encryption::test_gglwe_automorphism_key_encrypt_sk,
|
gglwe_automorphism_key_encrypt_sk => crate::tests::test_suite::encryption::test_gglwe_automorphism_key_encrypt_sk,
|
||||||
gglwe_automorphism_key_compressed_encrypt_sk => crate::tests::test_suite::encryption::test_gglwe_automorphism_key_compressed_encrypt_sk,
|
gglwe_automorphism_key_compressed_encrypt_sk => crate::tests::test_suite::encryption::test_gglwe_automorphism_key_compressed_encrypt_sk,
|
||||||
gglwe_tensor_key_encrypt_sk => crate::tests::test_suite::encryption::test_gglwe_tensor_key_encrypt_sk,
|
gglwe_tensor_key_encrypt_sk => crate::tests::test_suite::encryption::test_gglwe_tensor_key_encrypt_sk,
|
||||||
|
|||||||
@@ -275,8 +275,6 @@ where
|
|||||||
let mut glwe_pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc_from_infos(&glwe_infos);
|
let mut glwe_pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc_from_infos(&glwe_infos);
|
||||||
glwe_pt.encode_vec_i64(&data, k_lwe_pt);
|
glwe_pt.encode_vec_i64(&data, k_lwe_pt);
|
||||||
|
|
||||||
println!("glwe_pt: {glwe_pt}");
|
|
||||||
|
|
||||||
let mut glwe_ct: GLWE<Vec<u8>> = GLWE::alloc_from_infos(&glwe_infos);
|
let mut glwe_ct: GLWE<Vec<u8>> = GLWE::alloc_from_infos(&glwe_infos);
|
||||||
glwe_ct.encrypt_sk(
|
glwe_ct.encrypt_sk(
|
||||||
module,
|
module,
|
||||||
|
|||||||
@@ -176,7 +176,6 @@ where
|
|||||||
|
|
||||||
let max_noise: f64 = SIGMA.log2() - (atk.k().as_usize() as f64) + 0.5;
|
let max_noise: f64 = SIGMA.log2() - (atk.k().as_usize() as f64) + 0.5;
|
||||||
|
|
||||||
println!("rank: {rank} dsize: {dsize} dnum: {dnum}");
|
|
||||||
for row in 0..atk.dnum().as_usize() {
|
for row in 0..atk.dnum().as_usize() {
|
||||||
for col in 0..atk.rank().as_usize() {
|
for col in 0..atk.rank().as_usize() {
|
||||||
let noise_have = atk
|
let noise_have = atk
|
||||||
|
|||||||
@@ -5,12 +5,13 @@ use poulpy_hal::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
GGLWEEncryptSk, GGLWEKeyswitch, GLWESwitchingKeyCompressedEncryptSk, GLWESwitchingKeyEncryptSk, ScratchTakeCore,
|
GGLWECompressedEncryptSk, GGLWEEncryptSk, GGLWEKeyswitch, GLWESwitchingKeyCompressedEncryptSk, GLWESwitchingKeyEncryptSk,
|
||||||
|
ScratchTakeCore,
|
||||||
decryption::GLWEDecrypt,
|
decryption::GLWEDecrypt,
|
||||||
encryption::SIGMA,
|
encryption::SIGMA,
|
||||||
layouts::{
|
layouts::{
|
||||||
GGLWEInfos, GGLWELayout, GLWESecret, GLWESecretPreparedFactory, GLWESwitchingKey, GLWESwitchingKeyCompressed,
|
GGLWE, GGLWECompressed, GGLWEInfos, GGLWELayout, GLWESecret, GLWESecretPreparedFactory,
|
||||||
GLWESwitchingKeyDecompress, LWEInfos,
|
GLWESwitchingKey, GLWESwitchingKeyCompressed, GLWESwitchingKeyDecompress, LWEInfos,
|
||||||
prepared::{GGLWEPreparedFactory, GLWESecretPrepared},
|
prepared::{GGLWEPreparedFactory, GLWESecretPrepared},
|
||||||
},
|
},
|
||||||
noise::GGLWENoise,
|
noise::GGLWENoise,
|
||||||
@@ -121,18 +122,18 @@ where
|
|||||||
let n: usize = module.n();
|
let n: usize = module.n();
|
||||||
let base2k: usize = 12;
|
let base2k: usize = 12;
|
||||||
let k_ksk: usize = 54;
|
let k_ksk: usize = 54;
|
||||||
let dsize: usize = k_ksk / base2k;
|
let max_dsize: usize = k_ksk / base2k;
|
||||||
for rank_in in 1_usize..3 {
|
for rank_in in 1_usize..3 {
|
||||||
for rank_out in 1_usize..3 {
|
for rank_out in 1_usize..3 {
|
||||||
for di in 1_usize..dsize + 1 {
|
for dsize in 1_usize..max_dsize {
|
||||||
let dnum: usize = (k_ksk - di * base2k) / (di * base2k);
|
let dnum: usize = (k_ksk - dsize * base2k) / (dsize * base2k);
|
||||||
|
|
||||||
let gglwe_infos: GGLWELayout = GGLWELayout {
|
let gglwe_infos: GGLWELayout = GGLWELayout {
|
||||||
n: n.into(),
|
n: n.into(),
|
||||||
base2k: base2k.into(),
|
base2k: base2k.into(),
|
||||||
k: k_ksk.into(),
|
k: k_ksk.into(),
|
||||||
dnum: dnum.into(),
|
dnum: dnum.into(),
|
||||||
dsize: di.into(),
|
dsize: dsize.into(),
|
||||||
rank_in: rank_in.into(),
|
rank_in: rank_in.into(),
|
||||||
rank_out: rank_out.into(),
|
rank_out: rank_out.into(),
|
||||||
};
|
};
|
||||||
@@ -198,3 +199,95 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn test_gglwe_compressed_encrypt_sk<BE: Backend>(module: &Module<BE>)
|
||||||
|
where
|
||||||
|
Module<BE>: GGLWEEncryptSk<BE>
|
||||||
|
+ GGLWEPreparedFactory<BE>
|
||||||
|
+ GGLWEKeyswitch<BE>
|
||||||
|
+ GLWEDecrypt<BE>
|
||||||
|
+ GLWESecretPreparedFactory<BE>
|
||||||
|
+ GLWESwitchingKeyEncryptSk<BE>
|
||||||
|
+ GGLWECompressedEncryptSk<BE>
|
||||||
|
+ GLWESwitchingKeyDecompress
|
||||||
|
+ GGLWENoise<BE>
|
||||||
|
+ VecZnxFillUniform,
|
||||||
|
ScratchOwned<BE>: ScratchOwnedAlloc<BE> + ScratchOwnedBorrow<BE>,
|
||||||
|
Scratch<BE>: ScratchAvailable + ScratchTakeCore<BE>,
|
||||||
|
{
|
||||||
|
let n: usize = module.n();
|
||||||
|
let base2k: usize = 12;
|
||||||
|
let k_ksk: usize = 54;
|
||||||
|
let max_dsize: usize = k_ksk / base2k;
|
||||||
|
for rank_in in 1_usize..3 {
|
||||||
|
for rank_out in 1_usize..3 {
|
||||||
|
for dsize in 1_usize..max_dsize + 1 {
|
||||||
|
let dnum: usize = (k_ksk - dsize * base2k) / (dsize * base2k);
|
||||||
|
|
||||||
|
let gglwe_infos: GGLWELayout = GGLWELayout {
|
||||||
|
n: n.into(),
|
||||||
|
base2k: base2k.into(),
|
||||||
|
k: k_ksk.into(),
|
||||||
|
dnum: dnum.into(),
|
||||||
|
dsize: dsize.into(),
|
||||||
|
rank_in: rank_in.into(),
|
||||||
|
rank_out: rank_out.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut ksk_compressed: GGLWECompressed<Vec<u8>> = GGLWECompressed::alloc_from_infos(&gglwe_infos);
|
||||||
|
|
||||||
|
let mut source_xs: Source = Source::new([0u8; 32]);
|
||||||
|
let mut source_xe: Source = Source::new([0u8; 32]);
|
||||||
|
|
||||||
|
let mut scratch: ScratchOwned<BE> =
|
||||||
|
ScratchOwned::alloc(GGLWECompressed::encrypt_sk_tmp_bytes(module, &gglwe_infos));
|
||||||
|
|
||||||
|
let mut sk_in: GLWESecret<Vec<u8>> = GLWESecret::alloc(n.into(), rank_in.into());
|
||||||
|
sk_in.fill_ternary_prob(0.5, &mut source_xs);
|
||||||
|
|
||||||
|
let mut sk_out: GLWESecret<Vec<u8>> = GLWESecret::alloc(n.into(), rank_out.into());
|
||||||
|
sk_out.fill_ternary_prob(0.5, &mut source_xs);
|
||||||
|
let mut sk_out_prepared: GLWESecretPrepared<Vec<u8>, BE> = GLWESecretPrepared::alloc(module, rank_out.into());
|
||||||
|
sk_out_prepared.prepare(module, &sk_out);
|
||||||
|
|
||||||
|
let seed_xa = [1u8; 32];
|
||||||
|
|
||||||
|
ksk_compressed.encrypt_sk(
|
||||||
|
module,
|
||||||
|
&sk_in.data,
|
||||||
|
&sk_out_prepared,
|
||||||
|
seed_xa,
|
||||||
|
&mut source_xe,
|
||||||
|
scratch.borrow(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut ksk: GGLWE<Vec<u8>> = GGLWE::alloc_from_infos(&gglwe_infos);
|
||||||
|
ksk.decompress(module, &ksk_compressed);
|
||||||
|
|
||||||
|
let max_noise: f64 = SIGMA.log2() - (ksk.k().as_usize() as f64) + 0.5;
|
||||||
|
|
||||||
|
for row in 0..ksk.dnum().as_usize() {
|
||||||
|
for col in 0..ksk.rank_in().as_usize() {
|
||||||
|
let noise_have = ksk
|
||||||
|
.noise(
|
||||||
|
module,
|
||||||
|
row,
|
||||||
|
col,
|
||||||
|
&sk_in.data,
|
||||||
|
&sk_out_prepared,
|
||||||
|
scratch.borrow(),
|
||||||
|
)
|
||||||
|
.std()
|
||||||
|
.log2();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
noise_have < max_noise + 0.5,
|
||||||
|
"row:{row} col:{col} noise_have:{noise_have} > max_noise:{}",
|
||||||
|
max_noise + 0.5
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ use crate::{
|
|||||||
encryption::SIGMA,
|
encryption::SIGMA,
|
||||||
layouts::{
|
layouts::{
|
||||||
Dsize, GGLWE, GGLWEDecompress, GGLWEInfos, GGLWEToGGSWKey, GGLWEToGGSWKeyCompressed, GGLWEToGGSWKeyDecompress,
|
Dsize, GGLWE, GGLWEDecompress, GGLWEInfos, GGLWEToGGSWKey, GGLWEToGGSWKeyCompressed, GGLWEToGGSWKeyDecompress,
|
||||||
GGLWEToGGSWKeyLayout, GLWESecret, GLWESecretPreparedFactory, GLWESecretTensor, GLWESecretTensorFactory,
|
GGLWEToGGSWKeyLayout, GLWESecret, GLWESecretPreparedFactory, GLWESecretTensor, GLWESecretTensorFactory, LWEInfos,
|
||||||
LWEInfos, prepared::GLWESecretPrepared,
|
prepared::GLWESecretPrepared,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use crate::{
|
|||||||
decryption::GLWEDecrypt,
|
decryption::GLWEDecrypt,
|
||||||
encryption::SIGMA,
|
encryption::SIGMA,
|
||||||
layouts::{
|
layouts::{
|
||||||
Dsize, GGLWEDecompress, GGLWEInfos, GLWEInfos, GLWESecret, GLWESecretPreparedFactory, GLWESecretTensor,
|
Dsize, GGLWEDecompress, GGLWEInfos, GLWESecret, GLWESecretPreparedFactory, GLWESecretTensor,
|
||||||
GLWESecretTensorFactory, GLWETensorKey, GLWETensorKeyCompressed, GLWETensorKeyLayout, LWEInfos,
|
GLWESecretTensorFactory, GLWETensorKey, GLWETensorKeyCompressed, GLWETensorKeyLayout, LWEInfos,
|
||||||
prepared::GLWESecretPrepared,
|
prepared::GLWESecretPrepared,
|
||||||
},
|
},
|
||||||
@@ -71,7 +71,7 @@ where
|
|||||||
let max_noise: f64 = SIGMA.log2() - (tensor_key.k().as_usize() as f64) + 0.5;
|
let max_noise: f64 = SIGMA.log2() - (tensor_key.k().as_usize() as f64) + 0.5;
|
||||||
|
|
||||||
for row in 0..tensor_key.dnum().as_usize() {
|
for row in 0..tensor_key.dnum().as_usize() {
|
||||||
for col in 0..tensor_key.rank().as_usize() + 1 {
|
for col in 0..tensor_key.rank_in().as_usize() {
|
||||||
assert!(
|
assert!(
|
||||||
tensor_key
|
tensor_key
|
||||||
.0
|
.0
|
||||||
@@ -148,7 +148,7 @@ where
|
|||||||
let max_noise: f64 = SIGMA.log2() - (tensor_key.k().as_usize() as f64) + 0.5;
|
let max_noise: f64 = SIGMA.log2() - (tensor_key.k().as_usize() as f64) + 0.5;
|
||||||
|
|
||||||
for row in 0..tensor_key.dnum().as_usize() {
|
for row in 0..tensor_key.dnum().as_usize() {
|
||||||
for col in 0..tensor_key.rank().as_usize() + 1 {
|
for col in 0..tensor_key.rank_in().as_usize() {
|
||||||
assert!(
|
assert!(
|
||||||
tensor_key
|
tensor_key
|
||||||
.0
|
.0
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use crate::{
|
|||||||
GGLWEExternalProduct, GGLWENoise, GGSWEncryptSk, GLWESwitchingKeyEncryptSk, ScratchTakeCore,
|
GGLWEExternalProduct, GGLWENoise, GGSWEncryptSk, GLWESwitchingKeyEncryptSk, ScratchTakeCore,
|
||||||
encryption::SIGMA,
|
encryption::SIGMA,
|
||||||
layouts::{
|
layouts::{
|
||||||
GGLWEInfos, GGSW, GGSWLayout, GGSWPreparedFactory, GLWEInfos, GLWESecret, GLWESecretPreparedFactory, GLWESwitchingKey,
|
GGLWEInfos, GGSW, GGSWLayout, GGSWPreparedFactory, GLWESecret, GLWESecretPreparedFactory, GLWESwitchingKey,
|
||||||
GLWESwitchingKeyLayout,
|
GLWESwitchingKeyLayout,
|
||||||
prepared::{GGSWPrepared, GLWESecretPrepared},
|
prepared::{GGSWPrepared, GLWESecretPrepared},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -98,27 +98,6 @@ impl<'a, T: UnsignedInteger, BE: Backend> BitSize for FheUintHelper<'a, T, BE> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct JoinedBits<A, B> {
|
|
||||||
pub lo: A,
|
|
||||||
pub hi: B,
|
|
||||||
pub split: usize, // 32 in your example
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A, B, BE> GetGGSWBit<BE> for JoinedBits<A, B>
|
|
||||||
where
|
|
||||||
BE: Backend,
|
|
||||||
A: GetGGSWBit<BE>,
|
|
||||||
B: GetGGSWBit<BE>,
|
|
||||||
{
|
|
||||||
fn get_bit(&self, bit: usize) -> GGSWPrepared<&[u8], BE> {
|
|
||||||
if bit < self.split {
|
|
||||||
self.lo.get_bit(bit)
|
|
||||||
} else {
|
|
||||||
self.hi.get_bit(bit - self.split)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! define_bdd_2w_to_1w_trait {
|
macro_rules! define_bdd_2w_to_1w_trait {
|
||||||
($(#[$meta:meta])* $vis:vis $trait_name:ident, $method_name:ident) => {
|
($(#[$meta:meta])* $vis:vis $trait_name:ident, $method_name:ident) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user