What changed, and why it matters
This commit adds a new convenience method, XOnlyPublicKey::verify, that lets users verify Schnorr (taproot) signatures without calling the underlying secp256k1 library directly. It is a straightforward API addition with no obvious security bug; it simply wraps the existing, well-tested secp256k1 Schnorr verification routine.
No security action required. Treat as a normal API enhancement; review for documentation and error-message clarity if desired.
Security signals we found
New public API surface for signature verification
Wraps secp256k1::schnorr::verify without altering verification semantics
No input parsing, no secret-key handling, no allocator changes
No mention of security fix, vulnerability, CVE, or researcher attribution in commit
Evidence from the diff
The change introduces pub fn verify(&self, msg: &[u8], sig: taproot::Signature) -> Result<(), VerifyError> on XOnlyPublicKey. It delegates to secp256k1::schnorr::verify using sig.signature, the supplied message, and the inner x-only public key, mapping any failure to the crate’s existing VerifyError. A unit test confirms a valid signature passes and a wrong message fails. There is no change to cryptographic logic, parsing, serialization, or memory-unsafe code.
Changed components
rust-bitcoin crypto cratecrypto/src/key.rsXOnlyPublicKeytaproot::Signature verification wrapperInspect captured patch +28 / −1
diff --git a/crypto/src/key.rs b/crypto/src/key.rs
index 851210b2..4775b433 100644
--- a/crypto/src/key.rs
+++ b/crypto/src/key.rs
@@ -27,9 +27,9 @@ use network::NetworkKind;
pub use secp256k1::rand;
use self::error::FromSecretBytesErrorInner;
-use crate::ecdsa;
#[cfg(feature = "hex")]
use crate::hex::{self, DecodeFixedLengthBytesError};
+use crate::{ecdsa, taproot};
#[rustfmt::skip] // Keep public re-exports separate.
pub use secp256k1::{constants, Parity};
@@ -449,6 +449,16 @@ impl XOnlyPublicKey {
}
}
+ /// Checks that `sig` is a valid Schnorr signature for `msg` using this public key.
+ ///
+ /// # Errors
+ ///
+ /// [`VerifyError`] if the signature is not valid for the given message and key.
+ #[inline]
+ pub fn verify(&self, msg: &[u8], sig: taproot::Signature) -> Result<(), VerifyError> {
+ secp256k1::schnorr::verify(&sig.signature, msg, &self.to_inner()).map_err(|_| VerifyError)
+ }
+
/// Gets the hex representation of this type.
#[cfg(feature = "alloc")]
#[deprecated(since = "TBD", note = "use `format!(\"{var:x}\")` instead")]
@@ -2524,4 +2534,21 @@ mod tests {
let deser = LegacyPublicKey::from_slice(serialized).unwrap();
assert_eq!(deser, key);
}
+
+ #[test]
+ fn xonly_pubkey_verify() {
+ let keypair = "0101010101010101010101010101010101010101010101010101010101010101"
+ .parse::<Keypair>()
+ .unwrap();
+ let xonly = keypair.to_x_only_public_key();
+ let msg = b"test message for schnorr verification";
+ let secp_sig = keypair.raw_bip340_sign(msg);
+ let sig = taproot::Signature {
+ signature: secp_sig,
+ sighash_type: crate::sighash::TapSighashType::Default,
+ };
+
+ assert!(xonly.verify(msg, sig).is_ok());
+ assert!(matches!(xonly.verify(b"different message", sig).unwrap_err(), VerifyError));
+ }
}
Why this scored 18/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.