keyfork-derive-util: add some documentation
This commit is contained in:
parent
5424e66aed
commit
96e6c236f0
|
@ -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>;
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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`].
|
||||
///
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()))
|
||||
}
|
|
@ -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>>>()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue