Merge rust-bitcoin/rust-bitcoin#4387: bip32: overhaul error types and add a "maximum depth exceeded" error

b9a12043b0 bip32: return error when trying to derive a path too deep (Andrew Poelstra)
73781e047b bip32: rename Error to ParseError (Andrew Poelstra)
a66ad97fb6 bip32: split InvalidChildNumber and InvalidChildNumberFormat out of error (Andrew Poelstra)
a891fb9b74 bip32: remove unused error variants (Andrew Poelstra)
f0a237c001 bip32: split out DerivationError from the main error enum (Andrew Poelstra)
32d96f6c33 bip32: make Xpriv::new_master be infallible (Andrew Poelstra)
0e5e021b69 bip32: change several cryptographically unreachable paths to expects (Andrew Poelstra)

Pull request description:

  This PR makes a first pass at splitting the `bip32::Error` type into multiple distinct types -- one for derivation (which can fail if you try to derive a hardened child of an xpub, or if you try to derive too many layers), one for parsing child numbers or derivation paths, and one for parsing xkeys. Along the way it cleans up a ton of weird things and typos, e.g. the psbt `GetKeyError` having an unused `Bip32` variant whose display text references "bip 23".

  Because all the error types get renamed, every part of this PR is an API break, but only the last commit is a "real" API break which uses the new `DerivationError::MaximumDepthExceeded` error variant to return an error when trying to derive a path of length 256 or longer. This means that `Xpriv::derive_xpriv` again returns an error result.

  I will make a simpler version of this last commit suitable for backporting to 0.32.x. (In 0.32.x `Xpriv::derive_priv` returns an error, so we can change it to error out on max-depth-exceeded without breaking the API. Sadly most users are likely to be unwrapping the error because in 0.32.x currently the error path is cryptographically unreachable...but at least this way the panic will be in their control rather than ours.)

  Fixes https://github.com/rust-bitcoin/rust-bitcoin/issues/4308

ACKs for top commit:
  tcharding:
    ACK b9a12043b0

Tree-SHA512: 688826126ff24066c6de9de3caa73db68c516ba8893d64d9226a498774a2fb9db7d7fd797375c6b3f820588c178632e1e6e8561022dfa7042a560582bf1509b4
This commit is contained in:
merge-script 2025-04-30 13:12:01 +00:00
commit ee037042ae
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
8 changed files with 235 additions and 136 deletions

View File

@ -33,12 +33,12 @@ fn main() {
let secp = Secp256k1::preallocated_new(buf.as_mut_slice()).unwrap();
// calculate root key from seed
let root = Xpriv::new_master(NetworkKind::Main, &seed).unwrap();
let root = Xpriv::new_master(NetworkKind::Main, &seed);
println!("Root key: {}", root);
// derive child xpub
let path = "84h/0h/0h".parse::<DerivationPath>().unwrap();
let child = root.derive_xpriv(&secp, &path);
let child = root.derive_xpriv(&secp, &path).expect("only deriving three steps");
println!("Child at {}: {}", path, child);
let xpub = Xpub::from_xpriv(&secp, &child);
println!("Public key at {}: {}", path, xpub);

View File

@ -62,11 +62,12 @@ fn get_external_address_xpriv<C: Signing>(
) -> Xpriv {
let derivation_path =
BIP84_DERIVATION_PATH.into_derivation_path().expect("valid derivation path");
let child_xpriv = master_xpriv.derive_xpriv(secp, &derivation_path);
let child_xpriv =
master_xpriv.derive_xpriv(secp, &derivation_path).expect("only deriving three steps");
let external_index = ChildNumber::ZERO_NORMAL;
let idx = ChildNumber::from_normal_idx(index).expect("valid index number");
child_xpriv.derive_xpriv(secp, &[external_index, idx])
child_xpriv.derive_xpriv(secp, &[external_index, idx]).expect("only deriving two more steps")
}
// Derive the internal address xpriv.
@ -77,11 +78,12 @@ fn get_internal_address_xpriv<C: Signing>(
) -> Xpriv {
let derivation_path =
BIP84_DERIVATION_PATH.into_derivation_path().expect("valid derivation path");
let child_xpriv = master_xpriv.derive_xpriv(secp, &derivation_path);
let child_xpriv =
master_xpriv.derive_xpriv(secp, &derivation_path).expect("only deriving three steps");
let internal_index = ChildNumber::ONE_NORMAL;
let idx = ChildNumber::from_normal_idx(index).expect("valid index number");
child_xpriv.derive_xpriv(secp, &[internal_index, idx])
child_xpriv.derive_xpriv(secp, &[internal_index, idx]).expect("only deriving two more steps")
}
// The address to send to.

View File

@ -118,11 +118,12 @@ impl ColdStorage {
// Hardened children require secret data to derive.
let path = "84h/0h/0h".into_derivation_path()?;
let account_0_xpriv = master_xpriv.derive_xpriv(secp, &path);
let account_0_xpriv =
master_xpriv.derive_xpriv(secp, &path).expect("derivation path is short");
let account_0_xpub = Xpub::from_xpriv(secp, &account_0_xpriv);
let path = INPUT_UTXO_DERIVATION_PATH.into_derivation_path()?;
let input_xpriv = master_xpriv.derive_xpriv(secp, &path);
let input_xpriv = master_xpriv.derive_xpriv(secp, &path).expect("derivation path is short");
let input_xpub = Xpub::from_xpriv(secp, &input_xpriv);
let wallet = ColdStorage { master_xpriv, master_xpub };

View File

@ -60,11 +60,12 @@ fn get_external_address_xpriv<C: Signing>(
) -> Xpriv {
let derivation_path =
BIP86_DERIVATION_PATH.into_derivation_path().expect("valid derivation path");
let child_xpriv = master_xpriv.derive_xpriv(secp, &derivation_path);
let child_xpriv =
master_xpriv.derive_xpriv(secp, &derivation_path).expect("only deriving three steps");
let external_index = ChildNumber::ZERO_NORMAL;
let idx = ChildNumber::from_normal_idx(index).expect("valid index number");
child_xpriv.derive_xpriv(secp, &[external_index, idx])
child_xpriv.derive_xpriv(secp, &[external_index, idx]).expect("only deriving two more steps")
}
// Derive the internal address xpriv.
@ -75,11 +76,12 @@ fn get_internal_address_xpriv<C: Signing>(
) -> Xpriv {
let derivation_path =
BIP86_DERIVATION_PATH.into_derivation_path().expect("valid derivation path");
let child_xpriv = master_xpriv.derive_xpriv(secp, &derivation_path);
let child_xpriv =
master_xpriv.derive_xpriv(secp, &derivation_path).expect("only deriving three steps");
let internal_index = ChildNumber::ONE_NORMAL;
let idx = ChildNumber::from_normal_idx(index).expect("valid index number");
child_xpriv.derive_xpriv(secp, &[internal_index, idx])
child_xpriv.derive_xpriv(secp, &[internal_index, idx]).expect("only deriving two more steps")
}
// Get the Taproot Key Origin.

View File

@ -299,7 +299,7 @@ fn generate_bip86_key_spend_tx(
.ok_or("missing Taproot key origin")?;
let secret_key =
master_xpriv.derive_xpriv(secp, &derivation_path).to_private_key().inner;
master_xpriv.derive_xpriv(secp, &derivation_path)?.to_private_key().inner;
sign_psbt_taproot(
secret_key,
input.tap_internal_key.unwrap(),
@ -393,8 +393,11 @@ impl BenefactorWallet {
// We use some other derivation path in this example for our inheritance protocol. The important thing is to ensure
// that we use an unhardened path so we can make use of xpubs.
let derivation_path = format!("101/1/0/0/{}", self.next).parse::<DerivationPath>()?;
let internal_keypair =
self.master_xpriv.derive_xpriv(&self.secp, &derivation_path).to_keypair(&self.secp);
let internal_keypair = self
.master_xpriv
.derive_xpriv(&self.secp, &derivation_path)
.expect("derivation path is short")
.to_keypair(&self.secp);
let beneficiary_key =
self.beneficiary_xpub.derive_xpub(&self.secp, &derivation_path)?.to_x_only_public_key();
@ -486,6 +489,7 @@ impl BenefactorWallet {
let new_internal_keypair = self
.master_xpriv
.derive_xpriv(&self.secp, &new_derivation_path)
.expect("derivation path is short")
.to_keypair(&self.secp);
let beneficiary_key = self
.beneficiary_xpub
@ -538,6 +542,7 @@ impl BenefactorWallet {
let secret_key = self
.master_xpriv
.derive_xpriv(&self.secp, &derivation_path)
.expect("derivation path is short")
.to_private_key()
.inner;
sign_psbt_taproot(
@ -660,8 +665,11 @@ impl BeneficiaryWallet {
for (x_only_pubkey, (leaf_hashes, (_, derivation_path))) in
&psbt.inputs[0].tap_key_origins.clone()
{
let secret_key =
self.master_xpriv.derive_xpriv(&self.secp, &derivation_path).to_private_key().inner;
let secret_key = self
.master_xpriv
.derive_xpriv(&self.secp, &derivation_path)?
.to_private_key()
.inner;
for lh in leaf_hashes {
let sighash_type = TapSighashType::All;
let hash = SighashCache::new(&unsigned_tx).taproot_script_spend_signature_hash(

View File

@ -148,11 +148,11 @@ impl ChildNumber {
/// [0, 2^31 - 1].
///
/// [`Normal`]: #variant.Normal
pub fn from_normal_idx(index: u32) -> Result<Self, Error> {
pub fn from_normal_idx(index: u32) -> Result<Self, IndexOutOfRangeError> {
if index & (1 << 31) == 0 {
Ok(ChildNumber::Normal { index })
} else {
Err(Error::InvalidChildNumber(index))
Err(IndexOutOfRangeError { index })
}
}
@ -160,11 +160,11 @@ impl ChildNumber {
/// [0, 2^31 - 1].
///
/// [`Hardened`]: #variant.Hardened
pub fn from_hardened_idx(index: u32) -> Result<Self, Error> {
pub fn from_hardened_idx(index: u32) -> Result<Self, IndexOutOfRangeError> {
if index & (1 << 31) == 0 {
Ok(ChildNumber::Hardened { index })
} else {
Err(Error::InvalidChildNumber(index))
Err(IndexOutOfRangeError { index })
}
}
@ -184,7 +184,10 @@ impl ChildNumber {
}
/// Returns the child number that is a single increment from this one.
pub fn increment(self) -> Result<ChildNumber, Error> {
pub fn increment(self) -> Result<ChildNumber, IndexOutOfRangeError> {
// Bare addition in this function is okay, because we have an invariant that
// `index` is always within [0, 2^31 - 1]. FIXME this is not actually an
// invariant because the fields are public.
match self {
ChildNumber::Normal { index: idx } => ChildNumber::from_normal_idx(idx + 1),
ChildNumber::Hardened { index: idx } => ChildNumber::from_hardened_idx(idx + 1),
@ -225,16 +228,18 @@ impl fmt::Display for ChildNumber {
}
impl FromStr for ChildNumber {
type Err = Error;
type Err = ParseChildNumberError;
fn from_str(inp: &str) -> Result<ChildNumber, Error> {
fn from_str(inp: &str) -> Result<Self, Self::Err> {
let is_hardened = inp.chars().last().map_or(false, |l| l == '\'' || l == 'h');
Ok(if is_hardened {
ChildNumber::from_hardened_idx(
inp[0..inp.len() - 1].parse().map_err(|_| Error::InvalidChildNumberFormat)?,
)?
inp[0..inp.len() - 1].parse().map_err(ParseChildNumberError::ParseInt)?,
)
.map_err(ParseChildNumberError::IndexOutOfRange)?
} else {
ChildNumber::from_normal_idx(inp.parse().map_err(|_| Error::InvalidChildNumberFormat)?)?
ChildNumber::from_normal_idx(inp.parse().map_err(ParseChildNumberError::ParseInt)?)
.map_err(ParseChildNumberError::IndexOutOfRange)?
})
}
}
@ -267,7 +272,7 @@ impl serde::Serialize for ChildNumber {
/// derivation path
pub trait IntoDerivationPath {
/// Converts a given type into a [`DerivationPath`] with possible error
fn into_derivation_path(self) -> Result<DerivationPath, Error>;
fn into_derivation_path(self) -> Result<DerivationPath, ParseChildNumberError>;
}
/// A BIP-32 derivation path.
@ -295,15 +300,17 @@ impl<T> IntoDerivationPath for T
where
T: Into<DerivationPath>,
{
fn into_derivation_path(self) -> Result<DerivationPath, Error> { Ok(self.into()) }
fn into_derivation_path(self) -> Result<DerivationPath, ParseChildNumberError> {
Ok(self.into())
}
}
impl IntoDerivationPath for String {
fn into_derivation_path(self) -> Result<DerivationPath, Error> { self.parse() }
fn into_derivation_path(self) -> Result<DerivationPath, ParseChildNumberError> { self.parse() }
}
impl IntoDerivationPath for &'_ str {
fn into_derivation_path(self) -> Result<DerivationPath, Error> { self.parse() }
fn into_derivation_path(self) -> Result<DerivationPath, ParseChildNumberError> { self.parse() }
}
impl From<Vec<ChildNumber>> for DerivationPath {
@ -338,9 +345,9 @@ impl AsRef<[ChildNumber]> for DerivationPath {
}
impl FromStr for DerivationPath {
type Err = Error;
type Err = ParseChildNumberError;
fn from_str(path: &str) -> Result<DerivationPath, Error> {
fn from_str(path: &str) -> Result<Self, Self::Err> {
if path.is_empty() || path == "m" || path == "m/" {
return Ok(vec![].into());
}
@ -348,7 +355,7 @@ impl FromStr for DerivationPath {
let path = path.strip_prefix("m/").unwrap_or(path);
let parts = path.split('/');
let ret: Result<Vec<ChildNumber>, Error> = parts.map(str::parse).collect();
let ret: Result<Vec<ChildNumber>, _> = parts.map(str::parse).collect();
Ok(DerivationPath(ret?))
}
}
@ -496,27 +503,15 @@ pub type KeySource = (Fingerprint, DerivationPath);
/// A BIP32 error
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Error {
/// A pk->pk derivation was attempted on a hardened key
CannotDeriveFromHardenedKey,
pub enum ParseError {
/// A secp256k1 error occurred
Secp256k1(secp256k1::Error),
/// A child number was provided that was out of range
InvalidChildNumber(u32),
/// Invalid childnumber format.
InvalidChildNumberFormat,
/// Invalid derivation path format.
InvalidDerivationPathFormat,
/// Unknown version magic bytes
UnknownVersion([u8; 4]),
/// Encoded extended key data has wrong length
WrongExtendedKeyLength(usize),
/// Base58 encoding error
Base58(base58::Error),
/// Hexadecimal decoding error
Hex(hex::HexToArrayError),
/// `PublicKey` hex should be 66 or 130 digits long.
InvalidPublicKeyHexLength(usize),
/// Base58 decoded data was an invalid length.
InvalidBase58PayloadLength(InvalidBase58PayloadLengthError),
/// Invalid private key prefix (byte 45 must be 0)
@ -527,29 +522,20 @@ pub enum Error {
NonZeroChildNumberForMasterKey,
}
impl From<Infallible> for Error {
impl From<Infallible> for ParseError {
fn from(never: Infallible) -> Self { match never {} }
}
impl fmt::Display for Error {
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use Error::*;
use ParseError::*;
match *self {
CannotDeriveFromHardenedKey =>
f.write_str("cannot derive hardened key from public key"),
Secp256k1(ref e) => write_err!(f, "secp256k1 error"; e),
InvalidChildNumber(ref n) =>
write!(f, "child number {} is invalid (not within [0, 2^31 - 1])", n),
InvalidChildNumberFormat => f.write_str("invalid child number format"),
InvalidDerivationPathFormat => f.write_str("invalid derivation path format"),
UnknownVersion(ref bytes) => write!(f, "unknown version magic bytes: {:?}", bytes),
WrongExtendedKeyLength(ref len) =>
write!(f, "encoded extended key data has wrong length {}", len),
Base58(ref e) => write_err!(f, "base58 encoding error"; e),
Hex(ref e) => write_err!(f, "Hexadecimal decoding error"; e),
InvalidPublicKeyHexLength(got) =>
write!(f, "PublicKey hex should be 66 or 130 digits long, got: {}", got),
InvalidBase58PayloadLength(ref e) => write_err!(f, "base58 payload"; e),
InvalidPrivateKeyPrefix =>
f.write_str("invalid private key prefix, byte 45 must be 0 as required by BIP-32"),
@ -561,22 +547,15 @@ impl fmt::Display for Error {
}
#[cfg(feature = "std")]
impl std::error::Error for Error {
impl std::error::Error for ParseError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use Error::*;
use ParseError::*;
match *self {
Secp256k1(ref e) => Some(e),
Base58(ref e) => Some(e),
Hex(ref e) => Some(e),
InvalidBase58PayloadLength(ref e) => Some(e),
CannotDeriveFromHardenedKey
| InvalidChildNumber(_)
| InvalidChildNumberFormat
| InvalidDerivationPathFormat
| UnknownVersion(_)
| WrongExtendedKeyLength(_)
| InvalidPublicKeyHexLength(_) => None,
UnknownVersion(_) | WrongExtendedKeyLength(_) => None,
InvalidPrivateKeyPrefix => None,
NonZeroParentFingerprintForMasterKey => None,
NonZeroChildNumberForMasterKey => None,
@ -584,35 +563,121 @@ impl std::error::Error for Error {
}
}
impl From<secp256k1::Error> for Error {
fn from(e: secp256k1::Error) -> Error { Error::Secp256k1(e) }
impl From<secp256k1::Error> for ParseError {
fn from(e: secp256k1::Error) -> ParseError { ParseError::Secp256k1(e) }
}
impl From<base58::Error> for Error {
fn from(err: base58::Error) -> Self { Error::Base58(err) }
impl From<base58::Error> for ParseError {
fn from(err: base58::Error) -> Self { ParseError::Base58(err) }
}
impl From<InvalidBase58PayloadLengthError> for Error {
fn from(e: InvalidBase58PayloadLengthError) -> Error { Self::InvalidBase58PayloadLength(e) }
impl From<InvalidBase58PayloadLengthError> for ParseError {
fn from(e: InvalidBase58PayloadLengthError) -> ParseError {
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"),
}
}
}
/// Out-of-range index when constructing a child number.
///
/// *Indices* are always in the range [0, 2^31 - 1]. Normal child numbers have the
/// same range, while hardened child numbers lie in the range [2^31, 2^32 - 1].
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct IndexOutOfRangeError {
/// The index that was out of range for a child number.
pub index: u32,
}
#[cfg(feature = "std")]
impl std::error::Error for IndexOutOfRangeError {}
impl From<Infallible> for IndexOutOfRangeError {
fn from(never: Infallible) -> Self { match never {} }
}
impl fmt::Display for IndexOutOfRangeError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "index {} out of range [0, 2^31 - 1] (do you have an hardened child number, rather than an index?)", self.index)
}
}
/// Error parsing a child number.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParseChildNumberError {
/// Parsed the child number as an integer, but the integer was out of range.
IndexOutOfRange(IndexOutOfRangeError),
/// Failed to parse the child number as an integer.
ParseInt(core::num::ParseIntError),
}
impl From<Infallible> for ParseChildNumberError {
fn from(never: Infallible) -> Self { match never {} }
}
#[cfg(feature = "std")]
impl std::error::Error for ParseChildNumberError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match *self {
Self::IndexOutOfRange(ref e) => Some(e),
Self::ParseInt(ref e) => Some(e),
}
}
}
impl fmt::Display for ParseChildNumberError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Self::IndexOutOfRange(ref e) => e.fmt(f),
Self::ParseInt(ref e) => e.fmt(f),
}
}
}
impl Xpriv {
/// Constructs a new master key from a seed value
pub fn new_master(network: impl Into<NetworkKind>, seed: &[u8]) -> Result<Xpriv, Error> {
pub fn new_master(network: impl Into<NetworkKind>, seed: &[u8]) -> Xpriv {
let mut engine = HmacEngine::<sha512::HashEngine>::new(b"Bitcoin seed");
engine.input(seed);
let hmac = engine.finalize();
Ok(Xpriv {
Xpriv {
network: network.into(),
depth: 0,
parent_fingerprint: Default::default(),
child_number: ChildNumber::ZERO_NORMAL,
private_key: secp256k1::SecretKey::from_byte_array(
hmac.as_byte_array().split_array::<32, 32>().0,
)?,
)
.expect("cryptographically unreachable"),
chain_code: ChainCode::from_hmac(hmac),
})
}
}
/// Constructs a new ECDSA compressed private key matching internal secret key representation.
@ -644,7 +709,7 @@ impl Xpriv {
&self,
secp: &Secp256k1<C>,
path: &P,
) -> Xpriv {
) -> Result<Xpriv, DerivationError> {
self.derive_xpriv(secp, path)
}
@ -655,16 +720,20 @@ impl Xpriv {
&self,
secp: &Secp256k1<C>,
path: &P,
) -> Xpriv {
) -> Result<Xpriv, DerivationError> {
let mut sk: Xpriv = *self;
for cnum in path.as_ref() {
sk = sk.ckd_priv(secp, *cnum)
sk = sk.ckd_priv(secp, *cnum)?;
}
sk
Ok(sk)
}
/// Private->Private child key derivation
fn ckd_priv<C: secp256k1::Signing>(&self, secp: &Secp256k1<C>, i: ChildNumber) -> Xpriv {
fn ckd_priv<C: secp256k1::Signing>(
&self,
secp: &Secp256k1<C>,
i: ChildNumber,
) -> Result<Xpriv, DerivationError> {
let mut engine = HmacEngine::<sha512::HashEngine>::new(&self.chain_code[..]);
match i {
ChildNumber::Normal { .. } => {
@ -688,30 +757,30 @@ impl Xpriv {
let tweaked =
sk.add_tweak(&self.private_key.into()).expect("statistically impossible to hit");
Xpriv {
Ok(Xpriv {
network: self.network,
depth: self.depth + 1,
depth: self.depth.checked_add(1).ok_or(DerivationError::MaximumDepthExceeded)?,
parent_fingerprint: self.fingerprint(secp),
child_number: i,
private_key: tweaked,
chain_code: ChainCode::from_hmac(hmac),
}
})
}
/// Decoding extended private key from binary data according to BIP 32
pub fn decode(data: &[u8]) -> Result<Xpriv, Error> {
pub fn decode(data: &[u8]) -> Result<Xpriv, ParseError> {
let Common { network, depth, parent_fingerprint, child_number, chain_code, key } =
Common::decode(data)?;
let network = match network {
VERSION_BYTES_MAINNET_PRIVATE => NetworkKind::Main,
VERSION_BYTES_TESTNETS_PRIVATE => NetworkKind::Test,
unknown => return Err(Error::UnknownVersion(unknown)),
unknown => return Err(ParseError::UnknownVersion(unknown)),
};
let (&zero, private_key) = key.split_first();
if zero != 0 {
return Err(Error::InvalidPrivateKeyPrefix);
return Err(ParseError::InvalidPrivateKeyPrefix);
}
Ok(Xpriv {
@ -794,7 +863,7 @@ impl Xpub {
&self,
secp: &Secp256k1<C>,
path: &P,
) -> Result<Xpub, Error> {
) -> Result<Xpub, DerivationError> {
self.derive_xpub(secp, path)
}
@ -805,7 +874,7 @@ impl Xpub {
&self,
secp: &Secp256k1<C>,
path: &P,
) -> Result<Xpub, Error> {
) -> Result<Xpub, DerivationError> {
let mut pk: Xpub = *self;
for cnum in path.as_ref() {
pk = pk.ckd_pub(secp, *cnum)?
@ -817,9 +886,9 @@ impl Xpub {
pub fn ckd_pub_tweak(
&self,
i: ChildNumber,
) -> Result<(secp256k1::SecretKey, ChainCode), Error> {
) -> Result<(secp256k1::SecretKey, ChainCode), DerivationError> {
match i {
ChildNumber::Hardened { .. } => Err(Error::CannotDeriveFromHardenedKey),
ChildNumber::Hardened { .. } => Err(DerivationError::CannotDeriveHardenedChild),
ChildNumber::Normal { index: n } => {
let mut engine = HmacEngine::<sha512::HashEngine>::new(&self.chain_code[..]);
engine.input(&self.public_key.serialize()[..]);
@ -828,7 +897,8 @@ impl Xpub {
let hmac = engine.finalize();
let private_key = secp256k1::SecretKey::from_byte_array(
hmac.as_byte_array().split_array::<32, 32>().0,
)?;
)
.expect("cryptographically unreachable");
let chain_code = ChainCode::from_hmac(hmac);
Ok((private_key, chain_code))
}
@ -840,13 +910,14 @@ impl Xpub {
&self,
secp: &Secp256k1<C>,
i: ChildNumber,
) -> Result<Xpub, Error> {
) -> Result<Xpub, DerivationError> {
let (sk, chain_code) = self.ckd_pub_tweak(i)?;
let tweaked = self.public_key.add_exp_tweak(secp, &sk.into())?;
let tweaked =
self.public_key.add_exp_tweak(secp, &sk.into()).expect("cryptographically unreachable");
Ok(Xpub {
network: self.network,
depth: self.depth + 1,
depth: self.depth.checked_add(1).ok_or(DerivationError::MaximumDepthExceeded)?,
parent_fingerprint: self.fingerprint(),
child_number: i,
public_key: tweaked,
@ -855,14 +926,14 @@ impl Xpub {
}
/// Decoding extended public key from binary data according to BIP 32
pub fn decode(data: &[u8]) -> Result<Xpub, Error> {
pub fn decode(data: &[u8]) -> Result<Xpub, ParseError> {
let Common { network, depth, parent_fingerprint, child_number, chain_code, key } =
Common::decode(data)?;
let network = match network {
VERSION_BYTES_MAINNET_PUBLIC => NetworkKind::Main,
VERSION_BYTES_TESTNETS_PUBLIC => NetworkKind::Test,
unknown => return Err(Error::UnknownVersion(unknown)),
unknown => return Err(ParseError::UnknownVersion(unknown)),
};
Ok(Xpub {
@ -908,9 +979,9 @@ impl fmt::Display for Xpriv {
}
impl FromStr for Xpriv {
type Err = Error;
type Err = ParseError;
fn from_str(inp: &str) -> Result<Xpriv, Error> {
fn from_str(inp: &str) -> Result<Xpriv, ParseError> {
let data = base58::decode_check(inp)?;
if data.len() != 78 {
@ -928,9 +999,9 @@ impl fmt::Display for Xpub {
}
impl FromStr for Xpub {
type Err = Error;
type Err = ParseError;
fn from_str(inp: &str) -> Result<Xpub, Error> {
fn from_str(inp: &str) -> Result<Xpub, ParseError> {
let data = base58::decode_check(inp)?;
if data.len() != 78 {
@ -986,9 +1057,9 @@ struct Common {
}
impl Common {
fn decode(data: &[u8]) -> Result<Self, Error> {
fn decode(data: &[u8]) -> Result<Self, ParseError> {
let data: &[u8; 78] =
data.try_into().map_err(|_| Error::WrongExtendedKeyLength(data.len()))?;
data.try_into().map_err(|_| ParseError::WrongExtendedKeyLength(data.len()))?;
let (&network, data) = data.split_array::<4, 74>();
let (&depth, data) = data.split_first::<73>();
@ -998,11 +1069,11 @@ impl Common {
if depth == 0 {
if parent_fingerprint != [0u8; 4] {
return Err(Error::NonZeroParentFingerprintForMasterKey);
return Err(ParseError::NonZeroParentFingerprintForMasterKey);
}
if child_number != [0u8; 4] {
return Err(Error::NonZeroChildNumberForMasterKey);
return Err(ParseError::NonZeroChildNumberForMasterKey);
}
}
@ -1028,13 +1099,25 @@ mod tests {
#[test]
fn parse_derivation_path() {
assert_eq!("n/0'/0".parse::<DerivationPath>(), Err(Error::InvalidChildNumberFormat));
assert_eq!("4/m/5".parse::<DerivationPath>(), Err(Error::InvalidChildNumberFormat));
assert_eq!("//3/0'".parse::<DerivationPath>(), Err(Error::InvalidChildNumberFormat));
assert_eq!("0h/0x".parse::<DerivationPath>(), Err(Error::InvalidChildNumberFormat));
assert!(matches!(
"n/0'/0".parse::<DerivationPath>(),
Err(ParseChildNumberError::ParseInt(..)),
));
assert!(matches!(
"4/m/5".parse::<DerivationPath>(),
Err(ParseChildNumberError::ParseInt(..)),
));
assert!(matches!(
"//3/0'".parse::<DerivationPath>(),
Err(ParseChildNumberError::ParseInt(..)),
));
assert!(matches!(
"0h/0x".parse::<DerivationPath>(),
Err(ParseChildNumberError::ParseInt(..)),
));
assert_eq!(
"2147483648".parse::<DerivationPath>(),
Err(Error::InvalidChildNumber(2147483648))
Err(ParseChildNumberError::IndexOutOfRange(IndexOutOfRangeError { index: 2147483648 })),
);
assert_eq!(DerivationPath::master(), "".parse::<DerivationPath>().unwrap());
@ -1108,23 +1191,26 @@ mod tests {
expected_sk: &str,
expected_pk: &str,
) {
let mut sk = Xpriv::new_master(network, seed).unwrap();
let mut sk = Xpriv::new_master(network, seed);
let mut pk = Xpub::from_xpriv(secp, &sk);
// Check derivation convenience method for Xpriv
assert_eq!(&sk.derive_xpriv(secp, &path).to_string()[..], expected_sk);
assert_eq!(&sk.derive_xpriv(secp, &path).unwrap().to_string()[..], expected_sk);
// Check derivation convenience method for Xpub, should error
// appropriately if any ChildNumber 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 {
assert_eq!(&pk.derive_xpub(secp, &path).unwrap().to_string()[..], expected_pk);
}
// Derive keys, checking hardened and non-hardened derivation one-by-one
for &num in path.0.iter() {
sk = sk.ckd_priv(secp, num);
sk = sk.ckd_priv(secp, num).unwrap();
match num {
Normal { .. } => {
let pk2 = pk.ckd_pub(secp, num).unwrap();
@ -1132,7 +1218,10 @@ mod tests {
assert_eq!(pk, pk2);
}
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);
}
}
@ -1158,9 +1247,9 @@ mod tests {
let max = (1 << 31) - 1;
let cn = ChildNumber::from_normal_idx(max).unwrap();
assert_eq!(cn.increment().err(), Some(Error::InvalidChildNumber(1 << 31)));
assert_eq!(cn.increment(), Err(IndexOutOfRangeError { index: 1 << 31 }),);
let cn = ChildNumber::from_hardened_idx(max).unwrap();
assert_eq!(cn.increment().err(), Some(Error::InvalidChildNumber(1 << 31)));
assert_eq!(cn.increment(), Err(IndexOutOfRangeError { index: 1 << 31 }),);
let cn = ChildNumber::from_normal_idx(350).unwrap();
let path = "42'".parse::<DerivationPath>().unwrap();
@ -1290,7 +1379,7 @@ mod tests {
assert!(result.is_err());
match result {
Err(Error::InvalidPrivateKeyPrefix) => {}
Err(ParseError::InvalidPrivateKeyPrefix) => {}
_ => panic!("Expected InvalidPrivateKeyPrefix error, got {:?}", result),
}
}
@ -1301,7 +1390,7 @@ mod tests {
assert!(result.is_err());
match result {
Err(Error::NonZeroChildNumberForMasterKey) => {}
Err(ParseError::NonZeroChildNumberForMasterKey) => {}
_ => panic!("Expected NonZeroChildNumberForMasterKey error, got {:?}", result),
}
}
@ -1312,7 +1401,7 @@ mod tests {
assert!(result.is_err());
match result {
Err(Error::NonZeroParentFingerprintForMasterKey) => {}
Err(ParseError::NonZeroParentFingerprintForMasterKey) => {}
_ => panic!("Expected NonZeroParentFingerprintForMasterKey error, got {:?}", result),
}
}

View File

@ -775,14 +775,14 @@ impl GetKey for Xpriv {
KeyRequest::XOnlyPubkey(_) => Err(GetKeyError::NotSupported),
KeyRequest::Bip32((fingerprint, path)) => {
let key = if self.fingerprint(secp) == *fingerprint {
let k = self.derive_xpriv(secp, &path);
let k = self.derive_xpriv(secp, &path).map_err(GetKeyError::Bip32)?;
Some(k.to_private_key())
} else if self.parent_fingerprint == *fingerprint
&& !path.is_empty()
&& path[0] == self.child_number
{
let path = DerivationPath::from_iter(path.into_iter().skip(1).copied());
let k = self.derive_xpriv(secp, &path);
let k = self.derive_xpriv(secp, &path).map_err(GetKeyError::Bip32)?;
Some(k.to_private_key())
} else {
None
@ -915,8 +915,8 @@ impl_get_key_for_xonly_map!(HashMap);
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum GetKeyError {
/// A bip32 error.
Bip32(bip32::Error),
/// A bip32 derivation error.
Bip32(bip32::DerivationError),
/// The GetKey operation is not supported for this key request.
NotSupported,
}
@ -930,7 +930,7 @@ impl fmt::Display for GetKeyError {
use GetKeyError::*;
match *self {
Bip32(ref e) => write_err!(f, "a bip23 error"; e),
Bip32(ref e) => write_err!(f, "bip32 derivation"; e),
NotSupported =>
f.write_str("the GetKey operation is not supported for this key request"),
}
@ -949,10 +949,6 @@ impl std::error::Error for GetKeyError {
}
}
impl From<bip32::Error> for GetKeyError {
fn from(e: bip32::Error) -> Self { GetKeyError::Bip32(e) }
}
/// The various output types supported by the Bitcoin network.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
@ -1440,7 +1436,7 @@ mod tests {
let mut hd_keypaths: BTreeMap<secp256k1::PublicKey, KeySource> = Default::default();
let mut sk: Xpriv = Xpriv::new_master(NetworkKind::Main, &seed).unwrap();
let mut sk: Xpriv = Xpriv::new_master(NetworkKind::Main, &seed);
let fprint = sk.fingerprint(secp);
@ -1455,7 +1451,7 @@ mod tests {
ChildNumber::from_normal_idx(31337).unwrap(),
];
sk = sk.derive_xpriv(secp, &dpath);
sk = sk.derive_xpriv(secp, &dpath).unwrap();
let pk = Xpub::from_xpriv(secp, &sk);

View File

@ -122,7 +122,7 @@ fn build_extended_private_key() -> Xpriv {
let xpriv = extended_private_key.parse::<Xpriv>().unwrap();
let sk = PrivateKey::from_wif(seed).unwrap();
let seeded = Xpriv::new_master(NetworkKind::Test, &sk.inner.secret_bytes()).unwrap();
let seeded = Xpriv::new_master(NetworkKind::Test, &sk.inner.secret_bytes());
assert_eq!(xpriv, seeded);
xpriv
@ -317,7 +317,8 @@ fn parse_and_verify_keys(
let path =
derivation_path.into_derivation_path().expect("failed to convert derivation path");
let derived_priv = ext_priv.derive_xpriv(secp, &path).to_private_key();
let derived_priv =
ext_priv.derive_xpriv(secp, &path).expect("derivation path too long").to_private_key();
assert_eq!(wif_priv, derived_priv);
let derived_pub = derived_priv.public_key(secp);
key_map.insert(derived_pub, derived_priv);