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

Issue:
`Contex.headers` and `Header` type
- `Context.headers` method in `scontext` module;
- `SType::SHeader` type;
- `sheader` module with Header properties (see `scontext` as example);


Discussion / hints:
I will work on it

Fix patch:
diff --git a/bindings/ergo-lib-wasm/src/block_header.rs b/bindings/ergo-lib-wasm/src/block_header.rs
index 2f51e99ac..d8274221a 100644
--- a/bindings/ergo-lib-wasm/src/block_header.rs
+++ b/bindings/ergo-lib-wasm/src/block_header.rs
@@ -1,5 +1,7 @@
 //! Block header
-use ergo_lib::chain;
+
+use ergo_lib::chain::HeaderJsonHelper;
+use ergo_lib::ergotree_ir::chain::header::Header;
 use wasm_bindgen::prelude::*;
 
 extern crate derive_more;
@@ -10,13 +12,16 @@ use crate::error_conversion::to_js;
 /// Block header
 #[wasm_bindgen]
 #[derive(PartialEq, Eq, Debug, Clone, From, Into)]
-pub struct BlockHeader(ergo_lib::chain::block_header::BlockHeader);
+pub struct BlockHeader(Header);
 
 #[wasm_bindgen]
 impl BlockHeader {
     /// Parse from JSON (Node API)
     pub fn from_json(json: &str) -> Result<BlockHeader, JsValue> {
-        serde_json::from_str(json).map(Self).map_err(to_js)
+        serde_json::from_str(json)
+            .map(|HeaderJsonHelper(header)| header)
+            .map(Self)
+            .map_err(to_js)
     }
 }
 
@@ -33,13 +38,14 @@ impl BlockHeaders {
         json_vals
             .iter()
             .try_fold(vec![], |mut acc, jb| {
-                let b: chain::block_header::BlockHeader = if jb.is_string() {
+                let b: Header = if jb.is_string() {
                     let jb_str = jb
                         .as_string()
                         .ok_or(JsValue::from_str("Expected BlockHeader JSON as string"))?;
-                    serde_json::from_str(jb_str.as_str())
+                    serde_json::from_str(jb_str.as_str()).map(|HeaderJsonHelper(header)| header)
                 } else {
-                    jb.into_serde::<chain::block_header::BlockHeader>()
+                    jb.into_serde::<HeaderJsonHelper>()
+                        .map(|HeaderJsonHelper(header)| header)
                 }
                 .map_err(|e| {
                     JsValue::from_str(&format!(
@@ -75,16 +81,14 @@ impl BlockHeaders {
     }
 }
 
-impl From<Vec<chain::block_header::BlockHeader>> for BlockHeaders {
-    fn from(bs: Vec<chain::block_header::BlockHeader>) -> Self {
+impl From<Vec<Header>> for BlockHeaders {
+    fn from(bs: Vec<Header>) -> Self {
         BlockHeaders(bs.into_iter().map(BlockHeader::from).collect())
     }
 }
 
-impl From<BlockHeaders> for Vec<chain::block_header::BlockHeader> {
+impl From<BlockHeaders> for Vec<Header> {
     fn from(bs: BlockHeaders) -> Self {
-        bs.0.into_iter()
-            .map(chain::block_header::BlockHeader::from)
-            .collect()
+        bs.0.into_iter().map(Header::from).collect()
     }
 }
diff --git a/bindings/ergo-lib-wasm/src/ergo_state_ctx.rs b/bindings/ergo-lib-wasm/src/ergo_state_ctx.rs
index 9768f86d5..77511b5fb 100644
--- a/bindings/ergo-lib-wasm/src/ergo_state_ctx.rs
+++ b/bindings/ergo-lib-wasm/src/ergo_state_ctx.rs
@@ -17,10 +17,9 @@ impl ErgoStateContext {
     /// Create new context from pre-header
     #[wasm_bindgen(constructor)]
     pub fn new(pre_header: PreHeader) -> Self {
-        chain::ergo_state_context::ErgoStateContext {
-            pre_header: pre_header.into(),
-        }
-        .into()
+        let mut ergo_state_context = chain::ergo_state_context::ErgoStateContext::dummy();
+        ergo_state_context.pre_header = pre_header.into();
+        ergo_state_context.into()
     }
 
     /// empty (dummy) context (for signing P2PK tx only)
diff --git a/bindings/ergo-lib-wasm/src/header.rs b/bindings/ergo-lib-wasm/src/header.rs
index a19c218a6..c81e6799f 100644
--- a/bindings/ergo-lib-wasm/src/header.rs
+++ b/bindings/ergo-lib-wasm/src/header.rs
@@ -16,7 +16,7 @@ pub struct PreHeader(ergo_lib::ergotree_ir::mir::header::PreHeader);
 impl PreHeader {
     /// Create using data from block header
     pub fn from_block_header(block_header: BlockHeader) -> Self {
-        let bh: ergo_lib::chain::block_header::BlockHeader = block_header.into();
+        let bh: ergo_lib::ergotree_ir::chain::header::Header = block_header.into();
         let ph: ergo_lib::ergotree_ir::mir::header::PreHeader = bh.into();
         ph.into()
     }
diff --git a/bindings/ergo-lib-wasm/src/token.rs b/bindings/ergo-lib-wasm/src/token.rs
index 3cdec7769..823bc93e0 100644
--- a/bindings/ergo-lib-wasm/src/token.rs
+++ b/bindings/ergo-lib-wasm/src/token.rs
@@ -4,7 +4,7 @@ use std::convert::TryFrom;
 
 use ergo_lib::chain;
 use ergo_lib::chain::Base16DecodedBytes;
-use ergo_lib::chain::Digest32;
+use ergo_lib::ergotree_ir::chain::digest::Digest32;
 use js_sys::Uint8Array;
 use wasm_bindgen::prelude::*;
 
diff --git a/bindings/ergo-lib-wasm/src/transaction.rs b/bindings/ergo-lib-wasm/src/transaction.rs
index b103a97ea..364dbec3e 100644
--- a/bindings/ergo-lib-wasm/src/transaction.rs
+++ b/bindings/ergo-lib-wasm/src/transaction.rs
@@ -7,6 +7,7 @@ use crate::error_conversion::to_js;
 use crate::input::{Inputs, UnsignedInputs};
 use ergo_lib::chain;
 use ergo_lib::chain::transaction::distinct_token_ids;
+use ergo_lib::ergotree_ir::chain::digest::Digest32;
 use js_sys::Uint8Array;
 use std::convert::TryFrom;
 use std::convert::TryInto;
@@ -44,7 +45,7 @@ impl TxId {
             .map_err(|_e| {
                 JsValue::from_str(&format!(
                     "Expected a Vec of length {} but it was {}",
-                    chain::Digest32::SIZE,
+                    Digest32::SIZE,
                     s.len()
                 ))
             })
diff --git a/ergo-lib/Cargo.toml b/ergo-lib/Cargo.toml
index ce643ea1f..a2192b57a 100644
--- a/ergo-lib/Cargo.toml
+++ b/ergo-lib/Cargo.toml
@@ -26,7 +26,8 @@ serde_json = "1.0"
 thiserror = "1"
 derive_more = "0.99"
 # bounded-vec = { git = "https://github.com/ergoplatform/bounded-vec", rev="f684d78" }
-bounded-vec = { version = "^0.4.0" } 
+bounded-vec = { version = "^0.4.0" }
+num-bigint = "0.4.0"
 
 [features]
 default = ["json", "compiler"]
diff --git a/ergo-lib/src/chain.rs b/ergo-lib/src/chain.rs
index 185fd8af3..d0b9d1cb0 100644
--- a/ergo-lib/src/chain.rs
+++ b/ergo-lib/src/chain.rs
@@ -1,5 +1,8 @@
 //! Ergo chain types
 
+pub use block_header::HeaderJsonHelper;
+
+mod block_header;
 #[cfg(feature = "json")]
 mod json;
 
@@ -9,7 +12,6 @@ mod digest32;
 pub use base16_bytes::*;
 pub use digest32::*;
 
-pub mod block_header;
 pub mod contract;
 pub mod ergo_box;
 pub mod ergo_state_context;
diff --git a/ergo-lib/src/chain/block_header.rs b/ergo-lib/src/chain/block_header.rs
index 5f442790f..a1df1af13 100644
--- a/ergo-lib/src/chain/block_header.rs
+++ b/ergo-lib/src/chain/block_header.rs
@@ -1,102 +1,136 @@
 //! Block header
+use num_bigint::BigInt;
 
-use std::convert::TryFrom;
-use std::convert::TryInto;
-
-use ergotree_ir::mir::header::PreHeader;
+use ergotree_ir::chain::{
+    block_id::BlockId,
+    digest::{ADDigest, Digest32},
+    header::Header,
+    votes::Votes,
+};
 use ergotree_ir::sigma_protocol::dlog_group;
 #[cfg(feature = "json")]
 use serde::{Deserialize, Serialize};
 
-use super::Base16DecodedBytes;
-use super::Base16EncodedBytes;
-use super::Digest32;
-use thiserror::Error;
-
-/// Block id
+/// New-type wrapper for deserializing the remote `Header` type.
 #[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
-#[derive(PartialEq, Eq, Debug, Clone)]
-pub struct BlockId(Digest32);
+#[derive(Debug, Clone)]
+pub struct HeaderJsonHelper(
+    #[cfg_attr(feature = "json", serde(with = "BlockHeaderRef"))] pub Header,
+);
 
-/// Votes for changing system parameters
+/// Block header reference to `Header` type in ergotree-ir.
+///
+/// We do not implement serde traits in ergotree-ir, but they are requested
+/// by crate users. So we split definition into the main one in ergotree-ir and
+/// the one which implements `Serialize` and `Deserialize` (which is here).
 #[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
-#[cfg_attr(
-    feature = "json",
-    serde(
-        into = "Base16EncodedBytes",
-        try_from = "crate::chain::json::block_header::VotesEncodingVariants"
-    )
-)]
+#[cfg_attr(feature = "json", serde(remote = "Header"))]
 #[derive(PartialEq, Eq, Debug, Clone)]
-pub struct Votes(pub [u8; 3]);
-
-/// Votes errors
-#[derive(Error, Debug)]
-pub enum VotesError {
-    /// Invalid byte array size
-    #[error("Votes: Invalid byte array size ({0})")]
-    InvalidSize(#[from] std::array::TryFromSliceError),
+pub(in crate::chain) struct BlockHeaderRef {
+    /// Block version, to be increased on every soft and hardfork
+    version: u8,
+    /// Bytes representation of ModifierId of this Header
+    #[cfg_attr(feature = "json", serde(skip_serializing, skip_deserializing))]
+    id: BlockId,
+    /// Id of a parent block
+    #[cfg_attr(
+        feature = "json",
+        serde(rename = "parentId", with = "block_id::BlockIdRef")
+    )]
+    parent_id: BlockId,
+    /// Hash of ADProofs for transactions in a block
+    #[cfg_attr(feature = "json", serde(skip_serializing, skip_deserializing))]
+    ad_proofs_root: Digest32,
+    /// AvlTree of a state after block application
+    #[cfg_attr(feature = "json", serde(skip_serializing, skip_deserializing))]
+    state_root: ADDigest,
+    /// Root hash (for a Merkle tree) of transactions in a block.
+    #[cfg_attr(feature = "json", serde(skip_serializing, skip_deserializing))]
+    transaction_root: Digest32,
+    /// Timestamp of a block in ms from UNIX epoch
+    timestamp: u64,
+    /// Current difficulty in a compressed view.
+    #[cfg_attr(feature = "json", serde(rename = "nBits"))]
+    n_bits: u64,
+    /// Block height
+    height: u32,
+    /// Root hash of extension section
+    #[cfg_attr(feature = "json", serde(skip_serializing, skip_deserializing))]
+    extension_root: Digest32,
+    /// Public key of miner. Part of Autolykos solution.
+    #[cfg_attr(feature = "json", serde(skip_serializing, skip_deserializing))]
+    miner_pk: Box<dlog_group::EcPoint>,
+    /// One-time public key. Prevents revealing of miners secret.
+    #[cfg_attr(feature = "json", serde(skip_serializing, skip_deserializing))]
+    pow_onetime_pk: Box<dlog_group::EcPoint>,
+    /// nonce
+    #[cfg_attr(feature = "json", serde(skip_serializing, skip_deserializing))]
+    nonce: Vec<u8>,
+    /// Distance between pseudo-random number, corresponding to nonce `nonce` and a secret,
+    /// corresponding to `miner_pk`. The lower `pow_distance` is, the harder it was to find this solution.
+    #[cfg_attr(feature = "json", serde(skip_serializing, skip_deserializing))]
+    pow_distance: BigInt,
+    /// Votes
+    #[cfg_attr(feature = "json", serde(with = "votes::VotesRef"))]
+    votes: Votes,
 }
 
-impl TryFrom<Base16DecodedBytes> for Votes {
-    type Error = VotesError;
+mod votes {
+    use std::convert::{TryFrom, TryInto};
 
-    fn try_from(bytes: Base16DecodedBytes) -> Result<Self, Self::Error> {
-        bytes.0.try_into()
-    }
-}
+    use ergotree_ir::chain::votes::{Votes, VotesError};
+    #[cfg(feature = "json")]
+    use serde::{Deserialize, Serialize};
 
-impl TryFrom<Vec<u8>> for Votes {
-    type Error = VotesError;
+    use crate::chain::{Base16DecodedBytes, Base16EncodedBytes};
 
-    fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
-        let arr: [u8; 3] = bytes.as_slice().try_into()?;
-        Ok(Self(arr))
-    }
-}
+    /// Reference for remote Votes type. Remote Votes wasn't used, because in ergo-lib
+    /// this type is mostly needed for json serialization and deserialization. Such traits
+    /// of Votes aren't needed in ergotree-ir.
+    #[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
+    #[cfg_attr(
+        feature = "json",
+        serde(
+            into = "Base16EncodedBytes",
+            try_from = "crate::chain::json::block_header::VotesEncodingVariants"
+        )
+    )]
+    #[derive(PartialEq, Eq, Debug, Clone)]
+    #[cfg_attr(feature = "json", serde(remote = "Votes"))]
+    pub(super) struct VotesRef([u8; 3]);
+
+    impl TryFrom<Base16DecodedBytes> for Votes {
+        type Error = VotesError;
 
-impl From<Votes> for Base16EncodedBytes {
-    fn from(v: Votes) -> Self {
-        Base16EncodedBytes::new(v.0.as_ref())
+        fn try_from(bytes: Base16DecodedBytes) -> Result<Self, Self::Error> {
+            bytes.0.try_into()
+        }
     }
-}
 
-impl From<Votes> for Vec<u8> {
-    fn from(v: Votes) -> Self {
-        v.0.to_vec()
+    impl From<Votes> for Base16EncodedBytes {
+        fn from(v: Votes) -> Self {
+            Base16EncodedBytes::new(v.0.as_ref())
+        }
     }
 }
 
-/// Block header
-#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
-#[derive(PartialEq, Eq, Debug, Clone)]
-pub struct BlockHeader {
-    /// Block version, to be increased on every soft and hardfork
-    pub version: u8,
-    /// Id of a parent block
-    #[cfg_attr(feature = "json", serde(rename = "parentId"))]
-    pub parent_id: BlockId,
-    /// Timestamp of a block in ms from UNIX epoch
-    pub timestamp: u64,
-    /// Current difficulty in a compressed view.
-    #[cfg_attr(feature = "json", serde(rename = "nBits"))]
-    pub n_bits: u64,
-    /// Block height
-    pub height: u32,
-    /// Votes
-    pub votes: Votes,
-}
+mod block_id {
+    use ergotree_ir::chain::digest::Digest32;
+    #[cfg(feature = "json")]
+    use reexport_for_json::*;
 
-impl From<BlockHeader> for PreHeader {
-    fn from(bh: BlockHeader) -> Self {
-        PreHeader {
-            version: bh.version,
-            parent_id: bh.parent_id.0.into(),
-            timestamp: bh.timestamp,
-            n_bits: bh.n_bits,
-            height: bh.height,
-            miner_pk: dlog_group::identity().into(), // TODO: get from bh.powSolution when its implemented
-            votes: bh.votes.into(),
-        }
+    /// Reference for BlockId type. Remote BlockId wasn't used, because in ergo-lib
+    /// this type is mostly needed for json serialization and deserialization. Such traits
+    /// of BlockId aren't needed in ergotree-ir.
+    #[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
+    #[cfg_attr(feature = "json", serde(remote = "BlockId"))]
+    #[derive(PartialEq, Eq, Debug, Clone)]
+    pub(super) struct BlockIdRef(#[cfg_attr(feature = "json", serde(with = "DigestRef"))] Digest32);
+
+    #[cfg(feature = "json")]
+    mod reexport_for_json {
+        pub(super) use crate::chain::DigestRef;
+        pub(super) use ergotree_ir::chain::block_id::BlockId;
+        pub(super) use serde::{Deserialize, Serialize};
     }
 }
diff --git a/ergo-lib/src/chain/digest32.rs b/ergo-lib/src/chain/digest32.rs
index 43079e8a3..8589a3f1f 100644
--- a/ergo-lib/src/chain/digest32.rs
+++ b/ergo-lib/src/chain/digest32.rs
@@ -1,57 +1,31 @@
 use crate::chain::{Base16DecodedBytes, Base16EncodedBytes};
-use ergotree_ir::serialization::sigma_byte_reader::SigmaByteRead;
-use ergotree_ir::serialization::sigma_byte_writer::SigmaByteWrite;
-use ergotree_ir::serialization::SigmaParsingError;
-use ergotree_ir::serialization::SigmaSerializable;
-use ergotree_ir::serialization::SigmaSerializeResult;
-use ergotree_ir::util::AsVecI8;
+use ergotree_ir::chain::digest::Digest;
 #[cfg(feature = "json")]
 use serde::{Deserialize, Serialize};
 use std::convert::TryFrom;
 use std::convert::TryInto;
-use std::fmt::Formatter;
 use thiserror::Error;
 
-/// N-bytes array in a box. `Digest32` is most type synonym.
+/// Reference to remote Digest type. Remote Digest wasn't used, because in ergo-lib
+/// this type is mostly needed for json serialization and deserialization. Such traits
+/// of Digest aren't needed in ergotree-ir.
 #[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
 #[cfg_attr(
     feature = "json",
-    serde(into = "Base16EncodedBytes", try_from = "Base16DecodedBytes")
+    serde(
+        into = "Base16EncodedBytes",
+        try_from = "Base16DecodedBytes",
+        remote = "Digest"
+    )
 )]
 #[derive(PartialEq, Eq, Hash, Clone)]
-pub struct Digest<const N: usize>(pub Box<[u8; N]>);
+pub(crate) struct DigestRef<const N: usize>(pub(crate) Box<[u8; N]>);
 
-/// 32 byte array used as ID of some value: block, transaction, etc.
-/// Usually this is as blake2b hash of serialized form
-pub type Digest32 = Digest<32>;
-
-/// AVL tree digest: root hash along with tree height (33 bytes)
-pub type ADDigest = Digest<33>;
-
-impl<const N: usize> Digest<N> {
-    /// Digest size 32 bytes
-    pub const SIZE: usize = N;
-
-    /// All zeros
-    pub fn zero() -> Digest<N> {
-        Digest(Box::new([0u8; N]))
-    }
-}
-
-impl<const N: usize> std::fmt::Debug for Digest<N> {
-    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
-        base16::encode_lower(&(*self.0)).fmt(f)
-    }
-}
-
-/// Blake2b256 hash (256 bit)
-pub fn blake2b256_hash(bytes: &[u8]) -> Digest32 {
-    Digest(sigma_util::hash::blake2b256_hash(bytes))
-}
-
-impl<const N: usize> From<[u8; N]> for Digest<N> {
-    fn from(bytes: [u8; N]) -> Self {
-        Digest(Box::new(bytes))
+impl<const N: usize> TryFrom<Base16DecodedBytes> for Digest<N> {
+    type Error = Digest32Error;
+    fn try_from(bytes: Base16DecodedBytes) -> Result<Self, Self::Error> {
+        let arr: [u8; N] = bytes.0.as_slice().try_into()?;
+        Ok(Digest(Box::new(arr)))
     }
 }
 
@@ -61,63 +35,31 @@ impl<const N: usize> From<Digest<N>> for Base16EncodedBytes {
     }
 }
 
-impl<const N: usize> From<Digest<N>> for Vec<i8> {
-    fn from(v: Digest<N>) -> Self {
-        v.0.to_vec().as_vec_i8()
-    }
-}
-
-impl<const N: usize> From<Digest<N>> for Vec<u8> {
-    fn from(v: Digest<N>) -> Self {
-        v.0.to_vec()
-    }
-}
-
-impl<const N: usize> From<Digest<N>> for [u8; N] {
-    fn from(v: Digest<N>) -> Self {
-        *v.0
-    }
-}
-
-impl<const N: usize> From<Digest<N>> for String {
-    fn from(v: Digest<N>) -> Self {
-        let bytes: Base16EncodedBytes = v.into();
-        bytes.into()
-    }
-}
-
-impl<const N: usize> TryFrom<Base16DecodedBytes> for Digest<N> {
-    type Error = Digest32Error;
-    fn try_from(bytes: Base16DecodedBytes) -> Result<Self, Self::Error> {
-        let arr: [u8; N] = bytes.0.as_slice().try_into()?;
-        Ok(Digest(Box::new(arr)))
-    }
-}
-
-impl<const N: usize> TryFrom<String> for Digest<N> {
+impl<const N: usize> TryFrom<String> for DigestRef<N> {
     type Error = Digest32Error;
 
     fn try_from(value: String) -> Result<Self, Self::Error> {
         let bytes = Base16DecodedBytes::try_from(value)?;
-        Digest::<N>::try_from(bytes)
+        Digest::try_from(bytes).map(|dig| DigestRef(dig.0))
     }
 }
 
-impl<const N: usize> SigmaSerializable for Digest<N> {
-    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> SigmaSerializeResult {
-        w.write_all(self.0.as_ref())?;
-        Ok(())
+impl<const N: usize> From<DigestRef<N>> for String {
+    fn from(v: DigestRef<N>) -> Self {
+        let bytes = Base16EncodedBytes::from(Into::<Digest<N>>::into(v));
+        bytes.into()
     }
-    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SigmaParsingError> {
-        let mut bytes = [0; N];
-        r.read_exact(&mut bytes)?;
-        Ok(Self(bytes.into()))
+}
+
+impl<const N: usize> From<DigestRef<N>> for Digest<N> {
+    fn from(value: DigestRef<N>) -> Self {
+        Digest(value.0)
     }
 }
 
-impl AsRef<[u8]> for Digest32 {
-    fn as_ref(&self) -> &[u8] {
-        &self.0[..]
+impl<const N: usize> From<Digest<N>> for DigestRef<N> {
+    fn from(value: Digest<N>) -> Self {
+        DigestRef(value.0)
     }
 }
 
@@ -131,22 +73,3 @@ pub enum Digest32Error {
     #[error("Invalid byte array size ({0})")]
     InvalidSize(#[from] std::array::TryFromSliceError),
 }
-
-#[cfg(test)]
-mod tests {
-    use super::Digest;
-    use proptest::prelude::{Arbitrary, BoxedStrategy};
-    use proptest::{collection::vec, prelude::*};
-    use std::convert::TryInto;
-
-    impl<const N: usize> Arbitrary for Digest<N> {
-        type Parameters = ();
-        type Strategy = BoxedStrategy<Self>;
-
-        fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
-            vec(any::<u8>(), Self::SIZE)
-                .prop_map(|v| Digest(Box::new(v.try_into().unwrap())))
-                .boxed()
-        }
-    }
-}
diff --git a/ergo-lib/src/chain/ergo_box.rs b/ergo-lib/src/chain/ergo_box.rs
index 1f5247c52..f3ead65a9 100644
--- a/ergo-lib/src/chain/ergo_box.rs
+++ b/ergo-lib/src/chain/ergo_box.rs
@@ -7,6 +7,7 @@ mod register;
 
 pub use box_id::*;
 pub use box_value::*;
+use ergotree_ir::chain::digest::{blake2b256_hash, Digest32};
 use ergotree_ir::ergo_tree::ErgoTree;
 use ergotree_ir::ir_ergo_box::IrBoxId;
 use ergotree_ir::ir_ergo_box::IrErgoBox;
@@ -23,9 +24,7 @@ pub use register::*;
 #[cfg(feature = "json")]
 use super::json;
 use super::token::TokenAmount;
-use super::Digest32;
 use super::{
-    digest32::blake2b256_hash,
     token::{Token, TokenId},
     transaction::TxId,
 };
diff --git a/ergo-lib/src/chain/ergo_box/box_id.rs b/ergo-lib/src/chain/ergo_box/box_id.rs
index e761ffa01..989a074e9 100644
--- a/ergo-lib/src/chain/ergo_box/box_id.rs
+++ b/ergo-lib/src/chain/ergo_box/box_id.rs
@@ -1,16 +1,15 @@
 //! Box id type
-use std::convert::TryFrom;
-use std::convert::TryInto;
+use std::convert::{TryFrom, TryInto};
 
+use ergotree_ir::chain::digest::Digest32;
 use ergotree_ir::ir_ergo_box::IrBoxId;
 
 use ergotree_ir::serialization::SigmaSerializeResult;
 #[cfg(feature = "json")]
 use serde::{Deserialize, Serialize};
 
-use crate::chain::Digest32Error;
+use crate::chain::{Digest32Error, DigestRef};
 
-use super::super::digest32::Digest32;
 use derive_more::From;
 use derive_more::Into;
 use ergotree_ir::serialization::{
@@ -24,7 +23,7 @@ use proptest_derive::Arbitrary;
 #[derive(PartialEq, Eq, Hash, Debug, Clone, From, Into)]
 #[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
 #[cfg_attr(test, derive(Arbitrary))]
-pub struct BoxId(Digest32);
+pub struct BoxId(#[cfg_attr(feature = "json", serde(with = "DigestRef"))] Digest32);
 
 impl BoxId {
     /// Size in bytes
@@ -45,7 +44,7 @@ impl AsRef<[u8]> for BoxId {
 #[cfg(feature = "json")]
 impl From<BoxId> for String {
     fn from(v: BoxId) -> Self {
-        v.0.into()
+        String::from(Into::<DigestRef<32>>::into(v.0))
     }
 }
 
@@ -68,7 +67,9 @@ impl TryFrom<String> for BoxId {
     type Error = Digest32Error;
 
     fn try_from(value: String) -> Result<Self, Self::Error> {
-        Ok(Digest32::try_from(value)?.into())
+        DigestRef::try_from(value)
+            .map(Into::<Digest32>::into)
+            .map(BoxId)
     }
 }
 
diff --git a/ergo-lib/src/chain/ergo_state_context.rs b/ergo-lib/src/chain/ergo_state_context.rs
index b0c43bd61..aea7ab0a7 100644
--- a/ergo-lib/src/chain/ergo_state_context.rs
+++ b/ergo-lib/src/chain/ergo_state_context.rs
@@ -1,4 +1,7 @@
 //! Blockchain state
+use std::convert::TryInto;
+
+use ergotree_ir::chain::header::Header;
 use ergotree_ir::mir::header::PreHeader;
 
 /// Blockchain state (last headers, etc.)
@@ -7,13 +10,19 @@ pub struct ErgoStateContext {
     /// Block header with the current `spendingTransaction`, that can be predicted
     /// by a miner before it's formation
     pub pre_header: PreHeader,
+    /// Fixed number of last block headers in descending order (first header is the newest one)
+    pub headers: [Header; 10],
 }
 
 impl ErgoStateContext {
     /// Dummy instance intended for tests where actual values are not used
     pub fn dummy() -> ErgoStateContext {
+        let headers = vec![Header::dummy(); 10]
+            .try_into()
+            .expect("internal error: Headers array length isn't eq to 10");
         ErgoStateContext {
             pre_header: PreHeader::dummy(),
+            headers,
         }
     }
 }
diff --git a/ergo-lib/src/chain/json/block_header.rs b/ergo-lib/src/chain/json/block_header.rs
index 56f7a9ef0..caed1f54c 100644
--- a/ergo-lib/src/chain/json/block_header.rs
+++ b/ergo-lib/src/chain/json/block_header.rs
@@ -2,8 +2,8 @@ use serde::Deserialize;
 use std::convert::TryFrom;
 use std::convert::TryInto;
 
-use crate::chain::block_header::Votes;
-use crate::chain::block_header::VotesError;
+use ergotree_ir::chain::votes::{Votes, VotesError};
+
 use crate::chain::Base16DecodedBytes;
 
 #[derive(Deserialize, Debug, Clone)]
@@ -26,7 +26,16 @@ impl TryFrom<VotesEncodingVariants> for Votes {
 
 #[cfg(test)]
 mod tests {
-    use crate::chain::block_header::BlockHeader;
+    use ergotree_ir::chain::header::Header;
+
+    use crate::chain::block_header::BlockHeaderRef;
+
+    // Tries to convert json string to `Header`.
+    fn header_try_from_json(json: &str) -> Result<Header, serde_json::Error> {
+        let mut de = serde_json::Deserializer::from_str(json);
+        // actually, returns `Header`: https://serde.rs/remote-derive.html
+        BlockHeaderRef::deserialize(&mut de)
+    }
 
     #[test]
     fn parse_block_header() {
@@ -54,8 +63,8 @@ mod tests {
             "transactionsId": "ac80245714f25aa2fafe5494ad02a26d46e7955b8f5709f3659f1b9440797b3e",
             "parentId": "6481752bace5fa5acba5d5ef7124d48826664742d46c974c98a2d60ace229a34"
         }"#;
-        let b: BlockHeader = serde_json::from_str(json).unwrap();
-        assert_eq!(b.height, 471746);
+        let header: Header = header_try_from_json(json).unwrap();
+        assert_eq!(header.height, 471746);
     }
 
     #[test]
@@ -85,7 +94,7 @@ mod tests {
             "transactionsId": "ac80245714f25aa2fafe5494ad02a26d46e7955b8f5709f3659f1b9440797b3e",
             "parentId": "6481752bace5fa5acba5d5ef7124d48826664742d46c974c98a2d60ace229a34"
         }"#;
-        let b: BlockHeader = serde_json::from_str(json).unwrap();
-        assert_eq!(b.height, 471746);
+        let header: Header = header_try_from_json(json).unwrap();
+        assert_eq!(header.height, 471746);
     }
 }
diff --git a/ergo-lib/src/chain/token.rs b/ergo-lib/src/chain/token.rs
index 13d16b95d..ad23d529d 100644
--- a/ergo-lib/src/chain/token.rs
+++ b/ergo-lib/src/chain/token.rs
@@ -1,5 +1,6 @@
 //! Token related types
 
+use ergotree_ir::chain::digest::Digest32;
 use ergotree_ir::serialization::SigmaSerializeResult;
 use ergotree_ir::serialization::{
     sigma_byte_reader::SigmaByteRead, sigma_byte_writer::SigmaByteWrite, SigmaParsingError,
@@ -7,7 +8,7 @@ use ergotree_ir::serialization::{
 };
 use std::convert::TryFrom;
 
-use super::digest32::Digest32;
+use super::digest32::DigestRef;
 use super::ergo_box::BoxId;
 use derive_more::From;
 use derive_more::Into;
@@ -20,7 +21,7 @@ use thiserror::Error;
 /// newtype for token id
 #[derive(PartialEq, Eq, Hash, Debug, Clone, From, Into)]
 #[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
-pub struct TokenId(Digest32);
+pub struct TokenId(#[cfg_attr(feature = "json", serde(with = "DigestRef"))] Digest32);
 
 impl TokenId {
     /// token id size in bytes
@@ -53,7 +54,7 @@ impl AsRef<[u8]> for TokenId {
 
 impl From<TokenId> for String {
     fn from(v: TokenId) -> Self {
-        v.0.into()
+        String::from(Into::<DigestRef<32>>::into(v.0))
     }
 }
 
diff --git a/ergo-lib/src/chain/transaction.rs b/ergo-lib/src/chain/transaction.rs
index aada04559..3f2fa8890 100644
--- a/ergo-lib/src/chain/transaction.rs
+++ b/ergo-lib/src/chain/transaction.rs
@@ -9,6 +9,7 @@ use thiserror::Error;
 
 pub use data_input::*;
 use ergotree_interpreter::sigma_protocol::prover::ProofBytes;
+use ergotree_ir::chain::digest::{blake2b256_hash, Digest32};
 use ergotree_ir::serialization::sigma_byte_reader::SigmaByteRead;
 use ergotree_ir::serialization::sigma_byte_writer::SigmaByteWrite;
 use ergotree_ir::serialization::SigmaParsingError;
@@ -19,14 +20,11 @@ pub use input::*;
 
 use self::unsigned::UnsignedTransaction;
 
+#[cfg(feature = "json")]
+use super::digest32::DigestRef;
 #[cfg(feature = "json")]
 use super::json;
-use super::{
-    digest32::{blake2b256_hash, Digest32},
-    ergo_box::ErgoBox,
-    ergo_box::ErgoBoxCandidate,
-    token::TokenId,
-};
+use super::{ergo_box::ErgoBox, ergo_box::ErgoBoxCandidate, token::TokenId};
 use indexmap::IndexSet;
 #[cfg(test)]
 use proptest_derive::Arbitrary;
@@ -41,7 +39,7 @@ use std::iter::FromIterator;
 #[derive(PartialEq, Eq, Hash, Debug, Clone)]
 #[cfg_attr(test, derive(Arbitrary))]
 #[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
-pub struct TxId(pub Digest32);
+pub struct TxId(#[cfg_attr(feature = "json", serde(with = "DigestRef"))] pub Digest32);
 
 impl TxId {
     /// All zeros
@@ -63,7 +61,7 @@ impl SigmaSerializable for TxId {
 #[cfg(feature = "json")]
 impl From<TxId> for String {
     fn from(v: TxId) -> Self {
-        v.0.into()
+        String::from(Into::<DigestRef<32>>::into(v.0))
     }
 }
 
diff --git a/ergo-lib/src/chain/transaction/unsigned.rs b/ergo-lib/src/chain/transaction/unsigned.rs
index be85dd383..9bcdacf6c 100644
--- a/ergo-lib/src/chain/transaction/unsigned.rs
+++ b/ergo-lib/src/chain/transaction/unsigned.rs
@@ -9,11 +9,9 @@ use super::json;
 use super::prover_result::ProverResult;
 use super::DataInput;
 use super::TxIoVec;
-use super::{
-    super::{digest32::blake2b256_hash, ergo_box::ErgoBoxCandidate},
-    Transaction, TxId,
-};
+use super::{super::ergo_box::ErgoBoxCandidate, Transaction, TxId};
 use ergotree_interpreter::sigma_protocol::prover::ProofBytes;
+use ergotree_ir::chain::digest::blake2b256_hash;
 use ergotree_ir::serialization::SigmaSerializationError;
 use indexmap::IndexSet;
 #[cfg(feature = "json")]
diff --git a/ergo-lib/src/wallet/signing.rs b/ergo-lib/src/wallet/signing.rs
index e7d47a7f9..5a25f840f 100644
--- a/ergo-lib/src/wallet/signing.rs
+++ b/ergo-lib/src/wallet/signing.rs
@@ -131,6 +131,7 @@ pub fn make_context(
         inputs: inputs_ir,
         pre_header: state_ctx.pre_header.clone(),
         extension: ContextExtension::empty(),
+        headers: state_ctx.headers.clone(),
     })
 }
 
diff --git a/ergotree-interpreter/src/eval.rs b/ergotree-interpreter/src/eval.rs
index 68bed426a..be6ada082 100644
--- a/ergotree-interpreter/src/eval.rs
+++ b/ergotree-interpreter/src/eval.rs
@@ -213,6 +213,7 @@ fn smethod_eval_fn(method: &SMethod) -> Result<EvalFn, EvalError> {
         scontext::TYPE_CODE => match method.method_id() {
             scontext::DATA_INPUTS_PROPERTY_METHOD_ID => self::scontext::DATA_INPUTS_EVAL_FN,
             scontext::SELF_BOX_INDEX_PROPERTY_METHOD_ID => self::scontext::SELF_BOX_INDEX_EVAL_FN,
+            scontext::HEADERS_PROPERTY_METHOD_ID => self::scontext::HEADERS_EVAL_FN,
             method_id => {
                 return Err(EvalError::NotFound(format!(
                     "Eval fn: unknown method id in SContext: {:?}",
diff --git a/ergotree-interpreter/src/eval/context.rs b/ergotree-interpreter/src/eval/context.rs
index c571390d2..e145ec406 100644
--- a/ergotree-interpreter/src/eval/context.rs
+++ b/ergotree-interpreter/src/eval/context.rs
@@ -3,6 +3,7 @@ pub(crate) mod ir_ergo_box_dummy;
 use std::rc::Rc;
 
 use crate::sigma_protocol::prover::ContextExtension;
+use ergotree_ir::chain::header::Header;
 use ergotree_ir::ir_ergo_box::IrBoxId;
 use ergotree_ir::ir_ergo_box::IrErgoBoxArena;
 use ergotree_ir::mir::header::PreHeader;
@@ -24,6 +25,8 @@ pub struct Context {
     pub inputs: Vec<IrBoxId>,
     /// Pre header of current block
     pub pre_header: PreHeader,
+    /// Fixed number of last block headers in descending order (first header is the newest one)
+    pub headers: [Header; 10],
     /// prover-defined key-value pairs, that may be used inside a script
     pub extension: ContextExtension,
 }
@@ -60,9 +63,19 @@ mod arbitrary {
                 vec(any::<IrErgoBoxDummy>(), 0..3),
                 any::<PreHeader>(),
                 any::<ContextExtension>(),
+                any::<[Header; 10]>(),
             )
                 .prop_map(
-                    |(height, self_box, outputs, inputs, data_inputs, pre_header, extension)| {
+                    |(
+                        height,
+                        self_box,
+                        outputs,
+                        inputs,
+                        data_inputs,
+                        pre_header,
+                        extension,
+                        headers,
+                    )| {
                         let self_box_id = self_box.id();
                         let outputs_ids = outputs.iter().map(|b| b.id()).collect();
                         let inputs_ids = inputs.iter().map(|b| b.id()).collect();
@@ -88,6 +101,7 @@ mod arbitrary {
                             inputs: inputs_ids,
                             pre_header,
                             extension,
+                            headers,
                         }
                     },
                 )
diff --git a/ergotree-interpreter/src/eval/scontext.rs b/ergotree-interpreter/src/eval/scontext.rs
index d148f1d29..01c69f8ba 100644
--- a/ergotree-interpreter/src/eval/scontext.rs
+++ b/ergotree-interpreter/src/eval/scontext.rs
@@ -41,6 +41,19 @@ pub(crate) static SELF_BOX_INDEX_EVAL_FN: EvalFn = |_env, ctx, obj, _args| {
     Ok(Value::Int(box_index as i32))
 };
 
+pub(crate) static HEADERS_EVAL_FN: EvalFn = |_env, ctx, obj, _args| {
+    if obj != Value::Context {
+        return Err(EvalError::UnexpectedValue(format!(
+            "Context.headers: expected object of Value::Context, got {:?}",
+            obj
+        )));
+    }
+    Ok(Value::Coll(CollKind::WrappedColl {
+        items: ctx.ctx.headers.clone().map(Value::Header).to_vec(),
+        elem_tpe: SType::SHeader,
+    }))
+};
+
 #[cfg(test)]
 #[cfg(feature = "arbitrary")]
 #[allow(clippy::unwrap_used)]
@@ -48,6 +61,7 @@ mod tests {
     use crate::eval::context::ir_ergo_box_dummy::IrErgoBoxDummy;
     use crate::eval::context::Context;
     use crate::eval::tests::eval_out;
+    use ergotree_ir::chain::header::Header;
     use ergotree_ir::mir::expr::Expr;
     use ergotree_ir::mir::property_call::PropertyCall;
     use ergotree_ir::types::scontext;
@@ -75,4 +89,13 @@ mod tests {
         let rc = Rc::new(make_ctx_inputs_includes_self_box());
         assert_eq!(eval_out::<i32>(&expr, rc), 1);
     }
+
+    #[test]
+    fn eval_headers() {
+        let expr: Expr = PropertyCall::new(Expr::Context, scontext::HEADERS_PROPERTY.clone())
+            .expect("internal error: `headers` method has parameters length != 1")
+            .into();
+        let ctx = Rc::new(force_any_val::<Context>());
+        assert_eq!(eval_out::<[Header; 10]>(&expr, ctx.clone()), ctx.headers);
+    }
 }
diff --git a/ergotree-ir/src/chain.rs b/ergotree-ir/src/chain.rs
new file mode 100644
index 000000000..f9351657d
--- /dev/null
+++ b/ergotree-ir/src/chain.rs
@@ -0,0 +1,6 @@
+//! Predefined types encapsulating chain data
+
+pub mod block_id;
+pub mod digest;
+pub mod header;
+pub mod votes;
diff --git a/ergotree-ir/src/chain/block_id.rs b/ergotree-ir/src/chain/block_id.rs
new file mode 100644
index 000000000..2f01bb193
--- /dev/null
+++ b/ergotree-ir/src/chain/block_id.rs
@@ -0,0 +1,7 @@
+//! Main "remote" type for [BlockId]()
+
+use super::digest::Digest32;
+
+/// Block id
+#[derive(PartialEq, Eq, Debug, Clone, Default)]
+pub struct BlockId(pub Digest32);
diff --git a/ergotree-ir/src/chain/digest.rs b/ergotree-ir/src/chain/digest.rs
new file mode 100644
index 000000000..d95ccd140
--- /dev/null
+++ b/ergotree-ir/src/chain/digest.rs
@@ -0,0 +1,110 @@
+//! Main "remote" type for [Digest]()
+
+use crate::serialization::{
+    sigma_byte_reader::SigmaByteRead, sigma_byte_writer::SigmaByteWrite, SigmaParsingError,
+    SigmaSerializable, SigmaSerializeResult,
+};
+use crate::util::AsVecI8;
+
+/// Blake2b256 hash (256 bit)
+pub fn blake2b256_hash(bytes: &[u8]) -> Digest32 {
+    Digest(sigma_util::hash::blake2b256_hash(bytes))
+}
+
+/// N-bytes array in a box. `Digest32` is most type synonym.
+#[derive(PartialEq, Eq, Hash, Debug, Clone)]
+pub struct Digest<const N: usize>(pub Box<[u8; N]>);
+
+/// 32 byte array used as ID of some value: block, transaction, etc.
+/// Usually this is as blake2b hash of serialized form
+pub type Digest32 = Digest<32>;
+
+/// AVL tree digest: root hash along with tree height (33 bytes)
+pub type ADDigest = Digest<33>;
+
+impl<const N: usize> Digest<N> {
+    /// Digest size bytes
+    pub const SIZE: usize = N;
+
+    /// All zeros
+    pub fn zero() -> Digest<N> {
+        Digest(Box::new([0u8; N]))
+    }
+}
+
+impl<const N: usize> Default for Digest<N> {
+    fn default() -> Self {
+        Digest::zero()
+    }
+}
+
+impl<const N: usize> From<[u8; N]> for Digest<N> {
+    fn from(bytes: [u8; N]) -> Self {
+        Digest(Box::new(bytes))
+    }
+}
+
+impl<const N: usize> From<Digest<N>> for [u8; N] {
+    fn from(v: Digest<N>) -> Self {
+        *v.0
+    }
+}
+
+impl<const N: usize> From<Digest<N>> for Vec<u8> {
+    fn from(v: Digest<N>) -> Self {
+        v.0.to_vec()
+    }
+}
+
+impl<const N: usize> From<Digest<N>> for Vec<i8> {
+    fn from(v: Digest<N>) -> Self {
+        v.0.to_vec().as_vec_i8()
+    }
+}
+
+impl AsRef<[u8]> for Digest32 {
+    fn as_ref(&self) -> &[u8] {
+        &self.0[..]
+    }
+}
+
+impl<const N: usize> SigmaSerializable for Digest<N> {
+    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> SigmaSerializeResult {
+        w.write_all(self.0.as_ref())?;
+        Ok(())
+    }
+
+    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SigmaParsingError> {
+        let mut bytes = [0; N];
+        r.read_exact(&mut bytes)?;
+        Ok(Self(bytes.into()))
+    }
+}
+
+#[cfg(feature = "arbitrary")]
+mod arbitrary {
+    #![allow(clippy::expect_used)]
+
+    use std::convert::TryInto;
+
+    use proptest::prelude::{Arbitrary, BoxedStrategy};
+    use proptest::{collection::vec, prelude::*};
+
+    use super::Digest;
+
+    impl<const N: usize> Arbitrary for Digest<N> {
+        type Parameters = ();
+        fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
+            vec(any::<u8>(), Self::SIZE)
+                .prop_map(|v| {
+                    Digest(Box::new(
+                        v.try_into()
+                            .expect("internal error: vec has length != Digest::SIZE"),
+                    ))
+                })
+                .boxed()
+        }
+
+        type Strategy = BoxedStrategy<Self>;
+    }
+}
diff --git a/ergotree-ir/src/chain/header.rs b/ergotree-ir/src/chain/header.rs
new file mode 100644
index 000000000..e6a8405f1
--- /dev/null
+++ b/ergotree-ir/src/chain/header.rs
@@ -0,0 +1,159 @@
+//! Block header
+use num_bigint::BigInt;
+
+use crate::mir::header::PreHeader;
+use crate::sigma_protocol::dlog_group;
+
+use super::block_id::BlockId;
+use super::digest::{ADDigest, Digest32};
+use super::votes::Votes;
+
+/// Represents data of the block header available in Sigma propositions.
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct Header {
+    /// Block version, to be increased on every soft and hardfork.
+    pub version: u8,
+    /// Bytes representation of ModifierId of this Header
+    pub id: BlockId,
+    /// Bytes representation of ModifierId of the parent block
+    pub parent_id: BlockId,
+    /// Hash of ADProofs for transactions in a block
+    pub ad_proofs_root: Digest32,
+    /// AvlTree of a state after block application
+    pub state_root: ADDigest,
+    /// Root hash (for a Merkle tree) of transactions in a block.
+    pub transaction_root: Digest32,
+    /// Timestamp of a block in ms from UNIX epoch
+    pub timestamp: u64,
+    /// Current difficulty in a compressed view.
+    pub n_bits: u64,
+    /// Block height
+    pub height: u32,
+    /// Root hash of extension section
+    pub extension_root: Digest32,
+    /// Public key of miner. Part of Autolykos solution.
+    pub miner_pk: Box<dlog_group::EcPoint>,
+    /// One-time public key. Prevents revealing of miners secret.
+    pub pow_onetime_pk: Box<dlog_group::EcPoint>,
+    /// nonce
+    pub nonce: Vec<u8>,
+    /// Distance between pseudo-random number, corresponding to nonce `nonce` and a secret,
+    /// corresponding to `miner_pk`. The lower `pow_distance` is, the harder it was to find this solution.
+    pub pow_distance: BigInt,
+    /// Miner votes for changing system parameters.
+    /// 3 bytes in accordance to Scala implementation, but will use `Vec` until further improvements
+    pub votes: Votes,
+}
+
+impl Header {
+    /// Dummy instance intended for tests where actual values are not used
+    pub fn dummy() -> Self {
+        let empty_digest = Digest32::zero();
+        Header {
+            version: 1,
+            id: BlockId(empty_digest.clone()),
+            parent_id: BlockId(empty_digest.clone()),
+            ad_proofs_root: empty_digest.clone(),
+            state_root: ADDigest::zero(),
+            transaction_root: empty_digest.clone(),
+            timestamp: 0,
+            n_bits: 0,
+            height: 0,
+            extension_root: empty_digest,
+            miner_pk: dlog_group::generator().into(),
+            pow_onetime_pk: dlog_group::generator().into(),
+            nonce: Vec::new(),
+            pow_distance: BigInt::default(),
+            votes: Votes([0u8; 3]),
+        }
+    }
+}
+
+// todo-sab refactor when implementing this https://github.com/ergoplatform/sigma-rust/issues/373
+impl From<Header> for PreHeader {
+    fn from(bh: Header) -> Self {
+        PreHeader {
+            version: bh.version,
+            parent_id: bh.parent_id.0.into(),
+            timestamp: bh.timestamp,
+            n_bits: bh.n_bits,
+            height: bh.height,
+            miner_pk: dlog_group::identity().into(), // TODO: get from bh.powSolution when its implemented
+            votes: bh.votes.into(),
+        }
+    }
+}
+
+#[cfg(feature = "arbitrary")]
+mod arbitrary {
+    use num_bigint::BigInt;
+    use proptest::array::{uniform3, uniform32};
+    use proptest::prelude::*;
+
+    use crate::chain::digest::{ADDigest, Digest};
+    use crate::sigma_protocol::dlog_group::EcPoint;
+
+    use super::{BlockId, Header, Votes};
+
+    impl Arbitrary for Header {
+        type Parameters = ();
+        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+            (
+                uniform32(1u8..),
+                uniform32(1u8..),
+                uniform32(1u8..),
+                uniform32(1u8..),
+                uniform32(1u8..),
+                // Timestamps between 2000-2050
+                946_674_000_000..2_500_400_300_000u64,
+                any::<u64>(),
+                0..1_000_000u32,
+                any::<Box<EcPoint>>(),
+                any::<Box<EcPoint>>(),
+                uniform3(1u8..),
+            )
+                .prop_map(
+                    |(
+                        id,
+                        parent_id,
+                        ad_proofs_root,
+                        transaction_root,
+                        extension_root,
+                        timestamp,
+                        n_bits,
+                        height,
+                        miner_pk,
+                        pow_onetime_pk,
+                        votes,
+                    )| {
+                        let id = BlockId(Digest(id.into()));
+                        let parent_id = BlockId(Digest(parent_id.into()));
+                        let ad_proofs_root = Digest(ad_proofs_root.into());
+                        let transaction_root = Digest(transaction_root.into());
+                        let extension_root = Digest(extension_root.into());
+                        let votes = Votes(votes);
+                        Self {
+                            version: 1,
+                            id,
+                            parent_id,
+                            ad_proofs_root,
+                            state_root: ADDigest::zero(),
+                            transaction_root,
+                            timestamp,
+                            n_bits,
+                            height,
+                            extension_root,
+                            miner_pk,
+                            pow_onetime_pk,
+                            nonce: Vec::new(),
+                            pow_distance: BigInt::default(),
+                            votes,
+                        }
+                    },
+                )
+                .boxed()
+        }
+
+        type Strategy = BoxedStrategy<Header>;
+    }
+}
diff --git a/ergotree-ir/src/chain/votes.rs b/ergotree-ir/src/chain/votes.rs
new file mode 100644
index 000000000..315a9f27a
--- /dev/null
+++ b/ergotree-ir/src/chain/votes.rs
@@ -0,0 +1,31 @@
+//! Main "remote" type for [Vote]()
+use thiserror::Error;
+
+use std::convert::{TryFrom, TryInto};
+
+/// Votes for changing system parameters
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct Votes(pub [u8; 3]);
+
+impl From<Votes> for Vec<u8> {
+    fn from(v: Votes) -> Self {
+        v.0.to_vec()
+    }
+}
+
+impl TryFrom<Vec<u8>> for Votes {
+    type Error = VotesError;
+
+    fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
+        let arr: [u8; 3] = bytes.as_slice().try_into()?;
+        Ok(Self(arr))
+    }
+}
+
+/// Votes errors
+#[derive(Error, Debug)]
+pub enum VotesError {
+    /// Invalid byte array size
+    #[error("Votes: Invalid byte array size ({0})")]
+    InvalidSize(#[from] std::array::TryFromSliceError),
+}
diff --git a/ergotree-ir/src/lib.rs b/ergotree-ir/src/lib.rs
index 1abb0802f..06c588e2e 100644
--- a/ergotree-ir/src/lib.rs
+++ b/ergotree-ir/src/lib.rs
@@ -22,6 +22,7 @@ mod has_opcode;
 
 pub mod address;
 pub mod bigint256;
+pub mod chain;
 pub mod ergo_tree;
 pub mod ir_ergo_box;
 pub mod mir;
diff --git a/ergotree-ir/src/mir/constant.rs b/ergotree-ir/src/mir/constant.rs
index 0e4dc4193..c4f48a022 100644
--- a/ergotree-ir/src/mir/constant.rs
+++ b/ergotree-ir/src/mir/constant.rs
@@ -214,6 +214,7 @@ impl TryFrom<Value> for Constant {
             }
             Value::AvlTree(a) => Ok(Constant::from(*a)),
             Value::Context => Err("Cannot convert Value::Context into Constant".into()),
+            Value::Header(_) => Err("Cannot convert Value::Header(_) into Constant".into()),
             Value::Global => Err("Cannot convert Value::Global into Constant".into()),
             Value::Lambda(_) => Err("Cannot convert Value::Lambda(_) into Constant".into()),
         }
diff --git a/ergotree-ir/src/mir/value.rs b/ergotree-ir/src/mir/value.rs
index 26e873e95..f87097268 100644
--- a/ergotree-ir/src/mir/value.rs
+++ b/ergotree-ir/src/mir/value.rs
@@ -5,6 +5,7 @@ use std::convert::TryInto;
 use impl_trait_for_tuples::impl_for_tuples;
 
 use crate::bigint256::BigInt256;
+use crate::chain::header::Header;
 use crate::ir_ergo_box::IrBoxId;
 use crate::sigma_protocol::dlog_group::EcPoint;
 use crate::sigma_protocol::sigma_boolean::SigmaProp;
@@ -158,6 +159,8 @@ pub enum Value {
     Tup(TupleItems<Value>),
     /// Transaction(and blockchain) context info
     Context,
+    /// Block header
+    Header(Header),
     /// Global which is used to define global methods
     Global,
     /// Optional value
@@ -241,6 +244,7 @@ impl StoreWrapped for i32 {}
 impl StoreWrapped for i64 {}
 impl StoreWrapped for BigInt256 {}
 impl StoreWrapped for IrBoxId {}
+impl StoreWrapped for Header {}
 impl StoreWrapped for EcPoint {}
 impl StoreWrapped for SigmaProp {}
 impl<T: StoreWrapped> StoreWrapped for Option<T> {}
@@ -353,6 +357,18 @@ impl TryExtractFrom<Value> for IrBoxId {
     }
 }
 
+impl TryExtractFrom<Value> for Header {
+    fn try_extract_from(c: Value) -> Result<Self, TryExtractFromError> {
+        match c {
+            Value::Header(h) => Ok(h),
+            _ => Err(TryExtractFromError(format!(
+                "expected Header, found {:?}",
+                c
+            ))),
+        }
+    }
+}
+
 impl<T: TryExtractFrom<Value> + StoreWrapped> TryExtractFrom<Value> for Vec<T> {
     fn try_extract_from(c: Value) -> Result<Self, TryExtractFromError> {
         match c {
@@ -376,6 +392,36 @@ impl<T: TryExtractFrom<Value> + StoreWrapped> TryExtractFrom<Value> for Vec<T> {
     }
 }
 
+impl<T: TryExtractFrom<Value> + StoreWrapped, const N: usize> TryExtractFrom<Value> for [T; N] {
+    fn try_extract_from(c: Value) -> Result<Self, TryExtractFromError> {
+        match c {
+            Value::Coll(coll) => match coll {
+                CollKind::WrappedColl {
+                    elem_tpe: _,
+                    items: v,
+                } => {
+                    let v = v
+                        .into_iter()
+                        .map(T::try_extract_from)
+                        .collect::<Result<Vec<_>, _>>()?;
+                    let len = v.len();
+                    v.try_into().map_err(|_| TryExtractFromError(format!("can't convert vec of {:?} with length of {:?} to array with length of {:?}", std::any::type_name::<T>(), len, N)))
+                }
+                _ => Err(TryExtractFromError(format!(
+                    "expected {:?}, found {:?}",
+                    std::any::type_name::<Self>(),
+                    coll
+                ))),
+            },
+            _ => Err(TryExtractFromError(format!(
+                "expected {:?}, found {:?}",
+                std::any::type_name::<Self>(),
+                c
+            ))),
+        }
+    }
+}
+
 impl TryExtractFrom<Value> for Vec<i8> {
     fn try_extract_from(v: Value) -> Result<Self, TryExtractFromError> {
         match v {
diff --git a/ergotree-ir/src/sigma_protocol/dlog_group.rs b/ergotree-ir/src/sigma_protocol/dlog_group.rs
index 5f5c97d79..4f014e897 100644
--- a/ergotree-ir/src/sigma_protocol/dlog_group.rs
+++ b/ergotree-ir/src/sigma_protocol/dlog_group.rs
@@ -33,7 +33,7 @@ use std::convert::TryFrom;
 use std::ops::{Add, Mul, Neg};
 
 /// Elliptic curve point
-#[derive(PartialEq, Clone)]
+#[derive(PartialEq, Clone, Default)]
 pub struct EcPoint(ProjectivePoint);
 
 #[allow(clippy::unwrap_used)]
diff --git a/ergotree-ir/src/types/sheader.rs b/ergotree-ir/src/types/sheader.rs
index b807bb09e..f6376b578 100644
--- a/ergotree-ir/src/types/sheader.rs
+++ b/ergotree-ir/src/types/sheader.rs
@@ -1,11 +1,13 @@
-use crate::serialization::types::TypeCode;
+#![allow(missing_docs)]
 
-use super::smethod::MethodId;
-use super::smethod::SMethodDesc;
-use super::stype::SType;
-use crate::types::stype::SType::{SByte, SColl};
 use lazy_static::lazy_static;
 
+use crate::serialization::types::TypeCode;
+
+use super::smethod::{MethodId, SMethod, SMethodDesc};
+use super::stype::SType::{self, SByte, SColl};
+use super::stype_companion::STypeCompanion::Header;
+
 /// SHeader type code
 pub const TYPE_CODE: TypeCode = TypeCode::SHEADER;
 /// SHeader type name
@@ -15,91 +17,165 @@ lazy_static! {
     /// Header method descriptors
     pub(crate) static ref METHOD_DESC: Vec<&'static SMethodDesc> =
         vec![
-            &ID_METHOD_DESC,
-            &VERSION_METHOD_DESC,
-            &PARENT_ID_METHOD,
-            &AD_PROOF_ROOT_METHOD,
-            &STATE_ROOT_METHOD,
-            &TRANSACTIONS_ROOT_METHOD,
-            &TIMESTAMP_METHOD,
-            &N_BITS_METHOD,
-            &HEIGHT_METHOD,
-            &EXTENSION_ROOT_METHOD,
-            &MINER_PK_METHOD,
-            &POW_ONETIME_PK_METHOD,
-            &POW_NONCE_METHOD,
-            &POW_DISTANCE_METHOD,
-            &VOTES_METHOD,
+            &ID_PROPERTY_METHOD_DESC,
+            &VERSION_PROPERTY_METHOD_DESC,
+            &PARENT_ID_PROPERTY_METHOD_DESC,
+            &AD_PROOF_ROOT_PROPERTY_METHOD_DESC,
+            &STATE_ROOT_PROPERTY_METHOD_DESC,
+            &TRANSACTIONS_ROOT_PROPERTY_METHOD_DESC,
+            &TIMESTAMP_PROPERTY_METHOD_DESC,
+            &N_BITS_PROPERTY_METHOD_DESC,
+            &HEIGHT_PROPERTY_METHOD_DESC,
+            &EXTENSION_ROOT_PROPERTY_METHOD_DESC,
+            &MINER_PK_PROPERTY_METHOD_DESC,
+            &POW_ONETIME_PK_PROPERTY_METHOD_DESC,
+            &POW_NONCE_PROPERTY_METHOD_DESC,
+            &POW_DISTANCE_PROPERTY_METHOD_DESC,
+            &VOTES_PROPERTY_METHOD_DESC,
         ]
     ;
 }
 
+// todo all to one lazy_static
+
+pub const ID_PROPERTY_METHOD_ID: MethodId = MethodId(1);
 lazy_static! {
-    static ref ID_METHOD_DESC: SMethodDesc = property("id", SColl(SByte.into()), MethodId(1));
+    pub static ref ID_PROPERTY: SMethod = SMethod::new(Header, ID_PROPERTY_METHOD_DESC.clone(),);
+    static ref ID_PROPERTY_METHOD_DESC: SMethodDesc =
+        property("id", SColl(SByte.into()), ID_PROPERTY_METHOD_ID);
 }
 
+pub const VERSION_PROPERTY_METHOD_ID: MethodId = MethodId(2);
 lazy_static! {
-    static ref VERSION_METHOD_DESC: SMethodDesc = property("id", SByte, MethodId(2));
+    pub static ref VERSION_PROPERTY: SMethod =
+        SMethod::new(Header, VERSION_PROPERTY_METHOD_DESC.clone(),);
+    static ref VERSION_PROPERTY_METHOD_DESC: SMethodDesc =
+        property("version", SByte, VERSION_PROPERTY_METHOD_ID);
 }
 
+pub const PARENT_ID_PROPERTY_METHOD_ID: MethodId = MethodId(3);
 lazy_static! {
-    static ref PARENT_ID_METHOD: SMethodDesc =
-        property("parentId", SColl(SByte.into()), MethodId(3));
+    pub static ref PARENT_ID_PROPERTY: SMethod =
+        SMethod::new(Header, PARENT_ID_PROPERTY_METHOD_DESC.clone(),);
+    static ref PARENT_ID_PROPERTY_METHOD_DESC: SMethodDesc = property(
+        "parentId",
+        SColl(SByte.into()),
+        PARENT_ID_PROPERTY_METHOD_ID
+    );
 }
 
+pub const AD_PROOF_ROOT_PROPERTY_METHOD_ID: MethodId = MethodId(4);
 lazy_static! {
-    static ref AD_PROOF_ROOT_METHOD: SMethodDesc =
-        property("ADProofsRoot", SColl(SByte.into()), MethodId(4));
+    pub static ref AD_PROOF_ROOT_PROPERTY: SMethod =
+        SMethod::new(Header, AD_PROOF_ROOT_PROPERTY_METHOD_DESC.clone(),);
+    static ref AD_PROOF_ROOT_PROPERTY_METHOD_DESC: SMethodDesc = property(
+        "ADProofsRoot",
+        SColl(SByte.into()),
+        AD_PROOF_ROOT_PROPERTY_METHOD_ID
+    );
 }
 
+pub const STATE_ROOT_PROPERTY_METHOD_ID: MethodId = MethodId(5);
 lazy_static! {
-    static ref STATE_ROOT_METHOD: SMethodDesc = property("stateRoot", SType::SAvlTree, MethodId(5));
+    pub static ref STATE_ROOT_PROPERTY: SMethod =
+        SMethod::new(Header, STATE_ROOT_PROPERTY_METHOD_DESC.clone(),);
+    static ref STATE_ROOT_PROPERTY_METHOD_DESC: SMethodDesc =
+        property("stateRoot", SType::SAvlTree, STATE_ROOT_PROPERTY_METHOD_ID);
 }
 
+pub const TRANSACTIONS_ROOT_PROPERTY_METHOD_ID: MethodId = MethodId(6);
 lazy_static! {
-    static ref TRANSACTIONS_ROOT_METHOD: SMethodDesc =
-        property("transactionsRoot", SColl(SByte.into()), MethodId(6));
+    pub static ref TRANSACTIONS_ROOT_PROPERTY: SMethod =
+        SMethod::new(Header, TRANSACTIONS_ROOT_PROPERTY_METHOD_DESC.clone(),);
+    static ref TRANSACTIONS_ROOT_PROPERTY_METHOD_DESC: SMethodDesc = property(
+        "transactionsRoot",
+        SColl(SByte.into()),
+        TRANSACTIONS_ROOT_PROPERTY_METHOD_ID
+    );
 }
 
+pub const TIMESTAMP_PROPERTY_METHOD_ID: MethodId = MethodId(7);
 lazy_static! {
-    static ref TIMESTAMP_METHOD: SMethodDesc = property("timestamp", SType::SLong, MethodId(7));
+    pub static ref TIMESTAMP_PROPERTY: SMethod =
+        SMethod::new(Header, TIMESTAMP_PROPERTY_METHOD_DESC.clone(),);
+    static ref TIMESTAMP_PROPERTY_METHOD_DESC: SMethodDesc =
+        property("timestamp", SType::SLong, TIMESTAMP_PROPERTY_METHOD_ID);
 }
 
+pub const N_BITS_PROPERTY_METHOD_ID: MethodId = MethodId(8);
 lazy_static! {
-    static ref N_BITS_METHOD: SMethodDesc = property("nBits", SType::SLong, MethodId(8));
+    pub static ref N_BITS_PROPERTY: SMethod =
+        SMethod::new(Header, N_BITS_PROPERTY_METHOD_DESC.clone(),);
+    static ref N_BITS_PROPERTY_METHOD_DESC: SMethodDesc =
+        property("nBits", SType::SLong, N_BITS_PROPERTY_METHOD_ID);
 }
 
+pub const HEIGHT_PROPERTY_METHOD_ID: MethodId = MethodId(9);
 lazy_static! {
-    static ref HEIGHT_METHOD: SMethodDesc = property("height", SType::SInt, MethodId(9));
+    pub static ref HEIGHT_PROPERTY: SMethod =
+        SMethod::new(Header, HEIGHT_PROPERTY_METHOD_DESC.clone(),);
+    static ref HEIGHT_PROPERTY_METHOD_DESC: SMethodDesc =
+        property("height", SType::SInt, HEIGHT_PROPERTY_METHOD_ID);
 }
 
+pub const EXTENSION_ROOT_PROPERTY_METHOD_ID: MethodId = MethodId(10);
 lazy_static! {
-    static ref EXTENSION_ROOT_METHOD: SMethodDesc =
-        property("extensionRoot", SColl(SByte.into()), MethodId(10));
+    pub static ref EXTENSION_ROOT_PROPERTY: SMethod =
+        SMethod::new(Header, EXTENSION_ROOT_PROPERTY_METHOD_DESC.clone(),);
+    static ref EXTENSION_ROOT_PROPERTY_METHOD_DESC: SMethodDesc = property(
+        "extensionRoot",
+        SColl(SByte.into()),
+        EXTENSION_ROOT_PROPERTY_METHOD_ID
+    );
 }
 
+pub const MINER_PK_PROPERTY_METHOD_ID: MethodId = MethodId(11);
 lazy_static! {
-    static ref MINER_PK_METHOD: SMethodDesc =
-        property("minerPk", SType::SGroupElement, MethodId(11));
+    pub static ref MINER_PK_PROPERTY: SMethod =
+        SMethod::new(Header, MINER_PK_PROPERTY_METHOD_DESC.clone(),);
+    static ref MINER_PK_PROPERTY_METHOD_DESC: SMethodDesc =
+        property("minerPk", SType::SGroupElement, MINER_PK_PROPERTY_METHOD_ID);
 }
 
+pub const POW_ONETIME_PK_PROPERTY_METHOD_ID: MethodId = MethodId(12);
 lazy_static! {
-    static ref POW_ONETIME_PK_METHOD: SMethodDesc =
-        property("powOnetimePk", SType::SGroupElement, MethodId(12));
+    pub static ref POW_ONETIME_PK_PROPERTY: SMethod =
+        SMethod::new(Header, POW_ONETIME_PK_PROPERTY_METHOD_DESC.clone(),);
+    static ref POW_ONETIME_PK_PROPERTY_METHOD_DESC: SMethodDesc = property(
+        "powOnetimePk",
+        SType::SGroupElement,
+        POW_ONETIME_PK_PROPERTY_METHOD_ID
+    );
 }
 
+pub const POW_NONCE_PROPERTY_METHOD_ID: MethodId = MethodId(13);
 lazy_static! {
-    static ref POW_NONCE_METHOD: SMethodDesc =
-        property("powNonce", SColl(SByte.into()), MethodId(13));
+    pub static ref POW_NONCE_PROPERTY: SMethod =
+        SMethod::new(Header, POW_NONCE_PROPERTY_METHOD_DESC.clone(),);
+    static ref POW_NONCE_PROPERTY_METHOD_DESC: SMethodDesc = property(
+        "powNonce",
+        SColl(SByte.into()),
+        POW_NONCE_PROPERTY_METHOD_ID
+    );
 }
 
+pub const POW_DISTANCE_PROPERTY_METHOD_ID: MethodId = MethodId(14);
 lazy_static! {
-    static ref POW_DISTANCE_METHOD: SMethodDesc =
-        property("powDistance", SType::SBigInt, MethodId(14));
+    pub static ref POW_DISTANCE_PROPERTY: SMethod =
+        SMethod::new(Header, POW_DISTANCE_PROPERTY_METHOD_DESC.clone(),);
+    static ref POW_DISTANCE_PROPERTY_METHOD_DESC: SMethodDesc = property(
+        "powDistance",
+        SType::SBigInt,
+        POW_DISTANCE_PROPERTY_METHOD_ID
+    );
 }
 
+pub const VOTES_PROPERTY_METHOD_ID: MethodId = MethodId(15);
 lazy_static! {
-    static ref VOTES_METHOD: SMethodDesc = property("votes", SColl(SByte.into()), MethodId(15));
+    pub static ref VOTES_PROPERTY: SMethod =
+        SMethod::new(Header, VOTES_PROPERTY_METHOD_DESC.clone(),);
+    static ref VOTES_PROPERTY_METHOD_DESC: SMethodDesc =
+        property("votes", SColl(SByte.into()), VOTES_PROPERTY_METHOD_ID);
 }
 
 fn property(name: &'static str, res_tpe: SType, id: MethodId) -> SMethodDesc {


Test patch: