Merge branch 'master' into lightrpc
This commit is contained in:
		
						commit
						1fa5b07321
					
				
							
								
								
									
										23
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										23
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -162,7 +162,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "byteorder" | ||||
| version = "0.5.3" | ||||
| version = "1.0.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| 
 | ||||
| [[package]] | ||||
| @ -324,8 +324,8 @@ dependencies = [ | ||||
| 
 | ||||
| [[package]] | ||||
| name = "eth-secp256k1" | ||||
| version = "0.5.4" | ||||
| source = "git+https://github.com/ethcore/rust-secp256k1#a9a0b1be1f39560ca86e8fc8e55e205a753ff25c" | ||||
| version = "0.5.6" | ||||
| source = "git+https://github.com/ethcore/rust-secp256k1#edab95f5569e4fb97579dc8947be96e7ac789c16" | ||||
| dependencies = [ | ||||
|  "arrayvec 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| @ -363,7 +363,7 @@ version = "1.6.0" | ||||
| dependencies = [ | ||||
|  "bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "bloomchain 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| @ -668,7 +668,7 @@ dependencies = [ | ||||
|  "clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "elastic-array 0.6.0 (git+https://github.com/ethcore/elastic-array)", | ||||
|  "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "eth-secp256k1 0.5.4 (git+https://github.com/ethcore/rust-secp256k1)", | ||||
|  "eth-secp256k1 0.5.6 (git+https://github.com/ethcore/rust-secp256k1)", | ||||
|  "ethcore-bigint 0.1.2", | ||||
|  "ethcore-bloom-journal 0.1.0", | ||||
|  "ethcore-devtools 1.6.0", | ||||
| @ -699,7 +699,7 @@ dependencies = [ | ||||
| name = "ethcrypto" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "eth-secp256k1 0.5.4 (git+https://github.com/ethcore/rust-secp256k1)", | ||||
|  "eth-secp256k1 0.5.6 (git+https://github.com/ethcore/rust-secp256k1)", | ||||
|  "ethcore-bigint 0.1.2", | ||||
|  "ethkey 0.2.0", | ||||
|  "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| @ -721,11 +721,13 @@ dependencies = [ | ||||
| name = "ethkey" | ||||
| version = "0.2.0" | ||||
| dependencies = [ | ||||
|  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "eth-secp256k1 0.5.4 (git+https://github.com/ethcore/rust-secp256k1)", | ||||
|  "eth-secp256k1 0.5.6 (git+https://github.com/ethcore/rust-secp256k1)", | ||||
|  "ethcore-bigint 0.1.2", | ||||
|  "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "tiny-keccak 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| @ -735,6 +737,7 @@ name = "ethstore" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "ethcore-devtools 1.6.0", | ||||
|  "ethcore-util 1.6.0", | ||||
|  "ethcrypto 0.1.0", | ||||
|  "ethkey 0.2.0", | ||||
| @ -1572,7 +1575,7 @@ dependencies = [ | ||||
| [[package]] | ||||
| name = "parity-ui-precompiled" | ||||
| version = "1.4.0" | ||||
| source = "git+https://github.com/ethcore/js-precompiled.git#4110b5bc85a15ae3f0b5c02b1c3caf8423f51b50" | ||||
| source = "git+https://github.com/ethcore/js-precompiled.git#a590186c6acf75e31b7cff259721793960ded4e1" | ||||
| dependencies = [ | ||||
|  "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| @ -2491,7 +2494,7 @@ dependencies = [ | ||||
| "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" | ||||
| "checksum blastfig 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "09640e0509d97d5cdff03a9f5daf087a8e04c735c3b113a75139634a19cfc7b2" | ||||
| "checksum bloomchain 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3f421095d2a76fc24cd3fb3f912b90df06be7689912b1bdb423caefae59c258d" | ||||
| "checksum byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855" | ||||
| "checksum byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c40977b0ee6b9885c9013cd41d9feffdd22deb3bb4dc3a71d901cc7a77de18c8" | ||||
| "checksum bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c129aff112dcc562970abb69e2508b40850dd24c274761bb50fb8a0067ba6c27" | ||||
| "checksum bytes 0.4.0-dev (git+https://github.com/carllerche/bytes)" = "<none>" | ||||
| "checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c" | ||||
| @ -2512,7 +2515,7 @@ dependencies = [ | ||||
| "checksum either 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3d2b503c86dad62aaf414ecf2b8c527439abedb3f8d812537f0b12bfd6f32a91" | ||||
| "checksum elastic-array 0.6.0 (git+https://github.com/ethcore/elastic-array)" = "<none>" | ||||
| "checksum env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "aba65b63ffcc17ffacd6cf5aa843da7c5a25e3bd4bbe0b7def8b214e411250e5" | ||||
| "checksum eth-secp256k1 0.5.4 (git+https://github.com/ethcore/rust-secp256k1)" = "<none>" | ||||
| "checksum eth-secp256k1 0.5.6 (git+https://github.com/ethcore/rust-secp256k1)" = "<none>" | ||||
| "checksum ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f7b0c53453517f620847be51943db329276ae52f2e210cfc659e81182864be2f" | ||||
| "checksum fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b1ee15a7050e5580b3712877157068ea713b245b080ff302ae2ca973cfcd9baa" | ||||
| "checksum flate2 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "3eeb481e957304178d2e782f2da1257f1434dfecbae883bafb61ada2a9fea3bb" | ||||
|  | ||||
| @ -2,7 +2,7 @@ FROM ubuntu:14.04 | ||||
| WORKDIR /build | ||||
| # install tools and dependencies | ||||
| RUN apt-get update && \ | ||||
|         apt-get install --force-yes --no-install-recommends \ | ||||
|         apt-get install -y --force-yes --no-install-recommends \ | ||||
|         # make | ||||
|         build-essential \ | ||||
|         # add-apt-repository | ||||
| @ -56,7 +56,7 @@ g++ -v | ||||
| RUN git clone https://github.com/ethcore/parity && \ | ||||
|         cd parity && \ | ||||
|         git pull && \ | ||||
|         cargo build --release --features final --verbose && \ | ||||
|         cargo build --release --features final && \ | ||||
|         ls /build/parity/target/release/parity &&       \ | ||||
|         strip /build/parity/target/release/parity | ||||
| 
 | ||||
|  | ||||
| @ -24,7 +24,7 @@ semver = "0.5" | ||||
| bit-set = "0.4" | ||||
| time = "0.1" | ||||
| rand = "0.3" | ||||
| byteorder = "0.5" | ||||
| byteorder = "1.0" | ||||
| transient-hashmap = "0.1" | ||||
| linked-hash-map = "0.3.0" | ||||
| evmjit = { path = "../evmjit", optional = true } | ||||
|  | ||||
| @ -280,7 +280,7 @@ impl AccountProvider { | ||||
| 
 | ||||
| 	/// Returns each account along with name and meta.
 | ||||
| 	pub fn account_meta(&self, address: Address) -> Result<AccountMeta, Error> { | ||||
| 		let account = StoreAccountRef::root(address); | ||||
| 		let account = self.sstore.account_ref(&address)?; | ||||
| 		Ok(AccountMeta { | ||||
| 			name: self.sstore.name(&account)?, | ||||
| 			meta: self.sstore.meta(&account)?, | ||||
| @ -290,38 +290,38 @@ impl AccountProvider { | ||||
| 
 | ||||
| 	/// Returns each account along with name and meta.
 | ||||
| 	pub fn set_account_name(&self, address: Address, name: String) -> Result<(), Error> { | ||||
| 		self.sstore.set_name(&StoreAccountRef::root(address), name)?; | ||||
| 		self.sstore.set_name(&self.sstore.account_ref(&address)?, name)?; | ||||
| 		Ok(()) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Returns each account along with name and meta.
 | ||||
| 	pub fn set_account_meta(&self, address: Address, meta: String) -> Result<(), Error> { | ||||
| 		self.sstore.set_meta(&StoreAccountRef::root(address), meta)?; | ||||
| 		self.sstore.set_meta(&self.sstore.account_ref(&address)?, meta)?; | ||||
| 		Ok(()) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Returns `true` if the password for `account` is `password`. `false` if not.
 | ||||
| 	pub fn test_password(&self, address: &Address, password: &str) -> Result<bool, Error> { | ||||
| 		self.sstore.test_password(&StoreAccountRef::root(address.clone()), password) | ||||
| 		self.sstore.test_password(&self.sstore.account_ref(&address)?, password) | ||||
| 			.map_err(Into::into) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Permanently removes an account.
 | ||||
| 	pub fn kill_account(&self, address: &Address, password: &str) -> Result<(), Error> { | ||||
| 		self.sstore.remove_account(&StoreAccountRef::root(address.clone()), &password)?; | ||||
| 		self.sstore.remove_account(&self.sstore.account_ref(&address)?, &password)?; | ||||
| 		Ok(()) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Changes the password of `account` from `password` to `new_password`. Fails if incorrect `password` given.
 | ||||
| 	pub fn change_password(&self, account: &Address, password: String, new_password: String) -> Result<(), Error> { | ||||
| 		self.sstore.change_password(&StoreAccountRef::root(account.clone()), &password, &new_password) | ||||
| 	pub fn change_password(&self, address: &Address, password: String, new_password: String) -> Result<(), Error> { | ||||
| 		self.sstore.change_password(&self.sstore.account_ref(address)?, &password, &new_password) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Helper method used for unlocking accounts.
 | ||||
| 	fn unlock_account(&self, address: Address, password: String, unlock: Unlock) -> Result<(), Error> { | ||||
| 		// verify password by signing dump message
 | ||||
| 		// result may be discarded
 | ||||
| 		let account = StoreAccountRef::root(address); | ||||
| 		let account = self.sstore.account_ref(&address)?; | ||||
| 		let _ = self.sstore.sign(&account, &password, &Default::default())?; | ||||
| 
 | ||||
| 		// check if account is already unlocked pernamently, if it is, do nothing
 | ||||
| @ -374,20 +374,21 @@ impl AccountProvider { | ||||
| 	/// Checks if given account is unlocked
 | ||||
| 	pub fn is_unlocked(&self, address: Address) -> bool { | ||||
| 		let unlocked = self.unlocked.read(); | ||||
| 		let account = StoreAccountRef::root(address); | ||||
| 		unlocked.get(&account).is_some() | ||||
| 		self.sstore.account_ref(&address) | ||||
| 			.map(|r| unlocked.get(&r).is_some()) | ||||
| 			.unwrap_or(false) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Signs the message. If password is not provided the account must be unlocked.
 | ||||
| 	pub fn sign(&self, address: Address, password: Option<String>, message: Message) -> Result<Signature, SignError> { | ||||
| 		let account = StoreAccountRef::root(address); | ||||
| 		let account = self.sstore.account_ref(&address)?; | ||||
| 		let password = password.map(Ok).unwrap_or_else(|| self.password(&account))?; | ||||
| 		Ok(self.sstore.sign(&account, &password, &message)?) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Signs given message with supplied token. Returns a token to use in next signing within this session.
 | ||||
| 	pub fn sign_with_token(&self, address: Address, token: AccountToken, message: Message) -> Result<(Signature, AccountToken), SignError> { | ||||
| 		let account = StoreAccountRef::root(address); | ||||
| 		let account = self.sstore.account_ref(&address)?; | ||||
| 		let is_std_password = self.sstore.test_password(&account, &token)?; | ||||
| 
 | ||||
| 		let new_token = random_string(16); | ||||
| @ -410,7 +411,7 @@ impl AccountProvider { | ||||
| 	pub fn decrypt_with_token(&self, address: Address, token: AccountToken, shared_mac: &[u8], message: &[u8]) | ||||
| 		-> Result<(Vec<u8>, AccountToken), SignError> | ||||
| 	{ | ||||
| 		let account = StoreAccountRef::root(address); | ||||
| 		let account = self.sstore.account_ref(&address)?; | ||||
| 		let is_std_password = self.sstore.test_password(&account, &token)?; | ||||
| 
 | ||||
| 		let new_token = random_string(16); | ||||
| @ -431,7 +432,7 @@ impl AccountProvider { | ||||
| 
 | ||||
| 	/// Decrypts a message. If password is not provided the account must be unlocked.
 | ||||
| 	pub fn decrypt(&self, address: Address, password: Option<String>, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, SignError> { | ||||
| 		let account = StoreAccountRef::root(address); | ||||
| 		let account = self.sstore.account_ref(&address)?; | ||||
| 		let password = password.map(Ok).unwrap_or_else(|| self.password(&account))?; | ||||
| 		Ok(self.sstore.decrypt(&account, &password, shared_mac, message)?) | ||||
| 	} | ||||
| @ -447,6 +448,51 @@ impl AccountProvider { | ||||
| 			.map(|a| a.into_iter().map(|a| a.address).collect()) | ||||
| 			.map_err(Into::into) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Create new vault.
 | ||||
| 	pub fn create_vault(&self, name: &str, password: &str) -> Result<(), Error> { | ||||
| 		self.sstore.create_vault(name, password) | ||||
| 			.map_err(Into::into) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Open existing vault.
 | ||||
| 	pub fn open_vault(&self, name: &str, password: &str) -> Result<(), Error> { | ||||
| 		self.sstore.open_vault(name, password) | ||||
| 			.map_err(Into::into) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Close previously opened vault.
 | ||||
| 	pub fn close_vault(&self, name: &str) -> Result<(), Error> { | ||||
| 		self.sstore.close_vault(name) | ||||
| 			.map_err(Into::into) | ||||
| 	} | ||||
| 
 | ||||
| 	/// List all vaults
 | ||||
| 	pub fn list_vaults(&self) -> Result<Vec<String>, Error> { | ||||
| 		self.sstore.list_vaults() | ||||
| 			.map_err(Into::into) | ||||
| 	} | ||||
| 
 | ||||
| 	/// List all currently opened vaults
 | ||||
| 	pub fn list_opened_vaults(&self) -> Result<Vec<String>, Error> { | ||||
| 		self.sstore.list_opened_vaults() | ||||
| 			.map_err(Into::into) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Change vault password.
 | ||||
| 	pub fn change_vault_password(&self, name: &str, new_password: &str) -> Result<(), Error> { | ||||
| 		self.sstore.change_vault_password(name, new_password) | ||||
| 			.map_err(Into::into) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Change vault of the given address.
 | ||||
| 	pub fn change_vault(&self, address: Address, new_vault: &str) -> Result<(), Error> { | ||||
| 		let new_vault_ref = if new_vault.is_empty() { SecretVaultRef::Root } else { SecretVaultRef::Vault(new_vault.to_owned()) }; | ||||
| 		let old_account_ref = self.sstore.account_ref(&address)?; | ||||
| 		self.sstore.change_account_vault(new_vault_ref, old_account_ref) | ||||
| 			.map_err(Into::into) | ||||
| 			.map(|_| ()) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
|  | ||||
| @ -233,7 +233,7 @@ impl Decodable for BlockReceipts { | ||||
| 
 | ||||
| impl Encodable for BlockReceipts { | ||||
| 	fn rlp_append(&self, s: &mut RlpStream) { | ||||
| 		s.append(&self.receipts); | ||||
| 		Encodable::rlp_append(&self.receipts, s); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| @ -242,3 +242,21 @@ impl HeapSizeOf for BlockReceipts { | ||||
| 		self.receipts.heap_size_of_children() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
| 	use rlp::*; | ||||
| 	use super::BlockReceipts; | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn encode_block_receipts() { | ||||
| 		let br = BlockReceipts::new(Vec::new()); | ||||
| 
 | ||||
| 		let mut s = RlpStream::new_list(2); | ||||
| 		s.append(&br); | ||||
| 		assert!(!s.is_finished(), "List shouldn't finished yet"); | ||||
| 		s.append(&br); | ||||
| 		assert!(s.is_finished(), "List should be finished now"); | ||||
| 		s.out(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -20,7 +20,7 @@ use util::*; | ||||
| use super::{Height, View, BlockHash, Step}; | ||||
| use error::Error; | ||||
| use header::Header; | ||||
| use rlp::{Rlp, UntrustedRlp, RlpStream, Stream, Encodable, Decodable, Decoder, DecoderError, View as RlpView}; | ||||
| use rlp::{Rlp, UntrustedRlp, RlpStream, Stream, RlpEncodable, Encodable, Decodable, Decoder, DecoderError, View as RlpView}; | ||||
| use ethkey::{recover, public_to_address}; | ||||
| use super::super::vote_collector::Message; | ||||
| 
 | ||||
| @ -162,7 +162,7 @@ impl Decodable for Step { | ||||
| 
 | ||||
| impl Encodable for Step { | ||||
| 	fn rlp_append(&self, s: &mut RlpStream) { | ||||
| 		s.append(&self.number()); | ||||
| 		RlpEncodable::rlp_append(&self.number(), s); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| @ -193,8 +193,7 @@ impl Encodable for ConsensusMessage { | ||||
| } | ||||
| 
 | ||||
| pub fn message_info_rlp(vote_step: &VoteStep, block_hash: Option<BlockHash>) -> Bytes { | ||||
| 	// TODO: figure out whats wrong with nested list encoding
 | ||||
| 	let mut s = RlpStream::new_list(5); | ||||
| 	let mut s = RlpStream::new_list(4); | ||||
| 	s.append(&vote_step.height).append(&vote_step.view).append(&vote_step.step).append(&block_hash.unwrap_or_else(H256::zero)); | ||||
| 	s.out() | ||||
| } | ||||
| @ -215,6 +214,18 @@ mod tests { | ||||
| 	use super::super::Step; | ||||
| 	use super::*; | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn encode_step() { | ||||
| 		let step = Step::Precommit; | ||||
| 
 | ||||
| 		let mut s = RlpStream::new_list(2); | ||||
| 		s.append(&step); | ||||
| 		assert!(!s.is_finished(), "List shouldn't finished yet"); | ||||
| 		s.append(&step); | ||||
| 		assert!(s.is_finished(), "List should be finished now"); | ||||
| 		s.out(); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn encode_decode() { | ||||
| 		let message = ConsensusMessage { | ||||
|  | ||||
| @ -46,7 +46,7 @@ impl Encodable for CallType { | ||||
| 			CallType::CallCode => 2, | ||||
| 			CallType::DelegateCall => 3, | ||||
| 		}; | ||||
| 		s.append(&v); | ||||
| 		Encodable::rlp_append(&v, s); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| @ -212,12 +212,28 @@ impl fmt::Display for CallError { | ||||
| /// Transaction execution result.
 | ||||
| pub type ExecutionResult = Result<Executed, ExecutionError>; | ||||
| 
 | ||||
| #[test] | ||||
| fn should_encode_and_decode_call_type() { | ||||
| 	use rlp; | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
| 	use rlp::*; | ||||
| 	use super::CallType; | ||||
| 
 | ||||
| 	let original = CallType::Call; | ||||
| 	let encoded = rlp::encode(&original); | ||||
| 	let decoded = rlp::decode(&encoded); | ||||
| 	assert_eq!(original, decoded); | ||||
| 	#[test] | ||||
| 	fn encode_call_type() { | ||||
| 		let ct = CallType::Call; | ||||
| 
 | ||||
| 		let mut s = RlpStream::new_list(2); | ||||
| 		s.append(&ct); | ||||
| 		assert!(!s.is_finished(), "List shouldn't finished yet"); | ||||
| 		s.append(&ct); | ||||
| 		assert!(s.is_finished(), "List should be finished now"); | ||||
| 		s.out(); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn should_encode_and_decode_call_type() { | ||||
| 		let original = CallType::Call; | ||||
| 		let encoded = encode(&original); | ||||
| 		let decoded = decode(&encoded); | ||||
| 		assert_eq!(original, decoded); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -17,7 +17,7 @@ | ||||
| //! Trace errors.
 | ||||
| 
 | ||||
| use std::fmt; | ||||
| use rlp::{Encodable, RlpStream, Decodable, Decoder, DecoderError, Stream, View}; | ||||
| use rlp::{RlpEncodable, Encodable, RlpStream, Decodable, Decoder, DecoderError, View}; | ||||
| use evm::Error as EvmError; | ||||
| 
 | ||||
| /// Trace evm errors.
 | ||||
| @ -79,7 +79,7 @@ impl Encodable for Error { | ||||
| 			OutOfStack => 4, | ||||
| 			Internal => 5, | ||||
| 		}; | ||||
| 		s.append(&value); | ||||
| 		RlpEncodable::rlp_append(&value, s); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| @ -98,3 +98,21 @@ impl Decodable for Error { | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
| 	use rlp::*; | ||||
| 	use super::Error; | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn encode_error() { | ||||
| 		let err = Error::BadJumpDestination; | ||||
| 
 | ||||
| 		let mut s = RlpStream::new_list(2); | ||||
| 		s.append(&err); | ||||
| 		assert!(!s.is_finished(), "List shouldn't finished yet"); | ||||
| 		s.append(&err); | ||||
| 		assert!(s.is_finished(), "List should be finished now"); | ||||
| 		s.out(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -103,7 +103,7 @@ impl FlatTransactionTraces { | ||||
| 
 | ||||
| impl Encodable for FlatTransactionTraces { | ||||
| 	fn rlp_append(&self, s: &mut RlpStream) { | ||||
| 		s.append(&self.0); | ||||
| 		Encodable::rlp_append(&self.0, s); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| @ -144,7 +144,7 @@ impl FlatBlockTraces { | ||||
| 
 | ||||
| impl Encodable for FlatBlockTraces { | ||||
| 	fn rlp_append(&self, s: &mut RlpStream) { | ||||
| 		s.append(&self.0); | ||||
| 		Encodable::rlp_append(&self.0, s); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| @ -162,10 +162,35 @@ impl Into<Vec<FlatTransactionTraces>> for FlatBlockTraces { | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
| 	use rlp::*; | ||||
| 	use super::{FlatBlockTraces, FlatTransactionTraces, FlatTrace}; | ||||
| 	use trace::trace::{Action, Res, CallResult, Call, Suicide}; | ||||
| 	use types::executed::CallType; | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn encode_flat_transaction_traces() { | ||||
| 		let ftt = FlatTransactionTraces::from(Vec::new()); | ||||
| 
 | ||||
| 		let mut s = RlpStream::new_list(2); | ||||
| 		s.append(&ftt); | ||||
| 		assert!(!s.is_finished(), "List shouldn't finished yet"); | ||||
| 		s.append(&ftt); | ||||
| 		assert!(s.is_finished(), "List should be finished now"); | ||||
| 		s.out(); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn encode_flat_block_traces() { | ||||
| 		let fbt = FlatBlockTraces::from(Vec::new()); | ||||
| 
 | ||||
| 		let mut s = RlpStream::new_list(2); | ||||
| 		s.append(&fbt); | ||||
| 		assert!(!s.is_finished(), "List shouldn't finished yet"); | ||||
| 		s.append(&fbt); | ||||
| 		assert!(s.is_finished(), "List should be finished now"); | ||||
| 		s.out(); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn test_trace_serialization() { | ||||
| 		// block #51921
 | ||||
|  | ||||
| @ -11,6 +11,8 @@ eth-secp256k1 = { git = "https://github.com/ethcore/rust-secp256k1" } | ||||
| rustc-serialize = "0.3" | ||||
| docopt = { version = "0.6", optional = true } | ||||
| ethcore-bigint = { path = "../util/bigint" } | ||||
| rust-crypto = "0.2" | ||||
| byteorder = "1.0" | ||||
| 
 | ||||
| [features] | ||||
| default = [] | ||||
|  | ||||
							
								
								
									
										448
									
								
								ethkey/src/extended.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										448
									
								
								ethkey/src/extended.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,448 @@ | ||||
| // Copyright 2015-2017 Parity Technologies (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity 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.
 | ||||
| 
 | ||||
| // Parity 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 Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| //! Extended keys
 | ||||
| 
 | ||||
| use secret::Secret; | ||||
| use Public; | ||||
| use bigint::hash::{H256, FixedHash}; | ||||
| pub use self::derivation::Error as DerivationError; | ||||
| 
 | ||||
| /// Extended secret key, allows deterministic derivation of subsequent keys.
 | ||||
| pub struct ExtendedSecret { | ||||
| 	secret: Secret, | ||||
| 	chain_code: H256, | ||||
| } | ||||
| 
 | ||||
| impl ExtendedSecret { | ||||
| 	/// New extended key from given secret and chain code.
 | ||||
| 	pub fn with_code(secret: Secret, chain_code: H256) -> ExtendedSecret { | ||||
| 		ExtendedSecret { | ||||
| 			secret: secret, | ||||
| 			chain_code: chain_code, | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/// New extended key from given secret with the random chain code.
 | ||||
| 	pub fn new_random(secret: Secret) -> ExtendedSecret { | ||||
| 		ExtendedSecret::with_code(secret, H256::random()) | ||||
| 	} | ||||
| 
 | ||||
| 	/// New extended key from given secret.
 | ||||
| 	/// Chain code will be derived from the secret itself (in a deterministic way).
 | ||||
| 	pub fn new(secret: Secret) -> ExtendedSecret { | ||||
| 		let chain_code = derivation::chain_code(*secret); | ||||
| 		ExtendedSecret::with_code(secret, chain_code) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Derive new private key
 | ||||
| 	pub fn derive(&self, index: u32) -> ExtendedSecret { | ||||
| 		let (derived_key, next_chain_code) = derivation::private(*self.secret, self.chain_code, index); | ||||
| 
 | ||||
| 		let derived_secret = Secret::from_slice(&*derived_key) | ||||
| 			.expect("Derivation always produced a valid private key; qed"); | ||||
| 
 | ||||
| 		ExtendedSecret::with_code(derived_secret, next_chain_code) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Private key component of the extended key.
 | ||||
| 	pub fn secret(&self) -> &Secret { | ||||
| 		&self.secret | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /// Extended public key, allows deterministic derivation of subsequent keys.
 | ||||
| pub struct ExtendedPublic { | ||||
| 	public: Public, | ||||
| 	chain_code: H256, | ||||
| } | ||||
| 
 | ||||
| impl ExtendedPublic { | ||||
| 	/// New extended public key from known parent and chain code
 | ||||
| 	pub fn new(public: Public, chain_code: H256) -> Self { | ||||
| 		ExtendedPublic { public: public, chain_code: chain_code } | ||||
| 	} | ||||
| 
 | ||||
| 	/// Create new extended public key from known secret
 | ||||
| 	pub fn from_secret(secret: &ExtendedSecret) -> Result<Self, DerivationError> { | ||||
| 		Ok( | ||||
| 			ExtendedPublic::new( | ||||
| 				derivation::point(**secret.secret())?, | ||||
| 				secret.chain_code.clone(), | ||||
| 			) | ||||
| 		) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Derive new public key
 | ||||
| 	/// Operation is defined only for index belongs [0..2^31)
 | ||||
| 	pub fn derive(&self, index: u32) -> Result<Self, DerivationError> { | ||||
| 		let (derived_key, next_chain_code) = derivation::public(self.public, self.chain_code, index)?; | ||||
| 		Ok(ExtendedPublic::new(derived_key, next_chain_code)) | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn public(&self) -> &Public { | ||||
| 		&self.public | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| pub struct ExtendedKeyPair { | ||||
| 	secret: ExtendedSecret, | ||||
| 	public: ExtendedPublic, | ||||
| } | ||||
| 
 | ||||
| impl ExtendedKeyPair { | ||||
| 	pub fn new(secret: Secret) -> Self { | ||||
| 		let extended_secret = ExtendedSecret::new(secret); | ||||
| 		let extended_public = ExtendedPublic::from_secret(&extended_secret) | ||||
| 			.expect("Valid `Secret` always produces valid public; qed"); | ||||
| 		ExtendedKeyPair { | ||||
| 			secret: extended_secret, | ||||
| 			public: extended_public, | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn with_code(secret: Secret, public: Public, chain_code: H256) -> Self { | ||||
| 		ExtendedKeyPair { | ||||
| 			secret: ExtendedSecret::with_code(secret, chain_code.clone()), | ||||
| 			public: ExtendedPublic::new(public, chain_code), | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn with_secret(secret: Secret, chain_code: H256) -> Self { | ||||
| 		let extended_secret = ExtendedSecret::with_code(secret, chain_code); | ||||
| 		let extended_public = ExtendedPublic::from_secret(&extended_secret) | ||||
| 			.expect("Valid `Secret` always produces valid public; qed"); | ||||
| 		ExtendedKeyPair { | ||||
| 			secret: extended_secret, | ||||
| 			public: extended_public, | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn with_seed(seed: &[u8]) -> Result<ExtendedKeyPair, DerivationError> { | ||||
| 		let (master_key, chain_code) = derivation::seed_pair(seed); | ||||
| 		Ok(ExtendedKeyPair::with_secret( | ||||
| 			Secret::from_slice(&*master_key).map_err(|_| DerivationError::InvalidSeed)?, | ||||
| 			chain_code, | ||||
| 		)) | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn secret(&self) -> &ExtendedSecret { | ||||
| 		&self.secret | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn public(&self) -> &ExtendedPublic { | ||||
| 		&self.public | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn derive(&self, index: u32) -> Result<Self, DerivationError> { | ||||
| 		let derived = self.secret.derive(index); | ||||
| 
 | ||||
| 		Ok(ExtendedKeyPair { | ||||
| 			public: ExtendedPublic::from_secret(&derived)?, | ||||
| 			secret: derived, | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Derivation functions for private and public keys
 | ||||
| // Work is based on BIP0032
 | ||||
| // https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
 | ||||
| mod derivation { | ||||
| 
 | ||||
| 	use rcrypto::hmac::Hmac; | ||||
| 	use rcrypto::mac::Mac; | ||||
| 	use rcrypto::sha2::Sha512; | ||||
| 	use bigint::hash::{H512, H256, FixedHash}; | ||||
| 	use bigint::prelude::{U256, U512, Uint}; | ||||
| 	use byteorder::{BigEndian, ByteOrder}; | ||||
| 	use secp256k1; | ||||
| 	use secp256k1::key::{SecretKey, PublicKey}; | ||||
| 	use SECP256K1; | ||||
| 	use keccak; | ||||
| 
 | ||||
| 	#[derive(Debug)] | ||||
| 	pub enum Error { | ||||
| 		InvalidHardenedUse, | ||||
| 		InvalidPoint, | ||||
| 		MissingIndex, | ||||
| 		InvalidSeed, | ||||
| 	} | ||||
| 
 | ||||
| 	// Deterministic derivation of the key using secp256k1 elliptic curve.
 | ||||
| 	// Derivation can be either hardened or not.
 | ||||
| 	// For hardened derivation, pass index at least 2^31
 | ||||
| 	//
 | ||||
| 	// Can panic if passed `private_key` is not a valid secp256k1 private key
 | ||||
| 	// (outside of (0..curve_n()]) field
 | ||||
| 	pub fn private(private_key: H256, chain_code: H256, index: u32) -> (H256, H256) { | ||||
| 		if index < (2 << 30) { | ||||
| 			private_soft(private_key, chain_code, index) | ||||
| 		} | ||||
| 		else { | ||||
| 			private_hard(private_key, chain_code, index) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	fn hmac_pair(data: [u8; 37], private_key: H256, chain_code: H256) -> (H256, H256) { | ||||
| 		let private: U256 = private_key.into(); | ||||
| 
 | ||||
| 		// produces 512-bit derived hmac (I)
 | ||||
| 		let mut hmac = Hmac::new(Sha512::new(), &*chain_code); | ||||
| 		let mut i_512 = [0u8; 64]; | ||||
| 		hmac.input(&data[..]); | ||||
| 		hmac.raw_result(&mut i_512); | ||||
| 
 | ||||
| 		// left most 256 bits are later added to original private key
 | ||||
| 		let hmac_key: U256 = H256::from_slice(&i_512[0..32]).into(); | ||||
| 		// right most 256 bits are new chain code for later derivations
 | ||||
| 		let next_chain_code = H256::from(&i_512[32..64]); | ||||
| 
 | ||||
| 		let child_key = private_add(hmac_key, private).into(); | ||||
| 		(child_key, next_chain_code) | ||||
| 	} | ||||
| 
 | ||||
| 	// Can panic if passed `private_key` is not a valid secp256k1 private key
 | ||||
| 	// (outside of (0..curve_n()]) field
 | ||||
| 	fn private_soft(private_key: H256, chain_code: H256, index: u32) -> (H256, H256) { | ||||
| 		let mut data = [0u8; 37]; | ||||
| 
 | ||||
| 		let sec_private = SecretKey::from_slice(&SECP256K1, &*private_key) | ||||
| 			.expect("Caller should provide valid private key"); | ||||
| 		let sec_public = PublicKey::from_secret_key(&SECP256K1, &sec_private) | ||||
| 			.expect("Caller should provide valid private key"); | ||||
| 		let public_serialized = sec_public.serialize_vec(&SECP256K1, true); | ||||
| 
 | ||||
| 		// curve point (compressed public key) --  index
 | ||||
| 		//             0.33                    --  33..37
 | ||||
| 		data[0..33].copy_from_slice(&public_serialized); | ||||
| 		BigEndian::write_u32(&mut data[33..37], index); | ||||
| 
 | ||||
| 		hmac_pair(data, private_key, chain_code) | ||||
| 	} | ||||
| 
 | ||||
| 	// Deterministic derivation of the key using secp256k1 elliptic curve
 | ||||
| 	// This is hardened derivation and does not allow to associate
 | ||||
| 	// corresponding public keys of the original and derived private keys
 | ||||
| 	fn private_hard(private_key: H256, chain_code: H256, index: u32) -> (H256, H256) { | ||||
| 		let mut data = [0u8; 37]; | ||||
| 		let private: U256 = private_key.into(); | ||||
| 
 | ||||
| 		// 0x00 (padding) -- private_key --  index
 | ||||
| 		//  0             --    1..33    -- 33..37
 | ||||
| 		private.to_big_endian(&mut data[1..33]); | ||||
| 		BigEndian::write_u32(&mut data[33..37], index); | ||||
| 
 | ||||
| 		hmac_pair(data, private_key, chain_code) | ||||
| 	} | ||||
| 
 | ||||
| 	fn private_add(k1: U256, k2: U256) -> U256 { | ||||
| 		let sum = U512::from(k1) + U512::from(k2); | ||||
| 		modulo(sum, curve_n()) | ||||
| 	} | ||||
| 
 | ||||
| 	// todo: surely can be optimized
 | ||||
| 	fn modulo(u1: U512, u2: U256) -> U256 { | ||||
| 		let dv = u1 / U512::from(u2); | ||||
| 		let md = u1 - (dv * U512::from(u2)); | ||||
| 		md.into() | ||||
| 	} | ||||
| 
 | ||||
| 	// returns n (for mod(n)) for the secp256k1 elliptic curve
 | ||||
| 	// todo: maybe lazy static
 | ||||
| 	fn curve_n() -> U256 { | ||||
| 		H256::from_slice(&secp256k1::constants::CURVE_ORDER).into() | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn public(public_key: H512, chain_code: H256, index: u32) -> Result<(H512, H256), Error> { | ||||
| 		if index >= (2 << 30) { | ||||
| 			// public derivation is only defined on 'soft' index space [0..2^31)
 | ||||
| 			return Err(Error::InvalidHardenedUse) | ||||
| 		} | ||||
| 
 | ||||
| 		let mut public_sec_raw = [0u8; 65]; | ||||
| 		public_sec_raw[0] = 4; | ||||
| 		public_sec_raw[1..65].copy_from_slice(&*public_key); | ||||
| 		let public_sec = PublicKey::from_slice(&SECP256K1, &public_sec_raw).map_err(|_| Error::InvalidPoint)?; | ||||
| 		let public_serialized = public_sec.serialize_vec(&SECP256K1, true); | ||||
| 
 | ||||
| 		let mut data = [0u8; 37]; | ||||
| 		// curve point (compressed public key) --  index
 | ||||
| 		//             0.33                    --  33..37
 | ||||
| 		data[0..33].copy_from_slice(&public_serialized); | ||||
| 		BigEndian::write_u32(&mut data[33..37], index); | ||||
| 
 | ||||
| 		// HMAC512SHA produces [derived private(256); new chain code(256)]
 | ||||
| 		let mut hmac = Hmac::new(Sha512::new(), &*chain_code); | ||||
| 		let mut i_512 = [0u8; 64]; | ||||
| 		hmac.input(&data[..]); | ||||
| 		hmac.raw_result(&mut i_512); | ||||
| 
 | ||||
| 		let new_private = H256::from(&i_512[0..32]); | ||||
| 		let new_chain_code = H256::from(&i_512[32..64]); | ||||
| 
 | ||||
| 		// Generated private key can (extremely rarely) be out of secp256k1 key field
 | ||||
| 		if curve_n() <= new_private.clone().into() { return Err(Error::MissingIndex); } | ||||
| 		let new_private_sec = SecretKey::from_slice(&SECP256K1, &*new_private) | ||||
| 			.expect("Private key belongs to the field [0..CURVE_ORDER) (checked above); So initializing can never fail; qed"); | ||||
| 		let mut new_public = PublicKey::from_secret_key(&SECP256K1, &new_private_sec) | ||||
| 			.expect("Valid private key produces valid public key"); | ||||
| 
 | ||||
| 		// Adding two points on the elliptic curves (combining two public keys)
 | ||||
| 		new_public.add_assign(&SECP256K1, &public_sec) | ||||
| 			.expect("Addition of two valid points produce valid point"); | ||||
| 
 | ||||
| 		let serialized = new_public.serialize_vec(&SECP256K1, false); | ||||
| 
 | ||||
| 		Ok(( | ||||
| 			H512::from(&serialized[1..65]), | ||||
| 			new_chain_code, | ||||
| 		)) | ||||
| 	} | ||||
| 
 | ||||
| 	fn sha3(slc: &[u8]) -> H256 { | ||||
| 		keccak::Keccak256::keccak256(slc).into() | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn chain_code(secret: H256) -> H256 { | ||||
| 		// 10,000 rounds of sha3
 | ||||
| 		let mut running_sha3 = sha3(&*secret); | ||||
| 		for _ in 0..99999 { running_sha3 = sha3(&*running_sha3); } | ||||
| 		running_sha3 | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn point(secret: H256) -> Result<H512, Error> { | ||||
| 		let sec = SecretKey::from_slice(&SECP256K1, &*secret) | ||||
| 			.map_err(|_| Error::InvalidPoint)?; | ||||
| 		let public_sec = PublicKey::from_secret_key(&SECP256K1, &sec) | ||||
| 			.map_err(|_| Error::InvalidPoint)?; | ||||
| 		let serialized = public_sec.serialize_vec(&SECP256K1, false); | ||||
| 		Ok(H512::from(&serialized[1..65])) | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn seed_pair(seed: &[u8]) -> (H256, H256) { | ||||
| 		let mut hmac = Hmac::new(Sha512::new(), b"Bitcoin seed"); | ||||
| 		let mut i_512 = [0u8; 64]; | ||||
| 		hmac.input(seed); | ||||
| 		hmac.raw_result(&mut i_512); | ||||
| 
 | ||||
| 		let master_key = H256::from_slice(&i_512[0..32]); | ||||
| 		let chain_code = H256::from_slice(&i_512[32..64]); | ||||
| 
 | ||||
| 		(master_key, chain_code) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
| 	use super::{ExtendedSecret, ExtendedPublic, ExtendedKeyPair}; | ||||
| 	use secret::Secret; | ||||
| 	use std::str::FromStr; | ||||
| 	use bigint::hash::{H128, H256}; | ||||
| 	use super::derivation; | ||||
| 
 | ||||
| 	fn master_chain_basic() -> (H256, H256) { | ||||
| 		let seed = H128::from_str("000102030405060708090a0b0c0d0e0f") | ||||
| 			.expect("Seed should be valid H128") | ||||
| 			.to_vec(); | ||||
| 
 | ||||
| 		derivation::seed_pair(&*seed) | ||||
| 	} | ||||
| 
 | ||||
| 	fn test_extended<F>(f: F, test_private: H256) where F: Fn(ExtendedSecret) -> ExtendedSecret { | ||||
| 		let (private_seed, chain_code) = master_chain_basic(); | ||||
| 		let extended_secret = ExtendedSecret::with_code(Secret::from_slice(&*private_seed).unwrap(), chain_code); | ||||
| 		let derived = f(extended_secret); | ||||
| 		assert_eq!(**derived.secret(), test_private); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn smoky() { | ||||
| 		let secret = Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65").unwrap(); | ||||
| 		let extended_secret = ExtendedSecret::with_code(secret.clone(), 0u64.into()); | ||||
| 
 | ||||
| 		// hardened
 | ||||
| 		assert_eq!(&**extended_secret.secret(), &*secret); | ||||
| 		assert_eq!(&**extended_secret.derive(2147483648).secret(), &"0927453daed47839608e414a3738dfad10aed17c459bbd9ab53f89b026c834b6".into()); | ||||
| 		assert_eq!(&**extended_secret.derive(2147483649).secret(), &"44238b6a29c6dcbe9b401364141ba11e2198c289a5fed243a1c11af35c19dc0f".into()); | ||||
| 
 | ||||
| 		// normal
 | ||||
| 		assert_eq!(&**extended_secret.derive(0).secret(), &"bf6a74e3f7b36fc4c96a1e12f31abc817f9f5904f5a8fc27713163d1f0b713f6".into()); | ||||
| 		assert_eq!(&**extended_secret.derive(1).secret(), &"bd4fca9eb1f9c201e9448c1eecd66e302d68d4d313ce895b8c134f512205c1bc".into()); | ||||
| 		assert_eq!(&**extended_secret.derive(2).secret(), &"86932b542d6cab4d9c65490c7ef502d89ecc0e2a5f4852157649e3251e2a3268".into()); | ||||
| 
 | ||||
| 		let extended_public = ExtendedPublic::from_secret(&extended_secret).expect("Extended public should be created"); | ||||
| 		let derived_public = extended_public.derive(0).expect("First derivation of public should succeed"); | ||||
| 		assert_eq!(&*derived_public.public(), &"f7b3244c96688f92372bfd4def26dc4151529747bab9f188a4ad34e141d47bd66522ff048bc6f19a0a4429b04318b1a8796c000265b4fa200dae5f6dda92dd94".into()); | ||||
| 
 | ||||
| 		let keypair = ExtendedKeyPair::with_secret( | ||||
| 			Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65").unwrap(), | ||||
| 			064.into(), | ||||
| 		); | ||||
| 		assert_eq!(&**keypair.derive(2147483648).expect("Derivation of keypair should succeed").secret().secret(), &"edef54414c03196557cf73774bc97a645c9a1df2164ed34f0c2a78d1375a930c".into()); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn match_() { | ||||
| 		let secret = Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65").unwrap(); | ||||
| 		let extended_secret = ExtendedSecret::with_code(secret.clone(), 1.into()); | ||||
| 		let extended_public = ExtendedPublic::from_secret(&extended_secret).expect("Extended public should be created"); | ||||
| 
 | ||||
| 		let derived_secret0 = extended_secret.derive(0); | ||||
| 		let derived_public0 = extended_public.derive(0).expect("First derivation of public should succeed"); | ||||
| 
 | ||||
| 		let public_from_secret0 = ExtendedPublic::from_secret(&derived_secret0).expect("Extended public should be created"); | ||||
| 
 | ||||
| 		assert_eq!(public_from_secret0.public(), derived_public0.public()); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn test_seeds() { | ||||
| 		let seed = H128::from_str("000102030405060708090a0b0c0d0e0f") | ||||
| 			.expect("Seed should be valid H128") | ||||
| 			.to_vec(); | ||||
| 
 | ||||
| 		/// private key from bitcoin test vector
 | ||||
| 		/// xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs
 | ||||
| 		let test_private = H256::from_str("e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35") | ||||
| 			.expect("Private should be decoded ok"); | ||||
| 
 | ||||
| 		let (private_seed, _) = derivation::seed_pair(&*seed); | ||||
| 
 | ||||
| 		assert_eq!(private_seed, test_private); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn test_vector_1() { | ||||
| 		/// xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7
 | ||||
| 		/// H(0)
 | ||||
| 		test_extended( | ||||
| 			|secret| secret.derive(2147483648), | ||||
| 			H256::from_str("edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea") | ||||
| 				.expect("Private should be decoded ok") | ||||
| 		); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn test_vector_2() { | ||||
| 		/// xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs
 | ||||
| 		/// H(0)/1
 | ||||
| 		test_extended( | ||||
| 			|secret| secret.derive(2147483648).derive(1), | ||||
| 			H256::from_str("3c6cb8d0f6a264c91ea8b5030fadaa8e538b020f0a387421a12de9319dc93368") | ||||
| 				.expect("Private should be decoded ok") | ||||
| 		); | ||||
| 	} | ||||
| } | ||||
| @ -21,6 +21,8 @@ extern crate tiny_keccak; | ||||
| extern crate secp256k1; | ||||
| extern crate rustc_serialize; | ||||
| extern crate ethcore_bigint as bigint; | ||||
| extern crate crypto as rcrypto; | ||||
| extern crate byteorder; | ||||
| 
 | ||||
| mod brain; | ||||
| mod error; | ||||
| @ -30,6 +32,7 @@ mod prefix; | ||||
| mod random; | ||||
| mod signature; | ||||
| mod secret; | ||||
| mod extended; | ||||
| 
 | ||||
| lazy_static! { | ||||
| 	pub static ref SECP256K1: secp256k1::Secp256k1 = secp256k1::Secp256k1::new(); | ||||
| @ -48,6 +51,7 @@ pub use self::prefix::Prefix; | ||||
| pub use self::random::Random; | ||||
| pub use self::signature::{sign, verify_public, verify_address, recover, Signature}; | ||||
| pub use self::secret::Secret; | ||||
| pub use self::extended::{ExtendedPublic, ExtendedSecret, ExtendedKeyPair, DerivationError}; | ||||
| 
 | ||||
| use bigint::hash::{H160, H256, H512}; | ||||
| 
 | ||||
|  | ||||
| @ -23,6 +23,7 @@ parking_lot = "0.3" | ||||
| ethcrypto = { path = "../ethcrypto" } | ||||
| ethcore-util = { path = "../util" } | ||||
| smallvec = "0.3.1" | ||||
| ethcore-devtools = { path = "../devtools" } | ||||
| 
 | ||||
| [build-dependencies] | ||||
| serde_codegen = { version = "0.8", optional = true } | ||||
|  | ||||
| @ -21,7 +21,7 @@ use time; | ||||
| use {json, SafeAccount, Error}; | ||||
| use json::Uuid; | ||||
| use super::{KeyDirectory, VaultKeyDirectory, VaultKeyDirectoryProvider, VaultKey}; | ||||
| use super::vault::VaultDiskDirectory; | ||||
| use super::vault::{VAULT_FILE_NAME, VaultDiskDirectory}; | ||||
| 
 | ||||
| const IGNORED_FILES: &'static [&'static str] = &[ | ||||
| 	"thumbs.db", | ||||
| @ -193,7 +193,7 @@ impl<T> KeyDirectory for DiskDirectory<T> where T: KeyFileManager { | ||||
| 		// and find entry with given address
 | ||||
| 		let to_remove = self.files()? | ||||
| 			.into_iter() | ||||
| 			.find(|&(_, ref acc)| acc == account); | ||||
| 			.find(|&(_, ref acc)| acc.id == account.id && acc.address == account.address); | ||||
| 
 | ||||
| 		// remove it
 | ||||
| 		match to_remove { | ||||
| @ -219,6 +219,21 @@ impl<T> VaultKeyDirectoryProvider for DiskDirectory<T> where T: KeyFileManager { | ||||
| 		let vault_dir = VaultDiskDirectory::at(&self.path, name, key)?; | ||||
| 		Ok(Box::new(vault_dir)) | ||||
| 	} | ||||
| 
 | ||||
| 	fn list_vaults(&self) -> Result<Vec<String>, Error> { | ||||
| 		Ok(fs::read_dir(&self.path)? | ||||
| 			.filter_map(|e| e.ok().map(|e| e.path())) | ||||
| 			.filter_map(|path| { | ||||
| 				let mut vault_file_path = path.clone(); | ||||
| 				vault_file_path.push(VAULT_FILE_NAME); | ||||
| 				if vault_file_path.is_file() { | ||||
| 					path.file_name().and_then(|f| f.to_str()).map(|f| f.to_owned()) | ||||
| 				} else { | ||||
| 					None | ||||
| 				} | ||||
| 			}) | ||||
| 			.collect()) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl KeyFileManager for DiskKeyFileManager { | ||||
| @ -240,6 +255,7 @@ mod test { | ||||
| 	use dir::{KeyDirectory, VaultKey}; | ||||
| 	use account::SafeAccount; | ||||
| 	use ethkey::{Random, Generator}; | ||||
| 	use devtools::RandomTempPath; | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn should_create_new_account() { | ||||
| @ -295,4 +311,20 @@ mod test { | ||||
| 		// cleanup
 | ||||
| 		let _ = fs::remove_dir_all(dir); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn should_list_vaults() { | ||||
| 		// given
 | ||||
| 		let temp_path = RandomTempPath::new(); | ||||
| 		let directory = RootDiskDirectory::create(&temp_path).unwrap(); | ||||
| 		let vault_provider = directory.as_vault_provider().unwrap(); | ||||
| 		vault_provider.create("vault1", VaultKey::new("password1", 1)).unwrap(); | ||||
| 		vault_provider.create("vault2", VaultKey::new("password2", 1)).unwrap(); | ||||
| 
 | ||||
| 		// then
 | ||||
| 		let vaults = vault_provider.list_vaults().unwrap(); | ||||
| 		assert_eq!(vaults.len(), 2); | ||||
| 		assert!(vaults.iter().any(|v| &*v == "vault1")); | ||||
| 		assert!(vaults.iter().any(|v| &*v == "vault2")); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -70,6 +70,8 @@ pub trait VaultKeyDirectoryProvider { | ||||
| 	fn create(&self, name: &str, key: VaultKey) -> Result<Box<VaultKeyDirectory>, Error>; | ||||
| 	/// Open existing vault with given key
 | ||||
| 	fn open(&self, name: &str, key: VaultKey) -> Result<Box<VaultKeyDirectory>, Error>; | ||||
| 	/// List all vaults
 | ||||
| 	fn list_vaults(&self) -> Result<Vec<String>, Error>; | ||||
| } | ||||
| 
 | ||||
| /// Vault directory
 | ||||
| @ -78,8 +80,10 @@ pub trait VaultKeyDirectory: KeyDirectory { | ||||
| 	fn as_key_directory(&self) -> &KeyDirectory; | ||||
| 	/// Vault name
 | ||||
| 	fn name(&self) -> &str; | ||||
| 	/// Get vault key
 | ||||
| 	fn key(&self) -> VaultKey; | ||||
| 	/// Set new key for vault
 | ||||
| 	fn set_key(&self, old_key: VaultKey, key: VaultKey) -> Result<(), SetKeyError>; | ||||
| 	fn set_key(&self, key: VaultKey) -> Result<(), SetKeyError>; | ||||
| } | ||||
| 
 | ||||
| pub use self::disk::RootDiskDirectory; | ||||
|  | ||||
| @ -22,13 +22,15 @@ use super::super::account::Crypto; | ||||
| use super::{KeyDirectory, VaultKeyDirectory, VaultKey, SetKeyError}; | ||||
| use super::disk::{DiskDirectory, KeyFileManager}; | ||||
| 
 | ||||
| const VAULT_FILE_NAME: &'static str = "vault.json"; | ||||
| /// Name of vault metadata file
 | ||||
| pub const VAULT_FILE_NAME: &'static str = "vault.json"; | ||||
| 
 | ||||
| /// Vault directory implementation
 | ||||
| pub type VaultDiskDirectory = DiskDirectory<VaultKeyFileManager>; | ||||
| 
 | ||||
| /// Vault key file manager
 | ||||
| pub struct VaultKeyFileManager { | ||||
| 	name: String, | ||||
| 	key: VaultKey, | ||||
| } | ||||
| 
 | ||||
| @ -48,7 +50,7 @@ impl VaultDiskDirectory { | ||||
| 			return Err(err); | ||||
| 		} | ||||
| 
 | ||||
| 		Ok(DiskDirectory::new(vault_dir_path, VaultKeyFileManager::new(key))) | ||||
| 		Ok(DiskDirectory::new(vault_dir_path, VaultKeyFileManager::new(name, key))) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Open existing vault directory with given key
 | ||||
| @ -62,7 +64,7 @@ impl VaultDiskDirectory { | ||||
| 		// check that passed key matches vault file
 | ||||
| 		check_vault_file(&vault_dir_path, &key)?; | ||||
| 
 | ||||
| 		Ok(DiskDirectory::new(vault_dir_path, VaultKeyFileManager::new(key))) | ||||
| 		Ok(DiskDirectory::new(vault_dir_path, VaultKeyFileManager::new(name, key))) | ||||
| 	} | ||||
| 
 | ||||
| 	fn create_temp_vault(&self, key: VaultKey) -> Result<VaultDiskDirectory, Error> { | ||||
| @ -84,12 +86,10 @@ impl VaultDiskDirectory { | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	fn copy_to_vault(&self, vault: &VaultDiskDirectory, vault_key: &VaultKey) -> Result<(), Error> { | ||||
| 		let password = &self.key_manager().key.password; | ||||
| 	fn copy_to_vault(&self, vault: &VaultDiskDirectory) -> Result<(), Error> { | ||||
| 		for account in self.load()? { | ||||
| 			let filename = account.filename.clone().expect("self is instance of DiskDirectory; DiskDirectory fills filename in load; qed"); | ||||
| 			let new_account = account.change_password(password, &vault_key.password, vault_key.iterations)?; | ||||
| 			vault.insert_with_filename(new_account, filename)?; | ||||
| 			vault.insert_with_filename(account, filename)?; | ||||
| 		} | ||||
| 
 | ||||
| 		Ok(()) | ||||
| @ -107,19 +107,14 @@ impl VaultKeyDirectory for VaultDiskDirectory { | ||||
| 	} | ||||
| 
 | ||||
| 	fn name(&self) -> &str { | ||||
| 		self.path() | ||||
| 			.expect("self is instance of DiskDirectory; DiskDirectory always returns path; qed") | ||||
| 			.file_name() | ||||
| 			.expect("last component of path is checked in make_vault_dir_path; it contains no fs-specific characters; file_name only returns None if last component is fs-specific; qed") | ||||
| 			.to_str() | ||||
| 			.expect("last component of path is checked in make_vault_dir_path; it contains only valid unicode characters; to_str fails when file_name is not valid unicode; qed") | ||||
| 		&self.key_manager().name | ||||
| 	} | ||||
| 
 | ||||
| 	fn set_key(&self, key: VaultKey, new_key: VaultKey) -> Result<(), SetKeyError> { | ||||
| 		if self.key_manager().key != key { | ||||
| 			return Err(SetKeyError::NonFatalOld(Error::InvalidPassword)); | ||||
| 		} | ||||
| 	fn key(&self) -> VaultKey { | ||||
| 		self.key_manager().key.clone() | ||||
| 	} | ||||
| 
 | ||||
| 	fn set_key(&self, new_key: VaultKey) -> Result<(), SetKeyError> { | ||||
| 		let temp_vault = VaultDiskDirectory::create_temp_vault(self, new_key.clone()).map_err(|err| SetKeyError::NonFatalOld(err))?; | ||||
| 		let mut source_path = temp_vault.path().expect("temp_vault is instance of DiskDirectory; DiskDirectory always returns path; qed").clone(); | ||||
| 		let mut target_path = self.path().expect("self is instance of DiskDirectory; DiskDirectory always returns path; qed").clone(); | ||||
| @ -127,7 +122,7 @@ impl VaultKeyDirectory for VaultDiskDirectory { | ||||
| 		source_path.push("next"); | ||||
| 		target_path.push("next"); | ||||
| 
 | ||||
| 		let temp_accounts = self.copy_to_vault(&temp_vault, &new_key) | ||||
| 		let temp_accounts = self.copy_to_vault(&temp_vault) | ||||
| 			.and_then(|_| temp_vault.load()) | ||||
| 			.map_err(|err| { | ||||
| 				// ignore error, as we already processing error
 | ||||
| @ -153,8 +148,9 @@ impl VaultKeyDirectory for VaultDiskDirectory { | ||||
| } | ||||
| 
 | ||||
| impl VaultKeyFileManager { | ||||
| 	pub fn new(key: VaultKey) -> Self { | ||||
| 	pub fn new(name: &str, key: VaultKey) -> Self { | ||||
| 		VaultKeyFileManager { | ||||
| 			name: name.into(), | ||||
| 			key: key, | ||||
| 		} | ||||
| 	} | ||||
| @ -163,20 +159,16 @@ impl VaultKeyFileManager { | ||||
| impl KeyFileManager for VaultKeyFileManager { | ||||
| 	fn read<T>(&self, filename: Option<String>, reader: T) -> Result<SafeAccount, Error> where T: io::Read { | ||||
| 		let vault_file = json::VaultKeyFile::load(reader).map_err(|e| Error::Custom(format!("{:?}", e)))?; | ||||
| 		let safe_account = SafeAccount::from_vault_file(&self.key.password, vault_file, filename.clone())?; | ||||
| 		if !safe_account.check_password(&self.key.password) { | ||||
| 			warn!("Invalid vault key file: {:?}", filename); | ||||
| 			return Err(Error::InvalidPassword); | ||||
| 		} | ||||
| 		let mut safe_account = SafeAccount::from_vault_file(&self.key.password, vault_file, filename.clone())?; | ||||
| 
 | ||||
| 		safe_account.meta = json::insert_vault_name_to_json_meta(&safe_account.meta, &self.name) | ||||
| 			.map_err(|err| Error::Custom(format!("{:?}", err)))?; | ||||
| 		Ok(safe_account) | ||||
| 	} | ||||
| 
 | ||||
| 	fn write<T>(&self, account: SafeAccount, writer: &mut T) -> Result<(), Error> where T: io::Write { | ||||
| 		// all accounts share the same password
 | ||||
| 		if !account.check_password(&self.key.password) { | ||||
| 			return Err(Error::InvalidPassword); | ||||
| 		} | ||||
| 	fn write<T>(&self, mut account: SafeAccount, writer: &mut T) -> Result<(), Error> where T: io::Write { | ||||
| 		account.meta = json::remove_vault_name_from_json_meta(&account.meta) | ||||
| 			.map_err(|err| Error::Custom(format!("{:?}", err)))?; | ||||
| 
 | ||||
| 		let vault_file: json::VaultKeyFile = account.into_vault_file(self.key.iterations, &self.key.password)?; | ||||
| 		vault_file.write(writer).map_err(|e| Error::Custom(format!("{:?}", e))) | ||||
| @ -243,10 +235,12 @@ fn check_vault_file<P>(vault_dir_path: P, key: &VaultKey) -> Result<(), Error> w | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod test { | ||||
| 	use std::{env, fs}; | ||||
| 	use std::fs; | ||||
| 	use std::io::Write; | ||||
| 	use std::path::PathBuf; | ||||
| 	use dir::VaultKey; | ||||
| 	use super::{VAULT_FILE_NAME, check_vault_name, make_vault_dir_path, create_vault_file, check_vault_file, VaultDiskDirectory}; | ||||
| 	use devtools::RandomTempPath; | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn check_vault_name_succeeds() { | ||||
| @ -282,10 +276,9 @@ mod test { | ||||
| 	#[test] | ||||
| 	fn create_vault_file_succeeds() { | ||||
| 		// given
 | ||||
| 		let temp_path = RandomTempPath::new(); | ||||
| 		let key = VaultKey::new("password", 1024); | ||||
| 		let mut dir = env::temp_dir(); | ||||
| 		dir.push("create_vault_file_succeeds"); | ||||
| 		let mut vault_dir = dir.clone(); | ||||
| 		let mut vault_dir: PathBuf = temp_path.as_path().into(); | ||||
| 		vault_dir.push("vault"); | ||||
| 		fs::create_dir_all(&vault_dir).unwrap(); | ||||
| 
 | ||||
| @ -297,20 +290,16 @@ mod test { | ||||
| 		let mut vault_file_path = vault_dir.clone(); | ||||
| 		vault_file_path.push(VAULT_FILE_NAME); | ||||
| 		assert!(vault_file_path.exists() && vault_file_path.is_file()); | ||||
| 
 | ||||
| 		// cleanup
 | ||||
| 		let _ = fs::remove_dir_all(dir); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn check_vault_file_succeeds() { | ||||
| 		// given
 | ||||
| 		let temp_path = RandomTempPath::create_dir(); | ||||
| 		let key = VaultKey::new("password", 1024); | ||||
| 		let vault_file_contents = r#"{"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"758696c8dc6378ab9b25bb42790da2f5"},"ciphertext":"54eb50683717d41caaeb12ea969f2c159daada5907383f26f327606a37dc7168","kdf":"pbkdf2","kdfparams":{"c":1024,"dklen":32,"prf":"hmac-sha256","salt":"3c320fa566a1a7963ac8df68a19548d27c8f40bf92ef87c84594dcd5bbc402b6"},"mac":"9e5c2314c2a0781962db85611417c614bd6756666b6b1e93840f5b6ed895f003"}}"#; | ||||
| 		let mut dir = env::temp_dir(); | ||||
| 		dir.push("check_vault_file_succeeds"); | ||||
| 		fs::create_dir_all(&dir).unwrap(); | ||||
| 		let mut vault_file_path = dir.clone(); | ||||
| 		let dir: PathBuf = temp_path.as_path().into(); | ||||
| 		let mut vault_file_path: PathBuf = dir.clone(); | ||||
| 		vault_file_path.push(VAULT_FILE_NAME); | ||||
| 		{ | ||||
| 			let mut vault_file = fs::File::create(vault_file_path).unwrap(); | ||||
| @ -322,20 +311,16 @@ mod test { | ||||
| 
 | ||||
| 		// then
 | ||||
| 		assert!(result.is_ok()); | ||||
| 
 | ||||
| 		// cleanup
 | ||||
| 		let _ = fs::remove_dir_all(dir); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn check_vault_file_fails() { | ||||
| 		// given
 | ||||
| 		let temp_path = RandomTempPath::create_dir(); | ||||
| 		let key = VaultKey::new("password1", 1024); | ||||
| 		let mut dir = env::temp_dir(); | ||||
| 		dir.push("check_vault_file_fails"); | ||||
| 		let mut vault_file_path = dir.clone(); | ||||
| 		let dir: PathBuf = temp_path.as_path().into(); | ||||
| 		let mut vault_file_path: PathBuf = dir.clone(); | ||||
| 		vault_file_path.push(VAULT_FILE_NAME); | ||||
| 		fs::create_dir_all(&dir).unwrap(); | ||||
| 
 | ||||
| 		// when
 | ||||
| 		let result = check_vault_file(&dir, &key); | ||||
| @ -355,17 +340,14 @@ mod test { | ||||
| 
 | ||||
| 		// then
 | ||||
| 		assert!(result.is_err()); | ||||
| 
 | ||||
| 		// cleanup
 | ||||
| 		let _ = fs::remove_dir_all(dir); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn vault_directory_can_be_created() { | ||||
| 		// given
 | ||||
| 		let temp_path = RandomTempPath::new(); | ||||
| 		let key = VaultKey::new("password", 1024); | ||||
| 		let mut dir = env::temp_dir(); | ||||
| 		dir.push("vault_directory_can_be_created"); | ||||
| 		let dir: PathBuf = temp_path.as_path().into(); | ||||
| 
 | ||||
| 		// when
 | ||||
| 		let vault = VaultDiskDirectory::create(&dir, "vault", key.clone()); | ||||
| @ -378,17 +360,14 @@ mod test { | ||||
| 
 | ||||
| 		// then
 | ||||
| 		assert!(vault.is_ok()); | ||||
| 
 | ||||
| 		// cleanup
 | ||||
| 		let _ = fs::remove_dir_all(dir); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn vault_directory_cannot_be_created_if_already_exists() { | ||||
| 		// given
 | ||||
| 		let temp_path = RandomTempPath::new(); | ||||
| 		let key = VaultKey::new("password", 1024); | ||||
| 		let mut dir = env::temp_dir(); | ||||
| 		dir.push("vault_directory_cannot_be_created_if_already_exists"); | ||||
| 		let dir: PathBuf = temp_path.as_path().into(); | ||||
| 		let mut vault_dir = dir.clone(); | ||||
| 		vault_dir.push("vault"); | ||||
| 		fs::create_dir_all(&vault_dir).unwrap(); | ||||
| @ -398,25 +377,19 @@ mod test { | ||||
| 
 | ||||
| 		// then
 | ||||
| 		assert!(vault.is_err()); | ||||
| 
 | ||||
| 		// cleanup
 | ||||
| 		let _ = fs::remove_dir_all(dir); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn vault_directory_cannot_be_opened_if_not_exists() { | ||||
| 		// given
 | ||||
| 		let temp_path = RandomTempPath::create_dir(); | ||||
| 		let key = VaultKey::new("password", 1024); | ||||
| 		let mut dir = env::temp_dir(); | ||||
| 		dir.push("vault_directory_cannot_be_opened_if_not_exists"); | ||||
| 		let dir: PathBuf = temp_path.as_path().into(); | ||||
| 
 | ||||
| 		// when
 | ||||
| 		let vault = VaultDiskDirectory::at(&dir, "vault", key); | ||||
| 
 | ||||
| 		// then
 | ||||
| 		assert!(vault.is_err()); | ||||
| 
 | ||||
| 		// cleanup
 | ||||
| 		let _ = fs::remove_dir_all(dir); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -54,6 +54,10 @@ impl SimpleSecretStore for EthStore { | ||||
| 		self.store.insert_account(vault, secret, password) | ||||
| 	} | ||||
| 
 | ||||
| 	fn account_ref(&self, address: &Address) -> Result<StoreAccountRef, Error> { | ||||
| 		self.store.account_ref(address) | ||||
| 	} | ||||
| 
 | ||||
| 	fn accounts(&self) -> Result<Vec<StoreAccountRef>, Error> { | ||||
| 		self.store.accounts() | ||||
| 	} | ||||
| @ -88,8 +92,20 @@ impl SimpleSecretStore for EthStore { | ||||
| 		self.store.close_vault(name) | ||||
| 	} | ||||
| 
 | ||||
| 	fn change_vault_password(&self, name: &str, password: &str, new_password: &str) -> Result<(), Error> { | ||||
| 		self.store.change_vault_password(name, password, new_password) | ||||
| 	fn list_vaults(&self) -> Result<Vec<String>, Error> { | ||||
| 		self.store.list_vaults() | ||||
| 	} | ||||
| 
 | ||||
| 	fn list_opened_vaults(&self) -> Result<Vec<String>, Error> { | ||||
| 		self.store.list_opened_vaults() | ||||
| 	} | ||||
| 
 | ||||
| 	fn change_vault_password(&self, name: &str, new_password: &str) -> Result<(), Error> { | ||||
| 		self.store.change_vault_password(name, new_password) | ||||
| 	} | ||||
| 
 | ||||
| 	fn change_account_vault(&self, vault: SecretVaultRef, account: StoreAccountRef) -> Result<StoreAccountRef, Error> { | ||||
| 		self.store.change_account_vault(vault, account) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| @ -121,12 +137,6 @@ impl SecretStore for EthStore { | ||||
| 		Ok(()) | ||||
| 	} | ||||
| 
 | ||||
| 	fn move_account(&self, new_store: &SimpleSecretStore, new_vault: SecretVaultRef, account: &StoreAccountRef, password: &str, new_password: &str) -> Result<(), Error> { | ||||
| 		self.copy_account(new_store, new_vault, account, password, new_password)?; | ||||
| 		self.remove_account(account, password)?; | ||||
| 		Ok(()) | ||||
| 	} | ||||
| 
 | ||||
| 	fn public(&self, account: &StoreAccountRef, password: &str) -> Result<Public, Error> { | ||||
| 		let account = self.get(account)?; | ||||
| 		account.public(password) | ||||
| @ -296,6 +306,32 @@ impl EthMultiStore { | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	fn remove_safe_account(&self, account_ref: &StoreAccountRef, account: &SafeAccount) -> Result<(), Error> { | ||||
| 		// Remove from dir
 | ||||
| 		match account_ref.vault { | ||||
| 			SecretVaultRef::Root => self.dir.remove(&account)?, | ||||
| 			SecretVaultRef::Vault(ref vault_name) => self.vaults.lock().get(vault_name).ok_or(Error::VaultNotFound)?.remove(&account)?, | ||||
| 		}; | ||||
| 
 | ||||
| 		// Remove from cache
 | ||||
| 		let mut cache = self.cache.write(); | ||||
| 		let is_empty = { | ||||
| 			if let Some(accounts) = cache.get_mut(account_ref) { | ||||
| 				if let Some(position) = accounts.iter().position(|acc| acc == account) { | ||||
| 					accounts.remove(position); | ||||
| 				} | ||||
| 				accounts.is_empty() | ||||
| 			} else { | ||||
| 				false | ||||
| 			} | ||||
| 		}; | ||||
| 
 | ||||
| 		if is_empty { | ||||
| 			cache.remove(account_ref); | ||||
| 		} | ||||
| 
 | ||||
| 		return Ok(()); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl SimpleSecretStore for EthMultiStore { | ||||
| @ -306,6 +342,14 @@ impl SimpleSecretStore for EthMultiStore { | ||||
| 		self.import(vault, account) | ||||
| 	} | ||||
| 
 | ||||
| 	fn account_ref(&self, address: &Address) -> Result<StoreAccountRef, Error> { | ||||
| 		self.reload_accounts()?; | ||||
| 		self.cache.read().keys() | ||||
| 			.find(|r| &r.address == address) | ||||
| 			.cloned() | ||||
| 			.ok_or(Error::InvalidAccount) | ||||
| 	} | ||||
| 
 | ||||
| 	fn accounts(&self) -> Result<Vec<StoreAccountRef>, Error> { | ||||
| 		self.reload_accounts()?; | ||||
| 		Ok(self.cache.read().keys().cloned().collect()) | ||||
| @ -320,50 +364,20 @@ impl SimpleSecretStore for EthMultiStore { | ||||
| 				continue; | ||||
| 			} | ||||
| 
 | ||||
| 			// Remove from dir
 | ||||
| 			match account_ref.vault { | ||||
| 				SecretVaultRef::Root => self.dir.remove(&account)?, | ||||
| 				SecretVaultRef::Vault(ref vault_name) => self.vaults.lock().get(vault_name).ok_or(Error::VaultNotFound)?.remove(&account)?, | ||||
| 			}; | ||||
| 
 | ||||
| 			// Remove from cache
 | ||||
| 			let mut cache = self.cache.write(); | ||||
| 			let is_empty = { | ||||
| 				if let Some(accounts) = cache.get_mut(account_ref) { | ||||
| 					if let Some(position) = accounts.iter().position(|acc| acc == &account) { | ||||
| 						accounts.remove(position); | ||||
| 					} | ||||
| 					accounts.is_empty() | ||||
| 				} else { | ||||
| 					false | ||||
| 				} | ||||
| 			}; | ||||
| 
 | ||||
| 			if is_empty { | ||||
| 				cache.remove(account_ref); | ||||
| 			} | ||||
| 
 | ||||
| 			return Ok(()); | ||||
| 			return self.remove_safe_account(account_ref, &account); | ||||
| 		} | ||||
| 		Err(Error::InvalidPassword) | ||||
| 	} | ||||
| 
 | ||||
| 	fn change_password(&self, account_ref: &StoreAccountRef, old_password: &str, new_password: &str) -> Result<(), Error> { | ||||
| 		match account_ref.vault { | ||||
| 			SecretVaultRef::Root => { | ||||
| 				let accounts = self.get(account_ref)?; | ||||
| 		let accounts = self.get(account_ref)?; | ||||
| 
 | ||||
| 				for account in accounts { | ||||
| 					// Change password
 | ||||
| 					let new_account = account.change_password(old_password, new_password, self.iterations)?; | ||||
| 					self.update(account_ref, account, new_account)?; | ||||
| 				} | ||||
| 				Ok(()) | ||||
| 			}, | ||||
| 			SecretVaultRef::Vault(ref vault_name) => { | ||||
| 				self.change_vault_password(vault_name, old_password, new_password) | ||||
| 			}, | ||||
| 		for account in accounts { | ||||
| 			// Change password
 | ||||
| 			let new_account = account.change_password(old_password, new_password, self.iterations)?; | ||||
| 			self.update(account_ref, account, new_account)?; | ||||
| 		} | ||||
| 		Ok(()) | ||||
| 	} | ||||
| 
 | ||||
| 	fn sign(&self, account: &StoreAccountRef, password: &str, message: &Message) -> Result<Signature, Error> { | ||||
| @ -435,10 +449,20 @@ impl SimpleSecretStore for EthMultiStore { | ||||
| 		Ok(()) | ||||
| 	} | ||||
| 
 | ||||
| 	fn change_vault_password(&self, name: &str, password: &str, new_password: &str) -> Result<(), Error> { | ||||
| 	fn list_vaults(&self) -> Result<Vec<String>, Error> { | ||||
| 		let vault_provider = self.dir.as_vault_provider().ok_or(Error::VaultsAreNotSupported)?; | ||||
| 		let vault = vault_provider.open(name, VaultKey::new(password, self.iterations))?; | ||||
| 		match vault.set_key(VaultKey::new(password, self.iterations), VaultKey::new(new_password, self.iterations)) { | ||||
| 		vault_provider.list_vaults() | ||||
| 	} | ||||
| 
 | ||||
| 	fn list_opened_vaults(&self) -> Result<Vec<String>, Error> { | ||||
| 		Ok(self.vaults.lock().keys().cloned().collect()) | ||||
| 	} | ||||
| 
 | ||||
| 	fn change_vault_password(&self, name: &str, new_password: &str) -> Result<(), Error> { | ||||
| 		let old_key = self.vaults.lock().get(name).map(|v| v.key()).ok_or(Error::VaultNotFound)?; | ||||
| 		let vault_provider = self.dir.as_vault_provider().ok_or(Error::VaultsAreNotSupported)?; | ||||
| 		let vault = vault_provider.open(name, old_key)?; | ||||
| 		match vault.set_key(VaultKey::new(new_password, self.iterations)) { | ||||
| 			Ok(_) => { | ||||
| 				self.close_vault(name) | ||||
| 					.and_then(|_| self.open_vault(name, new_password)) | ||||
| @ -446,7 +470,7 @@ impl SimpleSecretStore for EthMultiStore { | ||||
| 			Err(SetKeyError::Fatal(err)) => { | ||||
| 				let _ = self.close_vault(name); | ||||
| 				Err(err) | ||||
| 			} | ||||
| 			}, | ||||
| 			Err(SetKeyError::NonFatalNew(err)) => { | ||||
| 				let _ = self.close_vault(name) | ||||
| 					.and_then(|_| self.open_vault(name, new_password)); | ||||
| @ -455,17 +479,28 @@ impl SimpleSecretStore for EthMultiStore { | ||||
| 			Err(SetKeyError::NonFatalOld(err)) => Err(err), | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	fn change_account_vault(&self, vault: SecretVaultRef, account_ref: StoreAccountRef) -> Result<StoreAccountRef, Error> { | ||||
| 		if account_ref.vault == vault { | ||||
| 			return Ok(account_ref); | ||||
| 		} | ||||
| 
 | ||||
| 		let account = self.get(&account_ref)?.into_iter().nth(0).ok_or(Error::InvalidAccount)?; | ||||
| 		let new_account_ref = self.import(vault, account.clone())?; | ||||
| 		self.remove_safe_account(&account_ref, &account)?; | ||||
| 		self.reload_accounts()?; | ||||
| 		Ok(new_account_ref) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
| 
 | ||||
| 	use std::{env, fs}; | ||||
| 	use std::path::PathBuf; | ||||
| 	use dir::{KeyDirectory, MemoryDirectory, RootDiskDirectory}; | ||||
| 	use ethkey::{Random, Generator, KeyPair}; | ||||
| 	use secret_store::{SimpleSecretStore, SecretStore, SecretVaultRef, StoreAccountRef}; | ||||
| 	use super::{EthStore, EthMultiStore}; | ||||
| 	use devtools::RandomTempPath; | ||||
| 
 | ||||
| 	fn keypair() -> KeyPair { | ||||
| 		Random.generate().unwrap() | ||||
| @ -481,26 +516,17 @@ mod tests { | ||||
| 
 | ||||
| 	struct RootDiskDirectoryGuard { | ||||
| 		pub key_dir: Option<Box<KeyDirectory>>, | ||||
| 		path: Option<PathBuf>, | ||||
| 		_path: RandomTempPath, | ||||
| 	} | ||||
| 
 | ||||
| 	impl RootDiskDirectoryGuard { | ||||
| 		pub fn new(test_name: &str) -> Self { | ||||
| 			let mut path = env::temp_dir(); | ||||
| 			path.push(test_name); | ||||
| 			fs::create_dir_all(&path).unwrap(); | ||||
| 		pub fn new() -> Self { | ||||
| 			let temp_path = RandomTempPath::new(); | ||||
| 			let disk_dir = Box::new(RootDiskDirectory::create(temp_path.as_path()).unwrap()); | ||||
| 
 | ||||
| 			RootDiskDirectoryGuard { | ||||
| 				key_dir: Some(Box::new(RootDiskDirectory::create(&path).unwrap())), | ||||
| 				path: Some(path), | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	impl Drop for RootDiskDirectoryGuard { | ||||
| 		fn drop(&mut self) { | ||||
| 			if let Some(path) = self.path.take() { | ||||
| 				let _ = fs::remove_dir_all(path); | ||||
| 				key_dir: Some(disk_dir), | ||||
| 				_path: temp_path, | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| @ -606,7 +632,7 @@ mod tests { | ||||
| 	#[test] | ||||
| 	fn should_create_and_open_vaults() { | ||||
| 		// given
 | ||||
| 		let mut dir = RootDiskDirectoryGuard::new("should_create_and_open_vaults"); | ||||
| 		let mut dir = RootDiskDirectoryGuard::new(); | ||||
| 		let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap(); | ||||
| 		let name1 = "vault1"; let password1 = "password1"; | ||||
| 		let name2 = "vault2"; let password2 = "password2"; | ||||
| @ -660,7 +686,7 @@ mod tests { | ||||
| 	#[test] | ||||
| 	fn should_move_vault_acounts() { | ||||
| 		// given
 | ||||
| 		let mut dir = RootDiskDirectoryGuard::new("should_move_vault_acounts"); | ||||
| 		let mut dir = RootDiskDirectoryGuard::new(); | ||||
| 		let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap(); | ||||
| 		let name1 = "vault1"; let password1 = "password1"; | ||||
| 		let name2 = "vault2"; let password2 = "password2"; | ||||
| @ -677,72 +703,42 @@ mod tests { | ||||
| 		let account3 = store.insert_account(SecretVaultRef::Root, keypair3.secret().clone(), password3).unwrap(); | ||||
| 
 | ||||
| 		// then
 | ||||
| 		store.move_account(&store, SecretVaultRef::Root, &account1, password1, password2).unwrap(); | ||||
| 		store.move_account(&store, SecretVaultRef::Vault(name2.to_owned()), &account2, password1, password2).unwrap(); | ||||
| 		store.move_account(&store, SecretVaultRef::Vault(name2.to_owned()), &account3, password3, password2).unwrap(); | ||||
| 		let account1 = store.change_account_vault(SecretVaultRef::Root, account1.clone()).unwrap(); | ||||
| 		let account2 = store.change_account_vault(SecretVaultRef::Vault(name2.to_owned()), account2.clone()).unwrap(); | ||||
| 		let account3 = store.change_account_vault(SecretVaultRef::Vault(name2.to_owned()), account3).unwrap(); | ||||
| 		let accounts = store.accounts().unwrap(); | ||||
| 		assert_eq!(accounts.len(), 3); | ||||
| 		assert!(accounts.iter().any(|a| a == &StoreAccountRef::root(account1.address.clone()))); | ||||
| 		assert!(accounts.iter().any(|a| a == &StoreAccountRef::vault(name2, account2.address.clone()))); | ||||
| 		assert!(accounts.iter().any(|a| a == &StoreAccountRef::vault(name2, account3.address.clone()))); | ||||
| 
 | ||||
| 		// and then
 | ||||
| 		assert_eq!(store.meta(&StoreAccountRef::root(account1.address)).unwrap(), r#"{}"#); | ||||
| 		assert_eq!(store.meta(&StoreAccountRef::vault("vault2", account2.address)).unwrap(), r#"{"vault":"vault2"}"#); | ||||
| 		assert_eq!(store.meta(&StoreAccountRef::vault("vault2", account3.address)).unwrap(), r#"{"vault":"vault2"}"#); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn should_not_remove_account_when_moving_to_self() { | ||||
| 		// given
 | ||||
| 		let mut dir = RootDiskDirectoryGuard::new("should_not_remove_account_when_moving_to_self"); | ||||
| 		let mut dir = RootDiskDirectoryGuard::new(); | ||||
| 		let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap(); | ||||
| 		let password1 = "password1"; | ||||
| 		let keypair1 = keypair(); | ||||
| 
 | ||||
| 		// when
 | ||||
| 		let account1 = store.insert_account(SecretVaultRef::Root, keypair1.secret().clone(), password1).unwrap(); | ||||
| 		store.move_account(&store, SecretVaultRef::Root, &account1, password1, password1).unwrap(); | ||||
| 		store.change_account_vault(SecretVaultRef::Root, account1).unwrap(); | ||||
| 
 | ||||
| 		// then
 | ||||
| 		let accounts = store.accounts().unwrap(); | ||||
| 		assert_eq!(accounts.len(), 1); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn should_not_move_account_when_vault_password_incorrect() { | ||||
| 		// given
 | ||||
| 		let mut dir = RootDiskDirectoryGuard::new("should_not_move_account_when_vault_password_incorrect"); | ||||
| 		let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap(); | ||||
| 		let name1 = "vault1"; let password1 = "password1"; | ||||
| 		let name2 = "vault2"; let password2 = "password2"; | ||||
| 		let keypair1 = keypair(); | ||||
| 
 | ||||
| 		// when
 | ||||
| 		store.create_vault(name1, password1).unwrap(); | ||||
| 		store.create_vault(name2, password2).unwrap(); | ||||
| 		let account1 = store.insert_account(SecretVaultRef::Vault(name1.to_owned()), keypair1.secret().clone(), password1).unwrap(); | ||||
| 
 | ||||
| 		// then
 | ||||
| 		store.move_account(&store, SecretVaultRef::Root, &account1, password2, password1).unwrap_err(); | ||||
| 		store.move_account(&store, SecretVaultRef::Vault(name2.to_owned()), &account1, password1, password1).unwrap_err(); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn should_not_insert_account_when_vault_password_incorrect() { | ||||
| 		// given
 | ||||
| 		let mut dir = RootDiskDirectoryGuard::new("should_not_insert_account_when_vault_password_incorrect"); | ||||
| 		let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap(); | ||||
| 		let name1 = "vault1"; let password1 = "password1"; | ||||
| 		let password2 = "password2"; | ||||
| 		let keypair1 = keypair(); | ||||
| 
 | ||||
| 		// when
 | ||||
| 		store.create_vault(name1, password1).unwrap(); | ||||
| 
 | ||||
| 		// then
 | ||||
| 		store.insert_account(SecretVaultRef::Vault(name1.to_owned()), keypair1.secret().clone(), password2).unwrap_err(); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn should_remove_account_from_vault() { | ||||
| 		// given
 | ||||
| 		let mut dir = RootDiskDirectoryGuard::new("should_remove_account_from_vault"); | ||||
| 		let mut dir = RootDiskDirectoryGuard::new(); | ||||
| 		let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap(); | ||||
| 		let name1 = "vault1"; let password1 = "password1"; | ||||
| 		let keypair1 = keypair(); | ||||
| @ -760,7 +756,7 @@ mod tests { | ||||
| 	#[test] | ||||
| 	fn should_not_remove_account_from_vault_when_password_is_incorrect() { | ||||
| 		// given
 | ||||
| 		let mut dir = RootDiskDirectoryGuard::new("should_not_remove_account_from_vault_when_password_is_incorrect"); | ||||
| 		let mut dir = RootDiskDirectoryGuard::new(); | ||||
| 		let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap(); | ||||
| 		let name1 = "vault1"; let password1 = "password1"; | ||||
| 		let password2 = "password2"; | ||||
| @ -779,7 +775,7 @@ mod tests { | ||||
| 	#[test] | ||||
| 	fn should_change_vault_password() { | ||||
| 		// given
 | ||||
| 		let mut dir = RootDiskDirectoryGuard::new("should_change_vault_password"); | ||||
| 		let mut dir = RootDiskDirectoryGuard::new(); | ||||
| 		let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap(); | ||||
| 		let name = "vault"; let password = "password"; | ||||
| 		let keypair = keypair(); | ||||
| @ -791,9 +787,7 @@ mod tests { | ||||
| 		// then
 | ||||
| 		assert_eq!(store.accounts().unwrap().len(), 1); | ||||
| 		let new_password = "new_password"; | ||||
| 		store.change_vault_password(name, "bad_password", new_password).unwrap_err(); | ||||
| 		assert_eq!(store.accounts().unwrap().len(), 1); | ||||
| 		store.change_vault_password(name, password, new_password).unwrap(); | ||||
| 		store.change_vault_password(name, new_password).unwrap(); | ||||
| 		assert_eq!(store.accounts().unwrap().len(), 1); | ||||
| 
 | ||||
| 		// and when
 | ||||
| @ -803,4 +797,46 @@ mod tests { | ||||
| 		store.open_vault(name, new_password).unwrap(); | ||||
| 		assert_eq!(store.accounts().unwrap().len(), 1); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn should_have_different_passwords_for_vault_secret_and_meta() { | ||||
| 		// given
 | ||||
| 		let mut dir = RootDiskDirectoryGuard::new(); | ||||
| 		let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap(); | ||||
| 		let name = "vault"; let password = "password"; | ||||
| 		let secret_password = "sec_password"; | ||||
| 		let keypair = keypair(); | ||||
| 
 | ||||
| 		// when
 | ||||
| 		store.create_vault(name, password).unwrap(); | ||||
| 		let account_ref = store.insert_account(SecretVaultRef::Vault(name.to_owned()), keypair.secret().clone(), secret_password).unwrap(); | ||||
| 
 | ||||
| 		// then
 | ||||
| 		assert_eq!(store.accounts().unwrap().len(), 1); | ||||
| 		let new_secret_password = "new_sec_password"; | ||||
| 		store.change_password(&account_ref, secret_password, new_secret_password).unwrap(); | ||||
| 		assert_eq!(store.accounts().unwrap().len(), 1); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn should_list_opened_vaults() { | ||||
| 		// given
 | ||||
| 		let mut dir = RootDiskDirectoryGuard::new(); | ||||
| 		let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap(); | ||||
| 		let name1 = "vault1"; let password1 = "password1"; | ||||
| 		let name2 = "vault2"; let password2 = "password2"; | ||||
| 		let name3 = "vault3"; let password3 = "password3"; | ||||
| 
 | ||||
| 		// when
 | ||||
| 		store.create_vault(name1, password1).unwrap(); | ||||
| 		store.create_vault(name2, password2).unwrap(); | ||||
| 		store.create_vault(name3, password3).unwrap(); | ||||
| 		store.close_vault(name2).unwrap(); | ||||
| 
 | ||||
| 		// then
 | ||||
| 		let opened_vaults = store.list_opened_vaults().unwrap(); | ||||
| 		assert_eq!(opened_vaults.len(), 2); | ||||
| 		assert!(opened_vaults.iter().any(|v| &*v == name1)); | ||||
| 		assert!(opened_vaults.iter().any(|v| &*v == name3)); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -21,6 +21,6 @@ pub use self::kdf::{Kdf, KdfSer, Prf, Pbkdf2, Scrypt, KdfSerParams}; | ||||
| pub use self::key_file::KeyFile; | ||||
| pub use self::presale::{PresaleWallet, Encseed}; | ||||
| pub use self::vault_file::VaultFile; | ||||
| pub use self::vault_key_file::{VaultKeyFile, VaultKeyMeta}; | ||||
| pub use self::vault_key_file::{VaultKeyFile, VaultKeyMeta, insert_vault_name_to_json_meta, remove_vault_name_from_json_meta}; | ||||
| pub use self::version::Version; | ||||
| 
 | ||||
|  | ||||
| @ -18,8 +18,13 @@ use std::io::{Read, Write}; | ||||
| use serde::{Deserialize, Deserializer, Error}; | ||||
| use serde::de::{Visitor, MapVisitor}; | ||||
| use serde_json; | ||||
| use serde_json::value::Value; | ||||
| use serde_json::error; | ||||
| use super::{Uuid, Version, Crypto, H160}; | ||||
| 
 | ||||
| /// Meta key name for vault field
 | ||||
| const VAULT_NAME_META_KEY: &'static str = "vault"; | ||||
| 
 | ||||
| /// Key file as stored in vaults
 | ||||
| #[derive(Debug, PartialEq, Serialize)] | ||||
| pub struct VaultKeyFile { | ||||
| @ -27,9 +32,9 @@ pub struct VaultKeyFile { | ||||
| 	pub id: Uuid, | ||||
| 	/// Key version
 | ||||
| 	pub version: Version, | ||||
| 	/// Encrypted secret
 | ||||
| 	/// Secret, encrypted with account password
 | ||||
| 	pub crypto: Crypto, | ||||
| 	/// Encrypted serialized `VaultKeyMeta`
 | ||||
| 	/// Serialized `VaultKeyMeta`, encrypted with vault password
 | ||||
| 	pub metacrypto: Crypto, | ||||
| } | ||||
| 
 | ||||
| @ -44,6 +49,38 @@ pub struct VaultKeyMeta { | ||||
| 	pub meta: Option<String>, | ||||
| } | ||||
| 
 | ||||
| /// Insert vault name to the JSON meta field
 | ||||
| pub fn insert_vault_name_to_json_meta(meta: &str, vault_name: &str) -> Result<String, error::Error> { | ||||
| 	let mut meta = if meta.is_empty() { | ||||
| 		Value::Object(serde_json::Map::new()) | ||||
| 	} else { | ||||
| 		serde_json::from_str(meta)? | ||||
| 	}; | ||||
| 
 | ||||
| 	if let Some(meta_obj) = meta.as_object_mut() { | ||||
| 		meta_obj.insert(VAULT_NAME_META_KEY.to_owned(), Value::String(vault_name.to_owned())); | ||||
| 		serde_json::to_string(meta_obj) | ||||
| 	} else { | ||||
| 		Err(error::Error::custom("Meta is expected to be a serialized JSON object")) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /// Remove vault name from the JSON meta field
 | ||||
| pub fn remove_vault_name_from_json_meta(meta: &str) -> Result<String, error::Error> { | ||||
| 	let mut meta = if meta.is_empty() { | ||||
| 		Value::Object(serde_json::Map::new()) | ||||
| 	} else { | ||||
| 		serde_json::from_str(meta)? | ||||
| 	}; | ||||
| 
 | ||||
| 	if let Some(meta_obj) = meta.as_object_mut() { | ||||
| 		meta_obj.remove(VAULT_NAME_META_KEY); | ||||
| 		serde_json::to_string(meta_obj) | ||||
| 	} else { | ||||
| 		Err(error::Error::custom("Meta is expected to be a serialized JSON object")) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| enum VaultKeyFileField { | ||||
| 	Id, | ||||
| 	Version, | ||||
| @ -244,7 +281,8 @@ impl VaultKeyMeta { | ||||
| #[cfg(test)] | ||||
| mod test { | ||||
| 	use serde_json; | ||||
| 	use json::{VaultKeyFile, Version, Crypto, Cipher, Aes128Ctr, Kdf, Pbkdf2, Prf}; | ||||
| 	use json::{VaultKeyFile, Version, Crypto, Cipher, Aes128Ctr, Kdf, Pbkdf2, Prf, | ||||
| 		insert_vault_name_to_json_meta, remove_vault_name_from_json_meta}; | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn to_and_from_json() { | ||||
| @ -284,4 +322,28 @@ mod test { | ||||
| 
 | ||||
| 		assert_eq!(file, deserialized); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn vault_name_inserted_to_json_meta() { | ||||
| 		assert_eq!(insert_vault_name_to_json_meta(r#""#, "MyVault").unwrap(), r#"{"vault":"MyVault"}"#); | ||||
| 		assert_eq!(insert_vault_name_to_json_meta(r#"{"tags":["kalabala"]}"#, "MyVault").unwrap(), r#"{"tags":["kalabala"],"vault":"MyVault"}"#); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn vault_name_not_inserted_to_json_meta() { | ||||
| 		assert!(insert_vault_name_to_json_meta(r#"///3533"#, "MyVault").is_err()); | ||||
| 		assert!(insert_vault_name_to_json_meta(r#""string""#, "MyVault").is_err()); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn vault_name_removed_from_json_meta() { | ||||
| 		assert_eq!(remove_vault_name_from_json_meta(r#"{"vault":"MyVault"}"#).unwrap(), r#"{}"#); | ||||
| 		assert_eq!(remove_vault_name_from_json_meta(r#"{"tags":["kalabala"],"vault":"MyVault"}"#).unwrap(), r#"{"tags":["kalabala"]}"#); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn vault_name_not_removed_from_json_meta() { | ||||
| 		assert!(remove_vault_name_from_json_meta(r#"///3533"#).is_err()); | ||||
| 		assert!(remove_vault_name_from_json_meta(r#""string""#).is_err()); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -28,6 +28,7 @@ extern crate rustc_serialize; | ||||
| extern crate crypto as rcrypto; | ||||
| extern crate tiny_keccak; | ||||
| extern crate parking_lot; | ||||
| extern crate ethcore_devtools as devtools; | ||||
| 
 | ||||
| // reexport it nicely
 | ||||
| extern crate ethkey as _ethkey; | ||||
|  | ||||
| @ -47,6 +47,9 @@ pub trait SimpleSecretStore: Send + Sync { | ||||
| 	fn decrypt(&self, account: &StoreAccountRef, password: &str, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error>; | ||||
| 
 | ||||
| 	fn accounts(&self) -> Result<Vec<StoreAccountRef>, Error>; | ||||
| 	/// Get reference to some account with given address.
 | ||||
| 	/// This method could be removed if we will guarantee that there is max(1) account for given address.
 | ||||
| 	fn account_ref(&self, address: &Address) -> Result<StoreAccountRef, Error>; | ||||
| 
 | ||||
| 	/// Create new vault with given password
 | ||||
| 	fn create_vault(&self, name: &str, password: &str) -> Result<(), Error>; | ||||
| @ -54,15 +57,20 @@ pub trait SimpleSecretStore: Send + Sync { | ||||
| 	fn open_vault(&self, name: &str, password: &str) -> Result<(), Error>; | ||||
| 	/// Close vault
 | ||||
| 	fn close_vault(&self, name: &str) -> Result<(), Error>; | ||||
| 	/// List all vaults
 | ||||
| 	fn list_vaults(&self) -> Result<Vec<String>, Error>; | ||||
| 	/// List all currently opened vaults
 | ||||
| 	fn list_opened_vaults(&self) -> Result<Vec<String>, Error>; | ||||
| 	/// Change vault password
 | ||||
| 	fn change_vault_password(&self, name: &str, password: &str, new_password: &str) -> Result<(), Error>; | ||||
| 	fn change_vault_password(&self, name: &str, new_password: &str) -> Result<(), Error>; | ||||
| 	/// Cnage account' vault
 | ||||
| 	fn change_account_vault(&self, vault: SecretVaultRef, account: StoreAccountRef) -> Result<StoreAccountRef, Error>; | ||||
| } | ||||
| 
 | ||||
| pub trait SecretStore: SimpleSecretStore { | ||||
| 	fn import_presale(&self, vault: SecretVaultRef, json: &[u8], password: &str) -> Result<StoreAccountRef, Error>; | ||||
| 	fn import_wallet(&self, vault: SecretVaultRef, json: &[u8], password: &str) -> Result<StoreAccountRef, Error>; | ||||
| 	fn copy_account(&self, new_store: &SimpleSecretStore, new_vault: SecretVaultRef, account: &StoreAccountRef, password: &str, new_password: &str) -> Result<(), Error>; | ||||
| 	fn move_account(&self, new_store: &SimpleSecretStore, new_vault: SecretVaultRef, account: &StoreAccountRef, password: &str, new_password: &str) -> Result<(), Error>; | ||||
| 	fn test_password(&self, account: &StoreAccountRef, password: &str) -> Result<bool, Error>; | ||||
| 
 | ||||
| 	fn public(&self, account: &StoreAccountRef, password: &str) -> Result<Public, Error>; | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "parity.js", | ||||
|   "version": "0.3.66", | ||||
|   "version": "0.3.70", | ||||
|   "main": "release/index.js", | ||||
|   "jsnext:main": "src/index.js", | ||||
|   "author": "Parity Team <admin@parity.io>", | ||||
|  | ||||
| @ -55,6 +55,7 @@ if (process.env.NODE_ENV === 'development') { | ||||
| 
 | ||||
| const AUTH_HASH = '#/auth?'; | ||||
| const parityUrl = process.env.PARITY_URL || window.location.host; | ||||
| const urlScheme = window.location.href.match(/^https/) ? 'wss://' : 'ws://'; | ||||
| 
 | ||||
| let token = null; | ||||
| 
 | ||||
| @ -62,7 +63,7 @@ if (window.location.hash && window.location.hash.indexOf(AUTH_HASH) === 0) { | ||||
|   token = qs.parse(window.location.hash.substr(AUTH_HASH.length)).token; | ||||
| } | ||||
| 
 | ||||
| const api = new SecureApi(`ws://${parityUrl}`, token); | ||||
| const api = new SecureApi(`${urlScheme}${parityUrl}`, token); | ||||
| 
 | ||||
| patchApi(api); | ||||
| ContractInstances.create(api); | ||||
|  | ||||
| @ -30,6 +30,10 @@ | ||||
| .list { | ||||
|   margin-bottom: 1.5em; | ||||
| 
 | ||||
|   &:last-child { | ||||
|     margin-bottom: 0; | ||||
|   } | ||||
| 
 | ||||
|   .background { | ||||
|     padding: 0.5em 0; | ||||
|   } | ||||
|  | ||||
| @ -31,8 +31,10 @@ | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .selected, .unselected { | ||||
| .selected, | ||||
| .unselected { | ||||
|   margin-bottom: 0.25em; | ||||
|   width: 100%; | ||||
| 
 | ||||
|   &:focus { | ||||
|     outline: none; | ||||
|  | ||||
| @ -23,10 +23,11 @@ | ||||
| 
 | ||||
| .compact, | ||||
| .padded { | ||||
|   background-color: transparent !important; | ||||
|   border-radius: 0 !important; | ||||
|   height: 100%; | ||||
|   position: relative; | ||||
|   overflow: auto; | ||||
|   background-color: transparent !important; | ||||
| } | ||||
| 
 | ||||
| .compact { | ||||
|  | ||||
| @ -50,6 +50,7 @@ $popoverZ: 3600; | ||||
|   left: 0; | ||||
|   right: 0; | ||||
|   opacity: 0.25; | ||||
|   z-index: -1; | ||||
| } | ||||
| 
 | ||||
| .overlay { | ||||
|  | ||||
| @ -34,7 +34,11 @@ export default class Portal extends Component { | ||||
|     activeStep: PropTypes.number, | ||||
|     busy: PropTypes.bool, | ||||
|     busySteps: PropTypes.array, | ||||
|     buttons: PropTypes.array, | ||||
|     buttons: PropTypes.oneOfType([ | ||||
|       PropTypes.array, | ||||
|       PropTypes.node, | ||||
|       PropTypes.object | ||||
|     ]), | ||||
|     children: PropTypes.node, | ||||
|     className: PropTypes.string, | ||||
|     hideClose: PropTypes.bool, | ||||
|  | ||||
| @ -21,9 +21,12 @@ const MAX_GAS_ESTIMATION = '50000000'; | ||||
| 
 | ||||
| const NULL_ADDRESS = '0000000000000000000000000000000000000000'; | ||||
| 
 | ||||
| const DOMAIN = '.web3.site'; | ||||
| 
 | ||||
| export { | ||||
|   DEFAULT_GAS, | ||||
|   DEFAULT_GASPRICE, | ||||
|   MAX_GAS_ESTIMATION, | ||||
|   NULL_ADDRESS | ||||
|   NULL_ADDRESS, | ||||
|   DOMAIN | ||||
| }; | ||||
|  | ||||
| @ -16,7 +16,9 @@ | ||||
| 
 | ||||
| import base32 from 'base32.js'; | ||||
| 
 | ||||
| const BASE_URL = '.web.web3.site'; | ||||
| import { DOMAIN } from './constants'; | ||||
| 
 | ||||
| const BASE_URL = `.web${DOMAIN}`; | ||||
| const ENCODER_OPTS = { type: 'crockford' }; | ||||
| 
 | ||||
| export function encodePath (token, url) { | ||||
|  | ||||
| @ -21,6 +21,8 @@ import { action, computed, observable } from 'mobx'; | ||||
| import store from 'store'; | ||||
| import browser from 'useragent.js/lib/browser'; | ||||
| 
 | ||||
| import { DOMAIN } from '~/util/constants'; | ||||
| 
 | ||||
| const A_DAY = 24 * 60 * 60 * 1000; | ||||
| const NEXT_DISPLAY = '_parity::extensionWarning::nextDisplay'; | ||||
| 
 | ||||
| @ -68,6 +70,19 @@ export default class Store { | ||||
|   installExtension = () => { | ||||
|     this.setInstalling(true); | ||||
| 
 | ||||
|     if (window.location.hostname.toString().endsWith(DOMAIN)) { | ||||
|       return this.inlineInstall() | ||||
|         .catch((error) => { | ||||
|           console.warn('Unable to perform direct install', error); | ||||
|           window.open(EXTENSION_PAGE, '_blank'); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     window.open(EXTENSION_PAGE, '_blank'); | ||||
|     return Promise.resolve(true); | ||||
|   } | ||||
| 
 | ||||
|   inlineInstall = () => { | ||||
|     return new Promise((resolve, reject) => { | ||||
|       const link = document.createElement('link'); | ||||
| 
 | ||||
| @ -80,10 +95,6 @@ export default class Store { | ||||
|       } else { | ||||
|         reject(new Error('Direct installation failed.')); | ||||
|       } | ||||
|     }) | ||||
|     .catch((error) => { | ||||
|       console.warn('Unable to perform direct install', error); | ||||
|       window.open(EXTENSION_PAGE, '_blank'); | ||||
|     }); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -201,6 +201,53 @@ impl ParityAccounts for ParityAccountsClient { | ||||
| 
 | ||||
| 		Ok(into_vec(store.list_geth_accounts(false))) | ||||
| 	} | ||||
| 
 | ||||
| 	fn create_vault(&self, name: String, password: String) -> Result<bool, Error> { | ||||
| 		take_weak!(self.accounts) | ||||
| 			.create_vault(&name, &password) | ||||
| 			.map_err(|e| errors::account("Could not create vault.", e)) | ||||
| 			.map(|_| true) | ||||
| 	} | ||||
| 
 | ||||
| 	fn open_vault(&self, name: String, password: String) -> Result<bool, Error> { | ||||
| 		take_weak!(self.accounts) | ||||
| 			.open_vault(&name, &password) | ||||
| 			.map_err(|e| errors::account("Could not open vault.", e)) | ||||
| 			.map(|_| true) | ||||
| 	} | ||||
| 
 | ||||
| 	fn close_vault(&self, name: String) -> Result<bool, Error> { | ||||
| 		take_weak!(self.accounts) | ||||
| 			.close_vault(&name) | ||||
| 			.map_err(|e| errors::account("Could not close vault.", e)) | ||||
| 			.map(|_| true) | ||||
| 	} | ||||
| 
 | ||||
| 	fn list_vaults(&self) -> Result<Vec<String>, Error> { | ||||
| 		take_weak!(self.accounts) | ||||
| 			.list_vaults() | ||||
| 			.map_err(|e| errors::account("Could not list vaults.", e)) | ||||
| 	} | ||||
| 
 | ||||
| 	fn list_opened_vaults(&self) -> Result<Vec<String>, Error> { | ||||
| 		take_weak!(self.accounts) | ||||
| 			.list_opened_vaults() | ||||
| 			.map_err(|e| errors::account("Could not list vaults.", e)) | ||||
| 	} | ||||
| 
 | ||||
| 	fn change_vault_password(&self, name: String, new_password: String) -> Result<bool, Error> { | ||||
| 		take_weak!(self.accounts) | ||||
| 			.change_vault_password(&name, &new_password) | ||||
| 			.map_err(|e| errors::account("Could not change vault password.", e)) | ||||
| 			.map(|_| true) | ||||
| 	} | ||||
| 
 | ||||
| 	fn change_vault(&self, address: RpcH160, new_vault: String) -> Result<bool, Error> { | ||||
| 		take_weak!(self.accounts) | ||||
| 			.change_vault(address.into(), &new_vault) | ||||
| 			.map_err(|e| errors::account("Could not change vault.", e)) | ||||
| 			.map(|_| true) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| fn into_vec<A, B>(a: Vec<A>) -> Vec<B> where | ||||
|  | ||||
| @ -17,6 +17,9 @@ | ||||
| use std::sync::Arc; | ||||
| 
 | ||||
| use ethcore::account_provider::AccountProvider; | ||||
| use ethstore::EthStore; | ||||
| use ethstore::dir::RootDiskDirectory; | ||||
| use devtools::RandomTempPath; | ||||
| 
 | ||||
| use jsonrpc_core::IoHandler; | ||||
| use v1::{ParityAccounts, ParityAccountsClient}; | ||||
| @ -30,21 +33,33 @@ fn accounts_provider() -> Arc<AccountProvider> { | ||||
| 	Arc::new(AccountProvider::transient_provider()) | ||||
| } | ||||
| 
 | ||||
| fn setup() -> ParityAccountsTester { | ||||
| 	let accounts = accounts_provider(); | ||||
| 	let parity_accounts = ParityAccountsClient::new(&accounts); | ||||
| fn accounts_provider_with_vaults_support(temp_path: &str) -> Arc<AccountProvider> { | ||||
| 	let root_keys_dir = RootDiskDirectory::create(temp_path).unwrap(); | ||||
| 	let secret_store = EthStore::open(Box::new(root_keys_dir)).unwrap(); | ||||
| 	Arc::new(AccountProvider::new(Box::new(secret_store))) | ||||
| } | ||||
| 
 | ||||
| fn setup_with_accounts_provider(accounts_provider: Arc<AccountProvider>) -> ParityAccountsTester { | ||||
| 	let parity_accounts = ParityAccountsClient::new(&accounts_provider); | ||||
| 	let mut io = IoHandler::default(); | ||||
| 	io.extend_with(parity_accounts.to_delegate()); | ||||
| 
 | ||||
| 	let tester = ParityAccountsTester { | ||||
| 		accounts: accounts, | ||||
| 		accounts: accounts_provider, | ||||
| 		io: io, | ||||
| 	}; | ||||
| 
 | ||||
| 	tester | ||||
| } | ||||
| 
 | ||||
| fn setup() -> ParityAccountsTester { | ||||
| 	setup_with_accounts_provider(accounts_provider()) | ||||
| } | ||||
| 
 | ||||
| fn setup_with_vaults_support(temp_path: &str) -> ParityAccountsTester { | ||||
| 	setup_with_accounts_provider(accounts_provider_with_vaults_support(temp_path)) | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn should_be_able_to_get_account_info() { | ||||
| 	let tester = setup(); | ||||
| @ -217,3 +232,122 @@ fn should_be_able_to_remove_address() { | ||||
| 	let response = r#"{"jsonrpc":"2.0","result":{},"id":4}"#; | ||||
| 	assert_eq!(res, Some(response.into())); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn rpc_parity_new_vault() { | ||||
| 	let temp_path = RandomTempPath::new(); | ||||
| 	let tester = setup_with_vaults_support(temp_path.as_str()); | ||||
| 
 | ||||
| 	let request = r#"{"jsonrpc": "2.0", "method": "parity_newVault", "params":["vault1", "password1"], "id": 1}"#; | ||||
| 	let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; | ||||
| 
 | ||||
| 	assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); | ||||
| 	assert!(tester.accounts.close_vault("vault1").is_ok()); | ||||
| 	assert!(tester.accounts.open_vault("vault1", "password1").is_ok()); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn rpc_parity_open_vault() { | ||||
| 	let temp_path = RandomTempPath::new(); | ||||
| 	let tester = setup_with_vaults_support(temp_path.as_str()); | ||||
| 
 | ||||
| 	assert!(tester.accounts.create_vault("vault1", "password1").is_ok()); | ||||
| 	assert!(tester.accounts.close_vault("vault1").is_ok()); | ||||
| 
 | ||||
| 	let request = r#"{"jsonrpc": "2.0", "method": "parity_openVault", "params":["vault1", "password1"], "id": 1}"#; | ||||
| 	let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; | ||||
| 
 | ||||
| 	assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn rpc_parity_close_vault() { | ||||
| 	let temp_path = RandomTempPath::new(); | ||||
| 	let tester = setup_with_vaults_support(temp_path.as_str()); | ||||
| 
 | ||||
| 	assert!(tester.accounts.create_vault("vault1", "password1").is_ok()); | ||||
| 
 | ||||
| 	let request = r#"{"jsonrpc": "2.0", "method": "parity_closeVault", "params":["vault1"], "id": 1}"#; | ||||
| 	let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; | ||||
| 
 | ||||
| 	assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn rpc_parity_change_vault_password() { | ||||
| 	let temp_path = RandomTempPath::new(); | ||||
| 	let tester = setup_with_vaults_support(temp_path.as_str()); | ||||
| 
 | ||||
| 	assert!(tester.accounts.create_vault("vault1", "password1").is_ok()); | ||||
| 
 | ||||
| 	let request = r#"{"jsonrpc": "2.0", "method": "parity_changeVaultPassword", "params":["vault1", "password2"], "id": 1}"#; | ||||
| 	let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; | ||||
| 
 | ||||
| 	assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn rpc_parity_change_vault() { | ||||
| 	let temp_path = RandomTempPath::new(); | ||||
| 	let tester = setup_with_vaults_support(temp_path.as_str()); | ||||
| 
 | ||||
| 	let (address, _) = tester.accounts.new_account_and_public("root_password").unwrap(); | ||||
| 	assert!(tester.accounts.create_vault("vault1", "password1").is_ok()); | ||||
| 
 | ||||
| 	let request = format!(r#"{{"jsonrpc": "2.0", "method": "parity_changeVault", "params":["0x{}", "vault1"], "id": 1}}"#, address.hex()); | ||||
| 	let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; | ||||
| 
 | ||||
| 	assert_eq!(tester.io.handle_request_sync(&request), Some(response.to_owned())); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn rpc_parity_vault_adds_vault_field_to_acount_meta() { | ||||
| 	let temp_path = RandomTempPath::new(); | ||||
| 	let tester = setup_with_vaults_support(temp_path.as_str()); | ||||
| 
 | ||||
| 	let (address1, _) = tester.accounts.new_account_and_public("root_password1").unwrap(); | ||||
| 	let uuid1 = tester.accounts.account_meta(address1.clone()).unwrap().uuid.unwrap(); | ||||
| 	assert!(tester.accounts.create_vault("vault1", "password1").is_ok()); | ||||
| 	assert!(tester.accounts.change_vault(address1, "vault1").is_ok()); | ||||
| 
 | ||||
| 	let request = r#"{"jsonrpc": "2.0", "method": "parity_allAccountsInfo", "params":[], "id": 1}"#; | ||||
| 	let response = format!(r#"{{"jsonrpc":"2.0","result":{{"0x{}":{{"meta":"{{\"vault\":\"vault1\"}}","name":"","uuid":"{}"}}}},"id":1}}"#, address1.hex(), uuid1); | ||||
| 
 | ||||
| 	assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn rpc_parity_list_vaults() { | ||||
| 	let temp_path = RandomTempPath::new(); | ||||
| 	let tester = setup_with_vaults_support(temp_path.as_str()); | ||||
| 
 | ||||
| 	assert!(tester.accounts.create_vault("vault1", "password1").is_ok()); | ||||
| 	assert!(tester.accounts.create_vault("vault2", "password2").is_ok()); | ||||
| 
 | ||||
| 	let request = r#"{"jsonrpc": "2.0", "method": "parity_listVaults", "params":[], "id": 1}"#; | ||||
| 	let response1 = r#"{"jsonrpc":"2.0","result":["vault1","vault2"],"id":1}"#; | ||||
| 	let response2 = r#"{"jsonrpc":"2.0","result":["vault2","vault1"],"id":1}"#; | ||||
| 
 | ||||
| 	let actual_response = tester.io.handle_request_sync(request); | ||||
| 	assert!(actual_response == Some(response1.to_owned()) | ||||
| 		|| actual_response == Some(response2.to_owned())); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn rpc_parity_list_opened_vaults() { | ||||
| 	let temp_path = RandomTempPath::new(); | ||||
| 	let tester = setup_with_vaults_support(temp_path.as_str()); | ||||
| 
 | ||||
| 	assert!(tester.accounts.create_vault("vault1", "password1").is_ok()); | ||||
| 	assert!(tester.accounts.create_vault("vault2", "password2").is_ok()); | ||||
| 	assert!(tester.accounts.create_vault("vault3", "password3").is_ok()); | ||||
| 	assert!(tester.accounts.close_vault("vault2").is_ok()); | ||||
| 
 | ||||
| 	let request = r#"{"jsonrpc": "2.0", "method": "parity_listOpenedVaults", "params":[], "id": 1}"#; | ||||
| 	let response1 = r#"{"jsonrpc":"2.0","result":["vault1","vault3"],"id":1}"#; | ||||
| 	let response2 = r#"{"jsonrpc":"2.0","result":["vault3","vault1"],"id":1}"#; | ||||
| 
 | ||||
| 	let actual_response = tester.io.handle_request_sync(request); | ||||
| 	assert!(actual_response == Some(response1.to_owned()) | ||||
| 		|| actual_response == Some(response2.to_owned())); | ||||
| } | ||||
|  | ||||
| @ -105,5 +105,33 @@ build_rpc_trait! { | ||||
| 		/// Returns the accounts available for importing from Geth.
 | ||||
| 		#[rpc(name = "parity_listGethAccounts")] | ||||
| 		fn geth_accounts(&self) -> Result<Vec<H160>, Error>; | ||||
| 
 | ||||
| 		/// Create new vault.
 | ||||
| 		#[rpc(name = "parity_newVault")] | ||||
| 		fn create_vault(&self, String, String) -> Result<bool, Error>; | ||||
| 
 | ||||
| 		/// Open existing vault.
 | ||||
| 		#[rpc(name = "parity_openVault")] | ||||
| 		fn open_vault(&self, String, String) -> Result<bool, Error>; | ||||
| 
 | ||||
| 		/// Close previously opened vault.
 | ||||
| 		#[rpc(name = "parity_closeVault")] | ||||
| 		fn close_vault(&self, String) -> Result<bool, Error>; | ||||
| 
 | ||||
| 		/// List all vaults.
 | ||||
| 		#[rpc(name = "parity_listVaults")] | ||||
| 		fn list_vaults(&self) -> Result<Vec<String>, Error>; | ||||
| 
 | ||||
| 		/// List all currently opened vaults.
 | ||||
| 		#[rpc(name = "parity_listOpenedVaults")] | ||||
| 		fn list_opened_vaults(&self) -> Result<Vec<String>, Error>; | ||||
| 
 | ||||
| 		/// Change vault password.
 | ||||
| 		#[rpc(name = "parity_changeVaultPassword")] | ||||
| 		fn change_vault_password(&self, String, String) -> Result<bool, Error>; | ||||
| 
 | ||||
| 		/// Change vault of the given address.
 | ||||
| 		#[rpc(name = "parity_changeVault")] | ||||
| 		fn change_vault(&self, H160, String) -> Result<bool, Error>; | ||||
| 	} | ||||
| } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user