diff --git a/src/ecdh.rs b/src/ecdh.rs index 913a747b8..6b4c7c285 100644 --- a/src/ecdh.rs +++ b/src/ecdh.rs @@ -10,7 +10,7 @@ use secp256k1_sys::types::{c_int, c_uchar, c_void}; use crate::ffi::{self, CPtr}; use crate::key::{PublicKey, SecretKey}; -use crate::{constants, Error}; +use crate::{constants, hex, Error}; // The logic for displaying shared secrets relies on this (see `secret.rs`). const SHARED_SECRET_SIZE: usize = constants::SECRET_KEY_SIZE; @@ -81,7 +81,7 @@ impl str::FromStr for SharedSecret { type Err = Error; fn from_str(s: &str) -> Result { let mut res = [0u8; SHARED_SECRET_SIZE]; - match crate::from_hex(s, &mut res) { + match hex::from_hex(s, &mut res) { Ok(SHARED_SECRET_SIZE) => Ok(SharedSecret::from_bytes(res)), _ => Err(Error::InvalidSharedSecret), } @@ -160,7 +160,7 @@ 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(crate::to_hex(&self.0, &mut buf).expect("fixed-size hex serialization")) + s.serialize_str(hex::to_hex(&self.0, &mut buf).expect("fixed-size hex serialization")) } else { s.serialize_bytes(self.as_ref()) } diff --git a/src/ecdsa/mod.rs b/src/ecdsa/mod.rs index e054368b6..45bf85f50 100644 --- a/src/ecdsa/mod.rs +++ b/src/ecdsa/mod.rs @@ -15,9 +15,7 @@ pub use self::serialized_signature::SerializedSignature; use crate::ffi::CPtr; #[cfg(feature = "global-context")] use crate::SECP256K1; -use crate::{ - ffi, from_hex, Error, Message, PublicKey, Secp256k1, SecretKey, Signing, Verification, -}; +use crate::{ffi, hex, Error, Message, PublicKey, Secp256k1, SecretKey, Signing, Verification}; /// An ECDSA signature #[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)] @@ -39,7 +37,7 @@ impl str::FromStr for Signature { type Err = Error; fn from_str(s: &str) -> Result { let mut res = [0u8; 72]; - match from_hex(s, &mut res) { + match hex::from_hex(s, &mut res) { Ok(x) => Signature::from_der(&res[0..x]), _ => Err(Error::InvalidSignature), } diff --git a/src/ellswift.rs b/src/ellswift.rs index 7cc3679a4..e95eaa1a5 100644 --- a/src/ellswift.rs +++ b/src/ellswift.rs @@ -42,7 +42,7 @@ use core::str::FromStr; use ffi::CPtr; use secp256k1_sys::types::{c_int, c_uchar, c_void}; -use crate::{constants, ffi, from_hex, Error, PublicKey, Secp256k1, SecretKey, Verification}; +use crate::{constants, ffi, hex, Error, PublicKey, Secp256k1, SecretKey, Verification}; unsafe extern "C" fn hash_callback( output: *mut c_uchar, @@ -292,7 +292,7 @@ impl FromStr for ElligatorSwift { fn from_str(hex: &str) -> Result { let mut ser = [0u8; 64]; - let parsed = from_hex(hex, &mut ser); + let parsed = hex::from_hex(hex, &mut ser); match parsed { Ok(64) => Ok(ElligatorSwift::from_array(ser)), _ => Err(Error::InvalidEllSwift), @@ -329,7 +329,7 @@ mod tests { use crate::ellswift::{ElligatorSwiftParty, ElligatorSwiftSharedSecret}; #[cfg(all(not(secp256k1_fuzz), feature = "alloc"))] use crate::SecretKey; - use crate::{from_hex, PublicKey, XOnlyPublicKey}; + use crate::{hex, PublicKey, XOnlyPublicKey}; #[test] #[cfg(all(not(secp256k1_fuzz), feature = "alloc"))] @@ -604,7 +604,7 @@ mod tests { #[inline] fn parse_test(ell: &str, x: &str, parity: u32) -> EllswiftDecodeTest { let mut enc = [0u8; 64]; - from_hex(ell, &mut enc).unwrap(); + hex::from_hex(ell, &mut enc).unwrap(); let xo = XOnlyPublicKey::from_str(x).unwrap(); let parity = if parity == 0 { crate::Parity::Even } else { crate::Parity::Odd }; let pk = PublicKey::from_x_only_public_key(xo, parity); diff --git a/src/hex.rs b/src/hex.rs new file mode 100644 index 000000000..8cf889369 --- /dev/null +++ b/src/hex.rs @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Conversion to and from hexadecimal strings. + +use core::str; + +/// 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. +pub(crate) fn from_hex(hex: &str, target: &mut [u8]) -> Result { + if hex.len() % 2 == 1 || hex.len() > target.len() * 2 { + return Err(()); + } + + let mut b = 0; + let mut idx = 0; + for c in hex.bytes() { + b <<= 4; + match c { + 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(()), + } + if (idx & 1) == 1 { + target[idx / 2] = b; + b = 0; + } + idx += 1; + } + Ok(idx / 2) +} + +/// Utility function used to encode hex into a target u8 buffer. Returns +/// a reference to the target buffer as an str. Returns an error if the target +/// buffer isn't big enough. +#[inline] +pub(crate) fn to_hex<'a>(src: &[u8], target: &'a mut [u8]) -> Result<&'a str, ()> { + let hex_len = src.len() * 2; + if target.len() < hex_len { + return Err(()); + } + const HEX_TABLE: [u8; 16] = *b"0123456789abcdef"; + + let mut i = 0; + for &b in src { + target[i] = HEX_TABLE[usize::from(b >> 4)]; + target[i + 1] = HEX_TABLE[usize::from(b & 0b00001111)]; + i += 2; + } + let result = &target[..hex_len]; + debug_assert!(str::from_utf8(result).is_ok()); + return unsafe { Ok(str::from_utf8_unchecked(result)) }; +} diff --git a/src/key.rs b/src/key.rs index 48bf81ba4..8cc56deb5 100644 --- a/src/key.rs +++ b/src/key.rs @@ -16,9 +16,7 @@ use crate::ffi::{self, CPtr}; use crate::Error::{self, InvalidPublicKey, InvalidPublicKeySum, InvalidSecretKey}; #[cfg(feature = "global-context")] use crate::SECP256K1; -use crate::{ - constants, ecdsa, from_hex, schnorr, Message, Scalar, Secp256k1, Signing, Verification, -}; +use crate::{constants, ecdsa, hex, schnorr, Message, Scalar, Secp256k1, Signing, Verification}; #[cfg(feature = "hashes")] use crate::{hashes, ThirtyTwoByteHash}; @@ -114,7 +112,7 @@ impl str::FromStr for SecretKey { type Err = Error; fn from_str(s: &str) -> Result { let mut res = [0u8; constants::SECRET_KEY_SIZE]; - match from_hex(s, &mut res) { + match hex::from_hex(s, &mut res) { Ok(constants::SECRET_KEY_SIZE) => SecretKey::from_slice(&res), _ => Err(Error::InvalidSecretKey), } @@ -167,7 +165,7 @@ impl str::FromStr for PublicKey { type Err = Error; fn from_str(s: &str) -> Result { let mut res = [0u8; constants::UNCOMPRESSED_PUBLIC_KEY_SIZE]; - match from_hex(s, &mut res) { + match hex::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), @@ -385,7 +383,7 @@ impl serde::Serialize for SecretKey { fn serialize(&self, s: S) -> Result { if s.is_human_readable() { let mut buf = [0u8; constants::SECRET_KEY_SIZE * 2]; - s.serialize_str(crate::to_hex(&self.0, &mut buf).expect("fixed-size hex serialization")) + s.serialize_str(hex::to_hex(&self.0, &mut buf).expect("fixed-size hex serialization")) } else { let mut tuple = s.serialize_tuple(constants::SECRET_KEY_SIZE)?; for byte in self.0.iter() { @@ -859,7 +857,7 @@ impl Keypair { #[inline] pub fn from_seckey_str(secp: &Secp256k1, s: &str) -> Result { let mut res = [0u8; constants::SECRET_KEY_SIZE]; - match from_hex(s, &mut res) { + match hex::from_hex(s, &mut res) { Ok(constants::SECRET_KEY_SIZE) => Keypair::from_seckey_slice(secp, &res[0..constants::SECRET_KEY_SIZE]), _ => Err(Error::InvalidPublicKey), @@ -1041,8 +1039,7 @@ impl serde::Serialize for Keypair { if s.is_human_readable() { let mut buf = [0u8; constants::SECRET_KEY_SIZE * 2]; s.serialize_str( - crate::to_hex(&self.secret_bytes(), &mut buf) - .expect("fixed-size hex serialization"), + hex::to_hex(&self.secret_bytes(), &mut buf).expect("fixed-size hex serialization"), ) } else { let mut tuple = s.serialize_tuple(constants::SECRET_KEY_SIZE)?; @@ -1134,7 +1131,7 @@ impl str::FromStr for XOnlyPublicKey { type Err = Error; fn from_str(s: &str) -> Result { let mut res = [0u8; constants::SCHNORR_PUBLIC_KEY_SIZE]; - match from_hex(s, &mut res) { + match hex::from_hex(s, &mut res) { Ok(constants::SCHNORR_PUBLIC_KEY_SIZE) => XOnlyPublicKey::from_slice(&res[0..constants::SCHNORR_PUBLIC_KEY_SIZE]), _ => Err(Error::InvalidPublicKey), @@ -1559,13 +1556,13 @@ mod test { use super::{Keypair, Parity, PublicKey, Secp256k1, SecretKey, XOnlyPublicKey, *}; use crate::Error::{InvalidPublicKey, InvalidSecretKey}; - use crate::{constants, from_hex, to_hex, Scalar}; + use crate::{constants, hex, Scalar}; #[cfg(not(secp256k1_fuzz))] macro_rules! hex { ($hex:expr) => {{ let mut result = vec![0; $hex.len() / 2]; - from_hex($hex, &mut result).expect("valid hex string"); + hex::from_hex($hex, &mut result).expect("valid hex string"); result }}; } @@ -1745,7 +1742,7 @@ mod test { let mut buf = [0u8; constants::SECRET_KEY_SIZE * 2]; assert_eq!( - to_hex(&sk[..], &mut buf).unwrap(), + hex::to_hex(&sk[..], &mut buf).unwrap(), "0100000000000000020000000000000003000000000000000400000000000000" ); } @@ -2422,7 +2419,7 @@ mod test { let ctx = crate::Secp256k1::new(); let keypair = Keypair::new(&ctx, &mut rand::thread_rng()); let mut buf = [0_u8; constants::SECRET_KEY_SIZE * 2]; // Holds hex digits. - let s = to_hex(&keypair.secret_key().secret_bytes(), &mut buf).unwrap(); + let s = hex::to_hex(&keypair.secret_key().secret_bytes(), &mut buf).unwrap(); let parsed_key = Keypair::from_str(s).unwrap(); assert_eq!(parsed_key, keypair); } diff --git a/src/lib.rs b/src/lib.rs index 9a092b526..1179bf019 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -159,6 +159,7 @@ mod macros; #[macro_use] mod secret; mod context; +mod hex; mod key; pub mod constants; @@ -172,7 +173,7 @@ mod serde_util; use core::marker::PhantomData; use core::ptr::NonNull; -use core::{fmt, mem, str}; +use core::{fmt, mem}; #[cfg(feature = "global-context")] pub use context::global::SECP256K1; @@ -483,55 +484,6 @@ pub fn generate_keypair(rng: &mut R) -> (key::SecretKey, SECP256K1.generate_keypair(rng) } -/// 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(()); - } - - let mut b = 0; - let mut idx = 0; - for c in hex.bytes() { - b <<= 4; - match c { - 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(()), - } - if (idx & 1) == 1 { - target[idx / 2] = b; - b = 0; - } - idx += 1; - } - Ok(idx / 2) -} - -/// Utility function used to encode hex into a target u8 buffer. Returns -/// 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, ()> { - let hex_len = src.len() * 2; - if target.len() < hex_len { - return Err(()); - } - const HEX_TABLE: [u8; 16] = *b"0123456789abcdef"; - - let mut i = 0; - for &b in src { - target[i] = HEX_TABLE[usize::from(b >> 4)]; - target[i + 1] = HEX_TABLE[usize::from(b & 0b00001111)]; - i += 2; - } - let result = &target[..hex_len]; - debug_assert!(str::from_utf8(result).is_ok()); - return unsafe { Ok(str::from_utf8_unchecked(result)) }; -} - #[cfg(feature = "rand")] pub(crate) fn random_32_bytes(rng: &mut R) -> [u8; 32] { let mut ret = [0u8; 32]; @@ -551,7 +503,7 @@ mod tests { macro_rules! hex { ($hex:expr) => {{ let mut result = vec![0; $hex.len() / 2]; - from_hex($hex, &mut result).expect("valid hex string"); + hex::from_hex($hex, &mut result).expect("valid hex string"); result }}; } @@ -888,8 +840,6 @@ mod tests { fn test_hex() { use rand::RngCore; - use super::to_hex; - let mut rng = rand::thread_rng(); const AMOUNT: usize = 1024; for i in 0..AMOUNT { @@ -900,17 +850,17 @@ mod tests { let src = &mut src_buf[0..i]; rng.fill_bytes(src); - let hex = to_hex(src, &mut hex_buf).unwrap(); - assert_eq!(from_hex(hex, &mut result_buf).unwrap(), i); + let hex = hex::to_hex(src, &mut hex_buf).unwrap(); + assert_eq!(hex::from_hex(hex, &mut result_buf).unwrap(), i); assert_eq!(src, &result_buf[..i]); } - assert!(to_hex(&[1; 2], &mut [0u8; 3]).is_err()); - assert!(to_hex(&[1; 2], &mut [0u8; 4]).is_ok()); - assert!(from_hex("deadbeaf", &mut [0u8; 3]).is_err()); - assert!(from_hex("deadbeaf", &mut [0u8; 4]).is_ok()); - assert!(from_hex("a", &mut [0u8; 4]).is_err()); - assert!(from_hex("ag", &mut [0u8; 4]).is_err()); + assert!(hex::to_hex(&[1; 2], &mut [0u8; 3]).is_err()); + assert!(hex::to_hex(&[1; 2], &mut [0u8; 4]).is_ok()); + assert!(hex::from_hex("deadbeaf", &mut [0u8; 3]).is_err()); + assert!(hex::from_hex("deadbeaf", &mut [0u8; 4]).is_ok()); + assert!(hex::from_hex("a", &mut [0u8; 4]).is_err()); + assert!(hex::from_hex("ag", &mut [0u8; 4]).is_err()); } #[test] diff --git a/src/schnorr.rs b/src/schnorr.rs index d6a9ed13f..b7d496dc7 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, hex, impl_array_newtype, Error, Message, Secp256k1, Signing, Verification}; /// Represents a schnorr signature. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -66,7 +64,7 @@ impl str::FromStr for Signature { type Err = Error; fn from_str(s: &str) -> Result { let mut res = [0u8; constants::SCHNORR_SIGNATURE_SIZE]; - match from_hex(s, &mut res) { + match hex::from_hex(s, &mut res) { Ok(constants::SCHNORR_SIGNATURE_SIZE) => Signature::from_slice(&res[0..constants::SCHNORR_SIGNATURE_SIZE]), _ => Err(Error::InvalidSignature), @@ -200,13 +198,13 @@ mod tests { use super::*; use crate::schnorr::{Keypair, Signature, XOnlyPublicKey}; use crate::Error::InvalidPublicKey; - use crate::{constants, from_hex, Message, Secp256k1, SecretKey}; + use crate::{constants, hex, Message, Secp256k1, SecretKey}; #[cfg(all(not(secp256k1_fuzz), feature = "alloc"))] macro_rules! hex_32 { ($hex:expr) => {{ let mut result = [0u8; 32]; - from_hex($hex, &mut result).expect("valid hex string"); + hex::from_hex($hex, &mut result).expect("valid hex string"); result }}; } diff --git a/src/secret.rs b/src/secret.rs index f13d91ae9..82742de7b 100644 --- a/src/secret.rs +++ b/src/secret.rs @@ -6,8 +6,8 @@ use core::fmt; use crate::constants::SECRET_KEY_SIZE; use crate::ecdh::SharedSecret; +use crate::hex; use crate::key::{Keypair, SecretKey}; -use crate::to_hex; macro_rules! impl_display_secret { // Default hasher exists only in standard library and not alloc ($thing:ident) => { @@ -79,7 +79,7 @@ impl fmt::Debug for DisplaySecret { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut slice = [0u8; SECRET_KEY_SIZE * 2]; - let hex = to_hex(&self.secret, &mut slice).expect("fixed-size hex serializer failed"); + let hex = hex::to_hex(&self.secret, &mut slice).expect("fixed-size hex serializer failed"); f.debug_tuple("DisplaySecret").field(&hex).finish() } }