keyfork-derive-util: add some documentation

This commit is contained in:
Ryan Heywood 2023-08-31 23:49:35 -05:00
parent 5424e66aed
commit 96e6c236f0
Signed by: ryan
GPG Key ID: 8E401478A3FBEF72
9 changed files with 87 additions and 69 deletions

View File

@ -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<T, E = Error> = std::result::Result<T, E>;

View File

@ -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<T, E = Error> = std::result::Result<T, E>;
pub type ChainCode = [u8; 32];
type HmacSha512 = Hmac<Sha512>;
/// Extended private keys derived using BIP-0032.
///
/// Generic over types implementing [`PrivateKey`].
#[derive(Clone, Serialize, Deserialize)]
pub struct ExtendedPrivateKey<K: PrivateKey + Clone> {
/// 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()
}

View File

@ -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<T, E = Error> = std::result::Result<T, E>;
pub type ChainCode = [u8; 32];
type HmacSha512 = Hmac<Sha512>;
/// Extended public keys derived using BIP-0032.
///
/// Generic over types implementing [`PublicKey`].
pub struct ExtendedPublicKey<K: PublicKey> {
public_key: K,
depth: u8,
@ -36,6 +43,8 @@ impl<K> ExtendedPublicKey<K>
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`].
///

View File

@ -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<T, E = Error> = std::result::Result<T, E>;
/// 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
}

View File

@ -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;

View File

@ -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<Sha512>;
/// 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<u8>, Vec<u8>)> {
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::<Vec<_>>())?;
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()))
}

View File

@ -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<T, E = Error> = std::result::Result<T, E>;
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<DerivationIndex>,
}
impl DerivationPath {
/// Returns an iterator over the [`DerivationPath`].
pub fn iter(&self) -> impl Iterator<Item = &DerivationIndex> {
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::<Result<_>>()?,
path: iter
.map(DerivationIndex::from_str)
.map(|maybe_err| maybe_err.map_err(From::from))
.collect::<Result<Vec<DerivationIndex>>>()?,
})
}
}

View File

@ -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<Self, Self::Err>;
}
/// 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),
}

View File

@ -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<Self, Self::Err>;
/// 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),
}