mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2024-12-13 09:54:26 -05:00
[WIP] Port to EdwardsPoint and hash_point_to_point
TODO: Use keccak instead of sha512 when hashing scalars.
This commit is contained in:
parent
ff68a1c1b5
commit
bc6a7fb6ff
@ -9,10 +9,9 @@ extern "C" {
|
||||
}
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT;
|
||||
use curve25519_dalek::constants::ED25519_BASEPOINT_POINT;
|
||||
use curve25519_dalek::digest::Digest;
|
||||
use curve25519_dalek::edwards::{CompressedEdwardsY, EdwardsPoint};
|
||||
use curve25519_dalek::ristretto::RistrettoPoint;
|
||||
use curve25519_dalek::scalar::Scalar;
|
||||
use rand::rngs::OsRng;
|
||||
use sha2::Sha512;
|
||||
@ -55,19 +54,14 @@ pub fn hash_point_to_point(point: EdwardsPoint) -> Result<EdwardsPoint> {
|
||||
|
||||
fn challenge(
|
||||
s_i: Scalar,
|
||||
pk_i: RistrettoPoint,
|
||||
pk_i: EdwardsPoint,
|
||||
h_prev: Scalar,
|
||||
I: RistrettoPoint,
|
||||
I: EdwardsPoint,
|
||||
prefix: Sha512,
|
||||
) -> Scalar {
|
||||
let L_i = s_i * RISTRETTO_BASEPOINT_POINT + h_prev * pk_i;
|
||||
) -> Result<Scalar> {
|
||||
let L_i = s_i * ED25519_BASEPOINT_POINT + h_prev * pk_i;
|
||||
|
||||
let H_p_pk_i = {
|
||||
let H_p = Sha512::new()
|
||||
.chain(KEY_TAG.to_string())
|
||||
.chain(pk_i.compress().as_bytes());
|
||||
RistrettoPoint::from_hash(H_p)
|
||||
};
|
||||
let H_p_pk_i = hash_point_to_point(pk_i)?;
|
||||
|
||||
let R_i = s_i * H_p_pk_i + h_prev * I;
|
||||
|
||||
@ -76,22 +70,23 @@ fn challenge(
|
||||
bytes.append(&mut R_i.compress().as_bytes().to_vec());
|
||||
|
||||
let hasher = prefix.chain(bytes);
|
||||
Scalar::from_hash(hasher)
|
||||
|
||||
Ok(Scalar::from_hash(hasher))
|
||||
}
|
||||
|
||||
fn foo(
|
||||
fake_responses: [Scalar; RING_SIZE - 1],
|
||||
ring: [RistrettoPoint; RING_SIZE],
|
||||
T_a: RistrettoPoint,
|
||||
T_b: RistrettoPoint,
|
||||
R_a: RistrettoPoint,
|
||||
I_hat_a: RistrettoPoint,
|
||||
I_hat_b: RistrettoPoint,
|
||||
R_prime_a: RistrettoPoint,
|
||||
I_a: RistrettoPoint,
|
||||
I_b: RistrettoPoint,
|
||||
ring: [EdwardsPoint; RING_SIZE],
|
||||
T_a: EdwardsPoint,
|
||||
T_b: EdwardsPoint,
|
||||
R_a: EdwardsPoint,
|
||||
I_hat_a: EdwardsPoint,
|
||||
I_hat_b: EdwardsPoint,
|
||||
R_prime_a: EdwardsPoint,
|
||||
I_a: EdwardsPoint,
|
||||
I_b: EdwardsPoint,
|
||||
msg: [u8; 32],
|
||||
) -> (Scalar, Scalar) {
|
||||
) -> Result<(Scalar, Scalar)> {
|
||||
let h_0 = {
|
||||
let ring = ring
|
||||
.iter()
|
||||
@ -109,22 +104,22 @@ fn foo(
|
||||
// ring size is 11
|
||||
let h_last = final_challenge(
|
||||
fake_responses,
|
||||
<[RistrettoPoint; 11]>::try_from(ring).unwrap(),
|
||||
<[EdwardsPoint; 11]>::try_from(ring).unwrap(),
|
||||
h_0,
|
||||
I_a + I_b,
|
||||
msg,
|
||||
);
|
||||
)?;
|
||||
|
||||
(h_last, h_0)
|
||||
Ok((h_last, h_0))
|
||||
}
|
||||
|
||||
fn final_challenge(
|
||||
fake_responses: [Scalar; RING_SIZE - 1],
|
||||
ring: [RistrettoPoint; RING_SIZE],
|
||||
ring: [EdwardsPoint; RING_SIZE],
|
||||
h_0: Scalar,
|
||||
I: RistrettoPoint,
|
||||
I: EdwardsPoint,
|
||||
msg: [u8; 32],
|
||||
) -> Scalar {
|
||||
) -> Result<Scalar> {
|
||||
let mut ring_concat = ring
|
||||
.iter()
|
||||
.flat_map(|pk| pk.compress().as_bytes().to_vec())
|
||||
@ -142,10 +137,10 @@ fn final_challenge(
|
||||
|
||||
for (i, s_i) in fake_responses.iter().enumerate() {
|
||||
let pk_i = ring[i + 1];
|
||||
h = challenge(*s_i, pk_i, h, I, prefix.clone());
|
||||
h = challenge(*s_i, pk_i, h, I, prefix.clone())?;
|
||||
}
|
||||
|
||||
h
|
||||
Ok(h)
|
||||
}
|
||||
|
||||
pub struct AdaptorSignature {
|
||||
@ -154,7 +149,7 @@ pub struct AdaptorSignature {
|
||||
fake_responses: [Scalar; RING_SIZE - 1],
|
||||
h_0: Scalar,
|
||||
/// Key image of the real key in the ring.
|
||||
I: RistrettoPoint,
|
||||
I: EdwardsPoint,
|
||||
}
|
||||
|
||||
impl AdaptorSignature {
|
||||
@ -182,23 +177,11 @@ pub struct Signature {
|
||||
pub responses: [Scalar; RING_SIZE],
|
||||
pub h_0: Scalar,
|
||||
/// Key image of the real key in the ring.
|
||||
pub I: RistrettoPoint,
|
||||
pub I: EdwardsPoint,
|
||||
}
|
||||
|
||||
impl Signature {
|
||||
#[cfg(test)]
|
||||
pub fn to_nazgul_signature(&self, ring: &[RistrettoPoint; RING_SIZE]) -> nazgul::clsag::CLSAG {
|
||||
let ring = ring.iter().map(|pk| vec![*pk]).collect();
|
||||
|
||||
nazgul::clsag::CLSAG {
|
||||
challenge: self.h_0,
|
||||
responses: self.responses.to_vec(),
|
||||
ring,
|
||||
key_images: vec![self.I],
|
||||
}
|
||||
}
|
||||
|
||||
fn verify(&self, ring: [RistrettoPoint; RING_SIZE], msg: &[u8; 32]) -> bool {
|
||||
fn verify(&self, ring: [EdwardsPoint; RING_SIZE], msg: &[u8; 32]) -> Result<bool> {
|
||||
let mut ring_concat = ring
|
||||
.iter()
|
||||
.flat_map(|pk| pk.compress().as_bytes().to_vec())
|
||||
@ -216,59 +199,54 @@ impl Signature {
|
||||
|
||||
for (i, s_i) in self.responses.iter().enumerate() {
|
||||
let pk_i = ring[(i + 1) % RING_SIZE];
|
||||
h = challenge(*s_i, pk_i, h, self.I, prefix.clone());
|
||||
h = challenge(*s_i, pk_i, h, self.I, prefix.clone())?;
|
||||
}
|
||||
|
||||
h == self.h_0
|
||||
Ok(h == self.h_0)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Alice0 {
|
||||
// secret index is always 0
|
||||
ring: [RistrettoPoint; RING_SIZE],
|
||||
ring: [EdwardsPoint; RING_SIZE],
|
||||
fake_responses: [Scalar; RING_SIZE - 1],
|
||||
msg: [u8; 32],
|
||||
// encryption key
|
||||
R_a: RistrettoPoint,
|
||||
R_a: EdwardsPoint,
|
||||
// R'a = r_a*H_p(p_k) where p_k is the signing public key
|
||||
R_prime_a: RistrettoPoint,
|
||||
R_prime_a: EdwardsPoint,
|
||||
// this is not s_a cos of something to with one-time-address??
|
||||
s_prime_a: Scalar,
|
||||
// secret value:
|
||||
alpha_a: Scalar,
|
||||
H_p_pk: RistrettoPoint,
|
||||
I_a: RistrettoPoint,
|
||||
I_hat_a: RistrettoPoint,
|
||||
T_a: RistrettoPoint,
|
||||
H_p_pk: EdwardsPoint,
|
||||
I_a: EdwardsPoint,
|
||||
I_hat_a: EdwardsPoint,
|
||||
T_a: EdwardsPoint,
|
||||
}
|
||||
|
||||
impl Alice0 {
|
||||
pub fn new(
|
||||
ring: [RistrettoPoint; RING_SIZE],
|
||||
ring: [EdwardsPoint; RING_SIZE],
|
||||
msg: [u8; 32],
|
||||
R_a: RistrettoPoint,
|
||||
R_prime_a: RistrettoPoint,
|
||||
R_a: EdwardsPoint,
|
||||
R_prime_a: EdwardsPoint,
|
||||
s_prime_a: Scalar,
|
||||
) -> Self {
|
||||
) -> Result<Self> {
|
||||
let mut fake_responses = [Scalar::zero(); RING_SIZE - 1];
|
||||
for response in fake_responses.iter_mut().take(RING_SIZE - 1) {
|
||||
*response = Scalar::random(&mut OsRng);
|
||||
}
|
||||
let alpha_a = Scalar::random(&mut OsRng);
|
||||
|
||||
let p_k = ring[0].compress();
|
||||
let H_p_pk = {
|
||||
let H_p = Sha512::new()
|
||||
.chain(KEY_TAG.to_string())
|
||||
.chain(p_k.as_bytes());
|
||||
RistrettoPoint::from_hash(H_p)
|
||||
};
|
||||
let p_k = ring[0];
|
||||
let H_p_pk = hash_point_to_point(p_k)?;
|
||||
|
||||
let I_a = s_prime_a * H_p_pk;
|
||||
let I_hat_a = alpha_a * H_p_pk;
|
||||
let T_a = alpha_a * RISTRETTO_BASEPOINT_POINT;
|
||||
let T_a = alpha_a * ED25519_BASEPOINT_POINT;
|
||||
|
||||
Alice0 {
|
||||
Ok(Alice0 {
|
||||
ring,
|
||||
fake_responses,
|
||||
msg,
|
||||
@ -280,13 +258,13 @@ impl Alice0 {
|
||||
I_a,
|
||||
I_hat_a,
|
||||
T_a,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn next_message(&self) -> Message0 {
|
||||
Message0 {
|
||||
pi_a: DleqProof::new(
|
||||
RISTRETTO_BASEPOINT_POINT,
|
||||
ED25519_BASEPOINT_POINT,
|
||||
self.T_a,
|
||||
self.H_p_pk,
|
||||
self.I_hat_a,
|
||||
@ -298,7 +276,7 @@ impl Alice0 {
|
||||
|
||||
pub fn receive(self, msg: Message1) -> Result<Alice1> {
|
||||
msg.pi_b
|
||||
.verify(RISTRETTO_BASEPOINT_POINT, msg.T_b, self.H_p_pk, msg.I_hat_b)?;
|
||||
.verify(ED25519_BASEPOINT_POINT, msg.T_b, self.H_p_pk, msg.I_hat_b)?;
|
||||
|
||||
let (h_last, h_0) = foo(
|
||||
self.fake_responses,
|
||||
@ -312,7 +290,7 @@ impl Alice0 {
|
||||
self.I_a,
|
||||
msg.I_b,
|
||||
self.msg,
|
||||
);
|
||||
)?;
|
||||
|
||||
let s_0_a = self.alpha_a - h_last * self.s_prime_a;
|
||||
|
||||
@ -330,11 +308,11 @@ impl Alice0 {
|
||||
|
||||
pub struct Alice1 {
|
||||
fake_responses: [Scalar; RING_SIZE - 1],
|
||||
I_a: RistrettoPoint,
|
||||
I_hat_a: RistrettoPoint,
|
||||
T_a: RistrettoPoint,
|
||||
I_a: EdwardsPoint,
|
||||
I_hat_a: EdwardsPoint,
|
||||
T_a: EdwardsPoint,
|
||||
h_0: Scalar,
|
||||
I_b: RistrettoPoint,
|
||||
I_b: EdwardsPoint,
|
||||
s_0_a: Scalar,
|
||||
}
|
||||
|
||||
@ -365,44 +343,39 @@ pub struct Alice2 {
|
||||
|
||||
pub struct Bob0 {
|
||||
// secret index is always 0
|
||||
ring: [RistrettoPoint; RING_SIZE],
|
||||
ring: [EdwardsPoint; RING_SIZE],
|
||||
msg: [u8; 32],
|
||||
// encryption key
|
||||
R_a: RistrettoPoint,
|
||||
R_a: EdwardsPoint,
|
||||
// R'a = r_a*H_p(p_k) where p_k is the signing public key
|
||||
R_prime_a: RistrettoPoint,
|
||||
R_prime_a: EdwardsPoint,
|
||||
s_b: Scalar,
|
||||
// secret value:
|
||||
alpha_b: Scalar,
|
||||
H_p_pk: RistrettoPoint,
|
||||
I_b: RistrettoPoint,
|
||||
I_hat_b: RistrettoPoint,
|
||||
T_b: RistrettoPoint,
|
||||
H_p_pk: EdwardsPoint,
|
||||
I_b: EdwardsPoint,
|
||||
I_hat_b: EdwardsPoint,
|
||||
T_b: EdwardsPoint,
|
||||
}
|
||||
|
||||
impl Bob0 {
|
||||
pub fn new(
|
||||
ring: [RistrettoPoint; RING_SIZE],
|
||||
ring: [EdwardsPoint; RING_SIZE],
|
||||
msg: [u8; 32],
|
||||
R_a: RistrettoPoint,
|
||||
R_prime_a: RistrettoPoint,
|
||||
R_a: EdwardsPoint,
|
||||
R_prime_a: EdwardsPoint,
|
||||
s_b: Scalar,
|
||||
) -> Self {
|
||||
) -> Result<Self> {
|
||||
let alpha_b = Scalar::random(&mut OsRng);
|
||||
|
||||
let p_k = ring.first().unwrap().compress();
|
||||
let H_p_pk = {
|
||||
let H_p = Sha512::new()
|
||||
.chain(KEY_TAG.to_string())
|
||||
.chain(p_k.as_bytes());
|
||||
RistrettoPoint::from_hash(H_p)
|
||||
};
|
||||
let p_k = ring.first().unwrap();
|
||||
let H_p_pk = hash_point_to_point(*p_k)?;
|
||||
|
||||
let I_b = s_b * H_p_pk;
|
||||
let I_hat_b = alpha_b * H_p_pk;
|
||||
let T_b = alpha_b * RISTRETTO_BASEPOINT_POINT;
|
||||
let T_b = alpha_b * ED25519_BASEPOINT_POINT;
|
||||
|
||||
Bob0 {
|
||||
Ok(Bob0 {
|
||||
ring,
|
||||
msg,
|
||||
R_a,
|
||||
@ -413,7 +386,7 @@ impl Bob0 {
|
||||
I_b,
|
||||
I_hat_b,
|
||||
T_b,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn receive(self, msg: Message0) -> Bob1 {
|
||||
@ -436,19 +409,19 @@ impl Bob0 {
|
||||
|
||||
pub struct Bob1 {
|
||||
// secret index is always 0
|
||||
ring: [RistrettoPoint; RING_SIZE],
|
||||
ring: [EdwardsPoint; RING_SIZE],
|
||||
msg: [u8; 32],
|
||||
// encryption key
|
||||
R_a: RistrettoPoint,
|
||||
R_a: EdwardsPoint,
|
||||
// R'a = r_a*H_p(p_k) where p_k is the signing public key
|
||||
R_prime_a: RistrettoPoint,
|
||||
R_prime_a: EdwardsPoint,
|
||||
s_b: Scalar,
|
||||
// secret value:
|
||||
alpha_b: Scalar,
|
||||
H_p_pk: RistrettoPoint,
|
||||
I_b: RistrettoPoint,
|
||||
I_hat_b: RistrettoPoint,
|
||||
T_b: RistrettoPoint,
|
||||
H_p_pk: EdwardsPoint,
|
||||
I_b: EdwardsPoint,
|
||||
I_hat_b: EdwardsPoint,
|
||||
T_b: EdwardsPoint,
|
||||
pi_a: DleqProof,
|
||||
c_a: Commitment,
|
||||
}
|
||||
@ -460,7 +433,7 @@ impl Bob1 {
|
||||
T_b: self.T_b,
|
||||
I_hat_b: self.I_hat_b,
|
||||
pi_b: DleqProof::new(
|
||||
RISTRETTO_BASEPOINT_POINT,
|
||||
ED25519_BASEPOINT_POINT,
|
||||
self.T_b,
|
||||
self.H_p_pk,
|
||||
self.I_hat_b,
|
||||
@ -473,7 +446,7 @@ impl Bob1 {
|
||||
let (fake_responses, I_a, I_hat_a, T_a) = msg.d_a.open(self.c_a)?;
|
||||
|
||||
self.pi_a
|
||||
.verify(RISTRETTO_BASEPOINT_POINT, T_a, self.H_p_pk, I_hat_a)?;
|
||||
.verify(ED25519_BASEPOINT_POINT, T_a, self.H_p_pk, I_hat_a)?;
|
||||
|
||||
let (h_last, h_0) = foo(
|
||||
fake_responses,
|
||||
@ -487,7 +460,7 @@ impl Bob1 {
|
||||
I_a,
|
||||
self.I_b,
|
||||
self.msg,
|
||||
);
|
||||
)?;
|
||||
|
||||
let s_0_b = self.alpha_b - h_last * self.s_b;
|
||||
|
||||
@ -521,10 +494,10 @@ struct DleqProof {
|
||||
|
||||
impl DleqProof {
|
||||
fn new(
|
||||
G: RistrettoPoint,
|
||||
xG: RistrettoPoint,
|
||||
H: RistrettoPoint,
|
||||
xH: RistrettoPoint,
|
||||
G: EdwardsPoint,
|
||||
xG: EdwardsPoint,
|
||||
H: EdwardsPoint,
|
||||
xH: EdwardsPoint,
|
||||
x: Scalar,
|
||||
) -> Self {
|
||||
let r = Scalar::random(&mut OsRng);
|
||||
@ -547,10 +520,10 @@ impl DleqProof {
|
||||
|
||||
fn verify(
|
||||
&self,
|
||||
G: RistrettoPoint,
|
||||
xG: RistrettoPoint,
|
||||
H: RistrettoPoint,
|
||||
xH: RistrettoPoint,
|
||||
G: EdwardsPoint,
|
||||
xG: EdwardsPoint,
|
||||
H: EdwardsPoint,
|
||||
xH: EdwardsPoint,
|
||||
) -> Result<()> {
|
||||
let s = self.s;
|
||||
let c = self.c;
|
||||
@ -581,9 +554,9 @@ struct Commitment([u8; 64]);
|
||||
impl Commitment {
|
||||
fn new(
|
||||
fake_responses: [Scalar; RING_SIZE - 1],
|
||||
I_a: RistrettoPoint,
|
||||
I_hat_a: RistrettoPoint,
|
||||
T_a: RistrettoPoint,
|
||||
I_a: EdwardsPoint,
|
||||
I_hat_a: EdwardsPoint,
|
||||
T_a: EdwardsPoint,
|
||||
) -> Self {
|
||||
let fake_responses = fake_responses
|
||||
.iter()
|
||||
@ -606,17 +579,17 @@ impl Commitment {
|
||||
|
||||
struct Opening {
|
||||
fake_responses: [Scalar; RING_SIZE - 1],
|
||||
I_a: RistrettoPoint,
|
||||
I_hat_a: RistrettoPoint,
|
||||
T_a: RistrettoPoint,
|
||||
I_a: EdwardsPoint,
|
||||
I_hat_a: EdwardsPoint,
|
||||
T_a: EdwardsPoint,
|
||||
}
|
||||
|
||||
impl Opening {
|
||||
fn new(
|
||||
fake_responses: [Scalar; RING_SIZE - 1],
|
||||
I_a: RistrettoPoint,
|
||||
I_hat_a: RistrettoPoint,
|
||||
T_a: RistrettoPoint,
|
||||
I_a: EdwardsPoint,
|
||||
I_hat_a: EdwardsPoint,
|
||||
T_a: EdwardsPoint,
|
||||
) -> Self {
|
||||
Self {
|
||||
fake_responses,
|
||||
@ -631,9 +604,9 @@ impl Opening {
|
||||
commitment: Commitment,
|
||||
) -> Result<(
|
||||
[Scalar; RING_SIZE - 1],
|
||||
RistrettoPoint,
|
||||
RistrettoPoint,
|
||||
RistrettoPoint,
|
||||
EdwardsPoint,
|
||||
EdwardsPoint,
|
||||
EdwardsPoint,
|
||||
)> {
|
||||
let self_commitment =
|
||||
Commitment::new(self.fake_responses, self.I_a, self.I_hat_a, self.T_a);
|
||||
@ -654,9 +627,9 @@ pub struct Message0 {
|
||||
|
||||
// Bob sends this to ALice
|
||||
pub struct Message1 {
|
||||
I_b: RistrettoPoint,
|
||||
T_b: RistrettoPoint,
|
||||
I_hat_b: RistrettoPoint,
|
||||
I_b: EdwardsPoint,
|
||||
T_b: EdwardsPoint,
|
||||
I_hat_b: EdwardsPoint,
|
||||
pi_b: DleqProof,
|
||||
}
|
||||
|
||||
@ -683,31 +656,30 @@ mod tests {
|
||||
let s_prime_a = Scalar::random(&mut OsRng);
|
||||
let s_b = Scalar::random(&mut OsRng);
|
||||
|
||||
let pk = (s_prime_a + s_b) * RISTRETTO_BASEPOINT_POINT;
|
||||
let pk = (s_prime_a + s_b) * ED25519_BASEPOINT_POINT;
|
||||
|
||||
let (r_a, R_a, R_prime_a) = {
|
||||
let r_a = Scalar::random(&mut OsRng);
|
||||
let R_a = r_a * RISTRETTO_BASEPOINT_POINT;
|
||||
let R_a = r_a * ED25519_BASEPOINT_POINT;
|
||||
|
||||
let pk_hashed_to_point = {
|
||||
let H_p = Sha512::new()
|
||||
.chain(KEY_TAG.to_string())
|
||||
.chain(pk.compress().as_bytes());
|
||||
RistrettoPoint::from_hash(H_p)
|
||||
};
|
||||
let pk_hashed_to_point = hash_point_to_point(pk).unwrap();
|
||||
|
||||
let R_prime_a = r_a * pk_hashed_to_point;
|
||||
|
||||
(r_a, R_a, R_prime_a)
|
||||
};
|
||||
|
||||
let mut ring = [RistrettoPoint::default(); RING_SIZE];
|
||||
let mut ring = [EdwardsPoint::default(); RING_SIZE];
|
||||
ring[0] = pk;
|
||||
|
||||
ring[1..].fill_with(|| RistrettoPoint::random(&mut OsRng));
|
||||
ring[1..].fill_with(|| {
|
||||
let x = Scalar::random(&mut OsRng);
|
||||
|
||||
let alice = Alice0::new(ring, *msg_to_sign, R_a, R_prime_a, s_prime_a);
|
||||
let bob = Bob0::new(ring, *msg_to_sign, R_a, R_prime_a, s_b);
|
||||
x * ED25519_BASEPOINT_POINT
|
||||
});
|
||||
|
||||
let alice = Alice0::new(ring, *msg_to_sign, R_a, R_prime_a, s_prime_a).unwrap();
|
||||
let bob = Bob0::new(ring, *msg_to_sign, R_a, R_prime_a, s_b).unwrap();
|
||||
|
||||
let msg = alice.next_message();
|
||||
let bob = bob.receive(msg);
|
||||
@ -723,7 +695,7 @@ mod tests {
|
||||
|
||||
let sig = alice.adaptor_sig.adapt(r_a);
|
||||
|
||||
assert!(sig.verify(ring, msg_to_sign));
|
||||
assert!(sig.verify(ring, msg_to_sign).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user