diff --git a/Cargo.toml b/Cargo.toml index 7017734..fe1d562 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,11 +14,11 @@ libsecp256k1-core = { version = "0.3.0", path = "core", default-features = false arrayref = "0.3" rand = { version = "0.8", default-features = false } digest = "0.9" -base64 = { version = "0.13", default-features = false } +base64 = { version = "0.21.2", default-features = false, optional = true } hmac-drbg = { version = "0.3", optional = true } sha2 = { version = "0.9", optional = true, default-features = false } typenum = { version = "1.12", optional = true } -serde = { version = "1.0.104", features = ["derive"], default-features = false } +serde = { version = "1.0.104", features = ["derive"], default-features = false, optional = true } lazy_static = { version = "1.4.0", optional = true } [dev-dependencies] @@ -28,14 +28,20 @@ serde_json = "1.0" hex = "0.4" hex-literal = "0.3.3" bincode = "1.3.3" +serde = { version = "1.0.104", features = ["derive"], default-features = false } [build-dependencies] libsecp256k1-gen-ecmult = { version = "0.3.0", path = "gen/ecmult" } libsecp256k1-gen-genmult = { version = "0.3.0", path = "gen/genmult" } [features] -default = ["std", "hmac", "static-context"] -std = ["libsecp256k1-core/std", "sha2/std", "rand/std", "serde/std", "base64/std"] +default = ["std", "serde", "hmac", "static-context"] +# Historically, the way to get serde support for PublicKey was by +# enabling `std` feature. Te maintain backwards compatibility `std` +# enables `serde` feature. Remove `serde` from list below at least +# once 0.8 is released. +std = ["libsecp256k1-core/std", "sha2/std", "rand/std", "serde"] +serde = ["dep:serde", "base64"] hmac = ["hmac-drbg", "sha2", "typenum"] static-context = [] lazy-static-context = ["static-context", "lazy_static", "std"] diff --git a/src/lib.rs b/src/lib.rs index 4111b7e..255b926 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,8 +25,6 @@ use rand::Rng; use core::fmt; #[cfg(feature = "hmac")] use hmac_drbg::HmacDRBG; -#[cfg(feature = "std")] -use serde::{de, ser::Serializer, Deserialize, Serialize}; #[cfg(feature = "hmac")] use sha2::Sha256; #[cfg(feature = "hmac")] @@ -330,25 +328,38 @@ impl TryFrom for PublicKey { } } -#[cfg(feature = "std")] -impl Serialize for PublicKey { +#[cfg(feature = "serde")] +impl serde::Serialize for PublicKey { fn serialize(&self, serializer: S) -> Result where - S: Serializer, + S: serde::ser::Serializer, { + use base64::Engine; + + // base64 encodes every three input bytes to four output characters + // rounding up. + const ENCODED_SIZE: usize = (util::FULL_PUBLIC_KEY_SIZE + 2) / 3 * 4; + + let bytes: [u8; util::FULL_PUBLIC_KEY_SIZE] = self.serialize(); if serializer.is_human_readable() { - serializer.serialize_str(&base64::encode(&self.serialize()[..])) + let mut buf = [0; ENCODED_SIZE]; + base64::engine::general_purpose::STANDARD + .encode_slice(&bytes[..], &mut buf[..]) + .unwrap(); + // SAFETY: base64 generates ASCII characters + let val = unsafe { core::str::from_utf8_unchecked(&buf[..]) }; + serializer.serialize_str(val) } else { - serializer.serialize_bytes(&self.serialize()) + serializer.serialize_bytes(&bytes) } } } -#[cfg(feature = "std")] +#[cfg(feature = "serde")] struct PublicKeyStrVisitor; -#[cfg(feature = "std")] -impl<'de> de::Visitor<'de> for PublicKeyStrVisitor { +#[cfg(feature = "serde")] +impl<'de> serde::de::Visitor<'de> for PublicKeyStrVisitor { type Value = PublicKey; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { @@ -358,25 +369,28 @@ impl<'de> de::Visitor<'de> for PublicKeyStrVisitor { fn visit_str(self, value: &str) -> Result where - E: de::Error, + E: serde::de::Error, { - let value: &[u8] = &base64::decode(value).map_err(|e| E::custom(e))?; - let key_format = match value.len() { - 33 => PublicKeyFormat::Compressed, - 64 => PublicKeyFormat::Raw, - 65 => PublicKeyFormat::Full, - _ => return Err(E::custom(Error::InvalidInputLength)), - }; - PublicKey::parse_slice(value, Some(key_format)) - .map_err(|_e| E::custom(Error::InvalidPublicKey)) + use base64::Engine; + + let mut buf = [0; 128]; + let len = base64::engine::general_purpose::STANDARD + .decode_slice(value.as_bytes(), &mut buf[..]) + .map_err(|err| match err { + base64::DecodeSliceError::DecodeError(err) => E::custom(err), + base64::DecodeSliceError::OutputSliceTooSmall => { + E::custom(Error::InvalidInputLength) + } + })?; + PublicKey::parse_slice(&buf[..len], None).map_err(|_e| E::custom(Error::InvalidPublicKey)) } } -#[cfg(feature = "std")] +#[cfg(feature = "serde")] struct PublicKeyBytesVisitor; -#[cfg(feature = "std")] -impl<'de> de::Visitor<'de> for PublicKeyBytesVisitor { +#[cfg(feature = "serde")] +impl<'de> serde::de::Visitor<'de> for PublicKeyBytesVisitor { type Value = PublicKey; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { @@ -387,17 +401,17 @@ impl<'de> de::Visitor<'de> for PublicKeyBytesVisitor { fn visit_bytes(self, value: &[u8]) -> Result where - E: de::Error, + E: serde::de::Error, { PublicKey::parse_slice(value, None).map_err(|_e| E::custom(Error::InvalidPublicKey)) } } -#[cfg(feature = "std")] -impl<'de> Deserialize<'de> for PublicKey { +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for PublicKey { fn deserialize(deserializer: D) -> Result where - D: de::Deserializer<'de>, + D: serde::de::Deserializer<'de>, { if deserializer.is_human_readable() { deserializer.deserialize_str(PublicKeyStrVisitor) diff --git a/tests/serde.rs b/tests/serde.rs index a9418d7..0f48cc1 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -1,4 +1,4 @@ -#![cfg(feature = "std")] +#![cfg(feature = "serde")] use libsecp256k1::*;