Repository: ergoplatform/sigma-rust (language: rust)

Issue:
VLQ decoding fails for signed int value 1234567890
Found in https://github.com/Emurgo/yoroi-frontend/pull/2081
Integer value larger than 1073741823 fails to decode from VLQ form. Also, the encoding of these values seems to be incorrect. 

Extend test suite with reference values from scala version to make sure that correct encoding/decoding.


Discussion / hints:

Fix patch:
diff --git a/ergo-lib/CHANGELOG.md b/ergo-lib/CHANGELOG.md
index e0761f42b..d8fa0c378 100644
--- a/ergo-lib/CHANGELOG.md
+++ b/ergo-lib/CHANGELOG.md
@@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 
 <!-- next-header -->
 ## [Unreleased] - ReleaseDate
+
+### Fixed 
+- Fix VQL encoding/decoding for signed ints in ranges i32::MIN..-1073741825 and 1073741824..i32::MAX [#263](https://github.com/ergoplatform/sigma-rust/pull/263);
+
 ## [0.13.0] - 2021-05-26
 
 ### Added 
diff --git a/ergotree-ir/src/serialization/serializable.rs b/ergotree-ir/src/serialization/serializable.rs
index bb1a684fa..888c18bbc 100644
--- a/ergotree-ir/src/serialization/serializable.rs
+++ b/ergotree-ir/src/serialization/serializable.rs
@@ -29,7 +29,7 @@ pub enum SerializationError {
     #[error("type parsing error")]
     InvalidTypePrefix,
     /// Failed to decode VLQ
-    #[error("vlq encode error")]
+    #[error("vlq encode error: {0}")]
     VlqEncode(vlq_encode::VlqEncodingError),
     /// IO fail (EOF, etc.)
     #[error("io error")]
diff --git a/sigma-ser/Cargo.toml b/sigma-ser/Cargo.toml
index 0ba87691d..5d836b0c8 100644
--- a/sigma-ser/Cargo.toml
+++ b/sigma-ser/Cargo.toml
@@ -28,3 +28,4 @@ bitvec = "0.22.3"
 # wasm-bindgen-test = "0.3.10"
 proptest = "0.10"
 proptest-derive = "0.2"
+derive_more = "0.99"
diff --git a/sigma-ser/src/vlq_encode.rs b/sigma-ser/src/vlq_encode.rs
index 6c9723db2..bd6467f52 100644
--- a/sigma-ser/src/vlq_encode.rs
+++ b/sigma-ser/src/vlq_encode.rs
@@ -6,15 +6,19 @@ use bitvec::order::Lsb0;
 use bitvec::prelude::BitVec;
 #[cfg(test)]
 use proptest::{num::u64, prelude::*};
+use thiserror::Error;
 
 /// Ways VLQ encoding/decoding might fail
-#[derive(Debug, Clone, Eq, PartialEq)]
+#[derive(Error, Debug, Clone, Eq, PartialEq)]
 pub enum VlqEncodingError {
     /// IO fail (EOF, etc.)
+    #[error("IO error: {0}")]
     Io(String),
     /// value bounds check error
-    TryFrom(std::num::TryFromIntError),
+    #[error("Bounds check error: {1} for input: {0}")]
+    TryFrom(String, std::num::TryFromIntError),
     /// Fail to decode a value from bytes
+    #[error("VLQ decoding failed")]
     VlqDecodingFailed,
 }
 
@@ -24,12 +28,6 @@ impl From<io::Error> for VlqEncodingError {
     }
 }
 
-impl From<std::num::TryFromIntError> for VlqEncodingError {
-    fn from(error: std::num::TryFromIntError) -> Self {
-        VlqEncodingError::TryFrom(error)
-    }
-}
-
 /// Write encoded unsigned values using VLQ and signed values first with ZigZag, then using VLQ
 /// for VLQ see [[https://en.wikipedia.org/wiki/Variable-length_quantity]]
 /// for ZigZag see https://developers.google.com/protocol-buffers/docs/encoding#types
@@ -46,7 +44,7 @@ pub trait WriteSigmaVlqExt: io::Write {
 
     /// Encode using ZigZag and then VLQ.
     fn put_i16(&mut self, v: i16) -> io::Result<()> {
-        Self::put_u32(self, zig_zag_encode::encode_i32(v as i32))
+        Self::put_u32(self, zig_zag_encode::encode_i32(v as i32) as u32)
     }
 
     /// Encode using VLQ.
@@ -66,7 +64,7 @@ pub trait WriteSigmaVlqExt: io::Write {
 
     /// Encode using ZigZag and then VLQ.
     fn put_i32(&mut self, v: i32) -> io::Result<()> {
-        Self::put_u64(self, zig_zag_encode::encode_i32(v as i32) as u64)
+        Self::put_u64(self, zig_zag_encode::encode_i32(v) as u64)
     }
 
     /// Encode using VLQ.
@@ -84,17 +82,21 @@ pub trait WriteSigmaVlqExt: io::Write {
         let mut buffer: [u8; 10] = [0; 10];
         let mut position = 0;
         let mut value = v;
-
-        // Base 128 Varints encoding for unsigned integers
-        // https://developers.google.com/protocol-buffers/docs/encoding?csw=1#varints
-        while value >= 0x80 {
-            buffer[position] = (value as u8) | 0x80;
-            value >>= 7;
-            position += 1;
+        // from https://github.com/ScorexFoundation/scorex-util/blob/3dc334f68ebefbfab6d33b57f2373e80245ab34d/src/main/scala/scorex/util/serialization/VLQWriter.scala#L97-L117
+        // original source: http://github.com/google/protobuf/blob/a7252bf42df8f0841cf3a0c85fdbf1a5172adecb/java/core/src/main/java/com/google/protobuf/CodedOutputStream.java#L1387
+        // see https://rosettacode.org/wiki/Variable-length_quantity for implementations in other languages
+        loop {
+            if (value & !0x7F) == 0 {
+                buffer[position] = value as u8;
+                position += 1;
+                break;
+            } else {
+                buffer[position] = (((value as i32) & 0x7F) | 0x80) as u8;
+                position += 1;
+                value >>= 7;
+            };
         }
-        buffer[position] = value as u8;
-
-        self.write_all(&buffer[..position + 1])
+        self.write_all(&buffer[..position])
     }
 
     /// Encode bool array as bit vector, filling trailing bits with `false`
@@ -131,27 +133,29 @@ pub trait ReadSigmaVlqExt: io::Read {
 
     /// Read and decode using VLQ and ZigZag value written with [`WriteSigmaVlqExt::put_i16`]
     fn get_i16(&mut self) -> Result<i16, VlqEncodingError> {
-        Self::get_u32(self).and_then(|v| {
+        Self::get_u64(self).and_then(|v| {
             let vd = zig_zag_encode::decode_u32(v);
-            i16::try_from(vd).map_err(VlqEncodingError::TryFrom)
+            i16::try_from(vd).map_err(|err| VlqEncodingError::TryFrom(vd.to_string(), err))
         })
     }
 
     /// Read and decode using VLQ value written with [`WriteSigmaVlqExt::put_u16`]
     fn get_u16(&mut self) -> Result<u16, VlqEncodingError> {
-        Self::get_u64(self).and_then(|v| u16::try_from(v).map_err(VlqEncodingError::TryFrom))
+        Self::get_u64(self).and_then(|v| {
+            u16::try_from(v).map_err(|err| VlqEncodingError::TryFrom(v.to_string(), err))
+        })
     }
 
     /// Read and decode using VLQ and ZigZag value written with [`WriteSigmaVlqExt::put_i32`]
     fn get_i32(&mut self) -> Result<i32, VlqEncodingError> {
-        Self::get_u64(self)
-            .and_then(|v| u32::try_from(v).map_err(VlqEncodingError::TryFrom))
-            .map(zig_zag_encode::decode_u32)
+        Self::get_u64(self).map(zig_zag_encode::decode_u32)
     }
 
     /// Read and decode using VLQ value written with [`WriteSigmaVlqExt::put_u32`]
     fn get_u32(&mut self) -> Result<u32, VlqEncodingError> {
-        Self::get_u64(self).and_then(|v| u32::try_from(v).map_err(VlqEncodingError::TryFrom))
+        Self::get_u64(self).and_then(|v| {
+            u32::try_from(v).map_err(|err| VlqEncodingError::TryFrom(v.to_string(), err))
+        })
     }
 
     /// Read and decode using VLQ and ZigZag value written with [`WriteSigmaVlqExt::put_i64`]
@@ -197,9 +201,86 @@ impl<R: io::Read + ?Sized> ReadSigmaVlqExt for R {}
 
 #[cfg(test)]
 mod tests {
+    // See corresponding test suite in
+    // https://github.com/ScorexFoundation/scorex-util/blob/9adb6c68b8a1c00ec17730e6da11c2976a892ad8/src/test/scala/scorex/util/serialization/VLQReaderWriterSpecification.scala#L11
     use super::*;
     use proptest::collection;
     use std::io::Cursor;
+    use std::io::Read;
+    use std::io::Write;
+
+    extern crate derive_more;
+    use derive_more::From;
+
+    #[derive(Debug, From, Clone, PartialEq)]
+    enum Val {
+        I8(i8),
+        U8(u8),
+        I16(i16),
+        U16(u16),
+        I32(i32),
+        U32(u32),
+        I64(i64),
+        U64(u64),
+        Bytes(Vec<u8>),
+        Bits(Vec<bool>),
+    }
+
+    impl Arbitrary for Val {
+        type Strategy = BoxedStrategy<Self>;
+        type Parameters = ();
+
+        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+            prop_oneof![
+                any::<i8>().prop_map_into(),
+                any::<u8>().prop_map_into(),
+                any::<i16>().prop_map_into(),
+                any::<u16>().prop_map_into(),
+                any::<i32>().prop_map_into(),
+                any::<u32>().prop_map_into(),
+                any::<i64>().prop_map_into(),
+                any::<u64>().prop_map_into(),
+                any::<Vec<u8>>().prop_map_into(),
+                any::<Vec<bool>>().prop_map_into(),
+            ]
+            .boxed()
+        }
+    }
+
+    fn bytes_u64(v: u64) -> Vec<u8> {
+        let mut w = Cursor::new(vec![]);
+        w.put_u64(v).unwrap();
+        w.into_inner()
+    }
+
+    fn bytes_i64(v: i64) -> Vec<u8> {
+        let mut w = Cursor::new(vec![]);
+        w.put_i64(v).unwrap();
+        w.into_inner()
+    }
+
+    fn bytes_u32(v: u32) -> Vec<u8> {
+        let mut w = Cursor::new(vec![]);
+        w.put_u32(v).unwrap();
+        w.into_inner()
+    }
+
+    fn bytes_i32(v: i32) -> Vec<u8> {
+        let mut w = Cursor::new(vec![]);
+        w.put_i32(v).unwrap();
+        w.into_inner()
+    }
+    fn bytes_u16(v: u16) -> Vec<u8> {
+        let mut w = Cursor::new(vec![]);
+        w.put_u16(v).unwrap();
+        w.into_inner()
+    }
+
+    fn bytes_i16(v: i16) -> Vec<u8> {
+        let mut w = Cursor::new(vec![]);
+        w.put_i16(v).unwrap();
+        w.into_inner()
+    }
 
     #[test]
     fn test_write_u8() {
@@ -289,17 +370,491 @@ mod tests {
         }
     }
 
+    #[test]
+    fn test_i32_ten_bytes_case() {
+        let input = 1234567890i32;
+        let mut w = Cursor::new(vec![]);
+        w.put_i32(input).unwrap();
+        let bytes = w.into_inner();
+        assert_eq!(bytes.len(), 10);
+        // 164, 139, 176, 153, 9,
+        let mut r = Cursor::new(bytes);
+        let decoded_value = r.get_i32().unwrap();
+        assert_eq!(decoded_value, input);
+    }
+
+    #[test]
+    fn malformed_input() {
+        // source: http://github.com/google/protobuf/blob/a7252bf42df8f0841cf3a0c85fdbf1a5172adecb/java/core/src/test/java/com/google/protobuf/CodedInputStreamTest.java#L281
+        assert!(Cursor::new([0x80]).get_u64().is_err());
+        assert!(
+            Cursor::new([0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00])
+                .get_u64()
+                .is_err()
+        );
+    }
+
+    #[test]
+    fn i16_corner_cases() {
+        fn roundtrip(v: i16, expected_bytes: &[u8]) {
+            let mut w = Cursor::new(vec![]);
+            w.put_i16(v).unwrap();
+            let bytes = w.into_inner();
+            assert_eq!(bytes, expected_bytes);
+            let mut r = Cursor::new(expected_bytes);
+            let decoded_value = r.get_i16().unwrap();
+            assert_eq!(decoded_value, v);
+        }
+
+        roundtrip(i16::MIN, &[0xFF, 0xFF, 0x03]);
+        roundtrip(-8194, &[0x83, 0x80, 0x01]);
+        roundtrip(-8193, &[0x81, 0x80, 0x01]);
+        roundtrip(-8192, &[0xFF, 0x7F]);
+        roundtrip(-8191, &[0xFD, 0x7F]);
+        roundtrip(-66, &[0x83, 0x01]);
+        assert_eq!(Cursor::new([0x83, 0x00]).get_i16().unwrap(), -2);
+        roundtrip(-65, &[0x81, 0x01]);
+        assert_eq!(Cursor::new([0x81, 0x00]).get_i16().unwrap(), -1);
+        roundtrip(-64, &[0x7F]);
+        roundtrip(-63, &[0x7D]);
+        roundtrip(-1, &[0x01]);
+        roundtrip(0, &[0]);
+        roundtrip(1, &[0x02]);
+        roundtrip(62, &[0x7C]);
+        roundtrip(63, &[0x7E]);
+        assert_eq!(Cursor::new([0x80, 0x00]).get_i16().unwrap(), 0);
+        roundtrip(64, &[0x80, 0x01]);
+        assert_eq!(Cursor::new([0x82, 0x00]).get_i16().unwrap(), 1);
+        roundtrip(65, &[0x82, 0x01]);
+        roundtrip(8190, &[0xFC, 0x7F]);
+        roundtrip(8191, &[0xFE, 0x7F]);
+        roundtrip(8192, &[0x80, 0x80, 0x01]);
+        roundtrip(8193, &[0x82, 0x80, 0x01]);
+        roundtrip(i16::MAX, &[0xFE, 0xFF, 0x03]);
+    }
+
+    #[test]
+    fn u16_corner_cases() {
+        fn roundtrip(v: u16, expected_bytes: &[u8]) {
+            let mut w = Cursor::new(vec![]);
+            w.put_u16(v).unwrap();
+            let bytes = w.into_inner();
+            assert_eq!(bytes, expected_bytes);
+            let mut r = Cursor::new(expected_bytes);
+            let decoded_value = r.get_u16().unwrap();
+            assert_eq!(decoded_value, v);
+        }
+
+        roundtrip(0, &[0x00]);
+        roundtrip(1, &[0x01]);
+        roundtrip(126, &[0x7E]);
+        roundtrip(127, &[0x7F]);
+        roundtrip(128, &[0x80, 0x01]);
+        roundtrip(129, &[0x81, 0x01]);
+        roundtrip(16382, &[0xFE, 0x7F]);
+        roundtrip(16383, &[0xFF, 0x7F]);
+        roundtrip(16384, &[0x80, 0x80, 0x01]);
+        roundtrip(16385, &[0x81, 0x80, 0x01]);
+        roundtrip(65534, &[0xFE, 0xFF, 0x03]);
+        roundtrip(65535, &[0xFF, 0xFF, 0x03]);
+    }
+
+    #[test]
+    fn i32_corner_cases() {
+        fn roundtrip(v: i32, expected_bytes: &[u8]) {
+            let mut w = Cursor::new(vec![]);
+            w.put_i32(v).unwrap();
+            let bytes = w.into_inner();
+            assert_eq!(
+                bytes,
+                expected_bytes,
+                "for {}, zigzag: {}",
+                v,
+                zig_zag_encode::encode_i32(v)
+            );
+            let mut r = Cursor::new(expected_bytes);
+            let decoded_value = r.get_i32().unwrap();
+            assert_eq!(decoded_value, v);
+        }
+
+        roundtrip(
+            i32::MIN,
+            &[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01],
+        ); // 10 bytes
+        roundtrip(
+            -1073741825,
+            &[0x81, 0x80, 0x80, 0x80, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0x01],
+        ); // 10 bytes
+        roundtrip(-1073741824, &[0xFF, 0xFF, 0xFF, 0xFF, 0x07]); // 5 bytes
+        roundtrip(-134217729, &[0x81, 0x80, 0x80, 0x80, 0x01]); // 5 bytes
+        roundtrip(-134217728, &[0xFF, 0xFF, 0xFF, 0x7F]); // 4 bytes
+        roundtrip(-1048577, &[0x81, 0x80, 0x80, 0x01]); // 4 bytes
+        roundtrip(-1048576, &[0xFF, 0xFF, 0x7F]);
+        roundtrip(-8194, &[0x83, 0x80, 0x01]);
+        roundtrip(-8193, &[0x81, 0x80, 0x01]);
+        roundtrip(-8192, &[0xFF, 0x7F]);
+        roundtrip(-8191, &[0xFD, 0x7F]);
+        roundtrip(-66, &[0x83, 0x01]);
+        roundtrip(-65, &[0x81, 0x01]);
+        roundtrip(-64, &[0x7F]);
+        roundtrip(-63, &[0x7D]);
+        roundtrip(-1, &[0x01]);
+        roundtrip(0, &[0]);
+        roundtrip(1, &[0x02]);
+        roundtrip(62, &[0x7C]);
+        roundtrip(63, &[0x7E]);
+        roundtrip(64, &[0x80, 0x01]);
+        roundtrip(65, &[0x82, 0x01]);
+        roundtrip(8190, &[0xFC, 0x7F]);
+        roundtrip(8191, &[0xFE, 0x7F]);
+        roundtrip(8192, &[0x80, 0x80, 0x01]);
+        roundtrip(8193, &[0x82, 0x80, 0x01]);
+        roundtrip(1048575, &[0xFE, 0xFF, 0x7F]);
+        roundtrip(1048576, &[0x80, 0x80, 0x80, 0x01]); // 4 bytes
+        roundtrip(134217727, &[0xFE, 0xFF, 0xFF, 0x7F]); // 4 bytes
+        roundtrip(134217728, &[0x80, 0x80, 0x80, 0x80, 0x01]); // 5 bytes
+        roundtrip(1073741823, &[0xFE, 0xFF, 0xFF, 0xFF, 0x07]); // 5 bytes
+        roundtrip(
+            1073741824,
+            &[0x80, 0x80, 0x80, 0x80, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0x01],
+        ); // 10 bytes
+        roundtrip(
+            i32::MAX,
+            &[0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01],
+        ); // 10 bytes
+    }
+
+    #[test]
+    fn u32_corner_cases() {
+        fn roundtrip(v: u32, expected_bytes: &[u8]) {
+            let mut w = Cursor::new(vec![]);
+            w.put_u32(v).unwrap();
+            let bytes = w.into_inner();
+            assert_eq!(bytes, expected_bytes, "for {}", v);
+            let mut r = Cursor::new(expected_bytes);
+            let decoded_value = r.get_u32().unwrap();
+            assert_eq!(decoded_value, v);
+        }
+
+        roundtrip(0, &[0]);
+        roundtrip(126, &[0x7E]);
+        roundtrip(127, &[0x7F]);
+        roundtrip(128, &[0x80, 0x01]);
+        roundtrip(129, &[0x81, 0x01]);
+        roundtrip(16383, &[0xFF, 0x7F]);
+        roundtrip(16384, &[0x80, 0x80, 0x01]);
+        roundtrip(16385, &[0x81, 0x80, 0x01]);
+        roundtrip(2097151, &[0xFF, 0xFF, 0x7F]);
+        roundtrip(2097152, &[0x80, 0x80, 0x80, 0x01]);
+        roundtrip(268435455, &[0xFF, 0xFF, 0xFF, 0x7F]);
+        roundtrip(268435456, &[0x80, 0x80, 0x80, 0x80, 0x01]);
+        roundtrip(u32::MAX, &[0xFF, 0xFF, 0xFF, 0xFF, 0x0F]);
+    }
+
+    #[test]
+    fn i64_corner_cases() {
+        fn roundtrip(v: i64, expected_bytes: &[u8]) {
+            let mut w = Cursor::new(vec![]);
+            w.put_i64(v).unwrap();
+            let bytes = w.into_inner();
+            assert_eq!(bytes, expected_bytes, "for {}", v);
+            let mut r = Cursor::new(expected_bytes);
+            let decoded_value = r.get_i64().unwrap();
+            assert_eq!(decoded_value, v);
+        }
+
+        roundtrip(
+            i64::MIN,
+            &[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01],
+        ); // 10 bytes
+        roundtrip(
+            i64::MIN / 2 - 1,
+            &[0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01],
+        ); // 10 bytes
+        roundtrip(
+            i64::MIN / 2,
+            &[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F],
+        ); // 9 bytes
+        roundtrip(
+            -36028797018963969,
+            &[0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01],
+        ); // 9 bytes
+        roundtrip(
+            -36028797018963968,
+            &[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F],
+        ); // 8 bytes
+        roundtrip(
+            -281474976710657,
+            &[0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01],
+        ); // 8 bytes
+        roundtrip(
+            -281474976710656,
+            &[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F],
+        ); // 7 bytes
+        roundtrip(-2199023255553, &[0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01]); // 7 bytes
+        roundtrip(-2199023255552, &[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F]); // 6 bytes
+        roundtrip(-17179869185, &[0x81, 0x80, 0x80, 0x80, 0x80, 0x01]); // 6 bytes
+        roundtrip(-17179869184, &[0xFF, 0xFF, 0xFF, 0xFF, 0x7F]); // 5 bytes
+        roundtrip(-1073741824, &[0xFF, 0xFF, 0xFF, 0xFF, 0x07]); // 5 bytes
+        roundtrip(-134217729, &[0x81, 0x80, 0x80, 0x80, 0x01]); // 5 bytes
+        roundtrip(-134217728, &[0xFF, 0xFF, 0xFF, 0x7F]); // 4 bytes
+        roundtrip(-1048577, &[0x81, 0x80, 0x80, 0x01]); // 4 bytes
+        roundtrip(-1048576, &[0xFF, 0xFF, 0x7F]);
+        roundtrip(-8194, &[0x83, 0x80, 0x01]);
+        roundtrip(-8193, &[0x81, 0x80, 0x01]);
+        roundtrip(-8192, &[0xFF, 0x7F]);
+        roundtrip(-8191, &[0xFD, 0x7F]);
+        roundtrip(-66, &[0x83, 0x01]);
+        roundtrip(-65, &[0x81, 0x01]);
+        roundtrip(-64, &[0x7F]);
+        roundtrip(-63, &[0x7D]);
+        roundtrip(-1, &[0x01]);
+        roundtrip(0, &[0]);
+        roundtrip(1, &[0x02]);
+        roundtrip(62, &[0x7C]);
+        roundtrip(63, &[0x7E]);
+        roundtrip(64, &[0x80, 0x01]);
+        roundtrip(65, &[0x82, 0x01]);
+        roundtrip(8190, &[0xFC, 0x7F]);
+        roundtrip(8191, &[0xFE, 0x7F]);
+        roundtrip(8192, &[0x80, 0x80, 0x01]);
+        roundtrip(8193, &[0x82, 0x80, 0x01]);
+        roundtrip(1048575, &[0xFE, 0xFF, 0x7F]);
+        roundtrip(1048576, &[0x80, 0x80, 0x80, 0x01]); // 4 bytes
+        roundtrip(134217727, &[0xFE, 0xFF, 0xFF, 0x7F]); // 4 bytes
+        roundtrip(134217728, &[0x80, 0x80, 0x80, 0x80, 0x01]); // 5 bytes
+        roundtrip(1073741823, &[0xFE, 0xFF, 0xFF, 0xFF, 0x07]); // 5 bytes
+        roundtrip(17179869183, &[0xFE, 0xFF, 0xFF, 0xFF, 0x7F]); // 5 bytes
+        roundtrip(17179869184, &[0x80, 0x80, 0x80, 0x80, 0x80, 0x01]); // 6 bytes
+        roundtrip(2199023255551, &[0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F]); // 6 bytes
+        roundtrip(2199023255552, &[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01]); // 7 bytes
+        roundtrip(281474976710655, &[0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F]); // 7 bytes
+        roundtrip(
+            281474976710656,
+            &[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01],
+        ); // 8 bytes
+        roundtrip(
+            36028797018963967,
+            &[0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F],
+        ); // 8 bytes
+        roundtrip(
+            36028797018963968,
+            &[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01],
+        ); // 9 bytes
+        roundtrip(
+            i64::MAX / 2,
+            &[0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F],
+        ); // 9 bytes
+        roundtrip(
+            i64::MAX / 2 + 1,
+            &[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01],
+        ); // 10 bytes
+        roundtrip(
+            i64::MAX,
+            &[0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01],
+        ); // 10 bytes
+    }
+
+    #[test]
+    fn u64_corner_cases() {
+        fn roundtrip(v: u64, expected_bytes: &[u8]) {
+            let mut w = Cursor::new(vec![]);
+            w.put_u64(v).unwrap();
+            let bytes = w.into_inner();
+            assert_eq!(bytes, expected_bytes, "for {}", v);
+            let mut r = Cursor::new(expected_bytes);
+            let decoded_value = r.get_u64().unwrap();
+            assert_eq!(decoded_value, v);
+        }
+
+        roundtrip(0, &[0]);
+
+        roundtrip(126, &[0x7E]);
+        roundtrip(127, &[0x7F]);
+        roundtrip(128, &[0x80, 0x01]);
+        roundtrip(129, &[0x81, 0x01]);
+        roundtrip(16383, &[0xFF, 0x7F]);
+        roundtrip(16384, &[0x80, 0x80, 0x01]);
+        roundtrip(16385, &[0x81, 0x80, 0x01]);
+        roundtrip(2097151, &[0xFF, 0xFF, 0x7F]);
+        roundtrip(2097152, &[0x80, 0x80, 0x80, 0x01]); // 4 bytes
+        roundtrip(268435455, &[0xFF, 0xFF, 0xFF, 0x7F]); // 4 bytes
+        roundtrip(268435456, &[0x80, 0x80, 0x80, 0x80, 0x01]); // 5 bytes
+        roundtrip(34359738367, &[0xFF, 0xFF, 0xFF, 0xFF, 0x7F]); // 5 bytes
+        roundtrip(34359738368, &[0x80, 0x80, 0x80, 0x80, 0x80, 0x01]); // 6 bytes
+        roundtrip(4398046511103, &[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F]); // 6 bytes
+        roundtrip(4398046511104, &[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01]); // 7 bytes
+        roundtrip(562949953421311, &[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F]); // 7 bytes
+        roundtrip(
+            562949953421312,
+            &[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01],
+        ); // 8 bytes
+        roundtrip(
+            72057594037927935,
+            &[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F],
+        ); // 8 bytes
+        roundtrip(
+            72057594037927936,
+            &[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01],
+        ); // 9 bytes
+        roundtrip(
+            i64::MAX as u64,
+            &[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F],
+        ); // 10 bytes
+           // roundtrip(
+           //     i64::MAX as u64 + 1,
+           //     &[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F],
+           // ); // 10 bytes
+    }
+
     #[cfg(test)]
     proptest! {
 
         #[test]
-        fn prop_u64_roundtrip(i in u64::ANY) {
+        fn u64_check_size_1(v in 0u64..=127u64) {
+            prop_assert_eq!(bytes_u64(v).len(), 1);
+        }
+
+        #[test]
+        fn u64_check_size_2(v in 128u64..=16383u64) {
+            prop_assert_eq!(bytes_u64(v).len(), 2);
+        }
+        #[test]
+        fn u64_check_size_3(v in 16384u64..=2097151u64) {
+            prop_assert_eq!(bytes_u64(v).len(), 3);
+        }
+        #[test]
+        fn u64_check_size_4(v in 2097152u64..=268435455u64) {
+            prop_assert_eq!(bytes_u64(v).len(), 4);
+        }
+
+        #[test]
+        fn u64_check_size_5(v in 268435456u64..=34359738367u64) {
+            prop_assert_eq!(bytes_u64(v).len(), 5);
+        }
+
+        #[test]
+        fn u64_check_size_6(v in 34359738368u64..=4398046511103u64) {
+            prop_assert_eq!(bytes_u64(v).len(), 6);
+        }
+
+        #[test]
+        fn u64_check_size_7(v in 4398046511104u64..=562949953421311u64) {
+            prop_assert_eq!(bytes_u64(v).len(), 7);
+        }
+
+        #[test]
+        fn u64_check_size_8(v in 562949953421312u64..=72057594037927935u64) {
+            prop_assert_eq!(bytes_u64(v).len(), 8);
+        }
+
+        #[test]
+        fn u64_check_size_9(v in 72057594037927936u64..=i64::MAX as u64) {
+            prop_assert_eq!(bytes_u64(v).len(), 9);
+        }
+
+        #[test]
+        fn i64_check_size_1(v in -64i64..=64i64 - 1) {
+            prop_assert_eq!(bytes_i64(v).len(), 1);
+        }
+
+        #[test]
+        fn i64_check_size_2_part1(v in -8192i64..=-64i64 - 1) {
+            prop_assert_eq!(bytes_i64(v).len(), 2);
+        }
+        #[test]
+        fn i64_check_size_2_part2(v in 64i64..=8192i64 - 1) {
+            prop_assert_eq!(bytes_i64(v).len(), 2);
+        }
+
+        #[test]
+        fn i64_check_size_3_part1(v in -1048576i64..=-8192i64 - 1) {
+            prop_assert_eq!(bytes_i64(v).len(), 3);
+        }
+        #[test]
+        fn i64_check_size_3_part2(v in 8192i64..=1048576i64 - 1) {
+            prop_assert_eq!(bytes_i64(v).len(), 3);
+        }
+
+        #[test]
+        fn i64_check_size_4_part1(v in -134217728i64..=-1048576i64 - 1) {
+            prop_assert_eq!(bytes_i64(v).len(), 4);
+        }
+        #[test]
+        fn i64_check_size_4_part2(v in 1048576i64..=134217728i64 - 1) {
+            prop_assert_eq!(bytes_i64(v).len(), 4);
+        }
+
+        #[test]
+        fn i64_check_size_5_part1(v in -17179869184i64..=-134217728i64 - 1) {
+            prop_assert_eq!(bytes_i64(v).len(), 5);
+        }
+        #[test]
+        fn i64_check_size_5_part2(v in 134217728i64..=17179869184i64 - 1) {
+            prop_assert_eq!(bytes_i64(v).len(), 5);
+        }
+
+        #[test]
+        fn i64_check_size_6_part1(v in -2199023255552i64..=-17179869184i64 - 1) {
+            prop_assert_eq!(bytes_i64(v).len(), 6);
+        }
+        #[test]
+        fn i64_check_size_6_part2(v in 17179869184i64..=2199023255552i64 - 1) {
+            prop_assert_eq!(bytes_i64(v).len(), 6);
+        }
+
+        #[test]
+        fn i64_check_size_7_part1(v in -281474976710656i64..=-2199023255552i64 - 1) {
+            prop_assert_eq!(bytes_i64(v).len(), 7);
+        }
+        #[test]
+        fn i64_check_size_7_part2(v in 2199023255552i64..=281474976710656i64 - 1) {
+            prop_assert_eq!(bytes_i64(v).len(), 7);
+        }
+
+        #[test]
+        fn i64_check_size_8_part1(v in -36028797018963968i64..=-281474976710656i64 - 1) {
+            prop_assert_eq!(bytes_i64(v).len(), 8);
+        }
+        #[test]
+        fn i64_check_size_8_part2(v in 281474976710656i64..=36028797018963968i64 - 1) {
+            prop_assert_eq!(bytes_i64(v).len(), 8);
+        }
+
+        #[test]
+        fn i64_check_size_9_part1(v in i64::MIN / 2..=-36028797018963968i64 - 1) {
+            prop_assert_eq!(bytes_i64(v).len(), 9);
+        }
+        #[test]
+        fn i64_check_size_9_part2(v in 36028797018963968i64..=i64::MAX / 2) {
+            prop_assert_eq!(bytes_i64(v).len(), 9);
+        }
+
+        #[test]
+        fn i64_check_size_10_part1(v in i64::MIN..=i64::MIN / 2 - 1) {
+            prop_assert_eq!(bytes_i64(v).len(), 10);
+        }
+        #[test]
+        fn i64_check_size_10_part2(v in i64::MAX / 2 + 1..=i64::MAX) {
+            prop_assert_eq!(bytes_i64(v).len(), 10);
+        }
+
+        #[test]
+        fn u64_roundtrip(i in u64::ANY) {
             let mut w = Cursor::new(vec![]);
             w.put_u64(i).unwrap();
             let mut r = Cursor::new(w.into_inner());
             prop_assert_eq![i, r.get_u64().unwrap()];
         }
 
+        #[test]
+        fn i64_roundtrip(i in any::<i64>()) {
+            let mut w = Cursor::new(vec![]);
+            w.put_i64(i).unwrap();
+            let mut r = Cursor::new(w.into_inner());
+            prop_assert_eq![i, r.get_i64().unwrap()];
+        }
+
         #[test]
         fn prop_u64_array_roundtrip(arr in any::<[u64; 32]>()) {
             let mut w = Cursor::new(vec![]);
@@ -322,5 +877,59 @@ mod tests {
             prop_assert_eq![bits.clone(), r.get_bits(bits.len()).unwrap()];
         }
 
+        #[test]
+        fn arbitrary_values_list(vals in collection::vec(any::<Val>(), 0..100)) {
+            let mut w = Cursor::new(vec![]);
+            for val in vals.clone() {
+                match val {
+                    Val::I8(v) => w.put_i8(v).unwrap(),
+                    Val::U8(v) => w.put_u8(v).unwrap(),
+                    Val::I16(v) => w.put_i16(v).unwrap(),
+                    Val::U16(v) => w.put_u16(v).unwrap(),
+                    Val::I32(v) => w.put_i32(v).unwrap(),
+                    Val::U32(v) => w.put_u32(v).unwrap(),
+                    Val::I64(v) => w.put_i64(v).unwrap(),
+                    Val::U64(v) => w.put_u64(v).unwrap(),
+                    Val::Bytes(v) => w.write_all(&v).unwrap(),
+                    Val::Bits(v) => w.put_bits(&v).unwrap(),
+                }
+
+            }
+            let mut r = Cursor::new(w.into_inner());
+            let mut parsed_vals: Vec<Val> = Vec::new();
+            for val in vals.clone() {
+                match val {
+                    Val::I8(_) => parsed_vals.push(r.get_i8().unwrap().into()),
+                    Val::U8(_) => parsed_vals.push(r.get_u8().unwrap().into()),
+                    Val::I16(_) => parsed_vals.push(r.get_i16().unwrap().into()),
+                    Val::U16(_) => parsed_vals.push(r.get_u16().unwrap().into()),
+                    Val::I32(_) => parsed_vals.push(r.get_i32().unwrap().into()),
+                    Val::U32(_) => parsed_vals.push(r.get_u32().unwrap().into()),
+                    Val::I64(_) => parsed_vals.push(r.get_i64().unwrap().into()),
+                    Val::U64(_) => parsed_vals.push(r.get_u64().unwrap().into()),
+                    Val::Bytes(bytes) => {
+                        let mut buf = vec![0u8; bytes.len()];
+                        r.read_exact(&mut buf).unwrap();
+                        parsed_vals.push(buf.to_vec().into());
+                    },
+                    Val::Bits(bits) => parsed_vals.push(r.get_bits(bits.len()).unwrap().into()),
+                }
+            }
+            prop_assert_eq!(parsed_vals, vals);
+        }
+
+        #[test]
+        fn u16_u32_u64_equivalence(i in any::<u16>()) {
+            let expected_bytes = bytes_u16(i);
+            prop_assert_eq!(&bytes_u64(i as u64), &expected_bytes);
+            prop_assert_eq!(&bytes_u32(i as u32), &expected_bytes);
+        }
+
+        #[test]
+        fn i16_i32_i64_equivalence(i in any::<i16>()) {
+            let expected_bytes = bytes_i16(i);
+            prop_assert_eq!(&bytes_i64(i as i64), &expected_bytes);
+            prop_assert_eq!(&bytes_i32(i as i32), &expected_bytes);
+        }
     }
 }
diff --git a/sigma-ser/src/zig_zag_encode.rs b/sigma-ser/src/zig_zag_encode.rs
index daf4a329b..c37ce0952 100644
--- a/sigma-ser/src/zig_zag_encode.rs
+++ b/sigma-ser/src/zig_zag_encode.rs
@@ -6,17 +6,20 @@ use proptest::{num::i32, num::i64, prelude::*};
 /// negative values must be sign-extended to 64 bits to be varint encoded,
 /// thus always taking 10 bytes on the wire.)
 /// see https://developers.google.com/protocol-buffers/docs/encoding#types
-pub fn encode_i32(v: i32) -> u32 {
+///
+/// Although result should be of u32 we need to use u64 due to the signed Int
+/// used for result in Scala version
+pub fn encode_i32(v: i32) -> u64 {
     // Note:  the right-shift must be arithmetic
     // source: http://github.com/google/protobuf/blob/a7252bf42df8f0841cf3a0c85fdbf1a5172adecb/java/core/src/main/java/com/google/protobuf/CodedOutputStream.java#L934
-    ((v << 1) ^ (v >> 31)) as u32
+    ((v << 1) ^ (v >> 31)) as u64
 }
 
 /// Decode a signed value previously ZigZag-encoded with [`encode_i32`]
 /// see https://developers.google.com/protocol-buffers/docs/encoding#types
-pub fn decode_u32(v: u32) -> i32 {
+pub fn decode_u32(v: u64) -> i32 {
     // source: http://github.com/google/protobuf/blob/a7252bf42df8f0841cf3a0c85fdbf1a5172adecb/java/core/src/main/java/com/google/protobuf/CodedInputStream.java#L553
-    ((v >> 1) ^ (-((v & 1) as i32)) as u32) as i32
+    (v as u32 >> 1) as i32 ^ -(v as i32 & 1)
 }
 
 /// Encode a 64-bit value with ZigZag. ZigZag encodes signed integers
@@ -49,8 +52,8 @@ mod tests {
         assert_eq!(3, encode_i32(-2));
         assert_eq!(0x7FFF_FFFE, encode_i32(0x3FFF_FFFF));
         assert_eq!(0x7FFF_FFFF, encode_i32(0xC000_0000));
-        assert_eq!(0xFFFF_FFFE, encode_i32(0x7FFF_FFFF));
-        assert_eq!(0xFFFF_FFFF, encode_i32(0x8000_0000));
+        assert_eq!(0xFFFF_FFFE, encode_i32(0x7FFF_FFFF) as i32);
+        assert_eq!(0xFFFF_FFFF, encode_i32(0x8000_0000) as i32);
 
         assert_eq!(0, encode_i64(0));
         assert_eq!(1, encode_i64(-1));


Test patch: