Repository: CosmWasm/cosmwasm
Pull request #785 (2021-02-15 17:28:27)

Crypto api errors
A small PR for implementing returning errors from the crypto API.

maurolacy: Merging this, to move forward. We can always continue in the base branch.

Patch:
diff --git a/README.md b/README.md
index d49903320..5346335d1 100644
--- a/README.md
+++ b/README.md
@@ -136,7 +136,8 @@ extern "C" {
 
     /// Verifies message hashes against a signature with a public key, using the
     /// secp256k1 ECDSA parametrization.
-    /// Returns 1 on verification success and 0 on failure.
+    /// Returns 0 on verification success, 1 on verification failure, and values
+    /// greater than 1 in case of error.
     fn secp256k1_verify(message_hash_ptr: u32, signature_ptr: u32, public_key_ptr: u32) -> u32;
 
     /// Executes a query on the chain (import). Not to be confused with the
diff --git a/contracts/crypto-verify/README.md b/contracts/crypto-verify/README.md
index 143a966eb..64343955a 100644
--- a/contracts/crypto-verify/README.md
+++ b/contracts/crypto-verify/README.md
@@ -12,13 +12,13 @@ Input formats are serialized byte slices for Message, Signature, and Public Key.
 
 - Message: A serialized message. It will be hashed by the contract using SHA-256, and the hashed value will be fed to the
   verification function.
-- Signature:  Serialized signature, in Cosmos format (64 bytes). Ethereum DER needs to be converted.
-- Public Key:  Compressed (33 bytes) or uncompressed (65 bytes) serialized public key.
+- Signature:  Serialized signature, in "compact" Cosmos format (64 bytes). Ethereum DER needs to be converted.
+- Public Key:  Compressed (33 bytes) or uncompressed (65 bytes) serialized public key, in SEC format.
 
 Output is a boolean value indicating if verification succeeded or not.
 
 
 ## Remarks
 
-In case of an error (wrong or unsupported inputs), the current (mock) implementation panics. This can be easily debugged,
-and optimizes for the production use case, where there is nothing the contract developer should or could handle.
+In case of an error (wrong or unsupported inputs), the current implementation returns an error, which can be
+easily handled by the contract, or returned to the client.
diff --git a/contracts/crypto-verify/src/contract.rs b/contracts/crypto-verify/src/contract.rs
index bb723cb93..99fd770a5 100644
--- a/contracts/crypto-verify/src/contract.rs
+++ b/contracts/crypto-verify/src/contract.rs
@@ -52,9 +52,11 @@ pub fn query_verify(
     let hash = Sha256::digest(message);
 
     // Verification
-    let verifies = deps.api.secp256k1_verify(&*hash, signature, public_key);
-
-    Ok(VerifyResponse { verifies })
+    let result = deps.api.secp256k1_verify(&*hash, signature, public_key);
+    match result {
+        Ok(verifies) => Ok(VerifyResponse { verifies }),
+        Err(err) => Err(err.into()),
+    }
 }
 
 pub fn query_list_verifications(deps: Deps) -> StdResult<ListVerificationsResponse> {
@@ -70,7 +72,7 @@ mod tests {
     use cosmwasm_std::testing::{
         mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage,
     };
-    use cosmwasm_std::{from_slice, Binary, OwnedDeps};
+    use cosmwasm_std::{from_slice, Binary, OwnedDeps, StdError, VerificationError};
 
     const CREATOR: &str = "creator";
 
@@ -135,8 +137,7 @@ mod tests {
     }
 
     #[test]
-    #[should_panic(expected = "empty")]
-    fn verify_panics() {
+    fn verify_errors() {
         let deps = setup();
 
         let message = hex::decode(MESSAGE_HEX).unwrap();
@@ -148,7 +149,14 @@ mod tests {
             signature: Binary(signature),
             public_key: Binary(public_key),
         };
-        query(deps.as_ref(), mock_env(), verify_msg).unwrap();
+        let res = query(deps.as_ref(), mock_env(), verify_msg);
+        assert!(res.is_err());
+        assert_eq!(
+            res.unwrap_err(),
+            StdError::VerificationErr {
+                source: VerificationError::PublicKeyErr
+            }
+        )
     }
 
     #[test]
diff --git a/packages/crypto/src/errors.rs b/packages/crypto/src/errors.rs
index ccf474a37..bac021941 100644
--- a/packages/crypto/src/errors.rs
+++ b/packages/crypto/src/errors.rs
@@ -6,29 +6,32 @@ use thiserror::Error;
 pub type CryptoResult<T> = core::result::Result<T, CryptoError>;
 
 #[derive(Error, Debug)]
-#[non_exhaustive]
 pub enum CryptoError {
     #[error("Crypto error: {msg}")]
     GenericErr {
         msg: String,
+        error_code: u32,
         #[cfg(feature = "backtraces")]
         backtrace: Backtrace,
     },
     #[error("Hash error: {msg}")]
     HashErr {
         msg: String,
+        error_code: u32,
         #[cfg(feature = "backtraces")]
         backtrace: Backtrace,
     },
     #[error("Signature error: {msg}")]
     SignatureErr {
         msg: String,
+        error_code: u32,
         #[cfg(feature = "backtraces")]
         backtrace: Backtrace,
     },
     #[error("Public key error: {msg}")]
     PublicKeyErr {
         msg: String,
+        error_code: u32,
         #[cfg(feature = "backtraces")]
         backtrace: Backtrace,
     },
@@ -38,6 +41,7 @@ impl CryptoError {
     pub fn generic_err<S: Into<String>>(msg: S) -> Self {
         CryptoError::GenericErr {
             msg: msg.into(),
+            error_code: 10,
             #[cfg(feature = "backtraces")]
             backtrace: Backtrace::capture(),
         }
@@ -46,6 +50,7 @@ impl CryptoError {
     pub fn hash_err<S: Into<String>>(msg: S) -> Self {
         CryptoError::HashErr {
             msg: msg.into(),
+            error_code: 3,
             #[cfg(feature = "backtraces")]
             backtrace: Backtrace::capture(),
         }
@@ -54,6 +59,7 @@ impl CryptoError {
     pub fn sig_err<S: Into<String>>(msg: S) -> Self {
         CryptoError::SignatureErr {
             msg: msg.into(),
+            error_code: 4,
             #[cfg(feature = "backtraces")]
             backtrace: Backtrace::capture(),
         }
@@ -62,6 +68,7 @@ impl CryptoError {
     pub fn pubkey_err<S: Into<String>>(msg: S) -> Self {
         CryptoError::PublicKeyErr {
             msg: msg.into(),
+            error_code: 5,
             #[cfg(feature = "backtraces")]
             backtrace: Backtrace::capture(),
         }
diff --git a/packages/crypto/src/lib.rs b/packages/crypto/src/lib.rs
index 4a9d23a27..db21efde3 100644
--- a/packages/crypto/src/lib.rs
+++ b/packages/crypto/src/lib.rs
@@ -4,6 +4,12 @@ mod crypto;
 mod errors;
 mod identity_digest;
 
+/// The crypto crate is intended to be used in internal crates / utils.
+/// Please don't use any of these types directly, as
+/// they might change frequently, or be removed in the future.
+#[doc(hidden)]
 pub use crate::crypto::secp256k1_verify;
+#[doc(hidden)]
 pub use crate::crypto::{MESSAGE_HASH_MAX_LENGTH, PUBKEY_MAX_LENGTH, SIGNATURE_MAX_LENGTH};
+#[doc(hidden)]
 pub use crate::errors::{CryptoError, CryptoResult};
diff --git a/packages/std/src/errors/mod.rs b/packages/std/src/errors/mod.rs
index dddea3793..598b847f0 100644
--- a/packages/std/src/errors/mod.rs
+++ b/packages/std/src/errors/mod.rs
@@ -1,5 +1,7 @@
 mod std_error;
 mod system_error;
+mod verification_error;
 
 pub use std_error::{StdError, StdResult};
 pub use system_error::SystemError;
+pub use verification_error::VerificationError;
diff --git a/packages/std/src/errors/std_error.rs b/packages/std/src/errors/std_error.rs
index 80cf39082..3f0e2d3b8 100644
--- a/packages/std/src/errors/std_error.rs
+++ b/packages/std/src/errors/std_error.rs
@@ -2,6 +2,8 @@
 use std::backtrace::Backtrace;
 use thiserror::Error;
 
+use crate::errors::VerificationError;
+
 /// Structured error type for init, handle and query.
 ///
 /// This can be serialized and passed over the Wasm/VM boundary, which allows us to use structured
@@ -19,6 +21,12 @@ use thiserror::Error;
 /// - Add creator function in std_error_helpers.rs
 #[derive(Error, Debug)]
 pub enum StdError {
+    #[error("Verification error: {source}")]
+    VerificationErr {
+        source: VerificationError,
+        #[cfg(feature = "backtraces")]
+        backtrace: Backtrace,
+    },
     /// Whenever there is no specific error type available
     #[error("Generic error: {msg}")]
     GenericErr {
@@ -78,6 +86,14 @@ pub enum StdError {
 }
 
 impl StdError {
+    pub fn verification_err(source: VerificationError) -> Self {
+        StdError::VerificationErr {
+            source,
+            #[cfg(feature = "backtraces")]
+            backtrace: Backtrace::capture(),
+        }
+    }
+
     pub fn generic_err<S: Into<String>>(msg: S) -> Self {
         StdError::GenericErr {
             msg: msg.into(),
@@ -151,6 +167,22 @@ impl StdError {
 impl PartialEq<StdError> for StdError {
     fn eq(&self, rhs: &StdError) -> bool {
         match self {
+            StdError::VerificationErr {
+                source,
+                #[cfg(feature = "backtraces")]
+                    backtrace: _,
+            } => {
+                if let StdError::VerificationErr {
+                    source: rhs_source,
+                    #[cfg(feature = "backtraces")]
+                        backtrace: _,
+                } = rhs
+                {
+                    source == rhs_source
+                } else {
+                    false
+                }
+            }
             StdError::GenericErr {
                 msg,
                 #[cfg(feature = "backtraces")]
@@ -303,6 +335,12 @@ impl From<std::string::FromUtf8Error> for StdError {
     }
 }
 
+impl From<VerificationError> for StdError {
+    fn from(source: VerificationError) -> Self {
+        Self::verification_err(source)
+    }
+}
+
 /// The return type for init, handle and query. Since the error type cannot be serialized to JSON,
 /// this is only available within the contract and its unit tests.
 ///
diff --git a/packages/std/src/errors/verification_error.rs b/packages/std/src/errors/verification_error.rs
new file mode 100644
index 000000000..7e517a0c8
--- /dev/null
+++ b/packages/std/src/errors/verification_error.rs
@@ -0,0 +1,84 @@
+#[cfg(feature = "backtraces")]
+use std::backtrace::Backtrace;
+use std::fmt::Debug;
+use thiserror::Error;
+
+#[cfg(not(target_arch = "wasm32"))]
+use cosmwasm_crypto::CryptoError;
+
+#[derive(Error, Debug)]
+pub enum VerificationError {
+    #[error("Generic error")]
+    GenericErr,
+    #[error("Hash error")]
+    HashErr,
+    #[error("Signature error")]
+    SignatureErr,
+    #[error("Public key error")]
+    PublicKeyErr,
+    #[error("Unknown error: {error_code}")]
+    UnknownErr {
+        error_code: u32,
+        #[cfg(feature = "backtraces")]
+        backtrace: Backtrace,
+    },
+}
+
+impl VerificationError {
+    pub fn unknown_err(error_code: u32) -> Self {
+        VerificationError::UnknownErr {
+            error_code,
+            #[cfg(feature = "backtraces")]
+            backtrace: Backtrace::capture(),
+        }
+    }
+}
+
+impl PartialEq<VerificationError> for VerificationError {
+    fn eq(&self, rhs: &VerificationError) -> bool {
+        match self {
+            VerificationError::GenericErr => matches!(rhs, VerificationError::GenericErr),
+            VerificationError::HashErr => matches!(rhs, VerificationError::HashErr),
+            VerificationError::SignatureErr => matches!(rhs, VerificationError::SignatureErr),
+            VerificationError::PublicKeyErr => matches!(rhs, VerificationError::PublicKeyErr),
+            VerificationError::UnknownErr { error_code, .. } => {
+                if let VerificationError::UnknownErr {
+                    error_code: rhs_error_code,
+                    ..
+                } = rhs
+                {
+                    error_code == rhs_error_code
+                } else {
+                    false
+                }
+            }
+        }
+    }
+}
+
+#[cfg(not(target_arch = "wasm32"))]
+impl From<CryptoError> for VerificationError {
+    fn from(original: CryptoError) -> Self {
+        match original {
+            CryptoError::HashErr { .. } => VerificationError::HashErr,
+            CryptoError::SignatureErr { .. } => VerificationError::SignatureErr,
+            CryptoError::PublicKeyErr { .. } => VerificationError::PublicKeyErr,
+            CryptoError::GenericErr { .. } => VerificationError::GenericErr,
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    // constructors
+    #[test]
+    fn unknown_err_works() {
+        let error = VerificationError::unknown_err(123);
+        match error {
+            VerificationError::UnknownErr { error_code, .. } => assert_eq!(error_code, 123),
+            _ => panic!("wrong error type!"),
+        }
+    }
+}
diff --git a/packages/std/src/imports.rs b/packages/std/src/imports.rs
index 264719ba5..b75f500ff 100644
--- a/packages/std/src/imports.rs
+++ b/packages/std/src/imports.rs
@@ -2,7 +2,7 @@ use std::vec::Vec;
 
 use crate::addresses::{CanonicalAddr, HumanAddr};
 use crate::binary::Binary;
-use crate::errors::{StdError, StdResult, SystemError};
+use crate::errors::{StdError, StdResult, SystemError, VerificationError};
 use crate::memory::{alloc, build_region, consume_region, Region};
 use crate::results::SystemResult;
 #[cfg(feature = "iterator")]
@@ -182,7 +182,12 @@ impl Api for ExternalApi {
         Ok(address.into())
     }
 
-    fn secp256k1_verify(&self, message_hash: &[u8], signature: &[u8], public_key: &[u8]) -> bool {
+    fn secp256k1_verify(
+        &self,
+        message_hash: &[u8],
+        signature: &[u8],
+        public_key: &[u8],
+    ) -> Result<bool, VerificationError> {
         let hash_send = build_region(message_hash);
         let hash_send_ptr = &*hash_send as *const Region as u32;
         let sig_send = build_region(signature);
@@ -192,9 +197,13 @@ impl Api for ExternalApi {
 
         let result = unsafe { secp256k1_verify(hash_send_ptr, sig_send_ptr, pubkey_send_ptr) };
         match result {
-            0 => false,
-            1 => true,
-            _ => unreachable!(),
+            0 => Ok(true),
+            1 => Ok(false),
+            3 => Err(VerificationError::HashErr),
+            4 => Err(VerificationError::SignatureErr),
+            5 => Err(VerificationError::PublicKeyErr),
+            10 => Err(VerificationError::GenericErr),
+            error_code => Err(VerificationError::unknown_err(error_code)),
         }
     }
 
diff --git a/packages/std/src/lib.rs b/packages/std/src/lib.rs
index 2c892b35b..d8cb610bd 100644
--- a/packages/std/src/lib.rs
+++ b/packages/std/src/lib.rs
@@ -25,7 +25,7 @@ pub use crate::addresses::{CanonicalAddr, HumanAddr};
 pub use crate::binary::{Binary, ByteArray};
 pub use crate::coins::{coin, coins, has_coins, Coin};
 pub use crate::deps::{Deps, DepsMut, OwnedDeps};
-pub use crate::errors::{StdError, StdResult, SystemError};
+pub use crate::errors::{StdError, StdResult, SystemError, VerificationError};
 #[cfg(feature = "stargate")]
 pub use crate::ibc::{
     ChannelResponse, IbcAcknowledgement, IbcBasicResponse, IbcChannel, IbcEndpoint, IbcMsg,
diff --git a/packages/std/src/mock.rs b/packages/std/src/mock.rs
index 4c91746b6..db10c90d9 100644
--- a/packages/std/src/mock.rs
+++ b/packages/std/src/mock.rs
@@ -7,7 +7,7 @@ use crate::addresses::{CanonicalAddr, HumanAddr};
 use crate::binary::Binary;
 use crate::coins::Coin;
 use crate::deps::OwnedDeps;
-use crate::errors::{StdError, StdResult, SystemError};
+use crate::errors::{StdError, StdResult, SystemError, VerificationError};
 #[cfg(feature = "stargate")]
 use crate::ibc::{IbcChannel, IbcEndpoint, IbcOrder, IbcPacket, IbcTimeoutBlock};
 use crate::query::{
@@ -120,12 +120,17 @@ impl Api for MockApi {
         Ok(human.into())
     }
 
-    fn secp256k1_verify(&self, message_hash: &[u8], signature: &[u8], public_key: &[u8]) -> bool {
-        let result = cosmwasm_crypto::secp256k1_verify(message_hash, signature, public_key);
-        match result {
-            Ok(v) => v,
-            Err(err) => panic!("secp256k1_verify error: {:?}", err),
-        }
+    fn secp256k1_verify(
+        &self,
+        message_hash: &[u8],
+        signature: &[u8],
+        public_key: &[u8],
+    ) -> Result<bool, VerificationError> {
+        Ok(cosmwasm_crypto::secp256k1_verify(
+            message_hash,
+            signature,
+            public_key,
+        )?)
     }
 
     fn debug(&self, message: &str) {
@@ -534,7 +539,9 @@ mod tests {
         let signature = hex::decode(SECP256K1_SIG_HEX).unwrap();
         let public_key = hex::decode(SECP256K1_PUBKEY_HEX).unwrap();
 
-        assert!(api.secp256k1_verify(&hash, &signature, &public_key));
+        assert!(api
+            .secp256k1_verify(&hash, &signature, &public_key)
+            .unwrap());
     }
 
     // Basic "fails" test. Exhaustive tests on VM's side (packages/vm/src/imports.rs)
@@ -548,20 +555,23 @@ mod tests {
         let signature = hex::decode(SECP256K1_SIG_HEX).unwrap();
         let public_key = hex::decode(SECP256K1_PUBKEY_HEX).unwrap();
 
-        assert!(!api.secp256k1_verify(&hash, &signature, &public_key));
+        assert!(!api
+            .secp256k1_verify(&hash, &signature, &public_key)
+            .unwrap());
     }
 
-    // Basic "panics" test. Exhaustive tests on VM's side (packages/vm/src/imports.rs)
+    // Basic "errors" test. Exhaustive tests on VM's side (packages/vm/src/imports.rs)
     #[test]
-    #[should_panic(expected = "empty")]
-    fn secp256k1_verify_panics() {
+    fn secp256k1_verify_errs() {
         let api = MockApi::default();
 
         let hash = hex::decode(SECP256K1_MSG_HASH_HEX).unwrap();
         let signature = hex::decode(SECP256K1_SIG_HEX).unwrap();
         let public_key = vec![];
 
-        assert!(!api.secp256k1_verify(&hash, &signature, &public_key));
+        let res = api.secp256k1_verify(&hash, &signature, &public_key);
+
+        assert_eq!(res.unwrap_err(), VerificationError::PublicKeyErr);
     }
 
     #[test]
diff --git a/packages/std/src/traits.rs b/packages/std/src/traits.rs
index 93d1d0bc2..8375a9464 100644
--- a/packages/std/src/traits.rs
+++ b/packages/std/src/traits.rs
@@ -4,7 +4,7 @@ use std::ops::Deref;
 use crate::addresses::{CanonicalAddr, HumanAddr};
 use crate::binary::Binary;
 use crate::coins::Coin;
-use crate::errors::{StdError, StdResult};
+use crate::errors::{StdError, StdResult, VerificationError};
 #[cfg(feature = "iterator")]
 use crate::iterator::{Order, KV};
 use crate::query::{
@@ -67,7 +67,12 @@ pub trait Api {
     fn canonical_address(&self, human: &HumanAddr) -> StdResult<CanonicalAddr>;
     fn human_address(&self, canonical: &CanonicalAddr) -> StdResult<HumanAddr>;
 
-    fn secp256k1_verify(&self, message_hash: &[u8], signature: &[u8], public_key: &[u8]) -> bool;
+    fn secp256k1_verify(
+        &self,
+        message_hash: &[u8],
+        signature: &[u8],
+        public_key: &[u8],
+    ) -> Result<bool, VerificationError>;
 
     /// Emits a debugging message that is handled depending on the environment (typically printed to console or ignored).
     /// Those messages are not persisted to chain.
diff --git a/packages/vm/src/imports.rs b/packages/vm/src/imports.rs
index f2ce1c0c1..9752de4b9 100644
--- a/packages/vm/src/imports.rs
+++ b/packages/vm/src/imports.rs
@@ -4,6 +4,7 @@
 use std::convert::TryInto;
 
 use cosmwasm_crypto::secp256k1_verify;
+use cosmwasm_crypto::CryptoError;
 use cosmwasm_crypto::{MESSAGE_HASH_MAX_LENGTH, PUBKEY_MAX_LENGTH, SIGNATURE_MAX_LENGTH};
 
 #[cfg(feature = "iterator")]
@@ -263,7 +264,15 @@ fn do_secp256k1_verify<A: BackendApi, S: Storage, Q: Querier>(
     let result = secp256k1_verify(&hash, &signature, &pubkey);
     let gas_info = GasInfo::with_cost(GAS_COST_VERIFY_SECP256K1_SIGNATURE);
     process_gas_info::<A, S, Q>(env, gas_info)?;
-    Ok(result?.into())
+    // Ok((!(result?)).into())
+    match result {
+        Ok(true) => Ok(0),
+        Ok(false) => Ok(1),
+        Err(CryptoError::HashErr { error_code, .. }) => Ok(error_code),
+        Err(CryptoError::SignatureErr { error_code, .. }) => Ok(error_code),
+        Err(CryptoError::PublicKeyErr { error_code, .. }) => Ok(error_code),
+        Err(CryptoError::GenericErr { error_code, .. }) => Ok(error_code),
+    }
 }
 
 /// Creates a Region in the contract, writes the given data to it and returns the memory location
@@ -363,7 +372,6 @@ fn encode_sections(sections: &[Vec<u8>]) -> VmResult<Vec<u8>> {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use cosmwasm_crypto::CryptoError;
     use cosmwasm_std::{
         coins, from_binary, AllBalanceResponse, BankQuery, Binary, Empty, HumanAddr, QueryRequest,
         SystemError, SystemResult, WasmQuery,
@@ -973,7 +981,7 @@ mod tests {
 
         assert_eq!(
             do_secp256k1_verify::<MA, MS, MQ>(&env, hash_ptr, sig_ptr, pubkey_ptr).unwrap(),
-            1
+            0
         );
     }
 
@@ -993,7 +1001,7 @@ mod tests {
 
         assert_eq!(
             do_secp256k1_verify::<MA, MS, MQ>(&env, hash_ptr, sig_ptr, pubkey_ptr).unwrap(),
-            0
+            1
         );
     }
 
@@ -1035,17 +1043,10 @@ mod tests {
         let pubkey = hex::decode(ECDSA_PUBKEY_HEX).unwrap();
         let pubkey_ptr = write_data(&env, &pubkey);
 
-        let result = do_secp256k1_verify::<MA, MS, MQ>(&env, hash_ptr, sig_ptr, pubkey_ptr);
-        match result.unwrap_err() {
-            VmError::CryptoErr {
-                source: CryptoError::HashErr { msg, .. },
-                ..
-            } => assert_eq!(
-                msg,
-                format!("wrong length: {}", MESSAGE_HASH_MAX_LENGTH - 1)
-            ),
-            e => panic!("Unexpected error: {:?}", e),
-        }
+        assert_eq!(
+            do_secp256k1_verify::<MA, MS, MQ>(&env, hash_ptr, sig_ptr, pubkey_ptr).unwrap(),
+            3 // mapped HashErr
+        );
     }
 
     #[test]
@@ -1064,7 +1065,7 @@ mod tests {
 
         assert_eq!(
             do_secp256k1_verify::<MA, MS, MQ>(&env, hash_ptr, sig_ptr, pubkey_ptr).unwrap(),
-            0
+            1
         );
     }
 
@@ -1106,17 +1107,10 @@ mod tests {
         let pubkey = hex::decode(ECDSA_PUBKEY_HEX).unwrap();
         let pubkey_ptr = write_data(&env, &pubkey);
 
-        let result = do_secp256k1_verify::<MA, MS, MQ>(&env, hash_ptr, sig_ptr, pubkey_ptr);
-        match result.unwrap_err() {
-            VmError::CryptoErr {
-                source: CryptoError::SignatureErr { msg, .. },
-                ..
-            } => assert_eq!(
-                msg,
-                format!("wrong / unsupported length: {}", SIGNATURE_MAX_LENGTH - 1)
-            ),
-            e => panic!("Unexpected error: {:?}", e),
-        }
+        assert_eq!(
+            do_secp256k1_verify::<MA, MS, MQ>(&env, hash_ptr, sig_ptr, pubkey_ptr).unwrap(),
+            4 // mapped SignatureErr
+        )
     }
 
     #[test]
@@ -1133,17 +1127,10 @@ mod tests {
         pubkey[0] ^= 0x01;
         let pubkey_ptr = write_data(&env, &pubkey);
 
-        let result = do_secp256k1_verify::<MA, MS, MQ>(&env, hash_ptr, sig_ptr, pubkey_ptr);
-        match result.unwrap_err() {
-            VmError::CryptoErr {
-                source: CryptoError::PublicKeyErr { msg, .. },
-                ..
-            } => assert_eq!(
-                msg,
-                format!("wrong / unsupported length/format: {}/5", PUBKEY_MAX_LENGTH)
-            ),
-            err => panic!("Incorrect error returned: {:?}", err),
-        }
+        assert_eq!(
+            do_secp256k1_verify::<MA, MS, MQ>(&env, hash_ptr, sig_ptr, pubkey_ptr).unwrap(),
+            5 // mapped PublicKeyError
+        )
     }
 
     #[test]
@@ -1160,14 +1147,10 @@ mod tests {
         pubkey[1] ^= 0x01;
         let pubkey_ptr = write_data(&env, &pubkey);
 
-        let result = do_secp256k1_verify::<MA, MS, MQ>(&env, hash_ptr, sig_ptr, pubkey_ptr);
-        match result.unwrap_err() {
-            VmError::CryptoErr {
-                source: CryptoError::GenericErr { msg, .. },
-                ..
-            } => assert_eq!(msg, "signature error"),
-            err => panic!("Incorrect error returned: {:?}", err),
-        }
+        assert_eq!(
+            do_secp256k1_verify::<MA, MS, MQ>(&env, hash_ptr, sig_ptr, pubkey_ptr).unwrap(),
+            10 // mapped GenericErr
+        )
     }
 
     #[test]
@@ -1207,21 +1190,10 @@ mod tests {
         // reduce / break pubkey
         pubkey.pop();
         let pubkey_ptr = write_data(&env, &pubkey);
-
-        let result = do_secp256k1_verify::<MA, MS, MQ>(&env, hash_ptr, sig_ptr, pubkey_ptr);
-        match result.unwrap_err() {
-            VmError::CryptoErr {
-                source: CryptoError::PublicKeyErr { msg, .. },
-                ..
-            } => assert_eq!(
-                msg,
-                format!(
-                    "wrong / unsupported length/format: {}/4",
-                    PUBKEY_MAX_LENGTH - 1
-                )
-            ),
-            err => panic!("Incorrect error returned: {:?}", err),
-        }
+        assert_eq!(
+            do_secp256k1_verify::<MA, MS, MQ>(&env, hash_ptr, sig_ptr, pubkey_ptr).unwrap(),
+            5 // mapped PublicKeyErr
+        )
     }
 
     #[test]
@@ -1236,14 +1208,10 @@ mod tests {
         let pubkey = vec![];
         let pubkey_ptr = write_data(&env, &pubkey);
 
-        let result = do_secp256k1_verify::<MA, MS, MQ>(&env, hash_ptr, sig_ptr, pubkey_ptr);
-        match result.unwrap_err() {
-            VmError::CryptoErr {
-                source: CryptoError::PublicKeyErr { msg, .. },
-                ..
-            } => assert_eq!(msg, "empty"),
-            err => panic!("Incorrect error returned: {:?}", err),
-        }
+        assert_eq!(
+            do_secp256k1_verify::<MA, MS, MQ>(&env, hash_ptr, sig_ptr, pubkey_ptr).unwrap(),
+            5 // mapped PublicKeyError
+        )
     }
 
     #[test]
@@ -1258,14 +1226,10 @@ mod tests {
         let pubkey = vec![0x04; PUBKEY_MAX_LENGTH];
         let pubkey_ptr = write_data(&env, &pubkey);
 
-        let result = do_secp256k1_verify::<MA, MS, MQ>(&env, hash_ptr, sig_ptr, pubkey_ptr);
-        match result.unwrap_err() {
-            VmError::CryptoErr {
-                source: CryptoError::GenericErr { msg, .. },
-                ..
-            } => assert_eq!(msg, "signature error"),
-            err => panic!("Incorrect error returned: {:?}", err),
-        }
+        assert_eq!(
+            do_secp256k1_verify::<MA, MS, MQ>(&env, hash_ptr, sig_ptr, pubkey_ptr).unwrap(),
+            10 // mapped GenericErr
+        )
     }
 
     #[test]
diff --git a/packages/vm/src/instance.rs b/packages/vm/src/instance.rs
index e2167f8c1..8027d6d13 100644
--- a/packages/vm/src/instance.rs
+++ b/packages/vm/src/instance.rs
@@ -123,7 +123,7 @@ where
         );
 
         // Verifies message hashes against a signature with a public key, using the secp256k1 ECDSA parametrization.
-        // Returns 1 on verification success and 0 on failure.
+        // Returns 0 on verification success, 1 on verification failure, and values greater than 1 in case of error.
         // Ownership of input pointers is not transferred to the host.
         env_imports.insert(
             "secp256k1_verify",


Test patch:
diff --git a/contracts/crypto-verify/tests/integration.rs b/contracts/crypto-verify/tests/integration.rs
index 8dfa4aec1..03a40cbee 100644
--- a/contracts/crypto-verify/tests/integration.rs
+++ b/contracts/crypto-verify/tests/integration.rs
@@ -94,8 +94,7 @@ fn verify_fails() {
 }
 
 #[test]
-#[should_panic(expected = "empty")]
-fn verify_panics() {
+fn verify_errors() {
     let mut deps = setup();
 
     let message = hex::decode(MESSAGE_HEX).unwrap();
@@ -107,7 +106,8 @@ fn verify_panics() {
         signature: Binary(signature),
         public_key: Binary(public_key),
     };
-    let _ = query(&mut deps, mock_env(), verify_msg).unwrap();
+    let res = query(&mut deps, mock_env(), verify_msg);
+    assert_eq!(res.unwrap_err(), "Verification error: Public key error")
 }
 
 #[test]