Return only XOnlyPublicKey from from_keypair
What changed, and why it matters
This commit is a routine API cleanup in the rust-bitcoin library. It changes a function so it returns only the x-only public key, instead of a pair of the key plus its parity bit. The parity information is now stored inside the key object itself, so callers no longer need to handle it separately. There is no security vulnerability here.
No security action needed. Developers upgrading to this version should update code that depends on the previous tuple return type of from_keypair/to_x_only_public_key.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch refactors XOnlyPublicKey::from_keypair and Keypair::to_x_only_public_key to return XOnlyPublicKey instead of (XOnlyPublicKey, Parity). The parity is now embedded via with_parity(parity) on the XOnlyPublicKey. Call sites are updated to remove .0 tuple access and _parity destructuring. One test changes an equality check to compare only the inner key. This is a breaking API change but not a security fix.
Changed components
bitcoin/src/crypto/key.rsbitcoin/src/crypto/sighash.rsbitcoin/examples/sign-tx-taproot.rsbitcoin/examples/taproot-psbt.rsbitcoin/tests/psbt-sign-taproot.rsInspect captured patch +20 / −19
diff --git a/bitcoin/examples/sign-tx-taproot.rs b/bitcoin/examples/sign-tx-taproot.rs
index 5eb28b7e..de473c75 100644
--- a/bitcoin/examples/sign-tx-taproot.rs
+++ b/bitcoin/examples/sign-tx-taproot.rs
@@ -19,7 +19,7 @@ const CHANGE_AMOUNT: Amount = Amount::from_sat_u32(14_999_000); // 1000 sat fee.
fn main() {
// Get a keypair we control. In a real application these would come from a stored secret.
let keypair = senders_keys();
- let (internal_key, _parity) = keypair.to_x_only_public_key();
+ let internal_key = keypair.to_x_only_public_key();
// Get an unspent output that is locked to the key above that we control.
// In a real application these would come from the chain.
diff --git a/bitcoin/examples/taproot-psbt.rs b/bitcoin/examples/taproot-psbt.rs
index 40dbbf14..7b9656dc 100644
--- a/bitcoin/examples/taproot-psbt.rs
+++ b/bitcoin/examples/taproot-psbt.rs
@@ -399,7 +399,7 @@ impl BenefactorWallet {
let taproot_spend_info = TaprootBuilder::new()
.add_leaf(0, script.clone())?
- .finalize(internal_keypair.to_x_only_public_key().0)
+ .finalize(internal_keypair.to_x_only_public_key())
.expect("should be finalizable");
self.current_spend_info = Some(taproot_spend_info.clone());
let script_pubkey = ScriptPubKeyBuf::new_p2tr(
@@ -435,7 +435,7 @@ impl BenefactorWallet {
(vec![leaf_hash], (self.beneficiary_xpub.fingerprint(), derivation_path.clone())),
);
origins.insert(
- internal_keypair.to_x_only_public_key().0,
+ internal_keypair.to_x_only_public_key(),
(vec![], (self.master_xpriv.fingerprint(), derivation_path)),
);
let ty = "SIGHASH_ALL".parse::<PsbtSighashType>()?;
@@ -450,7 +450,7 @@ impl BenefactorWallet {
tap_key_origins: origins,
tap_merkle_root: taproot_spend_info.merkle_root(),
sighash_type: Some(ty),
- tap_internal_key: Some(internal_keypair.to_x_only_public_key().0),
+ tap_internal_key: Some(internal_keypair.to_x_only_public_key()),
tap_scripts,
..Default::default()
};
@@ -494,7 +494,7 @@ impl BenefactorWallet {
let taproot_spend_info = TaprootBuilder::new()
.add_leaf(0, script.clone())?
- .finalize(new_internal_keypair.to_x_only_public_key().0)
+ .finalize(new_internal_keypair.to_x_only_public_key())
.expect("should be finalizable");
self.current_spend_info = Some(taproot_spend_info.clone());
let prevout_script_pubkey = input.witness_utxo.as_ref().unwrap().script_pubkey.clone();
@@ -599,7 +599,7 @@ impl BenefactorWallet {
tap_key_origins: origins,
tap_merkle_root: taproot_spend_info.merkle_root(),
sighash_type: Some(ty),
- tap_internal_key: Some(new_internal_keypair.to_x_only_public_key().0),
+ tap_internal_key: Some(new_internal_keypair.to_x_only_public_key()),
tap_scripts,
..Default::default()
};
diff --git a/bitcoin/src/crypto/key.rs b/bitcoin/src/crypto/key.rs
index 194ec2b6..4512342f 100644
--- a/bitcoin/src/crypto/key.rs
+++ b/bitcoin/src/crypto/key.rs
@@ -118,7 +118,7 @@ mod encapsulate {
/// Returns the [`TweakedPublicKey`] for `keypair`.
#[inline]
pub fn from_keypair(keypair: TweakedKeypair) -> Self {
- let (xonly, _parity) = keypair.to_keypair().to_x_only_public_key();
+ let xonly = keypair.to_keypair().to_x_only_public_key();
Self(xonly)
}
@@ -207,11 +207,11 @@ mod encapsulate {
impl XOnlyPublicKey {
/// Constructs an x-only public key from a keypair.
///
- /// Returns the x-only public key and the parity of the full public key.
+ /// Returns the x-only public key, with the relevant parity set from the full public key.
#[inline]
- pub fn from_keypair(keypair: &Keypair) -> (Self, Parity) {
+ pub fn from_keypair(keypair: &Keypair) -> Self {
let (xonly, parity) = secp256k1::XOnlyPublicKey::from_keypair(&keypair.to_inner());
- (Self::from_secp(xonly), parity)
+ Self::from_secp(xonly).with_parity(parity)
}
/// Constructs an x-only public key from a 32-byte x-coordinate.
@@ -375,7 +375,7 @@ impl Keypair {
///
/// This is equivalent to using [`XOnlyPublicKey::from_keypair`].
#[inline]
- pub fn to_x_only_public_key(self) -> (XOnlyPublicKey, Parity) {
+ pub fn to_x_only_public_key(self) -> XOnlyPublicKey {
XOnlyPublicKey::from_keypair(&self)
}
}
@@ -1182,7 +1182,7 @@ impl TapTweak for UntweakedKeypair {
///
/// The tweaked keypair.
fn tap_tweak(self, merkle_root: Option<TapNodeHash>) -> TweakedKeypair {
- let (pubkey, _parity) = XOnlyPublicKey::from_keypair(&self);
+ let pubkey = XOnlyPublicKey::from_keypair(&self);
let tweak = TapTweakHash::from_key_and_merkle_root(pubkey, merkle_root).to_scalar();
let tweaked = self.to_inner().add_xonly_tweak(&tweak).expect("Tap tweak failed");
TweakedKeypair::dangerous_assume_tweaked(Self::from(tweaked))
@@ -1217,8 +1217,8 @@ impl TweakedKeypair {
/// Returns the [`TweakedPublicKey`] and its [`Parity`] for this [`TweakedKeypair`].
#[inline]
pub fn public_parts(&self) -> (TweakedPublicKey, Parity) {
- let (xonly, parity) = self.as_keypair().to_x_only_public_key();
- (TweakedPublicKey::dangerous_assume_tweaked(xonly), parity)
+ let xonly = self.as_keypair().to_x_only_public_key();
+ (TweakedPublicKey::dangerous_assume_tweaked(xonly), xonly.parity())
}
}
diff --git a/bitcoin/src/crypto/sighash.rs b/bitcoin/src/crypto/sighash.rs
index da583184..9e1f22ba 100644
--- a/bitcoin/src/crypto/sighash.rs
+++ b/bitcoin/src/crypto/sighash.rs
@@ -1994,7 +1994,7 @@ mod tests {
// tests
let keypair = Keypair::from_secret_key(&internal_priv_key);
- let (internal_key, _parity) = XOnlyPublicKey::from_keypair(&keypair);
+ let internal_key = XOnlyPublicKey::from_keypair(&keypair);
let tweaked_keypair = keypair.tap_tweak(merkle_root);
let mut sig_msg = Vec::new();
cache
@@ -2017,7 +2017,8 @@ mod tests {
&[0u8; 32],
);
- assert_eq!(expected.internal_pubkey, internal_key);
+ // Only compare the inner key, not the parity
+ assert_eq!(expected.internal_pubkey.to_inner(), internal_key.to_inner());
assert_eq!(expected.sig_msg, sig_msg.to_lower_hex_string());
assert_eq!(expected.sig_hash, sighash);
assert_eq!(expected_hash_ty, hash_ty);
diff --git a/bitcoin/tests/psbt-sign-taproot.rs b/bitcoin/tests/psbt-sign-taproot.rs
index 69490627..1e409e97 100644
--- a/bitcoin/tests/psbt-sign-taproot.rs
+++ b/bitcoin/tests/psbt-sign-taproot.rs
@@ -57,7 +57,7 @@ fn psbt_sign_taproot() {
// Just use one of the secret keys for the key path spend.
let kp = sk_path[2].0.parse::<Keypair>().expect("failed to create keypair");
- let internal_key = kp.to_x_only_public_key().0; // Ignore the parity.
+ let internal_key = kp.to_x_only_public_key(); // Ignore the parity.
let tree = create_taproot_tree(script1, script2.clone(), script3, internal_key);
@@ -109,7 +109,7 @@ fn psbt_sign_taproot() {
{
// use private key of path "m/86'/1'/0'/0/1" as signing key
let kp = sk_path[1].0.parse::<Keypair>().expect("failed to create keypair");
- let x_only_pubkey = kp.to_x_only_public_key().0;
+ let x_only_pubkey = kp.to_x_only_public_key().with_parity(secp256k1::Parity::Even);
let signing_key_path = sk_path[1].1;
let keystore = Keystore {
@@ -162,7 +162,7 @@ fn psbt_sign_taproot() {
fn create_basic_single_sig_script(sk: &str) -> TapScriptBuf {
let kp = sk.parse::<Keypair>().expect("failed to create keypair");
- let x_only_pubkey = kp.to_x_only_public_key().0;
+ let x_only_pubkey = kp.to_x_only_public_key();
script::Builder::new()
.push_slice(x_only_pubkey.serialize().0)
.push_opcode(OP_CHECKSIG)
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.