use bip39::Mnemonic;
use bitcoin::bip32::{DerivationPath, Xpriv, Xpub};
use bitcoin::key::{Keypair, Secp256k1};
use bitcoin::secp256k1::ecdsa::Signature;
use bitcoin::secp256k1::{All, Message, PublicKey, SecretKey};
use bitcoin::{Address, CompressedPublicKey, Network, NetworkKind};
use secretstore::{SecretStore, SecretStoreCreator};
use std::str::FromStr;
use zeroize::{Zeroize, ZeroizeOnDrop};
const NONSECRET_DATA_LEN: usize = 4;
pub struct SeedStore {
secretstore: SecretStore,
scrambled_optional_passphrase: Vec<u8>,
secp: Secp256k1<All>,
}
pub struct SeedStoreCreator {}
pub enum ChildSpecifier {
Index4(u32),
ChangeAndIndex34(u32, u32),
Derivation(String),
}
impl SeedStore {
pub fn new_from_encrypted_file(
path_for_secret_file: &str,
encryption_password: &str,
seed_passphrase: Option<&str>,
) -> Result<Self, String> {
let secretstore =
SecretStore::new_from_encrypted_file(path_for_secret_file, encryption_password)?;
Self::new_from_secretstore(secretstore, seed_passphrase)
}
pub fn new_from_payload(
secret_payload: &Vec<u8>,
encryption_password: &str,
seed_passphrase: Option<&str>,
) -> Result<Self, String> {
let secretstore = SecretStore::new_from_payload(secret_payload, encryption_password)?;
Self::new_from_secretstore(secretstore, seed_passphrase)
}
fn new_from_secretstore(
secretstore: SecretStore,
seed_passphrase: Option<&str>,
) -> Result<Self, String> {
let nonsecret_data = secretstore.nonsecret_data();
if nonsecret_data.len() != NONSECRET_DATA_LEN {
return Err(format!(
"Nonsecret data len should be {} (was {})",
NONSECRET_DATA_LEN,
nonsecret_data.len()
));
}
debug_assert_eq!(nonsecret_data.len(), NONSECRET_DATA_LEN);
let scrambled_optional_passphrase = match seed_passphrase {
None => Vec::new(),
Some(passphrase) => {
let mut bytes = passphrase.as_bytes().to_vec();
let _res = secretstore.scramble_data(&mut bytes)?;
bytes
}
};
Ok(SeedStore {
secretstore,
scrambled_optional_passphrase,
secp: Secp256k1::new(),
})
}
pub(crate) fn write_to_file(
&self,
path_for_secret_file: &str,
encryption_password: &str,
) -> Result<(), String> {
SecretStoreCreator::write_to_file(
&self.secretstore,
path_for_secret_file,
encryption_password,
)
}
pub fn network(&self) -> Network {
Self::network_byte_to_enum(self.network_as_u8())
}
pub fn network_as_u8(&self) -> u8 {
let nonsecret_data = self.secretstore.nonsecret_data();
debug_assert_eq!(nonsecret_data.len(), NONSECRET_DATA_LEN);
nonsecret_data[0]
}
pub fn network_byte_to_enum(network: u8) -> Network {
match network {
0 => Network::Bitcoin,
1 => Network::Testnet,
2 => Network::Testnet4,
3 => Network::Signet,
4 => Network::Regtest,
_ => Network::Bitcoin,
}
}
pub fn network_enum_as_byte(network_enum: Network) -> u8 {
match network_enum {
Network::Bitcoin => 0,
Network::Testnet => 1,
Network::Testnet4 => 2,
Network::Signet => 3,
Network::Regtest => 4,
_ => 0,
}
}
pub fn get_xpub(&self) -> Result<Xpub, String> {
let xpub = self
.secretstore
.processed_secret_data(|entropy| self.xpub3_from_entropy(entropy))?;
Ok(xpub)
}
pub fn get_child_address(&self, child_specifier: &ChildSpecifier) -> Result<String, String> {
let derivation = child_specifier.derivation_path(self.network())?;
let pubkey = self
.secretstore
.processed_secret_data(|entropy| self.get_child_address_intern(entropy, &derivation))?;
Ok(pubkey)
}
pub fn get_child_public_key(
&self,
child_specifier: &ChildSpecifier,
) -> Result<PublicKey, String> {
let derivation = child_specifier.derivation_path(self.network())?;
let pubkey = self.secretstore.processed_secret_data(|entropy| {
self.get_child_public_key_intern(entropy, &derivation)
})?;
Ok(pubkey)
}
#[cfg(feature = "accesssecret")]
pub fn get_secret_child_private_key(
&self,
child_specifier: &ChildSpecifier,
) -> Result<SecretKey, String> {
self.get_secret_child_private_key_nonpub(child_specifier)
}
fn get_secret_child_private_key_nonpub(
&self,
child_specifier: &ChildSpecifier,
) -> Result<SecretKey, String> {
let derivation = child_specifier.derivation_path(self.network())?;
let privkey = self.secretstore.processed_secret_data(|entropy| {
self.get_secret_child_private_key_intern(entropy, &derivation)
})?;
Ok(privkey)
}
#[cfg(feature = "accesssecret")]
pub fn get_secret_mnemonic(&self) -> Result<String, String> {
let mnemonic = self
.secretstore
.processed_secret_data(|entropy| self.get_secret_mnemonic_intern(entropy))?;
Ok(mnemonic)
}
fn seed_from_entropy(&self, entropy: &Vec<u8>) -> Result<[u8; 64], String> {
let mut mnemo = Mnemonic::from_entropy(entropy)
.map_err(|e| format!("Invalid entropy, {} {}", entropy.len(), e.to_string()))?;
let mut passphrase = self.unscambled_passphrase()?;
let seed = mnemo.to_seed_normalized(&passphrase);
mnemo.zeroize();
drop(mnemo);
passphrase.zeroize();
drop(passphrase);
Ok(seed)
}
fn unscambled_passphrase(&self) -> Result<String, String> {
if self.scrambled_optional_passphrase.is_empty() {
return Ok(String::new());
}
let mut bytes = self.scrambled_optional_passphrase.clone();
let _res = self.secretstore.descramble_data(&mut bytes)?;
let passphrase = String::from_utf8(bytes)
.map_err(|e| format!("Passphrase processing error {}", e.to_string()))?;
Ok(passphrase)
}
fn xpriv3_from_entropy(&self, entropy: &Vec<u8>) -> Result<Xpriv, String> {
let mut seed = self.seed_from_entropy(entropy)?;
let xpriv = Xpriv::new_master(<Network as Into<NetworkKind>>::into(self.network()), &seed)
.map_err(|e| format!("Internal XPriv derivation error {}", e))?;
let derivation = ChildSpecifier::default_account_derivation_path3(self.network());
let derivation_path_3 = DerivationPath::from_str(&derivation)
.map_err(|e| format!("Internal derivation conversion error {}", e))?;
let xpriv_level_3 = xpriv
.derive_priv(&self.secp, &derivation_path_3)
.map_err(|e| format!("Internal XPriv derivation error {}", e))?;
seed.zeroize();
Ok(xpriv_level_3)
}
fn xpub3_from_entropy(&self, entropy: &Vec<u8>) -> Result<Xpub, String> {
let xpriv_level_3 = self.xpriv3_from_entropy(entropy)?;
let xpub_level_3 = Xpub::from_priv(&self.secp, &xpriv_level_3);
Ok(xpub_level_3)
}
fn get_secret_child_keypair_intern(
&self,
entropy: &Vec<u8>,
derivation: &DerivationPath,
) -> Result<Keypair, String> {
let mut seed = self.seed_from_entropy(entropy)?;
let xpriv = Xpriv::new_master(<Network as Into<NetworkKind>>::into(self.network()), &seed)
.map_err(|e| format!("Internal XPriv derivation error {}", e))?;
let child_xpriv = xpriv
.derive_priv(&self.secp, &derivation)
.map_err(|e| format!("Internal XPriv derivation error {}", e))?;
let keypair = child_xpriv.to_keypair(&self.secp);
seed.zeroize();
Ok(keypair)
}
fn get_child_address_intern(
&self,
entropy: &Vec<u8>,
derivation: &DerivationPath,
) -> Result<String, String> {
let public_key = self.get_child_public_key_intern(entropy, derivation)?;
let address = Address::p2wpkh(&CompressedPublicKey(public_key), self.network());
Ok(address.to_string())
}
fn get_child_public_key_intern(
&self,
entropy: &Vec<u8>,
derivation: &DerivationPath,
) -> Result<PublicKey, String> {
let public_key = self
.get_secret_child_keypair_intern(entropy, derivation)?
.public_key();
Ok(public_key)
}
fn get_secret_child_private_key_intern(
&self,
entropy: &Vec<u8>,
derivation: &DerivationPath,
) -> Result<SecretKey, String> {
let secret_key = self
.get_secret_child_keypair_intern(entropy, derivation)?
.secret_key();
Ok(secret_key)
}
#[cfg(feature = "accesssecret")]
fn get_secret_mnemonic_intern(&self, entropy: &Vec<u8>) -> Result<String, String> {
let mnemonic = Mnemonic::from_entropy(entropy).map_err(|e| e.to_string())?;
Ok(mnemonic.to_string())
}
pub fn sign_hash_with_child_private_key_ecdsa(
&self,
child_specifier: &ChildSpecifier,
hash: &[u8; 32],
signer_public_key: &PublicKey,
) -> Result<Signature, String> {
let private_key = self.get_secret_child_private_key_nonpub(child_specifier)?;
let public_key = private_key.public_key(&self.secp);
if *signer_public_key != public_key {
return Err(format!(
"Public key mismatch, {} vs {}",
signer_public_key.to_string(),
public_key.to_string()
));
}
let msg = Message::from_digest_slice(hash)
.map_err(|e| format!("Hash digest processing error {}", e.to_string()))?;
let signature = self.secp.sign_ecdsa(&msg, &private_key);
let _ = private_key;
Ok(signature)
}
}
impl Zeroize for SeedStore {
fn zeroize(&mut self) {
self.secretstore.zeroize();
self.scrambled_optional_passphrase.zeroize();
self.secp = Secp256k1::new();
}
}
impl ZeroizeOnDrop for SeedStore {}
impl SeedStoreCreator {
pub fn new_from_data(
entropy: &Vec<u8>,
network: Option<Network>,
seed_passphrase: Option<&str>,
) -> Result<SeedStore, String> {
let _res = Self::verify_entropy_length(entropy)?;
let network_byte = SeedStore::network_enum_as_byte(network.unwrap_or(Network::Bitcoin));
let nonsecret_data = vec![network_byte, 42, 43, 44];
debug_assert_eq!(nonsecret_data.len(), NONSECRET_DATA_LEN);
let secretstore = SecretStoreCreator::new_from_data(nonsecret_data, entropy)?;
SeedStore::new_from_secretstore(secretstore, seed_passphrase)
}
fn verify_entropy_length(entropy: &Vec<u8>) -> Result<(), String> {
let len = entropy.len();
if len == 16 || len == 20 || len == 24 || len == 28 || len == 32 {
Ok(())
} else {
Err(format!("Invalid entropy length {}", len))
}
}
pub fn write_to_file(
seedstore: &SeedStore,
path_for_secret_file: &str,
encryption_password: &str,
) -> Result<(), String> {
seedstore.write_to_file(path_for_secret_file, encryption_password)
}
}
impl ChildSpecifier {
pub fn derivation_path(&self, network: Network) -> Result<DerivationPath, String> {
let derivation_str = match &self {
Self::Derivation(derivation_str) => derivation_str.clone(),
Self::ChangeAndIndex34(i3, i4) => format!(
"{}/{}/{}",
Self::default_account_derivation_path3(network),
i3,
i4
),
Self::Index4(i4) => format!(
"{}/0/{}",
Self::default_account_derivation_path3(network),
i4
),
};
let derivation = DerivationPath::from_str(&derivation_str).map_err(|e| {
format!(
"Derivation parsing error {} {}",
derivation_str,
e.to_string()
)
})?;
Ok(derivation)
}
fn default_account_derivation_path3(network: Network) -> String {
match network {
Network::Bitcoin => "m/84'/0'/0'".to_string(),
_ => "m/84'/1'/0'".to_string(),
}
}
}
#[allow(unused)]
fn process_compute_checksum(entropy: &Vec<u8>) -> Result<u8, String> {
let entropy_checksum_computed = checksum_of_entropy(&entropy)?;
Ok(entropy_checksum_computed)
}
#[allow(unused)]
fn checksum_of_entropy(entropy: &Vec<u8>) -> Result<u8, String> {
let mnemo = Mnemonic::from_entropy(entropy)
.map_err(|e| format!("Could not process entropy {}", e.to_string()))?;
let checksum = mnemo.checksum();
Ok(checksum)
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use bitcoin::bip32::DerivationPath;
use bitcoin::Network;
use super::ChildSpecifier;
#[test]
fn test_default_path() {
assert_eq!(
ChildSpecifier::default_account_derivation_path3(Network::Bitcoin),
"m/84'/0'/0'"
);
assert_eq!(
ChildSpecifier::default_account_derivation_path3(Network::Signet),
"m/84'/1'/0'"
);
assert_eq!(
ChildSpecifier::default_account_derivation_path3(Network::Testnet4),
"m/84'/1'/0'"
);
}
#[test]
fn test_derivation() {
assert_eq!(
ChildSpecifier::Derivation("m/49'/1'/2'/3/4".to_owned())
.derivation_path(Network::Bitcoin)
.unwrap(),
DerivationPath::from_str("m/49'/1'/2'/3/4").unwrap()
);
assert_eq!(
ChildSpecifier::ChangeAndIndex34(1, 4)
.derivation_path(Network::Bitcoin)
.unwrap(),
DerivationPath::from_str("m/84'/0'/0'/1/4").unwrap()
);
assert_eq!(
ChildSpecifier::ChangeAndIndex34(1, 4)
.derivation_path(Network::Testnet)
.unwrap(),
DerivationPath::from_str("m/84'/1'/0'/1/4").unwrap()
);
assert_eq!(
ChildSpecifier::Index4(66)
.derivation_path(Network::Bitcoin)
.unwrap(),
DerivationPath::from_str("m/84'/0'/0'/0/66").unwrap()
);
}
#[test]
fn neg_test_invalid_derivation() {
assert_eq!(
ChildSpecifier::Derivation("what//deriv/j9".to_owned())
.derivation_path(Network::Bitcoin)
.err()
.unwrap(),
"Derivation parsing error what//deriv/j9 invalid child number format"
);
}
}