Repository: arkworks-rs/algebra (Rust)

Issue:
Implement `core::ops::Shl for BigInt` in place of `BigInt::muln()`
This issue follows the same rationale from https://github.com/arkworks-rs/algebra/issues/714. My proposal is to deprecate `BigInt::muln()` and replace it for `core::ops::Shl`. I propose that, in case of overflow, the functionality saturates the result to the highest bit as `BigInt::muln()` does.


Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index be58d6d1e..d637f5c57 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,7 +5,8 @@
 - [\#689](https://github.com/arkworks-rs/algebra/pull/689) (`ark-serialize`) Add `CanonicalSerialize` and `CanonicalDeserialize` impls for `VecDeque` and `LinkedList`.
 - [\#693](https://github.com/arkworks-rs/algebra/pull/693) (`ark-serialize`) Add `serialize_to_vec!` convenience macro.
 - [\#713](https://github.com/arkworks-rs/algebra/pull/713) (`ark-ff`) Add support for bitwise operations AND, OR, and XOR between `BigInteger`.
-- [\#736](https://github.com/arkworks-rs/algebra/pull/736) (`ark-ff`) Deprecate the use of `divn()`. Instead, it is implemented the trait `core::ops::Shr`.
+- [\#736](https://github.com/arkworks-rs/algebra/pull/736) (`ark-ff`) Deprecate `divn()`, and use `core::ops::{Shr, ShrAssign}` instead.
+- [\#739](https://github.com/arkworks-rs/algebra/pull/739) (`ark-ff`) Deprecate `muln()`, and use `core::ops::{Shl, ShlAssign}` instead.
 
 ### Breaking changes
 
diff --git a/ff/src/biginteger/mod.rs b/ff/src/biginteger/mod.rs
index d0b9e626c..0dff0129f 100644
--- a/ff/src/biginteger/mod.rs
+++ b/ff/src/biginteger/mod.rs
@@ -1,5 +1,6 @@
 use core::ops::{
-    BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not, Shr, ShrAssign,
+    BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not, Shl, ShlAssign, Shr,
+    ShrAssign,
 };
 
 use crate::{
@@ -779,6 +780,56 @@ impl<const N: usize> Shr<u32> for BigInt<N> {
     }
 }
 
+impl<const N: usize> ShlAssign<u32> for BigInt<N> {
+    /// Computes the bitwise shift left operation in place.
+    ///
+    /// Differently from the built-in numeric types (u8, u32, u64, etc.) this
+    /// operation does *not* return an overflow error if the number of bits
+    /// shifted is larger than N * 64. Instead, the overflow will be chopped
+    /// off.
+    fn shl_assign(&mut self, mut rhs: u32) {
+        if rhs >= (64 * N) as u32 {
+            *self = Self::from(0u64);
+            return;
+        }
+
+        while rhs >= 64 {
+            let mut t = 0;
+            for i in 0..N {
+                core::mem::swap(&mut t, &mut self.0[i]);
+            }
+            rhs -= 64;
+        }
+
+        if rhs > 0 {
+            let mut t = 0;
+            #[allow(unused)]
+            for i in 0..N {
+                let a = &mut self.0[i];
+                let t2 = *a >> (64 - rhs);
+                *a <<= rhs;
+                *a |= t;
+                t = t2;
+            }
+        }
+    }
+}
+
+impl<const N: usize> Shl<u32> for BigInt<N> {
+    type Output = Self;
+
+    /// Computes the bitwise shift left operation in place.
+    ///
+    /// Differently from the built-in numeric types (u8, u32, u64, etc.) this
+    /// operation does *not* return an overflow error if the number of bits
+    /// shifted is larger than N * 64. Instead, the overflow will be chopped
+    /// off.
+    fn shl(mut self, rhs: u32) -> Self::Output {
+        self <<= rhs;
+        self
+    }
+}
+
 impl<const N: usize> Not for BigInt<N> {
     type Output = Self;
 
@@ -861,6 +912,8 @@ pub trait BigInteger:
     + for<'a> BitOr<&'a Self, Output = Self>
     + Shr<u32, Output = Self>
     + ShrAssign<u32>
+    + Shl<u32, Output = Self>
+    + ShlAssign<u32>
 {
     /// Number of 64-bit limbs representing `Self`.
     const NUM_LIMBS: usize;
@@ -956,6 +1009,7 @@ pub trait BigInteger:
     /// mul.muln(5);
     /// assert_eq!(mul, B::from(0u64));
     /// ```
+    #[deprecated(since = "0.4.2", note = "please use the operator `>>` instead")]
     fn muln(&mut self, amt: u32);
 
     /// Performs a rightwise bitshift of this number, effectively dividing


Test patch:
diff --git a/ff/src/biginteger/tests.rs b/ff/src/biginteger/tests.rs
index f3fe5360f..9c65eb4c3 100644
--- a/ff/src/biginteger/tests.rs
+++ b/ff/src/biginteger/tests.rs
@@ -40,7 +40,7 @@ fn biginteger_arithmetic_test<B: BigInteger>(a: B, b: B, zero: B) {
 
     // a * 1 = a
     let mut a_mul1 = a;
-    a_mul1.muln(0);
+    a_mul1 <<= 0;
     assert_eq!(a_mul1, a);
 
     // a * 2 = a + a
@@ -72,6 +72,27 @@ fn biginteger_shr<B: BigInteger>() {
     assert_eq!(b.get_bit(B::NUM_LIMBS * 64 - 3), false);
 }
 
+fn biginteger_shl<B: BigInteger>() {
+    let mut rng = ark_std::test_rng();
+    let a = B::rand(&mut rng);
+    assert_eq!(a << 0, a);
+
+    // Binary simple test
+    let a = B::from(64u64);
+    assert_eq!(a << 2, B::from(256u64));
+
+    // Testing saturated overflow
+    let a = B::rand(&mut rng);
+    assert_eq!(a << ((B::NUM_LIMBS as u32) * 64), B::from(0u64));
+
+    // Test null bits
+    let a = B::rand(&mut rng);
+    let b = a << 3;
+    assert_eq!(b.get_bit(0), false);
+    assert_eq!(b.get_bit(1), false);
+    assert_eq!(b.get_bit(2), false);
+}
+
 // Test for BigInt's bitwise operations
 fn biginteger_bitwise_ops_test<B: BigInteger>() {
     let mut rng = ark_std::test_rng();
@@ -125,7 +146,7 @@ fn biginteger_bits_test<B: BigInteger>() {
     assert!(one.get_bit(0));
     // 1st bit of BigInteger representing 1 is not 1
     assert!(!one.get_bit(1));
-    one.muln(5);
+    one <<= 5;
     let thirty_two = one;
     // 0th bit of BigInteger representing 32 is not 1
     assert!(!thirty_two.get_bit(0));
@@ -162,6 +183,7 @@ fn test_biginteger<B: BigInteger>(zero: B) {
     biginteger_conversion_test::<B>();
     biginteger_bitwise_ops_test::<B>();
     biginteger_shr::<B>();
+    biginteger_shl::<B>();
 }
 
 #[test]