1use super::crypto::Crypto;
22use crate::{account::Version, json, Error};
23use cfx_crypto::crypto::ecdh::agree;
24use cfx_types::address_util::AddressUtil;
25use cfxkey::{
26 self, sign, Address, KeyPair, Message, Password, Public, Secret, Signature,
27};
28use log::warn;
29
30#[derive(Debug, PartialEq, Clone)]
32pub struct SafeAccount {
33 pub id: [u8; 16],
35 pub version: Version,
37 pub address: Address,
39 pub crypto: Crypto,
41 pub filename: Option<String>,
43 pub name: String,
45 pub meta: String,
47}
48
49impl From<SafeAccount> for json::KeyFile {
50 fn from(val: SafeAccount) -> Self {
51 json::KeyFile {
52 id: From::from(val.id),
53 version: val.version.into(),
54 address: Some(val.address.into()),
55 crypto: val.crypto.into(),
56 name: Some(val.name),
57 meta: Some(val.meta),
58 }
59 }
60}
61
62impl SafeAccount {
63 pub fn create(
65 keypair: &KeyPair, id: [u8; 16], password: &Password, iterations: u32,
66 name: String, meta: String,
67 ) -> Result<Self, Error> {
68 Ok(SafeAccount {
69 id,
70 version: Version::V3,
71 crypto: Crypto::with_secret(
72 keypair.secret(),
73 password,
74 iterations,
75 )?,
76 address: keypair.address(),
77 filename: None,
78 name,
79 meta,
80 })
81 }
82
83 pub fn from_file(
91 json: json::KeyFile, filename: Option<String>,
92 password: &Option<Password>,
93 ) -> Result<Self, Error> {
94 let crypto = Crypto::from(json.crypto);
95 let address = match (password, &json.address) {
96 (None, Some(json_address)) => json_address.into(),
97 (None, None) => return Err(Error::Custom(
98 "This keystore does not contain address. You need to provide password to import it".into())),
99 (Some(password), json_address) => {
100 let derived_address = KeyPair::from_secret(
101 crypto.secret(password).map_err(|_| Error::InvalidPassword)?
102 )?.address();
103
104 if let Some(json_address) = json_address {
105 let json_address = json_address.into();
106 if derived_address != json_address {
107 warn!("Detected address mismatch when opening an account. Derived: {:?}, in json got: {:?}. Are you trying to import an Ethkey for Conflux? Note that the address scheme between Ethereum and Conflux are different.", derived_address, json_address);
108 return Err(Error::Custom(format!("Address mismatch. Derived: {:?}, in json got: {:?}.", derived_address, json_address)));
109 }
110 }
111 derived_address
112 }
113 };
114
115 Ok(SafeAccount {
116 id: json.id.into(),
117 version: json.version.into(),
118 address,
119 crypto,
120 filename,
121 name: json.name.unwrap_or_default(),
122 meta: json.meta.unwrap_or("{}".to_owned()),
123 })
124 }
125
126 pub fn from_vault_file(
130 password: &Password, json: json::VaultKeyFile, filename: Option<String>,
131 ) -> Result<Self, Error> {
132 let meta_crypto: Crypto = json.metacrypto.into();
133 let meta_plain = meta_crypto.decrypt(password)?;
134 let meta_plain = json::VaultKeyMeta::load(&meta_plain)
135 .map_err(|e| Error::Custom(format!("{:?}", e)))?;
136
137 if !Address::from_slice(&meta_plain.address).is_user_account_address() {
138 warn!("Trying to import a non-user type account address. Are you trying to import an Ethkey for Conflux? Note that the address scheme between Ethereum and Conflux are different.");
139 return Err(Error::Custom(format!(
140 "Import non-user type address. Address: {:?}",
141 meta_plain.address
142 )));
143 }
144
145 SafeAccount::from_file(
146 json::KeyFile {
147 id: json.id,
148 version: json.version,
149 crypto: json.crypto,
150 address: Some(meta_plain.address),
151 name: meta_plain.name,
152 meta: meta_plain.meta,
153 },
154 filename,
155 &None,
156 )
157 }
158
159 pub fn into_vault_file(
161 self, iterations: u32, password: &Password,
162 ) -> Result<json::VaultKeyFile, Error> {
163 let meta_plain = json::VaultKeyMeta {
164 address: self.address.into(),
165 name: Some(self.name),
166 meta: Some(self.meta),
167 };
168 let meta_plain = meta_plain
169 .write()
170 .map_err(|e| Error::Custom(format!("{:?}", e)))?;
171 let meta_crypto =
172 Crypto::with_plain(&meta_plain, password, iterations)?;
173
174 Ok(json::VaultKeyFile {
175 id: self.id.into(),
176 version: self.version.into(),
177 crypto: self.crypto.into(),
178 metacrypto: meta_crypto.into(),
179 })
180 }
181
182 pub fn sign(
184 &self, password: &Password, message: &Message,
185 ) -> Result<Signature, Error> {
186 let secret = self.crypto.secret(password)?;
187 sign(&secret, message).map_err(From::from)
188 }
189
190 pub fn decrypt(
192 &self, password: &Password, shared_mac: &[u8], message: &[u8],
193 ) -> Result<Vec<u8>, Error> {
194 let secret = self.crypto.secret(password)?;
195 cfx_crypto::crypto::ecies::decrypt(&secret, shared_mac, message)
196 .map_err(From::from)
197 }
198
199 pub fn agree(
201 &self, password: &Password, other: &Public,
202 ) -> Result<Secret, Error> {
203 let secret = self.crypto.secret(password)?;
204 agree(&secret, other).map_err(From::from)
205 }
206
207 pub fn public(&self, password: &Password) -> Result<Public, Error> {
209 let secret = self.crypto.secret(password)?;
210 Ok(*KeyPair::from_secret(secret)?.public())
211 }
212
213 pub fn change_password(
215 &self, old_password: &Password, new_password: &Password,
216 iterations: u32,
217 ) -> Result<Self, Error> {
218 let secret = self.crypto.secret(old_password)?;
219 let result = SafeAccount {
220 id: self.id,
221 version: self.version.clone(),
222 crypto: Crypto::with_secret(&secret, new_password, iterations)?,
223 address: self.address,
224 filename: self.filename.clone(),
225 name: self.name.clone(),
226 meta: self.meta.clone(),
227 };
228 Ok(result)
229 }
230
231 pub fn check_password(&self, password: &Password) -> bool {
233 self.crypto.secret(password).is_ok()
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use super::SafeAccount;
240 use cfxkey::{verify_public, Generator, Message, Random};
241
242 #[test]
243 fn sign_and_verify_public() {
244 let keypair = Random.generate().unwrap();
245 let password = "hello world".into();
246 let message = Message::default();
247 let account = SafeAccount::create(
248 &keypair,
249 [0u8; 16],
250 &password,
251 10240,
252 "Test".to_owned(),
253 "{}".to_owned(),
254 );
255 let signature = account.unwrap().sign(&password, &message).unwrap();
256 assert!(verify_public(keypair.public(), &signature, &message).unwrap());
257 }
258
259 #[test]
260 fn change_password() {
261 let keypair = Random.generate().unwrap();
262 let first_password = "hello world".into();
263 let sec_password = "this is sparta".into();
264 let i = 10240;
265 let message = Message::default();
266 let account = SafeAccount::create(
267 &keypair,
268 [0u8; 16],
269 &first_password,
270 i,
271 "Test".to_owned(),
272 "{}".to_owned(),
273 )
274 .unwrap();
275 let new_account = account
276 .change_password(&first_password, &sec_password, i)
277 .unwrap();
278 assert!(account.sign(&first_password, &message).is_ok());
279 assert!(account.sign(&sec_password, &message).is_err());
280 assert!(new_account.sign(&first_password, &message).is_err());
281 assert!(new_account.sign(&sec_password, &message).is_ok());
282 }
283}