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

Issue:
Add `verify_tx_input` function to validate tx input proof
`verify_tx_input` with the same parameters as in https://github.com/ergoplatform/sigma-rust/blob/a0880371b3428d1e62c9adaaa42d045132800ebc/bindings/ergo-lib-wasm/src/wallet.rs#L197-L206 except for signed tx


Discussion / hints:

Fix patch:
diff --git a/bindings/ergo-lib-c-core/src/error_conversion.rs b/bindings/ergo-lib-c-core/src/error_conversion.rs
index 99c74ea70..ed9c64ba4 100644
--- a/bindings/ergo-lib-c-core/src/error_conversion.rs
+++ b/bindings/ergo-lib-c-core/src/error_conversion.rs
@@ -16,6 +16,7 @@ use ergo_lib::ergotree_ir::chain::ergo_box::box_value::BoxValueError;
 use ergo_lib::ergotree_ir::chain::token::TokenAmountError;
 use ergo_lib::wallet::derivation_path::ChildIndexError;
 use ergo_lib::wallet::signing::TxSigningError;
+use ergo_lib::wallet::tx_context::TransactionContextError;
 use ergo_lib::{
     chain::ergo_box::box_builder::ErgoBoxCandidateBuilderError,
     ergotree_ir::{
@@ -53,6 +54,7 @@ convert_error!(TxSigningError);
 convert_error!(WalletError);
 convert_error!(DecodeError);
 convert_error!(TryFromSliceError);
+convert_error!(TransactionContextError);
 
 macro_rules! convert_error_via_debug {
     ($t:ident) => {
diff --git a/bindings/ergo-lib-wasm/src/error_conversion.rs b/bindings/ergo-lib-wasm/src/error_conversion.rs
index f2a1f2d4a..275f76183 100644
--- a/bindings/ergo-lib-wasm/src/error_conversion.rs
+++ b/bindings/ergo-lib-wasm/src/error_conversion.rs
@@ -7,6 +7,7 @@ use std::num::ParseIntError;
 use base16::DecodeError;
 use bounded_vec::BoundedVecOutOfBounds;
 use ergo_lib::chain::transaction::TransactionSignatureVerificationError;
+use ergo_lib::chain::transaction::TxVerifyError;
 use ergo_lib::ergo_chain_types::DigestNError;
 use ergo_lib::ergo_nipopow::NipopowProofError;
 #[cfg(feature = "rest")]
@@ -21,6 +22,7 @@ use ergo_lib::wallet::derivation_path::DerivationPathError;
 use ergo_lib::wallet::ext_pub_key::ExtPubKeyError;
 use ergo_lib::wallet::ext_secret_key::ExtSecretKeyError;
 use ergo_lib::wallet::signing::TxSigningError;
+use ergo_lib::wallet::tx_context::TransactionContextError;
 use ergo_lib::{
     chain::ergo_box::box_builder::ErgoBoxCandidateBuilderError,
     ergotree_ir::{
@@ -92,6 +94,8 @@ from_error_to_wrap!(PeerDiscoveryError);
 #[cfg(feature = "rest")]
 from_error_to_wrap!(ParseError);
 from_error_to_wrap!(ConvError);
+from_error_to_wrap!(TransactionContextError);
+from_error_to_wrap!(TxVerifyError);
 
 macro_rules! from_error_to_wrap_via_debug {
     ($t:ident) => {
diff --git a/bindings/ergo-lib-wasm/src/transaction.rs b/bindings/ergo-lib-wasm/src/transaction.rs
index 1940c8e2a..3f233917b 100644
--- a/bindings/ergo-lib-wasm/src/transaction.rs
+++ b/bindings/ergo-lib-wasm/src/transaction.rs
@@ -277,7 +277,7 @@ impl Transaction {
 
     /// Returns ErgoBox's created from ErgoBoxCandidate's with tx id and indices
     pub fn outputs(&self) -> ErgoBoxes {
-        self.0.outputs.clone().into()
+        self.0.outputs.clone().to_vec().into()
     }
 
     /// Returns serialized bytes or fails with error if cannot be serialized
@@ -406,3 +406,29 @@ impl UnsignedTransaction {
             .collect()
     }
 }
+
+/// Verify transaction input's proof
+#[wasm_bindgen]
+pub fn verify_tx_input_proof(
+    input_idx: usize,
+    state_context: &ErgoStateContext,
+    tx: &Transaction,
+    boxes_to_spend: &ErgoBoxes,
+    data_boxes: &ErgoBoxes,
+) -> Result<bool, JsValue> {
+    let boxes_to_spend = boxes_to_spend.clone().into();
+    let data_boxes = data_boxes.clone().into();
+    let tx_context = ergo_lib::wallet::signing::TransactionContext::new(
+        tx.0.clone(),
+        boxes_to_spend,
+        data_boxes,
+    )
+    .map_err(to_js)?;
+    let state_context_inner = state_context.clone().into();
+    ergo_lib::chain::transaction::verify_tx_input_proof(
+        &tx_context,
+        &state_context_inner,
+        input_idx,
+    )
+    .map_err(to_js)
+}
diff --git a/ergo-chain-types/src/lib.rs b/ergo-chain-types/src/lib.rs
index 1e1709787..79c7f5aea 100644
--- a/ergo-chain-types/src/lib.rs
+++ b/ergo-chain-types/src/lib.rs
@@ -15,6 +15,7 @@
 #![deny(clippy::expect_used)]
 #![deny(clippy::todo)]
 #![deny(clippy::unimplemented)]
+#![deny(clippy::unreachable)]
 #![deny(clippy::panic)]
 
 mod base16_bytes;
diff --git a/ergo-lib/src/chain/json/transaction.rs b/ergo-lib/src/chain/json/transaction.rs
index 78a55385e..7558613cf 100644
--- a/ergo-lib/src/chain/json/transaction.rs
+++ b/ergo-lib/src/chain/json/transaction.rs
@@ -70,7 +70,7 @@ impl From<Transaction> for TransactionJson {
                 .data_inputs
                 .map(|di| di.as_vec().clone())
                 .unwrap_or_default(),
-            outputs: v.outputs,
+            outputs: v.outputs.to_vec(),
         }
     }
 }
diff --git a/ergo-lib/src/chain/transaction.rs b/ergo-lib/src/chain/transaction.rs
index fd5ebb992..9b47d0594 100644
--- a/ergo-lib/src/chain/transaction.rs
+++ b/ergo-lib/src/chain/transaction.rs
@@ -8,9 +8,12 @@ pub mod unsigned;
 use bounded_vec::BoundedVec;
 use ergo_chain_types::blake2b256_hash;
 pub use ergotree_interpreter::eval::context::TxIoVec;
+use ergotree_interpreter::eval::env::Env;
 use ergotree_interpreter::eval::extract_sigma_boolean;
 use ergotree_interpreter::eval::EvalError;
 use ergotree_interpreter::sigma_protocol::verifier::verify_signature;
+use ergotree_interpreter::sigma_protocol::verifier::TestVerifier;
+use ergotree_interpreter::sigma_protocol::verifier::Verifier;
 use ergotree_interpreter::sigma_protocol::verifier::VerifierError;
 use ergotree_ir::chain::ergo_box::BoxId;
 use ergotree_ir::chain::ergo_box::ErgoBox;
@@ -30,6 +33,10 @@ use ergotree_ir::serialization::SigmaSerializationError;
 use ergotree_ir::serialization::SigmaSerializeResult;
 pub use input::*;
 
+use crate::wallet::signing::make_context;
+use crate::wallet::signing::TransactionContext;
+use crate::wallet::tx_context::TransactionContextError;
+
 use self::unsigned::UnsignedTransaction;
 
 use indexmap::IndexSet;
@@ -37,6 +44,9 @@ use indexmap::IndexSet;
 use std::convert::TryFrom;
 use std::convert::TryInto;
 use std::iter::FromIterator;
+use std::rc::Rc;
+
+use super::ergo_state_context::ErgoStateContext;
 
 /**
  * ErgoTransaction is an atomic state transition operation. It destroys Boxes from the state
@@ -73,7 +83,7 @@ pub struct Transaction {
 
     /// Boxes to be created by this transaction. Differ from [`Self::output_candidates`] in that
     /// they include transaction id and index
-    pub outputs: Vec<ErgoBox>,
+    pub outputs: TxIoVec<ErgoBox>,
 }
 
 impl Transaction {
@@ -104,19 +114,24 @@ impl Transaction {
         data_inputs: Option<TxIoVec<DataInput>>,
         output_candidates: TxIoVec<ErgoBoxCandidate>,
     ) -> Result<Transaction, SigmaSerializationError> {
+        let outputs_with_zero_tx_id =
+            output_candidates
+                .clone()
+                .enumerated()
+                .try_mapped_ref(|(idx, bc)| {
+                    ErgoBox::from_box_candidate(bc, TxId::zero(), *idx as u16)
+                })?;
         let tx_to_sign = Transaction {
             tx_id: TxId::zero(),
             inputs,
             data_inputs,
             output_candidates: output_candidates.clone(),
-            outputs: vec![],
+            outputs: outputs_with_zero_tx_id,
         };
         let tx_id = tx_to_sign.calc_tx_id()?;
         let outputs = output_candidates
-            .iter()
-            .enumerate()
-            .map(|(idx, bc)| ErgoBox::from_box_candidate(bc, tx_id, idx as u16))
-            .collect::<Result<Vec<ErgoBox>, SigmaSerializationError>>()?;
+            .enumerated()
+            .try_mapped_ref(|(idx, bc)| ErgoBox::from_box_candidate(bc, tx_id, *idx as u16))?;
         Ok(Transaction {
             tx_id,
             outputs,
@@ -191,7 +206,7 @@ impl Transaction {
         Ok(verify_signature(
             sb,
             message.as_slice(),
-            input.spending_proof.proof.clone().to_bytes().as_slice(),
+            input.spending_proof.proof.as_ref(),
         )?)
     }
 }
@@ -316,6 +331,50 @@ pub enum TransactionError {
     InvalidOutputCandidatesCount(bounded_vec::BoundedVecOutOfBounds),
     #[error("Invalid Tx data inputs: {0:?}")]
     InvalidDataInputsCount(bounded_vec::BoundedVecOutOfBounds),
+    #[error("input with index {0} not found")]
+    InputNofFound(usize),
+}
+
+/// Errors on transaction verification
+#[derive(Error, Debug)]
+pub enum TxVerifyError {
+    /// TransactionContextError
+    #[error("TransactionContextError: {0}")]
+    TransactionContextError(#[from] TransactionContextError),
+    /// SerializationError
+    #[error("Transaction serialization failed: {0}")]
+    SerializationError(#[from] SigmaSerializationError),
+    /// VerifierError
+    #[error("VerifierError: {0}")]
+    VerifierError(#[from] VerifierError),
+}
+
+/// Verify transaction input's proof
+pub fn verify_tx_input_proof(
+    tx_context: &TransactionContext<Transaction>,
+    state_context: &ErgoStateContext,
+    input_idx: usize,
+) -> Result<bool, TxVerifyError> {
+    let input = tx_context
+        .spending_tx
+        .inputs
+        .get(input_idx)
+        .ok_or(TransactionContextError::InputBoxNotFound(input_idx))?;
+    let input_box = tx_context
+        .get_input_box(&input.box_id)
+        .ok_or(TransactionContextError::InputBoxNotFound(input_idx))?;
+    let ctx = Rc::new(make_context(state_context, tx_context, input_idx)?);
+    let verifier = TestVerifier;
+    let message_to_sign = tx_context.spending_tx.bytes_to_sign()?;
+    Ok(verifier
+        .verify(
+            &input_box.ergo_tree,
+            &Env::empty(),
+            ctx,
+            input.spending_proof.proof.clone(),
+            message_to_sign.as_slice(),
+        )?
+        .result)
 }
 
 /// Arbitrary impl
diff --git a/ergo-lib/src/chain/transaction/reduced.rs b/ergo-lib/src/chain/transaction/reduced.rs
index fd5267058..4e2c542b9 100644
--- a/ergo-lib/src/chain/transaction/reduced.rs
+++ b/ergo-lib/src/chain/transaction/reduced.rs
@@ -21,6 +21,7 @@ use crate::chain::transaction::UnsignedInput;
 use crate::wallet::signing::make_context;
 use crate::wallet::signing::TransactionContext;
 use crate::wallet::signing::TxSigningError;
+use crate::wallet::tx_context::TransactionContextError;
 
 use super::unsigned::UnsignedTransaction;
 use super::TxIoVec;
@@ -75,7 +76,7 @@ pub fn reduce_tx(
         .try_mapped::<_, _, TxSigningError>(|(idx, input)| {
             let input_box = tx_context
                 .get_input_box(&input.box_id)
-                .ok_or(TxSigningError::InputBoxNotFound(idx))?;
+                .ok_or(TransactionContextError::InputBoxNotFound(idx))?;
             let ctx = Rc::new(make_context(state_context, &tx_context, idx)?);
             let expr = input_box
                 .ergo_tree
diff --git a/ergo-lib/src/lib.rs b/ergo-lib/src/lib.rs
index 8f0fd5ce9..1d1aa7ec9 100644
--- a/ergo-lib/src/lib.rs
+++ b/ergo-lib/src/lib.rs
@@ -17,6 +17,7 @@
 #![deny(clippy::expect_used)]
 #![deny(clippy::todo)]
 #![deny(clippy::unimplemented)]
+#![deny(clippy::unreachable)]
 #![deny(clippy::panic)]
 
 pub mod chain;
diff --git a/ergo-lib/src/wallet.rs b/ergo-lib/src/wallet.rs
index 3487c96fa..8d72a31b9 100644
--- a/ergo-lib/src/wallet.rs
+++ b/ergo-lib/src/wallet.rs
@@ -12,7 +12,7 @@ pub mod multi_sig;
 pub mod secret_key;
 pub mod signing;
 pub mod tx_builder;
-mod tx_context;
+pub mod tx_context;
 
 use ergotree_interpreter::sigma_protocol::private_input::PrivateInput;
 use ergotree_interpreter::sigma_protocol::prover::Prover;
diff --git a/ergo-lib/src/wallet/multi_sig.rs b/ergo-lib/src/wallet/multi_sig.rs
index 234581233..c7a497b84 100644
--- a/ergo-lib/src/wallet/multi_sig.rs
+++ b/ergo-lib/src/wallet/multi_sig.rs
@@ -1,9 +1,7 @@
-//! multi sig prover
-
+//! multi sig prove crate::chain::ergo_state_context::ErgoStateContext;
 use crate::chain::ergo_state_context::ErgoStateContext;
 use crate::chain::transaction::unsigned::UnsignedTransaction;
 use crate::chain::transaction::Transaction;
-use crate::ergotree_interpreter::eval::context::Context;
 use crate::ergotree_interpreter::eval::env::Env;
 use crate::ergotree_interpreter::eval::reduce_to_crypto;
 use crate::ergotree_interpreter::sigma_protocol::dht_protocol::interactive_prover as dht_interactive_prover;
@@ -13,23 +11,22 @@ use crate::ergotree_interpreter::sigma_protocol::prover::hint::{
     CommitmentHint, Hint, HintsBag, OwnCommitment, RealCommitment, RealSecretProof, SecretProven,
     SimulatedCommitment, SimulatedSecretProof,
 };
-use crate::ergotree_interpreter::sigma_protocol::prover::{ProofBytes, ProverError};
+use crate::ergotree_interpreter::sigma_protocol::prover::ProverError;
 use crate::ergotree_interpreter::sigma_protocol::sig_serializer::SigParsingError;
 use crate::ergotree_interpreter::sigma_protocol::unproven_tree::NodePosition;
 use crate::ergotree_interpreter::sigma_protocol::FirstProverMessage;
-use crate::ergotree_ir::serialization::SigmaSerializationError;
 use crate::ergotree_ir::sigma_protocol::sigma_boolean::SigmaBoolean;
 use crate::ergotree_ir::sigma_protocol::sigma_boolean::SigmaConjecture;
 use crate::ergotree_ir::sigma_protocol::sigma_boolean::SigmaConjectureItems;
 use crate::ergotree_ir::sigma_protocol::sigma_boolean::SigmaProofOfKnowledgeTree;
-use crate::wallet::signing::{make_context, ErgoTransaction, TransactionContext, TxSigningError};
+use crate::wallet::signing::{make_context, TransactionContext, TxSigningError};
 use ergotree_interpreter::sigma_protocol::sig_serializer::parse_sig_compute_challenges;
 use ergotree_interpreter::sigma_protocol::unchecked_tree::UncheckedTree;
 use ergotree_interpreter::sigma_protocol::verifier::compute_commitments;
-use ergotree_ir::chain::ergo_box::ErgoBox;
 use std::collections::HashMap;
 use std::rc::Rc;
-use std::sync::Arc;
+
+use super::tx_context::TransactionContextError;
 
 /// TransactionHintsBag
 #[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
@@ -252,7 +249,7 @@ pub fn generate_commitments(
     for (i, input) in tx.inputs.iter().enumerate() {
         let input_box = tx_context
             .get_input_box(&input.box_id)
-            .ok_or(TxSigningError::InputBoxNotFound(i))?;
+            .ok_or(TransactionContextError::InputBoxNotFound(i))?;
         let ctx = Rc::new(make_context(state_context, &tx_context, i)?);
         let tree = input_box.ergo_tree.clone();
         let exp = tree
@@ -280,80 +277,9 @@ pub fn extract_hints(
     for (i, input) in tx_ctx.spending_tx.inputs.iter().enumerate() {
         let input_box = tx_ctx
             .get_input_box(&input.box_id)
-            .ok_or(TxSigningError::InputBoxNotFound(i))?;
-        let height = state_context.pre_header.height;
-        let self_box = tx_ctx
-            .get_input_box(&tx_ctx.spending_tx.inputs.as_vec()[i].box_id)
-            .ok_or_else(|| {
-                TxSigningError::ContextError("self_index is out of bounds".to_string())
-            })?;
-        let outputs = tx_ctx
-            .spending_tx
-            .output_candidates
-            .iter()
-            .enumerate()
-            .map(|(idx, b)| ErgoBox::from_box_candidate(b, tx_ctx.spending_tx.id(), idx as u16))
-            .collect::<Result<Vec<ErgoBox>, SigmaSerializationError>>()?;
-        let outputs_ir = outputs.into_iter().map(Arc::new).collect();
-        let data_inputs_ir = if let Some(data_inputs) = tx_ctx.spending_tx.data_inputs.as_ref() {
-            Some(data_inputs.clone().enumerated().try_mapped(|(idx, di)| {
-                tx_ctx
-                    .data_boxes
-                    .as_ref()
-                    .ok_or(TxSigningError::DataInputBoxNotFound(idx))?
-                    .iter()
-                    .find(|b| di.box_id == b.box_id())
-                    .map(|b| Arc::new(b.clone()))
-                    .ok_or(TxSigningError::DataInputBoxNotFound(idx))
-            })?)
-        } else {
-            None
-        };
-        let inputs_ir = tx_ctx
-            .spending_tx
-            .inputs_ids()
-            .enumerated()
-            .try_mapped(|(idx, u)| {
-                tx_ctx
-                    .get_input_box(&u)
-                    .map(Arc::new)
-                    .ok_or(TxSigningError::InputBoxNotFound(idx))
-            })?;
-        let extension = tx_ctx
-            .spending_tx
-            .inputs
-            .get(i)
-            .ok_or_else(|| {
-                TxSigningError::ContextError(
-                    "self_index not found in spending transaction inputs".to_string(),
-                )
-            })?
-            .spending_proof
-            .extension
-            .clone();
-        let ctx = Rc::new(Context {
-            height,
-            self_box: Arc::new(self_box),
-            outputs: outputs_ir,
-            data_inputs: data_inputs_ir,
-            inputs: inputs_ir,
-            pre_header: state_context.pre_header.clone(),
-            headers: state_context.headers.clone(),
-            extension,
-        });
+            .ok_or(TransactionContextError::InputBoxNotFound(i))?;
+        let ctx = Rc::new(make_context(state_context, tx_ctx, i)?);
         let tree = input_box.ergo_tree.clone();
-
-        // `i` is guaranteed to be a valid index since it's coming from an `enumerate()` call above.
-        #[allow(clippy::unwrap_used)]
-        let proof: ProofBytes = tx_ctx
-            .spending_tx
-            .inputs
-            .get(i)
-            .unwrap()
-            .clone()
-            .spending_proof
-            .proof;
-        let proof: Vec<u8> = Vec::from(proof);
         let exp = tree
             .proposition()
             .map_err(ProverError::ErgoTreeError)
@@ -366,7 +292,7 @@ pub fn extract_hints(
             sigma_tree,
             real_secrets_to_extract.as_slice(),
             simulated_secrets_to_extract.as_slice(),
-            proof.as_slice(),
+            input.spending_proof.proof.as_ref(),
         )?;
         hints_bag.add_hints_for_input(i, bag);
     }
diff --git a/ergo-lib/src/wallet/signing.rs b/ergo-lib/src/wallet/signing.rs
index 81e182abc..dedaf6c92 100644
--- a/ergo-lib/src/wallet/signing.rs
+++ b/ergo-lib/src/wallet/signing.rs
@@ -1,7 +1,7 @@
 //! Transaction signing
 
 use crate::chain::transaction::reduced::ReducedTransaction;
-use crate::chain::transaction::{DataInput, Input};
+use crate::chain::transaction::{DataInput, Input, TransactionError};
 use crate::chain::{
     ergo_state_context::ErgoStateContext,
     transaction::{unsigned::UnsignedTransaction, Transaction},
@@ -18,35 +18,23 @@ use crate::ergotree_ir::chain::ergo_box::BoxId;
 use crate::wallet::multi_sig::TransactionHintsBag;
 use ergotree_interpreter::eval::context::{Context, TxIoVec};
 use ergotree_interpreter::eval::env::Env;
-use ergotree_interpreter::sigma_protocol::prover::Prover;
 use ergotree_interpreter::sigma_protocol::prover::ProverError;
 use ergotree_interpreter::sigma_protocol::prover::ProverResult;
+use ergotree_interpreter::sigma_protocol::prover::{ContextExtension, Prover};
 use thiserror::Error;
 
+pub use super::tx_context::TransactionContext;
+use super::tx_context::TransactionContextError;
+
 /// Errors on transaction signing
 #[derive(Error, Debug)]
 pub enum TxSigningError {
+    /// Transaction context error
+    #[error("TransactionContextError: {0}")]
+    TransactionContextError(#[from] TransactionContextError),
     /// Error on proving an input
     #[error("Prover error (tx input index {1}): {0}")]
     ProverError(ProverError, usize),
-    /// Failed to find an input in `boxes_to_spend`
-    #[error("Input box not found (index {0})")]
-    InputBoxNotFound(usize),
-    /// Too many input boxes
-    #[error("A maximum of 255 input boxes is allowed (have {0})")]
-    TooManyInputBoxes(usize),
-    /// `boxes_to_spend` is empty
-    #[error("No Input boxes found")]
-    NoInputBoxes,
-    /// Too many data input boxes
-    #[error("A maximum of 255 data input boxes is allowed (have {0})")]
-    TooManyDataInputBoxes(usize),
-    /// Failed to find a data input in `data_boxes`
-    #[error("Data input box not found (index {0})")]
-    DataInputBoxNotFound(usize),
-    /// Context creation error
-    #[error("Context error: {0}")]
-    ContextError(String),
     /// Tx serialization failed (id calculation)
     #[error("Transaction serialization failed: {0}")]
     SerializationError(#[from] SigmaSerializationError),
@@ -55,14 +43,16 @@ pub enum TxSigningError {
     SigParsingError(#[from] SigParsingError),
 }
 
-pub use super::tx_context::TransactionContext;
-
 /// Exposes common properties for signed and unsigned transactions
 pub trait ErgoTransaction {
     /// input boxes ids
     fn inputs_ids(&self) -> TxIoVec<BoxId>;
     /// data input boxes
     fn data_inputs(&self) -> Option<TxIoVec<DataInput>>;
+    /// output boxes
+    fn outputs(&self) -> TxIoVec<ErgoBox>;
+    /// ContextExtension for the given input index
+    fn context_extension(&self, input_index: usize) -> Option<ContextExtension>;
 }
 
 impl ErgoTransaction for UnsignedTransaction {
@@ -73,6 +63,21 @@ impl ErgoTransaction for UnsignedTransaction {
     fn data_inputs(&self) -> Option<TxIoVec<DataInput>> {
         self.data_inputs.clone()
     }
+
+    fn outputs(&self) -> TxIoVec<ErgoBox> {
+        #[allow(clippy::unwrap_used)] // box serialization cannot fail?
+        self.output_candidates
+            .clone()
+            .enumerated()
+            .try_mapped(|(idx, b)| ErgoBox::from_box_candidate(&b, self.id(), idx as u16))
+            .unwrap()
+    }
+
+    fn context_extension(&self, input_index: usize) -> Option<ContextExtension> {
+        self.inputs
+            .get(input_index)
+            .map(|input| input.extension.clone())
+    }
 }
 
 impl ErgoTransaction for Transaction {
@@ -83,38 +88,48 @@ impl ErgoTransaction for Transaction {
     fn data_inputs(&self) -> Option<TxIoVec<DataInput>> {
         self.data_inputs.clone()
     }
+
+    fn outputs(&self) -> TxIoVec<ErgoBox> {
+        self.outputs.clone()
+    }
+
+    fn context_extension(&self, input_index: usize) -> Option<ContextExtension> {
+        self.inputs
+            .get(input_index)
+            .map(|input| input.spending_proof.extension.clone())
+    }
 }
 
 /// `self_index` - index of the SELF box in the tx_ctx.spending_tx.inputs
-pub fn make_context(
+pub fn make_context<T: ErgoTransaction>(
     state_ctx: &ErgoStateContext,
-    tx_ctx: &TransactionContext<UnsignedTransaction>,
+    tx_ctx: &TransactionContext<T>,
     self_index: usize,
-) -> Result<Context, TxSigningError> {
+) -> Result<Context, TransactionContextError> {
     let height = state_ctx.pre_header.height;
 
     // Find self_box by matching BoxIDs
     let self_box = tx_ctx
-        .get_input_box(&tx_ctx.spending_tx.inputs.as_vec()[self_index].box_id)
-        .ok_or_else(|| TxSigningError::ContextError("self_index is out of bounds".to_string()))?;
+        .get_input_box(
+            tx_ctx
+                .spending_tx
+                .inputs_ids()
+                .get(self_index)
+                .ok_or(TransactionError::InputNofFound(self_index))?,
+        )
+        .ok_or(TransactionContextError::InputBoxNotFound(self_index))?;
 
-    let outputs = tx_ctx
-        .spending_tx
-        .output_candidates
-        .iter()
-        .enumerate()
-        .map(|(idx, b)| ErgoBox::from_box_candidate(b, tx_ctx.spending_tx.id(), idx as u16))
-        .collect::<Result<Vec<ErgoBox>, SigmaSerializationError>>()?;
-    let data_inputs_ir = if let Some(data_inputs) = tx_ctx.spending_tx.data_inputs.as_ref() {
+    let outputs = tx_ctx.spending_tx.outputs();
+    let data_inputs_ir = if let Some(data_inputs) = tx_ctx.spending_tx.data_inputs().as_ref() {
         Some(data_inputs.clone().enumerated().try_mapped(|(idx, di)| {
             tx_ctx
                 .data_boxes
                 .as_ref()
-                .ok_or(TxSigningError::DataInputBoxNotFound(idx))?
+                .ok_or(TransactionContextError::DataInputBoxNotFound(idx))?
                 .iter()
                 .find(|b| di.box_id == b.box_id())
                 .map(|b| Arc::new(b.clone()))
-                .ok_or(TxSigningError::DataInputBoxNotFound(idx))
+                .ok_or(TransactionContextError::DataInputBoxNotFound(idx))
         })?)
     } else {
         None
@@ -129,19 +144,12 @@ pub fn make_context(
             tx_ctx
                 .get_input_box(&u)
                 .map(Arc::new)
-                .ok_or(TxSigningError::InputBoxNotFound(idx))
+                .ok_or(TransactionContextError::InputBoxNotFound(idx))
         })?;
     let extension = tx_ctx
         .spending_tx
-        .inputs
-        .get(self_index)
-        .ok_or_else(|| {
-            TxSigningError::ContextError(
-                "self_index not found in spending transaction inputs".to_string(),
-            )
-        })?
-        .extension
-        .clone();
+        .context_extension(self_index)
+        .ok_or(TransactionError::InputNofFound(self_index))?;
     Ok(Context {
         height,
         self_box: self_box_ir,
@@ -243,10 +251,10 @@ pub fn sign_tx_input(
         .spending_tx
         .inputs
         .get(input_idx)
-        .ok_or(TxSigningError::InputBoxNotFound(input_idx))?;
+        .ok_or(TransactionContextError::InputBoxNotFound(input_idx))?;
     let input_box = tx_context
         .get_input_box(&unsigned_input.box_id)
-        .ok_or(TxSigningError::InputBoxNotFound(input_idx))?;
+        .ok_or(TransactionContextError::InputBoxNotFound(input_idx))?;
     let ctx = Rc::new(make_context(state_context, tx_context, input_idx)?);
     let mut hints_bag = HintsBag::empty();
     if let Some(bag) = tx_hints {
diff --git a/ergo-lib/src/wallet/tx_context.rs b/ergo-lib/src/wallet/tx_context.rs
index 2bf743e07..691ede02f 100644
--- a/ergo-lib/src/wallet/tx_context.rs
+++ b/ergo-lib/src/wallet/tx_context.rs
@@ -1,11 +1,13 @@
 //! Transaction context
 
 use ergotree_ir::chain::ergo_box::ErgoBox;
+use thiserror::Error;
 
+use crate::chain::transaction::TransactionError;
 use crate::ergotree_ir::chain::ergo_box::BoxId;
 use ergotree_interpreter::eval::context::TxIoVec;
 
-use super::signing::{ErgoTransaction, TxSigningError};
+use super::signing::ErgoTransaction;
 
 /// Transaction and an additional info required for signing
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -24,27 +26,26 @@ impl<T: ErgoTransaction> TransactionContext<T> {
         spending_tx: T,
         boxes_to_spend: Vec<ErgoBox>,
         data_boxes: Vec<ErgoBox>,
-    ) -> Result<Self, TxSigningError> {
+    ) -> Result<Self, TransactionContextError> {
         let boxes_to_spend = TxIoVec::from_vec(boxes_to_spend).map_err(|e| match e {
             bounded_vec::BoundedVecOutOfBounds::LowerBoundError { .. } => {
-                TxSigningError::NoInputBoxes
+                TransactionContextError::NoInputBoxes
             }
             bounded_vec::BoundedVecOutOfBounds::UpperBoundError { got, .. } => {
-                TxSigningError::TooManyInputBoxes(got)
+                TransactionContextError::TooManyInputBoxes(got)
             }
         })?;
         for (i, unsigned_input) in spending_tx.inputs_ids().enumerated() {
             if !boxes_to_spend.iter().any(|b| unsigned_input == b.box_id()) {
-                return Err(TxSigningError::InputBoxNotFound(i));
+                return Err(TransactionContextError::InputBoxNotFound(i));
             }
         }
+        let data_boxes_len = data_boxes.len();
         let data_boxes = if !data_boxes.is_empty() {
-            Some(TxIoVec::from_vec(data_boxes).map_err(|e| match e {
-                bounded_vec::BoundedVecOutOfBounds::LowerBoundError { .. } => unreachable!(),
-                bounded_vec::BoundedVecOutOfBounds::UpperBoundError { got, .. } => {
-                    TxSigningError::TooManyDataInputBoxes(got)
-                }
-            })?)
+            Some(
+                TxIoVec::from_vec(data_boxes)
+                    .map_err(|_| TransactionContextError::TooManyDataInputBoxes(data_boxes_len))?,
+            )
         } else {
             None
         };
@@ -53,11 +54,11 @@ impl<T: ErgoTransaction> TransactionContext<T> {
             if let Some(data_boxes) = data_boxes.as_ref() {
                 for (i, data_input) in data_inputs.iter().enumerate() {
                     if !data_boxes.iter().any(|b| data_input.box_id == b.box_id()) {
-                        return Err(TxSigningError::DataInputBoxNotFound(i));
+                        return Err(TransactionContextError::DataInputBoxNotFound(i));
                     }
                 }
             } else {
-                return Err(TxSigningError::DataInputBoxNotFound(0));
+                return Err(TransactionContextError::DataInputBoxNotFound(0));
             }
         }
         Ok(TransactionContext {
@@ -75,3 +76,26 @@ impl<T: ErgoTransaction> TransactionContext<T> {
             .cloned()
     }
 }
+
+/// Transaction context errors
+#[derive(Error, Debug)]
+pub enum TransactionContextError {
+    /// Transaction error
+    #[error("Transaction error: {0}")]
+    TransactionError(#[from] TransactionError),
+    /// No input boxes (boxes_to_spend is empty)
+    #[error("No input boxes")]
+    NoInputBoxes,
+    /// Too many input boxes
+    #[error("Too many input boxes: {0}")]
+    TooManyInputBoxes(usize),
+    /// Input box not found
+    #[error("Input box not found: {0}")]
+    InputBoxNotFound(usize),
+    /// Too many data input boxes
+    #[error("Too many data input boxes: {0}")]
+    TooManyDataInputBoxes(usize),
+    /// Data input box not found
+    #[error("Data input box not found: {0}")]
+    DataInputBoxNotFound(usize),
+}
diff --git a/ergotree-interpreter/src/eval/savltree.rs b/ergotree-interpreter/src/eval/savltree.rs
index ce7ce5f4f..456ad763c 100644
--- a/ergotree-interpreter/src/eval/savltree.rs
+++ b/ergotree-interpreter/src/eval/savltree.rs
@@ -429,7 +429,7 @@ fn map_eval_err<T: std::fmt::Debug>(e: T) -> EvalError {
     EvalError::AvlTree(format!("{:?}", e))
 }
 
-#[allow(clippy::unwrap_used, clippy::panic)]
+#[allow(clippy::unwrap_used, clippy::panic, clippy::unreachable)]
 #[cfg(test)]
 #[cfg(feature = "arbitrary")]
 mod tests {
diff --git a/ergotree-interpreter/src/eval/subst_const.rs b/ergotree-interpreter/src/eval/subst_const.rs
index f81f05b17..f99e52481 100644
--- a/ergotree-interpreter/src/eval/subst_const.rs
+++ b/ergotree-interpreter/src/eval/subst_const.rs
@@ -87,6 +87,7 @@ fn to_misc_err<T: std::fmt::Debug>(e: T) -> EvalError {
 #[allow(clippy::unwrap_used)]
 #[allow(clippy::panic)]
 #[allow(clippy::expect_used)]
+#[allow(clippy::unreachable)]
 mod tests {
     use ergotree_ir::{
         ergo_tree::{ErgoTree, ErgoTreeHeader},
diff --git a/ergotree-interpreter/src/eval/tree_lookup.rs b/ergotree-interpreter/src/eval/tree_lookup.rs
index b1607dc0b..379dfefb9 100644
--- a/ergotree-interpreter/src/eval/tree_lookup.rs
+++ b/ergotree-interpreter/src/eval/tree_lookup.rs
@@ -60,7 +60,7 @@ fn map_eval_err<T: std::fmt::Debug>(e: T) -> EvalError {
     EvalError::AvlTree(format!("{:?}", e))
 }
 
-#[allow(clippy::unwrap_used, clippy::panic)]
+#[allow(clippy::unwrap_used, clippy::panic, clippy::unreachable)]
 #[cfg(test)]
 mod tests {
 
diff --git a/ergotree-interpreter/src/lib.rs b/ergotree-interpreter/src/lib.rs
index 549d40ba7..00410c56b 100644
--- a/ergotree-interpreter/src/lib.rs
+++ b/ergotree-interpreter/src/lib.rs
@@ -16,6 +16,7 @@
 #![deny(clippy::expect_used)]
 #![deny(clippy::todo)]
 #![deny(clippy::unimplemented)]
+#![deny(clippy::unreachable)]
 #![deny(clippy::panic)]
 
 mod contracts;
diff --git a/ergotree-interpreter/src/sigma_protocol/prover/prover_result.rs b/ergotree-interpreter/src/sigma_protocol/prover/prover_result.rs
index 8cc229ae3..596f73f7d 100644
--- a/ergotree-interpreter/src/sigma_protocol/prover/prover_result.rs
+++ b/ergotree-interpreter/src/sigma_protocol/prover/prover_result.rs
@@ -65,6 +65,15 @@ impl TryFrom<String> for ProofBytes {
     }
 }
 
+impl AsRef<[u8]> for ProofBytes {
+    fn as_ref(&self) -> &[u8] {
+        match self {
+            ProofBytes::Empty => &[],
+            ProofBytes::Some(bytes) => bytes.as_ref(),
+        }
+    }
+}
+
 impl SigmaSerializable for ProofBytes {
     fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> SigmaSerializeResult {
         match self {
diff --git a/ergotree-ir/src/lib.rs b/ergotree-ir/src/lib.rs
index a4607f643..9f81748ec 100644
--- a/ergotree-ir/src/lib.rs
+++ b/ergotree-ir/src/lib.rs
@@ -16,6 +16,7 @@
 #![deny(clippy::expect_used)]
 #![deny(clippy::todo)]
 #![deny(clippy::unimplemented)]
+#![deny(clippy::unreachable)]
 #![deny(clippy::panic)]
 
 mod has_opcode;


Test patch: