use std::path::Path;
use std::str::FromStr;
use std::{fs, io};
use bip39::Mnemonic;
use bpstd::{HardenedIndex, XkeyOrigin, Xpriv, XprivAccount};
use rand::RngCore;
use crate::bip43::DerivationStandard;
use crate::hot::{decrypt, encrypt, DataError, SecureIo};
use crate::Bip43;
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
#[repr(u16)]
pub enum SeedType {
Bit128 = 128,
Bit160 = 160,
Bit192 = 192,
Bit224 = 224,
Bit256 = 256,
}
impl SeedType {
#[inline]
pub fn bit_len(self) -> usize { self as usize }
#[inline]
pub fn byte_len(self) -> usize {
match self {
SeedType::Bit128 => 16,
SeedType::Bit160 => 160 / 8,
SeedType::Bit192 => 192 / 8,
SeedType::Bit224 => 224 / 8,
SeedType::Bit256 => 32,
}
}
#[inline]
pub fn word_len(self) -> usize {
match self {
SeedType::Bit128 => 12,
SeedType::Bit160 => 15,
SeedType::Bit192 => 18,
SeedType::Bit224 => 21,
SeedType::Bit256 => 24,
}
}
}
pub struct Seed(Box<[u8]>);
impl Seed {
pub fn random(seed_type: SeedType) -> Seed {
let mut entropy = vec![0u8; seed_type.byte_len()];
rand::rng().fill_bytes(&mut entropy);
Seed(Box::from(entropy))
}
#[inline]
pub fn as_entropy(&self) -> &[u8] { &self.0 }
#[inline]
pub fn master_xpriv(&self, testnet: bool) -> Xpriv {
Xpriv::new_master(testnet, self.as_entropy())
}
pub fn derive(&self, scheme: Bip43, testnet: bool, account: HardenedIndex) -> XprivAccount {
let master_xpriv = self.master_xpriv(testnet);
let master_xpub = master_xpriv.to_xpub();
let derivation = scheme.to_account_derivation(account, testnet);
let account_xpriv = master_xpriv.derive_priv(&derivation);
let origin = XkeyOrigin::new(master_xpub.fingerprint(), derivation);
XprivAccount::new(account_xpriv, origin).expect("seed must always derive")
}
}
impl SecureIo for Seed {
fn read<P>(file: P, password: &str) -> Result<Self, DataError>
where P: AsRef<Path> {
let data = fs::read(file)?;
let data = decrypt(&data, password).map_err(|_| DataError::SeedPassword)?;
let s = String::from_utf8(data).map_err(|_| DataError::SeedPassword)?;
let mnemonic = Mnemonic::from_str(&s).map_err(|_| DataError::SeedPassword)?;
Ok(Seed(Box::from(mnemonic.to_entropy())))
}
fn write<P>(&self, file: P, password: &str) -> io::Result<()>
where P: AsRef<Path> {
let mnemonic = Mnemonic::from_entropy(&self.0).expect("mnemonic generator is broken");
fs::write(file, encrypt(mnemonic.to_string().into_bytes(), password))
}
}
impl SecureIo for XprivAccount {
fn read<P>(file: P, password: &str) -> Result<Self, DataError>
where P: AsRef<Path> {
let data = fs::read(file)?;
let data = decrypt(&data, password).map_err(|_| DataError::AccountPassword)?;
let s = String::from_utf8(data).map_err(|_| DataError::AccountPassword)?;
XprivAccount::from_str(&s).map_err(|_| DataError::AccountPassword)
}
fn write<P>(&self, file: P, password: &str) -> io::Result<()>
where P: AsRef<Path> {
fs::write(file, encrypt(self.to_string().into_bytes(), password))
}
}