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

Issue:
Implement ReadSigmaVlqExt::peek_u8
Implement and enable test(disabled).


Discussion / hints:

Fix patch:
diff --git a/sigma-chain/Cargo.toml b/sigma-chain/Cargo.toml
index 8a971477c..5f9fc185b 100644
--- a/sigma-chain/Cargo.toml
+++ b/sigma-chain/Cargo.toml
@@ -15,8 +15,6 @@ default = ["console_error_panic_hook"]
 wasm-bindgen = "0.2"
 sigma-ser = { path = "../sigma-ser" }
 sigma-tree = { path = "../sigma-tree" }
-indexmap = "1.3.2"
-base16 = "0.2.1"
 
 # The `console_error_panic_hook` crate provides better debugging of panics by
 # logging them with `console.error`. This is great for development, but requires
diff --git a/sigma-chain/src/ergo_box.rs b/sigma-chain/src/ergo_box.rs
deleted file mode 100644
index 1b43650ad..000000000
--- a/sigma-chain/src/ergo_box.rs
+++ /dev/null
@@ -1,198 +0,0 @@
-//! Ergo box
-use crate::{token::TokenAmount, token::TokenId};
-use indexmap::IndexSet;
-use sigma_ser::serializer::SerializationError;
-use sigma_ser::serializer::SigmaSerializable;
-use sigma_ser::vlq_encode;
-use sigma_tree::ErgoTree;
-use std::collections::HashMap;
-use std::convert::TryFrom;
-use std::io;
-
-#[allow(dead_code)]
-const STARTING_NON_MANDATORY_INDEX: u8 = 4;
-
-#[derive(PartialEq, Eq, Hash, Debug)]
-/// newtype for additional registers R4 - R9
-pub struct NonMandatoryRegisterId(u8);
-
-#[derive(PartialEq, Debug)]
-/// Box (aka coin, or an unspent output) is a basic concept of a UTXO-based cryptocurrency.
-/// In Bitcoin, such an object is associated with some monetary value (arbitrary,
-/// but with predefined precision, so we use integer arithmetic to work with the value),
-/// and also a guarding script (aka proposition) to protect the box from unauthorized opening.
-///
-/// In other way, a box is a state element locked by some proposition (ErgoTree).
-///
-/// In Ergo, box is just a collection of registers, some with mandatory types and semantics,
-/// others could be used by applications in any way.
-/// We add additional fields in addition to amount and proposition~(which stored in the registers R0 and R1).
-/// Namely, register R2 contains additional tokens (a sequence of pairs (token identifier, value)).
-/// Register R3 contains height when block got included into the blockchain and also transaction
-/// identifier and box index in the transaction outputs.
-/// Registers R4-R9 are free for arbitrary usage.
-///
-/// A transaction is unsealing a box. As a box can not be open twice, any further valid transaction
-/// can not be linked to the same box.
-pub struct ErgoBoxCandidate {
-    /// amount of money associated with the box
-    pub value: u64,
-    /// guarding script, which should be evaluated to true in order to open this box
-    pub ergo_tree: ErgoTree,
-    /// secondary tokens the box contains
-    pub tokens: Vec<TokenAmount>,
-    ///  additional registers the box can carry over
-    pub additional_registers: HashMap<NonMandatoryRegisterId, Box<[u8]>>,
-    /// height when a transaction containing the box was created.
-    /// This height is declared by user and should not exceed height of the block,
-    /// containing the transaction with this box.
-    pub creation_height: u32,
-}
-
-impl SigmaSerializable for ErgoBoxCandidate {
-    fn sigma_serialize<W: vlq_encode::WriteSigmaVlqExt>(&self, mut w: W) -> Result<(), io::Error> {
-        serialize_body_with_indexed_digests(self, None, &mut w)
-    }
-    fn sigma_parse<R: vlq_encode::ReadSigmaVlqExt>(r: R) -> Result<Self, SerializationError> {
-        parse_body_with_indexed_digests(None, r)
-    }
-}
-
-/// Box serialization with token ids optionally saved in transaction (in this case only token index is saved)
-pub fn serialize_body_with_indexed_digests<W: vlq_encode::WriteSigmaVlqExt>(
-    b: &ErgoBoxCandidate,
-    token_ids_in_tx: Option<&IndexSet<TokenId>>,
-    mut w: W,
-) -> Result<(), io::Error> {
-    // reference implementation - https://github.com/ScorexFoundation/sigmastate-interpreter/blob/9b20cb110effd1987ff76699d637174a4b2fb441/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala#L95-L95
-    w.put_u64(b.value)?;
-    b.ergo_tree.sigma_serialize(&mut w)?;
-    w.put_u32(b.creation_height)?;
-    w.put_u8(u8::try_from(b.tokens.len()).unwrap())?;
-
-    b.tokens.iter().try_for_each(|t| {
-        match token_ids_in_tx {
-            Some(token_ids) => w.put_u32(
-                u32::try_from(
-                    token_ids
-                        .get_full(&t.token_id)
-                        .expect("failed to find token id in tx's digest index")
-                        .0,
-                )
-                .unwrap(),
-            ),
-            None => t.token_id.sigma_serialize(&mut w),
-        }
-        .and_then(|()| w.put_u64(t.amount))
-    })?;
-
-    assert!(
-        b.additional_registers.is_empty(),
-        "register serialization is not yet implemented"
-    );
-    /*
-        let regs_num = b.additional_registers.keys().len();
-        assert!(
-            (regs_num + STARTING_NON_MANDATORY_INDEX as usize) <= 255,
-            "The number of non-mandatory indexes exceeds 251 limit."
-        );
-        w.put_u8(regs_num as u8)?;
-    */
-
-    /*
-      val nRegs = obj.additionalRegisters.keys.size
-      if (nRegs + ErgoBox.startingNonMandatoryIndex > 255)
-        sys.error(s"The number of non-mandatory indexes $nRegs exceeds ${255 - ErgoBox.startingNonMandatoryIndex} limit.")
-      w.putUByte(nRegs)
-      // we assume non-mandatory indexes are densely packed from startingNonMandatoryIndex
-      // this convention allows to save 1 bite for each register
-      val startReg = ErgoBox.startingNonMandatoryIndex
-      val endReg = ErgoBox.startingNonMandatoryIndex + nRegs - 1
-      cfor(startReg: Int)(_ <= endReg, _ + 1) { regId =>
-        val reg = ErgoBox.findRegisterByIndex(regId.toByte).get
-        obj.get(reg) match {
-          case Some(v) =>
-            w.putValue(v)
-          case None =>
-            sys.error(s"Set of non-mandatory indexes is not densely packed: " +
-              s"register R$regId is missing in the range [$startReg .. $endReg]")
-        }
-      }
-    */
-    Ok(())
-}
-
-/// Box deserialization with token ids optionally parsed in transaction
-pub fn parse_body_with_indexed_digests<R: vlq_encode::ReadSigmaVlqExt>(
-    digests_in_tx: Option<&IndexSet<TokenId>>,
-    mut r: R,
-) -> Result<ErgoBoxCandidate, SerializationError> {
-    // reference implementation -https://github.com/ScorexFoundation/sigmastate-interpreter/blob/9b20cb110effd1987ff76699d637174a4b2fb441/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala#L144-L144
-
-    let value = r.get_u64()?;
-    let ergo_tree = ErgoTree::sigma_parse(&mut r)?;
-    let creation_height = r.get_u32()?;
-    let tokens_count = r.get_u8()?;
-    let mut tokens = Vec::with_capacity(tokens_count as usize);
-    for _ in 0..tokens_count {
-        let token_id = match digests_in_tx {
-            None => TokenId::sigma_parse(&mut r)?,
-            Some(digests) => {
-                let digest_index = r.get_u32()?;
-                *digests
-                    .get_index(digest_index as usize)
-                    .expect("failed to find token id in tx digests")
-            }
-        };
-        let amount = r.get_u64()?;
-        tokens.push(TokenAmount { token_id, amount })
-    }
-
-    let additional_registers = HashMap::new();
-
-    Ok(ErgoBoxCandidate {
-        value,
-        ergo_tree,
-        tokens,
-        additional_registers,
-        creation_height,
-    })
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use proptest::{arbitrary::Arbitrary, collection::vec, prelude::*};
-    use sigma_ser::test_helpers::*;
-    use sigma_testutil::generator::*;
-
-    impl Arbitrary for ErgoBoxCandidate {
-        type Parameters = ();
-
-        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
-            (
-                any::<u64>(),
-                any::<ErgoTreeArb>(),
-                vec(any::<TokenAmount>(), 0..10),
-                any::<u32>(),
-            )
-                .prop_map(|(value, ergo_tree, tokens, creation_height)| Self {
-                    value,
-                    ergo_tree: ergo_tree.0,
-                    tokens,
-                    additional_registers: HashMap::new(),
-                    creation_height,
-                })
-                .boxed()
-        }
-        type Strategy = BoxedStrategy<Self>;
-    }
-
-    proptest! {
-
-        #[test]
-        fn ser_roundtrip(v in any::<ErgoBoxCandidate>()) {
-            prop_assert_eq![sigma_serialize_roundtrip(&v), v];
-        }
-    }
-}
diff --git a/sigma-chain/src/lib.rs b/sigma-chain/src/lib.rs
index 74c6b01d0..ac48ab043 100644
--- a/sigma-chain/src/lib.rs
+++ b/sigma-chain/src/lib.rs
@@ -14,13 +14,3 @@ mod misc;
 mod utils;
 
 pub use misc::*;
-
-pub mod box_id;
-pub mod constants;
-pub mod context_extension;
-pub mod data_input;
-pub mod ergo_box;
-pub mod input;
-pub mod prover_result;
-pub mod token;
-pub mod transaction;
diff --git a/sigma-ser/src/lib.rs b/sigma-ser/src/lib.rs
index cb8227570..c420b7437 100644
--- a/sigma-ser/src/lib.rs
+++ b/sigma-ser/src/lib.rs
@@ -11,6 +11,8 @@
 #![deny(missing_docs)]
 #![forbid(unsafe_code)]
 
+/// io::Read wrapper with `peek` operation
+pub mod peekable_reader;
 /// Sigma serializer
 pub mod serializer;
 /// VLQ encoder
diff --git a/sigma-ser/src/peekable_reader.rs b/sigma-ser/src/peekable_reader.rs
new file mode 100644
index 000000000..af6bdd924
--- /dev/null
+++ b/sigma-ser/src/peekable_reader.rs
@@ -0,0 +1,137 @@
+use std::io::{Error, Read};
+use std::mem::swap;
+
+/// from https://github.com/C4K3/peekable-reader-rs/blob/master/src/lib.rs
+
+/// A wrapper around any struct implementing the `Read` trait, additionally
+/// allowing for `peek` operations to be performed. Therefore, the
+/// `PeekableReader` struct also implements the `Read` trait.
+///
+/// The primary invariant of the `PeekableReader` is that after calling the
+/// `peek` method, the next `read_byte` call will return the same result as
+/// the `peek` does. When the result is a byte (read off the wrapped reader),
+/// any read-type method of the `Reader` trait will include the byte as the
+/// first one. On the other hand, if the result is an `io::Error`, the error
+/// will be returned regardless of which read-type method of the `Reader` is
+/// invoked. Consecutive `peek`s before any read-type operation is used
+/// always return the same `io::Result`.
+///
+/// When using peek_u8 you will get a Result<u8, &Error>, while when you call
+/// any of the `Read` functions you will get an io::Result<u8>, thereby
+/// consuming the io::Error (if any.)
+pub struct PeekableReader<R> {
+    inner: R,
+    peeked_result: Option<Result<u8, Error>>,
+}
+
+/// A wrapper around any struct implementing the `Read` trait, additionally
+/// allowing for `peek` operations to be performed. Therefore, the
+/// `PeekableReader` struct also implements the `Read` trait.
+///
+/// The primary invariant of the `PeekableReader` is that after calling the
+/// `peek` method, the next `read_byte` call will return the same result as
+/// the `peek` does. When the result is a byte (read off the wrapped reader),
+/// any read-type method of the `Reader` trait will include the byte as the
+/// first one. On the other hand, if the result is an `io::Error`, the error
+/// will be returned regardless of which read-type method of the `Reader` is
+/// invoked. Consecutive `peek`s before any read-type operation is used
+/// always return the same `io::Result`.
+///
+pub trait Peekable: Read {
+    /// When using peek_u8 you will get a Result<u8, &Error>, while when you call
+    /// any of the `Read` functions you will get an io::Result<u8>, thereby
+    /// consuming the io::Error (if any.)
+    fn peek_u8(&mut self) -> Result<u8, &Error>;
+}
+
+impl<R: Read> Peekable for PeekableReader<R> {
+    /// Returns the `io::Result` which the Reader will return on the next
+    /// `get_byte` call.
+    ///
+    /// If the `io::Result` is indeed an `io::Error`, the error will be returned
+    /// for any subsequent read operation invoked upon the `Read`er.
+    fn peek_u8(&mut self) -> Result<u8, &Error> {
+        // Return either the currently cached peeked byte or obtain a new one
+        // from the underlying reader.
+        match self.peeked_result {
+            Some(Ok(x)) => Ok(x),
+            Some(Err(ref e)) => Err(e),
+            None => {
+                // First get the result of the read from the underlying reader
+                let mut tmp: [u8; 1] = [0];
+                self.peeked_result = match self.inner.read_exact(&mut tmp) {
+                    Ok(()) => Some(Ok(tmp[0])),
+                    Err(e) => Some(Err(e)),
+                };
+
+                // Now just return that
+                let tmp: Result<u8, &Error> = match self.peeked_result {
+                    Some(Ok(x)) => Ok(x),
+                    Some(Err(ref e)) => Err(e),
+                    None => unreachable!(),
+                };
+                tmp
+            }
+        }
+    }
+}
+
+impl<R: Read> PeekableReader<R> {
+    /// Initializes a new `PeekableReader` which wraps the given underlying
+    /// reader.
+    pub fn new(reader: R) -> PeekableReader<R> {
+        PeekableReader {
+            inner: reader,
+            peeked_result: None,
+        }
+    }
+}
+
+impl<R: Read> Read for PeekableReader<R> {
+    fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
+        if buf.is_empty() {
+            return Ok(0);
+        }
+
+        // First, put the byte that was read off the underlying reader in a
+        // (possible) previous peek operation (if such a byte is indeed cached)
+        let mut tmp = None;
+        swap(&mut tmp, &mut self.peeked_result);
+        let offset = match tmp {
+            Some(Err(e)) => {
+                return Err(e);
+            }
+            Some(Ok(b)) => {
+                buf[0] = b;
+                1
+            }
+            None => 0,
+        };
+
+        if offset == 1 && buf.len() == 1 {
+            // We've filled the buffer by using the previously peeked byte
+            Ok(1)
+        } else {
+            // We are still missing more bytes, so we read them from the
+            // underlying reader and place them directly in the correct place
+            // in the buffer.
+            Ok((self.inner.read(&mut buf[offset..]))? + offset)
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::vlq_encode::ReadSigmaVlqExt;
+    use std::io::Cursor;
+
+    #[test]
+    fn test_peek_u8() {
+        let mut r = PeekableReader::new(Cursor::new(vec![0, 1]));
+        assert_eq!(r.peek_u8().unwrap(), 0);
+        assert_eq!(r.get_u8().unwrap(), 0);
+        assert_eq!(r.peek_u8().unwrap(), 1);
+        assert_eq!(r.get_u8().unwrap(), 1);
+    }
+}
diff --git a/sigma-ser/src/serializer.rs b/sigma-ser/src/serializer.rs
index 608460589..62b7623f5 100644
--- a/sigma-ser/src/serializer.rs
+++ b/sigma-ser/src/serializer.rs
@@ -20,6 +20,9 @@ pub enum SerializationError {
     /// IO fail (EOF, etc.)
     #[error("io error")]
     Io(io::Error),
+    /// Misc fail
+    #[error("misc error")]
+    Misc(&'static str),
 }
 
 impl From<vlq_encode::VlqEncodingError> for SerializationError {
@@ -43,10 +46,10 @@ pub trait SigmaSerializable: Sized {
     /// serialization MUST be infallible up to errors in the underlying writer.
     /// In other words, any type implementing `SigmaSerializable`
     /// must make illegal states unrepresentable.
-    fn sigma_serialize<W: vlq_encode::WriteSigmaVlqExt>(&self, w: W) -> Result<(), io::Error>;
+    fn sigma_serialize<W: vlq_encode::WriteSigmaVlqExt>(&self, w: &mut W) -> Result<(), io::Error>;
 
     /// Try to read `self` from the given `reader`.
     /// `sigma-` prefix to alert the reader that the serialization in use
     /// is consensus-critical
-    fn sigma_parse<R: vlq_encode::ReadSigmaVlqExt>(r: R) -> Result<Self, SerializationError>;
+    fn sigma_parse<R: vlq_encode::ReadSigmaVlqExt>(r: &mut R) -> Result<Self, SerializationError>;
 }
diff --git a/sigma-ser/src/vlq_encode.rs b/sigma-ser/src/vlq_encode.rs
index 81ea6628b..301da5fe0 100644
--- a/sigma-ser/src/vlq_encode.rs
+++ b/sigma-ser/src/vlq_encode.rs
@@ -2,6 +2,8 @@ use super::zig_zag_encode;
 use std::convert::TryFrom;
 use std::io;
 
+use crate::peekable_reader;
+use peekable_reader::Peekable;
 #[cfg(test)]
 use proptest::{num::u64, prelude::*};
 
@@ -57,6 +59,11 @@ pub trait WriteSigmaVlqExt: io::Write {
         Self::put_u16(self, u16::try_from(v).unwrap())
     }
 
+    /// Cast to u32 (with range check) and encode using VLQ
+    fn put_usize_as_u32(&mut self, v: usize) -> io::Result<()> {
+        Self::put_u32(self, u32::try_from(v).unwrap())
+    }
+
     /// 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)
@@ -101,17 +108,7 @@ impl<W: io::Write + ?Sized> WriteSigmaVlqExt for W {}
 /// Read and decode values using VLQ (+ ZigZag for signed values) encoded and written with [`WriteSigmaVlqExt`]
 /// for VLQ see [[https://en.wikipedia.org/wiki/Variable-length_quantity]]
 /// for ZigZag see https://developers.google.com/protocol-buffers/docs/encoding#types
-pub trait ReadSigmaVlqExt: io::Read {
-    /// Peek a u8 without advancing the position
-    fn peek_u8(&mut self) -> Result<u8, io::Error> {
-        // https://github.com/ergoplatform/sigma-rust/issues/20
-        todo!("implement and enable test")
-        // does not work (consumes the reader)
-        // let mut slice = [0u8; 1];
-        // self.take(1).read(&mut slice)?;
-        // Ok(slice[0])
-    }
-
+pub trait ReadSigmaVlqExt: peekable_reader::Peekable {
     /// Read i8 without decoding
     fn get_i8(&mut self) -> Result<i8, io::Error> {
         Self::get_u8(self).map(|v| v as i8)
@@ -174,11 +171,13 @@ pub trait ReadSigmaVlqExt: io::Read {
 }
 
 /// Mark all types implementing `Read` as implementing the extension.
-impl<R: io::Read + ?Sized> ReadSigmaVlqExt for R {}
+// impl<R: io::Read + ?Sized> ReadSigmaVlqExt for R {}
+impl<R: Peekable + ?Sized> ReadSigmaVlqExt for R {}
 
 #[cfg(test)]
 mod tests {
     use super::*;
+    use peekable_reader::PeekableReader;
     use std::io::Cursor;
 
     #[test]
@@ -193,22 +192,12 @@ mod tests {
 
     #[test]
     fn test_read_u8() {
-        let mut r = Cursor::new(vec![0, 1, 255]);
+        let mut r = PeekableReader::new(Cursor::new(vec![0, 1, 255]));
         assert_eq!(r.get_u8().unwrap(), 0);
         assert_eq!(r.get_u8().unwrap(), 1);
         assert_eq!(r.get_u8().unwrap(), 255);
     }
 
-    #[ignore]
-    #[test]
-    fn test_peek_u8() {
-        let mut r = Cursor::new(vec![0, 1]);
-        assert_eq!(r.peek_u8().unwrap(), 0);
-        assert_eq!(r.get_u8().unwrap(), 0);
-        assert_eq!(r.peek_u8().unwrap(), 1);
-        assert_eq!(r.get_u8().unwrap(), 1);
-    }
-
     // from https://github.com/ScorexFoundation/scorex-util/blob/3dc334f68ebefbfab6d33b57f2373e80245ab34d/src/test/scala/scorex/util/serialization/VLQReaderWriterSpecification.scala#L32-L32
     // original source: http://github.com/google/protobuf/blob/a7252bf42df8f0841cf3a0c85fdbf1a5172adecb/java/core/src/test/java/com/google/protobuf/CodedInputStreamTest.java#L239
     #[allow(clippy::identity_op)]
@@ -273,7 +262,7 @@ mod tests {
     fn test_read_u64_expected_values() {
         for pair in expected_values() {
             let (bytes, value) = pair;
-            let mut r = Cursor::new(bytes);
+            let mut r = PeekableReader::new(Cursor::new(bytes));
             let decoded_value = r.get_u64().unwrap();
             assert_eq!(decoded_value, value)
         }
@@ -286,7 +275,7 @@ mod tests {
         fn prop_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());
+            let mut r = PeekableReader::new(Cursor::new(w.into_inner()));
             prop_assert_eq![i, r.get_u64().unwrap()];
         }
 
@@ -297,7 +286,7 @@ mod tests {
                 w.put_u64(*a).unwrap();
             }
             let mut dec = Vec::new();
-            let mut r = Cursor::new(w.into_inner());
+            let mut r = PeekableReader::new(Cursor::new(w.into_inner()));
             for _ in 0..arr.len() {
                 dec.push(r.get_u64().unwrap());
             }
diff --git a/sigma-tree/Cargo.toml b/sigma-tree/Cargo.toml
index b42a22a1d..f327e736b 100644
--- a/sigma-tree/Cargo.toml
+++ b/sigma-tree/Cargo.toml
@@ -9,11 +9,15 @@ crate-type = ["cdylib", "rlib"]
 
 [dependencies]
 sigma-ser = { path = "../sigma-ser" }
+indexmap = "1.3.2"
+base16 = "0.2.1"
+libsecp256k1 = "0.3.5"
 
 [dev-dependencies]
 wasm-bindgen-test = "0.3.10"
 proptest-derive = "0.1.2"
 sigma-testutil = { path = "../sigma-testutil"}
+rand = "0.7.3"
 
 [dev-dependencies.proptest]
 # wasm support, via https://altsysrq.github.io/proptest-book/proptest/wasm.html
diff --git a/sigma-tree/src/ast.rs b/sigma-tree/src/ast.rs
index e806510fe..bfe8fee59 100644
--- a/sigma-tree/src/ast.rs
+++ b/sigma-tree/src/ast.rs
@@ -1,12 +1,13 @@
-use crate::data::{SigmaBox, SigmaProp};
-use crate::{serialization::op_code::OpCode, types::*};
+use crate::{data::SigmaProp, serialization::op_code::OpCode, types::*};
 use core::fmt;
 use Expr::*;
 
 pub mod ops;
 
+#[derive(PartialEq, Eq, Debug)]
 pub struct RegisterId(u8);
 
+#[derive(PartialEq, Eq, Debug)]
 pub enum CollPrim {
     CollBoolean(Vec<bool>),
     CollByte(Vec<i8>),
@@ -15,7 +16,12 @@ pub enum CollPrim {
     CollLong(Vec<i64>),
 }
 
-pub enum Const {
+// TODO: extract and wrap ErgoBoxCandidate
+#[derive(PartialEq, Eq, Debug)]
+pub struct ErgoBox {}
+
+#[derive(PartialEq, Eq, Debug)]
+pub enum ConstantVal {
     Boolean(bool),
     Byte(i8),
     Short(i16),
@@ -23,19 +29,23 @@ pub enum Const {
     Long(i64),
     BigInt,
     GroupElement,
-    SigmaProp(Box<dyn SigmaProp>),
-    CBox(Box<dyn SigmaBox>),
+    SigmaProp(Box<SigmaProp>),
+    CBox(Box<ErgoBox>),
     AvlTree,
     CollPrim(CollPrim),
-    Coll(Vec<Const>),
-    Tup(Vec<Const>),
+    Coll(Vec<ConstantVal>),
+    Tup(Vec<ConstantVal>),
+}
+
+#[derive(PartialEq, Eq, Debug)]
+pub struct Constant {
+    pub tpe: SType,
+    pub v: ConstantVal,
 }
 
+#[derive(PartialEq, Eq, Debug)]
 pub enum Expr {
-    Constant {
-        tpe: SType,
-        v: Const,
-    },
+    Const(Constant),
     Coll {
         tpe: SType,
         v: Vec<Expr>,
@@ -60,7 +70,7 @@ pub enum Expr {
 impl Expr {
     pub fn op_code(&self) -> OpCode {
         match self {
-            Constant { .. } => todo!(),
+            Const { .. } => todo!(),
             Coll { .. } => todo!(),
             Tup { .. } => todo!(),
             BoxM(boxm) => boxm.op_code(),
@@ -74,7 +84,7 @@ impl Expr {
 
     pub fn tpe(&self) -> &SType {
         match self {
-            Constant { tpe, .. } => tpe,
+            Const(c) => &c.tpe,
             _ => todo!(),
         }
     }
@@ -86,6 +96,7 @@ impl fmt::Display for Expr {
     }
 }
 
+#[derive(PartialEq, Eq, Debug)]
 pub enum CollMethods {
     Fold {
         input: Box<Expr>,
@@ -94,6 +105,7 @@ pub enum CollMethods {
     },
 }
 
+#[derive(PartialEq, Eq, Debug)]
 pub enum BoxMethods {
     ExtractRegisterAs {
         input: Box<Expr>,
@@ -107,11 +119,13 @@ impl BoxMethods {
     }
 }
 
+#[derive(PartialEq, Eq, Debug)]
 pub enum ContextMethods {
     Inputs,
     Outputs,
 }
 
+#[derive(PartialEq, Eq, Debug)]
 pub enum PredefFunc {
     Sha256 { input: Box<Expr> },
 }
diff --git a/sigma-tree/src/ast/ops.rs b/sigma-tree/src/ast/ops.rs
index 89f9088c5..84ac95b1f 100644
--- a/sigma-tree/src/ast/ops.rs
+++ b/sigma-tree/src/ast/ops.rs
@@ -1,7 +1,9 @@
+#[derive(PartialEq, Eq, Debug)]
 pub enum NumOp {
     Add,
 }
 
+#[derive(PartialEq, Eq, Debug)]
 pub enum BinOp {
     Num(NumOp),
 }
diff --git a/sigma-tree/src/chain.rs b/sigma-tree/src/chain.rs
new file mode 100644
index 000000000..84b7875da
--- /dev/null
+++ b/sigma-tree/src/chain.rs
@@ -0,0 +1,10 @@
+//! Ergo chain types
+
+mod box_id;
+mod context_extension;
+mod data_input;
+mod ergo_box;
+mod input;
+mod prover_result;
+mod token;
+mod transaction;
diff --git a/sigma-chain/src/box_id.rs b/sigma-tree/src/chain/box_id.rs
similarity index 81%
rename from sigma-chain/src/box_id.rs
rename to sigma-tree/src/chain/box_id.rs
index c46d0fc25..13f956346 100644
--- a/sigma-chain/src/box_id.rs
+++ b/sigma-tree/src/chain/box_id.rs
@@ -16,11 +16,11 @@ use proptest_derive::Arbitrary;
 pub struct BoxId(pub [u8; BOX_ID_SIZE]);
 
 impl SigmaSerializable for BoxId {
-    fn sigma_serialize<W: vlq_encode::WriteSigmaVlqExt>(&self, mut w: W) -> Result<(), io::Error> {
+    fn sigma_serialize<W: vlq_encode::WriteSigmaVlqExt>(&self, w: &mut W) -> Result<(), io::Error> {
         w.write_all(&self.0)?;
         Ok(())
     }
-    fn sigma_parse<R: vlq_encode::ReadSigmaVlqExt>(mut r: R) -> Result<Self, SerializationError> {
+    fn sigma_parse<R: vlq_encode::ReadSigmaVlqExt>(r: &mut R) -> Result<Self, SerializationError> {
         let mut bytes = [0; BOX_ID_SIZE];
         r.read_exact(&mut bytes)?;
         Ok(Self(bytes))
diff --git a/sigma-chain/src/context_extension.rs b/sigma-tree/src/chain/context_extension.rs
similarity index 79%
rename from sigma-chain/src/context_extension.rs
rename to sigma-tree/src/chain/context_extension.rs
index 2b24370e1..becb94aff 100644
--- a/sigma-chain/src/context_extension.rs
+++ b/sigma-tree/src/chain/context_extension.rs
@@ -22,13 +22,11 @@ impl ContextExtension {
 }
 
 impl SigmaSerializable for ContextExtension {
-    fn sigma_serialize<W: vlq_encode::WriteSigmaVlqExt>(&self, _: W) -> Result<(), io::Error> {
+    fn sigma_serialize<W: vlq_encode::WriteSigmaVlqExt>(&self, _: &mut W) -> Result<(), io::Error> {
         assert!(self.values.is_empty(), "implemented only for empty");
         Ok(())
     }
-    fn sigma_parse<R: vlq_encode::ReadSigmaVlqExt>(_: R) -> Result<Self, SerializationError> {
-        Ok(ContextExtension {
-            values: HashMap::new(),
-        })
+    fn sigma_parse<R: vlq_encode::ReadSigmaVlqExt>(_: &mut R) -> Result<Self, SerializationError> {
+        Ok(ContextExtension::empty())
     }
 }
diff --git a/sigma-chain/src/data_input.rs b/sigma-tree/src/chain/data_input.rs
similarity index 86%
rename from sigma-chain/src/data_input.rs
rename to sigma-tree/src/chain/data_input.rs
index 5d622fdfc..a527417ce 100644
--- a/sigma-chain/src/data_input.rs
+++ b/sigma-tree/src/chain/data_input.rs
@@ -1,11 +1,11 @@
 //! DataInput type
 
-use crate::box_id::BoxId;
 use sigma_ser::serializer::SerializationError;
 use sigma_ser::serializer::SigmaSerializable;
 use sigma_ser::vlq_encode;
 use std::io;
 
+use super::box_id::BoxId;
 #[cfg(test)]
 use proptest::prelude::*;
 #[cfg(test)]
@@ -20,11 +20,11 @@ pub struct DataInput {
 }
 
 impl SigmaSerializable for DataInput {
-    fn sigma_serialize<W: vlq_encode::WriteSigmaVlqExt>(&self, w: W) -> Result<(), io::Error> {
+    fn sigma_serialize<W: vlq_encode::WriteSigmaVlqExt>(&self, w: &mut W) -> Result<(), io::Error> {
         self.box_id.sigma_serialize(w)?;
         Ok(())
     }
-    fn sigma_parse<R: vlq_encode::ReadSigmaVlqExt>(r: R) -> Result<Self, SerializationError> {
+    fn sigma_parse<R: vlq_encode::ReadSigmaVlqExt>(r: &mut R) -> Result<Self, SerializationError> {
         let box_id = BoxId::sigma_parse(r)?;
         Ok(DataInput { box_id })
     }
diff --git a/sigma-tree/src/chain/ergo_box.rs b/sigma-tree/src/chain/ergo_box.rs
new file mode 100644
index 000000000..ff1ceebe8
--- /dev/null
+++ b/sigma-tree/src/chain/ergo_box.rs
@@ -0,0 +1,199 @@
+//! Ergo box
+use super::token::{TokenAmount, TokenId};
+use crate::ergo_tree::ErgoTree;
+use indexmap::IndexSet;
+use sigma_ser::serializer::SerializationError;
+use sigma_ser::serializer::SigmaSerializable;
+use sigma_ser::vlq_encode;
+use std::collections::HashMap;
+use std::convert::TryFrom;
+use std::io;
+
+#[allow(dead_code)]
+const STARTING_NON_MANDATORY_INDEX: u8 = 4;
+
+#[derive(PartialEq, Eq, Hash, Debug)]
+/// newtype for additional registers R4 - R9
+pub struct NonMandatoryRegisterId(u8);
+
+#[derive(PartialEq, Debug)]
+/// Box (aka coin, or an unspent output) is a basic concept of a UTXO-based cryptocurrency.
+/// In Bitcoin, such an object is associated with some monetary value (arbitrary,
+/// but with predefined precision, so we use integer arithmetic to work with the value),
+/// and also a guarding script (aka proposition) to protect the box from unauthorized opening.
+///
+/// In other way, a box is a state element locked by some proposition (ErgoTree).
+///
+/// In Ergo, box is just a collection of registers, some with mandatory types and semantics,
+/// others could be used by applications in any way.
+/// We add additional fields in addition to amount and proposition~(which stored in the registers R0 and R1).
+/// Namely, register R2 contains additional tokens (a sequence of pairs (token identifier, value)).
+/// Register R3 contains height when block got included into the blockchain and also transaction
+/// identifier and box index in the transaction outputs.
+/// Registers R4-R9 are free for arbitrary usage.
+///
+/// A transaction is unsealing a box. As a box can not be open twice, any further valid transaction
+/// can not be linked to the same box.
+pub struct ErgoBoxCandidate {
+    /// amount of money associated with the box
+    pub value: u64,
+    /// guarding script, which should be evaluated to true in order to open this box
+    pub ergo_tree: ErgoTree,
+    /// secondary tokens the box contains
+    pub tokens: Vec<TokenAmount>,
+    ///  additional registers the box can carry over
+    pub additional_registers: HashMap<NonMandatoryRegisterId, Box<[u8]>>,
+    /// height when a transaction containing the box was created.
+    /// This height is declared by user and should not exceed height of the block,
+    /// containing the transaction with this box.
+    pub creation_height: u32,
+}
+
+impl ErgoBoxCandidate {
+    /// Box serialization with token ids optionally saved in transaction (in this case only token index is saved)
+    pub fn serialize_body_with_indexed_digests<W: vlq_encode::WriteSigmaVlqExt>(
+        &self,
+        token_ids_in_tx: Option<&IndexSet<TokenId>>,
+        w: &mut W,
+    ) -> Result<(), io::Error> {
+        // reference implementation - https://github.com/ScorexFoundation/sigmastate-interpreter/blob/9b20cb110effd1987ff76699d637174a4b2fb441/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala#L95-L95
+        w.put_u64(self.value)?;
+        self.ergo_tree.sigma_serialize(w)?;
+        w.put_u32(self.creation_height)?;
+        w.put_u8(u8::try_from(self.tokens.len()).unwrap())?;
+
+        self.tokens.iter().try_for_each(|t| {
+            match token_ids_in_tx {
+                Some(token_ids) => w.put_u32(
+                    u32::try_from(
+                        token_ids
+                            .get_full(&t.token_id)
+                            .expect("failed to find token id in tx's digest index")
+                            .0,
+                    )
+                    .unwrap(),
+                ),
+                None => t.token_id.sigma_serialize(w),
+            }
+            .and_then(|()| w.put_u64(t.amount))
+        })?;
+
+        assert!(
+            self.additional_registers.is_empty(),
+            "register serialization is not yet implemented"
+        );
+        /*
+            let regs_num = self.additional_registers.keys().len();
+            assert!(
+                (regs_num + STARTING_NON_MANDATORY_INDEX as usize) <= 255,
+                "The number of non-mandatory indexes exceeds 251 limit."
+            );
+            w.put_u8(regs_num as u8)?;
+        */
+
+        /*
+          val nRegs = obj.additionalRegisters.keys.size
+          if (nRegs + ErgoBox.startingNonMandatoryIndex > 255)
+            sys.error(s"The number of non-mandatory indexes $nRegs exceeds ${255 - ErgoBox.startingNonMandatoryIndex} limit.")
+          w.putUByte(nRegs)
+          // we assume non-mandatory indexes are densely packed from startingNonMandatoryIndex
+          // this convention allows to save 1 bite for each register
+          val startReg = ErgoBox.startingNonMandatoryIndex
+          val endReg = ErgoBox.startingNonMandatoryIndex + nRegs - 1
+          cfor(startReg: Int)(_ <= endReg, _ + 1) { regId =>
+            val reg = ErgoBox.findRegisterByIndex(regId.toByte).get
+            obj.get(reg) match {
+              case Some(v) =>
+                w.putValue(v)
+              case None =>
+                sys.error(s"Set of non-mandatory indexes is not densely packed: " +
+                  s"register R$regId is missing in the range [$startReg .. $endReg]")
+            }
+          }
+        */
+        Ok(())
+    }
+
+    /// Box deserialization with token ids optionally parsed in transaction
+    pub fn parse_body_with_indexed_digests<R: vlq_encode::ReadSigmaVlqExt>(
+        digests_in_tx: Option<&IndexSet<TokenId>>,
+        r: &mut R,
+    ) -> Result<ErgoBoxCandidate, SerializationError> {
+        // reference implementation -https://github.com/ScorexFoundation/sigmastate-interpreter/blob/9b20cb110effd1987ff76699d637174a4b2fb441/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala#L144-L144
+
+        let value = r.get_u64()?;
+        let ergo_tree = ErgoTree::sigma_parse(r)?;
+        let creation_height = r.get_u32()?;
+        let tokens_count = r.get_u8()?;
+        let mut tokens = Vec::with_capacity(tokens_count as usize);
+        for _ in 0..tokens_count {
+            let token_id = match digests_in_tx {
+                None => TokenId::sigma_parse(r)?,
+                Some(digests) => {
+                    let digest_index = r.get_u32()?;
+                    *digests
+                        .get_index(digest_index as usize)
+                        .expect("failed to find token id in tx digests")
+                }
+            };
+            let amount = r.get_u64()?;
+            tokens.push(TokenAmount { token_id, amount })
+        }
+
+        let additional_registers = HashMap::new();
+
+        Ok(ErgoBoxCandidate {
+            value,
+            ergo_tree,
+            tokens,
+            additional_registers,
+            creation_height,
+        })
+    }
+}
+
+impl SigmaSerializable for ErgoBoxCandidate {
+    fn sigma_serialize<W: vlq_encode::WriteSigmaVlqExt>(&self, w: &mut W) -> Result<(), io::Error> {
+        self.serialize_body_with_indexed_digests(None, w)
+    }
+    fn sigma_parse<R: vlq_encode::ReadSigmaVlqExt>(r: &mut R) -> Result<Self, SerializationError> {
+        ErgoBoxCandidate::parse_body_with_indexed_digests(None, r)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use proptest::{arbitrary::Arbitrary, collection::vec, prelude::*};
+    use sigma_ser::test_helpers::*;
+
+    impl Arbitrary for ErgoBoxCandidate {
+        type Parameters = ();
+
+        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+            (
+                any::<u64>(),
+                any::<ErgoTree>(),
+                vec(any::<TokenAmount>(), 0..10),
+                any::<u32>(),
+            )
+                .prop_map(|(value, ergo_tree, tokens, creation_height)| Self {
+                    value,
+                    ergo_tree,
+                    tokens,
+                    additional_registers: HashMap::new(),
+                    creation_height,
+                })
+                .boxed()
+        }
+        type Strategy = BoxedStrategy<Self>;
+    }
+
+    proptest! {
+
+        #[test]
+        fn ser_roundtrip(v in any::<ErgoBoxCandidate>()) {
+            prop_assert_eq![sigma_serialize_roundtrip(&v), v];
+        }
+    }
+}
diff --git a/sigma-chain/src/input.rs b/sigma-tree/src/chain/input.rs
similarity index 65%
rename from sigma-chain/src/input.rs
rename to sigma-tree/src/chain/input.rs
index 2e7abd8d0..e9fa6c4be 100644
--- a/sigma-chain/src/input.rs
+++ b/sigma-tree/src/chain/input.rs
@@ -1,11 +1,10 @@
 //! Transactio input
-use crate::box_id::BoxId;
-use crate::prover_result::ProverResult;
 use sigma_ser::serializer::SerializationError;
 use sigma_ser::serializer::SigmaSerializable;
 use sigma_ser::vlq_encode;
 use std::io;
 
+use super::{box_id::BoxId, prover_result::ProverResult};
 #[cfg(test)]
 use proptest::prelude::*;
 #[cfg(test)]
@@ -22,14 +21,14 @@ pub struct Input {
 }
 
 impl SigmaSerializable for Input {
-    fn sigma_serialize<W: vlq_encode::WriteSigmaVlqExt>(&self, mut w: W) -> Result<(), io::Error> {
-        self.box_id.sigma_serialize(&mut w)?;
-        self.spending_proof.sigma_serialize(&mut w)?;
+    fn sigma_serialize<W: vlq_encode::WriteSigmaVlqExt>(&self, w: &mut W) -> Result<(), io::Error> {
+        self.box_id.sigma_serialize(w)?;
+        self.spending_proof.sigma_serialize(w)?;
         Ok(())
     }
-    fn sigma_parse<R: vlq_encode::ReadSigmaVlqExt>(mut r: R) -> Result<Self, SerializationError> {
-        let box_id = BoxId::sigma_parse(&mut r)?;
-        let spending_proof = ProverResult::sigma_parse(&mut r)?;
+    fn sigma_parse<R: vlq_encode::ReadSigmaVlqExt>(r: &mut R) -> Result<Self, SerializationError> {
+        let box_id = BoxId::sigma_parse(r)?;
+        let spending_proof = ProverResult::sigma_parse(r)?;
         Ok(Input {
             box_id,
             spending_proof,
diff --git a/sigma-chain/src/prover_result.rs b/sigma-tree/src/chain/prover_result.rs
similarity index 86%
rename from sigma-chain/src/prover_result.rs
rename to sigma-tree/src/chain/prover_result.rs
index 2fe180b5e..d1c4fbdae 100644
--- a/sigma-chain/src/prover_result.rs
+++ b/sigma-tree/src/chain/prover_result.rs
@@ -1,10 +1,10 @@
 //! ProverResult
-use crate::context_extension::ContextExtension;
 use sigma_ser::serializer::SerializationError;
 use sigma_ser::serializer::SigmaSerializable;
 use sigma_ser::vlq_encode;
 use std::io;
 
+use super::context_extension::ContextExtension;
 #[cfg(test)]
 use proptest::{arbitrary::Arbitrary, collection::vec, prelude::*};
 
@@ -18,13 +18,13 @@ pub struct ProverResult {
 }
 
 impl SigmaSerializable for ProverResult {
-    fn sigma_serialize<W: vlq_encode::WriteSigmaVlqExt>(&self, mut w: W) -> Result<(), io::Error> {
+    fn sigma_serialize<W: vlq_encode::WriteSigmaVlqExt>(&self, w: &mut W) -> Result<(), io::Error> {
         w.put_u16(self.proof.len() as u16)?;
         w.write_all(&self.proof)?;
         self.extension.sigma_serialize(w)?;
         Ok(())
     }
-    fn sigma_parse<R: vlq_encode::ReadSigmaVlqExt>(mut r: R) -> Result<Self, SerializationError> {
+    fn sigma_parse<R: vlq_encode::ReadSigmaVlqExt>(r: &mut R) -> Result<Self, SerializationError> {
         let proof_len = r.get_u16()?;
         let mut proof = vec![0; proof_len as usize];
         r.read_exact(&mut proof)?;
diff --git a/sigma-chain/src/token.rs b/sigma-tree/src/chain/token.rs
similarity index 88%
rename from sigma-chain/src/token.rs
rename to sigma-tree/src/chain/token.rs
index e641a722c..84fb60092 100644
--- a/sigma-chain/src/token.rs
+++ b/sigma-tree/src/chain/token.rs
@@ -39,11 +39,11 @@ impl fmt::Display for TokenId {
 }
 
 impl SigmaSerializable for TokenId {
-    fn sigma_serialize<W: vlq_encode::WriteSigmaVlqExt>(&self, mut w: W) -> Result<(), io::Error> {
+    fn sigma_serialize<W: vlq_encode::WriteSigmaVlqExt>(&self, w: &mut W) -> Result<(), io::Error> {
         w.write_all(&self.0)?;
         Ok(())
     }
-    fn sigma_parse<R: vlq_encode::ReadSigmaVlqExt>(mut r: R) -> Result<Self, SerializationError> {
+    fn sigma_parse<R: vlq_encode::ReadSigmaVlqExt>(r: &mut R) -> Result<Self, SerializationError> {
         let mut bytes = [0; TOKEN_ID_SIZE];
         r.read_exact(&mut bytes)?;
         Ok(Self(bytes))
diff --git a/sigma-chain/src/transaction.rs b/sigma-tree/src/chain/transaction.rs
similarity index 83%
rename from sigma-chain/src/transaction.rs
rename to sigma-tree/src/chain/transaction.rs
index 33a1e44ea..b0eb5e8a5 100644
--- a/sigma-chain/src/transaction.rs
+++ b/sigma-tree/src/chain/transaction.rs
@@ -1,8 +1,6 @@
 //! Ergo transaction
 
-use crate::data_input::DataInput;
-use crate::ergo_box::{self, ErgoBoxCandidate};
-use crate::{input::Input, token::TokenId};
+use super::{data_input::DataInput, ergo_box::ErgoBoxCandidate, input::Input, token::TokenId};
 use indexmap::IndexSet;
 use sigma_ser::serializer::SerializationError;
 use sigma_ser::serializer::SigmaSerializable;
@@ -35,16 +33,14 @@ pub struct Transaction {
 }
 
 impl SigmaSerializable for Transaction {
-    fn sigma_serialize<W: vlq_encode::WriteSigmaVlqExt>(&self, mut w: W) -> Result<(), io::Error> {
+    fn sigma_serialize<W: vlq_encode::WriteSigmaVlqExt>(&self, w: &mut W) -> Result<(), io::Error> {
         // reference implementation - https://github.com/ScorexFoundation/sigmastate-interpreter/blob/9b20cb110effd1987ff76699d637174a4b2fb441/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala#L112-L112
         w.put_usize_as_u16(self.inputs.len())?;
-        self.inputs
-            .iter()
-            .try_for_each(|i| i.sigma_serialize(&mut w))?;
+        self.inputs.iter().try_for_each(|i| i.sigma_serialize(w))?;
         w.put_usize_as_u16(self.data_inputs.len())?;
         self.data_inputs
             .iter()
-            .try_for_each(|i| i.sigma_serialize(&mut w))?;
+            .try_for_each(|i| i.sigma_serialize(w))?;
 
         // Serialize distinct ids of tokens in transaction outputs.
         // This optimization is crucial to allow up to MaxTokens (== 255) in a box.
@@ -58,47 +54,47 @@ impl SigmaSerializable for Transaction {
         w.put_u32(u32::try_from(distinct_token_ids.len()).unwrap())?;
         distinct_token_ids
             .iter()
-            .try_for_each(|t_id| t_id.sigma_serialize(&mut w))?;
+            .try_for_each(|t_id| t_id.sigma_serialize(w))?;
 
         // serialize outputs
         w.put_usize_as_u16(self.outputs.len())?;
         self.outputs.iter().try_for_each(|o| {
-            ergo_box::serialize_body_with_indexed_digests(o, Some(&distinct_token_ids), &mut w)
+            ErgoBoxCandidate::serialize_body_with_indexed_digests(o, Some(&distinct_token_ids), w)
         })?;
         Ok(())
     }
 
-    fn sigma_parse<R: vlq_encode::ReadSigmaVlqExt>(mut r: R) -> Result<Self, SerializationError> {
+    fn sigma_parse<R: vlq_encode::ReadSigmaVlqExt>(r: &mut R) -> Result<Self, SerializationError> {
         // reference implementation - https://github.com/ScorexFoundation/sigmastate-interpreter/blob/9b20cb110effd1987ff76699d637174a4b2fb441/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala#L146-L146
 
         // parse transaction inputs
         let inputs_count = r.get_u16()?;
         let mut inputs = Vec::with_capacity(inputs_count as usize);
         for _ in 0..inputs_count {
-            inputs.push(Input::sigma_parse(&mut r)?);
+            inputs.push(Input::sigma_parse(r)?);
         }
 
         // parse transaction data inputs
         let data_inputs_count = r.get_u16()?;
         let mut data_inputs = Vec::with_capacity(data_inputs_count as usize);
         for _ in 0..data_inputs_count {
-            data_inputs.push(DataInput::sigma_parse(&mut r)?);
+            data_inputs.push(DataInput::sigma_parse(r)?);
         }
 
         // parse distinct ids of tokens in transaction outputs
         let tokens_count = r.get_u32()?;
         let mut token_ids = IndexSet::with_capacity(tokens_count as usize);
         for _ in 0..tokens_count {
-            token_ids.insert(TokenId::sigma_parse(&mut r)?);
+            token_ids.insert(TokenId::sigma_parse(r)?);
         }
 
         // parse outputs
         let outputs_count = r.get_u16()?;
         let mut outputs = Vec::with_capacity(outputs_count as usize);
         for _ in 0..outputs_count {
-            outputs.push(ergo_box::parse_body_with_indexed_digests(
+            outputs.push(ErgoBoxCandidate::parse_body_with_indexed_digests(
                 Some(&token_ids),
-                &mut r,
+                r,
             )?)
         }
 
diff --git a/sigma-chain/src/constants.rs b/sigma-tree/src/constants.rs
similarity index 100%
rename from sigma-chain/src/constants.rs
rename to sigma-tree/src/constants.rs
diff --git a/sigma-tree/src/data.rs b/sigma-tree/src/data.rs
index cd7ca01dd..1857f47e0 100644
--- a/sigma-tree/src/data.rs
+++ b/sigma-tree/src/data.rs
@@ -1,31 +1,65 @@
 //! Underlying Sigma data types
 
+use crate::{ecpoint::EcPoint, serialization::op_code::OpCode};
+
 #[allow(dead_code)]
+#[derive(PartialEq, Eq, Debug)]
 pub enum SigmaBoolean {
-    ProveDlog(u64),
+    ProveDHTuple {
+        gv: Box<EcPoint>,
+        hv: Box<EcPoint>,
+        uv: Box<EcPoint>,
+        vv: Box<EcPoint>,
+    },
+    ProveDlog(Box<EcPoint>),
     CAND(Vec<SigmaBoolean>),
 }
 
-pub trait SigmaProp {
-    fn is_valid(&self) -> bool;
+impl SigmaBoolean {
+    pub fn op_code(&self) -> OpCode {
+        match self {
+            SigmaBoolean::ProveDHTuple { .. } => todo!(),
+            SigmaBoolean::ProveDlog(_) => OpCode::PROVE_DLOG,
+            SigmaBoolean::CAND(_) => todo!(),
+        }
+    }
 }
 
-pub struct CSigmaProp {
-    pub sigma_tree: SigmaBoolean,
-}
+#[derive(PartialEq, Eq, Debug)]
+pub struct SigmaProp(SigmaBoolean);
 
-impl SigmaProp for CSigmaProp {
-    fn is_valid(&self) -> bool {
-        todo!()
+impl SigmaProp {
+    pub fn new(sbool: SigmaBoolean) -> Self {
+        SigmaProp { 0: sbool }
     }
-}
 
-pub trait SigmaBox {
-    fn value(&self) -> u64;
+    pub fn value(&self) -> &SigmaBoolean {
+        &self.0
+    }
 }
-pub struct CSigmaBox {}
-impl SigmaBox for CSigmaBox {
-    fn value(&self) -> u64 {
-        0
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use proptest::prelude::*;
+
+    impl Arbitrary for SigmaBoolean {
+        type Parameters = ();
+        type Strategy = BoxedStrategy<Self>;
+
+        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+            (any::<EcPoint>())
+                .prop_map(|ecp| SigmaBoolean::ProveDlog(Box::new(ecp)))
+                .boxed()
+        }
+    }
+
+    impl Arbitrary for SigmaProp {
+        type Parameters = ();
+        type Strategy = BoxedStrategy<Self>;
+
+        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+            (any::<SigmaBoolean>()).prop_map(|sb| SigmaProp(sb)).boxed()
+        }
     }
 }
diff --git a/sigma-tree/src/ecpoint.rs b/sigma-tree/src/ecpoint.rs
new file mode 100644
index 000000000..ce1228b09
--- /dev/null
+++ b/sigma-tree/src/ecpoint.rs
@@ -0,0 +1,60 @@
+use secp256k1::PublicKey;
+use sigma_ser::{
+    serializer::{SerializationError, SigmaSerializable},
+    vlq_encode,
+};
+use std::io;
+
+#[derive(PartialEq, Eq, Debug)]
+pub struct EcPoint(pub PublicKey);
+
+impl EcPoint {
+    pub const PUBLIC_KEY_SIZE: usize = secp256k1::util::COMPRESSED_PUBLIC_KEY_SIZE;
+}
+
+impl SigmaSerializable for EcPoint {
+    fn sigma_serialize<W: vlq_encode::WriteSigmaVlqExt>(&self, w: &mut W) -> Result<(), io::Error> {
+        w.write_all(&self.0.serialize_compressed())?;
+        Ok(())
+    }
+
+    fn sigma_parse<R: vlq_encode::ReadSigmaVlqExt>(r: &mut R) -> Result<Self, SerializationError> {
+        let mut bytes = [0; EcPoint::PUBLIC_KEY_SIZE];
+        r.read_exact(&mut bytes[..])?;
+        let pk = PublicKey::parse_compressed(&bytes)
+            .map_err(|_| SerializationError::Misc("invalid secp256k1 compressed public key"))?;
+        Ok(EcPoint(pk))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use proptest::prelude::*;
+    use rand::thread_rng;
+    use secp256k1::SecretKey;
+    use sigma_ser::test_helpers::*;
+
+    impl Arbitrary for EcPoint {
+        type Parameters = ();
+        type Strategy = BoxedStrategy<Self>;
+
+        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+            (any::<i32>())
+                .prop_map(|_| {
+                    let sk = SecretKey::random(&mut thread_rng());
+                    let pk = PublicKey::from_secret_key(&sk);
+                    EcPoint(pk)
+                })
+                .boxed()
+        }
+    }
+
+    proptest! {
+
+        #[test]
+        fn ser_roundtrip(v in any::<EcPoint>()) {
+            prop_assert_eq![sigma_serialize_roundtrip(&v), v];
+        }
+    }
+}
diff --git a/sigma-tree/src/ergo_tree.rs b/sigma-tree/src/ergo_tree.rs
index 9f2f5c2f8..4b7275a23 100644
--- a/sigma-tree/src/ergo_tree.rs
+++ b/sigma-tree/src/ergo_tree.rs
@@ -1,49 +1,117 @@
 //! ErgoTree
-use crate::ast::Expr;
+use crate::{
+    ast::{Constant, Expr},
+    types::SType,
+};
 use sigma_ser::serializer::SerializationError;
 use sigma_ser::serializer::SigmaSerializable;
 use sigma_ser::vlq_encode;
 use std::io;
+use std::rc::Rc;
+use vlq_encode::{ReadSigmaVlqExt, WriteSigmaVlqExt};
 
 /** The root of ErgoScript IR. Serialized instances of this class are self sufficient and can be passed around.
  */
 #[derive(PartialEq, Debug)]
-pub struct ErgoTree {}
+#[allow(dead_code)]
+pub struct ErgoTree {
+    header: ErgoTreeHeader,
+    constants: Vec<Constant>,
+    root: Rc<Expr>,
+}
+
+#[derive(PartialEq, Debug)]
+struct ErgoTreeHeader(u8);
 
 impl ErgoTree {
-    /// get value out of ErgoTree
-    pub fn to_proposition(&self, _replace_constants: bool) -> Box<Expr> {
-        todo!()
-        // let c = ConstantNode {
-        //     value: Box::new(CSigmaProp {
-        //         sigma_tree: SigmaBoolean::ProveDlog(0),
-        //     }) as Box<dyn SigmaProp>,
-        //     tpe: SType::SSigmaProp,
-        // };
-        // Box::new(c)
+    const DEFAULT_HEADER: ErgoTreeHeader = ErgoTreeHeader(0);
+
+    /// get Expr out of ErgoTree
+    pub fn proposition(&self) -> Rc<Expr> {
+        self.root.clone()
+    }
+
+    /// build ErgoTree from an Expr
+    pub fn from_proposition(expr: Rc<Expr>) -> ErgoTree {
+        match &*expr {
+            Expr::Const(c) if c.tpe == SType::SSigmaProp => ErgoTree {
+                header: ErgoTree::DEFAULT_HEADER,
+                constants: Vec::new(),
+                root: expr.clone(),
+            },
+            _ => panic!("not yet supported"),
+        }
+    }
+}
+
+impl SigmaSerializable for ErgoTreeHeader {
+    fn sigma_serialize<W: WriteSigmaVlqExt>(&self, w: &mut W) -> Result<(), io::Error> {
+        w.put_u8(self.0)?;
+        Ok(())
+    }
+    fn sigma_parse<R: ReadSigmaVlqExt>(r: &mut R) -> Result<Self, SerializationError> {
+        let header = r.get_u8()?;
+        Ok(ErgoTreeHeader(header))
     }
 }
 
 impl SigmaSerializable for ErgoTree {
-    fn sigma_serialize<W: vlq_encode::WriteSigmaVlqExt>(&self, _: W) -> Result<(), io::Error> {
+    fn sigma_serialize<W: WriteSigmaVlqExt>(&self, w: &mut W) -> Result<(), io::Error> {
+        self.header.sigma_serialize(w)?;
+        w.put_usize_as_u32(self.constants.len())?;
+        assert!(
+            self.constants.is_empty(),
+            "separate constants serialization is not yet supported"
+        );
+        self.root.sigma_serialize(w)?;
         Ok(())
     }
-    fn sigma_parse<R: vlq_encode::ReadSigmaVlqExt>(_: R) -> Result<Self, SerializationError> {
-        Ok(ErgoTree {})
+
+    fn sigma_parse<R: ReadSigmaVlqExt>(r: &mut R) -> Result<Self, SerializationError> {
+        let header = ErgoTreeHeader::sigma_parse(r)?;
+        let constants_len = r.get_u32()?;
+        assert!(
+            constants_len == 0,
+            "separate constants serialization is not yet supported"
+        );
+        let constants = Vec::new();
+        let root = Expr::sigma_parse(r)?;
+        Ok(ErgoTree {
+            header,
+            constants,
+            root: Rc::new(root),
+        })
     }
 }
 
 #[cfg(test)]
 mod tests {
+    use super::*;
+    use crate::{ast::ConstantVal, data::SigmaProp};
     use proptest::prelude::*;
     use sigma_ser::test_helpers::*;
-    use sigma_testutil::generator::*;
+
+    impl Arbitrary for ErgoTree {
+        type Parameters = ();
+        type Strategy = BoxedStrategy<Self>;
+
+        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+            (any::<SigmaProp>())
+                .prop_map(|p| {
+                    ErgoTree::from_proposition(Rc::new(Expr::Const(Constant {
+                        tpe: SType::SSigmaProp,
+                        v: ConstantVal::SigmaProp(Box::new(p)),
+                    })))
+                })
+                .boxed()
+        }
+    }
 
     proptest! {
 
         #[test]
-        fn ser_roundtrip(v in any::<ErgoTreeArb>()) {
-            prop_assert_eq![sigma_serialize_roundtrip(&(v.0)), v.0];
+        fn ser_roundtrip(v in any::<ErgoTree>()) {
+            prop_assert_eq![sigma_serialize_roundtrip(&(v)), v];
         }
     }
 }
diff --git a/sigma-tree/src/eval.rs b/sigma-tree/src/eval.rs
index 3a4c569b4..062a8d662 100644
--- a/sigma-tree/src/eval.rs
+++ b/sigma-tree/src/eval.rs
@@ -26,7 +26,7 @@ impl Executor for Interpreter {
     #[allow(unconditional_recursion)]
     fn eval(&mut self, expr: &Expr, env: &Env) -> Result<Value, EvalError> {
         match expr {
-            Expr::Constant { .. } => todo!(), //Ok(EvalResult(*v)),
+            Expr::Const(_) => todo!(), //Ok(EvalResult(*v)),
             Expr::Coll { .. } => todo!(),
             Expr::Tup { .. } => todo!(),
             Expr::PredefFunc(_) => todo!(),
diff --git a/sigma-tree/src/eval/costs.rs b/sigma-tree/src/eval/costs.rs
index 8f296039f..984ccadfc 100644
--- a/sigma-tree/src/eval/costs.rs
+++ b/sigma-tree/src/eval/costs.rs
@@ -7,7 +7,7 @@ pub struct Costs {}
 impl Costs {
     pub fn cost_of(&self, expr: &Expr) -> Cost {
         match expr {
-            Expr::Constant { .. } => todo!(),
+            Expr::Const(_) => todo!(),
             Expr::Coll { .. } => todo!(),
             Expr::Tup { .. } => todo!(),
             Expr::PredefFunc(_) => todo!(),
diff --git a/sigma-tree/src/eval/value.rs b/sigma-tree/src/eval/value.rs
index 25c64f0a7..2a2a8b563 100644
--- a/sigma-tree/src/eval/value.rs
+++ b/sigma-tree/src/eval/value.rs
@@ -1,4 +1,4 @@
-use crate::data::{SigmaBox, SigmaProp};
+use crate::{ast::ErgoBox, data::SigmaBoolean};
 use std::ops::Add;
 
 #[allow(dead_code)]
@@ -10,8 +10,8 @@ pub enum Value {
     Long(i64),
     BigInt,
     GroupElement,
-    SigmaProp(Box<dyn SigmaProp>),
-    Box(Box<dyn SigmaBox>),
+    SigmaProp(Box<SigmaBoolean>),
+    Box(Box<ErgoBox>),
     AvlTree,
     Coll(Vec<Value>),
     Tup(Vec<Value>),
diff --git a/sigma-tree/src/lib.rs b/sigma-tree/src/lib.rs
index f7746aa88..be926c499 100644
--- a/sigma-tree/src/lib.rs
+++ b/sigma-tree/src/lib.rs
@@ -11,10 +11,12 @@
 #![deny(missing_docs)]
 
 mod ast;
+mod constants;
 mod data;
-mod ergo_tree;
+mod ecpoint;
 mod eval;
 mod serialization;
 mod types;
 
-pub use ergo_tree::*;
+pub mod chain;
+pub mod ergo_tree;
diff --git a/sigma-tree/src/serialization.rs b/sigma-tree/src/serialization.rs
index 884680f0b..66273da13 100644
--- a/sigma-tree/src/serialization.rs
+++ b/sigma-tree/src/serialization.rs
@@ -1,62 +1,10 @@
 //! Serializers
-use crate::ast::{CollMethods, Expr};
-use constant::ConstantSerializer;
-use fold::FoldSerializer;
-use op_code::OpCode;
-use sigma_ser::{
-    serializer::{SerializationError, SigmaSerializable},
-    vlq_encode::{ReadSigmaVlqExt, WriteSigmaVlqExt},
-};
-use std::io;
 
 mod constant;
 mod data;
+mod expr;
 mod fold;
-pub mod op_code;
-mod types;
-
-impl SigmaSerializable for Expr {
-    fn sigma_serialize<W: WriteSigmaVlqExt>(&self, mut w: W) -> Result<(), io::Error> {
-        match self {
-            Expr::Constant { .. } => ConstantSerializer::sigma_serialize(self, w),
-            expr => {
-                let op_code = self.op_code();
-                op_code.sigma_serialize(&mut w)?;
-                ExprSerializers::sigma_serialize(expr, &mut w)
-            }
-        }
-    }
-
-    fn sigma_parse<R: ReadSigmaVlqExt>(mut r: R) -> Result<Self, SerializationError> {
-        let first_byte = r.peek_u8()?;
-        if first_byte <= OpCode::LAST_CONSTANT_CODE.value() {
-            ConstantSerializer::sigma_parse(&mut r)
-        } else {
-            let op_code = OpCode::sigma_parse(&mut r)?;
-            ExprSerializers::sigma_parse(op_code, &mut r)
-        }
-    }
-}
+mod sigmaboolean;
 
-pub struct ExprSerializers {}
-
-impl ExprSerializers {
-    pub fn sigma_serialize<W: WriteSigmaVlqExt>(expr: &Expr, w: W) -> Result<(), io::Error> {
-        match expr {
-            Expr::CollM(cm) => match cm {
-                CollMethods::Fold { .. } => FoldSerializer::sigma_serialize(expr, w),
-            },
-            _ => panic!(format!("don't know how to serialize {}", expr)),
-        }
-    }
-
-    pub fn sigma_parse<R: ReadSigmaVlqExt>(
-        op_code: OpCode,
-        r: R,
-    ) -> Result<Expr, SerializationError> {
-        match op_code {
-            FoldSerializer::OP_CODE => FoldSerializer::sigma_parse(r),
-            o => Err(SerializationError::NotImplementedOpCode(o.value())),
-        }
-    }
-}
+pub mod op_code;
+pub mod types;
diff --git a/sigma-tree/src/serialization/constant.rs b/sigma-tree/src/serialization/constant.rs
index 3a1743df2..ba8daa6dc 100644
--- a/sigma-tree/src/serialization/constant.rs
+++ b/sigma-tree/src/serialization/constant.rs
@@ -1,28 +1,21 @@
 use super::data::DataSerializer;
-use crate::{ast::Expr, types::SType};
+use crate::{ast::Constant, types::SType};
 use sigma_ser::{
     serializer::{SerializationError, SigmaSerializable},
     vlq_encode::{ReadSigmaVlqExt, WriteSigmaVlqExt},
 };
 use std::io;
 
-pub struct ConstantSerializer {}
-
-impl ConstantSerializer {
-    pub fn sigma_serialize<W: WriteSigmaVlqExt>(expr: &Expr, mut w: W) -> Result<(), io::Error> {
-        match expr {
-            Expr::Constant { tpe, v } => {
-                tpe.sigma_serialize(&mut w)?;
-                DataSerializer::sigma_serialize(v, w)
-            }
-            _ => panic!("constant expected"),
-        }
+impl SigmaSerializable for Constant {
+    fn sigma_serialize<W: WriteSigmaVlqExt>(&self, w: &mut W) -> Result<(), io::Error> {
+        self.tpe.sigma_serialize(w)?;
+        DataSerializer::sigma_serialize(&self.v, w)
     }
 
-    pub fn sigma_parse<R: ReadSigmaVlqExt>(mut r: R) -> Result<Expr, SerializationError> {
+    fn sigma_parse<R: ReadSigmaVlqExt>(r: &mut R) -> Result<Self, SerializationError> {
         // for reference see http://github.com/ScorexFoundation/sigmastate-interpreter/blob/25251c1313b0131835f92099f02cef8a5d932b5e/sigmastate/src/main/scala/sigmastate/serialization/DataSerializer.scala#L84-L84
-        let tpe = SType::sigma_parse(&mut r)?;
-        let v = DataSerializer::sigma_parse(&tpe, &mut r)?;
-        Ok(Expr::Constant { tpe, v })
+        let tpe = SType::sigma_parse(r)?;
+        let v = DataSerializer::sigma_parse(&tpe, r)?;
+        Ok(Constant { tpe, v })
     }
 }
diff --git a/sigma-tree/src/serialization/data.rs b/sigma-tree/src/serialization/data.rs
index 830695e3f..8464ccd44 100644
--- a/sigma-tree/src/serialization/data.rs
+++ b/sigma-tree/src/serialization/data.rs
@@ -1,14 +1,25 @@
-use crate::{ast::CollPrim, ast::Const, ast::Const::*, types::SType, types::SType::*};
+use crate::{
+    ast::CollPrim,
+    ast::ConstantVal,
+    ast::ConstantVal::*,
+    data::{self, SigmaBoolean},
+    types::SType,
+    types::SType::*,
+};
 use sigma_ser::{
-    serializer::SerializationError,
+    serializer::{SerializationError, SigmaSerializable},
     vlq_encode::{ReadSigmaVlqExt, WriteSigmaVlqExt},
 };
 use std::io;
 
 pub struct DataSerializer {}
 
+// TODO: convert to SigmaSerializable impl for ConstantVal
 impl DataSerializer {
-    pub fn sigma_serialize<W: WriteSigmaVlqExt>(c: &Const, mut w: W) -> Result<(), io::Error> {
+    pub fn sigma_serialize<W: WriteSigmaVlqExt>(
+        c: &ConstantVal,
+        w: &mut W,
+    ) -> Result<(), io::Error> {
         // for reference see http://github.com/ScorexFoundation/sigmastate-interpreter/blob/25251c1313b0131835f92099f02cef8a5d932b5e/sigmastate/src/main/scala/sigmastate/serialization/DataSerializer.scala#L26-L26
         match c {
             Boolean(_) => todo!(),
@@ -18,10 +29,10 @@ impl DataSerializer {
             Long(_) => todo!(),
             BigInt => todo!(),
             GroupElement => todo!(),
-            SigmaProp(_) => todo!(),
+            SigmaProp(s) => s.value().sigma_serialize(w),
             CBox(_) => todo!(),
             AvlTree => todo!(),
-            Const::CollPrim(_) => todo!(),
+            ConstantVal::CollPrim(_) => todo!(),
             Coll(_) => todo!(),
             Tup(_) => todo!(),
         }
@@ -29,12 +40,15 @@ impl DataSerializer {
 
     pub fn sigma_parse<R: ReadSigmaVlqExt>(
         tpe: &SType,
-        mut r: R,
-    ) -> Result<Const, SerializationError> {
+        r: &mut R,
+    ) -> Result<ConstantVal, SerializationError> {
         // for reference see http://github.com/ScorexFoundation/sigmastate-interpreter/blob/25251c1313b0131835f92099f02cef8a5d932b5e/sigmastate/src/main/scala/sigmastate/serialization/DataSerializer.scala#L84-L84
         let c = match tpe {
             SAny => todo!(),
             SByte => Byte(r.get_i8()?),
+            SSigmaProp => SigmaProp(Box::new(data::SigmaProp::new(SigmaBoolean::sigma_parse(
+                r,
+            )?))),
             SColl(elem_type) => {
                 let len = r.get_u16()? as usize;
                 if **elem_type == SByte {
@@ -50,7 +64,7 @@ impl DataSerializer {
             STup(types) => {
                 let mut items = Vec::new();
                 types.iter().try_for_each(|tpe| {
-                    DataSerializer::sigma_parse(tpe, &mut r).map(|v| items.push(v))
+                    DataSerializer::sigma_parse(tpe, r).map(|v| items.push(v))
                 })?;
                 Tup(items)
             }
diff --git a/sigma-tree/src/serialization/expr.rs b/sigma-tree/src/serialization/expr.rs
index 4ff104589..24eab448c 100644
--- a/sigma-tree/src/serialization/expr.rs
+++ b/sigma-tree/src/serialization/expr.rs
@@ -1,29 +1,46 @@
-use crate::ast::Expr;
+use super::{fold::FoldSerializer, op_code::OpCode};
+use crate::ast::{CollMethods, Constant, Expr};
 use sigma_ser::{
     serializer::{SerializationError, SigmaSerializable},
-    vlq_encode::{ReadSigmaVlqExt, WriteSigmaVlqExt},
+    vlq_encode,
 };
 use std::io;
 
 impl SigmaSerializable for Expr {
-    fn sigma_serialize<W: WriteSigmaVlqExt>(&self, mut w: W) -> Result<(), io::Error> {
+    fn sigma_serialize<W: vlq_encode::WriteSigmaVlqExt>(&self, w: &mut W) -> Result<(), io::Error> {
         match self {
-            c @ Constant { .. } => ConstantSerializer::sigma_serialize(self, w),
+            Expr::Const(c) => c.sigma_serialize(w),
             expr => {
                 let op_code = self.op_code();
-                w.put_u8(op_code.0)?;
-                ExprSerializers::sigma_serialize(self, w)
+                op_code.sigma_serialize(w)?;
+                match expr {
+                    Expr::CollM(cm) => match cm {
+                        CollMethods::Fold { .. } => FoldSerializer::sigma_serialize(expr, w),
+                    },
+                    _ => panic!(format!("don't know how to serialize {}", expr)),
+                }
             }
         }
     }
 
-    fn sigma_parse<R: ReadSigmaVlqExt>(mut r: R) -> Result<Self, SerializationError> {
-        let first_byte = r.peek_u8()?;
-        if first_byte <= LAST_CONSTANT_CODE {
-            ConstantSerializer::sigma_parse(&mut r)
+    fn sigma_parse<R: vlq_encode::ReadSigmaVlqExt>(r: &mut R) -> Result<Self, SerializationError> {
+        let first_byte = match r.peek_u8() {
+            Ok(b) => Ok(b),
+            Err(_) => {
+                let res = r.get_u8(); // get(consume) the error
+                assert!(res.is_err());
+                res
+            }
+        }?;
+        if first_byte <= OpCode::LAST_CONSTANT_CODE.value() {
+            let constant = Constant::sigma_parse(r)?;
+            Ok(Expr::Const(constant))
         } else {
-            let op_code = r.get_u8()?;
-            ExprSerializers::sigma_parse(&OpCode(op_code), r)
+            let op_code = OpCode::sigma_parse(r)?;
+            match op_code {
+                FoldSerializer::OP_CODE => FoldSerializer::sigma_parse(r),
+                o => Err(SerializationError::NotImplementedOpCode(o.value())),
+            }
         }
     }
 }
diff --git a/sigma-tree/src/serialization/fold.rs b/sigma-tree/src/serialization/fold.rs
index b17d503dd..fe492c0e6 100644
--- a/sigma-tree/src/serialization/fold.rs
+++ b/sigma-tree/src/serialization/fold.rs
@@ -11,26 +11,26 @@ pub struct FoldSerializer {}
 impl FoldSerializer {
     pub const OP_CODE: OpCode = OpCode::FOLD;
 
-    pub fn sigma_serialize<W: WriteSigmaVlqExt>(expr: &Expr, mut w: W) -> Result<(), io::Error> {
+    pub fn sigma_serialize<W: WriteSigmaVlqExt>(expr: &Expr, w: &mut W) -> Result<(), io::Error> {
         match expr {
             Expr::CollM(CollMethods::Fold {
                 input,
                 zero,
                 fold_op,
             }) => {
-                input.sigma_serialize(&mut w)?;
-                zero.sigma_serialize(&mut w)?;
-                fold_op.sigma_serialize(&mut w)?;
+                input.sigma_serialize(w)?;
+                zero.sigma_serialize(w)?;
+                fold_op.sigma_serialize(w)?;
                 Ok(())
             }
             _ => panic!("expected Fold"),
         }
     }
 
-    pub fn sigma_parse<R: ReadSigmaVlqExt>(mut r: R) -> Result<Expr, SerializationError> {
-        let input = Expr::sigma_parse(&mut r)?;
-        let zero = Expr::sigma_parse(&mut r)?;
-        let fold_op = Expr::sigma_parse(&mut r)?;
+    pub fn sigma_parse<R: ReadSigmaVlqExt>(r: &mut R) -> Result<Expr, SerializationError> {
+        let input = Expr::sigma_parse(r)?;
+        let zero = Expr::sigma_parse(r)?;
+        let fold_op = Expr::sigma_parse(r)?;
         Ok(Expr::CollM(CollMethods::Fold {
             input: Box::new(input),
             zero: Box::new(zero),
diff --git a/sigma-tree/src/serialization/op_code.rs b/sigma-tree/src/serialization/op_code.rs
index eb4402de3..2838b7005 100644
--- a/sigma-tree/src/serialization/op_code.rs
+++ b/sigma-tree/src/serialization/op_code.rs
@@ -13,6 +13,7 @@ impl OpCode {
     pub const LAST_CONSTANT_CODE: OpCode = OpCode(Self::LAST_DATA_TYPE.value() + 1);
 
     pub const FOLD: OpCode = Self::new_op_code(64);
+    pub const PROVE_DLOG: OpCode = Self::new_op_code(93);
 
     const fn new_op_code(shift: u8) -> OpCode {
         OpCode(Self::LAST_CONSTANT_CODE.value() + shift)
@@ -28,11 +29,11 @@ impl OpCode {
 }
 
 impl SigmaSerializable for OpCode {
-    fn sigma_serialize<W: WriteSigmaVlqExt>(&self, mut w: W) -> Result<(), io::Error> {
+    fn sigma_serialize<W: WriteSigmaVlqExt>(&self, w: &mut W) -> Result<(), io::Error> {
         w.put_u8(self.0)?;
         Ok(())
     }
-    fn sigma_parse<R: vlq_encode::ReadSigmaVlqExt>(mut r: R) -> Result<Self, SerializationError> {
+    fn sigma_parse<R: vlq_encode::ReadSigmaVlqExt>(r: &mut R) -> Result<Self, SerializationError> {
         let code = r.get_u8()?;
         Ok(OpCode::parse(code))
     }
diff --git a/sigma-tree/src/serialization/sigmaboolean.rs b/sigma-tree/src/serialization/sigmaboolean.rs
new file mode 100644
index 000000000..5a3f4154b
--- /dev/null
+++ b/sigma-tree/src/serialization/sigmaboolean.rs
@@ -0,0 +1,29 @@
+use super::op_code::OpCode;
+use crate::{data::SigmaBoolean, ecpoint::EcPoint};
+use sigma_ser::{
+    serializer::{SerializationError, SigmaSerializable},
+    vlq_encode,
+};
+use std::io;
+
+impl SigmaSerializable for SigmaBoolean {
+    fn sigma_serialize<W: vlq_encode::WriteSigmaVlqExt>(&self, w: &mut W) -> Result<(), io::Error> {
+        self.op_code().sigma_serialize(w)?;
+        match self {
+            SigmaBoolean::ProveDHTuple { .. } => todo!(),
+            SigmaBoolean::ProveDlog(v) => v.sigma_serialize(w),
+            SigmaBoolean::CAND(_) => todo!(),
+        }
+    }
+
+    fn sigma_parse<R: vlq_encode::ReadSigmaVlqExt>(r: &mut R) -> Result<Self, SerializationError> {
+        let op_code = OpCode::sigma_parse(r)?;
+        match op_code {
+            OpCode::PROVE_DLOG => {
+                let p = EcPoint::sigma_parse(r)?;
+                Ok(SigmaBoolean::ProveDlog(Box::new(p)))
+            }
+            _ => todo!(),
+        }
+    }
+}
diff --git a/sigma-tree/src/serialization/types.rs b/sigma-tree/src/serialization/types.rs
index 4018f55dd..6aa057b27 100644
--- a/sigma-tree/src/serialization/types.rs
+++ b/sigma-tree/src/serialization/types.rs
@@ -6,19 +6,65 @@ use sigma_ser::{
 use std::io;
 use vlq_encode::{ReadSigmaVlqExt, WriteSigmaVlqExt};
 
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct TypeCode(u8);
+
+impl TypeCode {
+    /// Type code of the last valid prim type so that (1 to LastPrimTypeCode) is a range of valid codes.
+    pub const LAST_PRIM_TYPECODE: u8 = 8;
+
+    /// Upper limit of the interval of valid type codes for primitive types
+    pub const MAX_PRIM_TYPECODE: u8 = 11;
+    pub const PRIM_RANGE: u8 = (TypeCode::MAX_PRIM_TYPECODE + 1);
+
+    pub const SBOOLEAN: TypeCode = Self::new_type_code(1);
+    pub const SSIGMAPROP: TypeCode = Self::new_type_code(8);
+
+    const fn new_type_code(c: u8) -> TypeCode {
+        TypeCode(c)
+    }
+
+    pub const fn value(&self) -> u8 {
+        self.0
+    }
+}
+
+impl SigmaSerializable for TypeCode {
+    fn sigma_serialize<W: WriteSigmaVlqExt>(&self, w: &mut W) -> Result<(), io::Error> {
+        w.put_u8(self.value())
+    }
+
+    fn sigma_parse<R: ReadSigmaVlqExt>(r: &mut R) -> Result<Self, SerializationError> {
+        let b = r.get_u8()?;
+        match b {
+            0 => Err(SerializationError::InvalidTypePrefix),
+            _ => Ok(Self(b)),
+        }
+    }
+}
+
+fn get_embeddable_type(code: TypeCode) -> Result<SType, SerializationError> {
+    Ok(match code {
+        TypeCode::SBOOLEAN => SType::SBoolean,
+        TypeCode::SSIGMAPROP => SType::SSigmaProp,
+        _ => todo!(),
+    })
+}
+
 impl SigmaSerializable for SType {
-    fn sigma_serialize<W: WriteSigmaVlqExt>(&self, _: W) -> Result<(), io::Error> {
+    fn sigma_serialize<W: WriteSigmaVlqExt>(&self, w: &mut W) -> Result<(), io::Error> {
         // for reference see http://github.com/ScorexFoundation/sigmastate-interpreter/blob/25251c1313b0131835f92099f02cef8a5d932b5e/sigmastate/src/main/scala/sigmastate/serialization/TypeSerializer.scala#L25-L25
-        todo!()
+        self.type_code().sigma_serialize(w)
     }
-    fn sigma_parse<R: ReadSigmaVlqExt>(mut r: R) -> Result<Self, SerializationError> {
+
+    fn sigma_parse<R: ReadSigmaVlqExt>(r: &mut R) -> Result<Self, SerializationError> {
         // for reference see http://github.com/ScorexFoundation/sigmastate-interpreter/blob/25251c1313b0131835f92099f02cef8a5d932b5e/sigmastate/src/main/scala/sigmastate/serialization/TypeSerializer.scala#L118-L118
-        let c = r.get_u8()?;
-        if c == 0 {
-            Err(SerializationError::InvalidTypePrefix)
-        } else {
-            todo!();
-            // Ok(SType::SAny)
-        }
+        let type_code = TypeCode::sigma_parse(r)?;
+        let constr_id = type_code.value() / TypeCode::PRIM_RANGE;
+        let tpe = match constr_id {
+            0 => get_embeddable_type(type_code)?,
+            _ => todo!(),
+        };
+        Ok(tpe)
     }
 }
diff --git a/sigma-tree/src/types.rs b/sigma-tree/src/types.rs
index 70993c669..9bd2dc18f 100644
--- a/sigma-tree/src/types.rs
+++ b/sigma-tree/src/types.rs
@@ -1,13 +1,16 @@
 //! Sigma types
 
-#[derive(Clone, Debug)]
-pub struct TypeCode(u8);
+use super::serialization::types::TypeCode;
+
+#[derive(PartialEq, Eq, Debug)]
 pub struct MethodId(u8);
+#[derive(PartialEq, Eq, Debug)]
 pub struct TypeId(u8);
 
-#[derive(PartialEq, Eq)]
+#[derive(PartialEq, Eq, Debug)]
 pub enum SType {
     SAny,
+    SBoolean,
     SByte,
     SShort,
     SInt,
@@ -25,7 +28,23 @@ pub enum SType {
 
 impl SType {
     pub fn type_code(&self) -> TypeCode {
-        todo!()
+        match self {
+            SType::SAny => todo!(),
+            SType::SBoolean => todo!(),
+            SType::SByte => todo!(),
+            SType::SShort => todo!(),
+            SType::SInt => todo!(),
+            SType::SLong => todo!(),
+            SType::SBigInt => todo!(),
+            SType::SGroupElement => todo!(),
+            SType::SSigmaProp => TypeCode::SSIGMAPROP,
+            SType::SBox => todo!(),
+            SType::SAvlTree => todo!(),
+            SType::SOption(_) => todo!(),
+            SType::SColl(_) => todo!(),
+            SType::STup(_) => todo!(),
+            SType::SFunc(_) => todo!(),
+        }
     }
 
     pub fn type_companion(&self) -> Option<STypeCompanion> {
@@ -33,31 +52,33 @@ impl SType {
     }
 }
 
-#[derive(PartialEq, Eq)]
+#[derive(PartialEq, Eq, Debug)]
 pub struct STypeVar {
     name: String,
 }
 
-#[derive(PartialEq, Eq)]
+#[derive(PartialEq, Eq, Debug)]
 pub struct STypeParam {
     ident: STypeVar,
     upper_bound: Option<SType>,
     lower_bound: Option<SType>,
 }
 
-#[derive(PartialEq, Eq)]
+#[derive(PartialEq, Eq, Debug)]
 pub struct SFunc {
     t_dom: Vec<SType>,
     t_range: SType,
     tpe_params: Vec<STypeParam>,
 }
 
+#[derive(PartialEq, Eq, Debug)]
 pub struct STypeCompanion {
     pub type_id: TypeId,
     pub type_name: String,
     pub methods: Vec<SMethod>,
 }
 
+#[derive(PartialEq, Eq, Debug)]
 pub struct SMethod {
     pub obj_type: Box<STypeCompanion>,
     pub name: String,


Test patch:
diff --git a/sigma-ser/src/test_helpers.rs b/sigma-ser/src/test_helpers.rs
index f906bcb8f..5db0b7cfd 100644
--- a/sigma-ser/src/test_helpers.rs
+++ b/sigma-ser/src/test_helpers.rs
@@ -1,7 +1,12 @@
 //! Helper function for testing purposes
+use crate::peekable_reader::PeekableReader;
+use std::io::Cursor;
+
 /// serialization roundtrip
 pub fn sigma_serialize_roundtrip<T: crate::serializer::SigmaSerializable>(v: &T) -> T {
     let mut data = Vec::new();
     v.sigma_serialize(&mut data).expect("serialization failed");
-    T::sigma_parse(&data[..]).expect("parse failed")
+    let cursor = Cursor::new(&mut data[..]);
+    let mut reader = PeekableReader::new(cursor);
+    T::sigma_parse(&mut reader).expect("parse failed")
 }
diff --git a/sigma-testutil/src/generator.rs b/sigma-testutil/src/generator.rs
index f6e843cfd..7ece9e1a7 100644
--- a/sigma-testutil/src/generator.rs
+++ b/sigma-testutil/src/generator.rs
@@ -1,16 +1,4 @@
-use proptest::{arbitrary::Arbitrary, prelude::*};
-use sigma_tree::ErgoTree;
+use sigma_tree::ergo_tree::ErgoTree;
 
 #[derive(Debug)]
 pub struct ErgoTreeArb(pub ErgoTree);
-
-impl Arbitrary for ErgoTreeArb {
-    type Parameters = ();
-
-    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
-        (any::<u32>(),)
-            .prop_map(|_| Self { 0: ErgoTree {} })
-            .boxed()
-    }
-    type Strategy = BoxedStrategy<Self>;
-}
diff --git a/sigma-tree/proptest-regressions/chain/ergo_box.txt b/sigma-tree/proptest-regressions/chain/ergo_box.txt
new file mode 100644
index 000000000..9e215b4f6
--- /dev/null
+++ b/sigma-tree/proptest-regressions/chain/ergo_box.txt
@@ -0,0 +1,7 @@
+# Seeds for failure cases proptest has generated in the past. It is
+# automatically read and these particular cases re-run before any
+# novel cases are generated.
+#
+# It is recommended to check this file in to source control so that
+# everyone who runs the test benefits from these saved cases.
+cc b597aa9726ae06f03acce6d22d35c0ebe1f6574f2e88eb2a2db75d114a02ff65 # shrinks to v = ErgoBoxCandidate { value: 0, ergo_tree: ErgoTree { header: ErgoTreeHeader(0), constants: [], root: Const(Constant { tpe: SSigmaProp, v: SigmaProp(SigmaProp(ProveDlog(EcPointType))) }) }, tokens: [], additional_registers: {}, creation_height: 0 }
diff --git a/sigma-tree/proptest-regressions/chain/transaction.txt b/sigma-tree/proptest-regressions/chain/transaction.txt
new file mode 100644
index 000000000..bec2c69db
--- /dev/null
+++ b/sigma-tree/proptest-regressions/chain/transaction.txt
@@ -0,0 +1,7 @@
+# Seeds for failure cases proptest has generated in the past. It is
+# automatically read and these particular cases re-run before any
+# novel cases are generated.
+#
+# It is recommended to check this file in to source control so that
+# everyone who runs the test benefits from these saved cases.
+cc e9c483c8c8b81aa908794575e1de2bbb50603e88a71ef3941937379adb3b9a01 # shrinks to v = Transaction { inputs: [Input { box_id: BoxId([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), spending_proof: ProverResult { proof: [], extension: ContextExtension { values: {} } } }], data_inputs: [], outputs: [ErgoBoxCandidate { value: 0, ergo_tree: ErgoTree { header: ErgoTreeHeader(0), constants: [], root: Const(Constant { tpe: SSigmaProp, v: SigmaProp(SigmaProp(ProveDlog(EcPointType))) }) }, tokens: [], additional_registers: {}, creation_height: 0 }] }
diff --git a/sigma-tree/proptest-regressions/ergo_tree.txt b/sigma-tree/proptest-regressions/ergo_tree.txt
new file mode 100644
index 000000000..28b383ebb
--- /dev/null
+++ b/sigma-tree/proptest-regressions/ergo_tree.txt
@@ -0,0 +1,7 @@
+# Seeds for failure cases proptest has generated in the past. It is
+# automatically read and these particular cases re-run before any
+# novel cases are generated.
+#
+# It is recommended to check this file in to source control so that
+# everyone who runs the test benefits from these saved cases.
+cc ce0ac914b3b393b05617fed50f6a4c13398ae5fb4893185cdb4d3b410c2fe4c1 # shrinks to v = ErgoTree { header: ErgoTreeHeader(0), constants: [], root: Const(Constant { tpe: SSigmaProp, v: SigmaProp(SigmaProp(ProveDlog(EcPointType))) }) }