Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add plonky3 support #1158

Open
wants to merge 28 commits into
base: main
Choose a base branch
from
Open

Add plonky3 support #1158

wants to merge 28 commits into from

Conversation

Schaeff
Copy link
Collaborator

@Schaeff Schaeff commented Mar 13, 2024

plonky3 backend integration. It uses Poseidon with a random array of constants, which acts as a setup. I am not sure this is required, maybe using a fixed constant array is fine, in which case a lot of logic around setup and verification key disappear.

number/src/traits.rs Outdated Show resolved Hide resolved
@Schaeff Schaeff force-pushed the support-plonky3 branch 2 times, most recently from c934ab6 to d7ac075 Compare April 4, 2024 15:56
@Schaeff Schaeff mentioned this pull request Apr 4, 2024
@Schaeff Schaeff changed the base branch from main to upgrade-to-rust-1-77 April 4, 2024 16:24
github-merge-queue bot pushed a commit that referenced this pull request Apr 4, 2024
Base automatically changed from upgrade-to-rust-1-77 to main April 4, 2024 20:49
@Schaeff Schaeff marked this pull request as ready for review May 29, 2024 13:31
Copy link
Member

@leonardoalt leonardoalt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checked all interfaces but not the plonky3 crate yet

pipeline/tests/pil.rs Outdated Show resolved Hide resolved
.with_prover_inputs(inputs)
.with_backend(powdr_backend::BackendType::Plonky3, None);

// Generate a proof with the setup and verification key generated on the fly
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Generate a proof with the setup and verification key generated on the fly
// Generate a proof with the verification key generated on the fly

I guess setup doesn't apply here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the description of the PR to explain why setup does apply here.

pipeline/src/test_util.rs Outdated Show resolved Hide resolved
pipeline/src/test_util.rs Outdated Show resolved Hide resolved
number/Cargo.toml Outdated Show resolved Hide resolved
@@ -63,11 +62,11 @@ pub struct Artifacts<T: FieldElement> {
/// An analyzed .pil file, with all dependencies imported, potentially from other files.
analyzed_pil: Option<Analyzed<T>>,
/// An optimized .pil file.
optimized_pil: Option<Rc<Analyzed<T>>>,
optimized_pil: Option<Arc<Analyzed<T>>>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in general because of plonky3 trait bounds, but maybe the change was not required on this struct, will check.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok it is, because from Pipeline we create WitnessCallback, which is included in PowdrCircuit, which implements BaseAir, which requires Sync, which isn't implemented on Rc. So we need Arc everywhere.

@@ -49,15 +48,15 @@ impl<T, F> QueryCallback<T> for F where F: Fn(&str) -> Result<Option<T>, String>

#[derive(Clone)]
pub struct WitgenCallback<T> {
analyzed: Rc<Analyzed<T>>,
fixed_col_values: Rc<Vec<(String, Vec<T>)>>,
analyzed: Arc<Analyzed<T>>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above

pil: &'a Analyzed<T>,
fixed: &'a [(String, Vec<T>)],
_output_dir: Option<&'a Path>,
setup: Option<&mut dyn io::Read>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the setup here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

Copy link
Member

@leonardoalt leonardoalt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!!

plonky3/Cargo.toml Outdated Show resolved Hide resolved
plonky3/Cargo.toml Outdated Show resolved Hide resolved

use self::types::*;

/// In the context of plonky3, "setup" generation is the process of instantiating Poseidon with randomly sampled constants
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to static

/// The constants which uniquely determine the prover, loosely interpreted as a setup
setup: Constants,
/// The constants which uniquely determine the prover, loosely interpreted as a verification key
vkey: Option<Constants>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the same as above? Shouldn't the vkey depend on the circuit as well?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good call, the verifier I'm using somehow depends on the air still, hmm

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I think it's fine as is, when you call verify the prover has access to the circuit

#[cfg(test)]
mod tests {
use powdr_number::GoldilocksField;
use powdr_pipeline::Pipeline;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure we should have Pipeline as a dependency here, even as a dev-dep. Maybe better to keep these tests together with the others?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this be fixed by moving plonky3 into backend?

@leonardoalt
Copy link
Member

Maybe @georgwiese can take a look at the circuit builder too?

@Schaeff Schaeff requested a review from leonardoalt June 6, 2024 12:22
Copy link
Collaborator

@georgwiese georgwiese left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really cool! I got a few questions & comments.

}

impl<'a, T: FieldElement> Plonky3Prover<'a, T> {
pub fn prove_ast(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does ast stand for?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question 😆 I was sure I copied this from halo2. Changing to prove

Comment on lines 34 to 36
pub fn cast_to_goldilocks<T: FieldElement>(v: T) -> Val {
Val::from_canonical_u64(v.to_integer().try_into_u64().unwrap())
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we write it in a way that it would fail for any non-Goldilocks field? Now it would fail for BN254 (because try_into_u64() fails), but I think it also fail for other fields that fit into u64 that we might have in the future.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

/// The analyzed PIL
analyzed: &'a Analyzed<T>,
/// The number of committed polynomials, computed from `analyzed` and cached
commitment_count: usize,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This field is redundant, it should equalanalyzed.commitment_count(), right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. I don't really like it but that method iterates through all definitions in the program and is needed to convert each reference. Seemed like a bit much for a number which never changes, but I didn't measure. I could alternatively pass it around in the functions so it's not in the state here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or even remove it completely, and eventually if it's an issue it should be cached from within Analyzed

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

Comment on lines 115 to 116
PolynomialType::Committed => r.poly_id.id as usize,
PolynomialType::Constant => self.commitment_count + r.poly_id.id as usize,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the Poly ID assumes that they are consecutive. I think that should be the case, but could you add an assertion?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a check that no poly_id can exceed the total count of polynomials of each kind. It does not prove that all declared polynomials are referenced somewhere, so it could technically be non-consecutive, but it makes sure there is no overlap in the plonky3 ids, which I think is what you want?

fn to_plonky3_expr<AB: AirBuilder<F = Val>>(
&self,
e: &AlgebraicExpression<T>,
builder: &AB,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just receive the row pair / matrix?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, although it requires more type annotations

let left =
self.to_plonky3_expr(identity.left.selector.as_ref().unwrap(), builder);

builder.assert_zero(left);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know why Plonky3 has a built-in is_first_row? I guess it saves a fixed column, because it is just the first Lagrange polynomial can be evaluated efficiently by the verifier?

We could detect when we're using a fixed column of the form [1] + [0]* and use the built-in. But if it only saves 1 commitment during setup + evaluation proof, it might be just a slight advantage and not worth the hassle now.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember Miden also has special treatment of the first row, so you could be right. I think this can be a separate issue

builder.assert_zero(left);
}
IdentityKind::Plookup => unimplemented!("Plonky3 does not support plookup"),
IdentityKind::Permutation => unimplemented!("Plonky does not support permutations"),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
IdentityKind::Permutation => unimplemented!("Plonky does not support permutations"),
IdentityKind::Permutation => unimplemented!("Plonky3 does not support permutations"),

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 90 to 93
self.publics
.iter()
.map(|(col_name, i)| cast_to_goldilocks(witness.get(col_name).unwrap()[*i]))
.collect()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you actually constrain them somewhere? If not, I think we should assert that the publics are empty for now.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I did not implement that logic in eval. Removing as it can be a separate PR.

Comment on lines +1 to +2
//! The concrete parameters used in the prover
//! Inspired from [this example](https://github.com/Plonky3/Plonky3/blob/6a1b0710fdf85136d0fdd645b92933615867740a/keccak-air/examples/prove_goldilocks_keccak.rs#L57)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, this looks like something a cryptographer should have a say on. I guess for now it doesn't really matter, except if it has performance implications?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed

Copy link
Collaborator

@georgwiese georgwiese left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do fixed columns work? The comment says "Since plonky3 does not have fixed columns", but it needs to right? Otherwise, we're not proving the right statement?

I think we need the equivalent of this code somewhere to commit to fixed columns during setup.

self.commitment_count + self.constant_count
}

fn preprocessed_trace(&self) -> Option<RowMajorMatrix<Val>> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know why this is actually in the BaseAir trait (doesn't seem to be called by Plonky3?), but from the name I think they mean this to return fixed columns only.

In SP1, they actually don't implement this function (they panic when it's called) and instead implement this trait, which has both generate_preprocessed_trace() and generate_trace(), which return fixed and committed columns.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants