Merge pull request #815 from mir-protocol/fri_pow_in_transcript

Include the FRI prover's PoW witness in the transcript
This commit is contained in:
Daniel Lubarov 2022-11-17 12:08:48 -08:00 committed by GitHub
commit 7720ff3799
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 52 additions and 42 deletions

View File

@ -49,16 +49,8 @@ impl<F: RichField, H: Hasher<F>> Challenger<F, H> {
self.observe_extension_elements(&final_poly.coeffs);
let fri_pow_response = C::InnerHasher::hash_no_pad(
&self
.get_hash()
.elements
.iter()
.copied()
.chain(Some(pow_witness))
.collect::<Vec<_>>(),
)
.elements[0];
self.observe_element(pow_witness);
let fri_pow_response = self.get_challenge();
let fri_query_indices = (0..num_fri_queries)
.map(|_| self.get_challenge().to_canonical_u64() as usize % lde_size)
@ -105,16 +97,8 @@ impl<F: RichField + Extendable<D>, H: AlgebraicHasher<F>, const D: usize>
self.observe_extension_elements(&final_poly.0);
let pow_inputs = self
.get_hash(builder)
.elements
.iter()
.copied()
.chain(Some(pow_witness))
.collect();
let fri_pow_response = builder
.hash_n_to_hash_no_pad::<C::InnerHasher>(pow_inputs)
.elements[0];
self.observe_element(pow_witness);
let fri_pow_response = self.get_challenge(builder);
let fri_query_indices = (0..num_fri_queries)
.map(|_| self.get_challenge(builder))

View File

@ -1,4 +1,3 @@
use itertools::Itertools;
use maybe_rayon::*;
use plonky2_field::extension::{flatten, unflatten, Extendable};
use plonky2_field::polynomial::{PolynomialCoeffs, PolynomialValues};
@ -6,7 +5,8 @@ use plonky2_util::reverse_index_bits_in_place;
use crate::fri::proof::{FriInitialTreeProof, FriProof, FriQueryRound, FriQueryStep};
use crate::fri::{FriConfig, FriParams};
use crate::hash::hash_types::{HashOut, RichField};
use crate::hash::hash_types::RichField;
use crate::hash::hashing::{PlonkyPermutation, SPONGE_RATE};
use crate::hash::merkle_tree::MerkleTree;
use crate::iop::challenger::Challenger;
use crate::plonk::config::{GenericConfig, Hasher};
@ -44,11 +44,10 @@ where
);
// PoW phase
let current_hash = challenger.get_hash();
let pow_witness = timed!(
timing,
"find proof-of-work witness",
fri_proof_of_work::<F, C, D>(current_hash, &fri_params.config)
fri_proof_of_work::<F, C, D>(challenger, &fri_params.config)
);
// Query phase
@ -114,28 +113,55 @@ where
(trees, coeffs)
}
/// Performs the proof-of-work (a.k.a. grinding) step of the FRI protocol. Returns the PoW witness.
fn fri_proof_of_work<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>(
current_hash: HashOut<F>,
challenger: &mut Challenger<F, C::Hasher>,
config: &FriConfig,
) -> F {
(0..=F::NEG_ONE.to_canonical_u64())
let min_leading_zeros = config.proof_of_work_bits + (64 - F::order().bits()) as u32;
// The easiest implementation would be repeatedly clone our Challenger. With each clone, we'd
// observe an incrementing PoW witness, then get the PoW response. If it contained sufficient
// leading zeros, we'd end the search, and store this clone as our new challenger.
//
// However, performance is critical here. We want to avoid cloning Challenger, particularly
// since it stores vectors, which means allocations. We'd like a more compact state to clone.
//
// We know that a duplex will be performed right after we send the PoW witness, so we can ignore
// any output_buffer, which will be invalidated. We also know input_buffer.len() < SPONGE_WIDTH,
// an invariant of Challenger.
//
// We separate the duplex operation into two steps, one which can be performed now, and the
// other which depends on the PoW witness candidate. The first step is the overwrite our sponge
// state with any inputs (excluding the PoW witness candidate). The second step is to overwrite
// one more element of our sponge state with the candidate, then apply the permutation,
// obtaining our duplex's post-state which contains the PoW response.
let mut duplex_intermediate_state = challenger.sponge_state;
let witness_input_pos = challenger.input_buffer.len();
for (i, input) in challenger.input_buffer.iter().enumerate() {
duplex_intermediate_state[i] = *input;
}
let pow_witness = (0..=F::NEG_ONE.to_canonical_u64())
.into_par_iter()
.find_any(|&i| {
C::InnerHasher::hash_no_pad(
&current_hash
.elements
.iter()
.copied()
.chain(Some(F::from_canonical_u64(i)))
.collect_vec(),
)
.elements[0]
.to_canonical_u64()
.leading_zeros()
>= config.proof_of_work_bits + (64 - F::order().bits()) as u32
.find_any(|&candidate| {
let mut duplex_state = duplex_intermediate_state;
duplex_state[witness_input_pos] = F::from_canonical_u64(candidate);
duplex_state =
<<C as GenericConfig<D>>::Hasher as Hasher<F>>::Permutation::permute(duplex_state);
let pow_response = duplex_state[SPONGE_RATE - 1];
let leading_zeros = pow_response.to_canonical_u64().leading_zeros();
leading_zeros >= min_leading_zeros
})
.map(F::from_canonical_u64)
.expect("Proof of work failed. This is highly unlikely!")
.expect("Proof of work failed. This is highly unlikely!");
// Recompute pow_response using our normal Challenger code, and make sure it matches.
challenger.observe_element(pow_witness);
let pow_response = challenger.get_challenge();
let leading_zeros = pow_response.to_canonical_u64().leading_zeros();
assert!(leading_zeros >= min_leading_zeros);
pow_witness
}
fn fri_prover_query_rounds<

View File

@ -15,8 +15,8 @@ use crate::plonk::config::{AlgebraicHasher, GenericHashOut, Hasher};
/// Observes prover messages, and generates challenges by hashing the transcript, a la Fiat-Shamir.
#[derive(Clone)]
pub struct Challenger<F: RichField, H: Hasher<F>> {
sponge_state: [F; SPONGE_WIDTH],
input_buffer: Vec<F>,
pub(crate) sponge_state: [F; SPONGE_WIDTH],
pub(crate) input_buffer: Vec<F>,
output_buffer: Vec<F>,
_phantom: PhantomData<H>,
}