zilliqa_rs/signers/
local_wallet.rs1use std::{path::Path, str::FromStr};
2
3use eth_keystore::{decrypt_key, encrypt_key};
4use k256::ecdsa::Signature;
5
6use crate::{
7 core::{PrivateKey, PublicKey, ZilAddress},
8 crypto::schnorr::sign,
9 Error,
10};
11
12use super::Signer;
13
14#[derive(Debug, Clone, PartialEq)]
16pub struct LocalWallet {
17 pub private_key: PrivateKey,
19 pub address: ZilAddress,
21 public_key: PublicKey,
23}
24
25impl LocalWallet {
26 pub fn create_random() -> Result<Self, Error> {
35 PrivateKey::create_random().try_into()
36 }
37
38 pub fn load_keystore(path: &Path, password: &str) -> Result<Self, Error> {
47 PrivateKey::from_slice(&decrypt_key(path, password).unwrap())?.try_into()
48 }
49
50 pub fn save_keystore(&self, path: &Path, password: &str) -> Result<String, Error> {
63 let mut rng = rand::thread_rng();
64 if path.is_dir() {
65 return Err(Error::IsADirectory);
66 }
67
68 let (dir, filename) = (
69 path.parent().ok_or(Error::FailedToGetTheParentDirectory)?,
70 path.file_name().map(|filename| filename.to_str().unwrap()),
71 );
72 Ok(encrypt_key(dir, &mut rng, self.private_key.to_bytes(), password, filename)?)
73 }
74}
75
76impl Signer for LocalWallet {
77 fn sign(&self, message: &[u8]) -> Signature {
78 sign(message, &self.private_key)
79 }
80
81 fn address(&self) -> &ZilAddress {
82 &self.address
83 }
84
85 fn public_key(&self) -> &PublicKey {
86 &self.public_key
87 }
88}
89
90impl FromStr for LocalWallet {
91 type Err = Error;
92
93 fn from_str(private_key: &str) -> Result<Self, Self::Err> {
95 private_key.parse::<PrivateKey>()?.try_into()
96 }
97}
98
99impl TryFrom<PrivateKey> for LocalWallet {
100 type Error = Error;
101
102 fn try_from(private_key: PrivateKey) -> Result<Self, Error> {
113 let address = ZilAddress::try_from(&private_key.public_key())?;
114
115 Ok(Self {
116 address,
117 public_key: private_key.public_key(),
118 private_key,
119 })
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use std::env;
126
127 use claim::assert_some;
128
129 use crate::{core::ZilAddress, crypto::schnorr::verify, signers::Signer};
130
131 use super::LocalWallet;
132
133 #[test]
134 fn a_valid_private_key_should_results_a_valid_account_with_parse_function() {
135 let account: LocalWallet = "0xD96e9eb5b782a80ea153c937fa83e5948485fbfc8b7e7c069d7b914dbc350aba"
136 .parse()
137 .unwrap();
138 assert_eq!(
139 account.address(),
140 &"0x381f4008505e940AD7681EC3468a719060caF796".parse::<ZilAddress>().unwrap()
141 );
142 }
143
144 #[test]
145 fn a_valid_private_key_should_results_a_valid_account_with_new() {
146 let account: LocalWallet = "0xD96e9eb5b782a80ea153c937fa83e5948485fbfc8b7e7c069d7b914dbc350aba"
147 .parse()
148 .unwrap();
149 assert_eq!(
150 account.address(),
151 &"0x381f4008505e940AD7681EC3468a719060caF796".parse::<ZilAddress>().unwrap()
152 );
153 }
154
155 #[test]
156 fn sign_should_return_signature() {
157 let account: LocalWallet = "0xD96e9eb5b782a80ea153c937fa83e5948485fbfc8b7e7c069d7b914dbc350aba"
158 .parse()
159 .unwrap();
160
161 let signature = account.sign(&hex::decode("11223344aabb").unwrap());
162 println!("{} {}", signature.r().to_string(), signature.s().to_string());
163
164 assert_some!(verify(
165 &hex::decode("11223344aabb").unwrap(),
166 &account.public_key(),
167 &signature
168 ));
169 }
170
171 #[test]
172 fn save_and_load_keystore_should_work_fine() {
173 let wallet = LocalWallet::create_random().unwrap();
174 let path = env::temp_dir().join("keystore.json");
175 let password = "qwerty";
176 wallet.save_keystore(&path, password).unwrap();
177
178 let wallet2 = LocalWallet::load_keystore(&path, password).unwrap();
179 assert_eq!(wallet.address, wallet2.address);
180 }
181}