use std::{path::Path, str::FromStr};
use eth_keystore::{decrypt_key, encrypt_key};
use k256::ecdsa::Signature;
use crate::{
core::{PrivateKey, PublicKey, ZilAddress},
crypto::schnorr::sign,
Error,
};
use super::Signer;
#[derive(Debug, Clone, PartialEq)]
pub struct LocalWallet {
pub private_key: PrivateKey,
pub address: ZilAddress,
public_key: PublicKey,
}
impl LocalWallet {
pub fn create_random() -> Result<Self, Error> {
PrivateKey::create_random().try_into()
}
pub fn load_keystore(path: &Path, password: &str) -> Result<Self, Error> {
PrivateKey::from_slice(&decrypt_key(path, password).unwrap())?.try_into()
}
pub fn save_keystore(&self, path: &Path, password: &str) -> Result<String, Error> {
let mut rng = rand::thread_rng();
if path.is_dir() {
return Err(Error::IsADirectory);
}
let (dir, filename) = (
path.parent().ok_or(Error::FailedToGetTheParentDirectory)?,
path.file_name().map(|filename| filename.to_str().unwrap()),
);
Ok(encrypt_key(dir, &mut rng, self.private_key.to_bytes(), password, filename)?)
}
}
impl Signer for LocalWallet {
fn sign(&self, message: &[u8]) -> Signature {
sign(message, &self.private_key)
}
fn address(&self) -> &ZilAddress {
&self.address
}
fn public_key(&self) -> &PublicKey {
&self.public_key
}
}
impl FromStr for LocalWallet {
type Err = Error;
fn from_str(private_key: &str) -> Result<Self, Self::Err> {
private_key.parse::<PrivateKey>()?.try_into()
}
}
impl TryFrom<PrivateKey> for LocalWallet {
type Error = Error;
fn try_from(private_key: PrivateKey) -> Result<Self, Error> {
let address = ZilAddress::try_from(&private_key.public_key())?;
Ok(Self {
address,
public_key: private_key.public_key(),
private_key,
})
}
}
#[cfg(test)]
mod tests {
use std::env;
use claim::assert_some;
use crate::{core::ZilAddress, crypto::schnorr::verify, signers::Signer};
use super::LocalWallet;
#[test]
fn a_valid_private_key_should_results_a_valid_account_with_parse_function() {
let account: LocalWallet = "0xD96e9eb5b782a80ea153c937fa83e5948485fbfc8b7e7c069d7b914dbc350aba"
.parse()
.unwrap();
assert_eq!(
account.address(),
&"0x381f4008505e940AD7681EC3468a719060caF796".parse::<ZilAddress>().unwrap()
);
}
#[test]
fn a_valid_private_key_should_results_a_valid_account_with_new() {
let account: LocalWallet = "0xD96e9eb5b782a80ea153c937fa83e5948485fbfc8b7e7c069d7b914dbc350aba"
.parse()
.unwrap();
assert_eq!(
account.address(),
&"0x381f4008505e940AD7681EC3468a719060caF796".parse::<ZilAddress>().unwrap()
);
}
#[test]
fn sign_should_return_signature() {
let account: LocalWallet = "0xD96e9eb5b782a80ea153c937fa83e5948485fbfc8b7e7c069d7b914dbc350aba"
.parse()
.unwrap();
let signature = account.sign(&hex::decode("11223344aabb").unwrap());
println!("{} {}", signature.r().to_string(), signature.s().to_string());
assert_some!(verify(
&hex::decode("11223344aabb").unwrap(),
&account.public_key(),
&signature
));
}
#[test]
fn save_and_load_keystore_should_work_fine() {
let wallet = LocalWallet::create_random().unwrap();
let path = env::temp_dir().join("keystore.json");
let password = "qwerty";
wallet.save_keystore(&path, password).unwrap();
let wallet2 = LocalWallet::load_keystore(&path, password).unwrap();
assert_eq!(wallet.address, wallet2.address);
}
}