Remove Copy from TweakedKeypair and Keypair
What changed, and why it matters
This commit removes the automatic 'Copy' trait from two Bitcoin key types that hold secret data (Keypair and TweakedKeypair). In Rust, 'Copy' makes it easy for the compiler to silently duplicate values, which can increase the chances of secret key material being copied around in memory unexpectedly. The change also renames a method from 'to_keypair' to 'into_keypair' and adjusts related code. It is a defensive hardening measure, not a fix for a known active exploit.
Treat as a defensive hardening improvement. Review downstream code that relied on implicit Copy of Keypair/TweakedKeypair, as it will now require explicit .clone() or ownership handling. Consider pairing this change with explicit zeroization or secure-memory practices if not already in place, since removing Copy alone does not prevent all secret duplication.
Security signals we found
Removal of Copy trait from secret-bearing types (defensive secret-handling hardening)
API rename from to_keypair to into_keypair to reflect consuming conversion semantics
Methods changed from self to &self to avoid unnecessary moves of secret-bearing values
No direct memory-zeroization or unsafe code changes present in diff
Evidence from the diff
The patch removes #[derive(Copy)] from Keypair and TweakedKeypair in rust-bitcoin. Because these types wrap secp256k1::Keypair (secret key + public key), allowing implicit Copy increases the risk of unintended duplication of secret material in stack or register copies. Removing Copy forces callers to use Clone explicitly, making secret duplication more visible. The patch also renames TweakedKeypair::to_keypair to into_keypair (and updates call sites) because a consuming conversion is more idiomatic once Copy is gone, and changes several Keypair methods (to_secret_key, to_secret_bytes, to_public_key, to_x_only_public_key) to take &self instead of self since Copy can no longer provide cheap ownership semantics. Tests and examples are updated to use .clone() or .into_keypair() where needed.
Changed components
bitcoin/src/crypto/key.rs (Keypair, TweakedKeypair definitions and methods)bitcoin/src/crypto/sighash.rs (test usage of tweaked keypair)bitcoin/src/psbt/mod.rs (PSBT taproot signing path)bitcoin/examples/taproot-psbt.rs (example taproot PSBT signing)Inspect captured patch +18 / −18
diff --git a/bitcoin/examples/taproot-psbt.rs b/bitcoin/examples/taproot-psbt.rs
index db57c423..54c2e7e7 100644
--- a/bitcoin/examples/taproot-psbt.rs
+++ b/bitcoin/examples/taproot-psbt.rs
@@ -751,7 +751,7 @@ fn sign_psbt_taproot(
) {
let keypair = Keypair::from_secret_key(secret_key);
let keypair = match leaf_hash {
- None => keypair.tap_tweak(psbt_input.tap_merkle_root).to_keypair(),
+ None => keypair.tap_tweak(psbt_input.tap_merkle_root).into_keypair(),
Some(_) => keypair, // no tweak for script spend
};
diff --git a/bitcoin/src/crypto/key.rs b/bitcoin/src/crypto/key.rs
index 7d88ff7f..0486e8eb 100644
--- a/bitcoin/src/crypto/key.rs
+++ b/bitcoin/src/crypto/key.rs
@@ -80,7 +80,7 @@ mod encapsulate {
}
/// A Bitcoin secret and public key pair.
- #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+ #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Keypair(secp256k1::Keypair);
@@ -178,7 +178,7 @@ mod encapsulate {
/// Returns the [`TweakedPublicKey`] for `keypair`.
#[inline]
pub fn from_keypair(keypair: TweakedKeypair) -> Self {
- Self(keypair.to_keypair().to_x_only_public_key())
+ Self(keypair.as_keypair().to_x_only_public_key())
}
/// Constructs a new [`TweakedPublicKey`] from a [`XOnlyPublicKey`]. No tweak is applied, consider
@@ -210,11 +210,11 @@ mod encapsulate {
/// # let keypair = TweakedKeypair::dangerous_assume_tweaked(Keypair::generate());
/// // There are various conversion methods available to get a tweaked pubkey from a tweaked keypair.
/// let (_pk, _parity) = keypair.public_parts();
- /// let _pk = TweakedPublicKey::from_keypair(keypair);
- /// let _pk = TweakedPublicKey::from(keypair);
+ /// let _pk = TweakedPublicKey::from_keypair(keypair.clone());
+ /// let _pk = TweakedPublicKey::from(keypair.clone());
/// # }
/// ```
- #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
+ #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub struct TweakedKeypair(Keypair);
@@ -230,7 +230,7 @@ mod encapsulate {
/// Returns the underlying key pair.
#[inline]
- pub fn to_keypair(self) -> Keypair { self.0 }
+ pub fn into_keypair(self) -> Keypair { self.0 }
/// Returns a reference to the underlying key pair.
#[inline]
@@ -410,13 +410,13 @@ impl Keypair {
///
/// This is equivalent to using [`secp256k1::SecretKey::from_keypair`] on the inner value.
#[inline]
- pub fn to_secret_key(self) -> secp256k1::SecretKey {
+ pub fn to_secret_key(&self) -> secp256k1::SecretKey {
secp256k1::SecretKey::from_keypair(self.as_inner())
}
/// Returns the secret bytes for this [`Keypair`].
#[inline]
- pub fn to_secret_bytes(self) -> [u8; constants::SECRET_KEY_SIZE] {
+ pub fn to_secret_bytes(&self) -> [u8; constants::SECRET_KEY_SIZE] {
self.as_inner().to_secret_bytes()
}
@@ -424,13 +424,13 @@ impl Keypair {
///
/// This is equivalent to using [`PublicKey::from_keypair`].
#[inline]
- pub fn to_public_key(self) -> PublicKey { PublicKey::from_keypair(&self) }
+ pub fn to_public_key(&self) -> PublicKey { PublicKey::from_keypair(self) }
/// Returns the [`XOnlyPublicKey`] for this [`Keypair`].
///
/// This is equivalent to using [`XOnlyPublicKey::from_keypair`].
#[inline]
- pub fn to_x_only_public_key(self) -> XOnlyPublicKey { XOnlyPublicKey::from_keypair(&self) }
+ pub fn to_x_only_public_key(&self) -> XOnlyPublicKey { XOnlyPublicKey::from_keypair(self) }
/// Schnorr sign a message slice with this keypair.
///
@@ -1458,8 +1458,8 @@ impl TweakedKeypair {
/// Returns the underlying key pair.
#[inline]
#[doc(hidden)]
- #[deprecated(since = "0.32.6", note = "use to_keypair() instead")]
- pub fn to_inner(self) -> Keypair { self.to_keypair() }
+ #[deprecated(since = "0.32.6", note = "use into_keypair() instead")]
+ pub fn to_inner(self) -> Keypair { self.into_keypair() }
/// Returns the [`TweakedPublicKey`] and its [`Parity`] for this [`TweakedKeypair`].
#[inline]
@@ -1476,7 +1476,7 @@ impl From<TweakedPublicKey> for XOnlyPublicKey {
impl From<TweakedKeypair> for Keypair {
#[inline]
- fn from(pair: TweakedKeypair) -> Self { pair.to_keypair() }
+ fn from(pair: TweakedKeypair) -> Self { pair.into_keypair() }
}
impl From<TweakedKeypair> for TweakedPublicKey {
@@ -2166,7 +2166,7 @@ mod tests {
fn public_key_constructors() {
let kp = Keypair::generate();
- let _ = PublicKey::from_secp(kp);
+ let _ = PublicKey::from_secp(kp.clone());
let _ = PublicKey::from_secp_uncompressed(kp);
}
diff --git a/bitcoin/src/crypto/sighash.rs b/bitcoin/src/crypto/sighash.rs
index 050ac20e..0c3ed66e 100644
--- a/bitcoin/src/crypto/sighash.rs
+++ b/bitcoin/src/crypto/sighash.rs
@@ -2031,8 +2031,8 @@ mod tests {
.taproot_signature_hash(tx_ind, &Prevouts::All(&utxos), None, None, hash_ty)
.unwrap();
+ let tweaked_keypair = tweaked_keypair.into_keypair();
let key_spend_sig = tweaked_keypair
- .to_keypair()
.raw_bip340_sign_with_aux_randomness(&sighash.to_byte_array(), &[0u8; 32]);
// Only compare the inner key, not the parity
@@ -2042,7 +2042,7 @@ mod tests {
assert_eq!(expected_hash_ty, hash_ty);
assert_eq!(expected_key_spend_sig, key_spend_sig);
- let tweaked_priv_key = tweaked_keypair.to_keypair().to_secret_key();
+ let tweaked_priv_key = tweaked_keypair.to_secret_key();
assert_eq!(expected.tweaked_privkey, tweaked_priv_key);
}
}
diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs
index 2165fde8..1d200bc5 100644
--- a/bitcoin/src/psbt/mod.rs
+++ b/bitcoin/src/psbt/mod.rs
@@ -431,7 +431,7 @@ impl Psbt {
let (sighash, sighash_type) = self.sighash_taproot(input_index, cache, None)?;
let key_pair = Keypair::from_secret_key(sk.as_inner())
.tap_tweak(input.tap_merkle_root)
- .to_keypair();
+ .into_keypair();
let signature = key_pair.raw_bip340_sign(&sighash.to_byte_array());
Why this scored 37/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.