Repository: arkworks-rs/algebra (Rust)

Issue:
Deserialization of array can panic
The deserialisation code of a static sized array uses `unwrap` instead of propagating the error:  

https://github.com/arkworks-rs/algebra/blob/065cd24fc5ae17e024c892cee126ad3bd885f01c/serialize/src/impls.rs#L481-L483

This was possibly done since the `try_from_fn` function is experimental. However, it can cause a panic instead of an error return.

I would propose to use a vector with capacity. There is probably a way to make it a faster with `unsafe`, but thats not my speciality:

```rust
let mut result = Vec::<T>::with_capacity(N);
for i in 0 .. N {
    result.push(T::deserialize_with_mode(&mut reader, compress, validate)?);
}

let array: [T; N] = result.try_into().map_err(|_| SerializationError::InvalidData)?;
Ok(array)
```


Discussion:
Yeah, this is a serious bug.  nice catch.

There are crates that do this, like [arrayvec](https://docs.rs/arrayvec/latest/arrayvec/struct.ArrayVec.html), 
but the unsafe solution looks like this:
```
impl<T: CanonicalDeserialize, const N: usize> CanonicalDeserialize for [T; N] {
    #[inline]
    fn deserialize_with_mode<R: Read>(
        mut reader: R,
        compress: Compress,
        validate: Validate,
    ) -> Result<Self, SerializationError> {
        let mut array = MaybeUninit::uninit_array::<N>();
        for i in 0..N {
            array[i].write(T::deserialize_with_mode(&mut reader, compress, Validate::No) ?);
        }
        if let Validate::Yes = validate {
            T::batch_check(result.iter())?
        }
        Ok(unsafe { MaybeUninit::array_assume_init(array) })
    }
}
```
What this unsafe is solving is the possibility that no sensible default initilization exists for `T`.
We want to avoid allocation when possible, but the `MaybeUninit` solution looks reasonable to me. Would folks be interested in opening a PR?
We should use arrayvec instead of my code above because arrayvec avoids possible memory leaks in my code.  Also arrayvec already gets used in ff, not really adding a dependency even.
```
impl<T: CanonicalDeserialize, const N: usize> CanonicalDeserialize for [T; N] {
    #[inline]
    fn deserialize_with_mode<R: Read>(
        mut reader: R,
        compress: Compress,
        validate: Validate,
    ) -> Result<Self, SerializationError> {
        let mut array = ArrayVec::<T, N>::new();
        for i in 0..N {
            array[i].push(T::deserialize_with_mode(&mut reader, compress, Validate::No) ?);
        }
        if let Validate::Yes = validate {
            T::batch_check(result.iter())?
        }
        Ok(array.into_inner())
    }
}
```



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1f8831dca..2acaf9643 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
 
 - [\#772](https://github.com/arkworks-rs/algebra/pull/772) (`ark-ff`) Implementation of `mul` method for `BigInteger`.
 - [\#794](https://github.com/arkworks-rs/algebra/pull/794) (`ark-ff`) Fix `wasm` compilation.
+- [\#837](https://github.com/arkworks-rs/algebra/pull/837) (`ark-serialize`) Fix array deserialization panic.
 
 ### Breaking changes
 
diff --git a/Cargo.toml b/Cargo.toml
index 9186f744b..2d3175a8e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -52,6 +52,7 @@ num-traits = { version = "0.2", default-features = false }
 num-bigint = { version = "0.4", default-features = false }
 num-integer = { version = "0.1", default-features = false }
 
+arrayvec = { version = "0.7", default-features = false }
 criterion = "0.5.0"
 educe = "0.5.0"
 digest = { version = "0.10", default-features = false }
diff --git a/ff/Cargo.toml b/ff/Cargo.toml
index d9a445ae4..35a5ed767 100644
--- a/ff/Cargo.toml
+++ b/ff/Cargo.toml
@@ -20,7 +20,7 @@ ark-ff-asm.workspace = true
 ark-ff-macros.workspace = true
 ark-std.workspace = true
 ark-serialize.workspace = true
-arrayvec = { version = "0.7", default-features = false }
+arrayvec.workspace = true
 educe.workspace = true
 num-traits.workspace = true
 paste.workspace = true
diff --git a/serialize/Cargo.toml b/serialize/Cargo.toml
index 1451e0b7f..ed55e0f37 100644
--- a/serialize/Cargo.toml
+++ b/serialize/Cargo.toml
@@ -18,6 +18,7 @@ keywords = ["cryptography", "serialization" ]
 [dependencies]
 ark-serialize-derive = { workspace = true, optional = true }
 ark-std.workspace = true
+arrayvec.workspace = true
 digest.workspace = true
 num-bigint.workspace = true
 rayon = { workspace = true, optional = true }
diff --git a/serialize/src/impls.rs b/serialize/src/impls.rs
index 9d2341ceb..5c62a5e69 100644
--- a/serialize/src/impls.rs
+++ b/serialize/src/impls.rs
@@ -10,6 +10,7 @@ use ark_std::{
     string::*,
     vec::*,
 };
+use arrayvec::ArrayVec;
 use num_bigint::BigUint;
 
 impl Valid for bool {
@@ -458,13 +459,18 @@ impl<T: CanonicalDeserialize, const N: usize> CanonicalDeserialize for [T; N] {
         compress: Compress,
         validate: Validate,
     ) -> Result<Self, SerializationError> {
-        let result = core::array::from_fn(|_| {
-            T::deserialize_with_mode(&mut reader, compress, Validate::No).unwrap()
-        });
+        let mut array = ArrayVec::<T, N>::new();
+        for _ in 0..N {
+            array.push(T::deserialize_with_mode(
+                &mut reader,
+                compress,
+                Validate::No,
+            )?);
+        }
         if let Validate::Yes = validate {
-            T::batch_check(result.iter())?
+            T::batch_check(array.iter())?
         }
-        Ok(result)
+        Ok(array.into_inner().ok().unwrap())
     }
 }
 


Test patch:
diff --git a/serialize/src/test.rs b/serialize/src/test.rs
index 090bc0c5b..248777ac0 100644
--- a/serialize/src/test.rs
+++ b/serialize/src/test.rs
@@ -126,6 +126,13 @@ fn test_array() {
     test_serialize([1u8; 33]);
 }
 
+#[test]
+fn test_array_bad_input() {
+    // Does not panic on invalid data:
+    let serialized = vec![0u8; 1];
+    assert!(<[u8; 2]>::deserialize_compressed(&serialized[..]).is_err());
+}
+
 #[test]
 fn test_vec() {
     test_serialize(vec![1u64, 2, 3, 4, 5]);