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 Into<json::KeyFile> for SafeAccount {
50 fn into(self) -> json::KeyFile {
51 json::KeyFile {
52 id: From::from(self.id),
53 version: self.version.into(),
54 address: Some(self.address.into()),
55 crypto: self.crypto.into(),
56 name: Some(self.name),
57 meta: Some(self.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 match json_address {
105 Some(json_address) => {
106 let json_address = json_address.into();
107 if derived_address != json_address {
108 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);
109 return Err(Error::Custom(format!("Address mismatch. Derived: {:?}, in json got: {:?}.", derived_address, json_address)));
110 }
111 },
112 _ => {},
113 }
114 derived_address
115 }
116 };
117
118 Ok(SafeAccount {
119 id: json.id.into(),
120 version: json.version.into(),
121 address,
122 crypto,
123 filename,
124 name: json.name.unwrap_or_default(),
125 meta: json.meta.unwrap_or("{}".to_owned()),
126 })
127 }
128
129 pub fn from_vault_file(
133 password: &Password, json: json::VaultKeyFile, filename: Option<String>,
134 ) -> Result<Self, Error> {
135 let meta_crypto: Crypto = json.metacrypto.into();
136 let meta_plain = meta_crypto.decrypt(password)?;
137 let meta_plain = json::VaultKeyMeta::load(&meta_plain)
138 .map_err(|e| Error::Custom(format!("{:?}", e)))?;
139
140 if !Address::from_slice(&meta_plain.address).is_user_account_address() {
141 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.");
142 return Err(Error::Custom(format!(
143 "Import non-user type address. Address: {:?}",
144 meta_plain.address
145 )));
146 }
147
148 SafeAccount::from_file(
149 json::KeyFile {
150 id: json.id,
151 version: json.version,
152 crypto: json.crypto,
153 address: Some(meta_plain.address),
154 name: meta_plain.name,
155 meta: meta_plain.meta,
156 },
157 filename,
158 &None,
159 )
160 }
161
162 pub fn into_vault_file(
164 self, iterations: u32, password: &Password,
165 ) -> Result<json::VaultKeyFile, Error> {
166 let meta_plain = json::VaultKeyMeta {
167 address: self.address.into(),
168 name: Some(self.name),
169 meta: Some(self.meta),
170 };
171 let meta_plain = meta_plain
172 .write()
173 .map_err(|e| Error::Custom(format!("{:?}", e)))?;
174 let meta_crypto =
175 Crypto::with_plain(&meta_plain, password, iterations)?;
176
177 Ok(json::VaultKeyFile {
178 id: self.id.into(),
179 version: self.version.into(),
180 crypto: self.crypto.into(),
181 metacrypto: meta_crypto.into(),
182 })
183 }
184
185 pub fn sign(
187 &self, password: &Password, message: &Message,
188 ) -> Result<Signature, Error> {
189 let secret = self.crypto.secret(password)?;
190 sign(&secret, message).map_err(From::from)
191 }
192
193 pub fn decrypt(
195 &self, password: &Password, shared_mac: &[u8], message: &[u8],
196 ) -> Result<Vec<u8>, Error> {
197 let secret = self.crypto.secret(password)?;
198 cfx_crypto::crypto::ecies::decrypt(&secret, shared_mac, message)
199 .map_err(From::from)
200 }
201
202 pub fn agree(
204 &self, password: &Password, other: &Public,
205 ) -> Result<Secret, Error> {
206 let secret = self.crypto.secret(password)?;
207 agree(&secret, other).map_err(From::from)
208 }
209
210 pub fn public(&self, password: &Password) -> Result<Public, Error> {
212 let secret = self.crypto.secret(password)?;
213 Ok(KeyPair::from_secret(secret)?.public().clone())
214 }
215
216 pub fn change_password(
218 &self, old_password: &Password, new_password: &Password,
219 iterations: u32,
220 ) -> Result<Self, Error> {
221 let secret = self.crypto.secret(old_password)?;
222 let result = SafeAccount {
223 id: self.id.clone(),
224 version: self.version.clone(),
225 crypto: Crypto::with_secret(&secret, new_password, iterations)?,
226 address: self.address.clone(),
227 filename: self.filename.clone(),
228 name: self.name.clone(),
229 meta: self.meta.clone(),
230 };
231 Ok(result)
232 }
233
234 pub fn check_password(&self, password: &Password) -> bool {
236 self.crypto.secret(password).is_ok()
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use super::SafeAccount;
243 use cfxkey::{verify_public, Generator, Message, Random};
244
245 #[test]
246 fn sign_and_verify_public() {
247 let keypair = Random.generate().unwrap();
248 let password = "hello world".into();
249 let message = Message::default();
250 let account = SafeAccount::create(
251 &keypair,
252 [0u8; 16],
253 &password,
254 10240,
255 "Test".to_owned(),
256 "{}".to_owned(),
257 );
258 let signature = account.unwrap().sign(&password, &message).unwrap();
259 assert!(verify_public(keypair.public(), &signature, &message).unwrap());
260 }
261
262 #[test]
263 fn change_password() {
264 let keypair = Random.generate().unwrap();
265 let first_password = "hello world".into();
266 let sec_password = "this is sparta".into();
267 let i = 10240;
268 let message = Message::default();
269 let account = SafeAccount::create(
270 &keypair,
271 [0u8; 16],
272 &first_password,
273 i,
274 "Test".to_owned(),
275 "{}".to_owned(),
276 )
277 .unwrap();
278 let new_account = account
279 .change_password(&first_password, &sec_password, i)
280 .unwrap();
281 assert!(account.sign(&first_password, &message).is_ok());
282 assert!(account.sign(&sec_password, &message).is_err());
283 assert!(new_account.sign(&first_password, &message).is_err());
284 assert!(new_account.sign(&sec_password, &message).is_ok());
285 }
286}