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

Issue:
Transaction signature verification
Expose in JS.


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 10fd5b7b6..9154b998f 100644
--- a/bindings/ergo-lib-wasm/src/error_conversion.rs
+++ b/bindings/ergo-lib-wasm/src/error_conversion.rs
@@ -6,6 +6,7 @@ use std::num::ParseIntError;
 
 use base16::DecodeError;
 use bounded_vec::BoundedVecOutOfBounds;
+use ergo_lib::chain::transaction::TransactionSignatureVerificationError;
 use ergo_lib::ergo_chain_types::DigestNError;
 use ergo_lib::ergo_nipopow::NipopowProofError;
 #[cfg(feature = "rest")]
@@ -73,6 +74,7 @@ from_error_to_wrap!(WalletError);
 from_error_to_wrap!(DecodeError);
 from_error_to_wrap!(TryFromSliceError);
 from_error_to_wrap!(AddrParseError);
+from_error_to_wrap!(TransactionSignatureVerificationError);
 
 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 54e7d88a1..d72fc52cb 100644
--- a/bindings/ergo-lib-wasm/src/transaction.rs
+++ b/bindings/ergo-lib-wasm/src/transaction.rs
@@ -5,6 +5,7 @@ use crate::box_coll::ErgoBoxes;
 use crate::context_extension::ContextExtension;
 use crate::data_input::DataInputs;
 use crate::ergo_box::BoxId;
+use crate::ergo_box::ErgoBox;
 use crate::error_conversion::to_js;
 use crate::input::{Inputs, UnsignedInputs};
 use crate::json::TransactionJsonEip12;
@@ -267,6 +268,12 @@ impl Transaction {
             .map(Transaction)
             .map_err(to_js)
     }
+
+    /// Check the signature of the transaction's input corresponding
+    /// to the given input box, guarded by P2PK script
+    pub fn verify_p2pk_input(&self, input_box: ErgoBox) -> Result<bool, JsValue> {
+        self.0.verify_p2pk_input(input_box.into()).map_err(to_js)
+    }
 }
 
 impl From<chain::transaction::Transaction> for Transaction {
diff --git a/ergo-lib/src/chain/transaction.rs b/ergo-lib/src/chain/transaction.rs
index 50090f9ce..3cb993f0f 100644
--- a/ergo-lib/src/chain/transaction.rs
+++ b/ergo-lib/src/chain/transaction.rs
@@ -8,10 +8,16 @@ pub mod unsigned;
 use bounded_vec::BoundedVec;
 use ergo_chain_types::blake2b256_hash;
 pub use ergotree_interpreter::eval::context::TxIoVec;
+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::VerifierError;
+use ergotree_ir::chain::ergo_box::BoxId;
 use ergotree_ir::chain::ergo_box::ErgoBox;
 use ergotree_ir::chain::ergo_box::ErgoBoxCandidate;
 use ergotree_ir::chain::token::TokenId;
 pub use ergotree_ir::chain::tx_id::TxId;
+use ergotree_ir::ergo_tree::ErgoTreeError;
 use thiserror::Error;
 
 pub use data_input::*;
@@ -164,6 +170,43 @@ impl Transaction {
     pub fn id(&self) -> TxId {
         self.tx_id.clone()
     }
+
+    /// Check the signature of the transaction's input corresponding
+    /// to the given input box, guarded by P2PK script
+    pub fn verify_p2pk_input(
+        &self,
+        input_box: ErgoBox,
+    ) -> Result<bool, TransactionSignatureVerificationError> {
+        #[allow(clippy::unwrap_used)]
+        // since we have a tx with tx_id at this point, serialization is safe to unwrap
+        let message = self.bytes_to_sign().unwrap();
+        let input = self
+            .inputs
+            .iter()
+            .find(|input| input.box_id == input_box.box_id())
+            .ok_or_else(|| {
+                TransactionSignatureVerificationError::InputNotFound(input_box.box_id())
+            })?;
+        let sb = extract_sigma_boolean(input_box.ergo_tree.proposition()?.as_ref())?;
+        Ok(verify_signature(
+            sb,
+            message.as_slice(),
+            input.spending_proof.proof.clone().to_bytes().as_slice(),
+        )?)
+    }
+}
+
+#[allow(missing_docs)]
+#[derive(Error, Debug)]
+pub enum TransactionSignatureVerificationError {
+    #[error("Input with id {0:?} not found")]
+    InputNotFound(BoxId),
+    #[error("input signature verification failed: {0:?}")]
+    VerifierError(#[from] VerifierError),
+    #[error("ErgoTreeError: {0}")]
+    ErgoTreeError(#[from] ErgoTreeError),
+    #[error("EvalError: {0}")]
+    EvalError(#[from] EvalError),
 }
 
 /// Returns distinct token ids from all given ErgoBoxCandidate's
diff --git a/ergotree-interpreter/src/eval.rs b/ergotree-interpreter/src/eval.rs
index cc3f28dd0..110793184 100644
--- a/ergotree-interpreter/src/eval.rs
+++ b/ergotree-interpreter/src/eval.rs
@@ -1,5 +1,7 @@
 //! Interpreter
 use bounded_vec::BoundedVecOutOfBounds;
+use ergotree_ir::mir::constant::TryExtractInto;
+use ergotree_ir::sigma_protocol::sigma_boolean::SigmaProp;
 use sigma_ser::ScorexParsingError;
 use sigma_ser::ScorexSerializationError;
 use std::rc::Rc;
@@ -188,6 +190,14 @@ pub fn reduce_to_crypto(
         })
 }
 
+/// Expects SigmaProp constant value and returns it's value. Otherwise, returns an error.
+pub fn extract_sigma_boolean(expr: &Expr) -> Result<SigmaBoolean, EvalError> {
+    match expr {
+        Expr::Const(c) => Ok(c.clone().try_extract_into::<SigmaProp>()?.into()),
+        _ => Err(EvalError::InvalidResultType),
+    }
+}
+
 #[derive(Debug)]
 pub(crate) struct EvalContext {
     pub(crate) ctx: Rc<Context>,


Test patch: