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

Issue:
Sigma protocol implementation for DHT



Discussion / hints:

Fix patch:
diff --git a/ergo-lib/src/wallet/signing.rs b/ergo-lib/src/wallet/signing.rs
index 97137415f..33ea41b2d 100644
--- a/ergo-lib/src/wallet/signing.rs
+++ b/ergo-lib/src/wallet/signing.rs
@@ -243,10 +243,9 @@ mod tests {
                 secrets: secrets.clone().into_iter().map(PrivateInput::DlogProverInput).collect(),
             };
             let inputs: Vec<UnsignedInput> = boxes_to_spend.clone().into_iter().map(UnsignedInput::from).collect();
-            let mut rng = thread_rng();
             // boxes_to_spend are in the different order to test inputs <-> boxes_to_spend association in the
             // prover (it should not depend on both of them to be in the same order)
-            boxes_to_spend.shuffle(&mut rng);
+            boxes_to_spend.shuffle(&mut thread_rng());
             let ergo_tree = ErgoTree::try_from(Expr::Const(secrets.get(0).unwrap().public_image().into())).unwrap();
             let candidate = ErgoBoxCandidateBuilder::new(BoxValue::SAFE_USER_MIN, ergo_tree, 0)
                 .build().unwrap();
diff --git a/ergotree-interpreter/src/eval/create_prove_dh_tuple.rs b/ergotree-interpreter/src/eval/create_prove_dh_tuple.rs
index ed10ea5ae..f529f4143 100644
--- a/ergotree-interpreter/src/eval/create_prove_dh_tuple.rs
+++ b/ergotree-interpreter/src/eval/create_prove_dh_tuple.rs
@@ -11,16 +11,10 @@ use crate::eval::Evaluable;
 
 impl Evaluable for CreateProveDhTuple {
     fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
-        let gv = self.gv.eval(env, ctx)?.try_extract_into::<EcPoint>()?;
-        let hv = self.hv.eval(env, ctx)?.try_extract_into::<EcPoint>()?;
-        let uv = self.uv.eval(env, ctx)?.try_extract_into::<EcPoint>()?;
-        let vv = self.vv.eval(env, ctx)?.try_extract_into::<EcPoint>()?;
-        Ok(ProveDhTuple {
-            gv: gv.into(),
-            hv: hv.into(),
-            uv: uv.into(),
-            vv: vv.into(),
-        }
-        .into())
+        let g = self.g.eval(env, ctx)?.try_extract_into::<EcPoint>()?;
+        let h = self.h.eval(env, ctx)?.try_extract_into::<EcPoint>()?;
+        let u = self.u.eval(env, ctx)?.try_extract_into::<EcPoint>()?;
+        let v = self.v.eval(env, ctx)?.try_extract_into::<EcPoint>()?;
+        Ok(ProveDhTuple::new(g, h, u, v).into())
     }
 }
diff --git a/ergotree-interpreter/src/sigma_protocol.rs b/ergotree-interpreter/src/sigma_protocol.rs
index 064664624..e06318068 100644
--- a/ergotree-interpreter/src/sigma_protocol.rs
+++ b/ergotree-interpreter/src/sigma_protocol.rs
@@ -8,6 +8,7 @@ pub mod verifier;
 
 mod challenge;
 mod crypto_utils;
+mod dht_protocol;
 mod dlog_protocol;
 mod fiat_shamir;
 mod proof_tree;
@@ -24,6 +25,7 @@ use unchecked_tree::{UncheckedSigmaTree, UncheckedTree};
 use unproven_tree::{UnprovenLeaf, UnprovenSchnorr};
 
 use self::challenge::Challenge;
+use self::dht_protocol::FirstDhTupleProverMessage;
 use self::unchecked_tree::UncheckedSchnorr;
 
 use derive_more::From;
@@ -41,14 +43,14 @@ pub enum FirstProverMessage {
     /// Discrete log
     FirstDlogProverMessage(FirstDlogProverMessage),
     /// DH tupl
-    FirstDhtProverMessage,
+    FirstDhtProverMessage(FirstDhTupleProverMessage),
 }
 
 impl ProverMessage for FirstProverMessage {
     fn bytes(&self) -> Vec<u8> {
         match self {
             FirstProverMessage::FirstDlogProverMessage(fdpm) => fdpm.bytes(),
-            FirstProverMessage::FirstDhtProverMessage => todo!(),
+            FirstProverMessage::FirstDhtProverMessage(fdhtpm) => fdhtpm.bytes(),
         }
     }
 }
diff --git a/ergotree-interpreter/src/sigma_protocol/crypto_utils.rs b/ergotree-interpreter/src/sigma_protocol/crypto_utils.rs
index f2c6e7e34..6101c275c 100644
--- a/ergotree-interpreter/src/sigma_protocol/crypto_utils.rs
+++ b/ergotree-interpreter/src/sigma_protocol/crypto_utils.rs
@@ -1,8 +1,19 @@
+use rand::RngCore;
+
 /// Generate cryptographically secure random bytes
 pub fn secure_random_bytes(how_many: usize) -> Vec<u8> {
-    use rand::rngs::OsRng;
-    use rand::RngCore;
     let mut bytes: Vec<u8> = vec![0; how_many];
-    OsRng.fill_bytes(&mut bytes);
+    secure_rng().fill_bytes(&mut bytes);
     bytes
 }
+
+/// Returns cryptographically secure PRNG
+/// Uses ThreadRng - https://rust-random.github.io/rand/rand/rngs/struct.ThreadRng.html
+/// which is StgRng (ChaCha block cipher with 12 as of July 2021 -
+/// https://rust-random.github.io/rand/rand/rngs/struct.StdRng.html) seeded from OsRng -
+/// https://rust-random.github.io/rand/rand/rngs/struct.OsRng.html which is a random number
+/// generator that retrieves randomness from the operating system.
+pub fn secure_rng() -> impl RngCore {
+    use rand::thread_rng;
+    thread_rng()
+}
diff --git a/ergotree-interpreter/src/sigma_protocol/dht_protocol.rs b/ergotree-interpreter/src/sigma_protocol/dht_protocol.rs
new file mode 100644
index 000000000..d00937114
--- /dev/null
+++ b/ergotree-interpreter/src/sigma_protocol/dht_protocol.rs
@@ -0,0 +1,140 @@
+//! Discrete logarithm signature protocol
+
+use super::ProverMessage;
+use ergotree_ir::serialization::SigmaSerializable;
+use ergotree_ir::sigma_protocol::dlog_group::EcPoint;
+use k256::Scalar;
+
+/// a = g^r, b = h^r
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct FirstDhTupleProverMessage {
+    a: Box<EcPoint>,
+    b: Box<EcPoint>,
+}
+
+impl FirstDhTupleProverMessage {
+    pub fn new(a: EcPoint, b: EcPoint) -> Self {
+        Self {
+            a: a.into(),
+            b: b.into(),
+        }
+    }
+}
+
+impl ProverMessage for FirstDhTupleProverMessage {
+    #[allow(clippy::unwrap_used)] // since only EcPoint is serialized here it's safe to unwrap
+    fn bytes(&self) -> Vec<u8> {
+        let mut res = self.a.sigma_serialize_bytes().unwrap();
+        res.append(self.b.sigma_serialize_bytes().unwrap().as_mut());
+        res
+    }
+}
+
+//z = r + ew mod q
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct SecondDhTupleProverMessage {
+    pub z: Scalar,
+}
+
+/// Interactive prover
+pub(crate) mod interactive_prover {
+
+    use std::ops::Mul;
+
+    use super::*;
+    use crate::sigma_protocol::crypto_utils;
+    use crate::sigma_protocol::private_input::DhTupleProverInput;
+    use crate::sigma_protocol::Challenge;
+    use ergotree_ir::sigma_protocol::dlog_group;
+    use ergotree_ir::sigma_protocol::sigma_boolean::ProveDhTuple;
+    use k256::Scalar;
+
+    /// Step 5 from https://ergoplatform.org/docs/ErgoScript.pdf
+    /// For every leaf marked “simulated”, use the simulator of the sigma protocol for that leaf
+    /// to compute the commitment "a" and the response "z", given the challenge "e" that
+    /// is already stored in the leaf
+    pub(crate) fn simulate(
+        public_input: &ProveDhTuple,
+        challenge: &Challenge,
+    ) -> (FirstDhTupleProverMessage, SecondDhTupleProverMessage) {
+        //SAMPLE a random z <- Zq
+        let z = dlog_group::random_scalar_in_group_range(crypto_utils::secure_rng());
+
+        // COMPUTE a = g^z*u^(-e) and b = h^z*v^{-e}  (where -e here means -e mod q)
+        let e: Scalar = challenge.clone().into();
+        let minus_e = e.negate();
+        let h_to_z = dlog_group::exponentiate(&public_input.h, &z);
+        let g_to_z = dlog_group::exponentiate(&public_input.g, &z);
+        let u_to_minus_e = dlog_group::exponentiate(&public_input.u, &minus_e);
+        let v_to_minus_e = dlog_group::exponentiate(&public_input.v, &minus_e);
+        let a = g_to_z.mul(&u_to_minus_e);
+        let b = h_to_z.mul(&v_to_minus_e);
+        (
+            FirstDhTupleProverMessage::new(a, b),
+            SecondDhTupleProverMessage { z },
+        )
+    }
+
+    /// Step 6 from https://ergoplatform.org/docs/ErgoScript.pdf
+    /// For every leaf marked “real”, use the first prover step of the sigma protocol for
+    /// that leaf to compute the necessary randomness "r" and the commitment "a"
+    ///
+    /// In this case (DH tuple) "a" is also a tuple
+    pub(crate) fn first_message(
+        public_input: &ProveDhTuple,
+    ) -> (Scalar, FirstDhTupleProverMessage) {
+        let r = dlog_group::random_scalar_in_group_range(crypto_utils::secure_rng());
+        let a = dlog_group::exponentiate(&public_input.g, &r);
+        let b = dlog_group::exponentiate(&public_input.h, &r);
+        (r, FirstDhTupleProverMessage::new(a, b))
+    }
+
+    /// Step 9 part 2 from https://ergoplatform.org/docs/ErgoScript.pdf
+    /// compute its response "z" according to the second prover step(step 5 in whitepaper)
+    /// of the sigma protocol given the randomness "r"(rnd) used for the commitment "a",
+    /// the challenge "e", and witness w.
+    pub(crate) fn second_message(
+        private_input: &DhTupleProverInput,
+        rnd: &Scalar,
+        challenge: &Challenge,
+    ) -> SecondDhTupleProverMessage {
+        let e: Scalar = challenge.clone().into();
+        // modulo multiplication, no need to explicit mod op
+        let ew = e.mul(&private_input.w);
+        // modulo addition, no need to explicit mod op
+        let z = rnd.add(&ew);
+        SecondDhTupleProverMessage { z }
+    }
+
+    /// The function computes initial prover's commitment to randomness
+    /// ("a" message of the sigma-protocol, which in this case has two parts "a" and "b")
+    /// based on the verifier's challenge ("e")
+    /// and prover's response ("z")
+    ///
+    /// g^z = a*u^e, h^z = b*v^e  => a = g^z/u^e, b = h^z/v^e
+    #[allow(clippy::many_single_char_names)]
+    pub(crate) fn compute_commitment(
+        proposition: &ProveDhTuple,
+        challenge: &Challenge,
+        second_message: &SecondDhTupleProverMessage,
+    ) -> (EcPoint, EcPoint) {
+        let g = proposition.g.clone();
+        let h = proposition.h.clone();
+        let u = proposition.u.clone();
+        let v = proposition.v.clone();
+
+        let z = second_message.z;
+
+        let e: Scalar = challenge.clone().into();
+
+        let g_to_z = dlog_group::exponentiate(&g, &z);
+        let h_to_z = dlog_group::exponentiate(&h, &z);
+
+        let u_to_e = dlog_group::exponentiate(&u, &e);
+        let v_to_e = dlog_group::exponentiate(&v, &e);
+
+        let a = g_to_z.mul(&dlog_group::inverse(&u_to_e));
+        let b = h_to_z.mul(&dlog_group::inverse(&v_to_e));
+        (a, b)
+    }
+}
diff --git a/ergotree-interpreter/src/sigma_protocol/dlog_protocol.rs b/ergotree-interpreter/src/sigma_protocol/dlog_protocol.rs
index 3359a3eb6..7b73c0535 100644
--- a/ergotree-interpreter/src/sigma_protocol/dlog_protocol.rs
+++ b/ergotree-interpreter/src/sigma_protocol/dlog_protocol.rs
@@ -7,11 +7,11 @@ use k256::Scalar;
 
 /// First message from the prover (message `a` of `SigmaProtocol`) for discrete logarithm case
 #[derive(PartialEq, Eq, Debug, Clone)]
-pub struct FirstDlogProverMessage(pub(crate) EcPoint);
+pub struct FirstDlogProverMessage(pub(crate) Box<EcPoint>);
 
 impl From<EcPoint> for FirstDlogProverMessage {
     fn from(ecp: EcPoint) -> Self {
-        FirstDlogProverMessage(ecp)
+        FirstDlogProverMessage(ecp.into())
     }
 }
 
@@ -41,18 +41,23 @@ pub(crate) mod interactive_prover {
     use std::ops::Mul;
 
     use super::{FirstDlogProverMessage, SecondDlogProverMessage};
+    use crate::sigma_protocol::crypto_utils;
     use crate::sigma_protocol::{private_input::DlogProverInput, Challenge};
     use ergotree_ir::sigma_protocol::dlog_group;
     use ergotree_ir::sigma_protocol::dlog_group::EcPoint;
     use ergotree_ir::sigma_protocol::sigma_boolean::ProveDlog;
     use k256::Scalar;
 
+    /// Step 5 from https://ergoplatform.org/docs/ErgoScript.pdf
+    /// For every leaf marked “simulated”, use the simulator of the sigma protocol for that leaf
+    /// to compute the commitment "a" and the response "z", given the challenge "e" that
+    /// is already stored in the leaf
     pub(crate) fn simulate(
         public_input: &ProveDlog,
         challenge: &Challenge,
     ) -> (FirstDlogProverMessage, SecondDlogProverMessage) {
         //SAMPLE a random z <- Zq
-        let z = dlog_group::random_scalar_in_group_range();
+        let z = dlog_group::random_scalar_in_group_range(crypto_utils::secure_rng());
 
         //COMPUTE a = g^z*h^(-e)  (where -e here means -e mod q)
         let e: Scalar = challenge.clone().into();
@@ -60,18 +65,26 @@ pub(crate) mod interactive_prover {
         let h_to_e = dlog_group::exponentiate(&public_input.h, &minus_e);
         let g_to_z = dlog_group::exponentiate(&dlog_group::generator(), &z);
         let a = g_to_z * &h_to_e;
-        (FirstDlogProverMessage(a), SecondDlogProverMessage { z })
+        (
+            FirstDlogProverMessage(a.into()),
+            SecondDlogProverMessage { z },
+        )
     }
 
-    /// Create first message from the prover and a randomness
+    /// Step 6 from https://ergoplatform.org/docs/ErgoScript.pdf
+    /// For every leaf marked “real”, use the first prover step of the sigma protocol for
+    /// that leaf to compute the necessary randomness "r" and the commitment "a"
     pub(crate) fn first_message() -> (Scalar, FirstDlogProverMessage) {
-        let r = dlog_group::random_scalar_in_group_range();
+        let r = dlog_group::random_scalar_in_group_range(crypto_utils::secure_rng());
         let g = dlog_group::generator();
         let a = dlog_group::exponentiate(&g, &r);
-        (r, FirstDlogProverMessage(a))
+        (r, FirstDlogProverMessage(a.into()))
     }
 
-    /// Create second message from the prover
+    /// Step 9 part 2 from https://ergoplatform.org/docs/ErgoScript.pdf
+    /// compute its response "z" according to the second prover step(step 5 in whitepaper)
+    /// of the sigma protocol given the randomness "r"(rnd) used for the commitment "a",
+    /// the challenge "e", and witness w.
     pub(crate) fn second_message(
         private_input: &DlogProverInput,
         rnd: Scalar,
@@ -85,13 +98,11 @@ pub(crate) mod interactive_prover {
         z.into()
     }
 
-    /**
-     * The function computes initial prover's commitment to randomness
-     * ("a" message of the sigma-protocol) based on the verifier's challenge ("e")
-     * and prover's response ("z")
-     *
-     * g^z = a*h^e => a = g^z/h^e
-     */
+    /// The function computes initial prover's commitment to randomness
+    /// ("a" message of the sigma-protocol) based on the verifier's challenge ("e")
+    /// and prover's response ("z")
+    ///  
+    /// g^z = a*h^e => a = g^z/h^e
     pub(crate) fn compute_commitment(
         proposition: &ProveDlog,
         challenge: &Challenge,
@@ -126,7 +137,7 @@ mod tests {
             let (r, commitment) = interactive_prover::first_message();
             let second_message = interactive_prover::second_message(&secret, r, &challenge);
             let a = interactive_prover::compute_commitment(&pk, &challenge, &second_message);
-            prop_assert_eq!(a, commitment.0);
+            prop_assert_eq!(a, *commitment.0);
         }
     }
 }
diff --git a/ergotree-interpreter/src/sigma_protocol/private_input.rs b/ergotree-interpreter/src/sigma_protocol/private_input.rs
index a85091e85..b99d156b6 100644
--- a/ergotree-interpreter/src/sigma_protocol/private_input.rs
+++ b/ergotree-interpreter/src/sigma_protocol/private_input.rs
@@ -2,8 +2,10 @@
 use std::convert::TryInto;
 
 use ergotree_ir::sigma_protocol::dlog_group;
+use ergotree_ir::sigma_protocol::sigma_boolean::ProveDhTuple;
 use ergotree_ir::sigma_protocol::sigma_boolean::ProveDlog;
 
+use ergotree_ir::sigma_protocol::sigma_boolean::SigmaBoolean;
 use k256::elliptic_curve::ff::PrimeField;
 use k256::Scalar;
 
@@ -11,6 +13,8 @@ extern crate derive_more;
 use derive_more::From;
 use num_bigint::BigUint;
 
+use super::crypto_utils;
+
 /// Secret key of discrete logarithm signature protocol
 #[derive(PartialEq, Debug, Clone)]
 pub struct DlogProverInput {
@@ -25,7 +29,7 @@ impl DlogProverInput {
     /// generates random secret in the range [0, n), where n is DLog group order.
     pub fn random() -> DlogProverInput {
         DlogProverInput {
-            w: dlog_group::random_scalar_in_group_range(),
+            w: dlog_group::random_scalar_in_group_range(crypto_utils::secure_rng()),
         }
     }
 
@@ -76,18 +80,63 @@ impl From<Scalar> for DlogProverInput {
     }
 }
 
+/// Diffie-Hellman tuple and secret
+/// Used in a proof that of equality of discrete logarithms (i.e., a proof of a Diffie-Hellman tuple):
+/// given group elements g, h, u, v, the proof convinces a verifier that the prover knows `w` such
+/// that `u = g^w` and `v = h^w`, without revealing `w`
+#[derive(PartialEq, Debug, Clone)]
+pub struct DhTupleProverInput {
+    /// Diffie-Hellman tuple's secret
+    pub w: Scalar,
+    /// Diffie-Hellman tuple
+    pub common_input: ProveDhTuple,
+}
+
+impl DhTupleProverInput {
+    /// Create random secret and Diffie-Hellman tuple
+    #[allow(clippy::many_single_char_names)]
+    pub fn random() -> DhTupleProverInput {
+        let g = dlog_group::generator();
+        let h = dlog_group::exponentiate(
+            &dlog_group::generator(),
+            &dlog_group::random_scalar_in_group_range(crypto_utils::secure_rng()),
+        );
+        let w = dlog_group::random_scalar_in_group_range(crypto_utils::secure_rng());
+        let u = dlog_group::exponentiate(&g, &w);
+        let v = dlog_group::exponentiate(&h, &w);
+        let common_input = ProveDhTuple::new(g, h, u, v);
+        DhTupleProverInput { w, common_input }
+    }
+
+    /// Public image (Diffie-Hellman tuple)
+    pub fn public_image(&self) -> &ProveDhTuple {
+        &self.common_input
+    }
+}
+
 /// Private inputs (secrets)
 #[derive(PartialEq, Debug, Clone, From)]
 pub enum PrivateInput {
     /// Discrete logarithm prover input
     DlogProverInput(DlogProverInput),
-    /// DH tuple prover input
-    DiffieHellmanTupleProverInput,
+    /// Diffie-Hellman tuple prover input
+    DhTupleProverInput(DhTupleProverInput),
+}
+
+impl PrivateInput {
+    /// Public image of the private input
+    pub fn public_image(&self) -> SigmaBoolean {
+        match self {
+            PrivateInput::DlogProverInput(dl) => dl.public_image().into(),
+            PrivateInput::DhTupleProverInput(dht) => dht.public_image().clone().into(),
+        }
+    }
 }
 
 #[cfg(feature = "arbitrary")]
 /// Arbitrary impl
-mod arbitrary {
+pub(crate) mod arbitrary {
+
     use super::*;
     use proptest::prelude::*;
 
@@ -105,6 +154,33 @@ mod arbitrary {
             .boxed()
         }
     }
+
+    impl Arbitrary for DhTupleProverInput {
+        type Parameters = ();
+        type Strategy = BoxedStrategy<Self>;
+        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+            prop_oneof![
+                Just(DhTupleProverInput::random()),
+                Just(DhTupleProverInput::random()),
+                Just(DhTupleProverInput::random()),
+                Just(DhTupleProverInput::random()),
+                Just(DhTupleProverInput::random()),
+            ]
+            .boxed()
+        }
+    }
+
+    impl Arbitrary for PrivateInput {
+        type Parameters = ();
+        type Strategy = BoxedStrategy<Self>;
+        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+            prop_oneof![
+                any::<DlogProverInput>().prop_map_into(),
+                any::<DhTupleProverInput>().prop_map_into(),
+            ]
+            .boxed()
+        }
+    }
 }
 
 #[cfg(test)]
diff --git a/ergotree-interpreter/src/sigma_protocol/proof_tree.rs b/ergotree-interpreter/src/sigma_protocol/proof_tree.rs
index c6446e4cb..666d77a5a 100644
--- a/ergotree-interpreter/src/sigma_protocol/proof_tree.rs
+++ b/ergotree-interpreter/src/sigma_protocol/proof_tree.rs
@@ -15,10 +15,12 @@ use crate::sigma_protocol::UnprovenSchnorr;
 use super::challenge::Challenge;
 use super::prover::ProverError;
 use super::unchecked_tree::UncheckedConjecture;
+use super::unchecked_tree::UncheckedDhTuple;
 use super::unchecked_tree::UncheckedSigmaTree;
 use super::unchecked_tree::UncheckedTree;
 use super::unproven_tree::CorUnproven;
 use super::unproven_tree::NodePosition;
+use super::unproven_tree::UnprovenDhTuple;
 use super::unproven_tree::UnprovenLeaf;
 use super::unproven_tree::UnprovenTree;
 use super::FirstProverMessage;
@@ -77,12 +79,24 @@ impl From<UncheckedSchnorr> for ProofTree {
     }
 }
 
+impl From<UncheckedDhTuple> for ProofTree {
+    fn from(v: UncheckedDhTuple) -> Self {
+        UncheckedTree::UncheckedSigmaTree(v.into()).into()
+    }
+}
+
 impl From<UnprovenSchnorr> for ProofTree {
     fn from(v: UnprovenSchnorr) -> Self {
         UnprovenTree::UnprovenLeaf(v.into()).into()
     }
 }
 
+impl From<UnprovenDhTuple> for ProofTree {
+    fn from(v: UnprovenDhTuple) -> Self {
+        UnprovenTree::UnprovenLeaf(v.into()).into()
+    }
+}
+
 impl From<CandUnproven> for ProofTree {
     fn from(v: CandUnproven) -> Self {
         UnprovenTree::UnprovenConjecture(v.into()).into()
diff --git a/ergotree-interpreter/src/sigma_protocol/prover.rs b/ergotree-interpreter/src/sigma_protocol/prover.rs
index 0e9518f56..479748c81 100644
--- a/ergotree-interpreter/src/sigma_protocol/prover.rs
+++ b/ergotree-interpreter/src/sigma_protocol/prover.rs
@@ -5,13 +5,15 @@ mod prover_result;
 
 pub mod hint;
 
+use crate::sigma_protocol::dht_protocol;
 use crate::sigma_protocol::fiat_shamir::fiat_shamir_hash_fn;
 use crate::sigma_protocol::fiat_shamir::fiat_shamir_tree_to_bytes;
 use crate::sigma_protocol::proof_tree::ProofTree;
-use crate::sigma_protocol::unchecked_tree::UncheckedLeaf;
+use crate::sigma_protocol::unchecked_tree::UncheckedDhTuple;
 use crate::sigma_protocol::unproven_tree::CandUnproven;
 use crate::sigma_protocol::unproven_tree::CorUnproven;
 use crate::sigma_protocol::unproven_tree::NodePosition;
+use crate::sigma_protocol::unproven_tree::UnprovenDhTuple;
 use crate::sigma_protocol::Challenge;
 use crate::sigma_protocol::UncheckedSigmaTree;
 use crate::sigma_protocol::UnprovenLeaf;
@@ -41,16 +43,17 @@ use super::unchecked_tree::UncheckedTree;
 use super::unproven_tree::UnprovenConjecture;
 use super::unproven_tree::UnprovenSchnorr;
 use super::unproven_tree::UnprovenTree;
+use super::FirstProverMessage::FirstDhtProverMessage;
+use super::FirstProverMessage::FirstDlogProverMessage;
 
 use crate::eval::context::Context;
 use crate::eval::env::Env;
 use crate::eval::{EvalError, Evaluator};
 
-use derive_more::From;
 use thiserror::Error;
 
 /// Prover errors
-#[derive(Error, PartialEq, Eq, Debug, Clone, From)]
+#[derive(Error, PartialEq, Eq, Debug, Clone)]
 pub enum ProverError {
     /// Failed to parse ErgoTree
     #[error("Ergo tree error: {0}")]
@@ -79,6 +82,21 @@ pub enum ProverError {
     /// Error while tree serialization for Fiat-Shamir hash
     #[error("Fiat-Shamir tree serialization error: {0}")]
     FiatShamirTreeSerializationError(FiatShamirTreeSerializationError),
+    /// Not yet implemented
+    #[error("not yet implemented: {0}")]
+    NotYetImplemented(String),
+}
+
+impl From<ErgoTreeError> for ProverError {
+    fn from(e: ErgoTreeError) -> Self {
+        ProverError::ErgoTreeError(e)
+    }
+}
+
+impl From<FiatShamirTreeSerializationError> for ProverError {
+    fn from(e: FiatShamirTreeSerializationError) -> Self {
+        ProverError::FiatShamirTreeSerializationError(e)
+    }
 }
 
 /// Prover
@@ -136,7 +154,7 @@ fn prove_to_unchecked<P: Prover + ?Sized>(
 ) -> Result<UncheckedSigmaTree, ProverError> {
     // Prover Step 1: Mark as real everything the prover can prove
     let step1 = mark_real(prover, unproven_tree, hints_bag)?;
-    dbg!(&step1);
+    // dbg!(&step1);
 
     // Prover Step 2: If the root of the tree is marked "simulated" then the prover does not have enough witnesses
     // to perform the proof. Abort.
@@ -148,12 +166,12 @@ fn prove_to_unchecked<P: Prover + ?Sized>(
     // has the right number of simulated children.
 
     let step3 = polish_simulated(prover, step1)?;
-    dbg!(&step3);
+    // dbg!(&step3);
 
     // Prover Steps 4, 5, and 6 together: find challenges for simulated nodes; simulate simulated leaves;
     // compute commitments for real leaves
     let step6 = simulate_and_commit(step3, hints_bag)?;
-    dbg!(&step6);
+    // dbg!(&step6);
 
     // Prover Steps 7: convert the relevant information in the tree (namely, tree structure, node types,
     // the statements being proven and commitments at the leaves)
@@ -166,11 +184,11 @@ fn prove_to_unchecked<P: Prover + ?Sized>(
     s.append(&mut message.to_vec());
     let root_challenge: Challenge = fiat_shamir_hash_fn(s.as_slice()).into();
     let step8 = step6.with_challenge(root_challenge);
-    dbg!(&step8);
+    // dbg!(&step8);
 
     // Prover Step 9: complete the proof by computing challenges at real nodes and additionally responses at real leaves
     let step9 = proving(prover, step8.into(), hints_bag)?;
-    dbg!(&step9);
+    // dbg!(&step9);
     // Prover Step 10: output the right information into the proof
     convert_to_unchecked(step9)
 }
@@ -190,28 +208,17 @@ fn mark_real<P: Prover + ?Sized>(
     proof_tree::rewrite(unproven_tree.into(), &|tree| {
         Ok(match tree {
             ProofTree::UnprovenTree(unp) => match unp {
-                UnprovenTree::UnprovenLeaf(unp_leaf) => match unp_leaf {
-                    UnprovenLeaf::UnprovenSchnorr(us) => {
-                        // If the node is a leaf, mark it "real'' if either the witness for it is
-                        // available or a hint shows the secret is known to an external participant in multi-signing;
-                        // else mark it "simulated"
-                        let secret_known =
-                            hints_bag.real_images().contains(&unp_leaf.proposition())
-                                || prover.secrets().iter().any(|s| match s {
-                                    PrivateInput::DlogProverInput(dl) => {
-                                        dl.public_image() == us.proposition
-                                    }
-                                    _ => false,
-                                });
-                        Some(
-                            UnprovenSchnorr {
-                                simulated: !secret_known,
-                                ..us.clone()
-                            }
-                            .into(),
-                        )
-                    }
-                },
+                UnprovenTree::UnprovenLeaf(unp_leaf) => {
+                    // If the node is a leaf, mark it "real'' if either the witness for it is
+                    // available or a hint shows the secret is known to an external participant in multi-signing;
+                    // else mark it "simulated"
+                    let secret_known = hints_bag.real_images().contains(&unp_leaf.proposition())
+                        || prover
+                            .secrets()
+                            .iter()
+                            .any(|s| s.public_image() == unp_leaf.proposition());
+                    Some(unp_leaf.clone().with_simulated(!secret_known).into())
+                }
                 UnprovenTree::UnprovenConjecture(unp_conj) => match unp_conj {
                     UnprovenConjecture::CandUnproven(cand) => {
                         // If the node is AND, mark it "real" if all of its children are marked real; else mark it "simulated"
@@ -535,6 +542,58 @@ fn simulate_and_commit(
                 };
                 Ok(Some(res))
             }
+            ProofTree::UnprovenTree(UnprovenTree::UnprovenLeaf(UnprovenLeaf::UnprovenDhTuple(
+                dhu,
+            ))) => {
+                //Steps 5 & 6: pull out commitment from the hints bag, otherwise, compute the commitment(if the node is real),
+                // or simulate it (if the node is simulated)
+
+                // Step 6 (real leaf -- compute the commitment a or take it from the hints bag)
+                let res: Result<ProofTree, _> = hints_bag
+                    .commitments()
+                    .iter()
+                    .find(|c| c.position() == dhu.position)
+                    .map(|cmt_hint| {
+                        Ok(dhu
+                            .clone()
+                            .with_commitment(match cmt_hint.commitment() {
+                                FirstDlogProverMessage(_) => panic!("not expected here"),
+                                FirstDhtProverMessage(dhtm) => dhtm,
+                            })
+                            .into())
+                    })
+                    .unwrap_or_else(|| {
+                        if dhu.simulated {
+                            // Step 5 (simulated leaf -- complete the simulation)
+                            if let Some(dhu_challenge) = dhu.challenge_opt.clone() {
+                                let (fm, sm) = dht_protocol::interactive_prover::simulate(
+                                    &dhu.proposition,
+                                    &dhu_challenge,
+                                );
+                                Ok(UncheckedDhTuple {
+                                    proposition: dhu.proposition.clone(),
+                                    commitment_opt: Some(fm),
+                                    challenge: dhu_challenge,
+                                    second_message: sm,
+                                }
+                                .into())
+                            } else {
+                                Err(ProverError::SimulatedLeafWithoutChallenge)
+                            }
+                        } else {
+                            // Step 6 -- compute the commitment
+                            let (r, fm) =
+                                dht_protocol::interactive_prover::first_message(&dhu.proposition);
+                            Ok(UnprovenDhTuple {
+                                commitment_opt: Some(fm),
+                                randomness_opt: Some(r),
+                                ..dhu.clone()
+                            }
+                            .into())
+                        }
+                    });
+                Ok(Some(res?))
+            }
             ProofTree::UncheckedTree(_) => Ok(None),
         }
     })?
@@ -661,6 +720,64 @@ fn proving<P: Prover + ?Sized>(
                             Err(ProverError::RealUnprovenTreeWithoutChallenge)
                         }
                     }
+                    UnprovenLeaf::UnprovenDhTuple(dhu) => {
+                        // If the node is a leaf marked "real", compute its response according to the second prover step
+                        // of the Sigma-protocol given the commitment, challenge, and witness, or pull response from
+                        // the hints bag
+                        if let Some(dhu_challenge) = dhu.challenge_opt.clone() {
+                            let priv_key_opt = prover
+                                .secrets()
+                                .iter()
+                                .find(|s| s.public_image() == dhu.proposition.clone().into());
+                            let z = match priv_key_opt {
+                                Some(PrivateInput::DhTupleProverInput(priv_key)) => match hints_bag
+                                    .own_commitments()
+                                    .iter()
+                                    .find(|c| c.position == dhu.position)
+                                {
+                                    Some(commitment_from_hints_bag) => {
+                                        dht_protocol::interactive_prover::second_message(
+                                            priv_key,
+                                            &commitment_from_hints_bag.secret_randomness,
+                                            &dhu_challenge,
+                                        )
+                                    }
+                                    None => dht_protocol::interactive_prover::second_message(
+                                        priv_key,
+                                        &dhu.randomness_opt.ok_or_else(|| {
+                                            ProverError::Unexpected(format!(
+                                                "empty randomness in {:?}",
+                                                dhu
+                                            ))
+                                        })?,
+                                        &dhu_challenge,
+                                    ),
+                                },
+                                Some(pi) => {
+                                    return Err(ProverError::Unexpected(format!(
+                                        "Expected DH prover input in prover secrets, got {:?}",
+                                        pi
+                                    )))
+                                }
+                                None => {
+                                    return Err(ProverError::NotYetImplemented(
+                                        "when secret not found".to_string(),
+                                    ))
+                                }
+                            };
+                            Ok(Some(
+                                UncheckedDhTuple {
+                                    proposition: dhu.proposition.clone(),
+                                    commitment_opt: None,
+                                    challenge: dhu_challenge,
+                                    second_message: z,
+                                }
+                                .into(),
+                            ))
+                        } else {
+                            Err(ProverError::RealUnprovenTreeWithoutChallenge)
+                        }
+                    }
                 },
                 UnprovenTree::UnprovenLeaf(unp_leaf) => {
                     // if the simulated node is proven by someone else, take it from hints bag
@@ -680,7 +797,15 @@ fn proving<P: Prover + ?Sized>(
 fn convert_to_unproven(sb: SigmaBoolean) -> UnprovenTree {
     match sb {
         SigmaBoolean::ProofOfKnowledge(pok) => match pok {
-            SigmaProofOfKnowledgeTree::ProveDhTuple(_) => todo!(),
+            SigmaProofOfKnowledgeTree::ProveDhTuple(pdht) => UnprovenDhTuple {
+                proposition: pdht,
+                commitment_opt: None,
+                randomness_opt: None,
+                challenge_opt: None,
+                simulated: false,
+                position: NodePosition::crypto_tree_prefix(),
+            }
+            .into(),
             SigmaProofOfKnowledgeTree::ProveDlog(prove_dlog) => UnprovenSchnorr {
                 proposition: prove_dlog,
                 commitment_opt: None,
@@ -721,9 +846,7 @@ fn convert_to_unchecked(tree: ProofTree) -> Result<UncheckedSigmaTree, ProverErr
                 "convert_to_unchecked: unexpected NoProof".to_string(),
             )),
             UncheckedTree::UncheckedSigmaTree(ust) => match ust {
-                UncheckedSigmaTree::UncheckedLeaf(ul) => match ul {
-                    UncheckedLeaf::UncheckedSchnorr(_) => Ok(ust.clone()),
-                },
+                UncheckedSigmaTree::UncheckedLeaf(_) => Ok(ust.clone()),
                 UncheckedSigmaTree::UncheckedConjecture(_) => Err(ProverError::Unexpected(
                     format!("convert_to_unchecked: unexpected {:?}", tree),
                 )),
@@ -771,6 +894,7 @@ impl Prover for TestProver {
 #[cfg(test)]
 mod tests {
     use super::*;
+    use crate::sigma_protocol::private_input::DhTupleProverInput;
     use crate::sigma_protocol::private_input::DlogProverInput;
     use ergotree_ir::mir::constant::Constant;
     use ergotree_ir::mir::expr::Expr;
@@ -958,4 +1082,25 @@ mod tests {
         );
         assert_ne!(res.unwrap().proof, ProofBytes::Empty);
     }
+
+    #[test]
+    fn test_prove_dht_prop() {
+        let secret = DhTupleProverInput::random();
+        let pi = secret.public_image();
+        let tree = ErgoTree::try_from(Expr::Const(pi.clone().into())).unwrap();
+        let message = vec![0u8; 100];
+
+        let prover = TestProver {
+            secrets: vec![PrivateInput::DhTupleProverInput(secret)],
+        };
+        let res = prover.prove(
+            &tree,
+            &Env::empty(),
+            Rc::new(force_any_val::<Context>()),
+            message.as_slice(),
+            &HintsBag::empty(),
+        );
+        assert!(res.is_ok());
+        assert_ne!(res.unwrap().proof, ProofBytes::Empty);
+    }
 }
diff --git a/ergotree-interpreter/src/sigma_protocol/prover/hint.rs b/ergotree-interpreter/src/sigma_protocol/prover/hint.rs
index cf1767e12..68fbe0305 100644
--- a/ergotree-interpreter/src/sigma_protocol/prover/hint.rs
+++ b/ergotree-interpreter/src/sigma_protocol/prover/hint.rs
@@ -1,7 +1,7 @@
 //! Hints for a prover which helps the prover to prove a statement.
 
 use ergotree_ir::sigma_protocol::sigma_boolean::SigmaBoolean;
-use num_bigint::BigInt;
+use k256::Scalar;
 
 use crate::sigma_protocol::challenge::Challenge;
 use crate::sigma_protocol::unchecked_tree::UncheckedTree;
@@ -82,23 +82,27 @@ pub struct RealCommitment {
     pub position: NodePosition,
 }
 
+/// A hint which a commitment to randomness associated with a public image of a secret, as well as randomness itself.
+/// Please note that this randomness should be kept in secret by the prover.
+#[derive(PartialEq, Debug, Clone)]
+pub struct OwnCommitment {
+    ///  image of a secret
+    pub image: SigmaBoolean,
+    /// randomness
+    pub secret_randomness: Scalar,
+    /// commitment to randomness used while proving knowledge of the secret
+    pub commitment: FirstProverMessage,
+    /// A hint is related to a subtree (or a leaf) of a tree. This field encodes a position in the tree.
+    pub position: NodePosition,
+}
+
 /// A family of hints which are about a correspondence between a public image of a secret image and prover's commitment
 /// to randomness ("a" in a sigma protocol).
 #[derive(PartialEq, Debug, Clone)]
 pub enum CommitmentHint {
-    /// * A hint which a commitment to randomness associated with a public image of a secret, as well as randomness itself.
-    ///  * Please note that this randomness should be kept in secret by the prover.
-    ///  *
-    OwnCommitment {
-        ///  image of a secret
-        image: SigmaBoolean,
-        /// randomness
-        secret_randomness: BigInt,
-        /// commitment to randomness used while proving knowledge of the secret
-        commitment: FirstProverMessage,
-        /// A hint is related to a subtree (or a leaf) of a tree. This field encodes a position in the tree.
-        position: NodePosition,
-    },
+    /// A hint which a commitment to randomness associated with a public image of a secret, as well as randomness itself.
+    /// Please note that this randomness should be kept in secret by the prover.
+    OwnCommitment(OwnCommitment),
     /// A hint which contains a commitment to randomness associated with a public image of a secret.
     RealCommitment(RealCommitment),
     ///A hint which contains a commitment to randomness associated with a public image of a secret.
@@ -181,6 +185,21 @@ impl HintsBag {
             .collect()
     }
 
+    /// OwnCommitment hints only
+    pub fn own_commitments(&self) -> Vec<OwnCommitment> {
+        self.hints
+            .clone()
+            .into_iter()
+            .filter_map(|hint| {
+                if let Hint::CommitmentHint(CommitmentHint::OwnCommitment(v)) = hint {
+                    Some(v)
+                } else {
+                    None
+                }
+            })
+            .collect()
+    }
+
     /// Images of real secrets + real commitments in the bag
     pub fn real_images(&self) -> Vec<SigmaBoolean> {
         let mut from_proofs: Vec<SigmaBoolean> =
diff --git a/ergotree-interpreter/src/sigma_protocol/sig_serializer.rs b/ergotree-interpreter/src/sigma_protocol/sig_serializer.rs
index 3a8d09fe3..8ee8fcd18 100644
--- a/ergotree-interpreter/src/sigma_protocol/sig_serializer.rs
+++ b/ergotree-interpreter/src/sigma_protocol/sig_serializer.rs
@@ -9,6 +9,8 @@ use super::unchecked_tree::UncheckedSigmaTree;
 use super::unchecked_tree::UncheckedTree;
 use super::GROUP_SIZE;
 use super::SOUNDNESS_BYTES;
+use crate::sigma_protocol::dht_protocol::SecondDhTupleProverMessage;
+use crate::sigma_protocol::unchecked_tree::UncheckedDhTuple;
 use crate::sigma_protocol::Challenge;
 use crate::sigma_protocol::GroupSizedBytes;
 use crate::sigma_protocol::UncheckedSchnorr;
@@ -61,6 +63,10 @@ fn sig_write_bytes<W: SigmaByteWrite>(
                 w.write_all(sm_bytes.as_mut_slice())?;
                 Ok(())
             }
+            UncheckedLeaf::UncheckedDhTuple(dh) => {
+                let mut sm_bytes = dh.second_message.z.to_bytes();
+                w.write_all(sm_bytes.as_mut_slice())
+            }
         },
         UncheckedSigmaTree::UncheckedConjecture(conj) => match conj {
             UncheckedConjecture::CandUnchecked {
@@ -141,7 +147,19 @@ fn parse_sig_compute_challnges_reader<R: SigmaByteRead>(
                 }
                 .into())
             }
-            SigmaProofOfKnowledgeTree::ProveDhTuple(_) => todo!("DHT is not yet supported"),
+            SigmaProofOfKnowledgeTree::ProveDhTuple(dh) => {
+                // Verifier Step 3: For every leaf node, read the response z provided in the proof.
+                let mut scalar_bytes: [u8; super::GROUP_SIZE] = [0; super::GROUP_SIZE];
+                r.read_exact(&mut scalar_bytes)?;
+                let z = Scalar::from(GroupSizedBytes(scalar_bytes.into()));
+                Ok(UncheckedDhTuple {
+                    proposition: dh.clone(),
+                    commitment_opt: None,
+                    challenge,
+                    second_message: SecondDhTupleProverMessage { z },
+                }
+                .into())
+            }
         },
         SigmaBoolean::SigmaConjecture(conj) => match conj {
             SigmaConjecture::Cand(cand) => {
diff --git a/ergotree-interpreter/src/sigma_protocol/unchecked_tree.rs b/ergotree-interpreter/src/sigma_protocol/unchecked_tree.rs
index 04dea8926..2aff7a62a 100644
--- a/ergotree-interpreter/src/sigma_protocol/unchecked_tree.rs
+++ b/ergotree-interpreter/src/sigma_protocol/unchecked_tree.rs
@@ -1,10 +1,13 @@
 //! Unchecked proof tree types
 
+use ergotree_ir::sigma_protocol::sigma_boolean::ProveDhTuple;
 use ergotree_ir::sigma_protocol::sigma_boolean::ProveDlog;
 use ergotree_ir::sigma_protocol::sigma_boolean::SigmaBoolean;
 use ergotree_ir::sigma_protocol::sigma_boolean::SigmaConjectureItems;
 use ergotree_ir::sigma_protocol::sigma_boolean::SigmaProofOfKnowledgeTree;
 
+use super::dht_protocol::FirstDhTupleProverMessage;
+use super::dht_protocol::SecondDhTupleProverMessage;
 use super::proof_tree::ConjectureType;
 use super::proof_tree::ProofTree;
 use super::proof_tree::ProofTreeConjecture;
@@ -55,9 +58,7 @@ impl UncheckedSigmaTree {
     /// Get challenge
     pub(crate) fn challenge(&self) -> Challenge {
         match self {
-            UncheckedSigmaTree::UncheckedLeaf(UncheckedLeaf::UncheckedSchnorr(us)) => {
-                us.challenge.clone()
-            }
+            UncheckedSigmaTree::UncheckedLeaf(ul) => ul.challenge(),
             UncheckedSigmaTree::UncheckedConjecture(uc) => uc.challenge(),
         }
     }
@@ -77,10 +78,20 @@ impl From<UncheckedSchnorr> for UncheckedSigmaTree {
 }
 
 /// Unchecked leaf
-#[derive(PartialEq, Debug, Clone)]
+#[derive(PartialEq, Debug, Clone, From)]
 pub enum UncheckedLeaf {
     /// Unchecked Schnorr
     UncheckedSchnorr(UncheckedSchnorr),
+    UncheckedDhTuple(UncheckedDhTuple),
+}
+
+impl UncheckedLeaf {
+    pub fn challenge(&self) -> Challenge {
+        match self {
+            UncheckedLeaf::UncheckedSchnorr(us) => us.challenge.clone(),
+            UncheckedLeaf::UncheckedDhTuple(udht) => udht.challenge.clone(),
+        }
+    }
 }
 
 impl ProofTreeLeaf for UncheckedLeaf {
@@ -89,21 +100,19 @@ impl ProofTreeLeaf for UncheckedLeaf {
             UncheckedLeaf::UncheckedSchnorr(us) => SigmaBoolean::ProofOfKnowledge(
                 SigmaProofOfKnowledgeTree::ProveDlog(us.proposition.clone()),
             ),
+            UncheckedLeaf::UncheckedDhTuple(dhu) => SigmaBoolean::ProofOfKnowledge(
+                SigmaProofOfKnowledgeTree::ProveDhTuple(dhu.proposition.clone()),
+            ),
         }
     }
     fn commitment_opt(&self) -> Option<FirstProverMessage> {
         match self {
             UncheckedLeaf::UncheckedSchnorr(us) => us.commitment_opt.clone().map(Into::into),
+            UncheckedLeaf::UncheckedDhTuple(udh) => udh.commitment_opt.clone().map(Into::into),
         }
     }
 }
 
-impl From<UncheckedSchnorr> for UncheckedLeaf {
-    fn from(us: UncheckedSchnorr) -> Self {
-        UncheckedLeaf::UncheckedSchnorr(us)
-    }
-}
-
 #[derive(PartialEq, Debug, Clone)]
 pub struct UncheckedSchnorr {
     pub proposition: ProveDlog,
@@ -118,6 +127,26 @@ impl From<UncheckedSchnorr> for UncheckedTree {
     }
 }
 
+impl From<UncheckedDhTuple> for UncheckedSigmaTree {
+    fn from(dh: UncheckedDhTuple) -> Self {
+        UncheckedSigmaTree::UncheckedLeaf(dh.into())
+    }
+}
+
+#[derive(PartialEq, Debug, Clone)]
+pub struct UncheckedDhTuple {
+    pub proposition: ProveDhTuple,
+    pub commitment_opt: Option<FirstDhTupleProverMessage>,
+    pub challenge: Challenge,
+    pub second_message: SecondDhTupleProverMessage,
+}
+
+impl From<UncheckedDhTuple> for UncheckedTree {
+    fn from(dh: UncheckedDhTuple) -> Self {
+        UncheckedTree::UncheckedSigmaTree(dh.into())
+    }
+}
+
 #[derive(PartialEq, Debug, Clone)]
 pub enum UncheckedConjecture {
     CandUnchecked {
diff --git a/ergotree-interpreter/src/sigma_protocol/unproven_tree.rs b/ergotree-interpreter/src/sigma_protocol/unproven_tree.rs
index 5fd8cc020..b4ccef87d 100644
--- a/ergotree-interpreter/src/sigma_protocol/unproven_tree.rs
+++ b/ergotree-interpreter/src/sigma_protocol/unproven_tree.rs
@@ -1,5 +1,6 @@
 //! Unproven tree types
 
+use super::dht_protocol::FirstDhTupleProverMessage;
 use super::proof_tree::ConjectureType;
 use super::proof_tree::ProofTree;
 use super::proof_tree::ProofTreeConjecture;
@@ -8,6 +9,7 @@ use super::{dlog_protocol::FirstDlogProverMessage, Challenge, FirstProverMessage
 use crate::sigma_protocol::proof_tree::ProofTreeLeaf;
 use ergotree_ir::sigma_protocol::sigma_boolean::cand::Cand;
 use ergotree_ir::sigma_protocol::sigma_boolean::cor::Cor;
+use ergotree_ir::sigma_protocol::sigma_boolean::ProveDhTuple;
 use ergotree_ir::sigma_protocol::sigma_boolean::ProveDlog;
 use ergotree_ir::sigma_protocol::sigma_boolean::SigmaBoolean;
 use ergotree_ir::sigma_protocol::sigma_boolean::SigmaConjectureItems;
@@ -32,11 +34,8 @@ impl UnprovenTree {
 
     pub(crate) fn simulated(&self) -> bool {
         match self {
-            UnprovenTree::UnprovenLeaf(UnprovenLeaf::UnprovenSchnorr(us)) => us.simulated,
-            UnprovenTree::UnprovenConjecture(uc) => match uc {
-                UnprovenConjecture::CandUnproven(cand) => cand.simulated,
-                UnprovenConjecture::CorUnproven(cor) => cor.simulated,
-            },
+            UnprovenTree::UnprovenLeaf(ul) => ul.simulated(),
+            UnprovenTree::UnprovenConjecture(uc) => uc.simulated(),
         }
     }
 
@@ -101,47 +100,67 @@ impl From<CorUnproven> for UnprovenTree {
     }
 }
 
+impl From<UnprovenDhTuple> for UnprovenTree {
+    fn from(v: UnprovenDhTuple) -> Self {
+        UnprovenTree::UnprovenLeaf(v.into())
+    }
+}
+
 /// Unproven leaf types
 #[derive(PartialEq, Debug, Clone, From)]
 pub(crate) enum UnprovenLeaf {
     /// Unproven Schnorr
     UnprovenSchnorr(UnprovenSchnorr),
+    UnprovenDhTuple(UnprovenDhTuple),
 }
 
 impl UnprovenLeaf {
-    fn with_position(self, updated: NodePosition) -> Self {
+    pub(crate) fn with_position(self, updated: NodePosition) -> Self {
         match self {
             UnprovenLeaf::UnprovenSchnorr(us) => us.with_position(updated).into(),
+            UnprovenLeaf::UnprovenDhTuple(ut) => ut.with_position(updated).into(),
         }
     }
 
-    fn with_challenge(self, challenge: Challenge) -> Self {
+    pub(crate) fn with_challenge(self, challenge: Challenge) -> Self {
         match self {
             UnprovenLeaf::UnprovenSchnorr(us) => us.with_challenge(challenge).into(),
+            UnprovenLeaf::UnprovenDhTuple(ut) => ut.with_challenge(challenge).into(),
         }
     }
 
-    fn with_simulated(self, simulated: bool) -> Self {
+    pub(crate) fn with_simulated(self, simulated: bool) -> Self {
         match self {
             UnprovenLeaf::UnprovenSchnorr(us) => us.with_simulated(simulated).into(),
+            UnprovenLeaf::UnprovenDhTuple(ut) => ut.with_simulated(simulated).into(),
         }
     }
 
     pub(crate) fn is_real(&self) -> bool {
         match self {
             UnprovenLeaf::UnprovenSchnorr(us) => us.is_real(),
+            UnprovenLeaf::UnprovenDhTuple(ut) => ut.is_real(),
         }
     }
 
     pub(crate) fn challenge(&self) -> Option<Challenge> {
         match self {
             UnprovenLeaf::UnprovenSchnorr(us) => us.challenge_opt.clone(),
+            UnprovenLeaf::UnprovenDhTuple(ut) => ut.challenge_opt.clone(),
         }
     }
 
     pub(crate) fn position(&self) -> &NodePosition {
         match self {
             UnprovenLeaf::UnprovenSchnorr(us) => &us.position,
+            UnprovenLeaf::UnprovenDhTuple(ut) => &ut.position,
+        }
+    }
+
+    pub(crate) fn simulated(&self) -> bool {
+        match self {
+            UnprovenLeaf::UnprovenSchnorr(us) => us.simulated,
+            UnprovenLeaf::UnprovenDhTuple(udht) => udht.simulated,
         }
     }
 }
@@ -152,12 +171,16 @@ impl ProofTreeLeaf for UnprovenLeaf {
             UnprovenLeaf::UnprovenSchnorr(us) => SigmaBoolean::ProofOfKnowledge(
                 SigmaProofOfKnowledgeTree::ProveDlog(us.proposition.clone()),
             ),
+            UnprovenLeaf::UnprovenDhTuple(udht) => SigmaBoolean::ProofOfKnowledge(
+                SigmaProofOfKnowledgeTree::ProveDhTuple(udht.proposition.clone()),
+            ),
         }
     }
 
     fn commitment_opt(&self) -> Option<FirstProverMessage> {
         match self {
             UnprovenLeaf::UnprovenSchnorr(us) => us.commitment_opt.clone().map(Into::into),
+            UnprovenLeaf::UnprovenDhTuple(udht) => udht.commitment_opt.clone().map(Into::into),
         }
     }
 }
@@ -210,6 +233,13 @@ impl UnprovenConjecture {
             UnprovenConjecture::CorUnproven(cor) => cor.with_simulated(simulated).into(),
         }
     }
+
+    fn simulated(&self) -> bool {
+        match self {
+            UnprovenConjecture::CandUnproven(au) => au.simulated,
+            UnprovenConjecture::CorUnproven(ou) => ou.simulated,
+        }
+    }
 }
 
 impl ProofTreeConjecture for UnprovenConjecture {
@@ -262,6 +292,47 @@ impl UnprovenSchnorr {
     }
 }
 
+#[derive(PartialEq, Debug, Clone)]
+pub struct UnprovenDhTuple {
+    pub proposition: ProveDhTuple,
+    pub commitment_opt: Option<FirstDhTupleProverMessage>,
+    pub randomness_opt: Option<Scalar>,
+    pub challenge_opt: Option<Challenge>,
+    pub simulated: bool,
+    pub position: NodePosition,
+}
+
+impl UnprovenDhTuple {
+    fn with_position(self, updated: NodePosition) -> Self {
+        UnprovenDhTuple {
+            position: updated,
+            ..self
+        }
+    }
+
+    fn with_challenge(self, challenge: Challenge) -> Self {
+        UnprovenDhTuple {
+            challenge_opt: Some(challenge),
+            ..self
+        }
+    }
+
+    fn with_simulated(self, simulated: bool) -> Self {
+        UnprovenDhTuple { simulated, ..self }
+    }
+
+    pub fn with_commitment(self, commitment: FirstDhTupleProverMessage) -> Self {
+        Self {
+            commitment_opt: Some(commitment),
+            ..self
+        }
+    }
+
+    pub(crate) fn is_real(&self) -> bool {
+        !self.simulated
+    }
+}
+
 /// Data type which encodes position of a node in a tree.
 ///
 /// Position is encoded like following (the example provided is for CTHRESHOLD(2, Seq(pk1, pk2, pk3 && pk4)) :
diff --git a/ergotree-interpreter/src/sigma_protocol/verifier.rs b/ergotree-interpreter/src/sigma_protocol/verifier.rs
index d608fd66a..cf9ab2c19 100644
--- a/ergotree-interpreter/src/sigma_protocol/verifier.rs
+++ b/ergotree-interpreter/src/sigma_protocol/verifier.rs
@@ -2,9 +2,12 @@
 
 use std::rc::Rc;
 
+use super::dht_protocol;
+use super::dht_protocol::FirstDhTupleProverMessage;
 use super::fiat_shamir::FiatShamirTreeSerializationError;
 use super::prover::ProofBytes;
 use super::sig_serializer::SigParsingError;
+use super::unchecked_tree::UncheckedDhTuple;
 use super::{
     dlog_protocol,
     fiat_shamir::{fiat_shamir_hash_fn, fiat_shamir_tree_to_bytes},
@@ -113,11 +116,23 @@ fn compute_commitments(sp: UncheckedSigmaTree) -> UncheckedSigmaTree {
                     &sn.second_message,
                 );
                 UncheckedSchnorr {
-                    commitment_opt: Some(FirstDlogProverMessage(a)),
+                    commitment_opt: Some(FirstDlogProverMessage(a.into())),
                     ..sn
                 }
                 .into()
             }
+            UncheckedLeaf::UncheckedDhTuple(dh) => {
+                let (a, b) = dht_protocol::interactive_prover::compute_commitment(
+                    &dh.proposition,
+                    &dh.challenge,
+                    &dh.second_message,
+                );
+                UncheckedDhTuple {
+                    commitment_opt: Some(FirstDhTupleProverMessage::new(a, b)),
+                    ..dh
+                }
+                .into()
+            }
         },
         UncheckedSigmaTree::UncheckedConjecture(conj) => conj
             .clone()
@@ -136,24 +151,21 @@ impl Verifier for TestVerifier {}
 #[cfg(test)]
 #[cfg(feature = "arbitrary")]
 mod tests {
-    use super::*;
+    use std::convert::TryFrom;
+
+    use crate::sigma_protocol::private_input::{DhTupleProverInput, DlogProverInput, PrivateInput};
     use crate::sigma_protocol::prover::hint::HintsBag;
-    use crate::sigma_protocol::{
-        private_input::{DlogProverInput, PrivateInput},
-        prover::{Prover, TestProver},
-    };
+    use crate::sigma_protocol::prover::{Prover, TestProver};
+
+    use super::*;
     use ergotree_ir::mir::expr::Expr;
     use ergotree_ir::mir::sigma_and::SigmaAnd;
     use ergotree_ir::mir::sigma_or::SigmaOr;
-    use ergotree_ir::serialization::SigmaSerializable;
-    use num_bigint::BigUint;
     use proptest::collection::vec;
     use proptest::prelude::*;
     use sigma_test_util::force_any_val;
-    use std::convert::{TryFrom, TryInto};
-    use std::rc::Rc;
 
-    fn proof_append_byte(proof: &ProofBytes) -> ProofBytes {
+    fn proof_append_some_byte(proof: &ProofBytes) -> ProofBytes {
         match proof {
             ProofBytes::Empty => panic!(),
             ProofBytes::Some(bytes) => {
@@ -163,10 +175,9 @@ mod tests {
             }
         }
     }
-
     proptest! {
 
-        #![proptest_config(ProptestConfig::with_cases(4))]
+        #![proptest_config(ProptestConfig::with_cases(16))]
 
         #[test]
         fn test_prover_verifier_p2pk(secret in any::<DlogProverInput>(), message in vec(any::<u8>(), 100..200)) {
@@ -195,7 +206,7 @@ mod tests {
             prop_assert_eq!(verifier.verify(&tree,
                                             &Env::empty(),
                                             Rc::new(force_any_val::<Context>()),
-                                            proof_append_byte(&proof),
+                                            proof_append_some_byte(&proof),
                                             message.as_slice())
                             .unwrap().result,
                             true);
@@ -211,8 +222,50 @@ mod tests {
         }
 
         #[test]
-        fn test_prover_verifier_conj_and(secret1 in any::<DlogProverInput>(),
-                                         secret2 in any::<DlogProverInput>(),
+        fn test_prover_verifier_dht(secret in any::<DhTupleProverInput>(), message in vec(any::<u8>(), 100..200)) {
+            let pk = secret.public_image().clone();
+            let tree = ErgoTree::try_from(Expr::Const(pk.into())).unwrap();
+
+            let prover = TestProver {
+                secrets: vec![PrivateInput::DhTupleProverInput(secret)],
+            };
+            let res = prover.prove(&tree,
+                &Env::empty(),
+                Rc::new(force_any_val::<Context>()),
+                message.as_slice(),
+                &HintsBag::empty());
+            let proof = res.unwrap().proof;
+            let verifier = TestVerifier;
+            prop_assert_eq!(verifier.verify(&tree,
+                                            &Env::empty(),
+                                            Rc::new(force_any_val::<Context>()),
+                                            proof.clone(),
+                                            message.as_slice())
+                            .unwrap().result,
+                            true);
+
+            // possible to append bytes
+            prop_assert_eq!(verifier.verify(&tree,
+                                            &Env::empty(),
+                                            Rc::new(force_any_val::<Context>()),
+                                            proof_append_some_byte(&proof),
+                                            message.as_slice())
+                            .unwrap().result,
+                            true);
+
+            // wrong message
+            prop_assert_eq!(verifier.verify(&tree,
+                                            &Env::empty(),
+                                            Rc::new(force_any_val::<Context>()),
+                                            proof,
+                                            vec![1u8; 100].as_slice())
+                            .unwrap().result,
+                            false);
+        }
+
+        #[test]
+        fn test_prover_verifier_conj_and(secret1 in any::<PrivateInput>(),
+                                         secret2 in any::<PrivateInput>(),
                                          message in vec(any::<u8>(), 100..200)) {
             let pk1 = secret1.public_image();
             let pk2 = secret2.public_image();
@@ -221,7 +274,7 @@ mod tests {
                 .into();
             let tree = ErgoTree::try_from(expr).unwrap();
             let prover = TestProver {
-                secrets: vec![PrivateInput::DlogProverInput(secret1), PrivateInput::DlogProverInput(secret2)],
+                secrets: vec![secret1, secret2],
             };
             let res = prover.prove(&tree,
                 &Env::empty(),
@@ -239,9 +292,9 @@ mod tests {
         }
 
         #[test]
-        fn test_prover_verifier_conj_and_and(secret1 in any::<DlogProverInput>(),
-                                             secret2 in any::<DlogProverInput>(),
-                                             secret3 in any::<DlogProverInput>(),
+        fn test_prover_verifier_conj_and_and(secret1 in any::<PrivateInput>(),
+                                             secret2 in any::<PrivateInput>(),
+                                             secret3 in any::<PrivateInput>(),
                                              message in vec(any::<u8>(), 100..200)) {
             let pk1 = secret1.public_image();
             let pk2 = secret2.public_image();
@@ -253,12 +306,7 @@ mod tests {
                     .into(),
             ]).unwrap().into();
             let tree = ErgoTree::try_from(expr).unwrap();
-            let prover = TestProver {
-                secrets: vec![PrivateInput::DlogProverInput(secret1),
-                    PrivateInput::DlogProverInput(secret2),
-                    PrivateInput::DlogProverInput(secret3)
-                ],
-            };
+            let prover = TestProver { secrets: vec![secret1, secret2, secret3] };
             let res = prover.prove(&tree,
                 &Env::empty(),
                 Rc::new(force_any_val::<Context>()),
@@ -275,8 +323,8 @@ mod tests {
         }
 
         #[test]
-        fn test_prover_verifier_conj_or(secret1 in any::<DlogProverInput>(),
-                                         secret2 in any::<DlogProverInput>(),
+        fn test_prover_verifier_conj_or(secret1 in any::<PrivateInput>(),
+                                         secret2 in any::<PrivateInput>(),
                                          message in vec(any::<u8>(), 100..200)) {
             let pk1 = secret1.public_image();
             let pk2 = secret2.public_image();
@@ -284,7 +332,7 @@ mod tests {
                 .unwrap()
                 .into();
             let tree = ErgoTree::try_from(expr).unwrap();
-            let secrets = vec![PrivateInput::DlogProverInput(secret1), PrivateInput::DlogProverInput(secret2)];
+            let secrets = vec![secret1, secret2];
             // any secret (out of 2) known to prover should be enough
             for secret in secrets {
                 let prover = TestProver {
@@ -307,9 +355,9 @@ mod tests {
         }
 
         #[test]
-        fn test_prover_verifier_conj_or_or(secret1 in any::<DlogProverInput>(),
-                                             secret2 in any::<DlogProverInput>(),
-                                             secret3 in any::<DlogProverInput>(),
+        fn test_prover_verifier_conj_or_or(secret1 in any::<PrivateInput>(),
+                                             secret2 in any::<PrivateInput>(),
+                                             secret3 in any::<PrivateInput>(),
                                              message in vec(any::<u8>(), 100..200)) {
             let pk1 = secret1.public_image();
             let pk2 = secret2.public_image();
@@ -321,11 +369,7 @@ mod tests {
                     .into(),
             ]).unwrap().into();
             let tree = ErgoTree::try_from(expr).unwrap();
-            let secrets = vec![
-                PrivateInput::DlogProverInput(secret1),
-                PrivateInput::DlogProverInput(secret2),
-                PrivateInput::DlogProverInput(secret3)
-            ];
+            let secrets = vec![secret1, secret2, secret3];
             // any secret (out of 3) known to prover should be enough
             for secret in secrets {
                 let prover = TestProver {
@@ -347,299 +391,4 @@ mod tests {
             }
         }
     }
-
-    #[test]
-    fn sig_test_vector_provedlog() {
-        // test vector data from
-        // https://github.com/ScorexFoundation/sigmastate-interpreter/blob/6c51c13f7a494a191a7ea5645e56b04fb46a418d/sigmastate/src/test/scala/sigmastate/crypto/SigningSpecification.scala#L14-L30
-        let msg =
-            base16::decode(b"1dc01772ee0171f5f614c673e3c7fa1107a8cf727bdf5a6dadb379e93c0d1d00")
-                .unwrap();
-        let sk = DlogProverInput::from_biguint(
-            BigUint::parse_bytes(
-                b"109749205800194830127901595352600384558037183218698112947062497909408298157746",
-                10,
-            )
-            .unwrap(),
-        )
-        .unwrap();
-        let signature = base16::decode(b"bcb866ba434d5c77869ddcbc3f09ddd62dd2d2539bf99076674d1ae0c32338ea95581fdc18a3b66789904938ac641eba1a66d234070207a2").unwrap();
-
-        // check expected public key
-        assert_eq!(
-            base16::encode_lower(&sk.public_image().sigma_serialize_bytes().unwrap()),
-            "03cb0d49e4eae7e57059a3da8ac52626d26fc11330af8fb093fa597d8b93deb7b1"
-        );
-
-        let expr: Expr = sk.public_image().into();
-        let verifier = TestVerifier;
-        let ver_res = verifier.verify(
-            &expr.try_into().unwrap(),
-            &Env::empty(),
-            Rc::new(force_any_val::<Context>()),
-            signature.into(),
-            msg.as_slice(),
-        );
-        assert!(ver_res.unwrap().result);
-    }
-
-    #[test]
-    fn sig_test_vector_conj_and() {
-        // corresponding sigmastate test
-        // in SigningSpecification.property("AND signature test vector")
-        let msg =
-            base16::decode(b"1dc01772ee0171f5f614c673e3c7fa1107a8cf727bdf5a6dadb379e93c0d1d00")
-                .unwrap();
-        let sk1 = DlogProverInput::from_biguint(
-            BigUint::parse_bytes(
-                b"109749205800194830127901595352600384558037183218698112947062497909408298157746",
-                10,
-            )
-            .unwrap(),
-        )
-        .unwrap();
-        let sk2 = DlogProverInput::from_biguint(
-            BigUint::parse_bytes(
-                b"50415569076448343263191022044468203756975150511337537963383000142821297891310",
-                10,
-            )
-            .unwrap(),
-        )
-        .unwrap();
-
-        let signature = base16::decode(b"9b2ebb226be42df67817e9c56541de061997c3ea84e7e72dbb69edb7318d7bb525f9c16ccb1adc0ede4700a046d0a4ab1e239245460c1ba45e5637f7a2d4cc4cc460e5895125be73a2ca16091db2dcf51d3028043c2b9340").unwrap();
-
-        let expr: Expr = SigmaAnd::new(vec![
-            Expr::Const(sk1.public_image().into()),
-            Expr::Const(sk2.public_image().into()),
-        ])
-        .unwrap()
-        .into();
-        let tree: ErgoTree = expr.try_into().unwrap();
-
-        // let prover = TestProver {
-        //     secrets: vec![sk1.into(), sk2.into()],
-        // };
-        // let res = prover.prove(
-        //     &tree,
-        //     &Env::empty(),
-        //     Rc::new(force_any_val::<Context>()),
-        //     msg.as_slice(),
-        //     &HintsBag::empty(),
-        // );
-        // let proof: Vec<u8> = res.unwrap().proof.into();
-        // dbg!(base16::encode_lower(&proof));
-
-        let verifier = TestVerifier;
-        let ver_res = verifier.verify(
-            &tree,
-            &Env::empty(),
-            Rc::new(force_any_val::<Context>()),
-            signature.into(),
-            msg.as_slice(),
-        );
-        assert!(ver_res.unwrap().result);
-    }
-
-    #[test]
-    fn sig_test_vector_conj_or() {
-        // corresponding sigmastate test
-        // in SigningSpecification.property("OR signature test vector")
-        let msg =
-            base16::decode(b"1dc01772ee0171f5f614c673e3c7fa1107a8cf727bdf5a6dadb379e93c0d1d00")
-                .unwrap();
-        let sk1 = DlogProverInput::from_biguint(
-            BigUint::parse_bytes(
-                b"109749205800194830127901595352600384558037183218698112947062497909408298157746",
-                10,
-            )
-            .unwrap(),
-        )
-        .unwrap();
-        let sk2 = DlogProverInput::from_biguint(
-            BigUint::parse_bytes(
-                b"50415569076448343263191022044468203756975150511337537963383000142821297891310",
-                10,
-            )
-            .unwrap(),
-        )
-        .unwrap();
-
-        let signature = base16::decode(b"ec94d2d5ef0e1e638237f53fd883c339f9771941f70020742a7dc85130aaee535c61321aa1e1367befb500256567b3e6f9c7a3720baa75ba6056305d7595748a93f23f9fc0eb9c1aaabc24acc4197030834d76d3c95ede60c5b59b4b306cd787d010e8217f34677d046646778877c669").unwrap();
-
-        let expr: Expr = SigmaOr::new(vec![
-            Expr::Const(sk1.public_image().into()),
-            Expr::Const(sk2.public_image().into()),
-        ])
-        .unwrap()
-        .into();
-        let tree: ErgoTree = expr.try_into().unwrap();
-
-        // let prover = TestProver {
-        //     secrets: vec![sk1.into()],
-        // };
-        // let res = prover.prove(
-        //     &tree,
-        //     &Env::empty(),
-        //     Rc::new(force_any_val::<Context>()),
-        //     msg.as_slice(),
-        //     &HintsBag::empty(),
-        // );
-        // let proof: Vec<u8> = res.unwrap().proof.into();
-        // dbg!(base16::encode_lower(&proof));
-
-        let verifier = TestVerifier;
-        let ver_res = verifier.verify(
-            &tree,
-            &Env::empty(),
-            Rc::new(force_any_val::<Context>()),
-            signature.into(),
-            msg.as_slice(),
-        );
-        assert!(ver_res.unwrap().result);
-    }
-
-    #[test]
-    fn sig_test_vector_conj_and_or() {
-        // corresponding sigmastate test
-        // in SigningSpecification.property("AND with OR signature test vector")
-        let msg =
-            base16::decode(b"1dc01772ee0171f5f614c673e3c7fa1107a8cf727bdf5a6dadb379e93c0d1d00")
-                .unwrap();
-        let sk1 = DlogProverInput::from_biguint(
-            BigUint::parse_bytes(
-                b"109749205800194830127901595352600384558037183218698112947062497909408298157746",
-                10,
-            )
-            .unwrap(),
-        )
-        .unwrap();
-        let sk2 = DlogProverInput::from_biguint(
-            BigUint::parse_bytes(
-                b"50415569076448343263191022044468203756975150511337537963383000142821297891310",
-                10,
-            )
-            .unwrap(),
-        )
-        .unwrap();
-
-        let sk3 = DlogProverInput::from_biguint(
-            BigUint::parse_bytes(
-                b"34648336872573478681093104997365775365807654884817677358848426648354905397359",
-                10,
-            )
-            .unwrap(),
-        )
-        .unwrap();
-
-        let signature = base16::decode(b"397e005d85c161990d0e44853fbf14951ff76e393fe1939bb48f68e852cd5af028f6c7eaaed587f6d5435891a564d8f9a77288773ce5b526a670ab0278aa4278891db53a9842df6fba69f95f6d55cfe77dd7b4bdccc1a3378ac4524b51598cb813258f64c94e98c3ef891a6eb8cbfd2e527a9038ca50b5bb50058de55a859a169628e6ae5ba4cb0332c694e450782d6f").unwrap();
-
-        let expr: Expr = SigmaAnd::new(vec![
-            Expr::Const(sk1.public_image().into()),
-            SigmaOr::new(vec![
-                Expr::Const(sk2.public_image().into()),
-                Expr::Const(sk3.public_image().into()),
-            ])
-            .unwrap()
-            .into(),
-        ])
-        .unwrap()
-        .into();
-        let tree: ErgoTree = expr.try_into().unwrap();
-
-        // let prover = TestProver {
-        //     secrets: vec![sk1.into(), sk2.into()],
-        // };
-        // let res = prover.prove(
-        //     &tree,
-        //     &Env::empty(),
-        //     Rc::new(force_any_val::<Context>()),
-        //     msg.as_slice(),
-        //     &HintsBag::empty(),
-        // );
-        // let proof: Vec<u8> = res.unwrap().proof.into();
-        // dbg!(base16::encode_lower(&proof));
-
-        let verifier = TestVerifier;
-        let ver_res = verifier.verify(
-            &tree,
-            &Env::empty(),
-            Rc::new(force_any_val::<Context>()),
-            signature.into(),
-            msg.as_slice(),
-        );
-        assert!(ver_res.unwrap().result);
-    }
-
-    #[test]
-    fn sig_test_vector_conj_or_and() {
-        // corresponding sigmastate test
-        // in SigningSpecification.property("OR with AND signature test vector")
-        let msg =
-            base16::decode(b"1dc01772ee0171f5f614c673e3c7fa1107a8cf727bdf5a6dadb379e93c0d1d00")
-                .unwrap();
-        let sk1 = DlogProverInput::from_biguint(
-            BigUint::parse_bytes(
-                b"109749205800194830127901595352600384558037183218698112947062497909408298157746",
-                10,
-            )
-            .unwrap(),
-        )
-        .unwrap();
-        let sk2 = DlogProverInput::from_biguint(
-            BigUint::parse_bytes(
-                b"50415569076448343263191022044468203756975150511337537963383000142821297891310",
-                10,
-            )
-            .unwrap(),
-        )
-        .unwrap();
-
-        let sk3 = DlogProverInput::from_biguint(
-            BigUint::parse_bytes(
-                b"34648336872573478681093104997365775365807654884817677358848426648354905397359",
-                10,
-            )
-            .unwrap(),
-        )
-        .unwrap();
-
-        let signature = base16::decode(b"a58b251be319a9656c21876b1136a59f42b18835dec6076c92f7a925ba28d2030218c177ab07563003eff5250cfafeb631ef610f4d710ab8e821bf632203adf23f4376580eaa17ddb36c0138f73a88551f45d92cde2b66dfbb5906c02e4d48106ff08be4a2fc29ec242f495468692f9ddeeb029dc5d8f38e2649cf09c44b67cbcfb3de4202026fb84d23ce2b4ff0f69b").unwrap();
-
-        let expr: Expr = SigmaOr::new(vec![
-            Expr::Const(sk1.public_image().into()),
-            SigmaAnd::new(vec![
-                Expr::Const(sk2.public_image().into()),
-                Expr::Const(sk3.public_image().into()),
-            ])
-            .unwrap()
-            .into(),
-        ])
-        .unwrap()
-        .into();
-        let tree: ErgoTree = expr.try_into().unwrap();
-
-        // let prover = TestProver {
-        //     secrets: vec![sk1.into(), sk2.into()],
-        // };
-        // let res = prover.prove(
-        //     &tree,
-        //     &Env::empty(),
-        //     Rc::new(force_any_val::<Context>()),
-        //     msg.as_slice(),
-        //     &HintsBag::empty(),
-        // );
-        // let proof: Vec<u8> = res.unwrap().proof.into();
-        // dbg!(base16::encode_lower(&proof));
-
-        let verifier = TestVerifier;
-        let ver_res = verifier.verify(
-            &tree,
-            &Env::empty(),
-            Rc::new(force_any_val::<Context>()),
-            signature.into(),
-            msg.as_slice(),
-        );
-        assert!(ver_res.unwrap().result);
-    }
 }
diff --git a/ergotree-ir/src/bigint256.rs b/ergotree-ir/src/bigint256.rs
index a67404d80..dc8ef64b2 100644
--- a/ergotree-ir/src/bigint256.rs
+++ b/ergotree-ir/src/bigint256.rs
@@ -230,6 +230,7 @@ impl fmt::Display for BigInt256 {
     }
 }
 
+#[allow(clippy::unwrap_used)]
 #[cfg(test)]
 mod tests {
     use super::*;
diff --git a/ergotree-ir/src/mir/constant.rs b/ergotree-ir/src/mir/constant.rs
index 9a2db2404..a580fe48e 100644
--- a/ergotree-ir/src/mir/constant.rs
+++ b/ergotree-ir/src/mir/constant.rs
@@ -2,9 +2,9 @@
 
 use crate::bigint256::BigInt256;
 use crate::ir_ergo_box::IrBoxId;
-use crate::sigma_protocol::sigma_boolean::ProveDlog;
 use crate::sigma_protocol::sigma_boolean::SigmaBoolean;
 use crate::sigma_protocol::sigma_boolean::SigmaProofOfKnowledgeTree;
+use crate::sigma_protocol::sigma_boolean::{ProveDhTuple, ProveDlog};
 use crate::sigma_protocol::{dlog_group::EcPoint, sigma_boolean::SigmaProp};
 use crate::types::stype::LiftIntoSType;
 use crate::types::stype::SType;
@@ -149,6 +149,20 @@ impl From<ProveDlog> for Constant {
     }
 }
 
+impl From<ProveDhTuple> for Constant {
+    fn from(dht: ProveDhTuple) -> Self {
+        Constant::from(SigmaProp::from(SigmaBoolean::from(
+            SigmaProofOfKnowledgeTree::from(dht),
+        )))
+    }
+}
+
+impl From<SigmaBoolean> for Constant {
+    fn from(sb: SigmaBoolean) -> Self {
+        Constant::from(SigmaProp::from(sb))
+    }
+}
+
 impl From<BigInt256> for Constant {
     fn from(b: BigInt256) -> Self {
         Constant {
diff --git a/ergotree-ir/src/mir/create_prove_dh_tuple.rs b/ergotree-ir/src/mir/create_prove_dh_tuple.rs
index a08b399bd..c2a20167d 100644
--- a/ergotree-ir/src/mir/create_prove_dh_tuple.rs
+++ b/ergotree-ir/src/mir/create_prove_dh_tuple.rs
@@ -14,27 +14,27 @@ use crate::mir::expr::InvalidArgumentError;
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct CreateProveDhTuple {
     /// Group generator `g`
-    pub gv: Box<Expr>,
+    pub g: Box<Expr>,
     /// Point `g^x`
-    pub hv: Box<Expr>,
+    pub h: Box<Expr>,
     /// Point `g^y`
-    pub uv: Box<Expr>,
+    pub u: Box<Expr>,
     /// Point `g^xy`
-    pub vv: Box<Expr>,
+    pub v: Box<Expr>,
 }
 
 impl CreateProveDhTuple {
     /// Create ProveDHTuple from four points on elliptic curve
-    pub fn new(gv: Expr, hv: Expr, uv: Expr, vv: Expr) -> Result<Self, InvalidArgumentError> {
-        gv.check_post_eval_tpe(&SType::SGroupElement)?;
-        hv.check_post_eval_tpe(&SType::SGroupElement)?;
-        uv.check_post_eval_tpe(&SType::SGroupElement)?;
-        vv.check_post_eval_tpe(&SType::SGroupElement)?;
+    pub fn new(g: Expr, h: Expr, u: Expr, v: Expr) -> Result<Self, InvalidArgumentError> {
+        g.check_post_eval_tpe(&SType::SGroupElement)?;
+        h.check_post_eval_tpe(&SType::SGroupElement)?;
+        u.check_post_eval_tpe(&SType::SGroupElement)?;
+        v.check_post_eval_tpe(&SType::SGroupElement)?;
         Ok(CreateProveDhTuple {
-            gv: gv.into(),
-            hv: hv.into(),
-            uv: uv.into(),
-            vv: vv.into(),
+            g: g.into(),
+            h: h.into(),
+            u: u.into(),
+            v: v.into(),
         })
     }
 
@@ -50,18 +50,19 @@ impl HasStaticOpCode for CreateProveDhTuple {
 
 impl SigmaSerializable for CreateProveDhTuple {
     fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> SigmaSerializeResult {
-        self.gv.sigma_serialize(w)?;
-        self.hv.sigma_serialize(w)?;
-        self.uv.sigma_serialize(w)?;
-        self.vv.sigma_serialize(w)
+        self.g.sigma_serialize(w)?;
+        self.h.sigma_serialize(w)?;
+        self.u.sigma_serialize(w)?;
+        self.v.sigma_serialize(w)
     }
 
+    #[allow(clippy::many_single_char_names)]
     fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SigmaParsingError> {
-        let gv = Expr::sigma_parse(r)?.into();
-        let hv = Expr::sigma_parse(r)?.into();
-        let uv = Expr::sigma_parse(r)?.into();
-        let vv = Expr::sigma_parse(r)?.into();
-        Ok(CreateProveDhTuple { gv, hv, uv, vv })
+        let g = Expr::sigma_parse(r)?.into();
+        let h = Expr::sigma_parse(r)?.into();
+        let u = Expr::sigma_parse(r)?.into();
+        let v = Expr::sigma_parse(r)?.into();
+        Ok(CreateProveDhTuple { g, h, u, v })
     }
 }
 
diff --git a/ergotree-ir/src/serialization/sigmaboolean.rs b/ergotree-ir/src/serialization/sigmaboolean.rs
index b4039af85..fb1e3e72a 100644
--- a/ergotree-ir/src/serialization/sigmaboolean.rs
+++ b/ergotree-ir/src/serialization/sigmaboolean.rs
@@ -4,6 +4,7 @@ use crate::has_opcode::{HasOpCode, HasStaticOpCode};
 use crate::serialization::{
     sigma_byte_reader::SigmaByteRead, SigmaParsingError, SigmaSerializable,
 };
+use crate::sigma_protocol::sigma_boolean::ProveDhTuple;
 use crate::sigma_protocol::{
     dlog_group::EcPoint,
     sigma_boolean::{ProveDlog, SigmaBoolean, SigmaConjecture, SigmaProofOfKnowledgeTree},
@@ -17,7 +18,7 @@ impl SigmaSerializable for SigmaBoolean {
         self.op_code().sigma_serialize(w)?;
         match self {
             SigmaBoolean::ProofOfKnowledge(proof) => match proof {
-                SigmaProofOfKnowledgeTree::ProveDhTuple { .. } => todo!(),
+                SigmaProofOfKnowledgeTree::ProveDhTuple(v) => v.sigma_serialize(w),
                 SigmaProofOfKnowledgeTree::ProveDlog(v) => v.sigma_serialize(w),
             },
             SigmaBoolean::SigmaConjecture(conj) => match conj {
@@ -56,3 +57,21 @@ impl SigmaSerializable for ProveDlog {
         Ok(ProveDlog::new(p))
     }
 }
+
+impl SigmaSerializable for ProveDhTuple {
+    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> SigmaSerializeResult {
+        self.g.sigma_serialize(w)?;
+        self.h.sigma_serialize(w)?;
+        self.u.sigma_serialize(w)?;
+        self.v.sigma_serialize(w)
+    }
+
+    #[allow(clippy::many_single_char_names)]
+    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SigmaParsingError> {
+        let g = EcPoint::sigma_parse(r)?;
+        let h = EcPoint::sigma_parse(r)?;
+        let u = EcPoint::sigma_parse(r)?;
+        let v = EcPoint::sigma_parse(r)?;
+        Ok(ProveDhTuple::new(g, h, u, v))
+    }
+}
diff --git a/ergotree-ir/src/sigma_protocol/dlog_group.rs b/ergotree-ir/src/sigma_protocol/dlog_group.rs
index eeeb9f9b0..42b1db247 100644
--- a/ergotree-ir/src/sigma_protocol/dlog_group.rs
+++ b/ergotree-ir/src/sigma_protocol/dlog_group.rs
@@ -25,6 +25,7 @@ use k256::elliptic_curve::ff::PrimeField;
 use k256::elliptic_curve::sec1::ToEncodedPoint;
 use k256::{ProjectivePoint, PublicKey, Scalar};
 use num_bigint::Sign;
+use rand::RngCore;
 use std::convert::TryFrom;
 use std::ops::{Add, Mul, Neg};
 
@@ -111,9 +112,9 @@ pub fn exponentiate(base: &EcPoint, exponent: &Scalar) -> EcPoint {
 // }
 
 /// Creates a random scalar, a big-endian integer in the range [0, n), where n is group order
-pub fn random_scalar_in_group_range() -> Scalar {
-    use rand::rngs::OsRng;
-    Scalar::generate_vartime(&mut OsRng)
+/// Use cryptographically secure PRNG (like rand::thread_rng())
+pub fn random_scalar_in_group_range(rng: impl RngCore) -> Scalar {
+    Scalar::generate_vartime(rng)
 }
 
 /// Attempts to create BigInt256 from Scalar
@@ -201,6 +202,7 @@ mod tests {
     use crate::mir::expr::Expr;
     use crate::serialization::sigma_serialize_roundtrip;
     use proptest::prelude::*;
+    use rand::thread_rng;
 
     proptest! {
 
@@ -215,7 +217,7 @@ mod tests {
     fn scalar_bigint256_roundtrip() {
         // Shift right to make sure that the MSB is 0, so that the Scalar can be
         // converted to a BigInt256 and back
-        let rand_scalar: Scalar = random_scalar_in_group_range() >> 1;
+        let rand_scalar: Scalar = random_scalar_in_group_range(thread_rng()) >> 1;
         let as_bigint256: BigInt256 = scalar_to_bigint256(rand_scalar).unwrap();
         assert_eq!(rand_scalar, bigint256_to_scalar(as_bigint256).unwrap());
     }
diff --git a/ergotree-ir/src/sigma_protocol/sigma_boolean.rs b/ergotree-ir/src/sigma_protocol/sigma_boolean.rs
index 554644e5f..65a88ee3d 100644
--- a/ergotree-ir/src/sigma_protocol/sigma_boolean.rs
+++ b/ergotree-ir/src/sigma_protocol/sigma_boolean.rs
@@ -54,17 +54,31 @@ impl From<EcPoint> for ProveDlog {
 }
 
 /// Construct a new SigmaProp value representing public key of Diffie Hellman signature protocol.
-/// Common input: (g,h,u,v)
+/// Used in a proof that of equality of discrete logarithms (i.e., a proof of a Diffie-Hellman tuple):
+/// given group elements g, h, u, v, the proof convinces a verifier that the prover knows `w` such
+/// that `u = g^w` and `v = h^w`, without revealing `w`
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct ProveDhTuple {
-    /// Generator `g`
-    pub gv: Box<EcPoint>,
-    /// Point `g^x`
-    pub hv: Box<EcPoint>,
-    /// Point `g^y`
-    pub uv: Box<EcPoint>,
-    /// Point `g^xy`
-    pub vv: Box<EcPoint>,
+    /// Generator g
+    pub g: Box<EcPoint>,
+    /// Point h
+    pub h: Box<EcPoint>,
+    /// Point `u = g^w`
+    pub u: Box<EcPoint>,
+    /// Point `v= h^w`
+    pub v: Box<EcPoint>,
+}
+
+impl ProveDhTuple {
+    /// Create new instance
+    pub fn new(g: EcPoint, h: EcPoint, u: EcPoint, v: EcPoint) -> Self {
+        Self {
+            g: g.into(),
+            h: h.into(),
+            u: u.into(),
+            v: v.into(),
+        }
+    }
 }
 
 impl HasStaticOpCode for ProveDhTuple {
@@ -159,6 +173,12 @@ impl From<ProveDlog> for SigmaBoolean {
     }
 }
 
+impl From<ProveDhTuple> for SigmaBoolean {
+    fn from(v: ProveDhTuple) -> Self {
+        SigmaBoolean::ProofOfKnowledge(SigmaProofOfKnowledgeTree::ProveDhTuple(v))
+    }
+}
+
 /// Proposition which can be proven and verified by sigma protocol.
 #[derive(PartialEq, Eq, Debug, Clone, From, Into)]
 pub struct SigmaProp(SigmaBoolean);
diff --git a/ergotree-ir/src/types/stype.rs b/ergotree-ir/src/types/stype.rs
index a6e45fa45..bed936aa4 100644
--- a/ergotree-ir/src/types/stype.rs
+++ b/ergotree-ir/src/types/stype.rs
@@ -9,10 +9,10 @@ use crate::bigint256::BigInt256;
 use crate::ir_ergo_box::IrBoxId;
 use crate::serialization::types::TypeCode;
 use crate::sigma_protocol::dlog_group::EcPoint;
-use crate::sigma_protocol::sigma_boolean::ProveDlog;
 use crate::sigma_protocol::sigma_boolean::SigmaBoolean;
 use crate::sigma_protocol::sigma_boolean::SigmaProofOfKnowledgeTree;
 use crate::sigma_protocol::sigma_boolean::SigmaProp;
+use crate::sigma_protocol::sigma_boolean::{ProveDhTuple, ProveDlog};
 
 use super::sfunc::SFunc;
 use super::stuple::STuple;
@@ -234,6 +234,12 @@ impl LiftIntoSType for BigInt256 {
     }
 }
 
+impl LiftIntoSType for ProveDhTuple {
+    fn stype() -> SType {
+        SType::SSigmaProp
+    }
+}
+
 impl<T: LiftIntoSType> LiftIntoSType for Option<T> {
     fn stype() -> SType {
         SType::SOption(Box::new(T::stype()))


Test patch:
diff --git a/ergotree-interpreter/proptest-regressions/sigma_protocol/verifier.txt b/ergotree-interpreter/proptest-regressions/sigma_protocol/verifier.txt
index bd39e49b1..6ce40488a 100644
--- a/ergotree-interpreter/proptest-regressions/sigma_protocol/verifier.txt
+++ b/ergotree-interpreter/proptest-regressions/sigma_protocol/verifier.txt
@@ -5,3 +5,4 @@
 # It is recommended to check this file in to source control so that
 # everyone who runs the test benefits from these saved cases.
 cc 175fd85df7535191e7a49ae8a3022e65a15120777adfa667511e8fd2a573ac2a # shrinks to secret = DlogProverInput { w: Scalar(Scalar4x64([2258665393118672676, 9815237481816509392, 12439591203699167698, 12195836321791745360])) }, message = [0]
+cc 6fd4e0cc0a4e4e9b4478c43b356accccc8190b6235d2ff8147ced0f17afc3785 # shrinks to secret1 = DlogProverInput(DlogProverInput { w: Scalar(Scalar4x64([8729212856231099292, 15205776798175679083, 931619618947783025, 6687857181593795265])) }), secret2 = DhTupleProverInput(DhTupleProverInput { w: Scalar(Scalar4x64([17294205895357277411, 10424853634279397406, 10710604382444081590, 11336043161607917226])), common_input: ProveDhTuple { g: EcPoint(ProjectivePoint { x: FieldElement(FieldElementImpl { value: FieldElement5x52([705178180786072, 3855836460717471, 4089131105950716, 3301581525494108, 133858670344668]), magnitude: 1, normalized: true }), y: FieldElement(FieldElementImpl { value: FieldElement5x52([2199641648059576, 1278080618437060, 3959378566518708, 3455034269351872, 79417610544803]), magnitude: 1, normalized: true }), z: FieldElement(FieldElementImpl { value: FieldElement5x52([1, 0, 0, 0, 0]), magnitude: 1, normalized: true }) }), h: EcPoint(ProjectivePoint { x: FieldElement(FieldElementImpl { value: FieldElement5x52([4023451815676881, 2134989306649576, 1948253536574613, 2152470188295901, 94630842765162]), magnitude: 1, normalized: false }), y: FieldElement(FieldElementImpl { value: FieldElement5x52([580079010928260, 1636551620063633, 3342509785399552, 2147480699504368, 223387887901914]), magnitude: 1, normalized: false }), z: FieldElement(FieldElementImpl { value: FieldElement5x52([3678813560612563, 1038239037471339, 2104630055860111, 3232225254902193, 142279304490882]), magnitude: 1, normalized: false }) }), u: EcPoint(ProjectivePoint { x: FieldElement(FieldElementImpl { value: FieldElement5x52([140327686343556, 2760184292484087, 1541381732555986, 3324185108904864, 192389774491127]), magnitude: 1, normalized: false }), y: FieldElement(FieldElementImpl { value: FieldElement5x52([418055432015546, 1330318749333152, 118274046162664, 4126915615277224, 15859331606023]), magnitude: 1, normalized: false }), z: FieldElement(FieldElementImpl { value: FieldElement5x52([2130003540966198, 3017539121191338, 1631629566507388, 3227289700005264, 84358770389813]), magnitude: 1, normalized: false }) }), v: EcPoint(ProjectivePoint { x: FieldElement(FieldElementImpl { value: FieldElement5x52([1611118380588549, 3526468670787636, 4472304735385974, 645786166274043, 212779557845278]), magnitude: 1, normalized: false }), y: FieldElement(FieldElementImpl { value: FieldElement5x52([1634114500560185, 2128337618580234, 223304008877611, 2856291254087702, 14932975111918]), magnitude: 1, normalized: false }), z: FieldElement(FieldElementImpl { value: FieldElement5x52([320981120213713, 418816762428117, 1489904715018123, 3341176115337990, 28115051803871]), magnitude: 1, normalized: false }) }) } }), message = [0, 0, 0, 0, 0, 0, 0, 71, 58, 227, 118, 193, 115, 8, 210, 198, 19, 137, 116, 246, 230, 205, 197, 98, 135, 142, 50, 22, 222, 71, 157, 215, 37, 85, 150, 61, 252, 31, 240, 190, 50, 138, 99, 210, 150, 228, 248, 25, 187, 39, 185, 78, 97, 150, 223, 135, 128, 153, 7, 6, 175, 31, 253, 135, 198, 207, 132, 138, 101, 88, 92, 146, 108, 215, 254, 118, 171, 86, 89, 151, 14, 137, 127, 242, 80, 132, 20, 34, 45, 124, 188, 106, 149, 69, 84, 191, 106, 45, 67, 177]
diff --git a/ergotree-interpreter/tests/signing_spec_tests.rs b/ergotree-interpreter/tests/signing_spec_tests.rs
new file mode 100644
index 000000000..ef7f35e0b
--- /dev/null
+++ b/ergotree-interpreter/tests/signing_spec_tests.rs
@@ -0,0 +1,390 @@
+use ergotree_interpreter::eval::context::Context;
+use ergotree_interpreter::eval::env::Env;
+use ergotree_interpreter::sigma_protocol::private_input::DlogProverInput;
+use ergotree_interpreter::sigma_protocol::verifier::{TestVerifier, Verifier};
+use ergotree_ir::ergo_tree::ErgoTree;
+use ergotree_ir::mir::expr::Expr;
+use ergotree_ir::mir::sigma_and::SigmaAnd;
+use ergotree_ir::mir::sigma_or::SigmaOr;
+use ergotree_ir::serialization::SigmaSerializable;
+use ergotree_ir::sigma_protocol::sigma_boolean::ProveDhTuple;
+use num_bigint::BigUint;
+use sigma_test_util::force_any_val;
+use std::convert::TryInto;
+use std::rc::Rc;
+
+#[test]
+fn sig_test_vector_provedlog() {
+    // test vector data from
+    // https://github.com/ScorexFoundation/sigmastate-interpreter/blob/6c51c13f7a494a191a7ea5645e56b04fb46a418d/sigmastate/src/test/scala/sigmastate/crypto/SigningSpecification.scala#L14-L30
+    let msg = base16::decode(b"1dc01772ee0171f5f614c673e3c7fa1107a8cf727bdf5a6dadb379e93c0d1d00")
+        .unwrap();
+    let sk = DlogProverInput::from_biguint(
+        BigUint::parse_bytes(
+            b"109749205800194830127901595352600384558037183218698112947062497909408298157746",
+            10,
+        )
+        .unwrap(),
+    )
+    .unwrap();
+    let signature = base16::decode(b"bcb866ba434d5c77869ddcbc3f09ddd62dd2d2539bf99076674d1ae0c32338ea95581fdc18a3b66789904938ac641eba1a66d234070207a2").unwrap();
+
+    // check expected public key
+    assert_eq!(
+        base16::encode_lower(&sk.public_image().sigma_serialize_bytes().unwrap()),
+        "03cb0d49e4eae7e57059a3da8ac52626d26fc11330af8fb093fa597d8b93deb7b1"
+    );
+
+    let expr: Expr = sk.public_image().into();
+    let verifier = TestVerifier;
+    let ver_res = verifier.verify(
+        &expr.try_into().unwrap(),
+        &Env::empty(),
+        Rc::new(force_any_val::<Context>()),
+        signature.into(),
+        msg.as_slice(),
+    );
+    assert!(ver_res.unwrap().result);
+}
+
+#[test]
+fn sig_test_vector_prove_dht() {
+    // corresponding sigmastate test
+    // in SigningSpecification.property("ProveDHT signature test vector")
+    let msg = base16::decode(b"1dc01772ee0171f5f614c673e3c7fa1107a8cf727bdf5a6dadb379e93c0d1d00")
+        .unwrap();
+    let pdht = ProveDhTuple::sigma_parse_bytes(&base16::decode(b"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817980280c66feee88d56e47bf3f47c4109d9218c60c373a472a0d9537507c7ee828c4802a96f19e97df31606183c1719400682d1d40b1ce50c9a1ed1b19845e2b1b551bf0255ac02191cb229891fb1b674ea9df7fc8426350131d821fc4a53f29c3b1cb21a").unwrap()).unwrap();
+    // let pdht = random_pdht_input.public_image().clone();
+    dbg!(base16::encode_lower(&pdht.sigma_serialize_bytes().unwrap()));
+    let signature = base16::decode(b"eba93a69b28cfdea261e9ea8914fca9a0b3868d50ce68c94f32e875730f8ca361bd3783c5d3e25802e54f49bd4fb9fafe51f4e8aafbf9815").unwrap();
+    let expr: Expr = pdht.into();
+
+    // let random_pdht_input = DhTupleProverInput::random();
+    // let tree: ErgoTree = expr.clone().into();
+    // let prover = TestProver {
+    //     secrets: vec![random_pdht_input.into()],
+    // };
+    // let res = prover.prove(
+    //     &tree,
+    //     &Env::empty(),
+    //     Rc::new(force_any_val::<Context>()),
+    //     msg.as_slice(),
+    //     &HintsBag::empty(),
+    // );
+    // let proof: Vec<u8> = res.unwrap().proof.into();
+    // dbg!(base16::encode_lower(&proof));
+
+    let verifier = TestVerifier;
+    let ver_res = verifier.verify(
+        &expr.try_into().unwrap(),
+        &Env::empty(),
+        Rc::new(force_any_val::<Context>()),
+        signature.into(),
+        msg.as_slice(),
+    );
+    assert!(ver_res.unwrap().result);
+}
+
+#[test]
+fn sig_test_vector_conj_and() {
+    // corresponding sigmastate test
+    // in SigningSpecification.property("AND signature test vector")
+    let msg = base16::decode(b"1dc01772ee0171f5f614c673e3c7fa1107a8cf727bdf5a6dadb379e93c0d1d00")
+        .unwrap();
+    let sk1 = DlogProverInput::from_biguint(
+        BigUint::parse_bytes(
+            b"109749205800194830127901595352600384558037183218698112947062497909408298157746",
+            10,
+        )
+        .unwrap(),
+    )
+    .unwrap();
+    let sk2 = DlogProverInput::from_biguint(
+        BigUint::parse_bytes(
+            b"50415569076448343263191022044468203756975150511337537963383000142821297891310",
+            10,
+        )
+        .unwrap(),
+    )
+    .unwrap();
+
+    let signature = base16::decode(b"9b2ebb226be42df67817e9c56541de061997c3ea84e7e72dbb69edb7318d7bb525f9c16ccb1adc0ede4700a046d0a4ab1e239245460c1ba45e5637f7a2d4cc4cc460e5895125be73a2ca16091db2dcf51d3028043c2b9340").unwrap();
+
+    let expr: Expr = SigmaAnd::new(vec![
+        Expr::Const(sk1.public_image().into()),
+        Expr::Const(sk2.public_image().into()),
+    ])
+    .unwrap()
+    .into();
+    let tree: ErgoTree = expr.try_into().unwrap();
+
+    // let prover = TestProver {
+    //     secrets: vec![sk1.into(), sk2.into()],
+    // };
+    // let res = prover.prove(
+    //     &tree,
+    //     &Env::empty(),
+    //     Rc::new(force_any_val::<Context>()),
+    //     msg.as_slice(),
+    //     &HintsBag::empty(),
+    // );
+    // let proof: Vec<u8> = res.unwrap().proof.into();
+    // dbg!(base16::encode_lower(&proof));
+
+    let verifier = TestVerifier;
+    let ver_res = verifier.verify(
+        &tree,
+        &Env::empty(),
+        Rc::new(force_any_val::<Context>()),
+        signature.into(),
+        msg.as_slice(),
+    );
+    assert!(ver_res.unwrap().result);
+}
+
+#[test]
+fn sig_test_vector_conj_or() {
+    // corresponding sigmastate test
+    // in SigningSpecification.property("OR signature test vector")
+    let msg = base16::decode(b"1dc01772ee0171f5f614c673e3c7fa1107a8cf727bdf5a6dadb379e93c0d1d00")
+        .unwrap();
+    let sk1 = DlogProverInput::from_biguint(
+        BigUint::parse_bytes(
+            b"109749205800194830127901595352600384558037183218698112947062497909408298157746",
+            10,
+        )
+        .unwrap(),
+    )
+    .unwrap();
+    let sk2 = DlogProverInput::from_biguint(
+        BigUint::parse_bytes(
+            b"50415569076448343263191022044468203756975150511337537963383000142821297891310",
+            10,
+        )
+        .unwrap(),
+    )
+    .unwrap();
+
+    let signature = base16::decode(b"ec94d2d5ef0e1e638237f53fd883c339f9771941f70020742a7dc85130aaee535c61321aa1e1367befb500256567b3e6f9c7a3720baa75ba6056305d7595748a93f23f9fc0eb9c1aaabc24acc4197030834d76d3c95ede60c5b59b4b306cd787d010e8217f34677d046646778877c669").unwrap();
+
+    let expr: Expr = SigmaOr::new(vec![
+        Expr::Const(sk1.public_image().into()),
+        Expr::Const(sk2.public_image().into()),
+    ])
+    .unwrap()
+    .into();
+    let tree: ErgoTree = expr.try_into().unwrap();
+
+    // let prover = TestProver {
+    //     secrets: vec![sk1.into()],
+    // };
+    // let res = prover.prove(
+    //     &tree,
+    //     &Env::empty(),
+    //     Rc::new(force_any_val::<Context>()),
+    //     msg.as_slice(),
+    //     &HintsBag::empty(),
+    // );
+    // let proof: Vec<u8> = res.unwrap().proof.into();
+    // dbg!(base16::encode_lower(&proof));
+
+    let verifier = TestVerifier;
+    let ver_res = verifier.verify(
+        &tree,
+        &Env::empty(),
+        Rc::new(force_any_val::<Context>()),
+        signature.into(),
+        msg.as_slice(),
+    );
+    assert!(ver_res.unwrap().result);
+}
+
+#[test]
+fn sig_test_vector_conj_or_prove_dht() {
+    // corresponding sigmastate test
+    // in SigningSpecification.property("OR with ProveDHT signature test vector")
+    let msg = base16::decode(b"1dc01772ee0171f5f614c673e3c7fa1107a8cf727bdf5a6dadb379e93c0d1d00")
+        .unwrap();
+    let sk1 = DlogProverInput::from_biguint(
+        BigUint::parse_bytes(
+            b"109749205800194830127901595352600384558037183218698112947062497909408298157746",
+            10,
+        )
+        .unwrap(),
+    )
+    .unwrap();
+    let pdht = ProveDhTuple::sigma_parse_bytes(&base16::decode(b"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817980214487635ebffa60b13a166bd0721c5f0ab603fc74168d7764d7ec5ef2107f5d40334c5b7efa5a4a22b83d102d2e6521eaa660fa911c5a213af63c8460f2327513b026a0be2a277291d42daad3830cb16a4ef20e4f1f7c36384f3fee065f0f143a355").unwrap()).unwrap();
+    // let random_pdht_input = DhTupleProverInput::random();
+    // let pdht = random_pdht_input.public_image().clone();
+    // dbg!(base16::encode_lower(&pdht.sigma_serialize_bytes()));
+    let signature = base16::decode(b"a80daebdcd57874296f49fd9910ddaefbf517ca076b6e16b97678e96a20239978836e7ec5b795cf3a55616d394f07c004f85e0d3e71880d4734b57ea874c7eba724e8887280f1affadaad962ee916b39207af2d2ab2a69a2e6f4d652f7389cc4f582bbe6d7937c59aa64cf2965a8b36a").unwrap();
+    let expr: Expr = SigmaOr::new(vec![Expr::Const(sk1.public_image().into()), pdht.into()])
+        .unwrap()
+        .into();
+
+    // let tree: ErgoTree = expr.clone().into();
+    // let prover = TestProver {
+    //     secrets: vec![random_pdht_input.into()],
+    // };
+    // let res = prover.prove(
+    //     &tree,
+    //     &Env::empty(),
+    //     Rc::new(force_any_val::<Context>()),
+    //     msg.as_slice(),
+    //     &HintsBag::empty(),
+    // );
+    // let proof: Vec<u8> = res.unwrap().proof.into();
+    // dbg!(base16::encode_lower(&proof));
+
+    let verifier = TestVerifier;
+    let ver_res = verifier.verify(
+        &expr.try_into().unwrap(),
+        &Env::empty(),
+        Rc::new(force_any_val::<Context>()),
+        signature.into(),
+        msg.as_slice(),
+    );
+    assert!(ver_res.unwrap().result);
+}
+
+#[test]
+fn sig_test_vector_conj_and_or() {
+    // corresponding sigmastate test
+    // in SigningSpecification.property("AND with OR signature test vector")
+    let msg = base16::decode(b"1dc01772ee0171f5f614c673e3c7fa1107a8cf727bdf5a6dadb379e93c0d1d00")
+        .unwrap();
+    let sk1 = DlogProverInput::from_biguint(
+        BigUint::parse_bytes(
+            b"109749205800194830127901595352600384558037183218698112947062497909408298157746",
+            10,
+        )
+        .unwrap(),
+    )
+    .unwrap();
+    let sk2 = DlogProverInput::from_biguint(
+        BigUint::parse_bytes(
+            b"50415569076448343263191022044468203756975150511337537963383000142821297891310",
+            10,
+        )
+        .unwrap(),
+    )
+    .unwrap();
+
+    let sk3 = DlogProverInput::from_biguint(
+        BigUint::parse_bytes(
+            b"34648336872573478681093104997365775365807654884817677358848426648354905397359",
+            10,
+        )
+        .unwrap(),
+    )
+    .unwrap();
+
+    let signature = base16::decode(b"397e005d85c161990d0e44853fbf14951ff76e393fe1939bb48f68e852cd5af028f6c7eaaed587f6d5435891a564d8f9a77288773ce5b526a670ab0278aa4278891db53a9842df6fba69f95f6d55cfe77dd7b4bdccc1a3378ac4524b51598cb813258f64c94e98c3ef891a6eb8cbfd2e527a9038ca50b5bb50058de55a859a169628e6ae5ba4cb0332c694e450782d6f").unwrap();
+
+    let expr: Expr = SigmaAnd::new(vec![
+        Expr::Const(sk1.public_image().into()),
+        SigmaOr::new(vec![
+            Expr::Const(sk2.public_image().into()),
+            Expr::Const(sk3.public_image().into()),
+        ])
+        .unwrap()
+        .into(),
+    ])
+    .unwrap()
+    .into();
+    let tree: ErgoTree = expr.try_into().unwrap();
+
+    // let prover = TestProver {
+    //     secrets: vec![sk1.into(), sk2.into()],
+    // };
+    // let res = prover.prove(
+    //     &tree,
+    //     &Env::empty(),
+    //     Rc::new(force_any_val::<Context>()),
+    //     msg.as_slice(),
+    //     &HintsBag::empty(),
+    // );
+    // let proof: Vec<u8> = res.unwrap().proof.into();
+    // dbg!(base16::encode_lower(&proof));
+
+    let verifier = TestVerifier;
+    let ver_res = verifier.verify(
+        &tree,
+        &Env::empty(),
+        Rc::new(force_any_val::<Context>()),
+        signature.into(),
+        msg.as_slice(),
+    );
+    assert!(ver_res.unwrap().result);
+}
+
+#[test]
+fn sig_test_vector_conj_or_and() {
+    // corresponding sigmastate test
+    // in SigningSpecification.property("OR with AND signature test vector")
+    let msg = base16::decode(b"1dc01772ee0171f5f614c673e3c7fa1107a8cf727bdf5a6dadb379e93c0d1d00")
+        .unwrap();
+    let sk1 = DlogProverInput::from_biguint(
+        BigUint::parse_bytes(
+            b"109749205800194830127901595352600384558037183218698112947062497909408298157746",
+            10,
+        )
+        .unwrap(),
+    )
+    .unwrap();
+    let sk2 = DlogProverInput::from_biguint(
+        BigUint::parse_bytes(
+            b"50415569076448343263191022044468203756975150511337537963383000142821297891310",
+            10,
+        )
+        .unwrap(),
+    )
+    .unwrap();
+
+    let sk3 = DlogProverInput::from_biguint(
+        BigUint::parse_bytes(
+            b"34648336872573478681093104997365775365807654884817677358848426648354905397359",
+            10,
+        )
+        .unwrap(),
+    )
+    .unwrap();
+
+    let signature = base16::decode(b"a58b251be319a9656c21876b1136a59f42b18835dec6076c92f7a925ba28d2030218c177ab07563003eff5250cfafeb631ef610f4d710ab8e821bf632203adf23f4376580eaa17ddb36c0138f73a88551f45d92cde2b66dfbb5906c02e4d48106ff08be4a2fc29ec242f495468692f9ddeeb029dc5d8f38e2649cf09c44b67cbcfb3de4202026fb84d23ce2b4ff0f69b").unwrap();
+
+    let expr: Expr = SigmaOr::new(vec![
+        Expr::Const(sk1.public_image().into()),
+        SigmaAnd::new(vec![
+            Expr::Const(sk2.public_image().into()),
+            Expr::Const(sk3.public_image().into()),
+        ])
+        .unwrap()
+        .into(),
+    ])
+    .unwrap()
+    .into();
+    let tree: ErgoTree = expr.try_into().unwrap();
+
+    // let prover = TestProver {
+    //     secrets: vec![sk1.into(), sk2.into()],
+    // };
+    // let res = prover.prove(
+    //     &tree,
+    //     &Env::empty(),
+    //     Rc::new(force_any_val::<Context>()),
+    //     msg.as_slice(),
+    //     &HintsBag::empty(),
+    // );
+    // let proof: Vec<u8> = res.unwrap().proof.into();
+    // dbg!(base16::encode_lower(&proof));
+
+    let verifier = TestVerifier;
+    let ver_res = verifier.verify(
+        &tree,
+        &Env::empty(),
+        Rc::new(force_any_val::<Context>()),
+        signature.into(),
+        msg.as_slice(),
+    );
+    assert!(ver_res.unwrap().result);
+}