arkworks-rs / r1cs-std

R1CS constraints for bits, fields, and elliptic curves
https://www.arkworks.rs
Apache License 2.0
133 stars 58 forks source link

make nonnative gadget params configurable #129

Open slumber opened 1 year ago

slumber commented 1 year ago

Depending on a goal, some developers would want to have a specific configuration perhaps with a smaller number of limbs.

This change is almost backwards compatible and actually breaks some code in ark-crypto-primitives, but it tries to follow the pattern from Rust std like "Vec<T, A: Allocator = Global>"

Hence the new definition

pub enum NonNativeFieldVar<
    TargetField: PrimeField,
    BaseField: PrimeField,
    P: Params = DefaultParams, // <-- opt in to define your own.
> {
    /// Constant
    Constant(TargetField),
    /// Allocated gadget
    Var(AllocatedNonNativeFieldVar<TargetField, BaseField, P>),
}

Likewise for AllocatedNonNativeFieldVar, NonNativeFieldMulResultVar, AllocatedNonNativeFieldMulResultVar.


Before we can merge this PR, please make sure that all the following items have been checked off. If any of the checklist items are not applicable, please leave them but write a little note why.

slumber commented 11 months ago

@Pratyush @weikengchen can you please take a look?

mmagician commented 11 months ago

@slumber Could you also provide the corresponding fix for crypto-primitives?

slumber commented 11 months ago

@slumber Could you also provide the corresponding fix for crypto-primitives?

I will once we're done with this PR.

slumber commented 11 months ago

@weikengchen @Pratyush can we have another iteration on this one?

Pratyush commented 8 months ago

@slumber, sorry for the delay on this! Could you provide some example alternative instantiations of Params? That way we can get a sense of the why the new API looks the way it does. Thanks!

slumber commented 8 months ago

@slumber, sorry for the delay on this! Could you provide some example alternative instantiations of Params? That way we can get a sense of the why the new API looks the way it does. Thanks!

@Pratyush thank you for getting back to the PR.

An example is computing poseidon hash. I'd like to minimize the number of limbs in emulated variables, but keep "constraints" optimizations target.

This is a sketch, but the idea should be clear; Knowing that the minimum legal number of limbs is 3 (https://github.com/arkworks-rs/r1cs-std/issues/128):

const MIN_SUPPORTED_LIMBS: usize = 3;

pub struct MinLimbsParams;

impl Params for MinLimbsParams {
    fn get<TargetField: PrimeField, BaseField: PrimeField>(
        _optimization_type: OptimizationType, // always minimize
    ) -> NonNativeFieldConfig {
        let target_bits = TargetField::MODULUS_BIT_SIZE;
        let base_bits = BaseField::MODULUS_BIT_SIZE;

        let num_limbs = if target_bits >= base_bits {
            MIN_SUPPORTED_LIMBS
        } else {
            (base_bits / target_bits).max(MIN_SUPPORTED_LIMBS)
        };
        let bits_per_limb = (base_bits / num_limbs).next_power_of_two();

        NonNativeFieldConfig {
            num_limbs: num_limbs as usize,
            bits_per_limb: bits_per_limb as usize,
        }

    }
}

Why put it as a generic parameter:

  1. It's the most flexible option: you can hardcode, use default, use anything custom like I did above.
  2. Keeps the type safety. You can't multiply Var<F1, F2, P1> by Var<F1, F2, P2>.
Pratyush commented 7 months ago

The primary concern I have is that users will need to specify an additional type parameter whenever they want to use EmulatedFpVar generically.

This happens, for example, here: https://github.com/arkworks-rs/r1cs-std/blob/4020fbc22625621baa8125ede87abaeac3c1ca26/src/groups/mod.rs#L51 . In this case, we want to support scalar multiplication by any EmulatedFpVar as long as it is for the right field.

Would it be possible to have a different way to track the parameters? E.g. via a Box<dyn EmulationConfig> that's stored within the struct?

slumber commented 7 months ago

The primary concern I have is that users will need to specify an additional type parameter whenever they want to use EmulatedFpVar generically.

This happens, for example, here:

https://github.com/arkworks-rs/r1cs-std/blob/4020fbc22625621baa8125ede87abaeac3c1ca26/src/groups/mod.rs#L51

.

Yes, this bound wasn't present at the time of writing this PR. Being required to specify config in a trait definition doesn't appeal to me either.

In this case, we want to support scalar multiplication by any EmulatedFpVar as long as it is for the right field. Would it be possible to have a different way to track the parameters? E.g. via a Box<dyn EmulationConfig> that's stored within the struct?

partially:

If you think it's OK then I'll reimplement it with suggested change.

slumber commented 7 months ago

@Pratyush on second thought, I don't think it's going to work because of object safety rules, at least I couldn't make it work in the playground

slumber commented 7 months ago

@Pratyush what do you think about storing configuration directly in the struct:

num_limbs: usize,
bits_per_limb: usize,

And whenever you want to use non-default one you provide generic EmulatedConfig, for example to squeeze_non_native or new_variable_with_config