crypto: Change taproot::Signature FromStr to array hex decode
What changed, and why it matters
This commit is a small internal cleanup in the rust-bitcoin library. It changes how a Taproot signature is parsed from a hexadecimal string so that it no longer needs to allocate a temporary byte vector. The change removes an unnecessary memory allocation but does not alter the allowed signature formats or fix any vulnerability. It is a performance and dependency-reduction refactor, not a security fix.
No security action required. Treat as a normal refactor. If reviewing, verify that the new error messages and variant names are acceptable API changes for downstream consumers.
Security signals we found
No memory-safety bug is fixed: the old code used a heap allocation, not an unsafe buffer overflow.
No input validation change: accepted hex lengths remain 128 or 130 characters.
No cryptographic correctness change: from_slice is still the final decoder.
No advisory, CVE, or security discussion is present in the commit message or diff.
Evidence from the diff
The FromStr implementation for taproot::Signature previously used hex::decode_to_vec (variable-length, heap-allocated Vec
Changed components
crypto/src/taproot.rstaproot::Signature FromStr parserParseSignatureError enumInspect captured patch +24 / −6
diff --git a/crypto/src/taproot.rs b/crypto/src/taproot.rs
index d02474d3..e2a91d42 100644
--- a/crypto/src/taproot.rs
+++ b/crypto/src/taproot.rs
@@ -116,8 +116,20 @@ impl FromStr for Signature {
type Err = ParseSignatureError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
- let bytes = hex::decode_to_vec(s).map_err(ParseSignatureError::Hex)?;
- Self::from_slice(&bytes).map_err(ParseSignatureError::Decode)
+ match hex::decode_to_array::<64>(s) {
+ Ok(bytes) => Self::from_slice(&bytes).map_err(ParseSignatureError::Decode),
+ Err(hex::DecodeFixedLengthBytesError::InvalidChar(error)) =>
+ Err(ParseSignatureError::InvalidChar(error)),
+ Err(hex::DecodeFixedLengthBytesError::InvalidLength(_)) => {
+ match hex::decode_to_array::<65>(s) {
+ Ok(bytes) => Self::from_slice(&bytes).map_err(ParseSignatureError::Decode),
+ Err(hex::DecodeFixedLengthBytesError::InvalidChar(error)) =>
+ Err(ParseSignatureError::InvalidChar(error)),
+ Err(hex::DecodeFixedLengthBytesError::InvalidLength(_)) =>
+ Err(ParseSignatureError::InvalidLength(s.len())),
+ }
+ }
+ }
}
}
@@ -461,8 +473,10 @@ pub mod error {
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ParseSignatureError {
- /// Hex string decoding error.
- Hex(hex::DecodeVariableLengthBytesError),
+ /// Hex string invalid length error.
+ InvalidLength(usize),
+ /// Hex string invalid character error.
+ InvalidChar(hex::error::InvalidCharError),
/// Signature byte slice decoding error.
Decode(SigFromSliceError),
}
@@ -474,7 +488,10 @@ pub mod error {
impl fmt::Display for ParseSignatureError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
- Self::Hex(ref e) => write_err!(f, "signature hex decoding error"; e),
+ Self::InvalidLength(len) =>
+ write!(f, "signature must be 128 or 130 ASCII characters long but it had {} bytes", len),
+ Self::InvalidChar(ref e) =>
+ write_err!(f, "invalid character in signature"; e),
Self::Decode(ref e) => write_err!(f, "signature byte slice decoding error"; e),
}
}
@@ -484,7 +501,8 @@ pub mod error {
impl std::error::Error for ParseSignatureError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
- Self::Hex(ref e) => Some(e),
+ Self::InvalidLength(_) => None,
+ Self::InvalidChar(ref e) => Some(e),
Self::Decode(ref e) => Some(e),
}
}
Why this scored 17/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.