Repository: arkworks-rs/r1cs-std (language: rust)

Issue:
Convenient method for context-based variable/constant allocation
<!-- < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < ☺ 
v                            ✰  Thanks for opening an issue! ✰    
v    Before smashing the submit button please review the template.
v    Word of caution: poorly thought-out proposals may be rejected 
v                     without deliberation 
☺ > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >  -->

## Summary

Add a new method to `AllocVar` that creates a witness variable or constant in the constraint system depending on the type of `cs`.

<!-- Short, concise description of the proposed feature -->

## Problem Definition

When feeding non-deterministic advice for some computation $f$ to a circuit, it is often desirable to call `new_variable` with a `mode` based on the context. That is, when the inputs to $f$ are all constant, we can choose `AllocationMode::Constant` for the advice to save some constraints for enforcing its validity. Otherwise, we need to choose `AllocationMode::Witness`.

However, doing so manually introduces redundancy. We can find many duplicated code fragments for this purpose in this repo, e.g.,

https://github.com/arkworks-rs/r1cs-std/blob/4020fbc22625621baa8125ede87abaeac3c1ca26/src/groups/curves/twisted_edwards/mod.rs#L120-L138
https://github.com/arkworks-rs/r1cs-std/blob/4020fbc22625621baa8125ede87abaeac3c1ca26/src/fields/quadratic_extension.rs#L279-L291

Furthermore, taking care for every advice allocation increases the mental burden of a circuit developer, while failing to do so may result in a larger circuit.

<!-- Why do we need this feature? 
What problems may be addressed by introducing this feature?
Are there any disadvantages of including this feature? -->

## Proposal

<!-- Detailed description of requirements of implementation -->
This proposal aims to address the above problem by introducing a new method (tentatively dubbed `new_hint`) to `AllocVar`, which creates a constant if `cs` is `None` and a witness variable otherwise. The logic of `new_hint` should be similar as below:

```rs
pub trait AllocVar<V: ?Sized, F: Field>: Sized {
    // ...
    #[tracing::instrument(target = "r1cs", skip(cs, f))]
    fn new_hint<T: Borrow<V>>(
        cs: impl Into<Namespace<F>>,
        f: impl FnOnce() -> Result<T, SynthesisError>,
    ) -> Result<Self, SynthesisError> {
        let ns: Namespace<F> = cs.into();
        let cs = ns.cs();
        let mode = if cs.is_none() {
            AllocationMode::Constant
        } else {
            AllocationMode::Witness
        };
        Self::new_variable(cs, f, mode)
    }
    // ...
}
```

Please let me know what you think, and I can create a PR if this proposal looks good to you :)

____

#### For Admin Use

- [ ] Not duplicate issue
- [ ] Appropriate labels applied
- [ ] Appropriate contributors tagged
- [ ] Contributor assigned/self-assigned



Discussion / hints:
This looks like a good idea to me! Maybe we can call it `new_variable_with_inferred_mode`? Or some variant.
Sure, `new_variable_with_inferred_mode` also sounds better to me! I agree that naming it by functionality is better than by usage, as users may use it in some other cases.

Fix patch:
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<V: ?Sized, F: Field>: Sized {
     ) -> Result<Self, SynthesisError> {
         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<F: PrimeField>(x_var: &FpVar<F>, y_var: &FpVar<F>) -> Result<FpVar<F>, 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<T: Borrow<V>>(
+        cs: impl Into<Namespace<F>>,
+        f: impl FnOnce() -> Result<T, SynthesisError>,
+    ) -> Result<Self, SynthesisError> {
+        let ns: Namespace<F> = 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`


Test patch: