Replace to_inner with as_inner on Keypair
What changed, and why it matters
This commit changes how the library exposes a Bitcoin secret key pair internally. Previously, calling `to_inner()` made a full copy of the secret key data. The new `as_inner()` returns a reference instead, so the secret bytes are not duplicated in memory as often. This is a defensive hardening change: it reduces the number of copies of sensitive key material floating around in memory, which slightly lowers the chance that a secret key could leak through memory dumps, swap, or debugging. It is not a fix for an active bug or a directly exploitable vulnerability.
Treat as a routine defensive-hardening/API-cleanup commit. Review downstream code that relied on `Keypair::to_inner()` because it has been removed. No urgent security response is warranted unless independent analysis shows the extra copies were exploitable in a specific deployment.
Security signals we found
Secret-key material handling changed from owned copy to borrowed reference
API surface change on a cryptographic Keypair type
No explicit bug, overflow, or authentication bypass in the diff
No incident or CVE referenced in commit message or diff
Evidence from the diff
The patch replaces Keypair::to_inner(self) -> secp256k1::Keypair with Keypair::as_inner(&self) -> &secp256k1::Keypair. Because secp256k1::Keypair implements Copy, to_inner() implicitly copied the secret key bytes every time it was called. as_inner() borrows the inner value, avoiding those copies. Call sites in examples, tests, and PSBT signing code are updated to pass a reference to secp256k1 signing functions. The change is API-breaking (removes to_inner, adds as_inner) and is best understood as a memory-hygiene improvement for secret key handling rather than a patch for a concrete exploit.
Changed components
bitcoin/src/crypto/key.rsbitcoin/src/crypto/sighash.rsbitcoin/src/psbt/mod.rsbitcoin/examples/sign-tx-taproot.rsbitcoin/examples/taproot-psbt.rsInspect captured patch +16 / −17
diff --git a/bitcoin/examples/sign-tx-taproot.rs b/bitcoin/examples/sign-tx-taproot.rs
index de473c75..fc80ab86 100644
--- a/bitcoin/examples/sign-tx-taproot.rs
+++ b/bitcoin/examples/sign-tx-taproot.rs
@@ -68,7 +68,7 @@ fn main() {
// Sign the sighash using the secp256k1 library (exported by rust-bitcoin).
let tweaked: TweakedKeypair = keypair.tap_tweak(None);
let signature =
- secp256k1::schnorr::sign(&sighash.to_byte_array(), &tweaked.as_keypair().to_inner());
+ secp256k1::schnorr::sign(&sighash.to_byte_array(), tweaked.as_keypair().as_inner());
// Update the witness stack.
let signature = bitcoin::taproot::Signature { signature, sighash_type };
diff --git a/bitcoin/examples/taproot-psbt.rs b/bitcoin/examples/taproot-psbt.rs
index ceb72475..5da8702f 100644
--- a/bitcoin/examples/taproot-psbt.rs
+++ b/bitcoin/examples/taproot-psbt.rs
@@ -755,7 +755,7 @@ fn sign_psbt_taproot(
Some(_) => keypair, // no tweak for script spend
};
- let signature = secp256k1::schnorr::sign(&hash.to_byte_array(), &keypair.to_inner());
+ let signature = secp256k1::schnorr::sign(&hash.to_byte_array(), keypair.as_inner());
let final_signature = taproot::Signature { signature, sighash_type };
diff --git a/bitcoin/src/crypto/key.rs b/bitcoin/src/crypto/key.rs
index b5b4265f..785d5ba1 100644
--- a/bitcoin/src/crypto/key.rs
+++ b/bitcoin/src/crypto/key.rs
@@ -91,9 +91,9 @@ mod encapsulate {
#[inline]
pub fn from_secp(keypair: impl Into<secp256k1::Keypair>) -> Self { Self(keypair.into()) }
- /// Returns the inner [`secp256k1::Keypair`].
+ /// Returns a reference to the inner [`secp256k1::Keypair`].
#[inline]
- pub fn to_inner(self) -> secp256k1::Keypair { self.0 }
+ pub fn as_inner(&self) -> &secp256k1::Keypair { &self.0 }
}
/// A Bitcoin ECDSA public key.
@@ -281,7 +281,7 @@ impl XOnlyPublicKey {
/// Returns the x-only public key, with the relevant parity set from the full public key.
#[inline]
pub fn from_keypair(keypair: &Keypair) -> Self {
- let (xonly, parity) = secp256k1::XOnlyPublicKey::from_keypair(&keypair.to_inner());
+ let (xonly, parity) = secp256k1::XOnlyPublicKey::from_keypair(keypair.as_inner());
Self::from_secp(xonly, parity)
}
@@ -426,13 +426,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 {
- secp256k1::SecretKey::from_keypair(&self.to_inner())
+ 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] {
- self.to_inner().to_secret_bytes()
+ self.as_inner().to_secret_bytes()
}
/// Returns the [`PublicKey`] for this [`Keypair`].
@@ -656,7 +656,7 @@ impl PublicKey {
/// Extracts the public key from a Keypair
pub fn from_keypair(pair: &Keypair) -> Self {
- Self::from_secp(secp256k1::PublicKey::from_keypair(&pair.to_inner()))
+ Self::from_secp(secp256k1::PublicKey::from_keypair(pair.as_inner()))
}
/// Checks that `sig` is a valid ECDSA signature for `msg` using this public key.
@@ -1314,7 +1314,7 @@ impl TapTweak for UntweakedKeypair {
fn tap_tweak(self, merkle_root: Option<TapNodeHash>) -> TweakedKeypair {
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");
+ let tweaked = self.as_inner().add_xonly_tweak(&tweak).expect("Tap tweak failed");
TweakedKeypair::dangerous_assume_tweaked(Self::from(tweaked))
}
@@ -2163,7 +2163,7 @@ mod tests {
};
// Use secp256k1::DisplaySecret, since no key type implements Display
- let encoded = format!("{}", keypair.to_inner().display_secret());
+ let encoded = format!("{}", keypair.as_inner().display_secret());
let decoded = encoded.parse::<Keypair>().unwrap();
assert_eq!(decoded, keypair);
}
diff --git a/bitcoin/src/crypto/sighash.rs b/bitcoin/src/crypto/sighash.rs
index 886129cb..4e57e6ec 100644
--- a/bitcoin/src/crypto/sighash.rs
+++ b/bitcoin/src/crypto/sighash.rs
@@ -2028,7 +2028,7 @@ mod tests {
let key_spend_sig = secp256k1::schnorr::sign_with_aux_rand(
&sighash.to_byte_array(),
- &tweaked_keypair.to_keypair().to_inner(),
+ tweaked_keypair.to_keypair().as_inner(),
&[0u8; 32],
);
@@ -2039,8 +2039,7 @@ mod tests {
assert_eq!(expected_hash_ty, hash_ty);
assert_eq!(expected_key_spend_sig, key_spend_sig);
- let tweaked_priv_key =
- SecretKey::from_keypair(&tweaked_keypair.to_keypair().to_inner());
+ let tweaked_priv_key = tweaked_keypair.to_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 7465d3e7..f32a609c 100644
--- a/bitcoin/src/psbt/mod.rs
+++ b/bitcoin/src/psbt/mod.rs
@@ -433,11 +433,11 @@ impl Psbt {
#[cfg(all(feature = "rand", feature = "std"))]
let signature =
- secp256k1::schnorr::sign(&sighash.to_byte_array(), &key_pair.to_inner());
+ secp256k1::schnorr::sign(&sighash.to_byte_array(), key_pair.as_inner());
#[cfg(not(all(feature = "rand", feature = "std")))]
let signature = secp256k1::schnorr::sign_no_aux_rand(
&sighash.to_byte_array(),
- &key_pair.to_inner(),
+ key_pair.as_inner(),
);
let signature = taproot::Signature { signature, sighash_type };
@@ -465,12 +465,12 @@ impl Psbt {
#[cfg(all(feature = "rand", feature = "std"))]
let signature = secp256k1::schnorr::sign(
&sighash.to_byte_array(),
- &key_pair.to_inner(),
+ key_pair.as_inner(),
);
#[cfg(not(all(feature = "rand", feature = "std")))]
let signature = secp256k1::schnorr::sign_no_aux_rand(
&sighash.to_byte_array(),
- &key_pair.to_inner(),
+ key_pair.as_inner(),
);
let signature = taproot::Signature { signature, sighash_type };
Why this scored 28/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.