mirror of
https://github.com/arnaucube/ark-curves-cherry-picked.git
synced 2026-01-12 08:51:36 +01:00
Implement the Bandersnatch curve (#64)
* impl bandersnatch * clean up * update changelog * Relocate the readme so they show up in the doc * Delete README.md * Relocate the changelog entry * rename & fmt Co-authored-by: Weikeng Chen <w.k@berkeley.edu>
This commit is contained in:
1
ed_on_bls12_381_bandersnatch/src/fields/fq.rs
Normal file
1
ed_on_bls12_381_bandersnatch/src/fields/fq.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub use ark_bls12_381::{Fr as Fq, FrParameters as FqParameters};
|
||||
115
ed_on_bls12_381_bandersnatch/src/fields/fr.rs
Normal file
115
ed_on_bls12_381_bandersnatch/src/fields/fr.rs
Normal file
@@ -0,0 +1,115 @@
|
||||
use ark_ff::{
|
||||
biginteger::BigInteger256 as BigInteger,
|
||||
fields::{FftParameters, Fp256, Fp256Parameters, FpParameters},
|
||||
};
|
||||
|
||||
pub type Fr = Fp256<FrParameters>;
|
||||
|
||||
pub struct FrParameters;
|
||||
|
||||
impl Fp256Parameters for FrParameters {}
|
||||
impl FftParameters for FrParameters {
|
||||
type BigInt = BigInteger;
|
||||
|
||||
/// Let `N` be the size of the multiplicative group defined by the field.
|
||||
/// Then `TWO_ADICITY` is the two-adicity of `N`, i.e. the integer `s`
|
||||
/// such that `N = 2^s * t` for some odd integer `t`.
|
||||
const TWO_ADICITY: u32 = 5;
|
||||
|
||||
/// 2^s root of unity computed by GENERATOR^t
|
||||
/// 4740934665446857387895054948191089665295030226009829406950782728666658007874
|
||||
#[rustfmt::skip]
|
||||
const TWO_ADIC_ROOT_OF_UNITY: BigInteger = BigInteger([
|
||||
0xa4dcdba087826b42,
|
||||
0x6e4ab162f57f862a,
|
||||
0xabc5492749348d6a,
|
||||
0xa7b462035f8c169,
|
||||
]);
|
||||
}
|
||||
impl FpParameters for FrParameters {
|
||||
/// The modulus of the field.
|
||||
/// MODULUS = 13108968793781547619861935127046491459309155893440570251786403306729687672801.
|
||||
#[rustfmt::skip]
|
||||
const MODULUS: BigInteger = BigInteger([
|
||||
0x74fd06b52876e7e1,
|
||||
0xff8f870074190471,
|
||||
0x0cce760202687600,
|
||||
0x1cfb69d4ca675f52,
|
||||
]);
|
||||
|
||||
/// The number of bits needed to represent the `Self::MODULUS`.
|
||||
const MODULUS_BITS: u32 = 253;
|
||||
|
||||
/// The number of bits that can be reliably stored.
|
||||
/// (Should equal `SELF::MODULUS_BITS - 1`)
|
||||
const CAPACITY: u32 = Self::MODULUS_BITS - 1;
|
||||
|
||||
/// The number of bits that must be shaved from the beginning of
|
||||
/// the representation when randomly sampling.
|
||||
const REPR_SHAVE_BITS: u32 = 4;
|
||||
|
||||
/// Let `M` be the power of 2^64 nearest to `Self::MODULUS_BITS`. Then
|
||||
/// `R = M % Self::MODULUS`.
|
||||
/// R = 10920338887063814464675503992315976178796737518116002025166357554075628257528
|
||||
#[rustfmt::skip]
|
||||
const R: BigInteger = BigInteger([
|
||||
0x5817ca56bc48c0f8,
|
||||
0x0383c7fc5f37dc74,
|
||||
0x998c4fefecbc4ff8,
|
||||
0x1824b159acc5056f,
|
||||
]);
|
||||
|
||||
/// R2 = R^2 % Self::MODULUS
|
||||
/// R2 = 4932290691328759802879919559207542894238895193980447506221046538067943049163
|
||||
#[rustfmt::skip]
|
||||
const R2: BigInteger = BigInteger([
|
||||
0xdbb4f5d658db47cb,
|
||||
0x40fa7ca27fecb938,
|
||||
0xaa9e6daec0055cea,
|
||||
0xae793ddb14aec7d
|
||||
]);
|
||||
|
||||
/// INV = -MODULUS^{-1} mod 2^64
|
||||
/// INV = 17410672245482742751
|
||||
const INV: u64 = 0xf19f22295cc063df;
|
||||
|
||||
/// A multiplicative generator of the field.
|
||||
/// `Self::GENERATOR` is an element having multiplicative order
|
||||
/// `Self::MODULUS - 1`.
|
||||
/// n = 9962557815892774795293348142308860067333132192265356416788884706064406244838
|
||||
#[rustfmt::skip]
|
||||
const GENERATOR: BigInteger = BigInteger([
|
||||
0x56b6f3ab7b616de6,
|
||||
0x114f419d6c9083e5,
|
||||
0xbf518d217780c4b9,
|
||||
0x16069b9f45dbce7f,
|
||||
]);
|
||||
|
||||
/// (Self::MODULUS - 1) / 2
|
||||
/// 6554484396890773809930967563523245729654577946720285125893201653364843836400
|
||||
const MODULUS_MINUS_ONE_DIV_TWO: BigInteger = BigInteger([
|
||||
0xba7e835a943b73f0,
|
||||
0x7fc7c3803a0c8238,
|
||||
0x06673b0101343b00,
|
||||
0xe7db4ea6533afa9,
|
||||
]);
|
||||
|
||||
/// t for 2^s * t = MODULUS - 1, and t coprime to 2.
|
||||
/// t = 409655274805673363120685472720202858103411121670017820368325103335302739775
|
||||
/// = (modulus-1)/2^5
|
||||
const T: BigInteger = BigInteger([
|
||||
0x8ba7e835a943b73f,
|
||||
0x07fc7c3803a0c823,
|
||||
0x906673b0101343b0,
|
||||
0xe7db4ea6533afa,
|
||||
]);
|
||||
|
||||
/// (t - 1) / 2
|
||||
/// = 204827637402836681560342736360101429051705560835008910184162551667651369887
|
||||
const T_MINUS_ONE_DIV_TWO: BigInteger = BigInteger([
|
||||
0xc5d3f41ad4a1db9f,
|
||||
0x03fe3e1c01d06411,
|
||||
0x483339d80809a1d8,
|
||||
0x73eda753299d7d,
|
||||
]);
|
||||
}
|
||||
8
ed_on_bls12_381_bandersnatch/src/fields/mod.rs
Normal file
8
ed_on_bls12_381_bandersnatch/src/fields/mod.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
pub mod fq;
|
||||
pub mod fr;
|
||||
|
||||
pub use fq::*;
|
||||
pub use fr::*;
|
||||
|
||||
#[cfg(all(feature = "ed_on_bls12_381_bandersnatch", test))]
|
||||
mod tests;
|
||||
423
ed_on_bls12_381_bandersnatch/src/fields/tests.rs
Normal file
423
ed_on_bls12_381_bandersnatch/src/fields/tests.rs
Normal file
@@ -0,0 +1,423 @@
|
||||
use crate::{Fq, Fr};
|
||||
use ark_algebra_test_templates::fields::*;
|
||||
use ark_ff::{
|
||||
biginteger::BigInteger256 as BigInteger,
|
||||
bytes::{FromBytes, ToBytes},
|
||||
fields::{Field, LegendreSymbol::*, SquareRootField},
|
||||
One, Zero,
|
||||
};
|
||||
use ark_std::{rand::Rng, str::FromStr, test_rng};
|
||||
|
||||
#[test]
|
||||
fn test_fr() {
|
||||
let mut rng = test_rng();
|
||||
let a: Fr = rng.gen();
|
||||
let b: Fr = rng.gen();
|
||||
field_test(a, b);
|
||||
primefield_test::<Fr>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fq() {
|
||||
let mut rng = test_rng();
|
||||
let a: Fq = rng.gen();
|
||||
let b: Fq = rng.gen();
|
||||
field_test(a, b);
|
||||
primefield_test::<Fq>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fq_add() {
|
||||
let f1 = Fq::from_str(
|
||||
"18386742314266644595564329008376577163854043021652781768352795308532764650733",
|
||||
)
|
||||
.unwrap();
|
||||
let f2 = Fq::from_str(
|
||||
"39786307610986038981023499868190793548353538256264351797285876981647142458383",
|
||||
)
|
||||
.unwrap();
|
||||
let f3 = Fq::from_str(
|
||||
"5737174750126493097140088368381404874517028777389495743035013590241325924603",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(!f1.is_zero());
|
||||
assert!(!f2.is_zero());
|
||||
assert!(!f3.is_zero());
|
||||
assert_eq!(f1 + &f2, f3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fq_add_one() {
|
||||
let f1 = Fq::from_str(
|
||||
"4946875394261337176810256604189376311946643975348516311606738923340201185904",
|
||||
)
|
||||
.unwrap();
|
||||
let f2 = Fq::from_str(
|
||||
"4946875394261337176810256604189376311946643975348516311606738923340201185905",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(!f1.is_zero());
|
||||
assert!(!f2.is_zero());
|
||||
assert_eq!(f1 + &Fq::one(), f2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fq_mul() {
|
||||
let f1 = Fq::from_str(
|
||||
"24703123148064348394273033316595937198355721297494556079070134653139656190956",
|
||||
)
|
||||
.unwrap();
|
||||
let f2 = Fq::from_str(
|
||||
"38196797080882758914424853878212529985425118523754343117256179679117054302131",
|
||||
)
|
||||
.unwrap();
|
||||
let f3 = Fq::from_str(
|
||||
"38057113854472161555556064369220825628027487067886761874351491955834635348140",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(!f1.is_zero());
|
||||
assert!(!f2.is_zero());
|
||||
assert!(!f3.is_zero());
|
||||
assert_eq!(f1 * &f2, f3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fq_triple_mul() {
|
||||
let f1 = Fq::from_str(
|
||||
"23834398828139479510988224171342199299644042568628082836691700490363123893905",
|
||||
)
|
||||
.unwrap();
|
||||
let f2 = Fq::from_str(
|
||||
"48343809612844640454129919255697536258606705076971130519928764925719046689317",
|
||||
)
|
||||
.unwrap();
|
||||
let f3 = Fq::from_str(
|
||||
"22704845471524346880579660022678666462201713488283356385810726260959369106033",
|
||||
)
|
||||
.unwrap();
|
||||
let f4 = Fq::from_str(
|
||||
"18897508522635316277030308074760673440128491438505204942623624791502972539393",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(!f1.is_zero());
|
||||
assert!(!f2.is_zero());
|
||||
assert!(!f3.is_zero());
|
||||
assert_eq!(f1 * &f2 * &f3, f4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fq_div() {
|
||||
let f1 = Fq::from_str(
|
||||
"31892744363926593013886463524057935370302352424137349660481695792871889573091",
|
||||
)
|
||||
.unwrap();
|
||||
let f2 = Fq::from_str(
|
||||
"47695868328933459965610498875668250916462767196500056002116961816137113470902",
|
||||
)
|
||||
.unwrap();
|
||||
let f3 = Fq::from_str(
|
||||
"29049672724678710659792141917402891276693777283079976086581207190825261000580",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(!f1.is_zero());
|
||||
assert!(!f2.is_zero());
|
||||
assert!(!f3.is_zero());
|
||||
assert_eq!(f1 / &f2, f3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fq_sub() {
|
||||
let f1 = Fq::from_str(
|
||||
"18695869713129401390241150743745601908470616448391638969502807001833388904079",
|
||||
)
|
||||
.unwrap();
|
||||
let f2 = Fq::from_str(
|
||||
"10105476028534616828778879109836101003805485072436929139123765141153277007373",
|
||||
)
|
||||
.unwrap();
|
||||
let f3 = Fq::from_str(
|
||||
"8590393684594784561462271633909500904665131375954709830379041860680111896706",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(!f1.is_zero());
|
||||
assert!(!f2.is_zero());
|
||||
assert!(!f3.is_zero());
|
||||
assert_eq!(f1 - &f2, f3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fq_double_in_place() {
|
||||
let mut f1 = Fq::from_str(
|
||||
"29729289787452206300641229002276778748586801323231253291984198106063944136114",
|
||||
)
|
||||
.unwrap();
|
||||
let f3 = Fq::from_str(
|
||||
"7022704399778222121834717496367591659483050145934868761364737512189307087715",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(!f1.is_zero());
|
||||
assert!(!f3.is_zero());
|
||||
f1.double_in_place();
|
||||
assert_eq!(f1, f3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fq_double_in_place_thrice() {
|
||||
let mut f1 = Fq::from_str(
|
||||
"32768907806651393940832831055386272949401004221411141755415956893066040832473",
|
||||
)
|
||||
.unwrap();
|
||||
let f3 = Fq::from_str(
|
||||
"52407761752706389608871686410346320244445823769178582752913020344774001921732",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(!f1.is_zero());
|
||||
assert!(!f3.is_zero());
|
||||
f1.double_in_place();
|
||||
f1.double_in_place();
|
||||
f1.double_in_place();
|
||||
assert_eq!(f1, f3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fq_generate_random_ed_on_bls12_381_point() {
|
||||
let d = Fq::from_str(
|
||||
"19257038036680949359750312669786877991949435402254120286184196891950884077233",
|
||||
)
|
||||
.unwrap();
|
||||
let y = Fq::from_str(
|
||||
"20269054604167148422407276086932743904275456233139568486008667107872965128512",
|
||||
)
|
||||
.unwrap();
|
||||
let x2 = Fq::from_str(
|
||||
"35041048504708632193693740149219726446678304552734087046982753200179718192840",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let computed_y2 = y.square();
|
||||
let y2 = Fq::from_str(
|
||||
"22730681238307918419349440108285755984465605552827817317611903495170775437833",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(y2, computed_y2);
|
||||
|
||||
let computed_dy2 = d * &computed_y2;
|
||||
let dy2 = Fq::from_str(
|
||||
"24720347560552809545835752815204882739669031262711919770503096707526812943411",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(dy2, computed_dy2);
|
||||
|
||||
let computed_divisor = computed_dy2 + &Fq::one();
|
||||
let divisor = Fq::from_str(
|
||||
"24720347560552809545835752815204882739669031262711919770503096707526812943412",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(divisor, computed_divisor);
|
||||
|
||||
let computed_x2 = (computed_y2 - &Fq::one()) / &computed_divisor;
|
||||
assert_eq!(x2, computed_x2);
|
||||
|
||||
let x = Fq::from_str(
|
||||
"15337652609730546173818014678723269532482775720866471265774032070871608223361",
|
||||
)
|
||||
.unwrap();
|
||||
let computed_x = computed_x2.sqrt().unwrap();
|
||||
assert_eq!(computed_x.square(), x2);
|
||||
assert_eq!(x, computed_x);
|
||||
|
||||
fn add<'a>(curr: (Fq, Fq), other: &'a (Fq, Fq)) -> (Fq, Fq) {
|
||||
let y1y2 = curr.1 * &other.1;
|
||||
let x1x2 = curr.0 * &other.0;
|
||||
let d = Fq::from_str(
|
||||
"19257038036680949359750312669786877991949435402254120286184196891950884077233",
|
||||
)
|
||||
.unwrap();
|
||||
let dx1x2y1y2 = d * &y1y2 * &x1x2;
|
||||
|
||||
let d1 = Fq::one() + &dx1x2y1y2;
|
||||
let d2 = Fq::one() - &dx1x2y1y2;
|
||||
|
||||
let x1y2 = curr.0 * &other.1;
|
||||
let y1x2 = curr.1 * &other.0;
|
||||
|
||||
let x = (x1y2 + &y1x2) / &d1;
|
||||
let y = (y1y2 + &x1x2) / &d2;
|
||||
|
||||
(x, y)
|
||||
}
|
||||
|
||||
let result = add((x, y), &(x, y));
|
||||
let result = add(result, &result);
|
||||
let result = add(result, &result);
|
||||
|
||||
let point_x = Fq::from_str(
|
||||
"47259664076168047050113154262636619161204477920503059672059915868534495873964",
|
||||
)
|
||||
.unwrap();
|
||||
let point_y = Fq::from_str(
|
||||
"19016409245280491801573912449420132838852726543024859389273314249842195919690",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!((point_x, point_y), result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fq_square_in_place() {
|
||||
let mut f1 = Fq::from_str(
|
||||
"34864651240005695523200639428464570946052769938774601449735727714436878540682",
|
||||
)
|
||||
.unwrap();
|
||||
let f3 =
|
||||
Fq::from_str("213133100629336594719108316042277780359104840987226496279264105585804377948")
|
||||
.unwrap();
|
||||
assert!(!f1.is_zero());
|
||||
assert!(!f3.is_zero());
|
||||
f1.square_in_place();
|
||||
assert_eq!(f1, f3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fq_sqrt() {
|
||||
let f1 = Fq::from_str(
|
||||
"10875927553327821418567659853801220899541454800710193788767706167237535308235",
|
||||
)
|
||||
.unwrap();
|
||||
let f3 = Fq::from_str(
|
||||
"10816221372957505053219354782681292880545918527618367765651802809826238616708",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(f1.sqrt().unwrap(), f3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fq_from_str() {
|
||||
let f1_from_repr = Fq::from(BigInteger([
|
||||
0xab8a2535947d1a77,
|
||||
0x9ba74cbfda0bbcda,
|
||||
0xe928b59724d60baf,
|
||||
0x1cccaaeb9bb1680a,
|
||||
]));
|
||||
let f1 = Fq::from_str(
|
||||
"13026376210409056429264774981357153555336288129100724591327877625017068755575",
|
||||
)
|
||||
.unwrap();
|
||||
let f2_from_repr = Fq::from(BigInteger([
|
||||
0x97e9103775d2f35c,
|
||||
0xbe6756b6c587544b,
|
||||
0x6ee38c3afd88ef4b,
|
||||
0x2bacd150f540c677,
|
||||
]));
|
||||
let f2 = Fq::from_str(
|
||||
"19754794831832707859764530223239420866832328728734160755396495950822165902172",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(f1_from_repr, f1);
|
||||
assert_eq!(f2_from_repr, f2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fq_legendre() {
|
||||
assert_eq!(QuadraticResidue, Fq::one().legendre());
|
||||
assert_eq!(Zero, Fq::zero().legendre());
|
||||
|
||||
let e = BigInteger([
|
||||
0x0dbc5349cd5664da,
|
||||
0x8ac5b6296e3ae29d,
|
||||
0x127cb819feceaa3b,
|
||||
0x3a6b21fb03867191,
|
||||
]);
|
||||
assert_eq!(QuadraticResidue, Fq::from(e).legendre());
|
||||
let e = BigInteger([
|
||||
0x96341aefd047c045,
|
||||
0x9b5f4254500a4d65,
|
||||
0x1ee08223b68ac240,
|
||||
0x31d9cd545c0ec7c6,
|
||||
]);
|
||||
assert_eq!(QuadraticNonResidue, Fq::from(e).legendre());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fq_bytes() {
|
||||
let f1_from_repr = Fq::from(BigInteger([
|
||||
0xab8a2535947d1a77,
|
||||
0x9ba74cbfda0bbcda,
|
||||
0xe928b59724d60baf,
|
||||
0x1cccaaeb9bb1680a,
|
||||
]));
|
||||
|
||||
let mut f1_bytes = [0u8; 32];
|
||||
f1_from_repr.write(f1_bytes.as_mut()).unwrap();
|
||||
|
||||
let f1 = Fq::read(f1_bytes.as_ref()).unwrap();
|
||||
assert_eq!(f1_from_repr, f1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fr_add() {
|
||||
let f1 = Fr::from(BigInteger([
|
||||
0xc81265fb4130fe0c,
|
||||
0xb308836c14e22279,
|
||||
0x699e887f96bff372,
|
||||
0x84ecc7e76c11ad,
|
||||
]));
|
||||
let f2 = Fr::from(BigInteger([
|
||||
0x71875719b422efb8,
|
||||
0x0043658e68a93612,
|
||||
0x9fa756be2011e833,
|
||||
0xaa2b2cb08dac497,
|
||||
]));
|
||||
let f3 = Fr::from(BigInteger([
|
||||
0x3999bd14f553edc4,
|
||||
0xb34be8fa7d8b588c,
|
||||
0x0945df3db6d1dba5,
|
||||
0xb279f92f046d645,
|
||||
]));
|
||||
assert_eq!(f1 + &f2, f3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fr_mul() {
|
||||
let f1 = Fr::from(BigInteger([
|
||||
0xc81265fb4130fe0c,
|
||||
0xb308836c14e22279,
|
||||
0x699e887f96bff372,
|
||||
0x84ecc7e76c11ad,
|
||||
]));
|
||||
let f2 = Fr::from(BigInteger([
|
||||
0x71875719b422efb8,
|
||||
0x43658e68a93612,
|
||||
0x9fa756be2011e833,
|
||||
0xaa2b2cb08dac497,
|
||||
]));
|
||||
let f3 = Fr::from(BigInteger([
|
||||
0xbe3e50c164fe3381,
|
||||
0x5ac45bc180974585,
|
||||
0x1c234ad6dcdc70c9,
|
||||
0x15a75fba99bc8ad,
|
||||
]));
|
||||
assert_eq!(f1 * &f2, f3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fr_bytes() {
|
||||
let f1_from_repr = Fr::from(BigInteger([
|
||||
0xc81265fb4130fe0c,
|
||||
0xb308836c14e22279,
|
||||
0x699e887f96bff372,
|
||||
0x84ecc7e76c11ad,
|
||||
]));
|
||||
|
||||
let mut f1_bytes = [0u8; 32];
|
||||
f1_from_repr.write(f1_bytes.as_mut()).unwrap();
|
||||
|
||||
let f1 = Fr::read(f1_bytes.as_ref()).unwrap();
|
||||
assert_eq!(f1_from_repr, f1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fr_from_str() {
|
||||
let f100_from_repr = Fr::from(BigInteger([0x64, 0, 0, 0]));
|
||||
let f100 = Fr::from_str("100").unwrap();
|
||||
assert_eq!(f100_from_repr, f100);
|
||||
}
|
||||
Reference in New Issue
Block a user