diff --git a/src/ecdh.rs b/src/ecdh.rs index cba74913a..387716cb0 100644 --- a/src/ecdh.rs +++ b/src/ecdh.rs @@ -15,9 +15,10 @@ //! Support for shared secret computations. //! -use core::ptr; +use core::{ptr, str}; use core::borrow::Borrow; +use {Error, from_hex}; use key::{SecretKey, PublicKey}; use ffi::{self, CPtr}; use secp256k1_sys::types::{c_int, c_uchar, c_void}; @@ -72,10 +73,35 @@ impl SharedSecret { self.0 } - /// Creates a shared secret from a byte serialization. + /// Creates a shared secret from `bytes` array. + #[inline] pub fn from_bytes(bytes: [u8; SHARED_SECRET_SIZE]) -> SharedSecret { SharedSecret(bytes) } + + /// Creates a shared secret from `bytes` slice. + #[inline] + 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) + } + } +} + +impl str::FromStr for SharedSecret { + type Err = Error; + fn from_str(s: &str) -> Result { + let mut res = [0u8; SHARED_SECRET_SIZE]; + match from_hex(s, &mut res) { + Ok(SHARED_SECRET_SIZE) => Ok(SharedSecret::from_bytes(res)), + _ => Err(Error::InvalidSharedSecret) + } + } } impl Borrow<[u8]> for SharedSecret { @@ -145,6 +171,36 @@ unsafe extern "C" fn c_callback(output: *mut c_uchar, x: *const c_uchar, y: *con 1 } +#[cfg(feature = "serde")] +#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] +impl ::serde::Serialize for SharedSecret { + fn serialize(&self, s: S) -> Result { + if s.is_human_readable() { + let mut buf = [0u8; SHARED_SECRET_SIZE * 2]; + s.serialize_str(::to_hex(&self.0, &mut buf).expect("fixed-size hex serialization")) + } else { + s.serialize_bytes(&self.as_ref()[..]) + } + } +} + +#[cfg(feature = "serde")] +#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] +impl<'de> ::serde::Deserialize<'de> for SharedSecret { + fn deserialize>(d: D) -> Result { + if d.is_human_readable() { + d.deserialize_str(super::serde_util::FromStrVisitor::new( + "a hex string representing 32 byte SharedSecret" + )) + } else { + d.deserialize_bytes(super::serde_util::BytesVisitor::new( + "raw 32 bytes SharedSecret", + SharedSecret::from_slice + )) + } + } +} + #[cfg(test)] #[allow(unused_imports)] mod tests { @@ -207,6 +263,31 @@ mod tests { assert_eq!(secret_bh.as_inner(), secret_sys.as_ref()); } + + #[test] + #[cfg(all(feature = "serde", any(feature = "alloc", feature = "std")))] + fn serde() { + use serde_test::{Configure, Token, assert_tokens}; + static BYTES: [u8; 32] = [ + 1, 1, 1, 1, 1, 1, 1, 1, + 0, 1, 2, 3, 4, 5, 6, 7, + 0xff, 0xff, 0, 0, 0xff, 0xff, 0, 0, + 99, 99, 99, 99, 99, 99, 99, 99 + ]; + static STR: &'static str = "\ + 01010101010101010001020304050607ffff0000ffff00006363636363636363\ + "; + + let secret = SharedSecret::from_slice(&BYTES).unwrap(); + + assert_tokens(&secret.compact(), &[Token::BorrowedBytes(&BYTES[..])]); + assert_tokens(&secret.compact(), &[Token::Bytes(&BYTES)]); + assert_tokens(&secret.compact(), &[Token::ByteBuf(&BYTES)]); + + assert_tokens(&secret.readable(), &[Token::BorrowedStr(STR)]); + assert_tokens(&secret.readable(), &[Token::Str(STR)]); + assert_tokens(&secret.readable(), &[Token::String(STR)]); + } } #[cfg(all(test, feature = "unstable"))] diff --git a/src/lib.rs b/src/lib.rs index 20e85d3b7..1dd51a093 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -352,6 +352,8 @@ pub enum Error { InvalidSignature, /// Bad secret key. InvalidSecretKey, + /// Bad shared secret. + InvalidSharedSecret, /// Bad recovery id. InvalidRecoveryId, /// Invalid tweak for `add_*_assign` or `mul_*_assign`. @@ -372,6 +374,7 @@ impl Error { Error::InvalidPublicKey => "secp: malformed public key", Error::InvalidSignature => "secp: malformed signature", Error::InvalidSecretKey => "secp: malformed or out-of-range secret key", + Error::InvalidSharedSecret => "secp: malformed or out-of-range shared secret", Error::InvalidRecoveryId => "secp: bad recovery id", Error::InvalidTweak => "secp: bad tweak", Error::NotEnoughMemory => "secp: not enough memory allocated", @@ -399,6 +402,7 @@ impl std::error::Error for Error { Error::InvalidPublicKey => None, Error::InvalidSignature => None, Error::InvalidSecretKey => None, + Error::InvalidSharedSecret => None, Error::InvalidRecoveryId => None, Error::InvalidTweak => None, Error::NotEnoughMemory => None,