cfxstore/account/
safe_account.rs

1// Copyright 2019 Conflux Foundation. All rights reserved.
2// Conflux is free software and distributed under GNU General Public License.
3// See http://www.gnu.org/licenses/
4
5// Copyright 2015-2019 Parity Technologies (UK) Ltd.
6// This file is part of Parity Ethereum.
7
8// Parity Ethereum is free software: you can redistribute it and/or modify
9// it under the terms of the GNU General Public License as published by
10// the Free Software Foundation, either version 3 of the License, or
11// (at your option) any later version.
12
13// Parity Ethereum is distributed in the hope that it will be useful,
14// but WITHOUT ANY WARRANTY; without even the implied warranty of
15// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16// GNU General Public License for more details.
17
18// You should have received a copy of the GNU General Public License
19// along with Parity Ethereum.  If not, see <http://www.gnu.org/licenses/>.
20
21use 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/// Account representation.
31#[derive(Debug, PartialEq, Clone)]
32pub struct SafeAccount {
33    /// Account ID
34    pub id: [u8; 16],
35    /// Account version
36    pub version: Version,
37    /// Account address
38    pub address: Address,
39    /// Account private key derivation definition.
40    pub crypto: Crypto,
41    /// Account filename
42    pub filename: Option<String>,
43    /// Account name
44    pub name: String,
45    /// Account metadata
46    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    /// Create a new account
64    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    /// Create a new `SafeAccount` from the given `json`; if it was read from a
84    /// file, the `filename` should be `Some` name. If it is as yet anonymous,
85    /// then it can be left `None`.
86    /// In case `password` is provided, we will attempt to read the secret from
87    /// the keyfile and derive the address from it instead of reading it
88    /// directly. Providing password is required for `json::KeyFile`s with
89    /// no address.
90    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    /// Create a new `SafeAccount` from the given vault `json`; if it was read
127    /// from a file, the `filename` should be `Some` name. If it is as yet
128    /// anonymous, then it can be left `None`.
129    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    /// Create a new `VaultKeyFile` from the given `self`
160    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    /// Sign a message.
183    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    /// Decrypt a message.
191    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    /// Agree on shared key.
200    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    /// Derive public key.
208    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    /// Change account's password.
214    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    /// Check if password matches the account.
232    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}