From 96e6c236f05134c3445800bd8178e74fa5f723e8 Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 31 Aug 2023 23:49:35 -0500 Subject: [PATCH] keyfork-derive-util: add some documentation --- keyfork-derive-util/src/error.rs | 11 ---- .../src/extended_key/private_key.rs | 9 ++++ .../src/extended_key/public_key.rs | 10 ++++ keyfork-derive-util/src/index.rs | 16 +++++- keyfork-derive-util/src/lib.rs | 3 +- keyfork-derive-util/src/master_key.rs | 54 ------------------- keyfork-derive-util/src/path.rs | 28 +++++++++- keyfork-derive-util/src/private_key.rs | 15 ++++++ keyfork-derive-util/src/public_key.rs | 10 ++++ 9 files changed, 87 insertions(+), 69 deletions(-) delete mode 100644 keyfork-derive-util/src/master_key.rs diff --git a/keyfork-derive-util/src/error.rs b/keyfork-derive-util/src/error.rs index 5dc4dcb..78272d8 100644 --- a/keyfork-derive-util/src/error.rs +++ b/keyfork-derive-util/src/error.rs @@ -10,17 +10,6 @@ pub enum Error { #[error("Unable to parse path due to bad path prefix")] UnknownPathPrefix, - - #[error("Seed length in bits must be divisible by 32")] - BadSeedLength(usize), - - /// This should never happen. HMAC keys should be able to take any size input. - #[error("Invalid length for HMAC key while generating master key (report me!)")] - HmacInvalidLength(#[from] hmac::digest::InvalidLength), - - /// There's a 1 in 2^256 chance this will happen. If it does, I'm sorry. Pick a new mnemonic. - #[error("Seed hash generated 32 bytes of zero, pick a new seed")] - HashedSeedIsZero, } pub type Result = std::result::Result; diff --git a/keyfork-derive-util/src/extended_key/private_key.rs b/keyfork-derive-util/src/extended_key/private_key.rs index 8eb809e..0314683 100644 --- a/keyfork-derive-util/src/extended_key/private_key.rs +++ b/keyfork-derive-util/src/extended_key/private_key.rs @@ -7,11 +7,14 @@ use thiserror::Error; const KEY_SIZE: usize = 256; +/// Errors associated with creating or deriving Extended Private Keys. #[derive(Error, Clone, Debug)] pub enum Error { + /// The seed has an unsuitable length; supported lengths are 16 bytes, 32 bytes, or 64 bytes. #[error("Seed had an unsuitable length: {0}")] BadSeedLength(usize), + /// The maximum depth for key derivation has been reached. The supported maximum depth is 255. #[error("Reached maximum depth for key derivation")] Depth, @@ -19,6 +22,7 @@ pub enum Error { #[error("Invalid length for HMAC key while generating master key (report me!)")] HmacInvalidLength(#[from] hmac::digest::InvalidLength), + /// An unknown error occurred while deriving a child key. #[error("Unknown error while deriving child key")] Derivation, } @@ -27,8 +31,12 @@ pub type Result = std::result::Result; pub type ChainCode = [u8; 32]; type HmacSha512 = Hmac; +/// Extended private keys derived using BIP-0032. +/// +/// Generic over types implementing [`PrivateKey`]. #[derive(Clone, Serialize, Deserialize)] pub struct ExtendedPrivateKey { + /// The internal private key data. pub private_key: K, depth: u8, pub(crate) chain_code: ChainCode, @@ -83,6 +91,7 @@ where }) } + /// Return a public key for the current [`PrivateKey`]. pub fn public_key(&self) -> K::PublicKey { self.private_key.public_key() } diff --git a/keyfork-derive-util/src/extended_key/public_key.rs b/keyfork-derive-util/src/extended_key/public_key.rs index a455738..340bc0a 100644 --- a/keyfork-derive-util/src/extended_key/public_key.rs +++ b/keyfork-derive-util/src/extended_key/public_key.rs @@ -6,11 +6,14 @@ use thiserror::Error; const KEY_SIZE: usize = 256; +/// Errors associated with creating or deriving Extended Public Keys. #[derive(Error, Clone, Debug)] pub enum Error { + /// BIP-0032 does not support deriving public keys from hardened private keys. #[error("Public keys may not be derived when hardened")] HardenedIndex, + /// The maximum depth for key derivation has been reached. The supported maximum depth is 255. #[error("Reached maximum depth for key derivation")] Depth, @@ -18,6 +21,7 @@ pub enum Error { #[error("Invalid length for HMAC key while generating master key (report me!)")] HmacInvalidLength(#[from] hmac::digest::InvalidLength), + /// An unknown error occurred while deriving a child key. #[error("Unknown error while deriving child key")] Derivation, } @@ -26,6 +30,9 @@ pub type Result = std::result::Result; pub type ChainCode = [u8; 32]; type HmacSha512 = Hmac; +/// Extended public keys derived using BIP-0032. +/// +/// Generic over types implementing [`PublicKey`]. pub struct ExtendedPublicKey { public_key: K, depth: u8, @@ -36,6 +43,8 @@ impl ExtendedPublicKey where K: PublicKey, { + /* + /// Create a new [`ExtendedPublicKey`] from previously known values. pub fn new(public_key: K, chain_code: ChainCode) -> Self { Self { public_key, @@ -43,6 +52,7 @@ where chain_code, } } + */ /// Derive a child with a given [`DerivationIndex`]. /// diff --git a/keyfork-derive-util/src/index.rs b/keyfork-derive-util/src/index.rs index 779ca27..17d793c 100644 --- a/keyfork-derive-util/src/index.rs +++ b/keyfork-derive-util/src/index.rs @@ -1,6 +1,19 @@ -use crate::error::{Error, Result}; use serde::{Deserialize, Serialize}; +use thiserror::Error; +/// Errors associated with creating a [`DerivableIndex`]. +#[derive(Error, Debug)] +pub enum Error { + #[error("Index is too large, must be less than 0x80000000: {0}")] + IndexTooLarge(u32), + + #[error("Unable to parse integer for index")] + IntParseError(#[from] std::num::ParseIntError), +} + +pub type Result = std::result::Result; + +/// Index for a given extended private key. #[derive(Serialize, Deserialize, Clone, Debug)] pub struct DerivationIndex(pub(crate) u32); @@ -28,6 +41,7 @@ impl DerivationIndex { self.0.to_be_bytes() } + /// Whether or not the index is hardened, allowing deriving the key from a known parent key. pub fn is_hardened(&self) -> bool { self.0 & (0b1 << 31) != 0 } diff --git a/keyfork-derive-util/src/lib.rs b/keyfork-derive-util/src/lib.rs index b1e075e..a8eb7c7 100644 --- a/keyfork-derive-util/src/lib.rs +++ b/keyfork-derive-util/src/lib.rs @@ -1,9 +1,10 @@ #![allow(clippy::module_name_repetitions, clippy::must_use_candidate)] +//! BIP-0032 derivation utilities. + pub mod error; pub mod extended_key; pub mod index; -pub mod master_key; pub mod path; pub mod private_key; pub mod public_key; diff --git a/keyfork-derive-util/src/master_key.rs b/keyfork-derive-util/src/master_key.rs deleted file mode 100644 index a8cbe5e..0000000 --- a/keyfork-derive-util/src/master_key.rs +++ /dev/null @@ -1,54 +0,0 @@ -use crate::error::{Error, Result}; - -use hmac::{Hmac, Mac}; -use sha2::Sha512; - -pub trait MasterKey<'a> { - /// Return the textual content used to derive the master key, as specified in BIP 0032 and SLIP - /// 0010. For example, a secp256k1 master key would use the textual content "Bitcoin seed", to - /// ensure compatibility with BIP 0032, despite the key being used for more functionality than - /// purely Bitcoin. - fn key() -> &'static str; - - /// Some key algorithms, such as Ed25519, allow 0 as a valid private key. Those algorithhms - /// should override this method to indicate as such. - fn is_zero_valid_private_key() -> bool { - false - } - - /// Return the seed used to derive the Master Key, with a size between 128 to 512 bits. Most - /// seeds should be 256 bits, the largest size available from a BIP-0039 mnemonic. - fn seed(&self) -> &'a [u8]; -} - -type HmacSha512 = Hmac; - -/// Generate a Master Secret Key and Chain Code (what is this used for?). -/// -/// # Errors -/// -/// An error may be returned if: -/// * The `HmacSha512` key returned by `T::key()` is invalid. This should never happen. -/// * The generated master key is all zeroes. This has a cosmically small chance of happening. -pub fn generate<'a, T: MasterKey<'a>>(generator: &T) -> Result<(Vec, Vec)> { - let seed = generator.seed(); - let len = seed.len(); - if len * 8 % 32 != 0 { - return Err(Error::BadSeedLength(len)); - } - - let mut hmac = HmacSha512::new_from_slice(&T::key().bytes().collect::>())?; - hmac.update(seed); - let result = hmac.finalize().into_bytes(); - let left = &result[..32]; - let right = &result[32..]; - if left.iter().all(|n| n == &0) { - // Wow. Impressive. - // NOTE: SLIP-0010 says to retry if this happens, but uses some weird terminology to do so. - // I do not trust it. BIP-0032 says this key is "invalid", with no instructions to retry. - // This is a low enough chance I am fine with it being freak-of-nature error. - return Err(Error::HashedSeedIsZero); - } - - Ok((left.to_vec(), right.to_vec())) -} diff --git a/keyfork-derive-util/src/path.rs b/keyfork-derive-util/src/path.rs index 9adb4a3..856a50a 100644 --- a/keyfork-derive-util/src/path.rs +++ b/keyfork-derive-util/src/path.rs @@ -1,15 +1,36 @@ -use crate::error::{Error, Result}; use crate::index::DerivationIndex; use serde::{Deserialize, Serialize}; +use thiserror::Error; + +/// Errors associated with creating a [`DerivationPath`]. +#[derive(Error, Debug)] +pub enum Error { + /// A [`DerivationIndex`] was not able to be created. + #[error("Unable to create index: {0}")] + UnableToCreateIndex(#[from] super::index::Error), + + /// The path could not be parsed due to a bad prefix. Paths must be in the format: + /// + /// m [/ index [']]+ + /// + /// The prefix for the path must be `m`, and all indices must be integers between 0 and + /// 2^31. + #[error("Unable to parse path due to bad path prefix")] + UnknownPathPrefix, +} + +pub type Result = std::result::Result; const PREFIX: &str = "m"; +/// A fully qualified path to derive a key. #[derive(Serialize, Deserialize, Clone, Debug, Default)] pub struct DerivationPath { pub(crate) path: Vec, } impl DerivationPath { + /// Returns an iterator over the [`DerivationPath`]. pub fn iter(&self) -> impl Iterator { self.path.iter() } @@ -24,7 +45,10 @@ impl std::str::FromStr for DerivationPath { return Err(Error::UnknownPathPrefix); } Ok(Self { - path: iter.map(DerivationIndex::from_str).collect::>()?, + path: iter + .map(DerivationIndex::from_str) + .map(|maybe_err| maybe_err.map_err(From::from)) + .collect::>>()?, }) } } diff --git a/keyfork-derive-util/src/private_key.rs b/keyfork-derive-util/src/private_key.rs index a94231d..669c200 100644 --- a/keyfork-derive-util/src/private_key.rs +++ b/keyfork-derive-util/src/private_key.rs @@ -4,18 +4,29 @@ use thiserror::Error; pub type PrivateKeyBytes = [u8; 32]; +/// Functions required to use an `ExtendedPrivateKey`. pub trait PrivateKey: Sized { + /// A type implementing [`PublicKey`] associated with Self. type PublicKey: PublicKey; + + /// The error returned by [`PrivateKey::derive_child()`]. type Err: std::error::Error; + /// Create a Self from bytes. fn from_bytes(b: &PrivateKeyBytes) -> Self; + + /// Convert a &Self to bytes. fn to_bytes(&self) -> PrivateKeyBytes; + /// Whether or not zero is a valid public key (such as with ed25519 keys). fn is_zero_valid_public_key() -> bool { false } + + /// The initial key for BIP-0032 and SLIP-0010 derivation, such as secp256k1's "Bitcoin seed". fn key() -> &'static str; + /// Generate a [`Self::PublicKey`]. fn public_key(&self) -> Self::PublicKey; /// Derive a child [`PrivateKey`] with given `PrivateKeyBytes`. @@ -28,11 +39,15 @@ pub trait PrivateKey: Sized { fn derive_child(&self, other: &PrivateKeyBytes) -> Result; } +/// Errors associated with creating and arithmetic on private keys. This specific error is only +/// intended to be used by the implementations in this crate. #[derive(Clone, Debug, Error)] pub enum PrivateKeyError { + /// For the given algorithm, the private key must be nonzero. #[error("The provided private key must be nonzero, but is not")] NonZero, + /// Unable to convert a point to a key. #[error("Unable to convert point to key")] PointToKey(#[from] k256::elliptic_curve::Error), } diff --git a/keyfork-derive-util/src/public_key.rs b/keyfork-derive-util/src/public_key.rs index 9412357..a062a24 100644 --- a/keyfork-derive-util/src/public_key.rs +++ b/keyfork-derive-util/src/public_key.rs @@ -7,10 +7,15 @@ use thiserror::Error; pub type PublicKeyBytes = [u8; 33]; +/// Functions required to use an `ExtendedPublicKey`. pub trait PublicKey: Sized { + /// The error returned by [`PublicKey::derive_child()`]. type Err: std::error::Error; + /// Create a Self from bytes. fn from_bytes(b: &PublicKeyBytes) -> Self; + + /// Convert a &Self to bytse. fn to_bytes(&self) -> PublicKeyBytes; /// Derive a child [`PublicKey`] with given `PrivateKeyBytes`. @@ -22,6 +27,7 @@ pub trait PublicKey: Sized { /// * An error specific to the given algorithm was encountered. fn derive_child(&self, other: PrivateKeyBytes) -> Result; + /// Create a BIP-0032/SLIP-0010 fingerprint from the public key. fn fingerprint(&self) -> [u8; 4] { let hash = Sha256::new().chain_update(self.to_bytes()).finalize(); let hash = Ripemd160::new().chain_update(hash).finalize(); @@ -30,11 +36,15 @@ pub trait PublicKey: Sized { } } +/// Errors associated with creating and arithmetic on public keys. This specific error is only +/// intended to be used by the implementations in this crate. #[derive(Clone, Debug, Error)] pub enum PublicKeyError { + /// For the given algorithm, the private key must be nonzero. #[error("The provided public key must be nonzero, but is not")] NonZero, + /// Unable to convert a point to a key. #[error("Unable to convert point to key")] PointToKey(#[from] k256::elliptic_curve::Error), }