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

Issue:
Implement ReducedTransaction from EIP-19
https://github.com/ergoplatform/eips/pull/36


Discussion / hints:

Fix patch:
diff --git a/bindings/ergo-lib-wasm/src/error_conversion.rs b/bindings/ergo-lib-wasm/src/error_conversion.rs
index f814c4c74..7f5a0de6b 100644
--- a/bindings/ergo-lib-wasm/src/error_conversion.rs
+++ b/bindings/ergo-lib-wasm/src/error_conversion.rs
@@ -8,6 +8,7 @@ use ergo_lib::ergotree_ir::chain::address::AddressError;
 use ergo_lib::ergotree_ir::chain::digest32::Digest32Error;
 use ergo_lib::ergotree_ir::chain::ergo_box::box_value::BoxValueError;
 use ergo_lib::ergotree_ir::chain::token::TokenAmountError;
+use ergo_lib::wallet::signing::TxSigningError;
 use ergo_lib::{
     chain::ergo_box::box_builder::ErgoBoxCandidateBuilderError,
     ergotree_ir::{
@@ -52,6 +53,7 @@ from_error_to_wrap!(Error);
 from_error_to_wrap!(BoxValueError);
 from_error_to_wrap!(TokenAmountError);
 from_error_to_wrap!(TxBuilderError);
+from_error_to_wrap!(TxSigningError);
 from_error_to_wrap!(WalletError);
 from_error_to_wrap!(DecodeError);
 
diff --git a/bindings/ergo-lib-wasm/src/transaction.rs b/bindings/ergo-lib-wasm/src/transaction.rs
index 199f7b073..25911eeee 100644
--- a/bindings/ergo-lib-wasm/src/transaction.rs
+++ b/bindings/ergo-lib-wasm/src/transaction.rs
@@ -20,6 +20,8 @@ use wasm_bindgen::prelude::*;
 extern crate derive_more;
 use derive_more::{From, Into};
 
+pub mod reduced;
+
 /// Transaction id
 #[wasm_bindgen]
 #[derive(PartialEq, Eq, Debug, Clone, From, Into)]
diff --git a/bindings/ergo-lib-wasm/src/transaction/reduced.rs b/bindings/ergo-lib-wasm/src/transaction/reduced.rs
new file mode 100644
index 000000000..c7ce81be4
--- /dev/null
+++ b/bindings/ergo-lib-wasm/src/transaction/reduced.rs
@@ -0,0 +1,71 @@
+//! Represent `reduced` transaction, i.e. unsigned transaction where each unsigned input
+//! is augmented with ReducedInput which contains a script reduction result.
+
+use super::UnsignedTransaction;
+use crate::box_coll::ErgoBoxes;
+use crate::ergo_state_ctx::ErgoStateContext;
+use crate::error_conversion::to_js;
+
+use ergo_lib::chain::transaction::reduced::reduce_tx;
+use ergo_lib::ergotree_ir::serialization::SigmaSerializable;
+use wasm_bindgen::prelude::*;
+
+/// Represent `reduced` transaction, i.e. unsigned transaction where each unsigned input
+/// is augmented with ReducedInput which contains a script reduction result.
+/// After an unsigned transaction is reduced it can be signed without context.
+/// Thus, it can be serialized and transferred for example to Cold Wallet and signed
+/// in an environment where secrets are known.
+/// see EIP-19 for more details -
+/// <https://github.com/ergoplatform/eips/blob/f280890a4163f2f2e988a0091c078e36912fc531/eip-0019.md>
+#[wasm_bindgen]
+#[derive(PartialEq, Debug, Clone)]
+pub struct ReducedTransaction(ergo_lib::chain::transaction::reduced::ReducedTransaction);
+
+#[wasm_bindgen]
+impl ReducedTransaction {
+    /// Returns `reduced` transaction, i.e. unsigned transaction where each unsigned input
+    /// is augmented with ReducedInput which contains a script reduction result.
+    pub fn from_unsigned_tx(
+        unsigned_tx: &UnsignedTransaction,
+        boxes_to_spend: &ErgoBoxes,
+        data_boxes: &ErgoBoxes,
+        state_context: &ErgoStateContext,
+    ) -> Result<ReducedTransaction, JsValue> {
+        let boxes_to_spend: Vec<ergo_lib::ergotree_ir::chain::ergo_box::ErgoBox> =
+            boxes_to_spend.clone().into();
+        let data_boxes: Vec<ergo_lib::ergotree_ir::chain::ergo_box::ErgoBox> =
+            data_boxes.clone().into();
+        let tx_context = ergo_lib::wallet::signing::TransactionContext {
+            spending_tx: unsigned_tx.clone().into(),
+            boxes_to_spend,
+            data_boxes,
+        };
+        reduce_tx(tx_context, &state_context.clone().into())
+            .map_err(to_js)
+            .map(ReducedTransaction::from)
+    }
+
+    /// Returns serialized bytes or fails with error if cannot be serialized
+    pub fn sigma_serialize_bytes(&self) -> Result<Vec<u8>, JsValue> {
+        self.0.sigma_serialize_bytes().map_err(to_js)
+    }
+
+    /// Parses ReducedTransaction or fails with error
+    pub fn sigma_parse_bytes(data: Vec<u8>) -> Result<ReducedTransaction, JsValue> {
+        ergo_lib::chain::transaction::reduced::ReducedTransaction::sigma_parse_bytes(&data)
+            .map(ReducedTransaction)
+            .map_err(to_js)
+    }
+}
+
+impl From<ergo_lib::chain::transaction::reduced::ReducedTransaction> for ReducedTransaction {
+    fn from(t: ergo_lib::chain::transaction::reduced::ReducedTransaction) -> Self {
+        ReducedTransaction(t)
+    }
+}
+
+impl From<ReducedTransaction> for ergo_lib::chain::transaction::reduced::ReducedTransaction {
+    fn from(t: ReducedTransaction) -> Self {
+        t.0
+    }
+}
diff --git a/bindings/ergo-lib-wasm/src/wallet.rs b/bindings/ergo-lib-wasm/src/wallet.rs
index b3cac4092..f86754d79 100644
--- a/bindings/ergo-lib-wasm/src/wallet.rs
+++ b/bindings/ergo-lib-wasm/src/wallet.rs
@@ -4,7 +4,8 @@ use wasm_bindgen::prelude::*;
 
 use crate::{
     box_coll::ErgoBoxes, ergo_state_ctx::ErgoStateContext, error_conversion::to_js,
-    secret_key::SecretKeys, transaction::Transaction, transaction::UnsignedTransaction,
+    secret_key::SecretKeys, transaction::reduced::ReducedTransaction, transaction::Transaction,
+    transaction::UnsignedTransaction,
 };
 
 /// A collection of secret keys. This simplified signing by matching the secret keys to the correct inputs automatically.
@@ -49,4 +50,18 @@ impl Wallet {
             .map_err(to_js)
             .map(Transaction::from)
     }
+
+    /// Sign a transaction:
+    /// `reduced_tx` - reduced transaction, i.e. unsigned transaction where for each unsigned input
+    /// added a script reduction result.
+    #[wasm_bindgen]
+    pub fn sign_reduced_transaction(
+        &self,
+        reduced_tx: &ReducedTransaction,
+    ) -> Result<Transaction, JsValue> {
+        self.0
+            .sign_reduced_transaction(reduced_tx.clone().into())
+            .map_err(to_js)
+            .map(Transaction::from)
+    }
 }
diff --git a/ergo-lib/src/chain/transaction.rs b/ergo-lib/src/chain/transaction.rs
index 21fd09bed..539db7249 100644
--- a/ergo-lib/src/chain/transaction.rs
+++ b/ergo-lib/src/chain/transaction.rs
@@ -2,6 +2,7 @@
 
 mod data_input;
 pub mod input;
+pub mod reduced;
 pub mod unsigned;
 
 use bounded_vec::BoundedVec;
diff --git a/ergo-lib/src/chain/transaction/reduced.rs b/ergo-lib/src/chain/transaction/reduced.rs
new file mode 100644
index 000000000..adaf4eb01
--- /dev/null
+++ b/ergo-lib/src/chain/transaction/reduced.rs
@@ -0,0 +1,176 @@
+//! Represent `reduced` transaction, i.e. unsigned transaction where each unsigned input
+//! is augmented with ReducedInput which contains a script reduction result.
+
+use std::rc::Rc;
+
+use ergotree_interpreter::eval::env::Env;
+use ergotree_interpreter::eval::reduce_to_crypto;
+use ergotree_interpreter::eval::ReductionResult;
+use ergotree_interpreter::sigma_protocol::prover::ContextExtension;
+use ergotree_interpreter::sigma_protocol::prover::ProverError;
+use ergotree_ir::serialization::sigma_byte_reader::SigmaByteRead;
+use ergotree_ir::serialization::sigma_byte_writer::SigmaByteWrite;
+use ergotree_ir::serialization::SigmaParsingError;
+use ergotree_ir::serialization::SigmaSerializable;
+use ergotree_ir::serialization::SigmaSerializeResult;
+use ergotree_ir::sigma_protocol::sigma_boolean::SigmaBoolean;
+
+use crate::chain::ergo_state_context::ErgoStateContext;
+use crate::chain::transaction::Transaction;
+use crate::chain::transaction::UnsignedInput;
+use crate::wallet::signing::make_context;
+use crate::wallet::signing::TransactionContext;
+use crate::wallet::signing::TxSigningError;
+
+use super::unsigned::UnsignedTransaction;
+use super::TxIoVec;
+
+/// Input box script reduced to SigmaBoolean
+/// see EIP-19 for more details -
+/// <https://github.com/ergoplatform/eips/blob/f280890a4163f2f2e988a0091c078e36912fc531/eip-0019.md>
+#[derive(PartialEq, Debug, Clone)]
+pub struct ReducedInput {
+    /// Input box script reduced to SigmaBoolean
+    pub reduction_result: ReductionResult,
+    /// ContextExtension for the input
+    pub extension: ContextExtension,
+}
+
+/// Represent `reduced` transaction, i.e. unsigned transaction where each unsigned input
+/// is augmented with ReducedInput which contains a script reduction result.
+/// After an unsigned transaction is reduced it can be signed without context.
+/// Thus, it can be serialized and transferred for example to Cold Wallet and signed
+/// in an environment where secrets are known.
+/// see EIP-19 for more details -
+/// <https://github.com/ergoplatform/eips/blob/f280890a4163f2f2e988a0091c078e36912fc531/eip-0019.md>
+/// Reference Scala implementation -
+/// <https://github.com/ergoplatform/ergo-appkit/blob/1b7347caa863ecb0b9ba49ae57b090d1f386c906/common/src/main/java/org/ergoplatform/appkit/AppkitProvingInterpreter.scala#L261-L266>
+#[derive(PartialEq, Debug, Clone)]
+pub struct ReducedTransaction {
+    /// Unsigned transation
+    pub unsigned_tx: UnsignedTransaction,
+    /// Reduction result for each unsigned tx input
+    reduced_inputs: TxIoVec<ReducedInput>,
+}
+
+impl ReducedTransaction {
+    /// Returns reduction results for each unsigned tx input
+    pub fn reduced_inputs(&self) -> TxIoVec<ReducedInput> {
+        self.reduced_inputs.clone()
+    }
+}
+
+/// Reduce each input of unsigned transaction to sigma proposition
+pub fn reduce_tx(
+    tx_context: TransactionContext,
+    state_context: &ErgoStateContext,
+) -> Result<ReducedTransaction, TxSigningError> {
+    let tx = &tx_context.spending_tx;
+    let reduced_inputs = tx.inputs.clone().enumerated().try_mapped(|(idx, input)| {
+        if let Some(input_box) = tx_context
+            .boxes_to_spend
+            .iter()
+            .find(|b| b.box_id() == input.box_id)
+        {
+            let ctx = Rc::new(make_context(state_context, &tx_context, idx)?);
+            let expr = input_box
+                .ergo_tree
+                .proposition()
+                .map_err(ProverError::ErgoTreeError)
+                .map_err(|e| TxSigningError::ProverError(e, idx))?;
+            let reduction_result = reduce_to_crypto(&expr, &Env::empty(), ctx)
+                .map_err(ProverError::EvalError)
+                .map_err(|e| TxSigningError::ProverError(e, idx))?;
+            Ok(ReducedInput {
+                reduction_result,
+                extension: input.extension,
+            })
+        } else {
+            Err(TxSigningError::InputBoxNotFound(idx))
+        }
+    })?;
+    Ok(ReducedTransaction {
+        unsigned_tx: tx.clone(),
+        reduced_inputs,
+    })
+}
+
+impl SigmaSerializable for ReducedTransaction {
+    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> SigmaSerializeResult {
+        let msg = self.unsigned_tx.bytes_to_sign()?;
+        w.put_usize_as_u32_unwrapped(msg.len())?;
+        w.write_all(&msg)?;
+        self.reduced_inputs.as_vec().iter().try_for_each(|red_in| {
+            red_in.reduction_result.sigma_prop.sigma_serialize(w)?;
+            Ok(w.put_u64(red_in.reduction_result.cost)?)
+        })
+    }
+
+    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SigmaParsingError> {
+        let bytes_len = r.get_u32()?;
+        let mut buf = vec![0u8; bytes_len as usize];
+        r.read_exact(buf.as_mut_slice())?;
+        let tx = Transaction::sigma_parse_bytes(&buf)?;
+        let input_pairs: TxIoVec<(ReducedInput, UnsignedInput)> =
+            tx.inputs.try_mapped::<_, _, SigmaParsingError>(|input| {
+                let sigma_prop = SigmaBoolean::sigma_parse(r)?;
+                let cost = r.get_u64()?;
+                let extension = input.spending_proof.extension;
+                let reduced_input = ReducedInput {
+                    reduction_result: ReductionResult { sigma_prop, cost },
+                    extension: extension.clone(),
+                };
+                let unsigned_input = UnsignedInput {
+                    box_id: input.box_id,
+                    extension,
+                };
+                Ok((reduced_input, unsigned_input))
+            })?;
+        let reduced_inputs = input_pairs.clone().mapped(|p| p.0);
+        let unsigned_inputs = input_pairs.mapped(|p| p.1);
+        let unsigned_tx =
+            UnsignedTransaction::new(unsigned_inputs, tx.data_inputs, tx.output_candidates)?;
+        Ok(ReducedTransaction {
+            unsigned_tx,
+            reduced_inputs,
+        })
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    use ergotree_ir::serialization::sigma_serialize_roundtrip;
+    use proptest::prelude::*;
+
+    impl Arbitrary for ReducedTransaction {
+        type Parameters = ();
+
+        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+            (any::<UnsignedTransaction>(), any::<SigmaBoolean>())
+                .prop_map(|(unsigned_tx, sb)| Self {
+                    unsigned_tx: unsigned_tx.clone(),
+                    reduced_inputs: unsigned_tx.inputs.mapped(|unsigned_input| ReducedInput {
+                        reduction_result: ReductionResult {
+                            sigma_prop: sb.clone(),
+                            cost: 0,
+                        },
+                        extension: unsigned_input.extension,
+                    }),
+                })
+                .boxed()
+        }
+        type Strategy = BoxedStrategy<Self>;
+    }
+
+    proptest! {
+
+        #![proptest_config(ProptestConfig::with_cases(64))]
+
+        #[test]
+        fn ser_roundtrip(v in any::<ReducedTransaction>()) {
+            prop_assert_eq![sigma_serialize_roundtrip(&v), v];
+        }
+    }
+}
diff --git a/ergo-lib/src/wallet.rs b/ergo-lib/src/wallet.rs
index 393d5c86e..abff6b773 100644
--- a/ergo-lib/src/wallet.rs
+++ b/ergo-lib/src/wallet.rs
@@ -13,8 +13,10 @@ use signing::{sign_transaction, TxSigningError};
 use thiserror::Error;
 
 use crate::chain::ergo_state_context::ErgoStateContext;
+use crate::chain::transaction::reduced::ReducedTransaction;
 use crate::chain::transaction::Transaction;
 
+use self::signing::sign_reduced_transaction;
 use self::signing::TransactionContext;
 
 /// Wallet
@@ -55,4 +57,12 @@ impl Wallet {
     ) -> Result<Transaction, WalletError> {
         sign_transaction(self.prover.as_ref(), tx_context, state_context).map_err(WalletError::from)
     }
+
+    /// Signs a reduced transaction (generating proofs for inputs)
+    pub fn sign_reduced_transaction(
+        &self,
+        reduced_tx: ReducedTransaction,
+    ) -> Result<Transaction, WalletError> {
+        sign_reduced_transaction(self.prover.as_ref(), reduced_tx).map_err(WalletError::from)
+    }
 }
diff --git a/ergo-lib/src/wallet/signing.rs b/ergo-lib/src/wallet/signing.rs
index fa99ff8f3..6cb9ab74e 100644
--- a/ergo-lib/src/wallet/signing.rs
+++ b/ergo-lib/src/wallet/signing.rs
@@ -5,6 +5,7 @@ use ergotree_ir::chain::ergo_box::ErgoBox;
 use ergotree_ir::serialization::SigmaSerializationError;
 use std::rc::Rc;
 
+use crate::chain::transaction::reduced::ReducedTransaction;
 use crate::chain::transaction::Input;
 use crate::chain::{
     ergo_state_context::ErgoStateContext,
@@ -122,6 +123,36 @@ pub fn sign_transaction(
     )?)
 }
 
+/// Signs a reduced transaction (generating proofs for inputs)
+pub fn sign_reduced_transaction(
+    prover: &dyn Prover,
+    reduced_tx: ReducedTransaction,
+) -> Result<Transaction, TxSigningError> {
+    let tx = reduced_tx.unsigned_tx.clone();
+    let message_to_sign = tx.bytes_to_sign()?;
+    let signed_inputs = tx.inputs.enumerated().try_mapped(|(idx, input)| {
+        prover
+            .generate_proof(
+                reduced_tx
+                    .reduced_inputs()
+                    .get(idx)
+                    .unwrap()
+                    .reduction_result
+                    .sigma_prop
+                    .clone(),
+                message_to_sign.as_slice(),
+                &HintsBag::empty(),
+            )
+            .map(|proof| Input::new(input.box_id.clone(), proof.into()))
+            .map_err(|e| TxSigningError::ProverError(e, idx))
+    })?;
+    Ok(Transaction::new(
+        signed_inputs,
+        tx.data_inputs,
+        tx.output_candidates,
+    )?)
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -142,6 +173,7 @@ mod tests {
     use rand::thread_rng;
     use sigma_test_util::force_any_val;
 
+    use crate::chain::transaction::reduced::reduce_tx;
     use crate::chain::{
         ergo_box::box_builder::ErgoBoxCandidateBuilder, transaction::UnsignedInput,
     };
@@ -190,9 +222,9 @@ mod tests {
                              TxId::zero(),
                              0).unwrap()
             }).collect();
-            let prover = TestProver {
+            let prover = Rc::new(TestProver {
                 secrets: secrets.clone().into_iter().map(PrivateInput::DlogProverInput).collect(),
-            };
+            });
             let inputs: Vec<UnsignedInput> = boxes_to_spend.clone().into_iter().map(UnsignedInput::from).collect();
             // 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)
@@ -205,9 +237,12 @@ mod tests {
                 None, output_candidates.try_into().unwrap()).unwrap();
             let tx_context = TransactionContext { spending_tx: tx,
                                                   boxes_to_spend: boxes_to_spend.clone(), data_boxes: vec![] };
-            let res = sign_transaction(Box::new(prover).as_ref(), tx_context, &ErgoStateContext::dummy());
+            let res = sign_transaction(prover.as_ref(), tx_context.clone(), &ErgoStateContext::dummy());
             let signed_tx = res.unwrap();
             prop_assert!(verify_tx_proofs(&signed_tx, &boxes_to_spend).unwrap());
+            let reduced_tx = reduce_tx(tx_context, &ErgoStateContext::dummy()).unwrap();
+            let signed_reduced_tx = sign_reduced_transaction(prover.as_ref(), reduced_tx).unwrap();
+            prop_assert!(verify_tx_proofs(&signed_reduced_tx, &boxes_to_spend).unwrap());
         }
     }
 
diff --git a/ergotree-interpreter/src/eval.rs b/ergotree-interpreter/src/eval.rs
index a1c037c70..f4fd0107a 100644
--- a/ergotree-interpreter/src/eval.rs
+++ b/ergotree-interpreter/src/eval.rs
@@ -140,6 +140,7 @@ pub enum EvalError {
 }
 
 /// Result of expression reduction procedure (see `reduce_to_crypto`).
+#[derive(PartialEq, Debug, Clone)]
 pub struct ReductionResult {
     /// value of SigmaProp type which represents a statement verifiable via sigma protocol.
     pub sigma_prop: SigmaBoolean,
@@ -147,32 +148,28 @@ pub struct ReductionResult {
     pub cost: u64,
 }
 
-/// Interpreter
-pub trait Evaluator {
-    /// Evaluate the given expression by reducing it to SigmaBoolean value.
-    fn reduce_to_crypto(
-        &self,
-        expr: &Expr,
-        env: &Env,
-        ctx: Rc<Context>,
-    ) -> Result<ReductionResult, EvalError> {
-        let cost_accum = CostAccumulator::new(0, None);
-        let mut ectx = EvalContext::new(ctx, cost_accum);
-        expr.eval(env, &mut ectx)
-            .and_then(|v| -> Result<ReductionResult, EvalError> {
-                match v {
-                    Value::Boolean(b) => Ok(ReductionResult {
-                        sigma_prop: SigmaBoolean::TrivialProp(b),
-                        cost: 0,
-                    }),
-                    Value::SigmaProp(sp) => Ok(ReductionResult {
-                        sigma_prop: sp.value().clone(),
-                        cost: 0,
-                    }),
-                    _ => Err(EvalError::InvalidResultType),
-                }
-            })
-    }
+/// Evaluate the given expression by reducing it to SigmaBoolean value.
+pub fn reduce_to_crypto(
+    expr: &Expr,
+    env: &Env,
+    ctx: Rc<Context>,
+) -> Result<ReductionResult, EvalError> {
+    let cost_accum = CostAccumulator::new(0, None);
+    let mut ectx = EvalContext::new(ctx, cost_accum);
+    expr.eval(env, &mut ectx)
+        .and_then(|v| -> Result<ReductionResult, EvalError> {
+            match v {
+                Value::Boolean(b) => Ok(ReductionResult {
+                    sigma_prop: SigmaBoolean::TrivialProp(b),
+                    cost: 0,
+                }),
+                Value::SigmaProp(sp) => Ok(ReductionResult {
+                    sigma_prop: sp.value().clone(),
+                    cost: 0,
+                }),
+                _ => Err(EvalError::InvalidResultType),
+            }
+        })
 }
 
 #[derive(Debug)]
diff --git a/ergotree-interpreter/src/sigma_protocol/prover.rs b/ergotree-interpreter/src/sigma_protocol/prover.rs
index 3648094a3..49ce27683 100644
--- a/ergotree-interpreter/src/sigma_protocol/prover.rs
+++ b/ergotree-interpreter/src/sigma_protocol/prover.rs
@@ -5,6 +5,7 @@ mod prover_result;
 
 pub mod hint;
 
+use crate::eval::reduce_to_crypto;
 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;
@@ -47,7 +48,7 @@ use super::FirstProverMessage::FirstDlogProverMessage;
 
 use crate::eval::context::Context;
 use crate::eval::env::Env;
-use crate::eval::{EvalError, Evaluator};
+use crate::eval::EvalError;
 
 use thiserror::Error;
 
@@ -99,7 +100,7 @@ impl From<FiatShamirTreeSerializationError> for ProverError {
 }
 
 /// Prover
-pub trait Prover: Evaluator {
+pub trait Prover {
     /// Secrets of the prover
     fn secrets(&self) -> &[PrivateInput];
 
@@ -117,18 +118,27 @@ pub trait Prover: Evaluator {
         hints_bag: &HintsBag,
     ) -> Result<ProverResult, ProverError> {
         let expr = tree.proposition()?;
-        let unchecked_tree_opt = self
-            .reduce_to_crypto(expr.as_ref(), env, ctx)
-            .map_err(ProverError::EvalError)
-            .and_then(|v| match v.sigma_prop {
-                SigmaBoolean::TrivialProp(true) => Ok(None),
-                SigmaBoolean::TrivialProp(false) => Err(ProverError::ReducedToFalse),
-                sb => {
-                    let tree = convert_to_unproven(sb)?;
-                    let unchecked_tree = prove_to_unchecked(self, tree, message, hints_bag)?;
-                    Ok(Some(unchecked_tree))
-                }
-            })?;
+        let reduction_result =
+            reduce_to_crypto(expr.as_ref(), env, ctx).map_err(ProverError::EvalError)?;
+        self.generate_proof(reduction_result.sigma_prop, message, hints_bag)
+    }
+
+    /// Generate proofs for the given message for the given Sigma boolean expression
+    fn generate_proof(
+        &self,
+        sigmabool: SigmaBoolean,
+        message: &[u8],
+        hints_bag: &HintsBag,
+    ) -> Result<ProverResult, ProverError> {
+        let unchecked_tree_opt = match sigmabool {
+            SigmaBoolean::TrivialProp(true) => Ok(None),
+            SigmaBoolean::TrivialProp(false) => Err(ProverError::ReducedToFalse),
+            sb => {
+                let tree = convert_to_unproven(sb)?;
+                let unchecked_tree = prove_to_unchecked(self, tree, message, hints_bag)?;
+                Ok(Some(unchecked_tree))
+            }
+        }?;
         let proof = match unchecked_tree_opt {
             Some(tree) => serialize_sig(tree),
             None => ProofBytes::Empty,
@@ -901,7 +911,6 @@ pub struct TestProver {
     pub secrets: Vec<PrivateInput>,
 }
 
-impl Evaluator for TestProver {}
 impl Prover for TestProver {
     fn secrets(&self) -> &[PrivateInput] {
         self.secrets.as_ref()
diff --git a/ergotree-interpreter/src/sigma_protocol/verifier.rs b/ergotree-interpreter/src/sigma_protocol/verifier.rs
index 0ac8ca7f4..dd07b77b1 100644
--- a/ergotree-interpreter/src/sigma_protocol/verifier.rs
+++ b/ergotree-interpreter/src/sigma_protocol/verifier.rs
@@ -17,7 +17,8 @@ use super::{
 };
 use crate::eval::context::Context;
 use crate::eval::env::Env;
-use crate::eval::{EvalError, Evaluator};
+use crate::eval::reduce_to_crypto;
+use crate::eval::EvalError;
 use dlog_protocol::FirstDlogProverMessage;
 use ergotree_ir::ergo_tree::ErgoTree;
 use ergotree_ir::ergo_tree::ErgoTreeError;
@@ -54,7 +55,7 @@ pub struct VerificationResult {
 }
 
 /// Verifier for the proofs generater by [`super::prover::Prover`]
-pub trait Verifier: Evaluator {
+pub trait Verifier {
     /// Executes the script in a given context.
     /// Step 1: Deserialize context variables
     /// Step 2: Evaluate expression and produce SigmaProp value, which is zero-knowledge statement (see also `SigmaBoolean`).
@@ -68,7 +69,7 @@ pub trait Verifier: Evaluator {
         message: &[u8],
     ) -> Result<VerificationResult, VerifierError> {
         let expr = tree.proposition()?;
-        let cprop = self.reduce_to_crypto(expr.as_ref(), env, ctx)?.sigma_prop;
+        let cprop = reduce_to_crypto(expr.as_ref(), env, ctx)?.sigma_prop;
         let res: bool = match cprop {
             SigmaBoolean::TrivialProp(b) => b,
             sb => {
@@ -145,7 +146,6 @@ fn compute_commitments(sp: UncheckedTree) -> UncheckedTree {
 /// Test Verifier implementation
 pub struct TestVerifier;
 
-impl Evaluator for TestVerifier {}
 impl Verifier for TestVerifier {}
 
 #[allow(clippy::unwrap_used)]


Test patch: