bitcoin: preserve parity for XOnlyPublicKey
What changed, and why it matters
This commit fixes a bug in how X-only public keys were created from full public keys. An X-only key is just the X coordinate of a point, but a point can have an even or odd Y coordinate (parity). The old code always assumed even parity, which could silently produce the wrong key for odd-parity inputs. The fix preserves the actual parity from the source key.
Review any code that converted a full PublicKey to XOnlyPublicKey before this patch, especially Taproot output construction, tweaking, or BIP-340 operations. Re-derive affected keys/addresses and verify parity correctness. Consider adding regression tests for odd-parity public keys.
Security signals we found
Incorrect cryptographic key derivation
Parity mismatch in X-only public key conversion
Potential Taproot address/script derivation divergence
Silent data corruption rather than crash
Evidence from the diff
Two From conversions for XOnlyPublicKey previously called from_secp() which internally defaults to Parity::Even. The patch extracts the true parity via x_only_public_key() and applies it with with_parity(). Affected paths: From
Changed components
bitcoin/src/crypto/key.rsXOnlyPublicKeyFrom<secp256k1::PublicKey> for XOnlyPublicKeyFrom<PublicKey> for XOnlyPublicKeyInspect captured patch +8 / −2
diff --git a/bitcoin/src/crypto/key.rs b/bitcoin/src/crypto/key.rs
index b0f09d23..b0fd6318 100644
--- a/bitcoin/src/crypto/key.rs
+++ b/bitcoin/src/crypto/key.rs
@@ -365,7 +365,10 @@ impl From<secp256k1::XOnlyPublicKey> for XOnlyPublicKey {
}
impl From<secp256k1::PublicKey> for XOnlyPublicKey {
- fn from(pk: secp256k1::PublicKey) -> Self { Self::from_secp(pk) }
+ fn from(pk: secp256k1::PublicKey) -> Self {
+ let (xonly, parity) = pk.x_only_public_key();
+ Self::from_secp(xonly).with_parity(parity)
+ }
}
impl fmt::LowerHex for XOnlyPublicKey {
@@ -654,7 +657,10 @@ impl From<secp256k1::PublicKey> for PublicKey {
}
impl From<PublicKey> for XOnlyPublicKey {
- fn from(pk: PublicKey) -> Self { Self::from_secp(pk.to_inner()) }
+ fn from(pk: PublicKey) -> Self {
+ let (xonly, parity) = pk.to_inner().x_only_public_key();
+ Self::from_secp(xonly).with_parity(parity)
+ }
}
/// An opaque return type for PublicKey::to_sort_key.
Why this scored 49/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.