mirror of
https://github.com/arnaucube/hyperplonk.git
synced 2026-01-12 17:01:28 +01:00
Bump to arkworks-0.4.0 (#126)
* Bump to arkworks-0.4.0 * Replace remaining usages of `msm_bigint` with `msm_unchecked` Using `msm_unchecked` instead of `msm_bigint` allows to delete the BigInt conversion code by letting the library take care of it.
This commit is contained in:
@@ -11,7 +11,7 @@ use crate::{
|
||||
pcs::PolynomialCommitmentScheme,
|
||||
poly_iop::{errors::PolyIOPErrors, prelude::ProductCheck, PolyIOP},
|
||||
};
|
||||
use ark_ec::PairingEngine;
|
||||
use ark_ec::pairing::Pairing;
|
||||
use ark_poly::DenseMultilinearExtension;
|
||||
use ark_std::{end_timer, start_timer};
|
||||
use std::sync::Arc;
|
||||
@@ -23,14 +23,14 @@ use transcript::IOPTranscript;
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct PermutationCheckSubClaim<E, PCS, PC>
|
||||
where
|
||||
E: PairingEngine,
|
||||
E: Pairing,
|
||||
PC: ProductCheck<E, PCS>,
|
||||
PCS: PolynomialCommitmentScheme<E>,
|
||||
{
|
||||
/// the SubClaim from the ProductCheck
|
||||
pub product_check_sub_claim: PC::ProductCheckSubClaim,
|
||||
/// Challenges beta and gamma
|
||||
pub challenges: (E::Fr, E::Fr),
|
||||
pub challenges: (E::ScalarField, E::ScalarField),
|
||||
}
|
||||
|
||||
pub mod util;
|
||||
@@ -48,7 +48,7 @@ pub mod util;
|
||||
/// - permutation oracles = (p1, ..., pk)
|
||||
pub trait PermutationCheck<E, PCS>: ProductCheck<E, PCS>
|
||||
where
|
||||
E: PairingEngine,
|
||||
E: Pairing,
|
||||
PCS: PolynomialCommitmentScheme<E>,
|
||||
{
|
||||
type PermutationCheckSubClaim;
|
||||
@@ -79,7 +79,7 @@ where
|
||||
fxs: &[Self::MultilinearExtension],
|
||||
gxs: &[Self::MultilinearExtension],
|
||||
perms: &[Self::MultilinearExtension],
|
||||
transcript: &mut IOPTranscript<E::Fr>,
|
||||
transcript: &mut IOPTranscript<E::ScalarField>,
|
||||
) -> Result<
|
||||
(
|
||||
Self::PermutationProof,
|
||||
@@ -98,16 +98,16 @@ where
|
||||
) -> Result<Self::PermutationCheckSubClaim, PolyIOPErrors>;
|
||||
}
|
||||
|
||||
impl<E, PCS> PermutationCheck<E, PCS> for PolyIOP<E::Fr>
|
||||
impl<E, PCS> PermutationCheck<E, PCS> for PolyIOP<E::ScalarField>
|
||||
where
|
||||
E: PairingEngine,
|
||||
PCS: PolynomialCommitmentScheme<E, Polynomial = Arc<DenseMultilinearExtension<E::Fr>>>,
|
||||
E: Pairing,
|
||||
PCS: PolynomialCommitmentScheme<E, Polynomial = Arc<DenseMultilinearExtension<E::ScalarField>>>,
|
||||
{
|
||||
type PermutationCheckSubClaim = PermutationCheckSubClaim<E, PCS, Self>;
|
||||
type PermutationProof = Self::ProductCheckProof;
|
||||
|
||||
fn init_transcript() -> Self::Transcript {
|
||||
IOPTranscript::<E::Fr>::new(b"Initializing PermutationCheck transcript")
|
||||
IOPTranscript::<E::ScalarField>::new(b"Initializing PermutationCheck transcript")
|
||||
}
|
||||
|
||||
fn prove(
|
||||
@@ -115,7 +115,7 @@ where
|
||||
fxs: &[Self::MultilinearExtension],
|
||||
gxs: &[Self::MultilinearExtension],
|
||||
perms: &[Self::MultilinearExtension],
|
||||
transcript: &mut IOPTranscript<E::Fr>,
|
||||
transcript: &mut IOPTranscript<E::ScalarField>,
|
||||
) -> Result<
|
||||
(
|
||||
Self::PermutationProof,
|
||||
@@ -195,22 +195,25 @@ mod test {
|
||||
};
|
||||
use arithmetic::{evaluate_opt, identity_permutation_mles, random_permutation_mles, VPAuxInfo};
|
||||
use ark_bls12_381::Bls12_381;
|
||||
use ark_ec::PairingEngine;
|
||||
use ark_ec::pairing::Pairing;
|
||||
use ark_poly::{DenseMultilinearExtension, MultilinearExtension};
|
||||
use ark_std::test_rng;
|
||||
use std::{marker::PhantomData, sync::Arc};
|
||||
|
||||
type KZG = MultilinearKzgPCS<Bls12_381>;
|
||||
type Kzg = MultilinearKzgPCS<Bls12_381>;
|
||||
|
||||
fn test_permutation_check_helper<E, PCS>(
|
||||
pcs_param: &PCS::ProverParam,
|
||||
fxs: &[Arc<DenseMultilinearExtension<E::Fr>>],
|
||||
gxs: &[Arc<DenseMultilinearExtension<E::Fr>>],
|
||||
perms: &[Arc<DenseMultilinearExtension<E::Fr>>],
|
||||
fxs: &[Arc<DenseMultilinearExtension<E::ScalarField>>],
|
||||
gxs: &[Arc<DenseMultilinearExtension<E::ScalarField>>],
|
||||
perms: &[Arc<DenseMultilinearExtension<E::ScalarField>>],
|
||||
) -> Result<(), PolyIOPErrors>
|
||||
where
|
||||
E: PairingEngine,
|
||||
PCS: PolynomialCommitmentScheme<E, Polynomial = Arc<DenseMultilinearExtension<E::Fr>>>,
|
||||
E: Pairing,
|
||||
PCS: PolynomialCommitmentScheme<
|
||||
E,
|
||||
Polynomial = Arc<DenseMultilinearExtension<E::ScalarField>>,
|
||||
>,
|
||||
{
|
||||
let nv = fxs[0].num_vars;
|
||||
// what's AuxInfo used for?
|
||||
@@ -221,20 +224,23 @@ mod test {
|
||||
};
|
||||
|
||||
// prover
|
||||
let mut transcript = <PolyIOP<E::Fr> as PermutationCheck<E, PCS>>::init_transcript();
|
||||
let mut transcript =
|
||||
<PolyIOP<E::ScalarField> as PermutationCheck<E, PCS>>::init_transcript();
|
||||
transcript.append_message(b"testing", b"initializing transcript for testing")?;
|
||||
let (proof, prod_x, _frac_poly) = <PolyIOP<E::Fr> as PermutationCheck<E, PCS>>::prove(
|
||||
pcs_param,
|
||||
fxs,
|
||||
gxs,
|
||||
perms,
|
||||
&mut transcript,
|
||||
)?;
|
||||
let (proof, prod_x, _frac_poly) =
|
||||
<PolyIOP<E::ScalarField> as PermutationCheck<E, PCS>>::prove(
|
||||
pcs_param,
|
||||
fxs,
|
||||
gxs,
|
||||
perms,
|
||||
&mut transcript,
|
||||
)?;
|
||||
|
||||
// verifier
|
||||
let mut transcript = <PolyIOP<E::Fr> as PermutationCheck<E, PCS>>::init_transcript();
|
||||
let mut transcript =
|
||||
<PolyIOP<E::ScalarField> as PermutationCheck<E, PCS>>::init_transcript();
|
||||
transcript.append_message(b"testing", b"initializing transcript for testing")?;
|
||||
let perm_check_sub_claim = <PolyIOP<E::Fr> as PermutationCheck<E, PCS>>::verify(
|
||||
let perm_check_sub_claim = <PolyIOP<E::ScalarField> as PermutationCheck<E, PCS>>::verify(
|
||||
&proof,
|
||||
&poly_info,
|
||||
&mut transcript,
|
||||
@@ -267,7 +273,7 @@ mod test {
|
||||
Arc::new(DenseMultilinearExtension::rand(nv, &mut rng)),
|
||||
];
|
||||
// perms is the identity map
|
||||
test_permutation_check_helper::<Bls12_381, KZG>(&pcs_param, &ws, &ws, &id_perms)?;
|
||||
test_permutation_check_helper::<Bls12_381, Kzg>(&pcs_param, &ws, &ws, &id_perms)?;
|
||||
}
|
||||
|
||||
{
|
||||
@@ -281,7 +287,7 @@ mod test {
|
||||
// perms is the reverse identity map
|
||||
let mut perms = id_perms.clone();
|
||||
perms.reverse();
|
||||
test_permutation_check_helper::<Bls12_381, KZG>(&pcs_param, &fs, &gs, &perms)?;
|
||||
test_permutation_check_helper::<Bls12_381, Kzg>(&pcs_param, &fs, &gs, &perms)?;
|
||||
}
|
||||
|
||||
{
|
||||
@@ -294,7 +300,7 @@ mod test {
|
||||
let perms = random_permutation_mles(nv, 2, &mut rng);
|
||||
|
||||
assert!(
|
||||
test_permutation_check_helper::<Bls12_381, KZG>(&pcs_param, &ws, &ws, &perms)
|
||||
test_permutation_check_helper::<Bls12_381, Kzg>(&pcs_param, &ws, &ws, &perms)
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
@@ -311,7 +317,7 @@ mod test {
|
||||
];
|
||||
// s_perm is the identity map
|
||||
|
||||
assert!(test_permutation_check_helper::<Bls12_381, KZG>(
|
||||
assert!(test_permutation_check_helper::<Bls12_381, Kzg>(
|
||||
&pcs_param, &fs, &gs, &id_perms
|
||||
)
|
||||
.is_err());
|
||||
|
||||
@@ -16,7 +16,7 @@ use crate::{
|
||||
},
|
||||
};
|
||||
use arithmetic::VPAuxInfo;
|
||||
use ark_ec::PairingEngine;
|
||||
use ark_ec::pairing::Pairing;
|
||||
use ark_ff::{One, PrimeField, Zero};
|
||||
use ark_poly::DenseMultilinearExtension;
|
||||
use ark_std::{end_timer, start_timer};
|
||||
@@ -52,9 +52,9 @@ mod util;
|
||||
/// 2. `generate_challenge` from current transcript (generate alpha)
|
||||
/// 3. `verify` to verify the zerocheck proof and generate the subclaim for
|
||||
/// polynomial evaluations
|
||||
pub trait ProductCheck<E, PCS>: ZeroCheck<E::Fr>
|
||||
pub trait ProductCheck<E, PCS>: ZeroCheck<E::ScalarField>
|
||||
where
|
||||
E: PairingEngine,
|
||||
E: Pairing,
|
||||
PCS: PolynomialCommitmentScheme<E>,
|
||||
{
|
||||
type ProductCheckSubClaim;
|
||||
@@ -90,7 +90,7 @@ where
|
||||
pcs_param: &PCS::ProverParam,
|
||||
fxs: &[Self::MultilinearExtension],
|
||||
gxs: &[Self::MultilinearExtension],
|
||||
transcript: &mut IOPTranscript<E::Fr>,
|
||||
transcript: &mut IOPTranscript<E::ScalarField>,
|
||||
) -> Result<
|
||||
(
|
||||
Self::ProductCheckProof,
|
||||
@@ -106,7 +106,7 @@ where
|
||||
/// = \prod_{x \in {0,1}^n} g1(x) * ... * gk(x)`
|
||||
fn verify(
|
||||
proof: &Self::ProductCheckProof,
|
||||
aux_info: &VPAuxInfo<E::Fr>,
|
||||
aux_info: &VPAuxInfo<E::ScalarField>,
|
||||
transcript: &mut Self::Transcript,
|
||||
) -> Result<Self::ProductCheckSubClaim, PolyIOPErrors>;
|
||||
}
|
||||
@@ -136,32 +136,32 @@ pub struct ProductCheckSubClaim<F: PrimeField, ZC: ZeroCheck<F>> {
|
||||
/// - a polynomial commitment for the fractional polynomial
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct ProductCheckProof<
|
||||
E: PairingEngine,
|
||||
E: Pairing,
|
||||
PCS: PolynomialCommitmentScheme<E>,
|
||||
ZC: ZeroCheck<E::Fr>,
|
||||
ZC: ZeroCheck<E::ScalarField>,
|
||||
> {
|
||||
pub zero_check_proof: ZC::ZeroCheckProof,
|
||||
pub prod_x_comm: PCS::Commitment,
|
||||
pub frac_comm: PCS::Commitment,
|
||||
}
|
||||
|
||||
impl<E, PCS> ProductCheck<E, PCS> for PolyIOP<E::Fr>
|
||||
impl<E, PCS> ProductCheck<E, PCS> for PolyIOP<E::ScalarField>
|
||||
where
|
||||
E: PairingEngine,
|
||||
PCS: PolynomialCommitmentScheme<E, Polynomial = Arc<DenseMultilinearExtension<E::Fr>>>,
|
||||
E: Pairing,
|
||||
PCS: PolynomialCommitmentScheme<E, Polynomial = Arc<DenseMultilinearExtension<E::ScalarField>>>,
|
||||
{
|
||||
type ProductCheckSubClaim = ProductCheckSubClaim<E::Fr, Self>;
|
||||
type ProductCheckSubClaim = ProductCheckSubClaim<E::ScalarField, Self>;
|
||||
type ProductCheckProof = ProductCheckProof<E, PCS, Self>;
|
||||
|
||||
fn init_transcript() -> Self::Transcript {
|
||||
IOPTranscript::<E::Fr>::new(b"Initializing ProductCheck transcript")
|
||||
IOPTranscript::<E::ScalarField>::new(b"Initializing ProductCheck transcript")
|
||||
}
|
||||
|
||||
fn prove(
|
||||
pcs_param: &PCS::ProverParam,
|
||||
fxs: &[Self::MultilinearExtension],
|
||||
gxs: &[Self::MultilinearExtension],
|
||||
transcript: &mut IOPTranscript<E::Fr>,
|
||||
transcript: &mut IOPTranscript<E::ScalarField>,
|
||||
) -> Result<
|
||||
(
|
||||
Self::ProductCheckProof,
|
||||
@@ -220,7 +220,7 @@ where
|
||||
|
||||
fn verify(
|
||||
proof: &Self::ProductCheckProof,
|
||||
aux_info: &VPAuxInfo<E::Fr>,
|
||||
aux_info: &VPAuxInfo<E::ScalarField>,
|
||||
transcript: &mut Self::Transcript,
|
||||
) -> Result<Self::ProductCheckSubClaim, PolyIOPErrors> {
|
||||
let start = start_timer!(|| "prod_check verify");
|
||||
@@ -232,14 +232,17 @@ where
|
||||
|
||||
// invoke the zero check on the iop_proof
|
||||
// the virtual poly info for Q(x)
|
||||
let zero_check_sub_claim =
|
||||
<Self as ZeroCheck<E::Fr>>::verify(&proof.zero_check_proof, aux_info, transcript)?;
|
||||
let zero_check_sub_claim = <Self as ZeroCheck<E::ScalarField>>::verify(
|
||||
&proof.zero_check_proof,
|
||||
aux_info,
|
||||
transcript,
|
||||
)?;
|
||||
|
||||
// the final query is on prod_x
|
||||
let mut final_query = vec![E::Fr::one(); aux_info.num_variables];
|
||||
let mut final_query = vec![E::ScalarField::one(); aux_info.num_variables];
|
||||
// the point has to be reversed because Arkworks uses big-endian.
|
||||
final_query[0] = E::Fr::zero();
|
||||
let final_eval = E::Fr::one();
|
||||
final_query[0] = E::ScalarField::zero();
|
||||
let final_eval = E::ScalarField::one();
|
||||
|
||||
end_timer!(start);
|
||||
|
||||
@@ -260,53 +263,60 @@ mod test {
|
||||
};
|
||||
use arithmetic::VPAuxInfo;
|
||||
use ark_bls12_381::{Bls12_381, Fr};
|
||||
use ark_ec::PairingEngine;
|
||||
use ark_ec::pairing::Pairing;
|
||||
use ark_poly::{DenseMultilinearExtension, MultilinearExtension};
|
||||
use ark_std::test_rng;
|
||||
use std::{marker::PhantomData, sync::Arc};
|
||||
|
||||
fn check_frac_poly<E>(
|
||||
frac_poly: &Arc<DenseMultilinearExtension<E::Fr>>,
|
||||
fs: &[Arc<DenseMultilinearExtension<E::Fr>>],
|
||||
gs: &[Arc<DenseMultilinearExtension<E::Fr>>],
|
||||
frac_poly: &Arc<DenseMultilinearExtension<E::ScalarField>>,
|
||||
fs: &[Arc<DenseMultilinearExtension<E::ScalarField>>],
|
||||
gs: &[Arc<DenseMultilinearExtension<E::ScalarField>>],
|
||||
) where
|
||||
E: PairingEngine,
|
||||
E: Pairing,
|
||||
{
|
||||
let mut flag = true;
|
||||
let num_vars = frac_poly.num_vars;
|
||||
for i in 0..1 << num_vars {
|
||||
let nom = fs
|
||||
.iter()
|
||||
.fold(E::Fr::from(1u8), |acc, f| acc * f.evaluations[i]);
|
||||
.fold(E::ScalarField::from(1u8), |acc, f| acc * f.evaluations[i]);
|
||||
let denom = gs
|
||||
.iter()
|
||||
.fold(E::Fr::from(1u8), |acc, g| acc * g.evaluations[i]);
|
||||
.fold(E::ScalarField::from(1u8), |acc, g| acc * g.evaluations[i]);
|
||||
if denom * frac_poly.evaluations[i] != nom {
|
||||
flag = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert_eq!(flag, true);
|
||||
assert!(flag);
|
||||
}
|
||||
// fs and gs are guaranteed to have the same product
|
||||
// fs and hs doesn't have the same product
|
||||
fn test_product_check_helper<E, PCS>(
|
||||
fs: &[Arc<DenseMultilinearExtension<E::Fr>>],
|
||||
gs: &[Arc<DenseMultilinearExtension<E::Fr>>],
|
||||
hs: &[Arc<DenseMultilinearExtension<E::Fr>>],
|
||||
fs: &[Arc<DenseMultilinearExtension<E::ScalarField>>],
|
||||
gs: &[Arc<DenseMultilinearExtension<E::ScalarField>>],
|
||||
hs: &[Arc<DenseMultilinearExtension<E::ScalarField>>],
|
||||
pcs_param: &PCS::ProverParam,
|
||||
) -> Result<(), PolyIOPErrors>
|
||||
where
|
||||
E: PairingEngine,
|
||||
PCS: PolynomialCommitmentScheme<E, Polynomial = Arc<DenseMultilinearExtension<E::Fr>>>,
|
||||
E: Pairing,
|
||||
PCS: PolynomialCommitmentScheme<
|
||||
E,
|
||||
Polynomial = Arc<DenseMultilinearExtension<E::ScalarField>>,
|
||||
>,
|
||||
{
|
||||
let mut transcript = <PolyIOP<E::Fr> as ProductCheck<E, PCS>>::init_transcript();
|
||||
let mut transcript = <PolyIOP<E::ScalarField> as ProductCheck<E, PCS>>::init_transcript();
|
||||
transcript.append_message(b"testing", b"initializing transcript for testing")?;
|
||||
|
||||
let (proof, prod_x, frac_poly) =
|
||||
<PolyIOP<E::Fr> as ProductCheck<E, PCS>>::prove(pcs_param, fs, gs, &mut transcript)?;
|
||||
let (proof, prod_x, frac_poly) = <PolyIOP<E::ScalarField> as ProductCheck<E, PCS>>::prove(
|
||||
pcs_param,
|
||||
fs,
|
||||
gs,
|
||||
&mut transcript,
|
||||
)?;
|
||||
|
||||
let mut transcript = <PolyIOP<E::Fr> as ProductCheck<E, PCS>>::init_transcript();
|
||||
let mut transcript = <PolyIOP<E::ScalarField> as ProductCheck<E, PCS>>::init_transcript();
|
||||
transcript.append_message(b"testing", b"initializing transcript for testing")?;
|
||||
|
||||
// what's aux_info for?
|
||||
@@ -315,8 +325,11 @@ mod test {
|
||||
num_variables: fs[0].num_vars,
|
||||
phantom: PhantomData::default(),
|
||||
};
|
||||
let prod_subclaim =
|
||||
<PolyIOP<E::Fr> as ProductCheck<E, PCS>>::verify(&proof, &aux_info, &mut transcript)?;
|
||||
let prod_subclaim = <PolyIOP<E::ScalarField> as ProductCheck<E, PCS>>::verify(
|
||||
&proof,
|
||||
&aux_info,
|
||||
&mut transcript,
|
||||
)?;
|
||||
assert_eq!(
|
||||
prod_x.evaluate(&prod_subclaim.final_query.0).unwrap(),
|
||||
prod_subclaim.final_query.1,
|
||||
@@ -325,15 +338,19 @@ mod test {
|
||||
check_frac_poly::<E>(&frac_poly, fs, gs);
|
||||
|
||||
// bad path
|
||||
let mut transcript = <PolyIOP<E::Fr> as ProductCheck<E, PCS>>::init_transcript();
|
||||
let mut transcript = <PolyIOP<E::ScalarField> as ProductCheck<E, PCS>>::init_transcript();
|
||||
transcript.append_message(b"testing", b"initializing transcript for testing")?;
|
||||
|
||||
let (bad_proof, prod_x_bad, frac_poly) =
|
||||
<PolyIOP<E::Fr> as ProductCheck<E, PCS>>::prove(pcs_param, fs, hs, &mut transcript)?;
|
||||
let (bad_proof, prod_x_bad, frac_poly) = <PolyIOP<E::ScalarField> as ProductCheck<
|
||||
E,
|
||||
PCS,
|
||||
>>::prove(
|
||||
pcs_param, fs, hs, &mut transcript
|
||||
)?;
|
||||
|
||||
let mut transcript = <PolyIOP<E::Fr> as ProductCheck<E, PCS>>::init_transcript();
|
||||
let mut transcript = <PolyIOP<E::ScalarField> as ProductCheck<E, PCS>>::init_transcript();
|
||||
transcript.append_message(b"testing", b"initializing transcript for testing")?;
|
||||
let bad_subclaim = <PolyIOP<E::Fr> as ProductCheck<E, PCS>>::verify(
|
||||
let bad_subclaim = <PolyIOP<E::ScalarField> as ProductCheck<E, PCS>>::verify(
|
||||
&bad_proof,
|
||||
&aux_info,
|
||||
&mut transcript,
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
use arithmetic::VirtualPolynomial;
|
||||
use ark_ff::PrimeField;
|
||||
use ark_serialize::{CanonicalSerialize, SerializationError, Write};
|
||||
use ark_serialize::CanonicalSerialize;
|
||||
|
||||
/// An IOP proof is a collections of
|
||||
/// - messages from prover to verifier at each round through the interactive
|
||||
|
||||
@@ -321,7 +321,7 @@ mod test {
|
||||
use super::interpolate_uni_poly;
|
||||
use crate::poly_iop::errors::PolyIOPErrors;
|
||||
use ark_bls12_381::Fr;
|
||||
use ark_poly::{univariate::DensePolynomial, Polynomial, UVPolynomial};
|
||||
use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial, Polynomial};
|
||||
use ark_std::{vec::Vec, UniformRand};
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
macro_rules! to_bytes {
|
||||
($x:expr) => {{
|
||||
let mut buf = ark_std::vec![];
|
||||
ark_serialize::CanonicalSerialize::serialize($x, &mut buf).map(|_| buf)
|
||||
ark_serialize::CanonicalSerialize::serialize_compressed($x, &mut buf).map(|_| buf)
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ mod test {
|
||||
let f1 = Fr::one();
|
||||
|
||||
let mut bytes = ark_std::vec![];
|
||||
f1.serialize(&mut bytes).unwrap();
|
||||
f1.serialize_compressed(&mut bytes).unwrap();
|
||||
assert_eq!(bytes, to_bytes!(&f1).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user