mirror of
https://github.com/logos-storage/plonky2.git
synced 2026-01-05 07:13:08 +00:00
Merge branch 'main' into extension-field
This commit is contained in:
commit
75711f1d3f
@ -4,9 +4,11 @@ use plonky2::circuit_builder::CircuitBuilder;
|
||||
use plonky2::circuit_data::CircuitConfig;
|
||||
use plonky2::field::crandall_field::CrandallField;
|
||||
use plonky2::field::field::Field;
|
||||
use plonky2::fri::FriConfig;
|
||||
use plonky2::gates::constant::ConstantGate;
|
||||
use plonky2::gates::gmimc::GMiMCGate;
|
||||
use plonky2::hash::GMIMC_ROUNDS;
|
||||
use plonky2::prover::PLONK_BLINDING;
|
||||
use plonky2::witness::PartialWitness;
|
||||
|
||||
fn main() {
|
||||
@ -34,11 +36,18 @@ fn bench_prove<F: Field>() {
|
||||
security_bits: 128,
|
||||
rate_bits: 3,
|
||||
num_checks: 3,
|
||||
fri_config: FriConfig {
|
||||
proof_of_work_bits: 1,
|
||||
rate_bits: 3,
|
||||
reduction_arity_bits: vec![1],
|
||||
num_query_rounds: 1,
|
||||
blinding: PLONK_BLINDING.to_vec(),
|
||||
},
|
||||
};
|
||||
|
||||
let mut builder = CircuitBuilder::<F>::new(config);
|
||||
|
||||
for _ in 0..5000 {
|
||||
for _ in 0..10000 {
|
||||
builder.add_gate_no_constants(gmimc_gate.clone());
|
||||
}
|
||||
|
||||
|
||||
@ -16,6 +16,7 @@ use crate::generator::{CopyGenerator, WitnessGenerator};
|
||||
use crate::hash::hash_n_to_hash;
|
||||
use crate::merkle_tree::MerkleTree;
|
||||
use crate::permutation_argument::TargetPartitions;
|
||||
use crate::polynomial::commitment::ListPolynomialCommitment;
|
||||
use crate::polynomial::polynomial::PolynomialValues;
|
||||
use crate::target::Target;
|
||||
use crate::util::{log2_strict, transpose, transpose_poly_values};
|
||||
@ -138,11 +139,11 @@ impl<F: Field> CircuitBuilder<F> {
|
||||
/// Both elements must be routable, otherwise this method will panic.
|
||||
pub fn assert_equal(&mut self, x: Target, y: Target) {
|
||||
assert!(
|
||||
x.is_routable(self.config),
|
||||
x.is_routable(&self.config),
|
||||
"Tried to route a wire that isn't routable"
|
||||
);
|
||||
assert!(
|
||||
y.is_routable(self.config),
|
||||
y.is_routable(&self.config),
|
||||
"Tried to route a wire that isn't routable"
|
||||
);
|
||||
self.copy_constraints.push((x, y));
|
||||
@ -271,18 +272,22 @@ impl<F: Field> CircuitBuilder<F> {
|
||||
info!("degree after blinding & padding: {}", degree);
|
||||
|
||||
let constant_vecs = self.constant_polys();
|
||||
let constant_ldes = PolynomialValues::lde_multiple(constant_vecs, self.config.rate_bits);
|
||||
let constant_ldes_t = transpose_poly_values(constant_ldes);
|
||||
let constants_tree = MerkleTree::new(constant_ldes_t, true);
|
||||
let constants_commitment = ListPolynomialCommitment::new(
|
||||
constant_vecs.into_iter().map(|v| v.ifft()).collect(),
|
||||
self.config.fri_config.rate_bits,
|
||||
false,
|
||||
);
|
||||
|
||||
let k_is = get_unique_coset_shifts(degree, self.config.num_routed_wires);
|
||||
let sigma_vecs = self.sigma_vecs(&k_is);
|
||||
let sigma_ldes = PolynomialValues::lde_multiple(sigma_vecs, self.config.rate_bits);
|
||||
let sigma_ldes_t = transpose_poly_values(sigma_ldes);
|
||||
let sigmas_tree = MerkleTree::new(sigma_ldes_t, true);
|
||||
let sigmas_commitment = ListPolynomialCommitment::new(
|
||||
sigma_vecs.into_iter().map(|v| v.ifft()).collect(),
|
||||
self.config.fri_config.rate_bits,
|
||||
false,
|
||||
);
|
||||
|
||||
let constants_root = constants_tree.root;
|
||||
let sigmas_root = sigmas_tree.root;
|
||||
let constants_root = constants_commitment.merkle_tree.root;
|
||||
let sigmas_root = sigmas_commitment.merkle_tree.root;
|
||||
let verifier_only = VerifierOnlyCircuitData {
|
||||
constants_root,
|
||||
sigmas_root,
|
||||
@ -291,8 +296,8 @@ impl<F: Field> CircuitBuilder<F> {
|
||||
let generators = self.generators;
|
||||
let prover_only = ProverOnlyCircuitData {
|
||||
generators,
|
||||
constants_tree,
|
||||
sigmas_tree,
|
||||
constants_commitment,
|
||||
sigmas_commitment,
|
||||
};
|
||||
|
||||
// The HashSet of gates will have a non-deterministic order. When converting to a Vec, we
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
use crate::field::field::Field;
|
||||
use crate::fri::FriConfig;
|
||||
use crate::gates::gate::GateRef;
|
||||
use crate::generator::WitnessGenerator;
|
||||
use crate::merkle_tree::MerkleTree;
|
||||
use crate::polynomial::commitment::ListPolynomialCommitment;
|
||||
use crate::proof::{Hash, HashTarget, Proof};
|
||||
use crate::prover::prove;
|
||||
use crate::verifier::verify;
|
||||
use crate::witness::PartialWitness;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Clone)]
|
||||
pub struct CircuitConfig {
|
||||
pub num_wires: usize,
|
||||
pub num_routed_wires: usize,
|
||||
@ -15,6 +17,9 @@ pub struct CircuitConfig {
|
||||
pub rate_bits: usize,
|
||||
/// The number of times to repeat checks that have soundness errors of (roughly) `degree / |F|`.
|
||||
pub num_checks: usize,
|
||||
|
||||
// TODO: Find a better place for this.
|
||||
pub fri_config: FriConfig,
|
||||
}
|
||||
|
||||
impl Default for CircuitConfig {
|
||||
@ -25,6 +30,13 @@ impl Default for CircuitConfig {
|
||||
security_bits: 128,
|
||||
rate_bits: 3,
|
||||
num_checks: 3,
|
||||
fri_config: FriConfig {
|
||||
proof_of_work_bits: 1,
|
||||
rate_bits: 1,
|
||||
reduction_arity_bits: vec![1],
|
||||
num_query_rounds: 1,
|
||||
blinding: vec![true],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -85,10 +97,10 @@ impl<F: Field> VerifierCircuitData<F> {
|
||||
/// Circuit data required by the prover, but not the verifier.
|
||||
pub(crate) struct ProverOnlyCircuitData<F: Field> {
|
||||
pub generators: Vec<Box<dyn WitnessGenerator<F>>>,
|
||||
/// Merkle tree containing LDEs of each constant polynomial.
|
||||
pub constants_tree: MerkleTree<F>,
|
||||
/// Merkle tree containing LDEs of each sigma polynomial.
|
||||
pub sigmas_tree: MerkleTree<F>,
|
||||
/// Commitments to the constants polynomial.
|
||||
pub constants_commitment: ListPolynomialCommitment<F>,
|
||||
/// Commitments to the sigma polynomial.
|
||||
pub sigmas_commitment: ListPolynomialCommitment<F>,
|
||||
}
|
||||
|
||||
/// Circuit data required by the verifier, but not the prover.
|
||||
|
||||
@ -5,7 +5,7 @@ pub mod verifier;
|
||||
/// while increasing L, potentially requiring more challenge points.
|
||||
const EPSILON: f64 = 0.01;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct FriConfig {
|
||||
pub proof_of_work_bits: u32,
|
||||
|
||||
@ -20,8 +20,9 @@ pub struct FriConfig {
|
||||
/// Number of query rounds to perform.
|
||||
pub num_query_rounds: usize,
|
||||
|
||||
/// True if the last element of the Merkle trees' leaf vectors is a blinding element.
|
||||
pub blinding: bool,
|
||||
/// Vector of the same length as the number of initial Merkle trees.
|
||||
/// `blinding[i]==true` iff the i-th tree is salted.
|
||||
pub blinding: Vec<bool>,
|
||||
}
|
||||
|
||||
fn fri_delta(rate_log: usize, conjecture: bool) -> f64 {
|
||||
@ -79,7 +80,7 @@ mod tests {
|
||||
rate_bits,
|
||||
proof_of_work_bits: 2,
|
||||
reduction_arity_bits,
|
||||
blinding: false,
|
||||
blinding: vec![false],
|
||||
};
|
||||
let tree = {
|
||||
let mut leaves = coset_lde
|
||||
@ -92,7 +93,7 @@ mod tests {
|
||||
};
|
||||
let root = tree.root;
|
||||
let mut challenger = Challenger::new();
|
||||
let proof = fri_proof(&[tree], &coeffs, &coset_lde, &mut challenger, &config);
|
||||
let proof = fri_proof(&[&tree], &coeffs, &coset_lde, &mut challenger, &config);
|
||||
|
||||
let mut challenger = Challenger::new();
|
||||
verify_fri_proof(
|
||||
|
||||
@ -10,7 +10,7 @@ use crate::util::reverse_index_bits_in_place;
|
||||
|
||||
/// Builds a FRI proof.
|
||||
pub fn fri_proof<F: Field>(
|
||||
initial_merkle_trees: &[MerkleTree<F>],
|
||||
initial_merkle_trees: &[&MerkleTree<F>],
|
||||
// Coefficients of the polynomial on which the LDT is performed. Only the first `1/rate` coefficients are non-zero.
|
||||
lde_polynomial_coeffs: &PolynomialCoeffs<F>,
|
||||
// Evaluation of the polynomial on the large domain.
|
||||
@ -113,7 +113,7 @@ fn fri_proof_of_work<F: Field>(current_hash: Hash<F>, config: &FriConfig) -> F {
|
||||
}
|
||||
|
||||
fn fri_prover_query_rounds<F: Field>(
|
||||
initial_merkle_trees: &[MerkleTree<F>],
|
||||
initial_merkle_trees: &[&MerkleTree<F>],
|
||||
trees: &[MerkleTree<F>],
|
||||
challenger: &mut Challenger<F>,
|
||||
n: usize,
|
||||
@ -125,7 +125,7 @@ fn fri_prover_query_rounds<F: Field>(
|
||||
}
|
||||
|
||||
fn fri_prover_query_round<F: Field>(
|
||||
initial_merkle_trees: &[MerkleTree<F>],
|
||||
initial_merkle_trees: &[&MerkleTree<F>],
|
||||
trees: &[MerkleTree<F>],
|
||||
challenger: &mut Challenger<F>,
|
||||
n: usize,
|
||||
|
||||
@ -4,6 +4,7 @@ use crate::fri::FriConfig;
|
||||
use crate::hash::hash_n_to_1;
|
||||
use crate::merkle_proofs::verify_merkle_proof;
|
||||
use crate::plonk_challenger::Challenger;
|
||||
use crate::polynomial::commitment::SALT_SIZE;
|
||||
use crate::polynomial::polynomial::PolynomialCoeffs;
|
||||
use crate::proof::{FriInitialTreeProof, FriProof, FriQueryRound, Hash};
|
||||
use crate::util::{log2_strict, reverse_bits, reverse_index_bits_in_place};
|
||||
@ -148,10 +149,9 @@ fn fri_combine_initial<F: Field>(
|
||||
let e = proof
|
||||
.evals_proofs
|
||||
.iter()
|
||||
.map(|(v, _)| v)
|
||||
.flatten()
|
||||
.enumerate()
|
||||
.flat_map(|(i, (v, _))| &v[..v.len() - if config.blinding[i] { SALT_SIZE } else { 0 }])
|
||||
.rev()
|
||||
.skip(if config.blinding { 2 } else { 0 }) // If blinding, the last two element are salt.
|
||||
.fold(F::ZERO, |acc, &e| alpha * acc + e);
|
||||
let numerator = e - interpolant.eval(subgroup_x);
|
||||
let denominator = points.iter().map(|&(x, _)| subgroup_x - x).product();
|
||||
|
||||
@ -6,33 +6,37 @@ use crate::plonk_challenger::Challenger;
|
||||
use crate::plonk_common::reduce_with_powers;
|
||||
use crate::polynomial::old_polynomial::Polynomial;
|
||||
use crate::polynomial::polynomial::PolynomialCoeffs;
|
||||
use crate::proof::{FriProof, Hash};
|
||||
use crate::proof::{FriProof, Hash, OpeningSet};
|
||||
use crate::util::{log2_strict, reverse_index_bits_in_place, transpose};
|
||||
use anyhow::Result;
|
||||
use rayon::prelude::*;
|
||||
|
||||
struct ListPolynomialCommitment<F: Field> {
|
||||
pub const SALT_SIZE: usize = 2;
|
||||
|
||||
pub struct ListPolynomialCommitment<F: Field> {
|
||||
pub polynomials: Vec<PolynomialCoeffs<F>>,
|
||||
pub fri_config: FriConfig,
|
||||
pub merkle_tree: MerkleTree<F>,
|
||||
pub degree: usize,
|
||||
pub rate_bits: usize,
|
||||
pub blinding: bool,
|
||||
}
|
||||
|
||||
impl<F: Field> ListPolynomialCommitment<F> {
|
||||
pub fn new(polynomials: Vec<PolynomialCoeffs<F>>, fri_config: &FriConfig) -> Self {
|
||||
pub fn new(polynomials: Vec<PolynomialCoeffs<F>>, rate_bits: usize, blinding: bool) -> Self {
|
||||
let degree = polynomials[0].len();
|
||||
let lde_values = polynomials
|
||||
.iter()
|
||||
.par_iter()
|
||||
.map(|p| {
|
||||
assert_eq!(p.len(), degree, "Polynomial degree invalid.");
|
||||
p.clone()
|
||||
.lde(fri_config.rate_bits)
|
||||
.lde(rate_bits)
|
||||
.coset_fft(F::MULTIPLICATIVE_GROUP_GENERATOR)
|
||||
.values
|
||||
})
|
||||
.chain(if fri_config.blinding {
|
||||
.chain(if blinding {
|
||||
// If blinding, salt with two random elements to each leaf vector.
|
||||
(0..2)
|
||||
.map(|_| F::rand_vec(degree << fri_config.rate_bits))
|
||||
(0..SALT_SIZE)
|
||||
.map(|_| F::rand_vec(degree << rate_bits))
|
||||
.collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
@ -45,17 +49,27 @@ impl<F: Field> ListPolynomialCommitment<F> {
|
||||
|
||||
Self {
|
||||
polynomials,
|
||||
fri_config: fri_config.clone(),
|
||||
merkle_tree,
|
||||
degree,
|
||||
rate_bits,
|
||||
blinding,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn leaf(&self, index: usize) -> &[F] {
|
||||
let leaf = &self.merkle_tree.leaves[index];
|
||||
&leaf[0..leaf.len() - if self.blinding { SALT_SIZE } else { 0 }]
|
||||
}
|
||||
|
||||
pub fn open(
|
||||
&self,
|
||||
points: &[F],
|
||||
challenger: &mut Challenger<F>,
|
||||
config: &FriConfig,
|
||||
) -> (OpeningProof<F>, Vec<Vec<F>>) {
|
||||
assert_eq!(self.rate_bits, config.rate_bits);
|
||||
assert_eq!(config.blinding.len(), 1);
|
||||
assert_eq!(self.blinding, config.blinding[0]);
|
||||
for p in points {
|
||||
assert_ne!(
|
||||
p.exp_usize(self.degree),
|
||||
@ -65,7 +79,7 @@ impl<F: Field> ListPolynomialCommitment<F> {
|
||||
}
|
||||
|
||||
let evaluations = points
|
||||
.iter()
|
||||
.par_iter()
|
||||
.map(|&x| {
|
||||
self.polynomials
|
||||
.iter()
|
||||
@ -88,23 +102,23 @@ impl<F: Field> ListPolynomialCommitment<F> {
|
||||
.fold(Polynomial::empty(), |acc, p| acc.scalar_mul(alpha).add(&p));
|
||||
// Scale evaluations by `alpha`.
|
||||
let composition_evals = evaluations
|
||||
.iter()
|
||||
.par_iter()
|
||||
.map(|e| reduce_with_powers(e, alpha))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let quotient = Self::compute_quotient(points, &composition_evals, &composition_poly);
|
||||
|
||||
let lde_quotient = PolynomialCoeffs::from(quotient.clone()).lde(self.fri_config.rate_bits);
|
||||
let lde_quotient = PolynomialCoeffs::from(quotient.clone()).lde(self.rate_bits);
|
||||
let lde_quotient_values = lde_quotient
|
||||
.clone()
|
||||
.coset_fft(F::MULTIPLICATIVE_GROUP_GENERATOR);
|
||||
|
||||
let fri_proof = fri_proof(
|
||||
&[self.merkle_tree.clone()],
|
||||
&[&self.merkle_tree],
|
||||
&lde_quotient,
|
||||
&lde_quotient_values,
|
||||
challenger,
|
||||
&self.fri_config,
|
||||
&config,
|
||||
);
|
||||
|
||||
(
|
||||
@ -116,6 +130,118 @@ impl<F: Field> ListPolynomialCommitment<F> {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn batch_open(
|
||||
commitments: &[&Self],
|
||||
points: &[F],
|
||||
challenger: &mut Challenger<F>,
|
||||
config: &FriConfig,
|
||||
) -> (OpeningProof<F>, Vec<Vec<Vec<F>>>) {
|
||||
let degree = commitments[0].degree;
|
||||
assert_eq!(config.blinding.len(), commitments.len());
|
||||
for (i, commitment) in commitments.iter().enumerate() {
|
||||
assert_eq!(commitment.rate_bits, config.rate_bits, "Invalid rate.");
|
||||
assert_eq!(
|
||||
commitment.blinding, config.blinding[i],
|
||||
"Invalid blinding paramater."
|
||||
);
|
||||
assert_eq!(
|
||||
commitment.degree, degree,
|
||||
"Trying to open polynomial commitments of different degrees."
|
||||
);
|
||||
}
|
||||
for p in points {
|
||||
assert_ne!(
|
||||
p.exp_usize(degree),
|
||||
F::ONE,
|
||||
"Opening point is in the subgroup."
|
||||
);
|
||||
}
|
||||
|
||||
let evaluations = points
|
||||
.par_iter()
|
||||
.map(|&x| {
|
||||
commitments
|
||||
.iter()
|
||||
.map(move |c| c.polynomials.iter().map(|p| p.eval(x)).collect::<Vec<_>>())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
for evals_per_point in &evaluations {
|
||||
for evals in evals_per_point {
|
||||
challenger.observe_elements(evals);
|
||||
}
|
||||
}
|
||||
|
||||
let alpha = challenger.get_challenge();
|
||||
|
||||
// Scale polynomials by `alpha`.
|
||||
let composition_poly = commitments
|
||||
.iter()
|
||||
.flat_map(|c| &c.polynomials)
|
||||
.rev()
|
||||
.map(|p| p.clone().into())
|
||||
.fold(Polynomial::empty(), |acc, p| acc.scalar_mul(alpha).add(&p));
|
||||
// Scale evaluations by `alpha`.
|
||||
let composition_evals = &evaluations
|
||||
.par_iter()
|
||||
.map(|v| {
|
||||
v.iter()
|
||||
.flatten()
|
||||
.rev()
|
||||
.fold(F::ZERO, |acc, &e| acc * alpha + e)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let quotient = Self::compute_quotient(points, &composition_evals, &composition_poly);
|
||||
|
||||
let lde_quotient = PolynomialCoeffs::from(quotient.clone()).lde(config.rate_bits);
|
||||
let lde_quotient_values = lde_quotient
|
||||
.clone()
|
||||
.coset_fft(F::MULTIPLICATIVE_GROUP_GENERATOR);
|
||||
|
||||
let fri_proof = fri_proof(
|
||||
&commitments
|
||||
.par_iter()
|
||||
.map(|c| &c.merkle_tree)
|
||||
.collect::<Vec<_>>(),
|
||||
&lde_quotient,
|
||||
&lde_quotient_values,
|
||||
challenger,
|
||||
&config,
|
||||
);
|
||||
|
||||
(
|
||||
OpeningProof {
|
||||
fri_proof,
|
||||
quotient_degree: quotient.len(),
|
||||
},
|
||||
evaluations,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn batch_open_plonk(
|
||||
commitments: &[&Self; 5],
|
||||
points: &[F],
|
||||
challenger: &mut Challenger<F>,
|
||||
config: &FriConfig,
|
||||
) -> (OpeningProof<F>, Vec<OpeningSet<F>>) {
|
||||
let (op, mut evaluations) = Self::batch_open(commitments, points, challenger, config);
|
||||
let opening_sets = evaluations
|
||||
.par_iter_mut()
|
||||
.map(|evals| {
|
||||
evals.reverse();
|
||||
OpeningSet {
|
||||
constants: evals.pop().unwrap(),
|
||||
plonk_sigmas: evals.pop().unwrap(),
|
||||
wires: evals.pop().unwrap(),
|
||||
plonk_zs: evals.pop().unwrap(),
|
||||
quotient_polys: evals.pop().unwrap(),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
(op, opening_sets)
|
||||
}
|
||||
|
||||
/// Given `points=(x_i)`, `evals=(y_i)` and `poly=P` with `P(x_i)=y_i`, computes the polynomial
|
||||
/// `Q=(P-I)/Z` where `I` interpolates `(x_i, y_i)` and `Z` is the vanishing polynomial on `(x_i)`.
|
||||
fn compute_quotient(points: &[F], evals: &[F], poly: &Polynomial<F>) -> Polynomial<F> {
|
||||
@ -151,20 +277,27 @@ impl<F: Field> OpeningProof<F> {
|
||||
pub fn verify(
|
||||
&self,
|
||||
points: &[F],
|
||||
evaluations: &[Vec<F>],
|
||||
merkle_root: Hash<F>,
|
||||
evaluations: &[Vec<Vec<F>>],
|
||||
merkle_roots: &[Hash<F>],
|
||||
challenger: &mut Challenger<F>,
|
||||
fri_config: &FriConfig,
|
||||
) -> Result<()> {
|
||||
for evals in evaluations {
|
||||
challenger.observe_elements(evals);
|
||||
for evals_per_point in evaluations {
|
||||
for evals in evals_per_point {
|
||||
challenger.observe_elements(evals);
|
||||
}
|
||||
}
|
||||
|
||||
let alpha = challenger.get_challenge();
|
||||
|
||||
let scaled_evals = evaluations
|
||||
.iter()
|
||||
.map(|e| reduce_with_powers(e, alpha))
|
||||
.par_iter()
|
||||
.map(|v| {
|
||||
v.iter()
|
||||
.flatten()
|
||||
.rev()
|
||||
.fold(F::ZERO, |acc, &e| acc * alpha + e)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let pairs = points
|
||||
@ -177,7 +310,7 @@ impl<F: Field> OpeningProof<F> {
|
||||
log2_strict(self.quotient_degree),
|
||||
&pairs,
|
||||
alpha,
|
||||
&[merkle_root],
|
||||
merkle_roots,
|
||||
&self.fri_proof,
|
||||
challenger,
|
||||
fri_config,
|
||||
@ -221,16 +354,16 @@ mod tests {
|
||||
rate_bits: 2,
|
||||
reduction_arity_bits: vec![3, 2, 1, 2],
|
||||
num_query_rounds: 3,
|
||||
blinding: false,
|
||||
blinding: vec![false],
|
||||
};
|
||||
let (polys, points) = gen_random_test_case::<F>(k, degree_log, num_points);
|
||||
|
||||
let lpc = ListPolynomialCommitment::new(polys, &fri_config);
|
||||
let (proof, evaluations) = lpc.open(&points, &mut Challenger::new());
|
||||
let lpc = ListPolynomialCommitment::new(polys, fri_config.rate_bits, false);
|
||||
let (proof, evaluations) = lpc.open(&points, &mut Challenger::new(), &fri_config);
|
||||
proof.verify(
|
||||
&points,
|
||||
&evaluations,
|
||||
lpc.merkle_tree.root,
|
||||
&evaluations.into_iter().map(|e| vec![e]).collect::<Vec<_>>(),
|
||||
&[lpc.merkle_tree.root],
|
||||
&mut Challenger::new(),
|
||||
&fri_config,
|
||||
)
|
||||
@ -248,16 +381,102 @@ mod tests {
|
||||
rate_bits: 2,
|
||||
reduction_arity_bits: vec![3, 2, 1, 2],
|
||||
num_query_rounds: 3,
|
||||
blinding: true,
|
||||
blinding: vec![true],
|
||||
};
|
||||
let (polys, points) = gen_random_test_case::<F>(k, degree_log, num_points);
|
||||
|
||||
let lpc = ListPolynomialCommitment::new(polys, &fri_config);
|
||||
let (proof, evaluations) = lpc.open(&points, &mut Challenger::new());
|
||||
let lpc = ListPolynomialCommitment::new(polys, fri_config.rate_bits, true);
|
||||
let (proof, evaluations) = lpc.open(&points, &mut Challenger::new(), &fri_config);
|
||||
proof.verify(
|
||||
&points,
|
||||
&evaluations.into_iter().map(|e| vec![e]).collect::<Vec<_>>(),
|
||||
&[lpc.merkle_tree.root],
|
||||
&mut Challenger::new(),
|
||||
&fri_config,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_batch_polynomial_commitment() -> Result<()> {
|
||||
type F = CrandallField;
|
||||
|
||||
let k0 = 10;
|
||||
let k1 = 3;
|
||||
let k2 = 7;
|
||||
let degree_log = 11;
|
||||
let num_points = 5;
|
||||
let fri_config = FriConfig {
|
||||
proof_of_work_bits: 2,
|
||||
rate_bits: 2,
|
||||
reduction_arity_bits: vec![2, 3, 1, 2],
|
||||
num_query_rounds: 3,
|
||||
blinding: vec![false, false, false],
|
||||
};
|
||||
let (polys0, _) = gen_random_test_case::<F>(k0, degree_log, num_points);
|
||||
let (polys1, _) = gen_random_test_case::<F>(k0, degree_log, num_points);
|
||||
let (polys2, points) = gen_random_test_case::<F>(k0, degree_log, num_points);
|
||||
|
||||
let lpc0 = ListPolynomialCommitment::new(polys0, fri_config.rate_bits, false);
|
||||
let lpc1 = ListPolynomialCommitment::new(polys1, fri_config.rate_bits, false);
|
||||
let lpc2 = ListPolynomialCommitment::new(polys2, fri_config.rate_bits, false);
|
||||
|
||||
let (proof, evaluations) = ListPolynomialCommitment::batch_open(
|
||||
&[&lpc0, &lpc1, &lpc2],
|
||||
&points,
|
||||
&mut Challenger::new(),
|
||||
&fri_config,
|
||||
);
|
||||
proof.verify(
|
||||
&points,
|
||||
&evaluations,
|
||||
lpc.merkle_tree.root,
|
||||
&[
|
||||
lpc0.merkle_tree.root,
|
||||
lpc1.merkle_tree.root,
|
||||
lpc2.merkle_tree.root,
|
||||
],
|
||||
&mut Challenger::new(),
|
||||
&fri_config,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_batch_polynomial_commitment_blinding() -> Result<()> {
|
||||
type F = CrandallField;
|
||||
|
||||
let k0 = 10;
|
||||
let k1 = 3;
|
||||
let k2 = 7;
|
||||
let degree_log = 11;
|
||||
let num_points = 5;
|
||||
let fri_config = FriConfig {
|
||||
proof_of_work_bits: 2,
|
||||
rate_bits: 2,
|
||||
reduction_arity_bits: vec![2, 3, 1, 2],
|
||||
num_query_rounds: 3,
|
||||
blinding: vec![true, false, true],
|
||||
};
|
||||
let (polys0, _) = gen_random_test_case::<F>(k0, degree_log, num_points);
|
||||
let (polys1, _) = gen_random_test_case::<F>(k0, degree_log, num_points);
|
||||
let (polys2, points) = gen_random_test_case::<F>(k0, degree_log, num_points);
|
||||
|
||||
let lpc0 = ListPolynomialCommitment::new(polys0, fri_config.rate_bits, true);
|
||||
let lpc1 = ListPolynomialCommitment::new(polys1, fri_config.rate_bits, false);
|
||||
let lpc2 = ListPolynomialCommitment::new(polys2, fri_config.rate_bits, true);
|
||||
|
||||
let (proof, evaluations) = ListPolynomialCommitment::batch_open(
|
||||
&[&lpc0, &lpc1, &lpc2],
|
||||
&points,
|
||||
&mut Challenger::new(),
|
||||
&fri_config,
|
||||
);
|
||||
proof.verify(
|
||||
&points,
|
||||
&evaluations,
|
||||
&[
|
||||
lpc0.merkle_tree.root,
|
||||
lpc1.merkle_tree.root,
|
||||
lpc2.merkle_tree.root,
|
||||
],
|
||||
&mut Challenger::new(),
|
||||
&fri_config,
|
||||
)
|
||||
|
||||
@ -26,6 +26,9 @@ impl<F: Field> PolynomialValues<F> {
|
||||
self.values.len()
|
||||
}
|
||||
|
||||
pub fn ifft(self) -> PolynomialCoeffs<F> {
|
||||
ifft(self)
|
||||
}
|
||||
pub fn lde_multiple(polys: Vec<Self>, rate_bits: usize) -> Vec<Self> {
|
||||
polys.into_iter().map(|p| p.lde(rate_bits)).collect()
|
||||
}
|
||||
|
||||
25
src/proof.rs
25
src/proof.rs
@ -1,5 +1,6 @@
|
||||
use crate::field::field::Field;
|
||||
use crate::merkle_proofs::{MerkleProof, MerkleProofTarget};
|
||||
use crate::polynomial::commitment::{ListPolynomialCommitment, OpeningProof};
|
||||
use crate::polynomial::polynomial::PolynomialCoeffs;
|
||||
use crate::target::Target;
|
||||
use std::convert::TryInto;
|
||||
@ -65,7 +66,7 @@ pub struct Proof<F: Field> {
|
||||
pub openings: Vec<OpeningSet<F>>,
|
||||
|
||||
/// A FRI argument for each FRI query.
|
||||
pub fri_proofs: Vec<FriProof<F>>,
|
||||
pub opening_proof: OpeningProof<F>,
|
||||
}
|
||||
|
||||
pub struct ProofTarget {
|
||||
@ -136,6 +137,28 @@ pub struct OpeningSet<F: Field> {
|
||||
pub quotient_polys: Vec<F>,
|
||||
}
|
||||
|
||||
impl<F: Field> OpeningSet<F> {
|
||||
pub fn new(
|
||||
z: F,
|
||||
constant_commitment: &ListPolynomialCommitment<F>,
|
||||
plonk_sigmas_commitment: &ListPolynomialCommitment<F>,
|
||||
wires_commitment: &ListPolynomialCommitment<F>,
|
||||
plonk_zs_commitment: &ListPolynomialCommitment<F>,
|
||||
quotient_polys_commitment: &ListPolynomialCommitment<F>,
|
||||
) -> Self {
|
||||
let eval_commitment = |z: F, c: &ListPolynomialCommitment<F>| {
|
||||
c.polynomials.iter().map(|p| p.eval(z)).collect::<Vec<_>>()
|
||||
};
|
||||
Self {
|
||||
constants: eval_commitment(z, constant_commitment),
|
||||
plonk_sigmas: eval_commitment(z, plonk_sigmas_commitment),
|
||||
wires: eval_commitment(z, wires_commitment),
|
||||
plonk_zs: eval_commitment(z, plonk_zs_commitment),
|
||||
quotient_polys: eval_commitment(z, quotient_polys_commitment),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The purported values of each polynomial at a single point.
|
||||
pub struct OpeningSetTarget {
|
||||
pub constants: Vec<Target>,
|
||||
|
||||
196
src/prover.rs
196
src/prover.rs
@ -4,67 +4,67 @@ use log::info;
|
||||
use rayon::prelude::*;
|
||||
|
||||
use crate::circuit_data::{CommonCircuitData, ProverOnlyCircuitData};
|
||||
use crate::field::fft::{fft, ifft};
|
||||
use crate::field::fft::ifft;
|
||||
use crate::field::field::Field;
|
||||
use crate::generator::generate_partial_witness;
|
||||
use crate::merkle_tree::MerkleTree;
|
||||
use crate::plonk_challenger::Challenger;
|
||||
use crate::plonk_common::{eval_l_1, evaluate_gate_constraints, reduce_with_powers_multi};
|
||||
use crate::polynomial::commitment::ListPolynomialCommitment;
|
||||
use crate::polynomial::division::divide_by_z_h;
|
||||
use crate::polynomial::polynomial::{PolynomialCoeffs, PolynomialValues};
|
||||
use crate::proof::Proof;
|
||||
use crate::util::{transpose, transpose_poly_values};
|
||||
use crate::util::transpose;
|
||||
use crate::vars::EvaluationVars;
|
||||
use crate::wire::Wire;
|
||||
use crate::witness::PartialWitness;
|
||||
|
||||
macro_rules! timed {
|
||||
($a:expr, $msg:expr) => {{
|
||||
let timer = Instant::now();
|
||||
let res = $a;
|
||||
info!("{:.3}s {}", timer.elapsed().as_secs_f32(), $msg);
|
||||
res
|
||||
}};
|
||||
}
|
||||
|
||||
/// Corresponds to constants - sigmas - wires - zs - quotient — polynomial commitments.
|
||||
pub const PLONK_BLINDING: [bool; 5] = [false, false, true, true, true];
|
||||
|
||||
pub(crate) fn prove<F: Field>(
|
||||
prover_data: &ProverOnlyCircuitData<F>,
|
||||
common_data: &CommonCircuitData<F>,
|
||||
inputs: PartialWitness<F>,
|
||||
) -> Proof<F> {
|
||||
let fri_config = &common_data.config.fri_config;
|
||||
|
||||
let start_proof_gen = Instant::now();
|
||||
|
||||
let start_witness = Instant::now();
|
||||
let mut witness = inputs;
|
||||
info!("Running {} generators", prover_data.generators.len());
|
||||
generate_partial_witness(&mut witness, &prover_data.generators);
|
||||
info!(
|
||||
"{:.3}s to generate witness",
|
||||
start_witness.elapsed().as_secs_f32()
|
||||
timed!(
|
||||
generate_partial_witness(&mut witness, &prover_data.generators),
|
||||
"to generate witness"
|
||||
);
|
||||
|
||||
let config = common_data.config;
|
||||
let config = &common_data.config;
|
||||
let num_wires = config.num_wires;
|
||||
let num_checks = config.num_checks;
|
||||
let quotient_degree = common_data.quotient_degree();
|
||||
|
||||
let start_wire_ldes = Instant::now();
|
||||
let degree = common_data.degree();
|
||||
let wire_ldes = (0..num_wires)
|
||||
.into_par_iter()
|
||||
.map(|i| compute_wire_lde(i, &witness, degree, config.rate_bits))
|
||||
.collect::<Vec<_>>();
|
||||
info!(
|
||||
"{:.3}s to compute wire LDEs",
|
||||
start_wire_ldes.elapsed().as_secs_f32()
|
||||
let wires_polynomials: Vec<PolynomialCoeffs<F>> = timed!(
|
||||
(0..num_wires)
|
||||
.into_par_iter()
|
||||
.map(|i| compute_wire_polynomial(i, &witness, degree))
|
||||
.collect(),
|
||||
"to compute wire polynomials"
|
||||
);
|
||||
|
||||
// TODO: Could try parallelizing the transpose, or not doing it explicitly, instead having
|
||||
// merkle_root_bit_rev_order do it implicitly.
|
||||
let start_wire_transpose = Instant::now();
|
||||
let wire_ldes_t = transpose_poly_values(wire_ldes);
|
||||
info!(
|
||||
"{:.3}s to transpose wire LDEs",
|
||||
start_wire_transpose.elapsed().as_secs_f32()
|
||||
);
|
||||
|
||||
// TODO: Could avoid cloning if it's significant?
|
||||
let start_wires_root = Instant::now();
|
||||
let wires_tree = MerkleTree::new(wire_ldes_t, true);
|
||||
info!(
|
||||
"{:.3}s to Merklize wire LDEs",
|
||||
start_wires_root.elapsed().as_secs_f32()
|
||||
let wires_commitment = timed!(
|
||||
ListPolynomialCommitment::new(wires_polynomials, fri_config.rate_bits, true),
|
||||
"to compute wires commitment"
|
||||
);
|
||||
|
||||
let mut challenger = Challenger::new();
|
||||
@ -72,70 +72,71 @@ pub(crate) fn prove<F: Field>(
|
||||
// TODO: Need to include public inputs as well.
|
||||
challenger.observe_hash(&common_data.circuit_digest);
|
||||
|
||||
challenger.observe_hash(&wires_tree.root);
|
||||
challenger.observe_hash(&wires_commitment.merkle_tree.root);
|
||||
let betas = challenger.get_n_challenges(num_checks);
|
||||
let gammas = challenger.get_n_challenges(num_checks);
|
||||
|
||||
let start_plonk_z = Instant::now();
|
||||
let plonk_z_vecs = compute_zs(&common_data);
|
||||
let plonk_z_ldes = PolynomialValues::lde_multiple(plonk_z_vecs, config.rate_bits);
|
||||
let plonk_z_ldes_t = transpose_poly_values(plonk_z_ldes);
|
||||
info!(
|
||||
"{:.3}s to compute Z's and their LDEs",
|
||||
start_plonk_z.elapsed().as_secs_f32()
|
||||
let plonk_z_vecs = timed!(compute_zs(&common_data), "to compute Z's");
|
||||
|
||||
let plonk_zs_commitment = timed!(
|
||||
ListPolynomialCommitment::new(plonk_z_vecs, fri_config.rate_bits, true),
|
||||
"to commit to Z's"
|
||||
);
|
||||
|
||||
let start_plonk_z_root = Instant::now();
|
||||
let plonk_zs_tree = MerkleTree::new(plonk_z_ldes_t, true);
|
||||
info!(
|
||||
"{:.3}s to Merklize Z's",
|
||||
start_plonk_z_root.elapsed().as_secs_f32()
|
||||
);
|
||||
|
||||
challenger.observe_hash(&plonk_zs_tree.root);
|
||||
challenger.observe_hash(&plonk_zs_commitment.merkle_tree.root);
|
||||
|
||||
let alphas = challenger.get_n_challenges(num_checks);
|
||||
|
||||
let start_vanishing_polys = Instant::now();
|
||||
let vanishing_polys = compute_vanishing_polys(
|
||||
common_data,
|
||||
prover_data,
|
||||
&wires_tree,
|
||||
&plonk_zs_tree,
|
||||
&betas,
|
||||
&gammas,
|
||||
&alphas,
|
||||
);
|
||||
info!(
|
||||
"{:.3}s to compute vanishing polys",
|
||||
start_vanishing_polys.elapsed().as_secs_f32()
|
||||
let vanishing_polys = timed!(
|
||||
compute_vanishing_polys(
|
||||
common_data,
|
||||
prover_data,
|
||||
&wires_commitment,
|
||||
&plonk_zs_commitment,
|
||||
&betas,
|
||||
&gammas,
|
||||
&alphas,
|
||||
),
|
||||
"to compute vanishing polys"
|
||||
);
|
||||
|
||||
// Compute the quotient polynomials, aka `t` in the Plonk paper.
|
||||
let quotient_polys_start = Instant::now();
|
||||
let mut all_quotient_poly_chunk_ldes = Vec::with_capacity(num_checks * quotient_degree);
|
||||
for vanishing_poly in vanishing_polys.into_iter() {
|
||||
let vanishing_poly_coeff = ifft(vanishing_poly);
|
||||
let quotient_poly_coeff = divide_by_z_h(vanishing_poly_coeff, degree);
|
||||
// Split t into degree-n chunks.
|
||||
let quotient_poly_coeff_chunks = quotient_poly_coeff.chunks(degree);
|
||||
let quotient_poly_coeff_ldes =
|
||||
PolynomialCoeffs::lde_multiple(quotient_poly_coeff_chunks, config.rate_bits);
|
||||
let quotient_poly_chunk_ldes: Vec<PolynomialValues<F>> =
|
||||
quotient_poly_coeff_ldes.into_par_iter().map(fft).collect();
|
||||
all_quotient_poly_chunk_ldes.extend(quotient_poly_chunk_ldes);
|
||||
}
|
||||
let quotient_polys_tree =
|
||||
MerkleTree::new(transpose_poly_values(all_quotient_poly_chunk_ldes), true);
|
||||
challenger.observe_hash("ient_polys_tree.root);
|
||||
info!(
|
||||
"{:.3}s to compute quotient polys and their LDEs",
|
||||
quotient_polys_start.elapsed().as_secs_f32()
|
||||
let quotient_polys_commitment = timed!(
|
||||
{
|
||||
let mut all_quotient_poly_chunks = Vec::with_capacity(num_checks * quotient_degree);
|
||||
for vanishing_poly in vanishing_polys.into_iter() {
|
||||
let vanishing_poly_coeff = ifft(vanishing_poly);
|
||||
let quotient_poly_coeff = divide_by_z_h(vanishing_poly_coeff, degree);
|
||||
// Split t into degree-n chunks.
|
||||
let quotient_poly_coeff_chunks = quotient_poly_coeff.chunks(degree);
|
||||
all_quotient_poly_chunks.extend(quotient_poly_coeff_chunks);
|
||||
}
|
||||
ListPolynomialCommitment::new(all_quotient_poly_chunks, fri_config.rate_bits, true)
|
||||
},
|
||||
"to compute quotient polys and commit to them"
|
||||
);
|
||||
|
||||
let openings = Vec::new(); // TODO
|
||||
challenger.observe_hash("ient_polys_commitment.merkle_tree.root);
|
||||
|
||||
let fri_proofs = Vec::new(); // TODO
|
||||
// TODO: How many do we need?
|
||||
let num_zetas = 2;
|
||||
let zetas = challenger.get_n_challenges(num_zetas);
|
||||
|
||||
let (opening_proof, openings) = timed!(
|
||||
ListPolynomialCommitment::batch_open_plonk(
|
||||
&[
|
||||
&prover_data.constants_commitment,
|
||||
&prover_data.sigmas_commitment,
|
||||
&wires_commitment,
|
||||
&plonk_zs_commitment,
|
||||
"ient_polys_commitment,
|
||||
],
|
||||
&zetas,
|
||||
&mut challenger,
|
||||
&common_data.config.fri_config
|
||||
),
|
||||
"to compute opening proofs"
|
||||
);
|
||||
|
||||
info!(
|
||||
"{:.3}s for overall witness & proof generation",
|
||||
@ -143,30 +144,30 @@ pub(crate) fn prove<F: Field>(
|
||||
);
|
||||
|
||||
Proof {
|
||||
wires_root: wires_tree.root,
|
||||
plonk_zs_root: plonk_zs_tree.root,
|
||||
quotient_polys_root: quotient_polys_tree.root,
|
||||
wires_root: wires_commitment.merkle_tree.root,
|
||||
plonk_zs_root: plonk_zs_commitment.merkle_tree.root,
|
||||
quotient_polys_root: quotient_polys_commitment.merkle_tree.root,
|
||||
openings,
|
||||
fri_proofs,
|
||||
opening_proof,
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_zs<F: Field>(common_data: &CommonCircuitData<F>) -> Vec<PolynomialValues<F>> {
|
||||
fn compute_zs<F: Field>(common_data: &CommonCircuitData<F>) -> Vec<PolynomialCoeffs<F>> {
|
||||
(0..common_data.config.num_checks)
|
||||
.map(|i| compute_z(common_data, i))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn compute_z<F: Field>(common_data: &CommonCircuitData<F>, i: usize) -> PolynomialValues<F> {
|
||||
PolynomialValues::zero(common_data.degree()) // TODO
|
||||
fn compute_z<F: Field>(common_data: &CommonCircuitData<F>, _i: usize) -> PolynomialCoeffs<F> {
|
||||
PolynomialCoeffs::zero(common_data.degree()) // TODO
|
||||
}
|
||||
|
||||
// TODO: Parallelize.
|
||||
fn compute_vanishing_polys<F: Field>(
|
||||
common_data: &CommonCircuitData<F>,
|
||||
prover_data: &ProverOnlyCircuitData<F>,
|
||||
wires_tree: &MerkleTree<F>,
|
||||
plonk_zs_tree: &MerkleTree<F>,
|
||||
wires_commitment: &ListPolynomialCommitment<F>,
|
||||
plonk_zs_commitment: &ListPolynomialCommitment<F>,
|
||||
betas: &[F],
|
||||
gammas: &[F],
|
||||
alphas: &[F],
|
||||
@ -181,11 +182,11 @@ fn compute_vanishing_polys<F: Field>(
|
||||
.enumerate()
|
||||
.map(|(i, x)| {
|
||||
let i_next = (i + 1) % lde_size;
|
||||
let local_wires = &wires_tree.leaves[i];
|
||||
let local_constants = &prover_data.constants_tree.leaves[i];
|
||||
let local_plonk_zs = &plonk_zs_tree.leaves[i];
|
||||
let next_plonk_zs = &plonk_zs_tree.leaves[i_next];
|
||||
let s_sigmas = &prover_data.sigmas_tree.leaves[i];
|
||||
let local_wires = wires_commitment.leaf(i);
|
||||
let local_constants = prover_data.constants_commitment.leaf(i);
|
||||
let local_plonk_zs = plonk_zs_commitment.leaf(i);
|
||||
let next_plonk_zs = plonk_zs_commitment.leaf(i_next);
|
||||
let s_sigmas = prover_data.sigmas_commitment.leaf(i);
|
||||
|
||||
debug_assert_eq!(local_wires.len(), common_data.config.num_wires);
|
||||
debug_assert_eq!(local_plonk_zs.len(), num_checks);
|
||||
@ -264,12 +265,11 @@ fn compute_vanishing_poly_entry<F: Field>(
|
||||
reduce_with_powers_multi(&vanishing_terms, alphas)
|
||||
}
|
||||
|
||||
fn compute_wire_lde<F: Field>(
|
||||
fn compute_wire_polynomial<F: Field>(
|
||||
input: usize,
|
||||
witness: &PartialWitness<F>,
|
||||
degree: usize,
|
||||
rate_bits: usize,
|
||||
) -> PolynomialValues<F> {
|
||||
) -> PolynomialCoeffs<F> {
|
||||
let wire_values = (0..degree)
|
||||
// Some gates do not use all wires, and we do not require that generators populate unused
|
||||
// wires, so some wire values will not be set. We can set these to any value; here we
|
||||
@ -281,5 +281,5 @@ fn compute_wire_lde<F: Field>(
|
||||
.unwrap_or(F::ZERO)
|
||||
})
|
||||
.collect();
|
||||
PolynomialValues::new(wire_values).lde(rate_bits)
|
||||
PolynomialValues::new(wire_values).ifft()
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@ impl Target {
|
||||
Self::Wire(Wire { gate, input })
|
||||
}
|
||||
|
||||
pub fn is_routable(&self, config: CircuitConfig) -> bool {
|
||||
pub fn is_routable(&self, config: &CircuitConfig) -> bool {
|
||||
match self {
|
||||
Target::Wire(wire) => wire.is_routable(config),
|
||||
Target::PublicInput { .. } => true,
|
||||
|
||||
@ -10,7 +10,7 @@ pub struct Wire {
|
||||
}
|
||||
|
||||
impl Wire {
|
||||
pub fn is_routable(&self, config: CircuitConfig) -> bool {
|
||||
pub fn is_routable(&self, config: &CircuitConfig) -> bool {
|
||||
self.input < config.num_routed_wires
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user