Skip to content

Commit

Permalink
Merge pull request #5 from EYBlockchain/lydia/hyperplonkComments
Browse files Browse the repository at this point in the history
comments for hyperplonk
  • Loading branch information
Michael-EY authored Nov 28, 2024
2 parents ab61754 + c2515b0 commit bf04eac
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 6 deletions.
2 changes: 2 additions & 0 deletions plonkish_backend/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,14 @@ impl<F: Clone> PlonkishCircuitInfo<F> {
.unwrap_or(true)
}

//Total number of columns/ polynomials in the circuit.
pub fn num_poly(&self) -> usize {
self.num_instances.len()
+ self.preprocess_polys.len()
+ self.num_witness_polys.iter().sum::<usize>()
}

//Return all index of columns that are used in the permutation, in order.
pub fn permutation_polys(&self) -> Vec<usize> {
self.permutations
.iter()
Expand Down
24 changes: 23 additions & 1 deletion plonkish_backend/src/backend/hyperplonk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ where
pub(crate) num_lookups: usize,
pub(crate) num_permutation_z_polys: usize,
pub(crate) num_vars: usize,
// Expression for the constraint system
pub(crate) expression: Expression<F>,
pub(crate) preprocess_comms: Vec<Pcs::Commitment>,
pub(crate) permutation_comms: Vec<(usize, Pcs::Commitment)>,
Expand All @@ -85,14 +86,17 @@ where
type ProverParam = HyperPlonkProverParam<F, Pcs>;
type VerifierParam = HyperPlonkVerifierParam<F, Pcs>;

//TO DO - for KZG we want to use a trusted setup from a ceremony as currently generated in nightfish/ nightfall
fn setup(
circuit_info: &PlonkishCircuitInfo<F>,
rng: impl RngCore,
) -> Result<Pcs::Param, Error> {
assert!(circuit_info.is_well_formed());

let num_vars = circuit_info.k;
// number of variables in the multilinear polynomial
let poly_size = 1 << num_vars;
// set batch size for polynomial commitment scheme
let batch_size = batch_size(circuit_info);
Pcs::setup(poly_size, batch_size, rng)
}
Expand All @@ -115,12 +119,14 @@ where
) -> Result<(), Error> {
let instance_polys = {
let instances = circuit.instances();
//Check there is the correct amount of instances and write them to the transcript
for (num_instances, instances) in pp.num_instances.iter().zip_eq(instances) {
assert_eq!(instances.len(), *num_instances);
for instance in instances.iter() {
transcript.common_field_element(instance)?;
}
}
// Create multi-linear polynomials from the instance columns
instance_polys::<_, BinaryField>(pp.num_vars, instances)
};

Expand All @@ -129,6 +135,7 @@ where
let mut witness_polys = Vec::with_capacity(pp.num_witness_polys.iter().sum());
let mut witness_comms = Vec::with_capacity(witness_polys.len());
let mut challenges = Vec::with_capacity(pp.num_challenges.iter().sum::<usize>() + 4);
// For each round, generate multi-linear polynomials from witness columns and commit
for (round, (num_witness_polys, num_challenges)) in pp
.num_witness_polys
.iter()
Expand All @@ -152,8 +159,10 @@ where

// Round n

// beta is used to compress the polynomials in the lookup argument
let beta = transcript.squeeze_challenge();

// Generate a compressed multilinear polynomial for each lookup in the vector of lookups
let timer = start_timer(|| format!("lookup_compressed_polys-{}", pp.lookups.len()));
let lookup_compressed_polys = {
let max_lookup_width = pp.lookups.iter().map(Vec::len).max().unwrap_or_default();
Expand All @@ -162,6 +171,8 @@ where
};
end_timer(timer);

// m and h are the polynomials generated as part of the logup GKR protocol
// if the lookups are f (input), t (table), m[i] is |j \in 2^{numvars} s.t f[j] = t[i] |, i.e. the number of times t[i] appears in f
let timer = start_timer(|| format!("lookup_m_polys-{}", pp.lookups.len()));
let lookup_m_polys = lookup_m_polys(&lookup_compressed_polys)?;
end_timer(timer);
Expand All @@ -172,6 +183,7 @@ where

let gamma = transcript.squeeze_challenge();

//h = (gamma + input)^-1 - m * (gamma + table)^-1
let timer = start_timer(|| format!("lookup_h_polys-{}", pp.lookups.len()));
let lookup_h_polys = lookup_h_polys(&lookup_compressed_polys, &lookup_m_polys, &gamma);
end_timer(timer);
Expand All @@ -186,6 +198,7 @@ where
);
end_timer(timer);

// Commit to h polynomiald and permutation z polynomials
let lookup_h_permutation_z_polys =
chain![lookup_h_polys.iter(), permutation_z_polys.iter()].collect_vec();
let lookup_h_permutation_z_comms =
Expand All @@ -196,6 +209,7 @@ where
let alpha = transcript.squeeze_challenge();
let y = transcript.squeeze_challenges(pp.num_vars);

// All of the polynomials in the trace committed to in the transcript
let polys = chain![
polys,
pp.permutation_polys.iter().map(|(_, poly)| poly),
Expand All @@ -204,6 +218,7 @@ where
]
.collect_vec();
challenges.extend([beta, gamma, alpha]);
// Prove the zero check is satisfied for the expression wrt the polynomials
let (points, evals) = prove_zero_check(
pp.num_instances.len(),
&pp.expression,
Expand All @@ -226,9 +241,10 @@ where
]
.collect_vec();
let timer = start_timer(|| format!("pcs_batch_open-{}", evals.len()));
// Open all polynomials at the points from the zero check and give the opening proofs
Pcs::batch_open(&pp.pcs, polys, comms, &points, &evals, transcript)?;
end_timer(timer);

// Proof is saved in transcript
Ok(())
}

Expand All @@ -238,6 +254,7 @@ where
transcript: &mut impl TranscriptRead<Pcs::CommitmentChunk, F>,
_: impl RngCore,
) -> Result<(), Error> {
//Check there is the correct amount of instances and write them to the transcript
for (num_instances, instances) in vp.num_instances.iter().zip_eq(instances) {
assert_eq!(instances.len(), *num_instances);
for instance in instances.iter() {
Expand All @@ -247,6 +264,7 @@ where

// Round 0..n

// For each round, read the witness commitments from the transcript and generate the challenges
let mut witness_comms = Vec::with_capacity(vp.num_witness_polys.iter().sum());
let mut challenges = Vec::with_capacity(vp.num_challenges.iter().sum::<usize>() + 4);
for (num_polys, num_challenges) in
Expand All @@ -260,12 +278,14 @@ where

let beta = transcript.squeeze_challenge();

// Read the commitments to the m polynomials from the lookup arguments
let lookup_m_comms = Pcs::read_commitments(&vp.pcs, vp.num_lookups, transcript)?;

// Round n+1

let gamma = transcript.squeeze_challenge();

// Read the commitments to the h polynomials and permutation z polynomials
let lookup_h_permutation_z_comms = Pcs::read_commitments(
&vp.pcs,
vp.num_lookups + vp.num_permutation_z_polys,
Expand All @@ -278,6 +298,7 @@ where
let y = transcript.squeeze_challenges(vp.num_vars);

challenges.extend([beta, gamma, alpha]);
// Verify the zero check for the constraints defined in the expression
let (points, evals) = verify_zero_check(
vp.num_vars,
&vp.expression,
Expand All @@ -299,6 +320,7 @@ where
&lookup_h_permutation_z_comms,
]
.collect_vec();
// Verify the opening proofs for the polynomials commitments
Pcs::batch_verify(&vp.pcs, comms, &points, &evals, transcript)?;

Ok(())
Expand Down
40 changes: 39 additions & 1 deletion plonkish_backend/src/backend/hyperplonk/preprocessor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ pub(crate) fn preprocess<F: PrimeField, Pcs: PolynomialCommitmentScheme<F>>(

let num_vars = circuit_info.k;
let poly_size = 1 << num_vars;
// Batch size for the polynomial commitment scheme
let batch_size = batch_size(circuit_info);
// Trim the parameters for the PCS to those necessary for the size of the circuit
let (pcs_pp, pcs_vp) = Pcs::trim(param, poly_size, batch_size)?;

// Compute preprocesses comms
Expand All @@ -56,6 +58,7 @@ pub(crate) fn preprocess<F: PrimeField, Pcs: PolynomialCommitmentScheme<F>>(
.cloned()
.map(MultilinearPolynomial::new)
.collect_vec();
// Batch commit to all pre-processing polynomials - i.e. fixed colummns/ selector columns
let (preprocess_polys, preprocess_comms) = batch_commit(&pcs_pp, preprocess_polys)?;

// Compute permutation polys and comms
Expand All @@ -66,8 +69,9 @@ pub(crate) fn preprocess<F: PrimeField, Pcs: PolynomialCommitmentScheme<F>>(
);
let (permutation_polys, permutation_comms) = batch_commit(&pcs_pp, permutation_polys)?;

// Compose expression
// Compose an expression for all the constraints
let (num_permutation_z_polys, expression) = compose(circuit_info);
// Setup parameters for verifier and prover
let vp = HyperPlonkVerifierParam {
pcs: pcs_vp,
num_instances: circuit_info.num_instances.clone(),
Expand Down Expand Up @@ -105,16 +109,21 @@ pub(crate) fn preprocess<F: PrimeField, Pcs: PolynomialCommitmentScheme<F>>(
Ok((pp, vp))
}

// compose all constraints
pub(crate) fn compose<F: PrimeField>(
circuit_info: &PlonkishCircuitInfo<F>,
) -> (usize, Expression<F>) {
//total number of challenges
let challenge_offset = circuit_info.num_challenges.iter().sum::<usize>();
// Generates three extra challenges beta, gamma, alpha
let [beta, gamma, alpha] =
&array::from_fn(|idx| Expression::<F>::Challenge(challenge_offset + idx));

// lookup_zero_checks are the sumcheck constraints in the logup GKR protocol
let (lookup_constraints, lookup_zero_checks) = lookup_constraints(circuit_info, beta, gamma);

let max_degree = max_degree(circuit_info, Some(&lookup_constraints));
// Generate constraints for the permuation argument
let (num_permutation_z_polys, permutation_constraints) = permutation_constraints(
circuit_info,
max_degree,
Expand All @@ -125,8 +134,11 @@ pub(crate) fn compose<F: PrimeField>(

let expression = {
let constraints = chain![
// constraints from halo2 frontend , i.e. custom gates
circuit_info.constraints.iter(),
// constraints from lookup argument
lookup_constraints.iter(),
// constraints from permutation argument
permutation_constraints.iter(),
]
.collect_vec();
Expand Down Expand Up @@ -159,11 +171,13 @@ pub(super) fn max_degree<F: PrimeField>(
.unwrap()
}

//generate lookup constraints using logup GKR
pub(super) fn lookup_constraints<F: PrimeField>(
circuit_info: &PlonkishCircuitInfo<F>,
beta: &Expression<F>,
gamma: &Expression<F>,
) -> (Vec<Expression<F>>, Vec<Expression<F>>) {
// Define indices where m and h polynomials begin in the trace
let m_offset = circuit_info.num_poly() + circuit_info.permutation_polys().len();
let h_offset = m_offset + circuit_info.lookups.len();
let constraints = circuit_info
Expand All @@ -172,66 +186,85 @@ pub(super) fn lookup_constraints<F: PrimeField>(
.zip(m_offset..)
.zip(h_offset..)
.flat_map(|((lookup, m), h)| {
// make m and h into polynomials, these are created during proving
let [m, h] = &[m, h]
.map(|poly| Query::new(poly, Rotation::cur()))
.map(Expression::<F>::Polynomial);
// separate the input and tables from the lookup
let (inputs, tables) = lookup
.iter()
.map(|(input, table)| (input, table))
.unzip::<_, _, Vec<_>, Vec<_>>();
// Returns a distributed power expression for the input and table, with base beta, i.e. inputs[0] + \beta inputs[1] + \beta^2 inputs[2] + ...
let input = &Expression::distribute_powers(inputs, beta);
let table = &Expression::distribute_powers(tables, beta);
// h[i] = (gamma + input[i])^-1 - m[i] * (gamma + table[i])^-1
[h * (input + gamma) * (table + gamma) - (table + gamma) + m * (input + gamma)]
})
.collect_vec();
// Every expression that must be proved in the sum check argument
let sum_check = (h_offset..)
.take(circuit_info.lookups.len())
.map(|h| Query::new(h, Rotation::cur()).into())
.collect_vec();
(constraints, sum_check)
}

// create constraints for the permutation argument
pub(crate) fn permutation_constraints<F: PrimeField>(
circuit_info: &PlonkishCircuitInfo<F>,
max_degree: usize,
beta: &Expression<F>,
gamma: &Expression<F>,
num_builtin_witness_polys: usize,
) -> (usize, Vec<Expression<F>>) {
// get index of all columns used in the permutation
let permutation_polys = circuit_info.permutation_polys();
let chunk_size = max_degree - 1;
// If there are more columns than max degree split into chunks, num_chunks corresponds to b in halo2 gitbook
let num_chunks = div_ceil(permutation_polys.len(), chunk_size);
// The offset is set to the total number of instance columns in the circuit
let permutation_offset = circuit_info.num_poly();
let z_offset = permutation_offset + permutation_polys.len() + num_builtin_witness_polys;
// Represent all columns in permutation argument with polynomials
let polys = permutation_polys
.iter()
.map(|idx| Expression::Polynomial(Query::new(*idx, Rotation::cur())))
.collect_vec();
//ids_i(X) = i 2^k + X
let ids = (0..polys.len())
.map(|idx| {
let offset = F::from((idx << circuit_info.k) as u64);
Expression::Constant(offset) + Expression::identity()
})
.collect_vec();
// Create the polynomials to represent the permutation columns
let permutations = (permutation_offset..)
.map(|idx| Expression::Polynomial(Query::new(idx, Rotation::cur())))
.take(permutation_polys.len())
.collect_vec();
// Represents Z polynomials from the permutation argument
let zs = (z_offset..)
.map(|idx| Expression::Polynomial(Query::new(idx, Rotation::cur())))
.take(num_chunks)
.collect_vec();
// Z_0(shift(X))
let z_0_next = Expression::<F>::Polynomial(Query::new(z_offset, Rotation::next()));
let l_0 = &Expression::<F>::lagrange(0);
let one = &Expression::one();
// Create the constraints for the permutation argument
// The contraints here are the like those from the halo2 gitbook but the matrix Z_0 Z_1 ... Z_{b-1} is transposed
let constraints = chain![
zs.first().map(|z_0| l_0 * (z_0 - one)),
polys
//iterating over b elements which are vectors of length m
.chunks(chunk_size)
.zip(ids.chunks(chunk_size))
.zip(permutations.chunks(chunk_size))
.zip(zs.iter())
.zip(zs.iter().skip(1).chain([&z_0_next]))
// z_a prod_{am)}^{(a+1)m-1}(poly_i + beta * id_i + gamma) - z_{a+1} prod_{am)}^{(a+1)m-1}(poly_i + beta * permutation_i + gamma)
// z_{b-1} prod_{(b-1)m)}^{bm-1}(poly_{b-1} + beta * id_{b-1} + gamma) - z_0(shift(X)) prod_{(b-1)m)}^{bm-1}(poly_{b-1} + beta * permutation_{b-1} + gamma)
.map(|((((polys, ids), permutations), z_lhs), z_rhs)| {
z_lhs
* polys
Expand All @@ -251,32 +284,37 @@ pub(crate) fn permutation_constraints<F: PrimeField>(
(num_chunks, constraints)
}

// Generate multi-linear permutation polynomials for permutation argument
pub(crate) fn permutation_polys<F: PrimeField>(
num_vars: usize,
permutation_polys: &[usize],
cycles: &[Vec<(usize, usize)>],
) -> Vec<MultilinearPolynomial<F>> {
// The index of an element in permutation_polys
let poly_index = {
let mut poly_index = vec![0; permutation_polys.last().map(|poly| 1 + poly).unwrap_or(0)];
for (idx, poly) in permutation_polys.iter().enumerate() {
poly_index[*poly] = idx;
}
poly_index
};
// permutations will be the matrix defining all permutation polynomials. As we start with the identity permutation, all entries have value of the index within the matrix.
let mut permutations = (0..permutation_polys.len() as u64)
.map(|idx| {
steps(F::from(idx << num_vars))
.take(1 << num_vars)
.collect_vec()
})
.collect_vec();
// For each cycle we update the permutation matrix. For each entry in the matrix, we have the location of the next element in the cycle.
for cycle in cycles.iter() {
let (i0, j0) = cycle[0];
let mut last = permutations[poly_index[i0]][j0];
for &(i, j) in cycle.iter().cycle().skip(1).take(cycle.len()) {
mem::swap(&mut permutations[poly_index[i]][j], &mut last);
}
}
// We generate a multilinear polynomial from each column of the permutation matrix.
permutations
.into_iter()
.map(MultilinearPolynomial::new)
Expand Down
Loading

0 comments on commit bf04eac

Please sign in to comment.