Repository: dusk-network/bls12_381 (language: rust)

Issue:
Add `from_var_bytes` to `Scalar`
#### Summary
A function that will take arbitrary slices, hash with BLAKE2b into 256-bit numbers, and perform a modulus multiplication to produce valid scalars.

```rust
fn from_var_bytes(bytes: &[u8]) -> Scalar
```

#### Possible solution design or implementation
Such functionality is required for downstream protocols such as Phoenix or Rusk contract ID computation. They produce arbitrary bytes, being either asymmetric key exchange protocols or network transaction payload, annd perform off-circuit field operations with such inputs.


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2b805ed8..3c5037d9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 
+### Added
+
+- Add `from_var_bytes` to scalar [#133]
+
 ## [0.12.3] - 2023-11-01
 
 ### Added
@@ -220,6 +224,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Rename `S` to `TWO_ADACITY` and export it
 
 <!-- Issues -->
+[#133]: https://github.com/dusk-network/bls12_381/issues/133
 [#125]: https://github.com/dusk-network/bls12_381/issues/125
 [#117]: https://github.com/dusk-network/bls12_381/issues/117
 [#109]: https://github.com/dusk-network/bls12_381/issues/109
diff --git a/Cargo.toml b/Cargo.toml
index 7ff91526..675d8fdf 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -33,6 +33,7 @@ sha3 = "0.9"
 # Dev-dependencies added by Dusk
 bincode = "1"
 rkyv = {version = "0.7", default-features = false, features = ["size_32"]}
+quickcheck = "1"
 
 [[bench]]
 name = "groups"
@@ -75,6 +76,10 @@ default-features = false
 optional = true
 
 # Start of dependencies added by Dusk
+[dependencies.blake2b_simd]
+version = "1.0"
+default-features = false
+
 [dependencies.byteorder]
 version = "1"
 default-features = false
diff --git a/src/scalar/dusk.rs b/src/scalar/dusk.rs
index e3355a58..59b416e0 100644
--- a/src/scalar/dusk.rs
+++ b/src/scalar/dusk.rs
@@ -297,6 +297,36 @@ impl Scalar {
         scalar.unwrap()
     }
 
+    /// Creates a `Scalar` from arbitrary bytes by hashing the input with BLAKE2b into a 256-bits
+    /// number, and then converting it into its `Scalar` representation.
+    pub fn from_var_bytes(input: &[u8]) -> Scalar {
+        let state = blake2b_simd::Params::new()
+            .hash_length(32)
+            .to_state()
+            .update(input)
+            .finalize();
+
+        let h = state.as_bytes();
+        let mut r = [0u64; 4];
+
+        // will be optmized by the compiler, depending on the available target
+        for i in 0..4 {
+            r[i] = u64::from_le_bytes([
+                h[i * 8],
+                h[i * 8 + 1],
+                h[i * 8 + 2],
+                h[i * 8 + 3],
+                h[i * 8 + 4],
+                h[i * 8 + 5],
+                h[i * 8 + 6],
+                h[i * 8 + 7],
+            ]);
+        }
+
+        // `from_raw` converts from arbitrary to congruent scalar
+        Self::from_raw(r)
+    }
+
     /// SHR impl
     #[inline]
     pub fn divn(&mut self, mut n: u32) {
@@ -446,3 +476,30 @@ fn test_scalar_eq_and_hash() {
     assert_eq!(hash_r0, hash_r1);
     assert_ne!(hash_r0, hash_r2);
 }
+
+#[cfg(all(test, feature = "alloc"))]
+mod fuzz {
+    use alloc::vec::Vec;
+
+    use crate::scalar::{Scalar, MODULUS};
+    use crate::util::sbb;
+
+    fn is_scalar_in_range(scalar: &Scalar) -> bool {
+        // subtraction against modulus must underflow
+        let borrow = scalar
+            .0
+            .iter()
+            .zip(MODULUS.0.iter())
+            .fold(0, |borrow, (&s, &m)| sbb(s, m, borrow).1);
+
+        borrow == u64::MAX
+    }
+
+    quickcheck::quickcheck! {
+        fn prop_scalar_from_raw_bytes(bytes: Vec<u8>) -> bool {
+            let scalar = Scalar::from_var_bytes(&bytes);
+
+            is_scalar_in_range(&scalar)
+        }
+    }
+}


Test patch: