initial integration of hyperplonk snark(#39)

This commit is contained in:
zhenfei
2022-08-01 13:16:55 -04:00
committed by GitHub
parent 229148eb5a
commit a6ea6ac26b
38 changed files with 1946 additions and 444 deletions

View File

@@ -6,3 +6,39 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
poly-iop = { path = "../poly-iop" }
pcs = { path = "../pcs" }
ark-std = { version = "^0.3.0", default-features = false }
ark-ec = { version = "^0.3.0", default-features = false }
ark-ff = { version = "^0.3.0", default-features = false }
ark-poly = { version = "^0.3.0", default-features = false }
ark-serialize = { version = "^0.3.0", default-features = false, features = [ "derive" ] }
displaydoc = { version = "0.2.3", default-features = false }
transcript = { path = "../transcript" }
arithmetic = { path = "../arithmetic" }
[dev-dependencies]
ark-bls12-381 = { version = "0.3.0", default-features = false, features = [ "curve" ] }
[features]
default = [ "parallel", "print-trace" ]
# default = [ "parallel" ]
parallel = [
"ark-std/parallel",
"ark-ff/parallel",
"ark-poly/parallel",
"ark-ec/parallel",
"poly-iop/parallel",
"pcs/parallel",
"arithmetic/parallel",
]
print-trace = [
"ark-std/print-trace",
"poly-iop/print-trace",
"pcs/print-trace",
"arithmetic/print-trace",
]

64
hyperplonk/src/errors.rs Normal file
View File

@@ -0,0 +1,64 @@
//! Error module.
use arithmetic::ArithErrors;
use ark_serialize::SerializationError;
use ark_std::string::String;
use displaydoc::Display;
use pcs::prelude::PCSErrors;
use poly_iop::prelude::PolyIOPErrors;
use transcript::TranscriptErrors;
/// A `enum` specifying the possible failure modes of hyperplonk.
#[allow(dead_code)]
// todo: REMOVE
#[derive(Display, Debug)]
pub enum HyperPlonkErrors {
/// Invalid Prover: {0}
InvalidProver(String),
/// Invalid Verifier: {0}
InvalidVerifier(String),
/// Invalid Proof: {0}
InvalidProof(String),
/// Invalid parameters: {0}
InvalidParameters(String),
/// An error during (de)serialization: {0}
SerializationError(SerializationError),
/// PolyIOP error {0}
PolyIOPErrors(PolyIOPErrors),
/// PCS error {0}
PCSErrors(PCSErrors),
/// Transcript error {0}
TranscriptError(TranscriptErrors),
/// Arithmetic Error: {0}
ArithmeticErrors(ArithErrors),
}
impl From<SerializationError> for HyperPlonkErrors {
fn from(e: ark_serialize::SerializationError) -> Self {
Self::SerializationError(e)
}
}
impl From<PolyIOPErrors> for HyperPlonkErrors {
fn from(e: PolyIOPErrors) -> Self {
Self::PolyIOPErrors(e)
}
}
impl From<PCSErrors> for HyperPlonkErrors {
fn from(e: PCSErrors) -> Self {
Self::PCSErrors(e)
}
}
impl From<TranscriptErrors> for HyperPlonkErrors {
fn from(e: TranscriptErrors) -> Self {
Self::TranscriptError(e)
}
}
impl From<ArithErrors> for HyperPlonkErrors {
fn from(e: ArithErrors) -> Self {
Self::ArithmeticErrors(e)
}
}

View File

@@ -1,8 +1,851 @@
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
let result = 2 + 2;
assert_eq!(result, 4);
//! Main module for the HyperPlonk PolyIOP.
use arithmetic::VPAuxInfo;
use ark_ec::PairingEngine;
use ark_poly::{DenseMultilinearExtension, MultilinearExtension};
use ark_std::{end_timer, log2, start_timer, One, Zero};
use errors::HyperPlonkErrors;
use pcs::prelude::{compute_qx_degree, merge_polynomials, PCSErrors, PolynomialCommitmentScheme};
use poly_iop::{
prelude::{PermutationCheck, SumCheck, ZeroCheck},
PolyIOP,
};
use selectors::SelectorColumn;
use std::{marker::PhantomData, rc::Rc};
use structs::{
HyperPlonkParams, HyperPlonkProof, HyperPlonkProvingKey, HyperPlonkSubClaim,
HyperPlonkVerifyingKey,
};
use utils::build_f;
use witness::WitnessColumn;
mod errors;
mod selectors;
mod structs;
mod utils;
mod witness;
/// A trait for HyperPlonk Poly-IOPs.
/// A HyperPlonk is derived from SumChecks, ZeroChecks and PermutationChecks.
pub trait HyperPlonkSNARK<E, PCS>:
SumCheck<E::Fr> + ZeroCheck<E::Fr> + PermutationCheck<E::Fr>
where
E: PairingEngine,
PCS: PolynomialCommitmentScheme<E>,
{
type Parameters;
type ProvingKey;
type VerifyingKey;
type Proof;
type SubClaim;
/// Generate the preprocessed polynomials output by the indexer.
///
/// Inputs:
/// - `params`: HyperPlonk instance parameters
/// - `permutation`: the permutation for the copy constraints
/// - `selectors`: the list of selector vectors for custom gates
/// Outputs:
/// - The HyperPlonk proving key, which includes the preprocessed
/// polynomials.
fn preprocess(
params: &Self::Parameters,
pcs_srs: &PCS::SRS,
permutation: &[E::Fr],
selectors: &[SelectorColumn<E::Fr>],
) -> Result<(Self::ProvingKey, Self::VerifyingKey), HyperPlonkErrors>;
/// Generate HyperPlonk SNARK proof.
///
/// Inputs:
/// - `pk`: circuit proving key
/// - `pub_input`: online public input
/// - `witness`: witness assignment
/// - `transcript`: the transcript used for generating pseudorandom
/// challenges
/// Outputs:
/// - The HyperPlonk SNARK proof.
fn prove(
pk: &Self::ProvingKey,
pub_input: &[E::Fr],
witnesses: &[WitnessColumn<E::Fr>],
transcript: &mut Self::Transcript,
) -> Result<Self::Proof, HyperPlonkErrors>;
/// Verify the HyperPlonk proof and generate the evaluation subclaims to be
/// checked later by the SNARK verifier.
///
/// Inputs:
/// - `params`: instance parameter
/// - `pub_input`: online public input
/// - `proof`: HyperPlonk SNARK proof
/// - `transcript`: the transcript used for generating pseudorandom
/// challenges
/// Outputs:
/// - Return error if the verification fails, otherwise return the
/// evaluation subclaim
fn verify(
params: &Self::VerifyingKey,
pub_input: &[E::Fr],
proof: &Self::Proof,
transcript: &mut Self::Transcript,
) -> Result<Self::SubClaim, HyperPlonkErrors>;
}
impl<E, PCS> HyperPlonkSNARK<E, PCS> for PolyIOP<E::Fr>
where
E: PairingEngine,
// Ideally we want to access polynomial as PCS::Polynomial, instead of instantiating it here.
// But since PCS::Polynomial can be both univariate or multivariate in our implementation
// we cannot bound PCS::Polynomial with a property trait bound.
PCS: PolynomialCommitmentScheme<
E,
Polynomial = Rc<DenseMultilinearExtension<E::Fr>>,
Point = Vec<E::Fr>,
Evaluation = E::Fr,
>,
{
type Parameters = HyperPlonkParams;
type ProvingKey = HyperPlonkProvingKey<E, PCS>;
type VerifyingKey = HyperPlonkVerifyingKey<E, PCS>;
type Proof = HyperPlonkProof<E, PCS, Self, Self>;
type SubClaim = HyperPlonkSubClaim<E::Fr, Self, Self>;
/// Generate the preprocessed polynomials output by the indexer.
///
/// Inputs:
/// - `params`: HyperPlonk instance parameters
/// - `permutation`: the permutation for the copy constraints
/// - `selectors`: the list of selector vectors for custom gates
/// Outputs:
/// - The HyperPlonk proving key, which includes the preprocessed
/// polynomials.
fn preprocess(
params: &Self::Parameters,
pcs_srs: &PCS::SRS,
permutation: &[E::Fr],
selectors: &[SelectorColumn<E::Fr>],
) -> Result<(Self::ProvingKey, Self::VerifyingKey), HyperPlonkErrors> {
let num_vars = params.nv;
let log_num_witness_polys = params.log_n_wires;
// number of variables in merged polynomial for Multilinear-KZG
let merged_nv = num_vars + log_num_witness_polys;
// degree of q(x) for Univariate-KZG
let supported_uni_degree = compute_qx_degree(num_vars, 1 << log_num_witness_polys);
// extract PCS prover and verifier keys from SRS
let (pcs_prover_param, pcs_verifier_param) = PCS::trim(
pcs_srs,
log2(supported_uni_degree) as usize,
Some(merged_nv + 1),
)?;
// build permutation oracles
let permutation_oracles = Rc::new(DenseMultilinearExtension::from_evaluations_slice(
// num_vars = merged_nv + 1 because this oracle encodes both s_id and s_perm
merged_nv + 1,
permutation,
));
let perm_com = PCS::commit(&pcs_prover_param, &permutation_oracles)?;
// build selector oracles and commit to it
let selector_oracles: Vec<Rc<DenseMultilinearExtension<E::Fr>>> = selectors
.iter()
.map(|s| Rc::new(DenseMultilinearExtension::from(s)))
.collect();
let selector_com = selector_oracles
.iter()
.map(|poly| PCS::commit(&pcs_prover_param, poly))
.collect::<Result<Vec<PCS::Commitment>, PCSErrors>>()?;
Ok((
Self::ProvingKey {
params: params.clone(),
permutation_oracles,
selector_oracles,
pcs_param: pcs_prover_param,
},
Self::VerifyingKey {
params: params.clone(),
pcs_param: pcs_verifier_param,
selector_com,
perm_com,
},
))
}
/// Generate HyperPlonk SNARK proof.
///
/// Inputs:
/// - `pk`: circuit proving key
/// - `pub_input`: online public input of length 2^\ell
/// - `witness`: witness assignment of length 2^n
/// - `transcript`: the transcript used for generating pseudorandom
/// challenges
/// Outputs:
/// - The HyperPlonk SNARK proof.
///
/// Steps:
///
/// 1. Commit Witness polynomials `w_i(x)` and append commitment to
/// transcript
///
/// 2. Run ZeroCheck on
///
/// `f(q_0(x),...q_l(x), w_0(x),...w_d(x))`
///
/// where `f` is the constraint polynomial i.e.,
/// ```ignore
/// f(q_l, q_r, q_m, q_o, w_a, w_b, w_c)
/// = q_l w_a(x) + q_r w_b(x) + q_m w_a(x)w_b(x) - q_o w_c(x)
/// ```
/// in vanilla plonk, and obtain a ZeroCheckSubClaim
///
/// 3. Run permutation check on `\{w_i(x)\}` and `permutation_oracles`, and
/// obtain a PermCheckSubClaim.
///
/// 4. Generate evaluations and corresponding proofs
/// - permutation check evaluations and proofs
/// - zero check evaluations and proofs
/// - public input consistency checks
fn prove(
pk: &Self::ProvingKey,
pub_input: &[E::Fr],
witnesses: &[WitnessColumn<E::Fr>],
transcript: &mut Self::Transcript,
) -> Result<Self::Proof, HyperPlonkErrors> {
let start = start_timer!(|| "hyperplonk proving");
// witness assignment of length 2^n
let num_vars = pk.params.nv;
let log_num_witness_polys = pk.params.log_n_wires;
// number of variables in merged polynomial for Multilinear-KZG
let merged_nv = num_vars + log_num_witness_polys;
// degree of q(x) for Univariate-KZG
let _supported_uni_degree = compute_qx_degree(num_vars, 1 << log_num_witness_polys);
// online public input of length 2^\ell
let ell = pk.params.log_pub_input_len;
let witness_polys: Vec<Rc<DenseMultilinearExtension<E::Fr>>> = witnesses
.iter()
.map(|w| Rc::new(DenseMultilinearExtension::from(w)))
.collect();
let pi_poly = Rc::new(DenseMultilinearExtension::from_evaluations_slice(
ell as usize,
pub_input,
));
// =======================================================================
// 0. sanity checks
// =======================================================================
// public input length
if pub_input.len() != 1 << ell {
return Err(HyperPlonkErrors::InvalidProver(format!(
"Public input length is not correct: got {}, expect {}",
pub_input.len(),
1 << ell
)));
}
// witnesses length
for (i, w) in witnesses.iter().enumerate() {
if w.0.len() != 1 << num_vars {
return Err(HyperPlonkErrors::InvalidProver(format!(
"{}-th witness length is not correct: got {}, expect {}",
i,
pub_input.len(),
1 << ell
)));
}
}
// check public input matches witness[0]'s first 2^ell elements
let pi_in_w0 =
Rc::new(witness_polys[0].fix_variables(vec![E::Fr::zero(); num_vars - ell].as_ref()));
if pi_in_w0 != pi_poly {
return Err(HyperPlonkErrors::InvalidProver(format!(
"Public input {:?} does not match witness[0] {:?}",
pi_poly, pi_in_w0,
)));
}
// =======================================================================
// 1. Commit Witness polynomials `w_i(x)` and append commitment to
// transcript
// =======================================================================
let step = start_timer!(|| "commit witnesses");
let mut witness_commits = vec![];
// TODO: batch commit
for wi_poly in witness_polys.iter() {
let wi_com = PCS::commit(&pk.pcs_param, wi_poly)?;
witness_commits.push(wi_com);
}
let w_merged = merge_polynomials(&witness_polys)?;
if w_merged.num_vars != merged_nv {
return Err(HyperPlonkErrors::InvalidParameters(format!(
"merged witness poly has a different num_var ({}) from expected ({})",
w_merged.num_vars, merged_nv
)));
}
let w_merged_com = PCS::commit(&pk.pcs_param, &Rc::new(w_merged.clone()))?;
transcript.append_serializable_element(b"w", &w_merged_com)?;
end_timer!(step);
// =======================================================================
// 2 Run ZeroCheck on
//
// `f(q_0(x),...q_l(x), w_0(x),...w_d(x))`
//
// where `f` is the constraint polynomial i.e.,
//
// f(q_l, q_r, q_m, q_o, w_a, w_b, w_c)
// = q_l w_a(x) + q_r w_b(x) + q_m w_a(x)w_b(x) - q_o w_c(x)
//
// in vanilla plonk, and obtain a ZeroCheckSubClaim
// =======================================================================
let step = start_timer!(|| "ZeroCheck on f");
let fx = build_f(
&pk.params.gate_func,
pk.params.nv,
&pk.selector_oracles,
&witness_polys,
)?;
let zero_check_proof = <Self as ZeroCheck<E::Fr>>::prove(&fx, transcript)?;
end_timer!(step);
// =======================================================================
// 3. Run permutation check on `\{w_i(x)\}` and `permutation_oracles`, and
// obtain a PermCheckSubClaim.
//
// 3.1. `generate_challenge` from current transcript (generate beta, gamma)
// 3.2. `compute_product` to build `prod(x)` etc. from f, g and s_perm
// 3.3. push a commitment of `prod(x)` to the transcript
// 3.4. `update_challenge` with the updated transcript
// 3.5. `prove` to generate the proof
// =======================================================================
let step = start_timer!(|| "Permutation check on w_i(x)");
// 3.1 `generate_challenge` from current transcript (generate beta, gamma)
let mut permutation_challenge = Self::generate_challenge(transcript)?;
// 3.2. `compute_product` to build `prod(x)` etc. from f, g and s_perm
// s_perm is the second half of permutation oracle
let s_perm = pk.permutation_oracles.fix_variables(&[E::Fr::one()]);
// This function returns 3 MLEs:
// - prod(x)
// - numerator
// - denominator
// See function signature for details.
let prod_x_and_aux_info =
Self::compute_prod_evals(&permutation_challenge, &w_merged, &w_merged, &s_perm)?;
// 3.3 push a commitment of `prod(x)` to the transcript
let prod_com = PCS::commit(&pk.pcs_param, &Rc::new(prod_x_and_aux_info[0].clone()))?;
// 3.4. `update_challenge` with the updated transcript
Self::update_challenge(&mut permutation_challenge, transcript, &prod_com)?;
// 3.5. `prove` to generate the proof
let perm_check_proof = <Self as PermutationCheck<E::Fr>>::prove(
&prod_x_and_aux_info,
&permutation_challenge,
transcript,
)?;
end_timer!(step);
// =======================================================================
// 4. Generate evaluations and corresponding proofs
// - permutation check evaluations and proofs
// - wi_poly(r_perm_check) where r_perm_check is from perm_check_proof
// - selector_poly(r_perm_check)
//
// - zero check evaluations and proofs
// - wi_poly(r_zero_check) where r_zero_check is from zero_check_proof
// - selector_poly(r_zero_check)
//
// - public input consistency checks
// - pi_poly(r_pi) where r_pi is sampled from transcript
// =======================================================================
let step = start_timer!(|| "opening and evaluations");
// 4.1 permutation check
let mut witness_zero_check_evals = vec![];
let mut witness_zero_check_openings = vec![];
// TODO: parallelization
// TODO: Batch opening
// open permutation check proof
let (witness_perm_check_opening, witness_perm_check_eval) = PCS::open(
&pk.pcs_param,
&Rc::new(w_merged.clone()),
&perm_check_proof.point,
)?;
// sanity checks
if w_merged.evaluate(&perm_check_proof.point).ok_or_else(|| {
HyperPlonkErrors::InvalidParameters("evaluation dimension does not match".to_string())
})? != witness_perm_check_eval
{
return Err(HyperPlonkErrors::InvalidProver(
"Evaluation is different from PCS opening".to_string(),
));
}
// 4.2 open zero check proof
// TODO: batch opening
for wire_poly in witness_polys {
// Open zero check proof
let (zero_proof, zero_eval) =
PCS::open(&pk.pcs_param, &wire_poly, &zero_check_proof.point)?;
{
if wire_poly.evaluate(&zero_check_proof.point).ok_or_else(|| {
HyperPlonkErrors::InvalidParameters(
"evaluation dimension does not match".to_string(),
)
})? != zero_eval
{
return Err(HyperPlonkErrors::InvalidProver(
"Evaluation is different from PCS opening".to_string(),
));
}
}
witness_zero_check_evals.push(zero_eval);
witness_zero_check_openings.push(zero_proof);
}
// Open permutation check proof
let (perm_oracle_opening, perm_oracle_eval) = PCS::open(
&pk.pcs_param,
&pk.permutation_oracles,
&[&[E::Fr::one()], perm_check_proof.point.as_slice()].concat(),
)?;
{
// sanity check
if s_perm.evaluate(&perm_check_proof.point).ok_or_else(|| {
HyperPlonkErrors::InvalidParameters(
"evaluation dimension does not match".to_string(),
)
})? != perm_oracle_eval
{
return Err(HyperPlonkErrors::InvalidProver(
"Evaluation is different from PCS opening".to_string(),
));
}
}
let mut selector_oracle_openings = vec![];
let mut selector_oracle_evals = vec![];
// TODO: parallelization
for selector_poly in pk.selector_oracles.iter() {
// Open zero check proof
// during verification, use this eval against subclaim
let (zero_proof, zero_eval) =
PCS::open(&pk.pcs_param, selector_poly, &zero_check_proof.point)?;
{
if selector_poly
.evaluate(&zero_check_proof.point)
.ok_or_else(|| {
HyperPlonkErrors::InvalidParameters(
"evaluation dimension does not match".to_string(),
)
})?
!= zero_eval
{
return Err(HyperPlonkErrors::InvalidProver(
"Evaluation is different from PCS
opening"
.to_string(),
));
}
}
selector_oracle_openings.push(zero_proof);
selector_oracle_evals.push(zero_eval);
}
// 4.3 public input consistency checks
let r_pi = transcript.get_and_append_challenge_vectors(b"r_pi", ell)?;
let (pi_opening, pi_eval) = PCS::open(&pk.pcs_param, &pi_in_w0, &r_pi)?;
{
// sanity check
if pi_poly.evaluate(&r_pi).ok_or_else(|| {
HyperPlonkErrors::InvalidParameters(
"evaluation dimension does not match".to_string(),
)
})? != pi_eval
{
return Err(HyperPlonkErrors::InvalidProver(
"Evaluation is different from PCS opening".to_string(),
));
}
}
end_timer!(step);
end_timer!(start);
Ok(HyperPlonkProof {
// PCS components
witness_commits,
w_merged_com,
// We do not validate prod(x), this is checked by subclaim
prod_commit: prod_com,
witness_perm_check_opening,
witness_zero_check_openings,
witness_perm_check_eval,
witness_zero_check_evals,
perm_oracle_opening,
perm_oracle_eval,
selector_oracle_openings,
selector_oracle_evals,
pi_eval,
pi_opening,
// IOP components
zero_check_proof,
perm_check_proof,
})
}
/// Verify the HyperPlonk proof and generate the evaluation subclaims to be
/// checked later by the SNARK verifier.
///
/// Inputs:
/// - `params`: instance parameter
/// - `pub_input`: online public input
/// - `proof`: HyperPlonk SNARK proof
/// - `transcript`: the transcript used for generating pseudorandom
/// challenges
/// Outputs:
/// - Return error if the verification fails, otherwise return the
/// evaluation subclaim
///
/// 1. Verify zero_check_proof on
///
/// `f(q_0(x),...q_l(x), w_0(x),...w_d(x))`
///
/// where `f` is the constraint polynomial i.e.,
/// ```ignore
/// f(q_l, q_r, q_m, q_o, w_a, w_b, w_c)
/// = q_l w_a(x) + q_r w_b(x) + q_m w_a(x)w_b(x) - q_o w_c(x)
/// ```
/// in vanilla plonk, and obtain a ZeroCheckSubClaim
///
/// 2. Verify perm_check_proof on `\{w_i(x)\}` and `permutation_oracles`
///
/// 3. Verify the opening against the commitment:
/// - check permutation check evaluations
/// - check zero check evaluations
/// - public input consistency checks
/// 4. check subclaim validity // todo
fn verify(
vk: &Self::VerifyingKey,
pub_input: &[E::Fr],
proof: &Self::Proof,
transcript: &mut Self::Transcript,
) -> Result<Self::SubClaim, HyperPlonkErrors> {
let start = start_timer!(|| "hyperplonk verification");
// witness assignment of length 2^n
let num_var = vk.params.nv;
let log_num_witness_polys = vk.params.log_n_wires;
// number of variables in merged polynomial for Multilinear-KZG
let merged_nv = num_var + log_num_witness_polys;
// online public input of length 2^\ell
let ell = vk.params.log_pub_input_len;
let pi_poly = DenseMultilinearExtension::from_evaluations_slice(ell as usize, pub_input);
// =======================================================================
// 0. sanity checks
// =======================================================================
// public input length
if pub_input.len() != 1 << ell {
return Err(HyperPlonkErrors::InvalidProver(format!(
"Public input length is not correct: got {}, expect {}",
pub_input.len(),
1 << ell
)));
}
// =======================================================================
// 1. Verify zero_check_proof on
// `f(q_0(x),...q_l(x), w_0(x),...w_d(x))`
//
// where `f` is the constraint polynomial i.e.,
//
// f(q_l, q_r, q_m, q_o, w_a, w_b, w_c)
// = q_l w_a(x) + q_r w_b(x) + q_m w_a(x)w_b(x) - q_o w_c(x)
//
// =======================================================================
let step = start_timer!(|| "verify zero check");
// Zero check and sum check have different AuxInfo because `w_merged` and
// `Prod(x)` have degree and num_vars
let zero_check_aux_info = VPAuxInfo::<E::Fr> {
// TODO: get the real max degree from gate_func
// Here we use 6 is because the test has q[0] * w[0]^5 which is degree 6
max_degree: 6,
num_variables: num_var,
phantom: PhantomData::default(),
};
// push witness to transcript
transcript.append_serializable_element(b"w", &proof.w_merged_com)?;
let zero_check_sub_claim = <Self as ZeroCheck<E::Fr>>::verify(
&proof.zero_check_proof,
&zero_check_aux_info,
transcript,
)?;
end_timer!(step);
// =======================================================================
// 2. Verify perm_check_proof on `\{w_i(x)\}` and `permutation_oracles`
// =======================================================================
// Zero check and sum check have different AuxInfo because `w_merged` and
// `Prod(x)` have degree and num_vars
let perm_check_aux_info = VPAuxInfo::<E::Fr> {
// Prod(x) has a max degree of 2
max_degree: 2,
// degree of merged poly
num_variables: merged_nv,
phantom: PhantomData::default(),
};
let mut challenge = <Self as PermutationCheck<E::Fr>>::generate_challenge(transcript)?;
<Self as PermutationCheck<E::Fr>>::update_challenge(
&mut challenge,
transcript,
&proof.prod_commit,
)?;
let perm_check_sub_claim = <Self as PermutationCheck<E::Fr>>::verify(
&proof.perm_check_proof,
&perm_check_aux_info,
transcript,
)?;
end_timer!(step);
// =======================================================================
// 3. Verify the opening against the commitment
// =======================================================================
let step = start_timer!(|| "verify commitments");
let perm_point = &perm_check_sub_claim
.zero_check_sub_claim
.sum_check_sub_claim
.point;
let zero_point = &zero_check_sub_claim.sum_check_sub_claim.point;
// =======================================================================
// 3.1 check permutation check evaluations
// =======================================================================
// witness for permutation check
if !PCS::verify(
&vk.pcs_param,
&proof.w_merged_com,
perm_point,
&proof.witness_perm_check_eval,
&proof.witness_perm_check_opening,
)? {
return Err(HyperPlonkErrors::InvalidProof(
"pcs verification failed".to_string(),
));
}
// perm for permutation check
if !PCS::verify(
&vk.pcs_param,
&vk.perm_com,
&[&[E::Fr::one()], perm_point.as_slice()].concat(),
&proof.perm_oracle_eval,
&proof.perm_oracle_opening,
)? {
return Err(HyperPlonkErrors::InvalidProof(
"pcs verification failed".to_string(),
));
}
// =======================================================================
// 3.2 check zero check evaluations
// =======================================================================
// witness for zero check
// TODO: batch verification
for (commitment, (opening, eval)) in proof.witness_commits.iter().zip(
proof
.witness_zero_check_openings
.iter()
.zip(proof.witness_zero_check_evals.iter()),
) {
if !PCS::verify(&vk.pcs_param, commitment, zero_point, eval, opening)? {
return Err(HyperPlonkErrors::InvalidProof(
"pcs verification failed".to_string(),
));
}
}
// selector for zero check
// TODO: for now we only support a single selector polynomial
for (opening, eval) in proof
.selector_oracle_openings
.iter()
.zip(proof.selector_oracle_evals.iter())
{
if !PCS::verify(
&vk.pcs_param,
&vk.selector_com[0],
perm_point,
eval,
opening,
)? {
return Err(HyperPlonkErrors::InvalidProof(
"pcs verification failed".to_string(),
));
}
}
// let f_eval = eval_f(
// &vk.params.gate_func,
// &proof.selector_oracle_evals,
// &proof.witness_zero_check_evals,
// )?;
// if f_eval != zero_check_sub_claim.sum_check_sub_claim.expected_evaluation {
// return Err(HyperPlonkErrors::InvalidProof(
// "zero check evaluation failed".to_string(),
// ));
// }
// =======================================================================
// 3.3 public input consistency checks
// =======================================================================
let mut r_pi = transcript.get_and_append_challenge_vectors(b"r_pi", ell)?;
let pi_eval = pi_poly.evaluate(&r_pi).ok_or_else(|| {
HyperPlonkErrors::InvalidParameters("evaluation dimension does not match".to_string())
})?;
r_pi = [vec![E::Fr::zero(); num_var - ell], r_pi].concat();
if !PCS::verify(
&vk.pcs_param,
&proof.witness_commits[0],
&r_pi,
&pi_eval,
&proof.pi_opening,
)? {
return Err(HyperPlonkErrors::InvalidProof(
"pcs verification failed".to_string(),
));
}
end_timer!(step);
end_timer!(start);
// todo: verify the subclaim within snark
Ok(HyperPlonkSubClaim {
zero_check_sub_claim,
perm_check_sub_claim,
pub_input_sub_claim: (vec![], E::Fr::default()), // FIXME
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{selectors::SelectorColumn, structs::CustomizedGates, witness::WitnessColumn};
use ark_bls12_381::Bls12_381;
use ark_std::test_rng;
use pcs::prelude::KZGMultilinearPCS;
use poly_iop::{identity_permutation_mle, random_permutation_mle};
use transcript::IOPTranscript;
#[test]
fn test_hyperplonk_e2e() -> Result<(), HyperPlonkErrors> {
// Example:
// q_L(X) * W_1(X)^5 - W_2(X) = 0
// is represented as
// vec![
// ( 1, Some(id_qL), vec![id_W1, id_W1, id_W1, id_W1, id_W1]),
// (-1, None, vec![id_W2])
// ]
//
// 4 public input
// 1 selector,
// 2 witnesses,
// 2 variables for MLE,
// 4 wires,
let gates = CustomizedGates {
gates: vec![(1, Some(0), vec![0, 0, 0, 0, 0]), (-1, None, vec![1])],
};
test_hyperplonk_helper::<Bls12_381>(2, 2, 0, 1, gates)
}
fn test_hyperplonk_helper<E: PairingEngine>(
nv: usize,
log_pub_input_len: usize,
log_n_selectors: usize,
log_n_wires: usize,
gate_func: CustomizedGates,
) -> Result<(), HyperPlonkErrors> {
let mut rng = test_rng();
// system parameters
let params = HyperPlonkParams {
nv,
log_pub_input_len,
log_n_selectors,
log_n_wires,
gate_func,
};
let pcs_srs = KZGMultilinearPCS::<E>::gen_srs_for_testing(&mut rng, 15)?;
let merged_nv = nv + log_n_wires;
let s_perm = random_permutation_mle(merged_nv, &mut rng);
let s_id = identity_permutation_mle(merged_nv);
let perm: Vec<E::Fr> = [s_id.evaluations, s_perm.evaluations].concat();
let q1 = SelectorColumn(vec![E::Fr::one(), E::Fr::one(), E::Fr::one(), E::Fr::one()]);
// w1 := [0, 1, 2, 3]
let w1 = WitnessColumn(vec![
E::Fr::zero(),
E::Fr::one(),
E::Fr::from(2u64),
E::Fr::from(3u64),
]);
// w2 := [0^5, 1^5, 2^5, 3^5]
let w2 = WitnessColumn(vec![
E::Fr::zero(),
E::Fr::one(),
E::Fr::from(32u64),
E::Fr::from(243u64),
]);
// public input = w1
let pi = w1.clone();
// generate pk and vks
let (pk, vk) = <PolyIOP<E::Fr> as HyperPlonkSNARK<E, KZGMultilinearPCS<E>>>::preprocess(
&params,
&pcs_srs,
&perm,
&[q1],
)?;
// generate a proof and verify
let mut transcript = IOPTranscript::<E::Fr>::new(b"test hyperplonk");
let proof = <PolyIOP<E::Fr> as HyperPlonkSNARK<E, KZGMultilinearPCS<E>>>::prove(
&pk,
&pi.0,
&[w1, w2],
&mut transcript,
)?;
let mut transcript = IOPTranscript::<E::Fr>::new(b"test hyperplonk");
let _sub_claim = <PolyIOP<E::Fr> as HyperPlonkSNARK<E, KZGMultilinearPCS<E>>>::verify(
&vk,
&pi.0,
&proof,
&mut transcript,
)?;
Ok(())
}
}

View File

@@ -0,0 +1,46 @@
use crate::errors::HyperPlonkErrors;
use ark_ff::PrimeField;
use ark_poly::DenseMultilinearExtension;
use ark_std::log2;
/// A column of selectors of length `#constraints`
#[derive(Debug, Clone)]
pub struct SelectorColumn<F: PrimeField>(pub(crate) Vec<F>);
impl<F: PrimeField> SelectorColumn<F> {
/// the number of variables for MLE to present a column.
pub fn get_nv(&self) -> usize {
log2(self.0.len()) as usize
}
/// Build selector columns from rows
pub fn from_selector_rows(
selector_rows: &[SelectorColumn<F>],
) -> Result<Vec<Self>, HyperPlonkErrors> {
if selector_rows.is_empty() {
return Err(HyperPlonkErrors::InvalidParameters(
"empty witness rows".to_string(),
));
}
let mut res = Vec::with_capacity(selector_rows.len());
let num_wires = selector_rows[0].0.len();
for i in 0..num_wires {
let mut cur_column = Vec::new();
for row in selector_rows.iter() {
cur_column.push(row.0[i])
}
res.push(Self(cur_column))
}
Ok(res)
}
}
impl<F: PrimeField> From<&SelectorColumn<F>> for DenseMultilinearExtension<F> {
fn from(witness: &SelectorColumn<F>) -> Self {
let nv = witness.get_nv();
Self::from_evaluations_slice(nv, witness.0.as_ref())
}
}

156
hyperplonk/src/structs.rs Normal file
View File

@@ -0,0 +1,156 @@
//! Main module for the HyperPlonk PolyIOP.
use ark_ec::PairingEngine;
use ark_ff::PrimeField;
use ark_poly::DenseMultilinearExtension;
use pcs::PolynomialCommitmentScheme;
use poly_iop::prelude::{PermutationCheck, ZeroCheck};
use std::rc::Rc;
/// The sub-claim for the HyperPlonk PolyIOP, consists of the following:
/// - the SubClaim for the zero-check PIOP
/// - the SubClaim for the permutation-check PIOP
/// - the SubClaim for public input consistency
#[derive(Clone, Debug, Default, PartialEq)]
pub struct HyperPlonkSubClaim<F: PrimeField, ZC: ZeroCheck<F>, PC: PermutationCheck<F>> {
/// the SubClaim for the custom gate zerocheck
pub zero_check_sub_claim: ZC::ZeroCheckSubClaim,
/// the SubClaim for the permutation check
pub perm_check_sub_claim: PC::PermutationCheckSubClaim,
/// the public input consistency check
pub pub_input_sub_claim: (Vec<F>, F), // (point, expected_eval)
}
/// The proof for the HyperPlonk PolyIOP, consists of the following:
/// - a batch commitment to all the witness MLEs
/// - a batch opening to all the MLEs at certain index
/// - the zero-check proof for checking custom gate-satisfiability
/// - the permutation-check proof for checking the copy constraints
#[derive(Clone, Debug, Default, PartialEq)]
pub struct HyperPlonkProof<
E: PairingEngine,
PCS: PolynomialCommitmentScheme<E>,
ZC: ZeroCheck<E::Fr>,
PC: PermutationCheck<E::Fr>,
> {
// =======================================================================
// PCS components
// =======================================================================
/// PCS commit for witnesses
// TODO: replace me with a batch commitment
pub witness_commits: Vec<PCS::Commitment>,
pub w_merged_com: PCS::Commitment,
/// PCS commit for prod(x)
// TODO: replace me with a batch commitment
pub prod_commit: PCS::Commitment,
/// PCS openings for witness on permutation check point
// TODO: replace me with a batch opening
pub witness_perm_check_opening: PCS::Proof,
/// PCS openings for witness on zero check point
// TODO: replace me with a batch opening
pub witness_zero_check_openings: Vec<PCS::Proof>,
/// Evaluates of witnesses on permutation check point
pub witness_perm_check_eval: E::Fr,
/// Evaluates of witnesses on zero check point
pub witness_zero_check_evals: Vec<E::Fr>,
/// PCS openings for selectors on permutation check point
// TODO: replace me with a batch opening
pub perm_oracle_opening: PCS::Proof,
/// Evaluates of selectors on permutation check point
pub perm_oracle_eval: E::Fr,
/// PCS openings for selectors on zero check point
// TODO: replace me with a batch opening
pub selector_oracle_openings: Vec<PCS::Proof>,
/// Evaluates of selectors on zero check point
pub selector_oracle_evals: Vec<E::Fr>,
/// Evaluates of public inputs on r_pi from transcript
pub pi_eval: E::Fr,
/// Opening of public inputs on r_pi from transcript
pub pi_opening: PCS::Proof,
// =======================================================================
// IOP components
// =======================================================================
/// the custom gate zerocheck proof
pub zero_check_proof: ZC::ZeroCheckProof,
/// the permutation check proof for copy constraints
pub perm_check_proof: PC::PermutationProof,
}
/// The HyperPlonk instance parameters, consists of the following:
/// - the number of variables in the poly-IOP
/// - binary log of the number of public input variables
/// - binary log of the number of selectors
/// - binary log of the number of witness wires
/// - the customized gate function
#[derive(Clone, Debug, Default, PartialEq)]
pub struct HyperPlonkParams {
/// the number of variables in polys
pub nv: usize,
/// binary log of the public input length
pub log_pub_input_len: usize,
// binary log of the number of selectors
pub log_n_selectors: usize,
/// binary log of the number of witness wires
pub log_n_wires: usize,
/// customized gate function
pub gate_func: CustomizedGates,
}
/// The HyperPlonk proving key, consists of the following:
/// - the hyperplonk instance parameters
/// - the preprocessed polynomials output by the indexer
#[derive(Clone, Debug, Default, PartialEq)]
pub struct HyperPlonkProvingKey<E: PairingEngine, PCS: PolynomialCommitmentScheme<E>> {
/// hyperplonk instance parameters
pub params: HyperPlonkParams,
/// the preprocessed permutation polynomials
pub permutation_oracles: Rc<DenseMultilinearExtension<E::Fr>>,
/// the preprocessed selector polynomials
// TODO: merge the list into a single MLE
pub selector_oracles: Vec<Rc<DenseMultilinearExtension<E::Fr>>>,
/// the parameters for PCS commitment
pub pcs_param: PCS::ProverParam,
}
/// The HyperPlonk verifying key, consists of the following:
/// - the hyperplonk instance parameters
/// - the preprocessed polynomials output by the indexer
#[derive(Clone, Debug, Default, PartialEq)]
pub struct HyperPlonkVerifyingKey<E: PairingEngine, PCS: PolynomialCommitmentScheme<E>> {
/// hyperplonk instance parameters
pub params: HyperPlonkParams,
/// the parameters for PCS commitment
pub pcs_param: PCS::VerifierParam,
/// Selector's commitment
// TODO: replace me with a batch commitment
pub selector_com: Vec<PCS::Commitment>,
/// Permutation oracle's commitment
pub perm_com: PCS::Commitment,
}
/// Customized gate is a list of tuples of
/// (coefficient, selector_index, wire_indices)
///
/// Example:
/// q_L(X) * W_1(X)^5 - W_2(X)
/// is represented as
/// vec![
/// ( 1, Some(id_qL), vec![id_W1, id_W1, id_W1, id_W1, id_W1]),
/// (-1, None, vec![id_W2])
/// ]
///
/// CustomizedGates {
/// gates: vec![
/// (1, Some(0), vec![0, 0, 0, 0, 0]),
/// (-1, None, vec![1])
/// ],
/// };
/// where id_qL = 0 // first selector
/// id_W1 = 0 // first witness
/// id_w2 = 1 // second witness
///
/// NOTE: here coeff is a signed integer, instead of a field element
#[derive(Clone, Debug, Default, PartialEq)]
pub struct CustomizedGates {
pub(crate) gates: Vec<(i64, Option<usize>, Vec<usize>)>,
}

210
hyperplonk/src/utils.rs Normal file
View File

@@ -0,0 +1,210 @@
use std::rc::Rc;
use arithmetic::VirtualPolynomial;
use ark_ff::PrimeField;
use ark_poly::DenseMultilinearExtension;
use crate::{errors::HyperPlonkErrors, structs::CustomizedGates};
/// Build MLE from matrix of witnesses.
///
/// Given a matrix := [row1, row2, ...] where
/// row1:= (a1, a2, ...)
/// row2:= (b1, b2, ...)
/// row3:= (c1, c2, ...)
///
/// output mle(a1,b1,c1, ...), mle(a2,b2,c2, ...), ...
#[macro_export]
macro_rules! build_mle {
($rows:expr) => {{
let mut res = Vec::with_capacity($rows.len());
let num_vars = log2($rows.len()) as usize;
let num_mles = $rows[0].0.len();
for i in 0..num_mles {
let mut cur_coeffs = Vec::new();
for row in $rows.iter() {
cur_coeffs.push(row.0[i])
}
res.push(Rc::new(DenseMultilinearExtension::from_evaluations_vec(
num_vars, cur_coeffs,
)))
}
Ok(res)
}};
}
/// build `f(w_0(x),...w_d(x))` where `f` is the constraint polynomial
/// i.e., `f(a, b, c) = q_l a(x) + q_r b(x) + q_m a(x)b(x) - q_o c(x)` in
/// vanilla plonk
pub(crate) fn build_f<F: PrimeField>(
gates: &CustomizedGates,
num_vars: usize,
selector_mles: &[Rc<DenseMultilinearExtension<F>>],
witness_mles: &[Rc<DenseMultilinearExtension<F>>],
) -> Result<VirtualPolynomial<F>, HyperPlonkErrors> {
// TODO: check that selector and witness lengths match what is in
// the gate definition
for selector_mle in selector_mles.iter() {
if selector_mle.num_vars != num_vars {
return Err(HyperPlonkErrors::InvalidParameters(format!(
"selector has different number of vars: {} vs {}",
selector_mle.num_vars, num_vars
)));
}
}
for witness_mle in witness_mles.iter() {
if witness_mle.num_vars != num_vars {
return Err(HyperPlonkErrors::InvalidParameters(format!(
"selector has different number of vars: {} vs {}",
witness_mle.num_vars, num_vars
)));
}
}
let mut res = VirtualPolynomial::<F>::new(num_vars);
for (coeff, selector, witnesses) in gates.gates.iter() {
let coeff_fr = if *coeff < 0 {
-F::from(-*coeff as u64)
} else {
F::from(*coeff as u64)
};
let mut mle_list = vec![];
match *selector {
Some(s) => mle_list.push(selector_mles[s].clone()),
None => (),
};
for &witness in witnesses.iter() {
mle_list.push(witness_mles[witness].clone())
}
res.add_mle_list(mle_list, coeff_fr)?;
}
Ok(res)
}
#[allow(dead_code)]
pub(crate) fn eval_f<F: PrimeField>(
gates: &CustomizedGates,
selector_evals: &[F],
witness_evals: &[F],
) -> Result<F, HyperPlonkErrors> {
let mut res = F::zero();
for (coeff, selector, witnesses) in gates.gates.iter() {
let mut cur_value = if *coeff < 0 {
-F::from(-*coeff as u64)
} else {
F::from(*coeff as u64)
};
cur_value *= match selector {
Some(s) => selector_evals[*s],
None => F::one(),
};
for &witness in witnesses.iter() {
cur_value *= witness_evals[witness]
}
res += cur_value;
}
Ok(res)
}
#[cfg(test)]
mod test {
use super::*;
use ark_bls12_381::Fr;
use ark_ff::PrimeField;
use ark_poly::MultilinearExtension;
#[test]
fn test_build_gate() -> Result<(), HyperPlonkErrors> {
test_build_gate_helper::<Fr>()
}
fn test_build_gate_helper<F: PrimeField>() -> Result<(), HyperPlonkErrors> {
let num_vars = 2;
// ql = 3x1x2 + 2x2 whose evaluations are
// 0, 0 |-> 0
// 0, 1 |-> 2
// 1, 0 |-> 0
// 1, 1 |-> 5
let ql_eval = vec![F::zero(), F::from(2u64), F::zero(), F::from(5u64)];
let ql = Rc::new(DenseMultilinearExtension::from_evaluations_vec(2, ql_eval));
// W1 = x1x2 + x1 whose evaluations are
// 0, 0 |-> 0
// 0, 1 |-> 0
// 1, 0 |-> 1
// 1, 1 |-> 2
let w_eval = vec![F::zero(), F::zero(), F::from(1u64), F::from(2u64)];
let w1 = Rc::new(DenseMultilinearExtension::from_evaluations_vec(2, w_eval));
// W2 = x1 + x2 whose evaluations are
// 0, 0 |-> 0
// 0, 1 |-> 1
// 1, 0 |-> 1
// 1, 1 |-> 2
let w_eval = vec![F::zero(), F::one(), F::from(1u64), F::from(2u64)];
let w2 = Rc::new(DenseMultilinearExtension::from_evaluations_vec(2, w_eval));
// Example:
// q_L(X) * W_1(X)^5 - W_2(X)
// is represented as
// vec![
// ( 1, Some(id_qL), vec![id_W1, id_W1, id_W1, id_W1, id_W1]),
// (-1, None, vec![id_W2])
// ]
let gates = CustomizedGates {
gates: vec![(1, Some(0), vec![0, 0, 0, 0, 0]), (-1, None, vec![1])],
};
let f = build_f(&gates, num_vars, &[ql.clone()], &[w1.clone(), w2.clone()])?;
// Sanity check on build_f
// f(0, 0) = 0
assert_eq!(f.evaluate(&[F::zero(), F::zero()])?, F::zero());
// f(0, 1) = 2 * 0^5 + (-1) * 1 = -1
assert_eq!(f.evaluate(&[F::zero(), F::one()])?, -F::one());
// f(1, 0) = 0 * 1^5 + (-1) * 1 = -1
assert_eq!(f.evaluate(&[F::one(), F::zero()])?, -F::one());
// f(1, 1) = 5 * 2^5 + (-1) * 2 = 158
assert_eq!(f.evaluate(&[F::one(), F::one()])?, F::from(158u64));
// test eval_f
{
let point = [F::zero(), F::zero()];
let selector_evals = ql.evaluate(&point).unwrap();
let witness_evals = [w1.evaluate(&point).unwrap(), w2.evaluate(&point).unwrap()];
let eval_f = eval_f(&gates, &[selector_evals], &witness_evals)?;
// f(0, 0) = 0
assert_eq!(eval_f, F::zero());
}
{
let point = [F::zero(), F::one()];
let selector_evals = ql.evaluate(&point).unwrap();
let witness_evals = [w1.evaluate(&point).unwrap(), w2.evaluate(&point).unwrap()];
let eval_f = eval_f(&gates, &[selector_evals], &witness_evals)?;
// f(0, 1) = 2 * 0^5 + (-1) * 1 = -1
assert_eq!(eval_f, -F::one());
}
{
let point = [F::one(), F::zero()];
let selector_evals = ql.evaluate(&point).unwrap();
let witness_evals = [w1.evaluate(&point).unwrap(), w2.evaluate(&point).unwrap()];
let eval_f = eval_f(&gates, &[selector_evals], &witness_evals)?;
// f(1, 0) = 0 * 1^5 + (-1) * 1 = -1
assert_eq!(eval_f, -F::one());
}
{
let point = [F::one(), F::one()];
let selector_evals = ql.evaluate(&point).unwrap();
let witness_evals = [w1.evaluate(&point).unwrap(), w2.evaluate(&point).unwrap()];
let eval_f = eval_f(&gates, &[selector_evals], &witness_evals)?;
// f(1, 1) = 5 * 2^5 + (-1) * 2 = 158
assert_eq!(eval_f, F::from(158u64));
}
Ok(())
}
}

67
hyperplonk/src/witness.rs Normal file
View File

@@ -0,0 +1,67 @@
use crate::{build_mle, errors::HyperPlonkErrors};
use ark_ff::PrimeField;
use ark_poly::DenseMultilinearExtension;
use ark_std::log2;
use std::rc::Rc;
/// A row of witnesses of width `#wires`
#[derive(Debug, Clone)]
pub struct WitnessRow<F: PrimeField>(pub(crate) Vec<F>);
/// A column of witnesses of length `#constraints`
#[derive(Debug, Clone)]
pub struct WitnessColumn<F: PrimeField>(pub(crate) Vec<F>);
impl<F: PrimeField> WitnessColumn<F> {
/// the number of variables for MLE to present a column.
pub fn get_nv(&self) -> usize {
log2(self.0.len()) as usize
}
/// Build witness columns from rows
pub fn from_witness_rows(
witness_rows: &[WitnessRow<F>],
) -> Result<Vec<Self>, HyperPlonkErrors> {
if witness_rows.is_empty() {
return Err(HyperPlonkErrors::InvalidParameters(
"empty witness rows".to_string(),
));
}
let mut res = Vec::with_capacity(witness_rows.len());
let num_wires = witness_rows[0].0.len();
for i in 0..num_wires {
let mut cur_column = Vec::new();
for row in witness_rows.iter() {
cur_column.push(row.0[i])
}
res.push(Self(cur_column))
}
Ok(res)
}
}
impl<F: PrimeField> From<&WitnessColumn<F>> for DenseMultilinearExtension<F> {
fn from(witness: &WitnessColumn<F>) -> Self {
let nv = witness.get_nv();
Self::from_evaluations_slice(nv, witness.0.as_ref())
}
}
impl<F: PrimeField> WitnessRow<F> {
/// Build MLE from matrix of witnesses.
///
/// Given a matrix := [row1, row2, ...] where
/// row1:= (a1, a2, ...)
/// row2:= (b1, b2, ...)
/// row3:= (c1, c2, ...)
///
/// output mle(a1,b1,c1, ...), mle(a2,b2,c2, ...), ...
pub fn build_mles(
matrix: &[Self],
) -> Result<Vec<Rc<DenseMultilinearExtension<F>>>, HyperPlonkErrors> {
build_mle!(matrix)
}
}