refactor(bip32): Xpub child tweak api
What changed, and why it matters
This commit is a straightforward API refactor in the BIP-32 (Bitcoin key derivation) code. It replaces a function that returned a raw secret key and chain code with one that returns a new structured type called XpubChildTweak. The actual cryptographic math and behavior remain the same; only the way callers receive the result has changed. There is no indication this fixes a security bug.
No security action required. Treat as a normal API refactor; downstream users should update call sites from ckd_pub_tweak to derive_child_tweak and adapt to the XpubChildTweak return type.
Security signals we found
No security-relevant behavioral change observed
API rename and return-type encapsulation only
No new validation, bounds checking, or constant-time guarantees added
No advisory, CVE, or vendor security disclosure referenced in commit
Evidence from the diff
The patch renames Xpub::ckd_pub_tweak to Xpub::derive_child_tweak and wraps its former (secp256k1::SecretKey, ChainCode) return value in a new public struct XpubChildTweak containing a secp256k1::Scalar and ChainCode. The scalar is produced via .into() on the previously returned secret key. derive_child is updated to consume the new struct. Tests are adjusted to assert the new API. No cryptographic constants, validation logic, or error handling semantics are changed.
Changed components
key_expression/src/bip32.rsXpub::derive_childXpub::derive_child_tweak (formerly ckd_pub_tweak)XpubChildTweak structInspect captured patch +21 / −7
diff --git a/key_expression/src/bip32.rs b/key_expression/src/bip32.rs
index c3837fdb..19662785 100644
--- a/key_expression/src/bip32.rs
+++ b/key_expression/src/bip32.rs
@@ -209,6 +209,15 @@ pub struct Xpub {
#[cfg(feature = "serde")]
internals::serde_string_impl!(Xpub, "a BIP-0032 extended public key");
+/// Tweak data for deriving a BIP-0032 child xpub.
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+pub struct XpubChildTweak {
+ /// The scalar added to the parent public key to derive the child public key.
+ pub tweak: secp256k1::Scalar,
+ /// The derived child chain code.
+ pub chain_code: ChainCode,
+}
+
/// Flag with the hardened bit turned on.
const HARDENED_FLAG: u32 = 1 << 31;
@@ -912,10 +921,11 @@ impl Xpub {
/// derivation depth.
#[allow(clippy::missing_panics_doc)]
pub fn derive_child(&self, child_number: ChildNumber) -> Result<Self, DeriveXpubError> {
- let (sk, chain_code) =
- self.ckd_pub_tweak(child_number).map_err(DeriveXpubError::CannotDeriveHardenedChild)?;
+ let tweak = self
+ .derive_child_tweak(child_number)
+ .map_err(DeriveXpubError::CannotDeriveHardenedChild)?;
let tweaked =
- self.public_key.add_exp_tweak(&sk.into()).expect("cryptographically unreachable");
+ self.public_key.add_exp_tweak(&tweak.tweak).expect("cryptographically unreachable");
Ok(Self {
network: self.network,
@@ -926,7 +936,7 @@ impl Xpub {
parent_fingerprint: self.fingerprint(),
child_number,
public_key: tweaked,
- chain_code,
+ chain_code: tweak.chain_code,
})
}
@@ -953,10 +963,10 @@ impl Xpub {
///
/// Returns an error if the given [`ChildNumber`] is hardened.
#[allow(clippy::missing_panics_doc)]
- pub fn ckd_pub_tweak(
+ pub fn derive_child_tweak(
&self,
i: ChildNumber,
- ) -> Result<(secp256k1::SecretKey, ChainCode), CannotDeriveHardenedChildError> {
+ ) -> Result<XpubChildTweak, CannotDeriveHardenedChildError> {
if i.is_hardened() {
return Err(CannotDeriveHardenedChildError {});
}
@@ -971,7 +981,7 @@ impl Xpub {
)
.expect("cryptographically unreachable");
let chain_code = ChainCode::from_hmac(hmac);
- Ok((private_key, chain_code))
+ Ok(XpubChildTweak { tweak: private_key.into(), chain_code })
}
/// Decodes an extended public key from binary data according to BIP-0032.
@@ -1954,10 +1964,14 @@ mod tests {
for &num in &path.0 {
sk = sk.derive_child(num).unwrap();
if num.is_normal() {
+ let tweak = pk.derive_child_tweak(num).unwrap();
+ assert_eq!(tweak.chain_code, Xpub::from_xpriv(&sk).chain_code);
+
let pk2 = pk.derive_child(num).unwrap();
pk = Xpub::from_xpriv(&sk);
assert_eq!(pk, pk2);
} else {
+ assert_eq!(pk.derive_child_tweak(num), Err(CannotDeriveHardenedChildError {}));
assert_eq!(
pk.derive_child(num),
Err(DeriveXpubError::CannotDeriveHardenedChild(
Why this scored 17/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.