diff --git a/CHANGELOG.md b/CHANGELOG.md index 5041df4..0baf48a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,8 +23,8 @@ - Add `UInt::rotate_{left,right}_in_place`. - Add `{Boolean,UInt}::not_in_place`. - Add `UInt::{from_bytes_le, from_bytes_be, to_bytes_be}`. -- [\#144](https://github.com/arkworks-rs/r1cs-std/pull/144) - - Add `ToConstraintFieldGadget` bounds to `CurveVar` and `FieldVar` +- [\#143](https://github.com/arkworks-rs/r1cs-std/pull/143) Add `AllocVar::new_variable_with_inferred_mode`. +- [\#144](https://github.com/arkworks-rs/r1cs-std/pull/144) Add `ToConstraintFieldGadget` bounds to `CurveVar` and `FieldVar` ### Improvements diff --git a/src/alloc.rs b/src/alloc.rs index ccce47f..d961405 100644 --- a/src/alloc.rs +++ b/src/alloc.rs @@ -76,6 +76,53 @@ pub trait AllocVar: Sized { ) -> Result { Self::new_variable(cs, f, AllocationMode::Witness) } + + /// Allocates a new constant or private witness of type `Self` in the + /// `ConstraintSystem` `cs` with the allocation mode inferred from `cs`. + /// A constant is allocated if `cs` is `None`, and a private witness is + /// allocated otherwise. + /// + /// A common use case is the creation of non-deterministic advice (a.k.a. + /// hints) in the circuit, where this method can avoid boilerplate code + /// while allowing optimization on circuit size. + /// + /// For example, to compute `x_var / y_var` where `y_var` is a non-zero + /// variable, one can write: + /// ``` + /// use ark_ff::PrimeField; + /// use ark_r1cs_std::{alloc::AllocVar, fields::{fp::FpVar, FieldVar}, R1CSVar}; + /// use ark_relations::r1cs::SynthesisError; + /// + /// fn div(x_var: &FpVar, y_var: &FpVar) -> Result, SynthesisError> { + /// let cs = x_var.cs().or(y_var.cs()); + /// let z_var = FpVar::new_variable_with_inferred_mode(cs, || Ok(x_var.value()? / y_var.value()?))?; + /// z_var.mul_equals(y_var, x_var)?; + /// Ok(z_var) + /// } + /// ``` + /// In this example, if either `x_var` or `y_var` is a witness variable, + /// then `z_var` is also a witness variable. On the other hand, `z_var` + /// is a constant if both `x_var` and `y_var` are constants (i.e., `cs` + /// is `None`), and future operations on `z_var` do not generate any + /// constraints. + /// + /// (Note that we use division as an example for simplicity. You may + /// call `x_var.mul_by_inverse(y_var)?` directly, which internally works + /// similarly to the above code.) + #[tracing::instrument(target = "r1cs", skip(cs, f))] + fn new_variable_with_inferred_mode>( + cs: impl Into>, + f: impl FnOnce() -> Result, + ) -> Result { + let ns: Namespace = cs.into(); + let cs = ns.cs(); + let mode = if cs.is_none() { + AllocationMode::Constant + } else { + AllocationMode::Witness + }; + Self::new_variable(cs, f, mode) + } } /// This blanket implementation just allocates variables in `Self`