use hmac::{Hmac, Mac};
use sha2::{Digest, Sha256, Sha512};
use crate::crypto::constants::KeyType;
use crate::crypto::signature::{Ed25519KeyPair, Ed448KeyPair};
use crate::error::{Result, ZeraError};
use crate::wallet::constants::{EXTENDED_PRIVATE_VERSION, EXTENDED_PUBLIC_VERSION};
const SLIP0010_HARDENED_OFFSET: u32 = 0x8000_0000;
const SLIP0010_SEED_KEY: &[u8] = b"ZERA seed";
#[derive(Debug, Clone)]
pub struct Slip0010HdWallet {
pub seed: Vec<u8>,
pub derivation_path: String,
pub key_type: KeyType,
pub depth: u8,
pub index: u32,
pub chain_code: [u8; 32],
pub private_key: [u8; 32],
pub public_key: Vec<u8>,
}
impl Slip0010HdWallet {
pub fn new(seed: &[u8], derivation_path: &str, key_type: KeyType) -> Result<Self> {
let path_parts = parse_derivation_path(derivation_path)?;
let depth = path_parts.len().saturating_sub(1) as u8;
let index = *path_parts.last().unwrap_or(&0);
let (private_key, chain_code) = derive_private_key(seed, &path_parts)?;
let public_key = generate_public_key(&private_key, key_type)?;
Ok(Self {
seed: seed.to_vec(),
derivation_path: derivation_path.to_string(),
key_type,
depth,
index,
chain_code,
private_key,
public_key,
})
}
pub fn from_parent_node(
parent_private_key: [u8; 32],
parent_chain_code: [u8; 32],
child_indices: &[u32],
full_path: &str,
seed: &[u8],
key_type: KeyType,
) -> Result<Self> {
let mut current_private = parent_private_key;
let mut current_chain = parent_chain_code;
for index in child_indices {
let (next_private, next_chain) =
derive_child_key(¤t_private, ¤t_chain, *index)?;
current_private = next_private;
current_chain = next_chain;
}
let public_key = generate_public_key(¤t_private, key_type)?;
Ok(Self {
seed: seed.to_vec(),
derivation_path: full_path.to_string(),
key_type,
depth: full_path.split('/').count().saturating_sub(2) as u8,
index: *child_indices.last().unwrap_or(&0),
chain_code: current_chain,
private_key: current_private,
public_key,
})
}
pub fn get_key_material(&self) -> ([u8; 32], [u8; 32]) {
(self.private_key, self.chain_code)
}
pub fn get_address(&self) -> String {
bs58::encode(&self.public_key).into_string()
}
pub fn get_extended_private_key(&self) -> String {
let mut bytes = Vec::with_capacity(4 + 1 + 4 + 32 + 32);
bytes.extend_from_slice(&EXTENDED_PRIVATE_VERSION.to_be_bytes());
bytes.push(self.depth);
bytes.extend_from_slice(&self.index.to_be_bytes());
bytes.extend_from_slice(&self.chain_code);
bytes.extend_from_slice(&self.private_key);
bs58::encode(bytes).into_string()
}
pub fn get_extended_public_key(&self) -> String {
let mut bytes = Vec::with_capacity(4 + 1 + 4 + 32 + 57);
bytes.extend_from_slice(&EXTENDED_PUBLIC_VERSION.to_be_bytes());
bytes.push(self.depth);
bytes.extend_from_slice(&self.index.to_be_bytes());
bytes.extend_from_slice(&self.chain_code);
bytes.extend_from_slice(&self.public_key);
bs58::encode(bytes).into_string()
}
pub fn get_private_key_base58(&self) -> String {
bs58::encode(self.private_key).into_string()
}
pub fn get_fingerprint(&self) -> String {
let hash = Sha256::digest(&self.public_key);
hex::encode(&hash[..4])
}
pub fn secure_clear(&mut self) {
self.seed.fill(0);
self.private_key.fill(0);
self.chain_code.fill(0);
}
}
fn parse_derivation_path(path: &str) -> Result<Vec<u32>> {
if !path.starts_with("m/") {
return Err(ZeraError::Validation(
"Invalid derivation path: must start with \"m/\"".into(),
));
}
let parts = path.split('/').skip(1);
let mut out = Vec::new();
for part in parts {
if part.is_empty() {
return Err(ZeraError::Validation(
"Invalid derivation path segment".into(),
));
}
if let Some(stripped) = part.strip_suffix('\'') {
let value: u32 = stripped.parse().map_err(|_| {
ZeraError::Validation(format!("Invalid derivation path segment: {part}"))
})?;
out.push(value + SLIP0010_HARDENED_OFFSET);
} else {
let value: u32 = part.parse().map_err(|_| {
ZeraError::Validation(format!("Invalid derivation path segment: {part}"))
})?;
out.push(value);
}
}
Ok(out)
}
fn derive_private_key(seed: &[u8], path_indices: &[u32]) -> Result<([u8; 32], [u8; 32])> {
let (mut current_private, mut current_chain) = derive_key_from_seed(seed)?;
for index in path_indices {
let (next_private, next_chain) =
derive_child_key(¤t_private, ¤t_chain, *index)?;
current_private = next_private;
current_chain = next_chain;
}
Ok((current_private, current_chain))
}
fn derive_key_from_seed(seed: &[u8]) -> Result<([u8; 32], [u8; 32])> {
let mut mac = Hmac::<Sha512>::new_from_slice(SLIP0010_SEED_KEY)
.map_err(|e| ZeraError::Crypto(format!("failed to init seed HMAC: {e}")))?;
mac.update(seed);
let out = mac.finalize().into_bytes();
let mut private_key = [0_u8; 32];
private_key.copy_from_slice(&out[..32]);
let mut chain_code = [0_u8; 32];
chain_code.copy_from_slice(&out[32..64]);
Ok((private_key, chain_code))
}
fn derive_child_key(
private_key: &[u8; 32],
chain_code: &[u8; 32],
index: u32,
) -> Result<([u8; 32], [u8; 32])> {
let mut mac = Hmac::<Sha512>::new_from_slice(chain_code)
.map_err(|e| ZeraError::Crypto(format!("failed to init child HMAC: {e}")))?;
mac.update(&index.to_le_bytes());
mac.update(private_key);
let out = mac.finalize().into_bytes();
let mut child_private = [0_u8; 32];
child_private.copy_from_slice(&out[..32]);
let mut child_chain = [0_u8; 32];
child_chain.copy_from_slice(&out[32..64]);
Ok((child_private, child_chain))
}
fn generate_public_key(private_key: &[u8; 32], key_type: KeyType) -> Result<Vec<u8>> {
match key_type {
KeyType::Ed25519 => {
let key = Ed25519KeyPair::from_private_key(private_key)?;
Ok(key.public_key_bytes().to_vec())
}
KeyType::Ed448 => {
let key = Ed448KeyPair::from_private_key(private_key)?;
Ok(key.public_key_bytes().to_vec())
}
}
}