From b18475c98c8afffdb8970c8b6367e7c8d5d0bb24 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Wed, 26 Jul 2023 14:00:28 +1000 Subject: [PATCH] Introduce specific errors for individual use cases This PR attempts to introduce a new convention for error handling. The aim of which is to make errors more informative to help debugging, to make the code easier to read, and to help stabalize the API. Each function that returns a `Result` returns is made to return an error that is either a struct or an enum in which _every_ variant is used. We then provide a general purpose error for the crate and conversion functions to it from all the other error types. --- examples/sign_verify_recovery.rs | 4 +- src/context.rs | 28 +++- src/ecdh.rs | 23 ++- src/ecdsa/mod.rs | 113 +++++++++++--- src/ecdsa/recovery.rs | 49 +++++-- src/ecdsa/serialized_signature.rs | 5 +- src/error.rs | 236 ++++++++++++++++++++++++++++++ src/key/error.rs | 122 +++++++++++++++ src/{key.rs => key/mod.rs} | 179 ++++++++++------------ src/lib.rs | 185 +++++++++++++---------- src/macros.rs | 19 --- src/schnorr.rs | 45 +++--- 12 files changed, 740 insertions(+), 268 deletions(-) create mode 100644 src/error.rs create mode 100644 src/key/error.rs rename src/{key.rs => key/mod.rs} (94%) diff --git a/examples/sign_verify_recovery.rs b/examples/sign_verify_recovery.rs index edcc7b3bc..42370b48d 100644 --- a/examples/sign_verify_recovery.rs +++ b/examples/sign_verify_recovery.rs @@ -4,6 +4,8 @@ extern crate secp256k1; use bitcoin_hashes::{sha256, Hash}; use secp256k1::{ecdsa, Error, Message, PublicKey, Secp256k1, SecretKey, Signing, Verification}; +// Notice that we provide a general error type for this crate and conversion +// functions to it from all the other error types so `?` works as expected. fn recover( secp: &Secp256k1, msg: &[u8], @@ -15,7 +17,7 @@ fn recover( let id = ecdsa::RecoveryId::from_i32(recovery_id as i32)?; let sig = ecdsa::RecoverableSignature::from_compact(&sig, id)?; - secp.recover_ecdsa(&msg, &sig) + Ok(secp.recover_ecdsa(&msg, &sig)?) } fn sign_recovery( diff --git a/src/context.rs b/src/context.rs index 61ab985ad..45d7cba35 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,5 +1,6 @@ // SPDX-License-Identifier: CC0-1.0 +use core::fmt; use core::marker::PhantomData; use core::mem::ManuallyDrop; use core::ptr::NonNull; @@ -8,7 +9,7 @@ use core::ptr::NonNull; pub use self::alloc_only::*; use crate::ffi::types::{c_uint, c_void, AlignedType}; use crate::ffi::{self, CPtr}; -use crate::{Error, Secp256k1}; +use crate::Secp256k1; #[cfg(all(feature = "global-context", feature = "std"))] /// Module implementing a singleton pattern for a global `Secp256k1` context. @@ -320,14 +321,29 @@ unsafe impl<'buf> PreallocatedContext<'buf> for AllPreallocated<'buf> {} unsafe impl<'buf> PreallocatedContext<'buf> for SignOnlyPreallocated<'buf> {} unsafe impl<'buf> PreallocatedContext<'buf> for VerifyOnlyPreallocated<'buf> {} +/// Not enough preallocated memory for the requested buffer size. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct NotEnoughMemoryError; + +impl fmt::Display for NotEnoughMemoryError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("not enough preallocated memory for the requested buffer size") + } +} + +#[cfg(feature = "std")] +impl std::error::Error for NotEnoughMemoryError {} + impl<'buf, C: Context + PreallocatedContext<'buf>> Secp256k1 { /// Lets you create a context with a preallocated buffer in a generic manner (sign/verify/all). - pub fn preallocated_gen_new(buf: &'buf mut [AlignedType]) -> Result, Error> { + pub fn preallocated_gen_new( + buf: &'buf mut [AlignedType], + ) -> Result, NotEnoughMemoryError> { #[cfg(target_arch = "wasm32")] ffi::types::sanity_checks_for_wasm(); if buf.len() < Self::preallocate_size_gen() { - return Err(Error::NotEnoughMemory); + return Err(NotEnoughMemoryError); } // Safe because buf is not null since it is not empty. let buf = unsafe { NonNull::new_unchecked(buf.as_mut_c_ptr() as *mut c_void) }; @@ -343,7 +359,7 @@ impl<'buf> Secp256k1> { /// Creates a new Secp256k1 context with all capabilities. pub fn preallocated_new( buf: &'buf mut [AlignedType], - ) -> Result>, Error> { + ) -> Result>, NotEnoughMemoryError> { Secp256k1::preallocated_gen_new(buf) } /// Uses the ffi `secp256k1_context_preallocated_size` to check the memory size needed for a context. @@ -378,7 +394,7 @@ impl<'buf> Secp256k1> { /// Creates a new Secp256k1 context that can only be used for signing. pub fn preallocated_signing_only( buf: &'buf mut [AlignedType], - ) -> Result>, Error> { + ) -> Result>, NotEnoughMemoryError> { Secp256k1::preallocated_gen_new(buf) } @@ -402,7 +418,7 @@ impl<'buf> Secp256k1> { /// Creates a new Secp256k1 context that can only be used for verification pub fn preallocated_verification_only( buf: &'buf mut [AlignedType], - ) -> Result>, Error> { + ) -> Result>, NotEnoughMemoryError> { Secp256k1::preallocated_gen_new(buf) } diff --git a/src/ecdh.rs b/src/ecdh.rs index 60990944a..226cea80b 100644 --- a/src/ecdh.rs +++ b/src/ecdh.rs @@ -4,13 +4,13 @@ //! use core::borrow::Borrow; -use core::{ptr, str}; +use core::{fmt, ptr, str}; use secp256k1_sys::types::{c_int, c_uchar, c_void}; +use crate::constants; use crate::ffi::{self, CPtr}; use crate::key::{PublicKey, SecretKey}; -use crate::{constants, Error}; // The logic for displaying shared secrets relies on this (see `secret.rs`). const SHARED_SECRET_SIZE: usize = constants::SECRET_KEY_SIZE; @@ -65,25 +65,25 @@ impl SharedSecret { /// Creates a shared secret from `bytes` slice. #[inline] - pub fn from_slice(bytes: &[u8]) -> Result { + pub fn from_slice(bytes: &[u8]) -> Result { match bytes.len() { SHARED_SECRET_SIZE => { let mut ret = [0u8; SHARED_SECRET_SIZE]; ret[..].copy_from_slice(bytes); Ok(SharedSecret(ret)) } - _ => Err(Error::InvalidSharedSecret), + _ => Err(SharedSecretError), } } } impl str::FromStr for SharedSecret { - type Err = Error; + type Err = SharedSecretError; fn from_str(s: &str) -> Result { let mut res = [0u8; SHARED_SECRET_SIZE]; match crate::from_hex(s, &mut res) { Ok(SHARED_SECRET_SIZE) => Ok(SharedSecret::from_bytes(res)), - _ => Err(Error::InvalidSharedSecret), + _ => Err(SharedSecretError), } } } @@ -183,6 +183,17 @@ impl<'de> ::serde::Deserialize<'de> for SharedSecret { } } +/// Share secret is invalid. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct SharedSecretError; + +impl fmt::Display for SharedSecretError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("shared secret is invalid") } +} + +#[cfg(feature = "std")] +impl std::error::Error for SharedSecretError {} + #[cfg(test)] #[allow(unused_imports)] mod tests { diff --git a/src/ecdsa/mod.rs b/src/ecdsa/mod.rs index 86c919586..02e96a901 100644 --- a/src/ecdsa/mod.rs +++ b/src/ecdsa/mod.rs @@ -12,11 +12,13 @@ use core::{fmt, ptr, str}; #[cfg(feature = "recovery")] pub use self::recovery::{RecoverableSignature, RecoveryId}; pub use self::serialized_signature::SerializedSignature; +pub use crate::ecdsa::recovery::InvalidRecoveryIdError; +use crate::error::{write_err, SysError}; use crate::ffi::CPtr; #[cfg(feature = "global-context")] use crate::SECP256K1; use crate::{ - ffi, from_hex, Error, Message, PublicKey, Secp256k1, SecretKey, Signing, Verification, + ffi, from_hex, FromHexError, Message, PublicKey, Secp256k1, SecretKey, Signing, Verification, }; /// An ECDSA signature @@ -36,22 +38,21 @@ impl fmt::Display for Signature { } impl str::FromStr for Signature { - type Err = Error; + type Err = SignatureFromStrError; fn from_str(s: &str) -> Result { let mut res = [0u8; 72]; - match from_hex(s, &mut res) { - Ok(x) => Signature::from_der(&res[0..x]), - _ => Err(Error::InvalidSignature), - } + let len = from_hex(s, &mut res)?; + let sig = Signature::from_der(&res[0..len])?; + Ok(sig) } } impl Signature { #[inline] /// Converts a DER-encoded byte slice to a signature - pub fn from_der(data: &[u8]) -> Result { + pub fn from_der(data: &[u8]) -> Result { if data.is_empty() { - return Err(Error::InvalidSignature); + return Err(SignatureError::InvalidLength(0)); } unsafe { @@ -65,15 +66,15 @@ impl Signature { { Ok(Signature(ret)) } else { - Err(Error::InvalidSignature) + Err(SignatureError::Sys(SysError)) } } } /// Converts a 64-byte compact-encoded byte slice to a signature - pub fn from_compact(data: &[u8]) -> Result { + pub fn from_compact(data: &[u8]) -> Result { if data.len() != 64 { - return Err(Error::InvalidSignature); + return Err(SignatureError::InvalidLength(data.len())); } unsafe { @@ -86,7 +87,7 @@ impl Signature { { Ok(Signature(ret)) } else { - Err(Error::InvalidSignature) + Err(SignatureError::Sys(SysError)) } } } @@ -95,9 +96,9 @@ impl Signature { /// only useful for validating signatures in the Bitcoin blockchain from before /// 2016. It should never be used in new applications. This library does not /// support serializing to this "format" - pub fn from_der_lax(data: &[u8]) -> Result { + pub fn from_der_lax(data: &[u8]) -> Result { if data.is_empty() { - return Err(Error::InvalidSignature); + return Err(SignatureError::InvalidLength(0)); } unsafe { @@ -111,7 +112,7 @@ impl Signature { { Ok(Signature(ret)) } else { - Err(Error::InvalidSignature) + Err(SignatureError::Sys(SysError)) } } } @@ -194,7 +195,7 @@ impl Signature { /// The signature must be normalized or verification will fail (see [`Signature::normalize_s`]). #[inline] #[cfg(feature = "global-context")] - pub fn verify(&self, msg: &Message, pk: &PublicKey) -> Result<(), Error> { + pub fn verify(&self, msg: &Message, pk: &PublicKey) -> Result<(), SysError> { SECP256K1.verify_ecdsa(msg, self, pk) } } @@ -366,7 +367,7 @@ impl Secp256k1 { /// /// ```rust /// # #[cfg(feature = "rand-std")] { - /// # use secp256k1::{rand, Secp256k1, Message, Error}; + /// # use secp256k1::{ecdsa, rand, Secp256k1, Message}; /// # /// # let secp = Secp256k1::new(); /// # let (secret_key, public_key) = secp.generate_keypair(&mut rand::thread_rng()); @@ -376,7 +377,7 @@ impl Secp256k1 { /// assert_eq!(secp.verify_ecdsa(&message, &sig, &public_key), Ok(())); /// /// let message = Message::from_slice(&[0xcd; 32]).expect("32 bytes"); - /// assert_eq!(secp.verify_ecdsa(&message, &sig, &public_key), Err(Error::IncorrectSignature)); + /// assert_eq!(secp.verify_ecdsa(&message, &sig, &public_key), Err(ecdsa::SignatureError)); /// # } /// ``` #[inline] @@ -385,7 +386,7 @@ impl Secp256k1 { msg: &Message, sig: &Signature, pk: &PublicKey, - ) -> Result<(), Error> { + ) -> Result<(), SysError> { unsafe { if ffi::secp256k1_ecdsa_verify( self.ctx.as_ptr(), @@ -394,7 +395,7 @@ impl Secp256k1 { pk.as_c_ptr(), ) == 0 { - Err(Error::IncorrectSignature) + Err(SysError) } else { Ok(()) } @@ -429,3 +430,75 @@ pub(crate) fn der_length_check(sig: &ffi::Signature, max_len: usize) -> bool { } len <= max_len } + +/// Signature is invalid. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum SignatureError { + /// Invalid signature length. + InvalidLength(usize), + /// FFI call failed. + Sys(SysError), +} + +impl fmt::Display for SignatureError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use SignatureError::*; + + match *self { + InvalidLength(len) => write!(f, "invalid signature length: {}", len), + Sys(ref e) => write_err!(f, "sys error"; e), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for SignatureError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use SignatureError::*; + + match *self { + InvalidLength(_) => None, + Sys(ref e) => Some(e), + } + } +} + +/// Signature is invalid. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum SignatureFromStrError { + /// Invalid hex string. + Hex(FromHexError), + /// Invalid signature. + Sig(SignatureError), +} + +impl fmt::Display for SignatureFromStrError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use SignatureFromStrError::*; + + match *self { + Hex(ref e) => write_err!(f, "error decoding hex"; e), + Sig(ref e) => write_err!(f, "invalid signature"; e), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for SignatureFromStrError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use SignatureFromStrError::*; + + match *self { + Hex(ref e) => Some(e), + Sig(ref e) => Some(e), + } + } +} + +impl From for SignatureFromStrError { + fn from(e: FromHexError) -> Self { Self::Hex(e) } +} + +impl From for SignatureFromStrError { + fn from(e: SignatureError) -> Self { Self::Sig(e) } +} diff --git a/src/ecdsa/recovery.rs b/src/ecdsa/recovery.rs index ab337d016..85786e375 100644 --- a/src/ecdsa/recovery.rs +++ b/src/ecdsa/recovery.rs @@ -4,13 +4,14 @@ //! signature. //! -use core::ptr; +use core::{fmt, ptr}; use self::super_ffi::CPtr; -use super::ffi as super_ffi; +use super::{ffi as super_ffi, SignatureError}; use crate::ecdsa::Signature; +use crate::error::SysError; use crate::ffi::recovery as ffi; -use crate::{key, Error, Message, Secp256k1, Signing, Verification}; +use crate::{key, Message, Secp256k1, Signing, Verification}; /// A tag used for recovering the public key from a compact signature. #[derive(Copy, Clone, PartialEq, Eq, Debug)] @@ -23,10 +24,10 @@ pub struct RecoverableSignature(ffi::RecoverableSignature); impl RecoveryId { #[inline] /// Allows library users to create valid recovery IDs from i32. - pub fn from_i32(id: i32) -> Result { + pub fn from_i32(id: i32) -> Result { match id { 0..=3 => Ok(RecoveryId(id)), - _ => Err(Error::InvalidRecoveryId), + other => Err(InvalidRecoveryIdError(other)), } } @@ -39,16 +40,19 @@ impl RecoverableSignature { #[inline] /// Converts a compact-encoded byte slice to a signature. This /// representation is nonstandard and defined by the libsecp256k1 library. - pub fn from_compact(data: &[u8], recid: RecoveryId) -> Result { + pub fn from_compact( + data: &[u8], + recid: RecoveryId, + ) -> Result { if data.is_empty() { - return Err(Error::InvalidSignature); + return Err(SignatureError::InvalidLength(0)); } let mut ret = ffi::RecoverableSignature::new(); unsafe { if data.len() != 64 { - Err(Error::InvalidSignature) + Err(SignatureError::InvalidLength(data.len())) } else if ffi::secp256k1_ecdsa_recoverable_signature_parse_compact( super_ffi::secp256k1_context_no_precomp, &mut ret, @@ -58,7 +62,7 @@ impl RecoverableSignature { { Ok(RecoverableSignature(ret)) } else { - Err(Error::InvalidSignature) + Err(SignatureError::Sys(SysError)) } } } @@ -113,7 +117,7 @@ impl RecoverableSignature { /// verify-capable context. #[inline] #[cfg(feature = "global-context")] - pub fn recover(&self, msg: &Message) -> Result { + pub fn recover(&self, msg: &Message) -> Result { crate::SECP256K1.recover_ecdsa(msg, self) } } @@ -191,7 +195,7 @@ impl Secp256k1 { &self, msg: &Message, sig: &RecoverableSignature, - ) -> Result { + ) -> Result { unsafe { let mut pk = super_ffi::PublicKey::new(); if ffi::secp256k1_ecdsa_recover( @@ -201,22 +205,35 @@ impl Secp256k1 { msg.as_c_ptr(), ) != 1 { - return Err(Error::InvalidSignature); + return Err(SignatureError::Sys(SysError)); } Ok(key::PublicKey::from(pk)) } } } +/// Recovery ID is invalid. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct InvalidRecoveryIdError(i32); + +impl fmt::Display for InvalidRecoveryIdError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "recovery ID is invalid: {}", self.0) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for InvalidRecoveryIdError {} + #[cfg(test)] #[allow(unused_imports)] mod tests { #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::wasm_bindgen_test as test; - use super::{RecoverableSignature, RecoveryId}; + use super::*; use crate::constants::ONE; - use crate::{Error, Message, Secp256k1, SecretKey}; + use crate::{Message, Secp256k1, SecretKey}; #[test] #[cfg(feature = "rand-std")] @@ -316,7 +333,7 @@ mod tests { let msg = crate::random_32_bytes(&mut rand::thread_rng()); let msg = Message::from_slice(&msg).unwrap(); - assert_eq!(s.verify_ecdsa(&msg, &sig, &pk), Err(Error::IncorrectSignature)); + assert_eq!(s.verify_ecdsa(&msg, &sig, &pk), Err(SysError)); let recovered_key = s.recover_ecdsa(&msg, &sigr).unwrap(); assert!(recovered_key != pk); @@ -366,7 +383,7 @@ mod tests { // Zero is not a valid sig let sig = RecoverableSignature::from_compact(&[0; 64], RecoveryId(0)).unwrap(); - assert_eq!(s.recover_ecdsa(&msg, &sig), Err(Error::InvalidSignature)); + assert_eq!(s.recover_ecdsa(&msg, &sig), Err(SignatureError::Sys(SysError))); // ...but 111..111 is let sig = RecoverableSignature::from_compact(&[1; 64], RecoveryId(0)).unwrap(); assert!(s.recover_ecdsa(&msg, &sig).is_ok()); diff --git a/src/ecdsa/serialized_signature.rs b/src/ecdsa/serialized_signature.rs index 1b2a65a2d..a6ef85305 100644 --- a/src/ecdsa/serialized_signature.rs +++ b/src/ecdsa/serialized_signature.rs @@ -11,8 +11,7 @@ use core::{fmt, ops}; pub use into_iter::IntoIter; -use super::Signature; -use crate::Error; +use super::{Signature, SignatureError}; pub(crate) const MAX_LEN: usize = 72; @@ -98,7 +97,7 @@ impl SerializedSignature { /// Convert the serialized signature into the Signature struct. /// (This DER deserializes it) #[inline] - pub fn to_signature(&self) -> Result { Signature::from_der(self) } + pub fn to_signature(&self) -> Result { Signature::from_der(self) } /// Create a SerializedSignature from a Signature. /// (this DER serializes it) diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 000000000..06d6d0d94 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Error types and conversion functions. + +use core::fmt; + +use crate::context::NotEnoughMemoryError; +use crate::ecdh::SharedSecretError; +use crate::key::error::{ + ParityValueError, PublicKeyError, PublicKeySumError, SecretKeyError, TweakError, + XOnlyTweakError, +}; +use crate::{ecdsa, schnorr, MessageLengthError}; + +/// Implements `From for $error` for all the errors in this crate. +/// +/// Either pass in the variant to use or have a variant `Secp256k1` on `$error`. +#[macro_export] +macro_rules! impl_from_for_all_crate_errors_for { + ($error:ty) => { + $crate::impl_from_for_all_crate_errors_for!($error, Secp256k1); + }; + ($error:ty, $variant:ident) => { + impl From<$crate::Error> for $error { + fn from(e: $crate::Error) -> Self { Self::$variant(e) } + } + + impl From<$crate::NotEnoughMemoryError> for $error { + fn from(e: $crate::NotEnoughMemoryError) -> Self { Self::$variant(e.into()) } + } + + impl From<$crate::MessageLengthError> for $error { + fn from(e: $crate::MessageLengthError) -> Self { Self::$variant(e.into()) } + } + + impl From<$crate::schnorr::SignatureError> for $error { + fn from(e: $crate::schnorr::SignatureError) -> Self { Self::$variant(e.into()) } + } + + impl From<$crate::ecdsa::SignatureError> for $error { + fn from(e: $crate::ecdsa::SignatureError) -> Self { Self::$variant(e.into()) } + } + + impl From<$crate::RecoveryIdError> for $error { + fn from(e: $crate::RecoveryIdError) -> Self { Self::$variant(e.into()) } + } + + impl From<$crate::SecretKeyError> for $error { + fn from(e: $crate::SecretKeyError) -> Self { Self::$variant(e.into()) } + } + + impl From<$crate::PublicKeyError> for $error { + fn from(e: $crate::PublicKeyError) -> Self { Self::$variant(e.into()) } + } + + impl From<$crate::PublicKeySumError> for $error { + fn from(e: $crate::PublicKeySumError) -> Self { Self::$variant(e.into()) } + } + + impl From<$crate::TweakError> for $error { + fn from(e: $crate::TweakError) -> Self { Self::$variant(e.into()) } + } + + impl From<$crate::ParityValueError> for $error { + fn from(e: $crate::ParityValueError) -> Self { Self::$variant(e.into()) } + } + + impl From<$crate::XOnlyTweakError> for $error { + fn from(e: $crate::XOnlyTweakError) -> Self { Self::$variant(e.into()) } + } + + impl From<$crate::SharedSecretError> for $error { + fn from(e: $crate::SharedSecretError) -> Self { Self::$variant(e.into()) } + } + }; +} + +/// This is a general purpose error type that can be used to wrap all the errors in this crate. +/// +/// Every error types in this crate can be converted (using `?`) to this type. We also support +/// converting from any of the inner error types to this type, irrespective of the level of nesting. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[non_exhaustive] +pub enum Error { + /// Not enough preallocated memory for the requested buffer size. + NotEnoughMemory(NotEnoughMemoryError), + /// Messages must be 32 bytes long. + MessageLength(MessageLengthError), + /// Schnorr signature is invalid. + SchnorrSignature(schnorr::SignatureError), + /// ECDSA signature is invalid. + EcdsaSignature(ecdsa::SignatureError), + /// Invalid recovery ID (ECDSA). + RecoveryId(ecdsa::InvalidRecoveryIdError), + /// Secret key is invalid. + SecretKey(SecretKeyError), + /// Public key is invalid. + PublicKey(PublicKeyError), + /// Public key summation is invalid. + PublicKeySum(PublicKeySumError), + /// Invalid key tweak. + Tweak(TweakError), + /// Invalid value for parity - must be 0 or 1. + ParityValue(ParityValueError), + /// X-only pubic key tweak failed. + XOnlyTweak(XOnlyTweakError), + /// Invalid shared secret. + SharedSecret(SharedSecretError), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use Error::*; + + // TODO: Check what gets out put in std and no-std builds an verify it useful and does not + // contain redundant content. + match *self { + NotEnoughMemory(ref e) => write_err!(f, "not enough memory"; e), + MessageLength(ref e) => write_err!(f, "invalid message length"; e), + SchnorrSignature(ref e) => write_err!(f, "invalid schnorr sig"; e), + EcdsaSignature(ref e) => write_err!(f, "invalid ECDSA sig"; e), + RecoveryId(ref e) => write_err!(f, "invalid recovery ID (ECDSA)"; e), + SecretKey(ref e) => write_err!(f, "invalid secret key"; e), + PublicKey(ref e) => write_err!(f, "invalid public key"; e), + PublicKeySum(ref e) => write_err!(f, "invalid public key sum"; e), + Tweak(ref e) => write_err!(f, "invalid tweak"; e), + ParityValue(ref e) => write_err!(f, "invalid parity"; e), + XOnlyTweak(ref e) => write_err!(f, "x-only tweak error"; e), + SharedSecret(ref e) => write_err!(f, "invalid shared secret"; e), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use Error::*; + + match *self { + NotEnoughMemory(ref e) => Some(e), + MessageLength(ref e) => Some(e), + SchnorrSignature(ref e) => Some(e), + EcdsaSignature(ref e) => Some(e), + RecoveryId(ref e) => Some(e), + SecretKey(ref e) => Some(e), + PublicKey(ref e) => Some(e), + PublicKeySum(ref e) => Some(e), + Tweak(ref e) => Some(e), + ParityValue(ref e) => Some(e), + XOnlyTweak(ref e) => Some(e), + SharedSecret(ref e) => Some(e), + } + } +} + +impl From for Error { + fn from(e: NotEnoughMemoryError) -> Self { Self::NotEnoughMemory(e) } +} + +impl From for Error { + fn from(e: MessageLengthError) -> Self { Self::MessageLength(e) } +} + +impl From for Error { + fn from(e: schnorr::SignatureError) -> Self { Self::SchnorrSignature(e) } +} + +impl From for Error { + fn from(e: ecdsa::SignatureError) -> Self { Self::EcdsaSignature(e) } +} + +impl From for Error { + fn from(e: ecdsa::InvalidRecoveryIdError) -> Self { Self::RecoveryId(e) } +} + +impl From for Error { + fn from(e: SecretKeyError) -> Self { Self::SecretKey(e) } +} + +impl From for Error { + fn from(e: PublicKeyError) -> Self { Self::PublicKey(e) } +} + +impl From for Error { + fn from(e: PublicKeySumError) -> Self { Self::PublicKeySum(e) } +} + +impl From for Error { + fn from(e: TweakError) -> Self { Self::Tweak(e) } +} + +impl From for Error { + fn from(e: ParityValueError) -> Self { Self::ParityValue(e) } +} + +impl From for Error { + fn from(e: XOnlyTweakError) -> Self { Self::XOnlyTweak(e) } +} + +impl From for Error { + fn from(e: SharedSecretError) -> Self { Self::SharedSecret(e) } +} + +/// Error calling into the FFI layer. +// TODO: Do we want to include the error code returned for C function calls? +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct SysError; + +impl core::fmt::Display for SysError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> { + f.write_str("FFI call failed") + } +} + +#[cfg(feature = "std")] +impl std::error::Error for SysError {} + +/// Formats error. If `std` feature is OFF appends error source (delimited by `: `). We do this +/// because `e.source()` is only available in std builds, without this macro the error source is +/// lost for no-std builds. +macro_rules! write_err { + ($writer:expr, $string:literal $(, $args:expr),*; $source:expr) => { + { + #[cfg(feature = "std")] + { + let _ = &$source; // Prevents clippy warnings. + write!($writer, $string $(, $args)*) + } + #[cfg(not(feature = "std"))] + { + write!($writer, concat!($string, ": {}") $(, $args)*, $source) + } + } + } +} +pub(crate) use write_err; diff --git a/src/key/error.rs b/src/key/error.rs new file mode 100644 index 000000000..13d7f20de --- /dev/null +++ b/src/key/error.rs @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Error types for the `key` module. + +use core::fmt; + +use crate::error::write_err; + +/// X-only public key tweak is invalid. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum XOnlyTweakError { + /// Invalid tweak. + Tweak(TweakError), + /// Invalid public key. + PublicKey(PublicKeyError), + /// Invalid parity value. + ParityValue(ParityValueError), +} + +impl fmt::Display for XOnlyTweakError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use XOnlyTweakError::*; + + // TODO: Check what gets out put in std and no-std builds an verify it useful and does not + // contain redundant content. + match *self { + Tweak(ref e) => write_err!(f, "invalid tweak"; e), + PublicKey(ref e) => write_err!(f, "invalid public key"; e), + ParityValue(ref e) => write_err!(f, "invalid parity value"; e), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for XOnlyTweakError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use XOnlyTweakError::*; + + match *self { + Tweak(ref e) => Some(e), + PublicKey(ref e) => Some(e), + ParityValue(ref e) => Some(e), + } + } +} + +impl From for XOnlyTweakError { + fn from(e: TweakError) -> Self { Self::Tweak(e) } +} + +impl From for XOnlyTweakError { + fn from(e: PublicKeyError) -> Self { Self::PublicKey(e) } +} + +impl From for XOnlyTweakError { + fn from(e: ParityValueError) -> Self { Self::ParityValue(e) } +} + +/// Secret key is invalid. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct SecretKeyError; + +impl core::fmt::Display for SecretKeyError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> { + f.write_str("secret key is invalid") + } +} + +#[cfg(feature = "std")] +impl std::error::Error for SecretKeyError {} + +/// Public key is invalid. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct PublicKeyError; + +impl core::fmt::Display for PublicKeyError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> { + f.write_str("public key is invalid") + } +} + +#[cfg(feature = "std")] +impl std::error::Error for PublicKeyError {} + +/// Public key summation is invalid. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct PublicKeySumError; + +impl core::fmt::Display for PublicKeySumError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> { + f.write_str("public key summation is invalid") + } +} + +#[cfg(feature = "std")] +impl std::error::Error for PublicKeySumError {} + +/// Invalid key tweak. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct TweakError; + +impl core::fmt::Display for TweakError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> { + f.write_str("invalid key tweak") + } +} + +#[cfg(feature = "std")] +impl std::error::Error for TweakError {} + +/// Invalid value for parity - must be 0 or 1. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ParityValueError(pub i32); + +impl fmt::Display for ParityValueError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "invalid value {} for parity - must be 0 or 1", self.0) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ParityValueError {} diff --git a/src/key.rs b/src/key/mod.rs similarity index 94% rename from src/key.rs rename to src/key/mod.rs index 2b7d8e9f9..f981edc6e 100644 --- a/src/key.rs +++ b/src/key/mod.rs @@ -12,13 +12,18 @@ use serde::ser::SerializeTuple; use crate::ffi::types::c_uint; use crate::ffi::{self, CPtr}; -use crate::Error::{self, InvalidPublicKey, InvalidPublicKeySum, InvalidSecretKey}; use crate::{constants, from_hex, schnorr, Message, Scalar, Secp256k1, Signing, Verification}; #[cfg(feature = "global-context")] use crate::{ecdsa, SECP256K1}; #[cfg(feature = "bitcoin_hashes")] use crate::{hashes, ThirtyTwoByteHash}; +pub mod error; +pub use self::error::{ + ParityValueError, PublicKeyError, PublicKeySumError, SecretKeyError, TweakError, + XOnlyTweakError, +}; + /// Secret 256-bit key used as `x` in an ECDSA signature. /// /// # Side channel attacks @@ -105,12 +110,12 @@ impl ffi::CPtr for SecretKey { } impl str::FromStr for SecretKey { - type Err = Error; + type Err = SecretKeyError; fn from_str(s: &str) -> Result { let mut res = [0u8; constants::SECRET_KEY_SIZE]; match from_hex(s, &mut res) { Ok(constants::SECRET_KEY_SIZE) => SecretKey::from_slice(&res), - _ => Err(Error::InvalidSecretKey), + _ => Err(SecretKeyError), } } } @@ -158,14 +163,14 @@ impl fmt::Display for PublicKey { } impl str::FromStr for PublicKey { - type Err = Error; + type Err = PublicKeyError; fn from_str(s: &str) -> Result { let mut res = [0u8; constants::UNCOMPRESSED_PUBLIC_KEY_SIZE]; match from_hex(s, &mut res) { Ok(constants::PUBLIC_KEY_SIZE) => PublicKey::from_slice(&res[0..constants::PUBLIC_KEY_SIZE]), Ok(constants::UNCOMPRESSED_PUBLIC_KEY_SIZE) => PublicKey::from_slice(&res), - _ => Err(Error::InvalidPublicKey), + _ => Err(PublicKeyError), } } } @@ -206,7 +211,7 @@ impl SecretKey { /// let sk = SecretKey::from_slice(&[0xcd; 32]).expect("32 bytes, within curve order"); /// ``` #[inline] - pub fn from_slice(data: &[u8]) -> Result { + pub fn from_slice(data: &[u8]) -> Result { match <[u8; constants::SECRET_KEY_SIZE]>::try_from(data) { Ok(data) => { unsafe { @@ -215,12 +220,12 @@ impl SecretKey { data.as_c_ptr(), ) == 0 { - return Err(InvalidSecretKey); + return Err(SecretKeyError); } } Ok(SecretKey(data)) } - Err(_) => Err(InvalidSecretKey), + Err(_) => Err(SecretKeyError), } } @@ -299,7 +304,7 @@ impl SecretKey { /// /// Returns an error if the resulting key would be invalid. #[inline] - pub fn add_tweak(mut self, tweak: &Scalar) -> Result { + pub fn add_tweak(mut self, tweak: &Scalar) -> Result { unsafe { if ffi::secp256k1_ec_seckey_tweak_add( ffi::secp256k1_context_no_precomp, @@ -307,7 +312,7 @@ impl SecretKey { tweak.as_c_ptr(), ) != 1 { - Err(Error::InvalidTweak) + Err(TweakError) } else { Ok(self) } @@ -320,7 +325,7 @@ impl SecretKey { /// /// Returns an error if the resulting key would be invalid. #[inline] - pub fn mul_tweak(mut self, tweak: &Scalar) -> Result { + pub fn mul_tweak(mut self, tweak: &Scalar) -> Result { unsafe { if ffi::secp256k1_ec_seckey_tweak_mul( ffi::secp256k1_context_no_precomp, @@ -328,7 +333,7 @@ impl SecretKey { tweak.as_c_ptr(), ) != 1 { - Err(Error::InvalidTweak) + Err(TweakError) } else { Ok(self) } @@ -455,9 +460,9 @@ impl PublicKey { /// Creates a public key directly from a slice. #[inline] - pub fn from_slice(data: &[u8]) -> Result { + pub fn from_slice(data: &[u8]) -> Result { if data.is_empty() { - return Err(Error::InvalidPublicKey); + return Err(PublicKeyError); } unsafe { @@ -471,7 +476,7 @@ impl PublicKey { { Ok(PublicKey(pk)) } else { - Err(InvalidPublicKey) + Err(PublicKeyError) } } } @@ -571,14 +576,14 @@ impl PublicKey { mut self, secp: &Secp256k1, tweak: &Scalar, - ) -> Result { + ) -> Result { unsafe { if ffi::secp256k1_ec_pubkey_tweak_add(secp.ctx.as_ptr(), &mut self.0, tweak.as_c_ptr()) == 1 { Ok(self) } else { - Err(Error::InvalidTweak) + Err(TweakError) } } } @@ -593,14 +598,14 @@ impl PublicKey { mut self, secp: &Secp256k1, other: &Scalar, - ) -> Result { + ) -> Result { unsafe { if ffi::secp256k1_ec_pubkey_tweak_mul(secp.ctx.as_ptr(), &mut self.0, other.as_c_ptr()) == 1 { Ok(self) } else { - Err(Error::InvalidTweak) + Err(TweakError) } } } @@ -624,7 +629,7 @@ impl PublicKey { /// let sum = pk1.combine(&pk2).expect("It's improbable to fail for 2 random public keys"); /// # } /// ``` - pub fn combine(&self, other: &PublicKey) -> Result { + pub fn combine(&self, other: &PublicKey) -> Result { PublicKey::combine_keys(&[self, other]) } @@ -651,12 +656,12 @@ impl PublicKey { /// let sum = PublicKey::combine_keys(&[&pk1, &pk2, &pk3]).expect("It's improbable to fail for 3 random public keys"); /// # } /// ``` - pub fn combine_keys(keys: &[&PublicKey]) -> Result { + pub fn combine_keys(keys: &[&PublicKey]) -> Result { use core::i32::MAX; use core::mem::transmute; if keys.is_empty() || keys.len() > MAX as usize { - return Err(InvalidPublicKeySum); + return Err(PublicKeySumError); } unsafe { @@ -672,7 +677,7 @@ impl PublicKey { { Ok(PublicKey(ret)) } else { - Err(InvalidPublicKeySum) + Err(PublicKeySumError) } } } @@ -811,15 +816,15 @@ impl KeyPair { /// /// # Errors /// - /// [`Error::InvalidSecretKey`] if the provided data has an incorrect length, exceeds Secp256k1 + /// [`SecretKeyError`] if the provided data has an incorrect length, exceeds Secp256k1 /// field `p` value or the corresponding public key is not even. #[inline] pub fn from_seckey_slice( secp: &Secp256k1, data: &[u8], - ) -> Result { + ) -> Result { if data.is_empty() || data.len() != constants::SECRET_KEY_SIZE { - return Err(Error::InvalidSecretKey); + return Err(SecretKeyError); } unsafe { @@ -827,7 +832,7 @@ impl KeyPair { if ffi::secp256k1_keypair_create(secp.ctx.as_ptr(), &mut kp, data.as_c_ptr()) == 1 { Ok(KeyPair(kp)) } else { - Err(Error::InvalidSecretKey) + Err(SecretKeyError) } } } @@ -836,14 +841,17 @@ impl KeyPair { /// /// # Errors /// - /// [`Error::InvalidSecretKey`] if corresponding public key for the provided secret key is not even. + /// [`SecretKeyError`] if corresponding public key for the provided secret key is not even. #[inline] - pub fn from_seckey_str(secp: &Secp256k1, s: &str) -> Result { + pub fn from_seckey_str( + secp: &Secp256k1, + s: &str, + ) -> Result { let mut res = [0u8; constants::SECRET_KEY_SIZE]; match from_hex(s, &mut res) { Ok(constants::SECRET_KEY_SIZE) => - KeyPair::from_seckey_slice(secp, &res[0..constants::SECRET_KEY_SIZE]), - _ => Err(Error::InvalidPublicKey), + Ok(KeyPair::from_seckey_slice(secp, &res[0..constants::SECRET_KEY_SIZE])?), + _ => Err(SecretKeyError), } } @@ -851,10 +859,10 @@ impl KeyPair { /// /// # Errors /// - /// [`Error::InvalidSecretKey`] if corresponding public key for the provided secret key is not even. + /// [`SecretKeyError`] if corresponding public key for the provided secret key is not even. #[inline] #[cfg(feature = "global-context")] - pub fn from_seckey_str_global(s: &str) -> Result { + pub fn from_seckey_str_global(s: &str) -> Result { KeyPair::from_seckey_str(SECP256K1, s) } @@ -925,7 +933,7 @@ impl KeyPair { mut self, secp: &Secp256k1, tweak: &Scalar, - ) -> Result { + ) -> Result { unsafe { let err = ffi::secp256k1_keypair_xonly_tweak_add( secp.ctx.as_ptr(), @@ -933,7 +941,7 @@ impl KeyPair { tweak.as_c_ptr(), ); if err != 1 { - return Err(Error::InvalidTweak); + return Err(TweakError); } Ok(self) @@ -998,7 +1006,7 @@ impl<'a> From<&'a KeyPair> for PublicKey { } impl str::FromStr for KeyPair { - type Err = Error; + type Err = SecretKeyError; #[allow(unused_variables, unreachable_code)] // When built with no default features. fn from_str(s: &str) -> Result { @@ -1110,13 +1118,13 @@ impl fmt::Display for XOnlyPublicKey { } impl str::FromStr for XOnlyPublicKey { - type Err = Error; + type Err = PublicKeyError; fn from_str(s: &str) -> Result { let mut res = [0u8; constants::SCHNORR_PUBLIC_KEY_SIZE]; match from_hex(s, &mut res) { Ok(constants::SCHNORR_PUBLIC_KEY_SIZE) => XOnlyPublicKey::from_slice(&res[0..constants::SCHNORR_PUBLIC_KEY_SIZE]), - _ => Err(Error::InvalidPublicKey), + _ => Err(PublicKeyError), } } } @@ -1159,12 +1167,12 @@ impl XOnlyPublicKey { /// /// # Errors /// - /// Returns [`Error::InvalidPublicKey`] if the length of the data slice is not 32 bytes or the + /// Returns [`PublicKeyError`] if the length of the data slice is not 32 bytes or the /// slice does not represent a valid Secp256k1 point x coordinate. #[inline] - pub fn from_slice(data: &[u8]) -> Result { + pub fn from_slice(data: &[u8]) -> Result { if data.is_empty() || data.len() != constants::SCHNORR_PUBLIC_KEY_SIZE { - return Err(Error::InvalidPublicKey); + return Err(PublicKeyError); } unsafe { @@ -1177,7 +1185,7 @@ impl XOnlyPublicKey { { Ok(XOnlyPublicKey(pk)) } else { - Err(Error::InvalidPublicKey) + Err(PublicKeyError) } } } @@ -1228,7 +1236,7 @@ impl XOnlyPublicKey { mut self, secp: &Secp256k1, tweak: &Scalar, - ) -> Result<(XOnlyPublicKey, Parity), Error> { + ) -> Result<(XOnlyPublicKey, Parity), XOnlyTweakError> { let mut pk_parity = 0; unsafe { let mut pubkey = ffi::PublicKey::new(); @@ -1239,7 +1247,7 @@ impl XOnlyPublicKey { tweak.as_c_ptr(), ); if err != 1 { - return Err(Error::InvalidTweak); + return Err(TweakError)?; } err = ffi::secp256k1_xonly_pubkey_from_pubkey( @@ -1249,7 +1257,7 @@ impl XOnlyPublicKey { &pubkey, ); if err == 0 { - return Err(Error::InvalidPublicKey); + return Err(PublicKeyError)?; } let parity = Parity::from_i32(pk_parity)?; @@ -1321,7 +1329,7 @@ impl XOnlyPublicKey { secp: &Secp256k1, msg: &Message, sig: &schnorr::Signature, - ) -> Result<(), Error> { + ) -> Result<(), schnorr::SignatureError> { secp.verify_schnorr(sig, msg, self) } } @@ -1350,7 +1358,7 @@ impl Parity { /// /// The only allowed values are `0` meaning even parity and `1` meaning odd. /// Other values result in error being returned. - pub fn from_u8(parity: u8) -> Result { + pub fn from_u8(parity: u8) -> Result { Parity::from_i32(parity.into()) } @@ -1358,25 +1366,25 @@ impl Parity { /// /// The only allowed values are `0` meaning even parity and `1` meaning odd. /// Other values result in error being returned. - pub fn from_i32(parity: i32) -> Result { + pub fn from_i32(parity: i32) -> Result { match parity { 0 => Ok(Parity::Even), 1 => Ok(Parity::Odd), - _ => Err(InvalidParityValue(parity)), + _ => Err(ParityValueError(parity)), } } } /// `Even` for `0`, `Odd` for `1`, error for anything else impl TryFrom for Parity { - type Error = InvalidParityValue; + type Error = ParityValueError; fn try_from(parity: i32) -> Result { Self::from_i32(parity) } } /// `Even` for `0`, `Odd` for `1`, error for anything else impl TryFrom for Parity { - type Error = InvalidParityValue; + type Error = ParityValueError; fn try_from(parity: u8) -> Result { Self::from_u8(parity) } } @@ -1405,27 +1413,6 @@ impl BitXor for Parity { } } -/// Error returned when conversion from an integer to `Parity` fails. -// -// Note that we don't allow inspecting the value because we may change the type. -// Yes, this comment is intentionally NOT doc comment. -// Too many derives for compatibility with current Error type. -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub struct InvalidParityValue(i32); - -impl fmt::Display for InvalidParityValue { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "invalid value {} for Parity - must be 0 or 1", self.0) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for InvalidParityValue {} - -impl From for Error { - fn from(error: InvalidParityValue) -> Self { Error::InvalidParityValue(error) } -} - /// The parity is serialized as `u8` - `0` for even, `1` for odd. #[cfg(feature = "serde")] impl serde::Serialize for Parity { @@ -1536,8 +1523,7 @@ mod test { #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::wasm_bindgen_test as test; - use super::{KeyPair, Parity, PublicKey, Secp256k1, SecretKey, XOnlyPublicKey, *}; - use crate::Error::{InvalidPublicKey, InvalidSecretKey}; + use super::*; use crate::{constants, from_hex, to_hex, Scalar}; #[cfg(not(secp256k1_fuzz))] @@ -1552,7 +1538,7 @@ mod test { #[test] fn skey_from_slice() { let sk = SecretKey::from_slice(&[1; 31]); - assert_eq!(sk, Err(InvalidSecretKey)); + assert_eq!(sk, Err(SecretKeyError)); let sk = SecretKey::from_slice(&[1; 32]); assert!(sk.is_ok()); @@ -1560,8 +1546,8 @@ mod test { #[test] fn pubkey_from_slice() { - assert_eq!(PublicKey::from_slice(&[]), Err(InvalidPublicKey)); - assert_eq!(PublicKey::from_slice(&[1, 2, 3]), Err(InvalidPublicKey)); + assert_eq!(PublicKey::from_slice(&[]), Err(PublicKeyError)); + assert_eq!(PublicKey::from_slice(&[1, 2, 3]), Err(PublicKeyError)); let uncompressed = PublicKey::from_slice(&[ 4, 54, 57, 149, 239, 162, 148, 175, 246, 254, 239, 75, 154, 152, 10, 82, 234, 224, 85, @@ -1604,13 +1590,13 @@ mod test { #[rustfmt::skip] fn invalid_secret_key() { // Zero - assert_eq!(SecretKey::from_slice(&[0; 32]), Err(InvalidSecretKey)); + assert_eq!(SecretKey::from_slice(&[0; 32]), Err(SecretKeyError)); assert_eq!( SecretKey::from_str("0000000000000000000000000000000000000000000000000000000000000000"), - Err(InvalidSecretKey) + Err(SecretKeyError) ); // -1 - assert_eq!(SecretKey::from_slice(&[0xff; 32]), Err(InvalidSecretKey)); + assert_eq!(SecretKey::from_slice(&[0xff; 32]), Err(SecretKeyError)); // Top of range assert!(SecretKey::from_slice(&[ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, @@ -1664,31 +1650,28 @@ mod test { // Bad sizes assert_eq!( PublicKey::from_slice(&[0; constants::PUBLIC_KEY_SIZE - 1]), - Err(InvalidPublicKey) + Err(PublicKeyError) ); assert_eq!( PublicKey::from_slice(&[0; constants::PUBLIC_KEY_SIZE + 1]), - Err(InvalidPublicKey) + Err(PublicKeyError) ); assert_eq!( PublicKey::from_slice(&[0; constants::UNCOMPRESSED_PUBLIC_KEY_SIZE - 1]), - Err(InvalidPublicKey) + Err(PublicKeyError) ); assert_eq!( PublicKey::from_slice(&[0; constants::UNCOMPRESSED_PUBLIC_KEY_SIZE + 1]), - Err(InvalidPublicKey) + Err(PublicKeyError) ); // Bad parse assert_eq!( PublicKey::from_slice(&[0xff; constants::UNCOMPRESSED_PUBLIC_KEY_SIZE]), - Err(InvalidPublicKey) - ); - assert_eq!( - PublicKey::from_slice(&[0x55; constants::PUBLIC_KEY_SIZE]), - Err(InvalidPublicKey) + Err(PublicKeyError) ); - assert_eq!(PublicKey::from_slice(&[]), Err(InvalidPublicKey)); + assert_eq!(PublicKey::from_slice(&[0x55; constants::PUBLIC_KEY_SIZE]), Err(PublicKeyError)); + assert_eq!(PublicKey::from_slice(&[]), Err(PublicKeyError)); } #[test] @@ -1696,22 +1679,16 @@ mod test { // Bad sizes assert_eq!( SecretKey::from_slice(&[0; constants::SECRET_KEY_SIZE - 1]), - Err(InvalidSecretKey) + Err(SecretKeyError) ); assert_eq!( SecretKey::from_slice(&[0; constants::SECRET_KEY_SIZE + 1]), - Err(InvalidSecretKey) + Err(SecretKeyError) ); // Bad parse - assert_eq!( - SecretKey::from_slice(&[0xff; constants::SECRET_KEY_SIZE]), - Err(InvalidSecretKey) - ); - assert_eq!( - SecretKey::from_slice(&[0x00; constants::SECRET_KEY_SIZE]), - Err(InvalidSecretKey) - ); - assert_eq!(SecretKey::from_slice(&[]), Err(InvalidSecretKey)); + assert_eq!(SecretKey::from_slice(&[0xff; constants::SECRET_KEY_SIZE]), Err(SecretKeyError)); + assert_eq!(SecretKey::from_slice(&[0x00; constants::SECRET_KEY_SIZE]), Err(SecretKeyError)); + assert_eq!(SecretKey::from_slice(&[]), Err(SecretKeyError)); } #[test] diff --git a/src/lib.rs b/src/lib.rs index 14d65f224..b0eec01c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -161,6 +161,7 @@ mod key; pub mod constants; pub mod ecdh; pub mod ecdsa; +pub mod error; pub mod scalar; pub mod schnorr; #[cfg(feature = "serde")] @@ -180,11 +181,18 @@ pub use secp256k1_sys as ffi; #[cfg(feature = "serde")] pub use serde; -pub use crate::context::*; +pub use crate::context::*; // Includes NotEnoughMemoryError +pub use crate::ecdh::SharedSecretError; +pub use crate::ecdsa::InvalidRecoveryIdError; +pub use crate::error::Error; use crate::ffi::types::AlignedType; use crate::ffi::CPtr; #[cfg(feature = "bitcoin_hashes")] use crate::hashes::Hash; +pub use crate::key::error::{ + ParityValueError, PublicKeyError, PublicKeySumError, SecretKeyError, TweakError, + XOnlyTweakError, +}; pub use crate::key::{PublicKey, SecretKey, *}; pub use crate::scalar::Scalar; @@ -225,14 +233,14 @@ impl Message { /// the result of signing isn't a /// [secure signature](https://twitter.com/pwuille/status/1063582706288586752). #[inline] - pub fn from_slice(data: &[u8]) -> Result { + pub fn from_slice(data: &[u8]) -> Result { match data.len() { constants::MESSAGE_SIZE => { let mut ret = [0u8; constants::MESSAGE_SIZE]; ret[..].copy_from_slice(data); Ok(Message(ret)) } - _ => Err(Error::InvalidMessage), + len => Err(MessageLengthError(len)), } } @@ -278,73 +286,18 @@ impl fmt::Display for Message { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) } } -/// The main error type for this library. -#[derive(Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Debug)] -pub enum Error { - /// Signature failed verification. - IncorrectSignature, - /// Bad sized message ("messages" are actually fixed-sized digests [`constants::MESSAGE_SIZE`]). - InvalidMessage, - /// Bad public key. - InvalidPublicKey, - /// Bad signature. - InvalidSignature, - /// Bad secret key. - InvalidSecretKey, - /// Bad shared secret. - InvalidSharedSecret, - /// Bad recovery id. - InvalidRecoveryId, - /// Tried to add/multiply by an invalid tweak. - InvalidTweak, - /// Didn't pass enough memory to context creation with preallocated memory. - NotEnoughMemory, - /// Bad set of public keys. - InvalidPublicKeySum, - /// The only valid parity values are 0 or 1. - InvalidParityValue(key::InvalidParityValue), -} +/// Messages must be 32 bytes long. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct MessageLengthError(usize); -impl fmt::Display for Error { +impl fmt::Display for MessageLengthError { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - use Error::*; - - match *self { - IncorrectSignature => f.write_str("signature failed verification"), - InvalidMessage => f.write_str("message was not 32 bytes (do you need to hash?)"), - InvalidPublicKey => f.write_str("malformed public key"), - InvalidSignature => f.write_str("malformed signature"), - InvalidSecretKey => f.write_str("malformed or out-of-range secret key"), - InvalidSharedSecret => f.write_str("malformed or out-of-range shared secret"), - InvalidRecoveryId => f.write_str("bad recovery id"), - InvalidTweak => f.write_str("bad tweak"), - NotEnoughMemory => f.write_str("not enough memory allocated"), - InvalidPublicKeySum => f.write_str( - "the sum of public keys was invalid or the input vector lengths was less than 1", - ), - InvalidParityValue(e) => write_err!(f, "couldn't create parity"; e), - } + write!(f, "messages must be 32 bytes long, got: {}", self.0) } } #[cfg(feature = "std")] -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Error::IncorrectSignature => None, - Error::InvalidMessage => None, - Error::InvalidPublicKey => None, - Error::InvalidSignature => None, - Error::InvalidSecretKey => None, - Error::InvalidSharedSecret => None, - Error::InvalidRecoveryId => None, - Error::InvalidTweak => None, - Error::NotEnoughMemory => None, - Error::InvalidPublicKeySum => None, - Error::InvalidParityValue(error) => Some(error), - } - } -} +impl std::error::Error for MessageLengthError {} /// The secp256k1 engine, used to execute all signature operations. pub struct Secp256k1 { @@ -450,9 +403,13 @@ pub fn generate_keypair(rng: &mut R) -> (key::SecretKey, /// Utility function used to parse hex into a target u8 buffer. Returns /// the number of bytes converted or an error if it encounters an invalid /// character or unexpected end of string. -fn from_hex(hex: &str, target: &mut [u8]) -> Result { - if hex.len() % 2 == 1 || hex.len() > target.len() * 2 { - return Err(()); +fn from_hex(hex: &str, target: &mut [u8]) -> Result { + if hex.len() % 2 == 1 { + return Err(FromHexError::UnevenLength(hex.len())); + } + + if hex.len() > target.len() * 2 { + return Err(FromHexError::BufferTooSmall { hex: hex.len(), buffer: target.len() }); } let mut b = 0; @@ -463,7 +420,7 @@ fn from_hex(hex: &str, target: &mut [u8]) -> Result { b'A'..=b'F' => b |= c - b'A' + 10, b'a'..=b'f' => b |= c - b'a' + 10, b'0'..=b'9' => b |= c - b'0', - _ => return Err(()), + byte => return Err(FromHexError::InvalidByte(byte)), } if (idx & 1) == 1 { target[idx / 2] = b; @@ -478,10 +435,10 @@ fn from_hex(hex: &str, target: &mut [u8]) -> Result { /// a reference to the target buffer as an str. Returns an error if the target /// buffer isn't big enough. #[inline] -fn to_hex<'a>(src: &[u8], target: &'a mut [u8]) -> Result<&'a str, ()> { +fn to_hex<'a>(src: &[u8], target: &'a mut [u8]) -> Result<&'a str, ToHexError> { let hex_len = src.len() * 2; if target.len() < hex_len { - return Err(()); + return Err(ToHexError { hex: hex_len, buffer: target.len() }); } const HEX_TABLE: [u8; 16] = *b"0123456789abcdef"; @@ -503,6 +460,78 @@ pub(crate) fn random_32_bytes(rng: &mut R) -> [u8; 32] { ret } +/// Error converting from a hex string. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum FromHexError { + /// Hex string length uneven. + UnevenLength(usize), + /// Target data buffer too small to decode hex. + BufferTooSmall { + /// Length of the hex string. + hex: usize, + /// Size of the target data buffer. + buffer: usize, + }, + /// Byte is not valid hex ASCII. + InvalidByte(u8), +} + +impl core::fmt::Display for FromHexError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> { + use FromHexError::*; + + match *self { + UnevenLength(len) => write!(f, "hex string uneven: {}", len), + BufferTooSmall { hex, buffer } => { + write!( + f, + "buffer too small to decode hex (hex length: {}, buffer size: {})", + hex, buffer + ) + } + InvalidByte(byte) => write!(f, "byte is not valid hex ASCII: {:x}", byte), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for FromHexError {} + +/// Buffer too small to encode hex data. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ToHexError { + /// Required length of the encoded hex string. + pub hex: usize, + /// Size of the buffer (must be equal or larger that hex length). + pub buffer: usize, +} + +impl core::fmt::Display for ToHexError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> { + write!( + f, + "buffer too small to encode hex (required: {}, buffer: {})", + self.hex, self.buffer + ) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ToHexError {} + +/// Hex error. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct HexError; + +impl core::fmt::Display for HexError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> { + f.write_str("hex error") + } +} + +#[cfg(feature = "std")] +impl std::error::Error for HexError {} + #[cfg(test)] mod tests { use std::str::FromStr; @@ -510,9 +539,9 @@ mod tests { #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::wasm_bindgen_test as test; - #[allow(unused_imports)] // When building with no default features. use super::*; - use crate::{constants, ecdsa, from_hex, Error, Message}; + use crate::error::SysError; + use crate::{constants, ecdsa, from_hex, Message}; #[cfg(feature = "alloc")] use crate::{ffi, PublicKey, Secp256k1, SecretKey}; @@ -830,27 +859,27 @@ mod tests { let msg = crate::random_32_bytes(&mut rand::thread_rng()); let msg = Message::from_slice(&msg).unwrap(); - assert_eq!(s.verify_ecdsa(&msg, &sig, &pk), Err(Error::IncorrectSignature)); + assert_eq!(s.verify_ecdsa(&msg, &sig, &pk), Err(SysError)); } #[test] fn test_bad_slice() { assert_eq!( ecdsa::Signature::from_der(&[0; constants::MAX_SIGNATURE_SIZE + 1]), - Err(Error::InvalidSignature) + Err(ecdsa::SignatureError::Sys(SysError)) ); assert_eq!( ecdsa::Signature::from_der(&[0; constants::MAX_SIGNATURE_SIZE]), - Err(Error::InvalidSignature) + Err(ecdsa::SignatureError::Sys(SysError)) ); assert_eq!( Message::from_slice(&[0; constants::MESSAGE_SIZE - 1]), - Err(Error::InvalidMessage) + Err(crate::MessageLengthError(31)) ); assert_eq!( Message::from_slice(&[0; constants::MESSAGE_SIZE + 1]), - Err(Error::InvalidMessage) + Err(MessageLengthError(33)) ); assert!(Message::from_slice(&[0; constants::MESSAGE_SIZE]).is_ok()); assert!(Message::from_slice(&[1; constants::MESSAGE_SIZE]).is_ok()); @@ -922,7 +951,7 @@ mod tests { let msg = Message::from_slice(&msg[..]).unwrap(); // without normalization we expect this will fail - assert_eq!(secp.verify_ecdsa(&msg, &sig, &pk), Err(Error::IncorrectSignature)); + assert_eq!(secp.verify_ecdsa(&msg, &sig, &pk), Err(SysError)); // after normalization it should pass sig.normalize_s(); assert_eq!(secp.verify_ecdsa(&msg, &sig, &pk), Ok(())); diff --git a/src/macros.rs b/src/macros.rs index 4111b3f0c..007e7f3a7 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -70,25 +70,6 @@ macro_rules! impl_non_secure_erase { }; } -/// Formats error. If `std` feature is OFF appends error source (delimited by `: `). We do this -/// because `e.source()` is only available in std builds, without this macro the error source is -/// lost for no-std builds. -macro_rules! write_err { - ($writer:expr, $string:literal $(, $args:expr),*; $source:expr) => { - { - #[cfg(feature = "std")] - { - let _ = &$source; // Prevents clippy warnings. - write!($writer, $string $(, $args)*) - } - #[cfg(not(feature = "std"))] - { - write!($writer, concat!($string, ": {}") $(, $args)*, $source) - } - } - } -} - /// Implements fast unstable comparison methods for `$ty`. macro_rules! impl_fast_comparisons { ($ty:ident) => { diff --git a/src/schnorr.rs b/src/schnorr.rs index aa0270391..440bdd018 100644 --- a/src/schnorr.rs +++ b/src/schnorr.rs @@ -12,9 +12,7 @@ use crate::ffi::{self, CPtr}; use crate::key::{KeyPair, XOnlyPublicKey}; #[cfg(feature = "global-context")] use crate::SECP256K1; -use crate::{ - constants, from_hex, impl_array_newtype, Error, Message, Secp256k1, Signing, Verification, -}; +use crate::{constants, from_hex, impl_array_newtype, Message, Secp256k1, Signing, Verification}; /// Represents a schnorr signature. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -63,13 +61,13 @@ impl fmt::Display for Signature { } impl str::FromStr for Signature { - type Err = Error; + type Err = SignatureError; fn from_str(s: &str) -> Result { let mut res = [0u8; constants::SCHNORR_SIGNATURE_SIZE]; match from_hex(s, &mut res) { Ok(constants::SCHNORR_SIGNATURE_SIZE) => Signature::from_slice(&res[0..constants::SCHNORR_SIGNATURE_SIZE]), - _ => Err(Error::InvalidSignature), + _ => Err(SignatureError), } } } @@ -77,14 +75,14 @@ impl str::FromStr for Signature { impl Signature { /// Creates a `Signature` directly from a slice. #[inline] - pub fn from_slice(data: &[u8]) -> Result { + pub fn from_slice(data: &[u8]) -> Result { match data.len() { constants::SCHNORR_SIGNATURE_SIZE => { let mut ret = [0u8; constants::SCHNORR_SIGNATURE_SIZE]; ret[..].copy_from_slice(data); Ok(Signature(ret)) } - _ => Err(Error::InvalidSignature), + _ => Err(SignatureError), } } @@ -95,7 +93,7 @@ impl Signature { /// Verifies a schnorr signature for `msg` using `pk` and the global [`SECP256K1`] context. #[inline] #[cfg(feature = "global-context")] - pub fn verify(&self, msg: &Message, pk: &XOnlyPublicKey) -> Result<(), Error> { + pub fn verify(&self, msg: &Message, pk: &XOnlyPublicKey) -> Result<(), SignatureError> { SECP256K1.verify_schnorr(self, msg, pk) } } @@ -168,7 +166,7 @@ impl Secp256k1 { sig: &Signature, msg: &Message, pubkey: &XOnlyPublicKey, - ) -> Result<(), Error> { + ) -> Result<(), SignatureError> { unsafe { let ret = ffi::secp256k1_schnorrsig_verify( self.ctx.as_ptr(), @@ -181,12 +179,23 @@ impl Secp256k1 { if ret == 1 { Ok(()) } else { - Err(Error::InvalidSignature) + Err(SignatureError) } } } } +/// Signature is invalid. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct SignatureError; + +impl fmt::Display for SignatureError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("signature is invalid") } +} + +#[cfg(feature = "std")] +impl std::error::Error for SignatureError {} + #[cfg(test)] #[allow(unused_imports)] mod tests { @@ -198,8 +207,8 @@ mod tests { use wasm_bindgen_test::wasm_bindgen_test as test; use super::*; + use crate::key::error::PublicKeyError; use crate::schnorr::{KeyPair, Signature, XOnlyPublicKey}; - use crate::Error::InvalidPublicKey; use crate::{constants, from_hex, Message, Secp256k1, SecretKey}; #[cfg(all(not(secp256k1_fuzz), feature = "alloc"))] @@ -310,8 +319,8 @@ mod tests { #[test] fn test_pubkey_from_slice() { - assert_eq!(XOnlyPublicKey::from_slice(&[]), Err(InvalidPublicKey)); - assert_eq!(XOnlyPublicKey::from_slice(&[1, 2, 3]), Err(InvalidPublicKey)); + assert_eq!(XOnlyPublicKey::from_slice(&[]), Err(PublicKeyError)); + assert_eq!(XOnlyPublicKey::from_slice(&[1, 2, 3]), Err(PublicKeyError)); let pk = XOnlyPublicKey::from_slice(&[ 0xB3, 0x3C, 0xC9, 0xED, 0xC0, 0x96, 0xD0, 0xA8, 0x34, 0x16, 0x96, 0x4B, 0xD3, 0xC6, 0x24, 0x7B, 0x8F, 0xEC, 0xD2, 0x56, 0xE4, 0xEF, 0xA7, 0x87, 0x0D, 0x2C, 0x85, 0x4B, @@ -351,26 +360,26 @@ mod tests { // Bad sizes assert_eq!( XOnlyPublicKey::from_slice(&[0; constants::SCHNORR_PUBLIC_KEY_SIZE - 1]), - Err(InvalidPublicKey) + Err(PublicKeyError) ); assert_eq!( XOnlyPublicKey::from_slice(&[0; constants::SCHNORR_PUBLIC_KEY_SIZE + 1]), - Err(InvalidPublicKey) + Err(PublicKeyError) ); // Bad parse assert_eq!( XOnlyPublicKey::from_slice(&[0xff; constants::SCHNORR_PUBLIC_KEY_SIZE]), - Err(InvalidPublicKey) + Err(PublicKeyError) ); // In fuzzing mode restrictions on public key validity are much more // relaxed, thus the invalid check below is expected to fail. #[cfg(not(secp256k1_fuzz))] assert_eq!( XOnlyPublicKey::from_slice(&[0x55; constants::SCHNORR_PUBLIC_KEY_SIZE]), - Err(InvalidPublicKey) + Err(PublicKeyError) ); - assert_eq!(XOnlyPublicKey::from_slice(&[]), Err(InvalidPublicKey)); + assert_eq!(XOnlyPublicKey::from_slice(&[]), Err(PublicKeyError)); } #[test]