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

Issue:
Serialization of `SigmaBoolean`
Implement serialization and parsing for all missing `SigmaBoolean` variants (see `todo!()` - `Cand`, `Cor`, parsing for `ProveDhTuple`). 

https://github.com/ergoplatform/sigma-rust/blob/6698880353fbec1038d0947ca2488746bcb77f34/ergotree-ir/src/serialization/sigmaboolean.rs#L14-L46

Write roundtrip serialization test (implement `Arbitrary` for `SigmaBoolean` first).

Reference Scala implementation - https://github.com/ScorexFoundation/sigmastate-interpreter/blob/00ffd5514cb8410c0646f5e10f51e383515fcb67/sigmastate/src/main/scala/sigmastate/Values.scala#L610-L684



Discussion / hints:

Fix patch:
diff --git a/ergotree-ir/src/serialization/sigmaboolean.rs b/ergotree-ir/src/serialization/sigmaboolean.rs
index fb1e3e72a..d6655fa65 100644
--- a/ergotree-ir/src/serialization/sigmaboolean.rs
+++ b/ergotree-ir/src/serialization/sigmaboolean.rs
@@ -4,12 +4,15 @@ use crate::has_opcode::{HasOpCode, HasStaticOpCode};
 use crate::serialization::{
     sigma_byte_reader::SigmaByteRead, SigmaParsingError, SigmaSerializable,
 };
-use crate::sigma_protocol::sigma_boolean::ProveDhTuple;
 use crate::sigma_protocol::{
     dlog_group::EcPoint,
-    sigma_boolean::{ProveDlog, SigmaBoolean, SigmaConjecture, SigmaProofOfKnowledgeTree},
+    sigma_boolean::{
+        ProveDhTuple, ProveDlog, SigmaBoolean, SigmaConjecture, SigmaProofOfKnowledgeTree,
+    },
 };
 
+use crate::sigma_protocol::sigma_boolean::cand::Cand;
+use crate::sigma_protocol::sigma_boolean::cor::Cor;
 use crate::sigma_protocol::sigma_boolean::cthreshold::Cthreshold;
 
 #[allow(clippy::todo)] // until https://github.com/ergoplatform/sigma-rust/issues/338 is implemented
@@ -22,8 +25,8 @@ impl SigmaSerializable for SigmaBoolean {
                 SigmaProofOfKnowledgeTree::ProveDlog(v) => v.sigma_serialize(w),
             },
             SigmaBoolean::SigmaConjecture(conj) => match conj {
-                SigmaConjecture::Cand(_) => todo!(),
-                SigmaConjecture::Cor(_) => todo!(),
+                SigmaConjecture::Cand(c) => c.sigma_serialize(w),
+                SigmaConjecture::Cor(c) => c.sigma_serialize(w),
                 SigmaConjecture::Cthreshold(c) => c.sigma_serialize(w),
             },
             SigmaBoolean::TrivialProp(_) => Ok(()), // besides opCode no additional bytes
@@ -36,6 +39,17 @@ impl SigmaSerializable for SigmaBoolean {
             ProveDlog::OP_CODE => Ok(SigmaBoolean::ProofOfKnowledge(
                 SigmaProofOfKnowledgeTree::ProveDlog(ProveDlog::sigma_parse(r)?),
             )),
+            ProveDhTuple::OP_CODE => Ok(SigmaBoolean::ProofOfKnowledge(
+                SigmaProofOfKnowledgeTree::ProveDhTuple(ProveDhTuple::sigma_parse(r)?),
+            )),
+            Cand::OP_CODE => {
+                let c = Cand::sigma_parse(r)?;
+                Ok(SigmaBoolean::SigmaConjecture(SigmaConjecture::Cand(c)))
+            }
+            Cor::OP_CODE => {
+                let c = Cor::sigma_parse(r)?;
+                Ok(SigmaBoolean::SigmaConjecture(SigmaConjecture::Cor(c)))
+            }
             Cthreshold::OP_CODE => {
                 let c = Cthreshold::sigma_parse(r)?;
                 Ok(SigmaBoolean::SigmaConjecture(SigmaConjecture::Cthreshold(
diff --git a/ergotree-ir/src/sigma_protocol/sigma_boolean.rs b/ergotree-ir/src/sigma_protocol/sigma_boolean.rs
index 65a88ee3d..8ee3ffe81 100644
--- a/ergotree-ir/src/sigma_protocol/sigma_boolean.rs
+++ b/ergotree-ir/src/sigma_protocol/sigma_boolean.rs
@@ -69,6 +69,10 @@ pub struct ProveDhTuple {
     pub v: Box<EcPoint>,
 }
 
+impl HasStaticOpCode for ProveDhTuple {
+    const OP_CODE: OpCode = OpCode::PROVE_DIFFIE_HELLMAN_TUPLE;
+}
+
 impl ProveDhTuple {
     /// Create new instance
     pub fn new(g: EcPoint, h: EcPoint, u: EcPoint, v: EcPoint) -> Self {
@@ -81,10 +85,6 @@ impl ProveDhTuple {
     }
 }
 
-impl HasStaticOpCode for ProveDhTuple {
-    const OP_CODE: OpCode = OpCode::PROVE_DIFFIE_HELLMAN_TUPLE;
-}
-
 /// Sigma proposition
 #[derive(PartialEq, Eq, Debug, Clone, From)]
 pub enum SigmaProofOfKnowledgeTree {
@@ -173,12 +173,70 @@ impl From<ProveDlog> for SigmaBoolean {
     }
 }
 
+impl TryInto<ProveDhTuple> for SigmaBoolean {
+    type Error = ConversionError;
+    fn try_into(self) -> Result<ProveDhTuple, Self::Error> {
+        match self {
+            SigmaBoolean::ProofOfKnowledge(SigmaProofOfKnowledgeTree::ProveDhTuple(pdh)) => Ok(pdh),
+            _ => Err(ConversionError),
+        }
+    }
+}
+
 impl From<ProveDhTuple> for SigmaBoolean {
     fn from(v: ProveDhTuple) -> Self {
         SigmaBoolean::ProofOfKnowledge(SigmaProofOfKnowledgeTree::ProveDhTuple(v))
     }
 }
 
+impl TryInto<Cand> for SigmaBoolean {
+    type Error = ConversionError;
+    fn try_into(self) -> Result<Cand, Self::Error> {
+        match self {
+            SigmaBoolean::SigmaConjecture(SigmaConjecture::Cand(c)) => Ok(c),
+            _ => Err(ConversionError),
+        }
+    }
+}
+
+impl From<Cand> for SigmaBoolean {
+    fn from(v: Cand) -> Self {
+        SigmaBoolean::SigmaConjecture(SigmaConjecture::Cand(v))
+    }
+}
+
+impl TryInto<Cor> for SigmaBoolean {
+    type Error = ConversionError;
+    fn try_into(self) -> Result<Cor, Self::Error> {
+        match self {
+            SigmaBoolean::SigmaConjecture(SigmaConjecture::Cor(c)) => Ok(c),
+            _ => Err(ConversionError),
+        }
+    }
+}
+
+impl From<Cor> for SigmaBoolean {
+    fn from(v: Cor) -> Self {
+        SigmaBoolean::SigmaConjecture(SigmaConjecture::Cor(v))
+    }
+}
+
+impl TryInto<Cthreshold> for SigmaBoolean {
+    type Error = ConversionError;
+    fn try_into(self) -> Result<Cthreshold, Self::Error> {
+        match self {
+            SigmaBoolean::SigmaConjecture(SigmaConjecture::Cthreshold(c)) => Ok(c),
+            _ => Err(ConversionError),
+        }
+    }
+}
+
+impl From<Cthreshold> for SigmaBoolean {
+    fn from(v: Cthreshold) -> Self {
+        SigmaBoolean::SigmaConjecture(SigmaConjecture::Cthreshold(v))
+    }
+}
+
 /// Proposition which can be proven and verified by sigma protocol.
 #[derive(PartialEq, Eq, Debug, Clone, From, Into)]
 pub struct SigmaProp(SigmaBoolean);
@@ -230,8 +288,10 @@ impl From<ProveDhTuple> for SigmaProp {
 }
 /// Arbitrary impl for ProveDlog
 #[cfg(feature = "arbitrary")]
+#[allow(clippy::unwrap_used)]
 mod arbitrary {
     use super::*;
+    use proptest::collection::vec;
     use proptest::prelude::*;
 
     impl Arbitrary for ProveDlog {
@@ -243,14 +303,49 @@ mod arbitrary {
         }
     }
 
-    impl Arbitrary for SigmaBoolean {
+    impl Arbitrary for ProveDhTuple {
         type Parameters = ();
         type Strategy = BoxedStrategy<Self>;
 
         fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
-            (any::<ProveDlog>())
-                .prop_map(|p| {
-                    SigmaBoolean::ProofOfKnowledge(SigmaProofOfKnowledgeTree::ProveDlog(p))
+            (
+                any::<EcPoint>(),
+                any::<EcPoint>(),
+                any::<EcPoint>(),
+                any::<EcPoint>(),
+            )
+                .prop_map(|(g, h, u, v)| ProveDhTuple::new(g, h, u, v))
+                .boxed()
+        }
+    }
+
+    pub fn primitive_type_value() -> BoxedStrategy<SigmaBoolean> {
+        prop_oneof![
+            any::<ProveDlog>().prop_map_into(),
+            any::<ProveDhTuple>().prop_map_into(),
+        ]
+        .boxed()
+    }
+
+    impl Arbitrary for SigmaBoolean {
+        type Parameters = ();
+        type Strategy = BoxedStrategy<Self>;
+
+        fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
+            primitive_type_value()
+                .prop_recursive(1, 8, 4, |elem| {
+                    prop_oneof![
+                        vec(elem.clone(), 2..=4)
+                            .prop_map(|elems| Cand {
+                                items: elems.try_into().unwrap()
+                            })
+                            .prop_map_into(),
+                        vec(elem, 2..=4)
+                            .prop_map(|elems| Cor {
+                                items: elems.try_into().unwrap()
+                            })
+                            .prop_map_into(),
+                    ]
                 })
                 .boxed()
         }
@@ -267,4 +362,17 @@ mod arbitrary {
 }
 
 #[cfg(test)]
-mod tests {}
+mod tests {
+    use super::*;
+    use crate::serialization::sigma_serialize_roundtrip;
+    use proptest::prelude::*;
+
+    proptest! {
+
+        #[test]
+        fn sigma_boolean_ser_roundtrip(
+            v in any::<SigmaBoolean>()) {
+                prop_assert_eq![sigma_serialize_roundtrip(&v), v]
+        }
+    }
+}
diff --git a/ergotree-ir/src/sigma_protocol/sigma_boolean/cand.rs b/ergotree-ir/src/sigma_protocol/sigma_boolean/cand.rs
index 52d99e52a..dec162f13 100644
--- a/ergotree-ir/src/sigma_protocol/sigma_boolean/cand.rs
+++ b/ergotree-ir/src/sigma_protocol/sigma_boolean/cand.rs
@@ -5,6 +5,9 @@ use super::SigmaBoolean;
 use super::SigmaConjectureItems;
 use crate::has_opcode::HasStaticOpCode;
 use crate::serialization::op_code::OpCode;
+use crate::serialization::sigma_byte_reader::SigmaByteRead;
+use crate::serialization::sigma_byte_writer::SigmaByteWrite;
+use crate::serialization::{SigmaParsingError, SigmaSerializable, SigmaSerializeResult};
 use crate::sigma_protocol::sigma_boolean::SigmaConjecture;
 
 /// AND conjunction for sigma proposition
@@ -45,11 +48,45 @@ impl Cand {
     }
 }
 
+impl SigmaSerializable for Cand {
+    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> SigmaSerializeResult {
+        self.items.sigma_serialize(w)
+    }
+
+    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SigmaParsingError> {
+        let items = SigmaConjectureItems::<_>::sigma_parse(r)?;
+        Ok(Cand { items })
+    }
+}
+
+#[cfg(feature = "arbitrary")]
+#[allow(clippy::unwrap_used)]
+mod arbitrary {
+    use super::*;
+    use proptest::collection::vec;
+    use proptest::prelude::*;
+
+    impl Arbitrary for Cand {
+        type Parameters = ();
+        type Strategy = BoxedStrategy<Self>;
+
+        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+            vec(any::<SigmaBoolean>(), 2..=4)
+                .prop_map(|items| Cand {
+                    items: items.try_into().unwrap(),
+                })
+                .boxed()
+        }
+    }
+}
+
 #[allow(clippy::unwrap_used)]
 #[cfg(test)]
 mod tests {
     use super::*;
+    use crate::serialization::sigma_serialize_roundtrip;
     use crate::sigma_protocol::sigma_boolean::ProveDlog;
+    use proptest::prelude::*;
     use sigma_test_util::force_any_val;
     use std::convert::TryInto;
 
@@ -92,4 +129,13 @@ mod tests {
             SigmaBoolean::SigmaConjecture(SigmaConjecture::Cand(Cand {items})) if items == pks
         ));
     }
+
+    proptest! {
+
+        #[test]
+        fn sigma_proposition_ser_roundtrip(
+            v in any_with::<Cand>(())) {
+                prop_assert_eq![sigma_serialize_roundtrip(&v), v]
+        }
+    }
 }
diff --git a/ergotree-ir/src/sigma_protocol/sigma_boolean/cor.rs b/ergotree-ir/src/sigma_protocol/sigma_boolean/cor.rs
index 9e430468e..ee58140b2 100644
--- a/ergotree-ir/src/sigma_protocol/sigma_boolean/cor.rs
+++ b/ergotree-ir/src/sigma_protocol/sigma_boolean/cor.rs
@@ -5,6 +5,9 @@ use super::SigmaBoolean;
 use super::SigmaConjectureItems;
 use crate::has_opcode::HasStaticOpCode;
 use crate::serialization::op_code::OpCode;
+use crate::serialization::sigma_byte_reader::SigmaByteRead;
+use crate::serialization::sigma_byte_writer::SigmaByteWrite;
+use crate::serialization::{SigmaParsingError, SigmaSerializable, SigmaSerializeResult};
 use crate::sigma_protocol::sigma_boolean::SigmaConjecture;
 
 /// OR conjunction for sigma proposition
@@ -46,11 +49,45 @@ impl Cor {
     }
 }
 
+impl SigmaSerializable for Cor {
+    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> SigmaSerializeResult {
+        self.items.sigma_serialize(w)
+    }
+
+    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SigmaParsingError> {
+        let items = SigmaConjectureItems::<_>::sigma_parse(r)?;
+        Ok(Cor { items })
+    }
+}
+
+#[cfg(feature = "arbitrary")]
+#[allow(clippy::unwrap_used)]
+mod arbitrary {
+    use super::*;
+    use proptest::collection::vec;
+    use proptest::prelude::*;
+
+    impl Arbitrary for Cor {
+        type Parameters = ();
+        type Strategy = BoxedStrategy<Self>;
+
+        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+            vec(any::<SigmaBoolean>(), 2..=4)
+                .prop_map(|items| Cor {
+                    items: items.try_into().unwrap(),
+                })
+                .boxed()
+        }
+    }
+}
+
 #[allow(clippy::unwrap_used)]
 #[cfg(test)]
 mod tests {
     use super::*;
+    use crate::serialization::sigma_serialize_roundtrip;
     use crate::sigma_protocol::sigma_boolean::ProveDlog;
+    use proptest::prelude::*;
     use sigma_test_util::force_any_val;
     use std::convert::TryInto;
 
@@ -93,4 +130,13 @@ mod tests {
             SigmaBoolean::SigmaConjecture(SigmaConjecture::Cor(Cor {items})) if items == pks
         ));
     }
+
+    proptest! {
+
+        #[test]
+        fn sigma_proposition_ser_roundtrip(
+            v in any_with::<Cor>(())) {
+                prop_assert_eq![sigma_serialize_roundtrip(&v), v]
+        }
+    }
 }


Test patch: