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
PI handling & Circuit Builder trait refactor #423
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Few nits
src/constraint_system/arithmetic.rs
Outdated
@@ -74,7 +74,9 @@ impl StandardComposer { | |||
self.q_fixed_group_add.push(BlsScalar::zero()); | |||
self.q_variable_group_add.push(BlsScalar::zero()); | |||
|
|||
self.public_inputs.push(pi); | |||
if let Some(pi) = pi { | |||
assert!(self.public_inputs_sparse_store.insert(self.n, pi).is_none()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe we should go in a direction to reduce the number of assertions inside plonk functions, not increase it. If the PI must fail in this case, then the function should return a Result
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's unclear to me the reason why we have such assertion, so I cannot judge it properly.
So, first of all I would add a comment about why this is needed.
Basically it should clarify:
- Why
assert!
is needed? - Why
assert!
instead ofdebug_assert!
?
If we're going to keep the it, we also should add a message so if it fails we know what failed and why.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should never happen. That's why it has an assert!
it's like unreachable!()
for me here. I just have it so that if at some point this assertion fails. We know that we made a change in a really wrong direction.
src/constraint_system/arithmetic.rs
Outdated
@@ -153,7 +155,9 @@ impl StandardComposer { | |||
self.q_fixed_group_add.push(BlsScalar::zero()); | |||
self.q_variable_group_add.push(BlsScalar::zero()); | |||
|
|||
self.public_inputs.push(pi); | |||
if let Some(pi) = pi { | |||
assert!(self.public_inputs_sparse_store.insert(self.n, pi).is_none()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unnecessary assertion, check above
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as mentioned before.
self.public_inputs_sparse_store | ||
.keys() | ||
.map(|pos| *pos) | ||
.collect::<Vec<usize>>() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unnecessary type annotation
@@ -206,7 +229,9 @@ impl StandardComposer { | |||
self.q_fixed_group_add.push(BlsScalar::zero()); | |||
self.q_variable_group_add.push(BlsScalar::zero()); | |||
|
|||
self.public_inputs.push(pi); | |||
if let Some(pi) = pi { | |||
assert!(self.public_inputs_sparse_store.insert(self.n, pi).is_none()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unnecessary assertion, check above
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment I made in the previous one.
src/circuit_builder.rs
Outdated
#[cfg_attr(feature = "canon", derive(Canon))] | ||
/// Structure that represents a PLONK Circuit Public Input | ||
/// structure converted into it's &[BlsScalar] repr. | ||
pub struct PublicInputValue(pub(crate) Vec<BlsScalar>); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The name suggests its a single public input when its in fact a collection
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think is correct: A single public input value is defined by one or more BlsScalar.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And in fact it is a Public Input. But expressed on it's BlsScalar form. And as you know. Since we have 3 types that are Public Inputs and one of them (AffinePoint
) gets represented as 2 BlsScalar instead of one.
Therefore the best way we've found with @ZER0 for now is to return a Vec<BlsScalar>
which will be 1 item for BlsScalar and JubJubScalar and 2 for AffinePoint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before it was an enum. Do we have a gain to convert from enum to this Vec
structure? Also, if we know the size to be either 1 or 2, any of these simpler structures will also do:
(BlsScalar, Option<BlsScalar>)
[Option<BlsScalar>; 2]
[BlsScalar; 2]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But Vec seemed much simpler than that. The only one that I might adopt is the Array. But is more missleading since makes it look like it's always 2 arrays instead of one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As alternative we could use const generic also here, you can play with this if you want and see how the API looks like.
src/circuit_builder.rs
Outdated
bytes | ||
} | ||
} | ||
impl From<BlsScalar> for PublicInputValue { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why should we reconstruct a collection of public inputs from a single scalar?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not a collection of public input, is a single public input. Maybe we could add a more descriptive documentation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its confusing to have a Vec
and not expect a variable size collection
src/circuit_builder.rs
Outdated
_ => unreachable!(), | ||
} | ||
} | ||
impl From<JubJubScalar> for PublicInputValue { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Single scalar should not result in a collection; check above
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case yes. Basically a PublicInputValue
in our case can be one or more (currently two) BlsScalar
. But it doesn't matter the underling representation, the entity is still a single PublicInputValue
.
src/circuit_builder.rs
Outdated
PublicInput::JubJubScalar(_, pos) => [*pos, 0], | ||
PublicInput::AffinePoint(_, pos_x, pos_y) => [*pos_x, *pos_y], | ||
} | ||
impl From<JubJubAffine> for PublicInputValue { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Single point should not result in a collection, check above
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, yes. A single point can be decoded as two BlsScalar
(x
and y
in this case).
src/circuit_builder.rs
Outdated
/// Initialization string used to fill the transcript for both parties. | ||
const TRANSCRIPT_INIT: &'static [u8]; | ||
/// Trimming size for the keys of the circuit. | ||
const TRIM_SIZE: usize; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TRIM_SIZE
can't be constant for a circuit. One example is the transfer circuit that has variable circuit size depending on the number of inputs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's talk about it tomorrow morning. It's crucial to fully understand the edge case of transfer circuit, it's the only anomaly there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#351 is only about the set_trim_size
that should never be called, as in the proposal inside the issue
Proposal: remove
Circuit::set_trim_size
However, the get_trim_size
(or any other method name with the same effect) will return the current size for that circuit. There is the "con" to tie the circuit instance to a fixed size trim without any clear "pro"
src/circuit_builder.rs
Outdated
pub_input_pos: &PublicInputPositions, | ||
) -> Vec<BlsScalar> { | ||
let mut pi = vec![BlsScalar::zero(); Self::TRIM_SIZE]; | ||
println!("{:?}", pub_input_pos); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove println
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Few things to fix, but overall is good.
src/constraint_system/arithmetic.rs
Outdated
@@ -74,7 +74,9 @@ impl StandardComposer { | |||
self.q_fixed_group_add.push(BlsScalar::zero()); | |||
self.q_variable_group_add.push(BlsScalar::zero()); | |||
|
|||
self.public_inputs.push(pi); | |||
if let Some(pi) = pi { | |||
assert!(self.public_inputs_sparse_store.insert(self.n, pi).is_none()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's unclear to me the reason why we have such assertion, so I cannot judge it properly.
So, first of all I would add a comment about why this is needed.
Basically it should clarify:
- Why
assert!
is needed? - Why
assert!
instead ofdebug_assert!
?
If we're going to keep the it, we also should add a message so if it fails we know what failed and why.
src/constraint_system/arithmetic.rs
Outdated
@@ -153,7 +155,9 @@ impl StandardComposer { | |||
self.q_fixed_group_add.push(BlsScalar::zero()); | |||
self.q_variable_group_add.push(BlsScalar::zero()); | |||
|
|||
self.public_inputs.push(pi); | |||
if let Some(pi) = pi { | |||
assert!(self.public_inputs_sparse_store.insert(self.n, pi).is_none()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as mentioned before.
src/circuit_builder.rs
Outdated
#[cfg_attr(feature = "canon", derive(Canon))] | ||
/// Structure that represents a PLONK Circuit Public Input | ||
/// structure converted into it's &[BlsScalar] repr. | ||
pub struct PublicInputValue(pub(crate) Vec<BlsScalar>); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think is correct: A single public input value is defined by one or more BlsScalar.
src/circuit_builder.rs
Outdated
bytes | ||
} | ||
} | ||
impl From<BlsScalar> for PublicInputValue { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not a collection of public input, is a single public input. Maybe we could add a more descriptive documentation.
src/circuit_builder.rs
Outdated
_ => unreachable!(), | ||
} | ||
} | ||
impl From<JubJubScalar> for PublicInputValue { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case yes. Basically a PublicInputValue
in our case can be one or more (currently two) BlsScalar
. But it doesn't matter the underling representation, the entity is still a single PublicInputValue
.
src/constraint_system/composer.rs
Outdated
.keys() | ||
.map(|pos| *pos) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.keys() | |
.map(|pos| *pos) | |
.keys() | |
.copied() |
src/constraint_system/arithmetic.rs
Outdated
+ (q_r * b_eval) | ||
+ (q_4 * d_eval) | ||
+ q_c | ||
+ pi.unwrap_or(BlsScalar::zero()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+ pi.unwrap_or(BlsScalar::zero()); | |
+ pi.unwrap_or_default(); |
or:
+ pi.unwrap_or(BlsScalar::zero()); | |
+ pi.unwrap_or_else(BlsScalar::zero); |
src/constraint_system/arithmetic.rs
Outdated
@@ -268,7 +276,8 @@ impl StandardComposer { | |||
let a_eval = self.variables[&a]; | |||
let b_eval = self.variables[&b]; | |||
let d_eval = self.variables[&d]; | |||
let c_eval = (q_m * a_eval * b_eval) + (q_4 * d_eval) + q_c + pi; | |||
let c_eval = | |||
(q_m * a_eval * b_eval) + (q_4 * d_eval) + q_c + pi.unwrap_or(BlsScalar::zero()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned in the comment above.
src/constraint_system/composer.rs
Outdated
self.public_inputs_sparse_store | ||
.keys() | ||
.zip(self.public_inputs_sparse_store.values()) | ||
.for_each(|(pos, value)| { | ||
pi[*pos] = *value; | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're zipping yourself. :) It make sense when you have the keys and the values on two different objects, but here the object is just one. You should be able just to iterate it to get the entries (a tuple (key, value)
).
@@ -206,7 +229,9 @@ impl StandardComposer { | |||
self.q_fixed_group_add.push(BlsScalar::zero()); | |||
self.q_variable_group_add.push(BlsScalar::zero()); | |||
|
|||
self.public_inputs.push(pi); | |||
if let Some(pi) = pi { | |||
assert!(self.public_inputs_sparse_store.insert(self.n, pi).is_none()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment I made in the previous one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM after the nits pointed by @ZER0 are addressed.
Looking forward to this merge; will help a lot the circuits development
13769e4
to
d2358f7
Compare
It wasted a lot of time and also was useless to call the `gadget` fn again for the verify fn in the Circuit trait. Thanks to the refactor done to the PublicInputs and how they're handled now we no longer need to call it.
Previously we were storing the dense representation of the public inputs that were used with the `Composer` instance. This was constly, since most of the public inputs were zero and therefore storing them was useless but also we needed to collect the position information in the Circuit trait in such difficult ways. With this refactor we currently have: - Public Inputs are now passed to gate-functions as `Option<BlsScalar>`. - The `Composer` no longer stores the dense public input vector. Instead we store two sparse vectors which contain the values and the positions. - We include a function that allows the `Composer` to construct the dense PI vector from it's sparse ones. - We've added ways for the consumer to get access to the positions vector. This will allow to simplify the public input management on the `Circuit` trait.
After discussions with @ZER0 we realized there's not a need to have a really big trait to handle the public inputs and all we need is a simple structure that implements the conversions from the different PI types that we can have in PLONK circuits into the format that PLONK requires which is `&[BlsScalar]`. - Created `PublicInputValue` which is implemented for `BlsScalar`, `JubJubScalar` and `JubJubAffine`. - Forced the `Circuit` trait to use this struct as source of the public input values needed in the verification step.
Instead of storing the sparse representation of the non-zero PublicInputs as two different `Vec` which are logically connected but not tecnollogically(code-wise). With this change we can have them strictly correlated since they're stored linked in the `HashMap`.
We need to return the PI positons(and store them) in order since otherways when consumers ask for these data, it would be returned wrongly if we don't return it ordered.
This also allows to test wether the support for the PI handling of the Circuit trait is correct.
To allow (although it's not recommended to do) variable-size circuits, we need a way to express different circuit sizes (TRIM_SIZE) for the same circuit. By setting the `Circuit::TRIM_SIZE` inherit the value from the const generic parameter N, we enable this behaviour leaving an API that is not bad.
Since these two functions didn't need `Self` or `self` and also were independent, we've been able to remove them from the trait. This closes #396 and also allows us to have a generic method that can verify Proofs of any `Circuit` without needing to have access to the type of it.
As it was mentioned in the latest iteration, for plonk v0.6 we wanted to improve the API that the
Circuit
trait was exposing and forcing users to implement as well as find better ways of handling the public inputs.Finally with this PR, we've been able to close some issues related to the problems mentioned above. Here is a summary of the problems that the PR addresses:
Composer
in a dense way. While we're still not sure that storing them inside is the right thing, we know that is helpful to have them for quick testing and also as a support for theCircuit
trait public inputs management. Therefore, we're now storing the sparse repr of the Values and positions in aBTreeMap
instead. Closes Store Public Inputs inside of the Composer on a sparse way #427proof
andverify
. Closes Removeset_trim_size
fromCircuit
#351. Closes Remove trim_size related fns for circuit and use trait associated constants istead + min_const_generics #389PublicInputValue
which is responsible of also implement the conversions from the Public Input types to theBlsScalar
"lingua franca". Closes Create a new struct for the Circuit Builder in order to serialize the Public Input position #416Circuit
implementations by abstracting the PI management and moving it inside of the `Composer. Closes Take profit of the Composer and use it as a backend manager of the Circuit Public Inputs #428Circuit::verify_proof()
no longer callsgadget()
inside which was unnecessary and a waste of time and resources. Closes Circuit::verify_proof() should not call gadget inside of it #426Self
orself
and also were independent, we've been able to remove them from the trait. Also allows us to have a generic method that can verify Proofs of anyCircuit
without needing to have access to the type of it. Closes Circuit builder - verify proof should not require&self
#396