use bip32::{
ChainCode, ChildNumber, DerivationPath, ExtendedKey, ExtendedKeyAttrs, ExtendedPrivateKey,
ExtendedPublicKey, Prefix,
};
pub use bip39::{Language, Mnemonic};
use secp256k1::{PublicKey, SecretKey as PrivateKey};
pub const VET_EXTERNAL_PATH: &str = "m/44'/818'/0'/0";
#[derive(Clone, Debug, Eq, PartialEq)]
enum HDNodeVariant {
Full(ExtendedPrivateKey<PrivateKey>),
Restricted(ExtendedPublicKey<PublicKey>),
}
use HDNodeVariant::{Full, Restricted};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct HDNode(HDNodeVariant);
impl HDNode {
pub fn build<'a>() -> HDNodeBuilder<'a> {
HDNodeBuilder::default()
}
pub fn derive(&self, index: u32) -> Result<Self, HDNodeError> {
let child = match &self.0 {
Full(privkey) => Self(Full(privkey.derive_child(ChildNumber(index))?)),
Restricted(pubkey) => Self(Restricted(pubkey.derive_child(ChildNumber(index))?)),
};
Ok(child)
}
pub fn public_key(&self) -> ExtendedPublicKey<PublicKey> {
match &self.0 {
Full(privkey) => privkey.public_key(),
Restricted(pubkey) => pubkey.clone(),
}
}
pub fn private_key(&self) -> Result<ExtendedPrivateKey<PrivateKey>, HDNodeError> {
match &self.0 {
Full(privkey) => Ok(privkey.clone()),
Restricted(_) => Err(HDNodeError::Crypto),
}
}
pub fn chain_code(&self) -> ChainCode {
match &self.0 {
Full(privkey) => privkey.attrs().chain_code,
Restricted(pubkey) => pubkey.attrs().chain_code,
}
}
pub fn parent_fingerprint(&self) -> [u8; 4] {
match &self.0 {
Full(privkey) => privkey.attrs().parent_fingerprint,
Restricted(pubkey) => pubkey.attrs().parent_fingerprint,
}
}
pub fn child_number(&self) -> ChildNumber {
match &self.0 {
Full(privkey) => privkey.attrs().child_number,
Restricted(pubkey) => pubkey.attrs().child_number,
}
}
pub fn depth(&self) -> u8 {
match &self.0 {
Full(privkey) => privkey.attrs().depth,
Restricted(pubkey) => pubkey.attrs().depth,
}
}
pub fn address(self) -> crate::address::Address {
use crate::address::AddressConvertible;
match &self.0 {
Full(privkey) => privkey.public_key().public_key().address(),
Restricted(pubkey) => pubkey.public_key().address(),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum HDNodeError {
Crypto,
Parse,
WrongChildNumber,
Unbuildable(String),
Custom(String),
}
#[cfg(not(tarpaulin_include))]
impl std::fmt::Display for HDNodeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Crypto => f.write_str("cryptography error"),
Self::Parse => f.write_str("decoding error"),
Self::WrongChildNumber => {
f.write_str("cannot derive hardened children from public key")
}
Self::Unbuildable(msg) => {
f.write_str("cannot build HDNode:")?;
f.write_str(msg)
}
Self::Custom(msg) => f.write_str(msg),
}
}
}
impl std::error::Error for HDNodeError {}
#[cfg(not(tarpaulin_include))]
impl From<bip32::Error> for HDNodeError {
fn from(err: bip32::Error) -> HDNodeError {
match err {
bip32::Error::Crypto => HDNodeError::Crypto,
bip32::Error::Decode => HDNodeError::Parse,
bip32::Error::ChildNumber => HDNodeError::WrongChildNumber,
err => HDNodeError::Custom(format!("{:?}", err)),
}
}
}
#[derive(Clone, Default)]
pub struct HDNodeBuilder<'a> {
path: Option<DerivationPath>,
seed: Option<[u8; 64]>,
mnemonic: Option<Mnemonic>,
password: Option<&'a str>,
ext_privkey: Option<ExtendedKey>,
ext_pubkey: Option<ExtendedKey>,
}
impl<'a> HDNodeBuilder<'a> {
pub fn path(mut self, path: DerivationPath) -> Self {
self.path = Some(path);
self
}
pub fn seed(mut self, seed: [u8; 64]) -> Self {
self.seed = Some(seed);
self
}
pub fn mnemonic(mut self, mnemonic: Mnemonic) -> Self {
self.mnemonic = Some(mnemonic);
self
}
pub fn mnemonic_with_password(mut self, mnemonic: Mnemonic, password: &'a str) -> Self {
self.mnemonic = Some(mnemonic);
self.password = Some(password);
self
}
pub fn master_private_key_bytes<T: Into<ChainCode>>(
mut self,
key: [u8; 33],
chain_code: T,
) -> Self {
self.ext_privkey = Some(ExtendedKey {
prefix: Prefix::XPRV,
attrs: ExtendedKeyAttrs {
depth: 0,
parent_fingerprint: [0; 4],
child_number: ChildNumber(0u32),
chain_code: chain_code.into(),
},
key_bytes: key,
});
self
}
pub fn private_key(mut self, ext_key: ExtendedKey) -> Self {
self.ext_privkey = Some(ext_key);
self
}
pub fn master_public_key_bytes<T: Into<ChainCode>>(
mut self,
key: [u8; 33],
chain_code: T,
) -> Self {
self.ext_pubkey = Some(ExtendedKey {
prefix: Prefix::XPUB,
attrs: ExtendedKeyAttrs {
depth: 0,
parent_fingerprint: [0; 4],
child_number: ChildNumber(0u32),
chain_code: chain_code.into(),
},
key_bytes: key,
});
self
}
pub fn public_key(mut self, ext_key: ExtendedKey) -> Self {
self.ext_pubkey = Some(ext_key);
self
}
pub fn build(self) -> Result<HDNode, HDNodeError> {
match (self.seed, self.mnemonic, self.ext_privkey, self.ext_pubkey) {
(Some(seed), None, None, None) => {
let path = self.path.unwrap_or_else(|| {
VET_EXTERNAL_PATH
.parse()
.expect("hardcoded path must be valid")
});
Ok(ExtendedPrivateKey::derive_from_path(seed, &path).map(|k| HDNode(Full(k)))?)
}
(None, Some(mnemonic), None, None) => {
let path = self.path.unwrap_or_else(|| {
VET_EXTERNAL_PATH
.parse()
.expect("hardcoded path must be valid")
});
Ok(ExtendedPrivateKey::derive_from_path(
bip39::Seed::new(&mnemonic, self.password.unwrap_or("")),
&path,
)
.map(|k| HDNode(Full(k)))?)
}
(None, None, Some(ext_key), None) => Ok(HDNode(Full(ext_key.try_into()?))),
(None, None, None, Some(ext_key)) => Ok(HDNode(Restricted(ext_key.try_into()?))),
(None, None, None, None) => Err(HDNodeError::Unbuildable(
"no parameters provided".to_string(),
)),
_ => Err(HDNodeError::Unbuildable(
"incompatible parameters".to_string(),
)),
}
}
}