* Upgrade Eth types ethereum-types -> 0.9.2 rlp -> 0.4.6 keccak-hash -> 0.5.0 parity-crypto -> 0.6.2 ethabi -> 0.12.0 ethabi-derive -> 0.12.0 ethabi-contract -> 0.11.0 ethbloom -> 0.9.1 rand -> 0.7.3 trie-standardmap -> 0.15.2 triehash -> 0.5.0 * backport #10714. Small changes, merge fixes Co-authored-by: mdben1247 <mdben1247@users.noreply.github.com>
288 lines
9.4 KiB
Rust
288 lines
9.4 KiB
Rust
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
|
// This file is part of OpenEthereum.
|
|
|
|
// OpenEthereum is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
|
|
// OpenEthereum is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
use super::crypto::Crypto;
|
|
use account::Version;
|
|
use crypto::{
|
|
self,
|
|
publickey::{ecdh::agree, sign, Address, KeyPair, Message, Public, Secret, Signature},
|
|
};
|
|
use ethkey::Password;
|
|
use json;
|
|
use std::num::NonZeroU32;
|
|
use Error;
|
|
|
|
/// Account representation.
|
|
#[derive(Debug, PartialEq, Clone)]
|
|
pub struct SafeAccount {
|
|
/// Account ID
|
|
pub id: [u8; 16],
|
|
/// Account version
|
|
pub version: Version,
|
|
/// Account address
|
|
pub address: Address,
|
|
/// Account private key derivation definition.
|
|
pub crypto: Crypto,
|
|
/// Account filename
|
|
pub filename: Option<String>,
|
|
/// Account name
|
|
pub name: String,
|
|
/// Account metadata
|
|
pub meta: String,
|
|
}
|
|
|
|
impl Into<json::KeyFile> for SafeAccount {
|
|
fn into(self) -> json::KeyFile {
|
|
json::KeyFile {
|
|
id: From::from(self.id),
|
|
version: self.version.into(),
|
|
address: Some(self.address.into()),
|
|
crypto: self.crypto.into(),
|
|
name: Some(self.name.into()),
|
|
meta: Some(self.meta.into()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl SafeAccount {
|
|
/// Create a new account
|
|
pub fn create(
|
|
keypair: &KeyPair,
|
|
id: [u8; 16],
|
|
password: &Password,
|
|
iterations: NonZeroU32,
|
|
name: String,
|
|
meta: String,
|
|
) -> Result<Self, crypto::Error> {
|
|
Ok(SafeAccount {
|
|
id: id,
|
|
version: Version::V3,
|
|
crypto: Crypto::with_secret(keypair.secret(), password, iterations)?,
|
|
address: keypair.address(),
|
|
filename: None,
|
|
name: name,
|
|
meta: meta,
|
|
})
|
|
}
|
|
|
|
/// Create a new `SafeAccount` from the given `json`; if it was read from a
|
|
/// file, the `filename` should be `Some` name. If it is as yet anonymous, then it
|
|
/// can be left `None`.
|
|
/// In case `password` is provided, we will attempt to read the secret from the keyfile
|
|
/// and derive the address from it instead of reading it directly.
|
|
/// Providing password is required for `json::KeyFile`s with no address.
|
|
pub fn from_file(
|
|
json: json::KeyFile,
|
|
filename: Option<String>,
|
|
password: &Option<Password>,
|
|
) -> Result<Self, Error> {
|
|
let crypto = Crypto::from(json.crypto);
|
|
let address = match (password, &json.address) {
|
|
(None, Some(json_address)) => json_address.into(),
|
|
(None, None) => Err(Error::Custom(
|
|
"This keystore does not contain address. You need to provide password to import it"
|
|
.into(),
|
|
))?,
|
|
(Some(password), json_address) => {
|
|
let derived_address = KeyPair::from_secret(
|
|
crypto
|
|
.secret(&password)
|
|
.map_err(|_| Error::InvalidPassword)?,
|
|
)?
|
|
.address();
|
|
|
|
match json_address {
|
|
Some(json_address) => {
|
|
let json_address = json_address.into();
|
|
if derived_address != json_address {
|
|
warn!("Detected address mismatch when opening an account. Derived: {:?}, in json got: {:?}",
|
|
derived_address, json_address);
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
derived_address
|
|
}
|
|
};
|
|
|
|
Ok(SafeAccount {
|
|
id: json.id.into(),
|
|
version: json.version.into(),
|
|
address,
|
|
crypto,
|
|
filename,
|
|
name: json.name.unwrap_or(String::new()),
|
|
meta: json.meta.unwrap_or("{}".to_owned()),
|
|
})
|
|
}
|
|
|
|
/// Create a new `SafeAccount` from the given vault `json`; if it was read from a
|
|
/// file, the `filename` should be `Some` name. If it is as yet anonymous, then it
|
|
/// can be left `None`.
|
|
pub fn from_vault_file(
|
|
password: &Password,
|
|
json: json::VaultKeyFile,
|
|
filename: Option<String>,
|
|
) -> Result<Self, Error> {
|
|
let meta_crypto: Crypto = json.metacrypto.into();
|
|
let meta_plain = meta_crypto.decrypt(password)?;
|
|
let meta_plain =
|
|
json::VaultKeyMeta::load(&meta_plain).map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
|
|
|
SafeAccount::from_file(
|
|
json::KeyFile {
|
|
id: json.id,
|
|
version: json.version,
|
|
crypto: json.crypto,
|
|
address: Some(meta_plain.address),
|
|
name: meta_plain.name,
|
|
meta: meta_plain.meta,
|
|
},
|
|
filename,
|
|
&None,
|
|
)
|
|
}
|
|
|
|
/// Create a new `VaultKeyFile` from the given `self`
|
|
pub fn into_vault_file(
|
|
self,
|
|
iterations: NonZeroU32,
|
|
password: &Password,
|
|
) -> Result<json::VaultKeyFile, Error> {
|
|
let meta_plain = json::VaultKeyMeta {
|
|
address: self.address.into(),
|
|
name: Some(self.name),
|
|
meta: Some(self.meta),
|
|
};
|
|
let meta_plain = meta_plain
|
|
.write()
|
|
.map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
|
let meta_crypto = Crypto::with_plain(&meta_plain, password, iterations)?;
|
|
|
|
Ok(json::VaultKeyFile {
|
|
id: self.id.into(),
|
|
version: self.version.into(),
|
|
crypto: self.crypto.into(),
|
|
metacrypto: meta_crypto.into(),
|
|
})
|
|
}
|
|
|
|
/// Sign a message.
|
|
pub fn sign(&self, password: &Password, message: &Message) -> Result<Signature, Error> {
|
|
let secret = self.crypto.secret(password)?;
|
|
sign(&secret, message).map_err(From::from)
|
|
}
|
|
|
|
/// Decrypt a message.
|
|
pub fn decrypt(
|
|
&self,
|
|
password: &Password,
|
|
shared_mac: &[u8],
|
|
message: &[u8],
|
|
) -> Result<Vec<u8>, Error> {
|
|
let secret = self.crypto.secret(password)?;
|
|
crypto::publickey::ecies::decrypt(&secret, shared_mac, message).map_err(From::from)
|
|
}
|
|
|
|
/// Agree on shared key.
|
|
pub fn agree(&self, password: &Password, other: &Public) -> Result<Secret, Error> {
|
|
let secret = self.crypto.secret(password)?;
|
|
agree(&secret, other).map_err(From::from)
|
|
}
|
|
|
|
/// Derive public key.
|
|
pub fn public(&self, password: &Password) -> Result<Public, Error> {
|
|
let secret = self.crypto.secret(password)?;
|
|
Ok(KeyPair::from_secret(secret)?.public().clone())
|
|
}
|
|
|
|
/// Change account's password.
|
|
pub fn change_password(
|
|
&self,
|
|
old_password: &Password,
|
|
new_password: &Password,
|
|
iterations: NonZeroU32,
|
|
) -> Result<Self, Error> {
|
|
let secret = self.crypto.secret(old_password)?;
|
|
let result = SafeAccount {
|
|
id: self.id.clone(),
|
|
version: self.version.clone(),
|
|
crypto: Crypto::with_secret(&secret, new_password, iterations)?,
|
|
address: self.address.clone(),
|
|
filename: self.filename.clone(),
|
|
name: self.name.clone(),
|
|
meta: self.meta.clone(),
|
|
};
|
|
Ok(result)
|
|
}
|
|
|
|
/// Check if password matches the account.
|
|
pub fn check_password(&self, password: &Password) -> bool {
|
|
self.crypto.secret(password).is_ok()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::{NonZeroU32, SafeAccount};
|
|
use crypto::publickey::{verify_public, Generator, Random};
|
|
|
|
lazy_static! {
|
|
static ref ITERATIONS: NonZeroU32 = NonZeroU32::new(10240).expect("10240 > 0; qed");
|
|
}
|
|
|
|
#[test]
|
|
fn sign_and_verify_public() {
|
|
let keypair = Random.generate();
|
|
let password = "hello world".into();
|
|
let message = [1u8; 32].into();
|
|
let account = SafeAccount::create(
|
|
&keypair,
|
|
[0u8; 16],
|
|
&password,
|
|
*ITERATIONS,
|
|
"Test".to_owned(),
|
|
"{}".to_owned(),
|
|
);
|
|
let signature = account.unwrap().sign(&password, &message).unwrap();
|
|
assert!(verify_public(keypair.public(), &signature, &message).unwrap());
|
|
}
|
|
|
|
#[test]
|
|
fn change_password() {
|
|
let keypair = Random.generate();
|
|
let first_password = "hello world".into();
|
|
let sec_password = "this is sparta".into();
|
|
let message = [1u8; 32].into();
|
|
let account = SafeAccount::create(
|
|
&keypair,
|
|
[0u8; 16],
|
|
&first_password,
|
|
*ITERATIONS,
|
|
"Test".to_owned(),
|
|
"{}".to_owned(),
|
|
)
|
|
.unwrap();
|
|
let new_account = account
|
|
.change_password(&first_password, &sec_password, *ITERATIONS)
|
|
.unwrap();
|
|
assert!(account.sign(&first_password, &message).is_ok());
|
|
assert!(account.sign(&sec_password, &message).is_err());
|
|
assert!(new_account.sign(&first_password, &message).is_err());
|
|
assert!(new_account.sign(&sec_password, &message).is_ok());
|
|
}
|
|
}
|