bip32: split out DerivationError from the main error enum

This is a breaking change to the API of the deprecated `derive_pub`
function, but it's in a way that's unlikely to break consumers (who are
probably just .expect'ing the result) so we will retain the deprecated
function.
This commit is contained in:
Andrew Poelstra 2025-04-21 22:25:26 +00:00
parent 32d96f6c33
commit f0a237c001
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
1 changed files with 41 additions and 13 deletions

View File

@ -497,8 +497,6 @@ pub type KeySource = (Fingerprint, DerivationPath);
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive] #[non_exhaustive]
pub enum Error { pub enum Error {
/// A pk->pk derivation was attempted on a hardened key
CannotDeriveFromHardenedKey,
/// A secp256k1 error occurred /// A secp256k1 error occurred
Secp256k1(secp256k1::Error), Secp256k1(secp256k1::Error),
/// A child number was provided that was out of range /// A child number was provided that was out of range
@ -536,8 +534,6 @@ impl fmt::Display for Error {
use Error::*; use Error::*;
match *self { match *self {
CannotDeriveFromHardenedKey =>
f.write_str("cannot derive hardened key from public key"),
Secp256k1(ref e) => write_err!(f, "secp256k1 error"; e), Secp256k1(ref e) => write_err!(f, "secp256k1 error"; e),
InvalidChildNumber(ref n) => InvalidChildNumber(ref n) =>
write!(f, "child number {} is invalid (not within [0, 2^31 - 1])", n), write!(f, "child number {} is invalid (not within [0, 2^31 - 1])", n),
@ -570,8 +566,7 @@ impl std::error::Error for Error {
Base58(ref e) => Some(e), Base58(ref e) => Some(e),
Hex(ref e) => Some(e), Hex(ref e) => Some(e),
InvalidBase58PayloadLength(ref e) => Some(e), InvalidBase58PayloadLength(ref e) => Some(e),
CannotDeriveFromHardenedKey InvalidChildNumber(_)
| InvalidChildNumber(_)
| InvalidChildNumberFormat | InvalidChildNumberFormat
| InvalidDerivationPathFormat | InvalidDerivationPathFormat
| UnknownVersion(_) | UnknownVersion(_)
@ -596,6 +591,33 @@ impl From<InvalidBase58PayloadLengthError> for Error {
fn from(e: InvalidBase58PayloadLengthError) -> Error { Self::InvalidBase58PayloadLength(e) } fn from(e: InvalidBase58PayloadLengthError) -> Error { Self::InvalidBase58PayloadLength(e) }
} }
/// A BIP32 error
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum DerivationError {
/// Attempted to derive a hardened child from an xpub.
///
/// You can only derive hardened children from xprivs.
CannotDeriveHardenedChild,
/// Attempted to derive a child of depth 256 or higher.
///
/// There is no way to encode such xkeys.
MaximumDepthExceeded,
}
#[cfg(feature = "std")]
impl std::error::Error for DerivationError {}
impl fmt::Display for DerivationError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Self::CannotDeriveHardenedChild =>
f.write_str("cannot derive hardened child of public key"),
Self::MaximumDepthExceeded => f.write_str("cannot derive child of depth 256 or higher"),
}
}
}
impl Xpriv { impl Xpriv {
/// Constructs a new master key from a seed value /// Constructs a new master key from a seed value
pub fn new_master(network: impl Into<NetworkKind>, seed: &[u8]) -> Xpriv { pub fn new_master(network: impl Into<NetworkKind>, seed: &[u8]) -> Xpriv {
@ -795,7 +817,7 @@ impl Xpub {
&self, &self,
secp: &Secp256k1<C>, secp: &Secp256k1<C>,
path: &P, path: &P,
) -> Result<Xpub, Error> { ) -> Result<Xpub, DerivationError> {
self.derive_xpub(secp, path) self.derive_xpub(secp, path)
} }
@ -806,7 +828,7 @@ impl Xpub {
&self, &self,
secp: &Secp256k1<C>, secp: &Secp256k1<C>,
path: &P, path: &P,
) -> Result<Xpub, Error> { ) -> Result<Xpub, DerivationError> {
let mut pk: Xpub = *self; let mut pk: Xpub = *self;
for cnum in path.as_ref() { for cnum in path.as_ref() {
pk = pk.ckd_pub(secp, *cnum)? pk = pk.ckd_pub(secp, *cnum)?
@ -818,9 +840,9 @@ impl Xpub {
pub fn ckd_pub_tweak( pub fn ckd_pub_tweak(
&self, &self,
i: ChildNumber, i: ChildNumber,
) -> Result<(secp256k1::SecretKey, ChainCode), Error> { ) -> Result<(secp256k1::SecretKey, ChainCode), DerivationError> {
match i { match i {
ChildNumber::Hardened { .. } => Err(Error::CannotDeriveFromHardenedKey), ChildNumber::Hardened { .. } => Err(DerivationError::CannotDeriveHardenedChild),
ChildNumber::Normal { index: n } => { ChildNumber::Normal { index: n } => {
let mut engine = HmacEngine::<sha512::HashEngine>::new(&self.chain_code[..]); let mut engine = HmacEngine::<sha512::HashEngine>::new(&self.chain_code[..]);
engine.input(&self.public_key.serialize()[..]); engine.input(&self.public_key.serialize()[..]);
@ -842,7 +864,7 @@ impl Xpub {
&self, &self,
secp: &Secp256k1<C>, secp: &Secp256k1<C>,
i: ChildNumber, i: ChildNumber,
) -> Result<Xpub, Error> { ) -> Result<Xpub, DerivationError> {
let (sk, chain_code) = self.ckd_pub_tweak(i)?; let (sk, chain_code) = self.ckd_pub_tweak(i)?;
let tweaked = let tweaked =
self.public_key.add_exp_tweak(secp, &sk.into()).expect("cryptographically unreachable"); self.public_key.add_exp_tweak(secp, &sk.into()).expect("cryptographically unreachable");
@ -1120,7 +1142,10 @@ mod tests {
// Check derivation convenience method for Xpub, should error // Check derivation convenience method for Xpub, should error
// appropriately if any ChildNumber is hardened // appropriately if any ChildNumber is hardened
if path.0.iter().any(|cnum| cnum.is_hardened()) { if path.0.iter().any(|cnum| cnum.is_hardened()) {
assert_eq!(pk.derive_xpub(secp, &path), Err(Error::CannotDeriveFromHardenedKey)); assert_eq!(
pk.derive_xpub(secp, &path),
Err(DerivationError::CannotDeriveHardenedChild)
);
} else { } else {
assert_eq!(&pk.derive_xpub(secp, &path).unwrap().to_string()[..], expected_pk); assert_eq!(&pk.derive_xpub(secp, &path).unwrap().to_string()[..], expected_pk);
} }
@ -1135,7 +1160,10 @@ mod tests {
assert_eq!(pk, pk2); assert_eq!(pk, pk2);
} }
Hardened { .. } => { Hardened { .. } => {
assert_eq!(pk.ckd_pub(secp, num), Err(Error::CannotDeriveFromHardenedKey)); assert_eq!(
pk.ckd_pub(secp, num),
Err(DerivationError::CannotDeriveHardenedChild)
);
pk = Xpub::from_xpriv(secp, &sk); pk = Xpub::from_xpriv(secp, &sk);
} }
} }