You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
We want people to be able to create their own types on top of the main type FieldVar.
We have a trait SnarkyType for that, and we're also working on a derive macro as well (e.g. #[derive(SnarkyType)]).
Because FieldVar is generic in its field (FieldVar<F: PrimeField>), SnarkyType also has to track that field.
We could use an associated type (e.g. trait SnarkyType { type F: PrimeField; /* ... */ }) but we instead chose to add a type parameter on the trait (i.e. trait SnarkyType<F>).
Problem
This means that types implemented by people will be generic, and might not take into account edge cases depending on the field.
For example, one of the Pasta field is larger than the other one, and as such a type containing a field element from the other curve can be represented in 1 or 2 elements depending on the curve you're on.
Furthermore, the check function that constrains a snarkytype to be valid might be different depending on the field you're on.
Discussion on solutions
For the reasons stated above, we might not want to have SnarkyType be generic over a field. And so, moving the field type to an associated type could be a better design.
This solution leads to more questions though:
what about general types that don't care?. For example, the boolean type should work the same on any curve, and so it should be the same for both fields.
We might want to simply have it generic at first:
pub struct BooleanVar<F>
but have two implementations:
impl SnarkyType for BooleanVar<Fp> { type Field = Fp; /* ... */ }
impl SnarkyType for BooleanVar<Fq> { type Field = Fq; /* ... */ }
but this mean having redundant code. One solution could be to have macros to easily derive the two implementations.
what about the derive macro? What does it mean for the derive macro if we use an associated type? I think we could specialize it like this: #[derive(SnarkyType(Fp))]. And if we don't specify Fp or Fq perhaps we could derive both implementations automatically.
In general though, I would imagine that a type that needs to be different on one curve or the other would need to have its own SnarkyType implementations anyway. So I suggest that we don't expose specialization of the field in the derive macro, and just always derive the two fields implementations automatically.
what about other fields? At the moment we only support the Pasta Fp and Fq fields. But what if we wanted to support more fields down the line? The generic approach allows us not to corner ourselves into a specific field (or set of fields) which is nice...
So perhaps, the associated type is not a good solution. Rather, in the case where a type really needs a SnarkyType implementation specific to a field, we could simply implement it for that specific field (instead of a general SnarkyType implementation):
implSnarkyType<Fp>forYourType{/* ... */}
The solution is thus to keep SnarkyType generic over a field (i.e. SnarkyType<F: PrimeField>).
Note: a SnarkyCircuit already fixes and forces a user into a curve/field:
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
We want people to be able to create their own types on top of the main type
FieldVar
.We have a trait
SnarkyType
for that, and we're also working on a derive macro as well (e.g.#[derive(SnarkyType)]
).Because
FieldVar
is generic in its field (FieldVar<F: PrimeField>
),SnarkyType
also has to track that field.We could use an associated type (e.g.
trait SnarkyType { type F: PrimeField; /* ... */ }
) but we instead chose to add a type parameter on the trait (i.e.trait SnarkyType<F>
).Problem
This means that types implemented by people will be generic, and might not take into account edge cases depending on the field.
For example, one of the Pasta field is larger than the other one, and as such a type containing a field element from the other curve can be represented in 1 or 2 elements depending on the curve you're on.
Furthermore, the
check
function that constrains a snarkytype to be valid might be different depending on the field you're on.Discussion on solutions
For the reasons stated above, we might not want to have
SnarkyType
be generic over a field. And so, moving the field type to an associated type could be a better design.This solution leads to more questions though:
what about general types that don't care?. For example, the boolean type should work the same on any curve, and so it should be the same for both fields.
We might want to simply have it generic at first:
but have two implementations:
but this mean having redundant code. One solution could be to have macros to easily derive the two implementations.
what about the derive macro? What does it mean for the derive macro if we use an associated type? I think we could specialize it like this:
#[derive(SnarkyType(Fp))]
. And if we don't specifyFp
orFq
perhaps we could derive both implementations automatically.In general though, I would imagine that a type that needs to be different on one curve or the other would need to have its own
SnarkyType
implementations anyway. So I suggest that we don't expose specialization of the field in the derive macro, and just always derive the two fields implementations automatically.what about other fields? At the moment we only support the Pasta Fp and Fq fields. But what if we wanted to support more fields down the line? The generic approach allows us not to corner ourselves into a specific field (or set of fields) which is nice...
So perhaps, the associated type is not a good solution. Rather, in the case where a type really needs a
SnarkyType
implementation specific to a field, we could simply implement it for that specific field (instead of a generalSnarkyType
implementation):The solution is thus to keep
SnarkyType
generic over a field (i.e.SnarkyType<F: PrimeField>
).Note: a
SnarkyCircuit
already fixes and forces a user into a curve/field:A useful derive macro
In general, we want two derive macro: one for types that are just dumb containers, and one for types that aren't just dumb containers.
Dumb containers
The first one just cares about creating a SnarkyType that is a tuple of all its SnarkyType fields.
For example, you can see this implementation on a tuple of two
SnarkyType
s:in this case, the derive macro
#[derive(SnarkyType)]
can simply do the following:F: PrimeField
(because we need to reuse thatF
in ourimpl<F> SnarkyType<F>
)SnarkyType
as wellSnarkyType
that follows the pattern aboveNot dumb containers
The second type is usually different in two ways:
check
functionauxiliary
type and functionSnarkyType
One way is to implement it using a derive macro that specifies the implementations, as well as specific attributes to skip some fields:
I don't think there's really another way to do it, as we need access to the structure in order to understand how to implement the different functions.
For handling the out-of-circuit value, we could require a trait to be implement if
#[snarky(value = _)]
is passed:Specific fields
I think we can even go further to address the previously discussed constraint of types that only work for a specific field. For example:
Implementation
The derive macro was implemented here: https://github.com/o1-labs/proof-systems/blob/mimoo/snarky2/kimchi/snarky-deriver/src/lib.rs
Beta Was this translation helpful? Give feedback.
All reactions