From 5fe54901e72cfe3ced401f3ed006b2174c073324 Mon Sep 17 00:00:00 2001 From: maciejhirsz Date: Mon, 30 Jan 2017 18:13:14 +0100 Subject: [PATCH 01/17] Fix `Step` encoding --- ethcore/src/engines/tendermint/message.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index e2e6ef243..8d7bffdfa 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -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); } } @@ -194,7 +194,7 @@ impl Encodable for ConsensusMessage { pub fn message_info_rlp(vote_step: &VoteStep, block_hash: Option) -> 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() } @@ -218,7 +218,7 @@ mod tests { #[test] fn encode_decode() { let message = ConsensusMessage { - signature: H520::default(), + signature: H520::default(), vote_step: VoteStep { height: 10, view: 123, @@ -231,7 +231,7 @@ mod tests { assert_eq!(message, rlp.as_val()); let message = ConsensusMessage { - signature: H520::default(), + signature: H520::default(), vote_step: VoteStep { height: 1314, view: 0, From 6795068ea4fd74ec88a85b8070f208046895604e Mon Sep 17 00:00:00 2001 From: maciejhirsz Date: Mon, 30 Jan 2017 19:13:35 +0100 Subject: [PATCH 02/17] Fixed other types --- ethcore/src/blockchain/extras.rs | 2 +- ethcore/src/types/executed.rs | 2 +- ethcore/src/types/trace_types/error.rs | 4 ++-- ethcore/src/types/trace_types/flat.rs | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ethcore/src/blockchain/extras.rs b/ethcore/src/blockchain/extras.rs index 2886fe92b..9e1109f1a 100644 --- a/ethcore/src/blockchain/extras.rs +++ b/ethcore/src/blockchain/extras.rs @@ -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); } } diff --git a/ethcore/src/types/executed.rs b/ethcore/src/types/executed.rs index a1a443b81..b8a9cb970 100644 --- a/ethcore/src/types/executed.rs +++ b/ethcore/src/types/executed.rs @@ -46,7 +46,7 @@ impl Encodable for CallType { CallType::CallCode => 2, CallType::DelegateCall => 3, }; - s.append(&v); + Encodable::rlp_append(&v, s); } } diff --git a/ethcore/src/types/trace_types/error.rs b/ethcore/src/types/trace_types/error.rs index 4ecb9a238..9a2f29f3b 100644 --- a/ethcore/src/types/trace_types/error.rs +++ b/ethcore/src/types/trace_types/error.rs @@ -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); } } diff --git a/ethcore/src/types/trace_types/flat.rs b/ethcore/src/types/trace_types/flat.rs index 62abbc474..6ceff86d9 100644 --- a/ethcore/src/types/trace_types/flat.rs +++ b/ethcore/src/types/trace_types/flat.rs @@ -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); } } From 681fa10d4bc6b137e373a60963a55696c0dca5ef Mon Sep 17 00:00:00 2001 From: maciejhirsz Date: Mon, 30 Jan 2017 20:01:32 +0100 Subject: [PATCH 03/17] Tests checking item sizes for changed types --- ethcore/src/blockchain/extras.rs | 18 ++++++++++++++ ethcore/src/engines/tendermint/message.rs | 12 +++++++++ ethcore/src/types/executed.rs | 30 +++++++++++++++++------ ethcore/src/types/trace_types/error.rs | 18 ++++++++++++++ ethcore/src/types/trace_types/flat.rs | 25 +++++++++++++++++++ 5 files changed, 96 insertions(+), 7 deletions(-) diff --git a/ethcore/src/blockchain/extras.rs b/ethcore/src/blockchain/extras.rs index 9e1109f1a..0e6dadbfe 100644 --- a/ethcore/src/blockchain/extras.rs +++ b/ethcore/src/blockchain/extras.rs @@ -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(); + } +} diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index 8d7bffdfa..2cddd9e69 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -215,6 +215,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 { diff --git a/ethcore/src/types/executed.rs b/ethcore/src/types/executed.rs index b8a9cb970..21858c194 100644 --- a/ethcore/src/types/executed.rs +++ b/ethcore/src/types/executed.rs @@ -212,12 +212,28 @@ impl fmt::Display for CallError { /// Transaction execution result. pub type ExecutionResult = Result; -#[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); + } } diff --git a/ethcore/src/types/trace_types/error.rs b/ethcore/src/types/trace_types/error.rs index 9a2f29f3b..7eb16570c 100644 --- a/ethcore/src/types/trace_types/error.rs +++ b/ethcore/src/types/trace_types/error.rs @@ -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(); + } +} diff --git a/ethcore/src/types/trace_types/flat.rs b/ethcore/src/types/trace_types/flat.rs index 6ceff86d9..870f13802 100644 --- a/ethcore/src/types/trace_types/flat.rs +++ b/ethcore/src/types/trace_types/flat.rs @@ -162,10 +162,35 @@ impl Into> 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 From 687ae4d7fac55649dceff2a8b025cc96fcf84e3a Mon Sep 17 00:00:00 2001 From: maciejhirsz Date: Wed, 1 Feb 2017 10:38:01 +0100 Subject: [PATCH 04/17] Removed fixed TODO --- ethcore/src/engines/tendermint/message.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index 2cddd9e69..1f6359ad4 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -193,7 +193,6 @@ impl Encodable for ConsensusMessage { } pub fn message_info_rlp(vote_step: &VoteStep, block_hash: Option) -> Bytes { - // TODO: figure out whats wrong with nested list encoding 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() From c5cc5e30ece097c9bdf833e8d05ff42664c71a16 Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Sun, 5 Feb 2017 02:39:22 +0400 Subject: [PATCH 05/17] Update Dockerfile fix build for docker hub [ci skip] --- docker/hub/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/hub/Dockerfile b/docker/hub/Dockerfile index b9e25b8be..ae6ec8814 100644 --- a/docker/hub/Dockerfile +++ b/docker/hub/Dockerfile @@ -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 From 4fa1717a991c03f64b87dd69e9ed6ccc149bf1e2 Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Sun, 5 Feb 2017 02:56:19 +0400 Subject: [PATCH 06/17] Update Dockerfile remove --verbose from hub build --- docker/hub/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/hub/Dockerfile b/docker/hub/Dockerfile index ae6ec8814..d4f5b96ee 100644 --- a/docker/hub/Dockerfile +++ b/docker/hub/Dockerfile @@ -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 From bcd3cd846799931000bd0dd0b13ad686d9b53541 Mon Sep 17 00:00:00 2001 From: Sebastian Kippe Date: Sun, 5 Feb 2017 10:04:17 +0000 Subject: [PATCH 07/17] Use secure websocket from HTTPS clients (#4436) Currently, the unsecure `ws://` scheme is hardcoded. With this change, the scheme will dynamically change to the secure `wss://` when the frontend is requested from an HTTPS origin. --- js/src/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/src/index.js b/js/src/index.js index 0314581c1..28abe953b 100644 --- a/js/src/index.js +++ b/js/src/index.js @@ -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); From f646ffbe61aca4f9d3968b86471058db184dee13 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Sun, 5 Feb 2017 10:15:32 +0000 Subject: [PATCH 08/17] [ci skip] js-precompiled 20170205-101133 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 48b95149b..7fef9d5fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1562,7 +1562,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#771f160bf67f46bce2fda0a7e7f87f1ab21b9e39" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index 705dd51df..6d321246d 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.3.66", + "version": "0.3.67", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From e257e4e3bd58ecbec483a9edeffc335ffbc3a3be Mon Sep 17 00:00:00 2001 From: Nikolay Volf Date: Sun, 5 Feb 2017 18:17:03 +0300 Subject: [PATCH 09/17] Ethkey - extended keys (#4377) * data structures initial * hard derivation * tabs, docs * more docs * soft private derivation * public derivation * finalize api, fix warnings * use simple new() * keypair api * bump byteorder * doc tweaks * remove heavyness from tests * added test vector infrastructure and examples * initialization from seed to key pair * add comment about panic --- Cargo.lock | 20 +- ethcore/Cargo.toml | 2 +- ethkey/Cargo.toml | 2 + ethkey/src/extended.rs | 448 +++++++++++++++++++++++++++++++++++++++++ ethkey/src/lib.rs | 4 + 5 files changed, 466 insertions(+), 10 deletions(-) create mode 100644 ethkey/src/extended.rs diff --git a/Cargo.lock b/Cargo.lock index 7fef9d5fb..c07e3376d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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]] @@ -319,8 +319,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)", @@ -358,7 +358,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)", @@ -661,7 +661,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", @@ -692,7 +692,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)", @@ -714,11 +714,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)", ] @@ -2481,7 +2483,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)" = "" "checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c" @@ -2501,7 +2503,7 @@ dependencies = [ "checksum dtoa 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5edd69c67b2f8e0911629b7e6b8a34cb3956613cd7c6e6414966dee349c2db4f" "checksum elastic-array 0.6.0 (git+https://github.com/ethcore/elastic-array)" = "" "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)" = "" +"checksum eth-secp256k1 0.5.6 (git+https://github.com/ethcore/rust-secp256k1)" = "" "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" diff --git a/ethcore/Cargo.toml b/ethcore/Cargo.toml index e0afa1106..d31e401f6 100644 --- a/ethcore/Cargo.toml +++ b/ethcore/Cargo.toml @@ -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 } diff --git a/ethkey/Cargo.toml b/ethkey/Cargo.toml index d9d1c7efa..d661952a7 100644 --- a/ethkey/Cargo.toml +++ b/ethkey/Cargo.toml @@ -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 = [] diff --git a/ethkey/src/extended.rs b/ethkey/src/extended.rs new file mode 100644 index 000000000..c5426601f --- /dev/null +++ b/ethkey/src/extended.rs @@ -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 . + +//! 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 { + 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 { + 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 { + 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 { + 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 { + 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, 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") + ); + } +} diff --git a/ethkey/src/lib.rs b/ethkey/src/lib.rs index bc9c4a4b9..3882b3559 100644 --- a/ethkey/src/lib.rs +++ b/ethkey/src/lib.rs @@ -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}; From 2f340a547a0fd93898812557336d075818c7e8d6 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Sun, 5 Feb 2017 18:17:56 +0300 Subject: [PATCH 10/17] Vaults RPCs (#4366) * vaults RPCs * vault.password != vault_account.password * moved vault RPCs to parityAccounts NS * parity_listVaults + parity_listOpenedVaults --- Cargo.lock | 1 + ethcore/src/account_provider/mod.rs | 74 ++++-- ethstore/Cargo.toml | 1 + ethstore/src/dir/disk.rs | 36 ++- ethstore/src/dir/mod.rs | 6 +- ethstore/src/dir/vault.rs | 103 +++----- ethstore/src/ethstore.rs | 264 ++++++++++++--------- ethstore/src/json/mod.rs.in | 2 +- ethstore/src/json/vault_key_file.rs | 68 +++++- ethstore/src/lib.rs | 1 + ethstore/src/secret_store.rs | 12 +- rpc/src/v1/impls/parity_accounts.rs | 47 ++++ rpc/src/v1/tests/mocked/parity_accounts.rs | 142 ++++++++++- rpc/src/v1/traits/parity_accounts.rs | 28 +++ 14 files changed, 579 insertions(+), 206 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c07e3376d..298377883 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -730,6 +730,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", diff --git a/ethcore/src/account_provider/mod.rs b/ethcore/src/account_provider/mod.rs index 46690fabd..95b2ad855 100755 --- a/ethcore/src/account_provider/mod.rs +++ b/ethcore/src/account_provider/mod.rs @@ -280,7 +280,7 @@ impl AccountProvider { /// Returns each account along with name and meta. pub fn account_meta(&self, address: Address) -> Result { - 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 { - 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, message: Message) -> Result { - 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, 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, shared_mac: &[u8], message: &[u8]) -> Result, 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, Error> { + self.sstore.list_vaults() + .map_err(Into::into) + } + + /// List all currently opened vaults + pub fn list_opened_vaults(&self) -> Result, 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)] diff --git a/ethstore/Cargo.toml b/ethstore/Cargo.toml index 56830dfd2..091080348 100755 --- a/ethstore/Cargo.toml +++ b/ethstore/Cargo.toml @@ -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 } diff --git a/ethstore/src/dir/disk.rs b/ethstore/src/dir/disk.rs index b1b3ecdd5..f78dba288 100755 --- a/ethstore/src/dir/disk.rs +++ b/ethstore/src/dir/disk.rs @@ -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 KeyDirectory for DiskDirectory 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 VaultKeyDirectoryProvider for DiskDirectory where T: KeyFileManager { let vault_dir = VaultDiskDirectory::at(&self.path, name, key)?; Ok(Box::new(vault_dir)) } + + fn list_vaults(&self) -> Result, 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")); + } } diff --git a/ethstore/src/dir/mod.rs b/ethstore/src/dir/mod.rs index fc35b740b..356fb5b07 100755 --- a/ethstore/src/dir/mod.rs +++ b/ethstore/src/dir/mod.rs @@ -70,6 +70,8 @@ pub trait VaultKeyDirectoryProvider { fn create(&self, name: &str, key: VaultKey) -> Result, Error>; /// Open existing vault with given key fn open(&self, name: &str, key: VaultKey) -> Result, Error>; + /// List all vaults + fn list_vaults(&self) -> Result, 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; diff --git a/ethstore/src/dir/vault.rs b/ethstore/src/dir/vault.rs index df67d9fe4..c068388f3 100755 --- a/ethstore/src/dir/vault.rs +++ b/ethstore/src/dir/vault.rs @@ -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; /// 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 { @@ -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(&self, filename: Option, reader: T) -> Result 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(&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(&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

(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); } } diff --git a/ethstore/src/ethstore.rs b/ethstore/src/ethstore.rs index add5be129..01ff5004d 100755 --- a/ethstore/src/ethstore.rs +++ b/ethstore/src/ethstore.rs @@ -54,6 +54,10 @@ impl SimpleSecretStore for EthStore { self.store.insert_account(vault, secret, password) } + fn account_ref(&self, address: &Address) -> Result { + self.store.account_ref(address) + } + fn accounts(&self) -> Result, 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, Error> { + self.store.list_vaults() + } + + fn list_opened_vaults(&self) -> Result, 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 { + 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 { 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 { + self.reload_accounts()?; + self.cache.read().keys() + .find(|r| &r.address == address) + .cloned() + .ok_or(Error::InvalidAccount) + } + fn accounts(&self) -> Result, 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 { @@ -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, 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, 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 { + 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>, - path: Option, + _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)); + } } diff --git a/ethstore/src/json/mod.rs.in b/ethstore/src/json/mod.rs.in index 8c4495825..2cec82877 100755 --- a/ethstore/src/json/mod.rs.in +++ b/ethstore/src/json/mod.rs.in @@ -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; diff --git a/ethstore/src/json/vault_key_file.rs b/ethstore/src/json/vault_key_file.rs index dce0e6d99..83745bf1b 100755 --- a/ethstore/src/json/vault_key_file.rs +++ b/ethstore/src/json/vault_key_file.rs @@ -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, } +/// Insert vault name to the JSON meta field +pub fn insert_vault_name_to_json_meta(meta: &str, vault_name: &str) -> Result { + 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 { + 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()); + } } diff --git a/ethstore/src/lib.rs b/ethstore/src/lib.rs index 3b92b439c..33327a01d 100755 --- a/ethstore/src/lib.rs +++ b/ethstore/src/lib.rs @@ -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; diff --git a/ethstore/src/secret_store.rs b/ethstore/src/secret_store.rs index 7042f434a..57cba259e 100755 --- a/ethstore/src/secret_store.rs +++ b/ethstore/src/secret_store.rs @@ -47,6 +47,9 @@ pub trait SimpleSecretStore: Send + Sync { fn decrypt(&self, account: &StoreAccountRef, password: &str, shared_mac: &[u8], message: &[u8]) -> Result, Error>; fn accounts(&self) -> Result, 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; /// 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, Error>; + /// List all currently opened vaults + fn list_opened_vaults(&self) -> Result, 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; } pub trait SecretStore: SimpleSecretStore { fn import_presale(&self, vault: SecretVaultRef, json: &[u8], password: &str) -> Result; fn import_wallet(&self, vault: SecretVaultRef, json: &[u8], password: &str) -> Result; 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; fn public(&self, account: &StoreAccountRef, password: &str) -> Result; diff --git a/rpc/src/v1/impls/parity_accounts.rs b/rpc/src/v1/impls/parity_accounts.rs index 1034b1df5..000e3c9eb 100644 --- a/rpc/src/v1/impls/parity_accounts.rs +++ b/rpc/src/v1/impls/parity_accounts.rs @@ -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 { + 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 { + 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 { + take_weak!(self.accounts) + .close_vault(&name) + .map_err(|e| errors::account("Could not close vault.", e)) + .map(|_| true) + } + + fn list_vaults(&self) -> Result, Error> { + take_weak!(self.accounts) + .list_vaults() + .map_err(|e| errors::account("Could not list vaults.", e)) + } + + fn list_opened_vaults(&self) -> Result, 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 { + 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 { + 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: Vec) -> Vec where diff --git a/rpc/src/v1/tests/mocked/parity_accounts.rs b/rpc/src/v1/tests/mocked/parity_accounts.rs index e245cb92f..3f329ca2b 100644 --- a/rpc/src/v1/tests/mocked/parity_accounts.rs +++ b/rpc/src/v1/tests/mocked/parity_accounts.rs @@ -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 { 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 { + 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) -> 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())); +} diff --git a/rpc/src/v1/traits/parity_accounts.rs b/rpc/src/v1/traits/parity_accounts.rs index 595a33740..c8c37964d 100644 --- a/rpc/src/v1/traits/parity_accounts.rs +++ b/rpc/src/v1/traits/parity_accounts.rs @@ -105,5 +105,33 @@ build_rpc_trait! { /// Returns the accounts available for importing from Geth. #[rpc(name = "parity_listGethAccounts")] fn geth_accounts(&self) -> Result, Error>; + + /// Create new vault. + #[rpc(name = "parity_newVault")] + fn create_vault(&self, String, String) -> Result; + + /// Open existing vault. + #[rpc(name = "parity_openVault")] + fn open_vault(&self, String, String) -> Result; + + /// Close previously opened vault. + #[rpc(name = "parity_closeVault")] + fn close_vault(&self, String) -> Result; + + /// List all vaults. + #[rpc(name = "parity_listVaults")] + fn list_vaults(&self) -> Result, Error>; + + /// List all currently opened vaults. + #[rpc(name = "parity_listOpenedVaults")] + fn list_opened_vaults(&self) -> Result, Error>; + + /// Change vault password. + #[rpc(name = "parity_changeVaultPassword")] + fn change_vault_password(&self, String, String) -> Result; + + /// Change vault of the given address. + #[rpc(name = "parity_changeVault")] + fn change_vault(&self, H160, String) -> Result; } } From 4ac91b3284a1df2dd7a800bd4caaf8533dd3ac1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Mon, 6 Feb 2017 09:26:11 +0100 Subject: [PATCH 11/17] Open popup without attempting inline (#4440) * Open popup without attempting inline * Cater for all .web3.site addresses --- js/src/util/constants.js | 5 ++++- js/src/util/dapplink.js | 4 +++- js/src/views/Application/Extension/store.js | 19 +++++++++++++++---- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/js/src/util/constants.js b/js/src/util/constants.js index 10ff21cde..4a5548de9 100644 --- a/js/src/util/constants.js +++ b/js/src/util/constants.js @@ -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 }; diff --git a/js/src/util/dapplink.js b/js/src/util/dapplink.js index 1a73d184f..e2b249b03 100644 --- a/js/src/util/dapplink.js +++ b/js/src/util/dapplink.js @@ -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) { diff --git a/js/src/views/Application/Extension/store.js b/js/src/views/Application/Extension/store.js index 40a3f09e7..965598f03 100644 --- a/js/src/views/Application/Extension/store.js +++ b/js/src/views/Application/Extension/store.js @@ -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'); }); } } From d7e87dbe199d59e60c90bc6945012bcecf9ebdc4 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Mon, 6 Feb 2017 08:36:28 +0000 Subject: [PATCH 12/17] [ci skip] js-precompiled 20170206-083224 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 298377883..4f70a9bd3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1565,7 +1565,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#771f160bf67f46bce2fda0a7e7f87f1ab21b9e39" +source = "git+https://github.com/ethcore/js-precompiled.git#b281cb0711d4a9d293abd43eeeb42f61864fe60d" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index 6d321246d..6c1bf7d47 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.3.67", + "version": "0.3.68", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 4172a5369cb4e1b770fc536597a3877ce44a538e Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Mon, 6 Feb 2017 17:21:35 +0100 Subject: [PATCH 13/17] Include total difficulty in CHTs and hide implementation details from consumers (#4428) * CHT builder and prover * use CHT abstraction in provider * hide CHT internals from header chain * fix itertools conflict by updating all to 0.5 * cht proof checker, use it in on_demand --- Cargo.lock | 18 ++- ethcore/light/Cargo.toml | 1 + ethcore/light/src/cht.rs | 150 +++++++++++++++++++++++ ethcore/light/src/client/header_chain.rs | 32 +++-- ethcore/light/src/lib.rs | 1 + ethcore/light/src/on_demand/mod.rs | 9 +- ethcore/light/src/on_demand/request.rs | 67 ++++------ ethcore/light/src/provider.rs | 68 +++++----- ethcore/src/client/test_client.rs | 2 +- ethstore/Cargo.toml | 2 +- util/Cargo.toml | 2 +- util/src/triehash.rs | 4 +- 12 files changed, 261 insertions(+), 95 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4f70a9bd3..3de39df51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -300,6 +300,11 @@ name = "dtoa" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "either" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "elastic-array" version = "0.6.0" @@ -531,6 +536,7 @@ dependencies = [ "ethcore-network 1.6.0", "ethcore-util 1.6.0", "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rlp 0.1.0", @@ -666,7 +672,7 @@ dependencies = [ "ethcore-bloom-journal 0.1.0", "ethcore-devtools 1.6.0", "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "itertools 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -734,7 +740,7 @@ dependencies = [ "ethcore-util 1.6.0", "ethcrypto 0.1.0", "ethkey 0.2.0", - "itertools 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -990,8 +996,11 @@ dependencies = [ [[package]] name = "itertools" -version = "0.4.13" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "either 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "itoa" @@ -2502,6 +2511,7 @@ dependencies = [ "checksum docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4cc0acb4ce0828c6a5a11d47baa432fe885881c27428c3a4e473e454ffe57a76" "checksum dtoa 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0dd841b58510c9618291ffa448da2e4e0f699d984d436122372f446dae62263d" "checksum dtoa 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5edd69c67b2f8e0911629b7e6b8a34cb3956613cd7c6e6414966dee349c2db4f" +"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)" = "" "checksum env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "aba65b63ffcc17ffacd6cf5aa843da7c5a25e3bd4bbe0b7def8b214e411250e5" "checksum eth-secp256k1 0.5.6 (git+https://github.com/ethcore/rust-secp256k1)" = "" @@ -2524,7 +2534,7 @@ dependencies = [ "checksum idna 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1053236e00ce4f668aeca4a769a09b3bf5a682d802abd6f3cb39374f6b162c11" "checksum igd 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c8c12b1795b8b168f577c45fa10379b3814dcb11b7ab702406001f0d63f40484" "checksum isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7408a548dc0e406b7912d9f84c261cc533c1866e047644a811c133c56041ac0c" -"checksum itertools 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)" = "086e1fa5fe48840b1cfdef3a20c7e3115599f8d5c4c87ef32a794a7cdd184d76" +"checksum itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)" = "d95557e7ba6b71377b0f2c3b3ae96c53f1b75a926a6901a500f557a370af730a" "checksum itoa 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae3088ea4baeceb0284ee9eea42f591226e6beaecf65373e41b38d95a1b8e7a1" "checksum itoa 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "91fd9dc2c587067de817fec4ad355e3818c3d893a78cab32a0a474c7a15bb8d5" "checksum jsonrpc-core 5.1.0 (git+https://github.com/ethcore/jsonrpc.git)" = "" diff --git a/ethcore/light/Cargo.toml b/ethcore/light/Cargo.toml index 534b0e5c1..d2444dd59 100644 --- a/ethcore/light/Cargo.toml +++ b/ethcore/light/Cargo.toml @@ -22,6 +22,7 @@ time = "0.1" smallvec = "0.3.1" futures = "0.1" rand = "0.3" +itertools = "0.5" [features] default = [] diff --git a/ethcore/light/src/cht.rs b/ethcore/light/src/cht.rs index 81cfb8356..1fcb7b26a 100644 --- a/ethcore/light/src/cht.rs +++ b/ethcore/light/src/cht.rs @@ -12,10 +12,154 @@ // GNU General Public License for more details. //! Canonical hash trie definitions and helper functions. +//! +//! Each CHT is a trie mapping block numbers to canonical hashes and total difficulty. +//! One is generated for every `SIZE` blocks, allowing us to discard those blocks in +//! favor the the trie root. When the "ancient" blocks need to be accessed, we simply +//! request an inclusion proof of a specific block number against the trie with the +//! root has. A correct proof implies that the claimed block is identical to the one +//! we discarded. + +use ethcore::ids::BlockId; +use util::{Bytes, H256, U256, HashDB, MemoryDB}; +use util::trie::{self, TrieMut, TrieDBMut, Trie, TrieDB, Recorder}; +use rlp::{Stream, RlpStream, UntrustedRlp, View}; + +// encode a key. +macro_rules! key { + ($num: expr) => { ::rlp::encode(&$num) } +} + +macro_rules! val { + ($hash: expr, $td: expr) => {{ + let mut stream = RlpStream::new_list(2); + stream.append(&$hash).append(&$td); + stream.drain() + }} +} /// The size of each CHT. pub const SIZE: u64 = 2048; +/// A canonical hash trie. This is generic over any database it can query. +/// See module docs for more details. +#[derive(Debug, Clone)] +pub struct CHT { + db: DB, + root: H256, // the root of this CHT. + number: u64, +} + +impl CHT { + /// Query the root of the CHT. + pub fn root(&self) -> H256 { self.root } + + /// Query the number of the CHT. + pub fn number(&self) -> u64 { self.number } + + /// Generate an inclusion proof for the entry at a specific block. + /// Nodes before level `from_level` will be omitted. + /// Returns an error on an incomplete trie, and `Ok(None)` on an unprovable request. + pub fn prove(&self, num: u64, from_level: u32) -> trie::Result>> { + if block_to_cht_number(num) != Some(self.number) { return Ok(None) } + + let mut recorder = Recorder::with_depth(from_level); + let t = TrieDB::new(&self.db, &self.root)?; + t.get_with(&key!(num), &mut recorder)?; + + Ok(Some(recorder.drain().into_iter().map(|x| x.data).collect())) + } +} + +/// Block information necessary to build a CHT. +pub struct BlockInfo { + /// The block's hash. + pub hash: H256, + /// The block's parent's hash. + pub parent_hash: H256, + /// The block's total difficulty. + pub total_difficulty: U256, +} + +/// Build an in-memory CHT from a closure which provides necessary information +/// about blocks. If the fetcher ever fails to provide the info, the CHT +/// will not be generated. +pub fn build(cht_num: u64, mut fetcher: F) -> Option> + where F: FnMut(BlockId) -> Option +{ + let mut db = MemoryDB::new(); + + // start from the last block by number and work backwards. + let last_num = start_number(cht_num + 1) - 1; + let mut id = BlockId::Number(last_num); + + let mut root = H256::default(); + + { + let mut t = TrieDBMut::new(&mut db, &mut root); + for blk_num in (0..SIZE).map(|n| last_num - n) { + let info = match fetcher(id) { + Some(info) => info, + None => return None, + }; + + id = BlockId::Hash(info.parent_hash); + t.insert(&key!(blk_num), &val!(info.hash, info.total_difficulty)) + .expect("fresh in-memory database is infallible; qed"); + } + } + + Some(CHT { + db: db, + root: root, + number: cht_num, + }) +} + +/// Compute a CHT root from an iterator of (hash, td) pairs. Fails if shorter than +/// SIZE items. The items are assumed to proceed sequentially from `start_number(cht_num)`. +/// Discards the trie's nodes. +pub fn compute_root(cht_num: u64, iterable: I) -> Option + where I: IntoIterator +{ + let mut v = Vec::with_capacity(SIZE as usize); + let start_num = start_number(cht_num) as usize; + + for (i, (h, td)) in iterable.into_iter().take(SIZE as usize).enumerate() { + v.push((key!(i + start_num).to_vec(), val!(h, td).to_vec())) + } + + if v.len() == SIZE as usize { + Some(::util::triehash::trie_root(v)) + } else { + None + } +} + +/// Check a proof for a CHT. +/// Given a set of a trie nodes, a number to query, and a trie root, +/// verify the given trie branch and extract the canonical hash and total difficulty. +// TODO: better support for partially-checked queries. +pub fn check_proof(proof: &[Bytes], num: u64, root: H256) -> Option<(H256, U256)> { + let mut db = MemoryDB::new(); + + for node in proof { db.insert(&node[..]); } + let res = match TrieDB::new(&db, &root) { + Err(_) => return None, + Ok(trie) => trie.get_with(&key!(num), |val: &[u8]| { + let rlp = UntrustedRlp::new(val); + rlp.val_at::(0) + .and_then(|h| rlp.val_at::(1).map(|td| (h, td))) + .ok() + }) + }; + + match res { + Ok(Some(Some((hash, td)))) => Some((hash, td)), + _ => None, + } +} + /// Convert a block number to a CHT number. /// Returns `None` for `block_num` == 0, `Some` otherwise. pub fn block_to_cht_number(block_num: u64) -> Option { @@ -37,6 +181,12 @@ pub fn start_number(cht_num: u64) -> u64 { #[cfg(test)] mod tests { + #[test] + fn size_is_lt_usize() { + // to ensure safe casting on the target platform. + assert!(::cht::SIZE < usize::max_value() as u64) + } + #[test] fn block_to_cht_number() { assert!(::cht::block_to_cht_number(0).is_none()); diff --git a/ethcore/light/src/client/header_chain.rs b/ethcore/light/src/client/header_chain.rs index 8bfbb7743..53c726b69 100644 --- a/ethcore/light/src/client/header_chain.rs +++ b/ethcore/light/src/client/header_chain.rs @@ -173,26 +173,34 @@ impl HeaderChain { // produce next CHT root if it's time. let earliest_era = *candidates.keys().next().expect("at least one era just created; qed"); if earliest_era + HISTORY + cht::SIZE <= number { - let mut values = Vec::with_capacity(cht::SIZE as usize); - { - let mut headers = self.headers.write(); - for i in (0..cht::SIZE).map(|x| x + earliest_era) { + let cht_num = cht::block_to_cht_number(earliest_era) + .expect("fails only for number == 0; genesis never imported; qed"); + debug_assert_eq!(cht_num as usize, self.cht_roots.lock().len()); + + let mut headers = self.headers.write(); + + let cht_root = { + let mut i = earliest_era; + + // iterable function which removes the candidates as it goes + // along. this will only be called until the CHT is complete. + let iter = || { let era_entry = candidates.remove(&i) .expect("all eras are sequential with no gaps; qed"); + i += 1; for ancient in &era_entry.candidates { headers.remove(&ancient.hash); } - values.push(( - ::rlp::encode(&i).to_vec(), - ::rlp::encode(&era_entry.canonical_hash).to_vec(), - )); - } - } + let canon = &era_entry.candidates[0]; + (canon.hash, canon.total_difficulty) + }; + cht::compute_root(cht_num, ::itertools::repeat_call(iter)) + .expect("fails only when too few items; this is checked; qed") + }; - let cht_root = ::util::triehash::trie_root(values); - debug!(target: "chain", "Produced CHT {} root: {:?}", (earliest_era - 1) % cht::SIZE, cht_root); + debug!(target: "chain", "Produced CHT {} root: {:?}", cht_num, cht_root); self.cht_roots.lock().push(cht_root); } diff --git a/ethcore/light/src/lib.rs b/ethcore/light/src/lib.rs index 6f57e075f..6236ba118 100644 --- a/ethcore/light/src/lib.rs +++ b/ethcore/light/src/lib.rs @@ -68,6 +68,7 @@ extern crate smallvec; extern crate time; extern crate futures; extern crate rand; +extern crate itertools; #[cfg(feature = "ipc")] extern crate ethcore_ipc as ipc; diff --git a/ethcore/light/src/on_demand/mod.rs b/ethcore/light/src/on_demand/mod.rs index 73a9f7e2c..10be00dd7 100644 --- a/ethcore/light/src/on_demand/mod.rs +++ b/ethcore/light/src/on_demand/mod.rs @@ -29,7 +29,7 @@ use futures::sync::oneshot; use network::PeerId; use net::{Handler, Status, Capabilities, Announcement, EventContext, BasicContext, ReqId}; -use util::{Bytes, RwLock}; +use util::{Bytes, RwLock, U256}; use types::les_request::{self as les_request, Request as LesRequest}; pub mod request; @@ -79,7 +79,7 @@ struct Peer { // Attempted request info and sender to put received value. enum Pending { - HeaderByNumber(request::HeaderByNumber, Sender), // num + CHT root + HeaderByNumber(request::HeaderByNumber, Sender<(encoded::Header, U256)>), // num + CHT root HeaderByHash(request::HeaderByHash, Sender), Block(request::Body, Sender), BlockReceipts(request::BlockReceipts, Sender>), @@ -105,14 +105,15 @@ impl Default for OnDemand { impl OnDemand { /// Request a header by block number and CHT root hash. - pub fn header_by_number(&self, ctx: &BasicContext, req: request::HeaderByNumber) -> Response { + /// Returns the header and the total difficulty. + pub fn header_by_number(&self, ctx: &BasicContext, req: request::HeaderByNumber) -> Response<(encoded::Header, U256)> { let (sender, receiver) = oneshot::channel(); self.dispatch_header_by_number(ctx, req, sender); Response(receiver) } // dispatch the request, completing the request if no peers available. - fn dispatch_header_by_number(&self, ctx: &BasicContext, req: request::HeaderByNumber, sender: Sender) { + fn dispatch_header_by_number(&self, ctx: &BasicContext, req: request::HeaderByNumber, sender: Sender<(encoded::Header, U256)>) { let num = req.num; let cht_num = match ::cht::block_to_cht_number(req.num) { Some(cht_num) => cht_num, diff --git a/ethcore/light/src/on_demand/request.rs b/ethcore/light/src/on_demand/request.rs index 025a92af6..90d6801c8 100644 --- a/ethcore/light/src/on_demand/request.rs +++ b/ethcore/light/src/on_demand/request.rs @@ -21,7 +21,7 @@ use ethcore::encoded; use ethcore::receipt::Receipt; use rlp::{RlpStream, Stream, UntrustedRlp, View}; -use util::{Address, Bytes, HashDB, H256}; +use util::{Address, Bytes, HashDB, H256, U256}; use util::memorydb::MemoryDB; use util::sha3::Hashable; use util::trie::{Trie, TrieDB, TrieError}; @@ -66,24 +66,16 @@ pub struct HeaderByNumber { impl HeaderByNumber { /// Check a response with a header and cht proof. - pub fn check_response(&self, header: &[u8], proof: &[Bytes]) -> Result { - use util::trie::{Trie, TrieDB}; - - // check the proof - let mut db = MemoryDB::new(); - - for node in proof { db.insert(&node[..]); } - let key = ::rlp::encode(&self.num); - - let expected_hash: H256 = match TrieDB::new(&db, &self.cht_root).and_then(|t| t.get(&*key))? { - Some(val) => ::rlp::decode(&val), - None => return Err(Error::BadProof) + pub fn check_response(&self, header: &[u8], proof: &[Bytes]) -> Result<(encoded::Header, U256), Error> { + let (expected_hash, td) = match ::cht::check_proof(proof, self.num, self.cht_root) { + Some((expected_hash, td)) => (expected_hash, td), + None => return Err(Error::BadProof), }; // and compare the hash to the found header. let found_hash = header.sha3(); match expected_hash == found_hash { - true => Ok(encoded::Header::new(header.to_vec())), + true => Ok((encoded::Header::new(header.to_vec()), td)), false => Err(Error::WrongHash(expected_hash, found_hash)), } } @@ -191,51 +183,44 @@ impl Account { mod tests { use super::*; use util::{MemoryDB, Address, H256, FixedHash}; - use util::trie::{Trie, TrieMut, TrieDB, SecTrieDB, TrieDBMut, SecTrieDBMut}; + use util::trie::{Trie, TrieMut, SecTrieDB, SecTrieDBMut}; use util::trie::recorder::Recorder; + use ethcore::client::{BlockChainClient, TestBlockChainClient, EachBlockWith}; use ethcore::header::Header; use ethcore::encoded; use ethcore::receipt::Receipt; #[test] fn check_header_by_number() { - let mut root = H256::default(); - let mut db = MemoryDB::new(); - let mut header = Header::new(); - header.set_number(10_000); - header.set_extra_data(b"test_header".to_vec()); + use ::cht; - { - let mut trie = TrieDBMut::new(&mut db, &mut root); - for i in (0..2048u64).map(|x| x + 8192) { - let hash = if i == 10_000 { - header.hash() - } else { - H256::random() - }; - trie.insert(&*::rlp::encode(&i), &*::rlp::encode(&hash)).unwrap(); - } - } + let test_client = TestBlockChainClient::new(); + test_client.add_blocks(10500, EachBlockWith::Nothing); - let proof = { - let trie = TrieDB::new(&db, &root).unwrap(); - let key = ::rlp::encode(&10_000u64); - let mut recorder = Recorder::new(); + let cht = { + let fetcher = |id| { + let hdr = test_client.block_header(id).unwrap(); + let td = test_client.block_total_difficulty(id).unwrap(); + Some(cht::BlockInfo { + hash: hdr.hash(), + parent_hash: hdr.parent_hash(), + total_difficulty: td, + }) + }; - trie.get_with(&*key, &mut recorder).unwrap().unwrap(); - - recorder.drain().into_iter().map(|r| r.data).collect::>() + cht::build(cht::block_to_cht_number(10_000).unwrap(), fetcher).unwrap() }; + let proof = cht.prove(10_000, 0).unwrap().unwrap(); let req = HeaderByNumber { num: 10_000, - cht_root: root, + cht_root: cht.root(), }; - let raw_header = ::rlp::encode(&header); + let raw_header = test_client.block_header(::ethcore::ids::BlockId::Number(10_000)).unwrap(); - assert!(req.check_response(&*raw_header, &proof[..]).is_ok()); + assert!(req.check_response(&raw_header.into_inner(), &proof[..]).is_ok()); } #[test] diff --git a/ethcore/light/src/provider.rs b/ethcore/light/src/provider.rs index 786415efb..4721caa73 100644 --- a/ethcore/light/src/provider.rs +++ b/ethcore/light/src/provider.rs @@ -23,6 +23,8 @@ use ethcore::transaction::PendingTransaction; use ethcore::ids::BlockId; use ethcore::encoded; +use cht::{self, BlockInfo}; + use util::{Bytes, H256}; use request; @@ -227,48 +229,54 @@ impl Provider for T { } fn header_proof(&self, req: request::HeaderProof) -> Option<(encoded::Header, Vec)> { - use util::MemoryDB; - use util::trie::{Trie, TrieMut, TrieDB, TrieDBMut, Recorder}; - - if Some(req.cht_number) != ::cht::block_to_cht_number(req.block_number) { + if Some(req.cht_number) != cht::block_to_cht_number(req.block_number) { debug!(target: "les_provider", "Requested CHT number mismatch with block number."); return None; } - let mut memdb = MemoryDB::new(); - let mut root = H256::default(); let mut needed_hdr = None; - { - let mut t = TrieDBMut::new(&mut memdb, &mut root); - let start_num = ::cht::start_number(req.cht_number); - for i in (0..::cht::SIZE).map(|x| x + start_num) { - match self.block_header(BlockId::Number(i)) { - None => return None, - Some(hdr) => { - t.insert( - &*::rlp::encode(&i), - &*::rlp::encode(&hdr.hash()), - ).expect("fresh in-memory database is infallible; qed"); - if i == req.block_number { needed_hdr = Some(hdr) } + // build the CHT, caching the requested header as we pass through it. + let cht = { + let block_info = |id| { + let hdr = self.block_header(id); + let td = self.block_total_difficulty(id); + + match (hdr, td) { + (Some(hdr), Some(td)) => { + let info = BlockInfo { + hash: hdr.hash(), + parent_hash: hdr.parent_hash(), + total_difficulty: td, + }; + + if hdr.number() == req.block_number { + needed_hdr = Some(hdr); + } + + Some(info) } + _ => None, } + }; + + match cht::build(req.cht_number, block_info) { + Some(cht) => cht, + None => return None, // incomplete CHT. } - } + }; + let needed_hdr = needed_hdr.expect("`needed_hdr` always set in loop, number checked before; qed"); - let mut recorder = Recorder::with_depth(req.from_level); - let t = TrieDB::new(&memdb, &root) - .expect("Same DB and root as just produced by TrieDBMut; qed"); - - if let Err(e) = t.get_with(&*::rlp::encode(&req.block_number), &mut recorder) { - debug!(target: "les_provider", "Error looking up number in freshly-created CHT: {}", e); - return None; + // prove our result. + match cht.prove(req.block_number, req.from_level) { + Ok(Some(proof)) => Some((needed_hdr, proof)), + Ok(None) => None, + Err(e) => { + debug!(target: "les_provider", "Error looking up number in freshly-created CHT: {}", e); + None + } } - - // TODO: cache calculated CHT if possible. - let proof = recorder.drain().into_iter().map(|x| x.data).collect(); - Some((needed_hdr, proof)) } fn ready_transactions(&self) -> Vec { diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 46785a6cb..dc9cb5944 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -552,7 +552,7 @@ impl BlockChainClient for TestBlockChainClient { let mut adding = false; let mut blocks = Vec::new(); - for (_, hash) in numbers_read.iter().sort_by(|tuple1, tuple2| tuple1.0.cmp(tuple2.0)) { + for (_, hash) in numbers_read.iter().sorted_by(|tuple1, tuple2| tuple1.0.cmp(tuple2.0)) { if hash == to { if adding { blocks.push(hash.clone()); diff --git a/ethstore/Cargo.toml b/ethstore/Cargo.toml index 091080348..a8e4be719 100755 --- a/ethstore/Cargo.toml +++ b/ethstore/Cargo.toml @@ -18,7 +18,7 @@ tiny-keccak = "1.0" docopt = { version = "0.6", optional = true } time = "0.1.34" lazy_static = "0.2" -itertools = "0.4" +itertools = "0.5" parking_lot = "0.3" ethcrypto = { path = "../ethcrypto" } ethcore-util = { path = "../util" } diff --git a/util/Cargo.toml b/util/Cargo.toml index 4cc9e78a7..7a195212a 100644 --- a/util/Cargo.toml +++ b/util/Cargo.toml @@ -21,7 +21,7 @@ rust-crypto = "0.2.34" elastic-array = { git = "https://github.com/ethcore/elastic-array" } rlp = { path = "rlp" } heapsize = { version = "0.3", features = ["unstable"] } -itertools = "0.4" +itertools = "0.5" sha3 = { path = "sha3" } clippy = { version = "0.0.103", optional = true} ethcore-devtools = { path = "../devtools" } diff --git a/util/src/triehash.rs b/util/src/triehash.rs index 13805509a..9884794ca 100644 --- a/util/src/triehash.rs +++ b/util/src/triehash.rs @@ -77,7 +77,9 @@ pub fn ordered_trie_root(input: I) -> H256 /// assert_eq!(trie_root(v), H256::from_str(root).unwrap()); /// } /// ``` -pub fn trie_root(input: Vec<(Vec, Vec)>) -> H256 { +pub fn trie_root(input: I) -> H256 + where I: IntoIterator, Vec)> +{ let gen_input = input // first put elements into btree to sort them and to remove duplicates .into_iter() From 66df4f64105100b0eddc4c1212649337dbc3b307 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Tue, 7 Feb 2017 08:46:17 +0100 Subject: [PATCH 14/17] Fix AccountCard stretch to 100% (#4450) --- js/src/modals/DappPermissions/dappPermissions.css | 4 +++- js/src/ui/Portal/portal.js | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/js/src/modals/DappPermissions/dappPermissions.css b/js/src/modals/DappPermissions/dappPermissions.css index 22df5d24b..3f2559bb3 100644 --- a/js/src/modals/DappPermissions/dappPermissions.css +++ b/js/src/modals/DappPermissions/dappPermissions.css @@ -31,8 +31,10 @@ } } -.selected, .unselected { +.selected, +.unselected { margin-bottom: 0.25em; + width: 100%; &:focus { outline: none; diff --git a/js/src/ui/Portal/portal.js b/js/src/ui/Portal/portal.js index 03b8b1f18..c24021cdb 100644 --- a/js/src/ui/Portal/portal.js +++ b/js/src/ui/Portal/portal.js @@ -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, From 1d774835de3926dfeb32356920821d1b2e6ad034 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Tue, 7 Feb 2017 07:56:44 +0000 Subject: [PATCH 15/17] [ci skip] js-precompiled 20170207-075234 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3de39df51..41210841b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1574,7 +1574,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#b281cb0711d4a9d293abd43eeeb42f61864fe60d" +source = "git+https://github.com/ethcore/js-precompiled.git#ca02457735ef5a0c15ceb792830ffae71aecfdd7" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index 6c1bf7d47..ffdea5431 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.3.68", + "version": "0.3.69", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 83cf5fc06836d70f3c6b521d4ff3a028e3e18dc3 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Tue, 7 Feb 2017 13:02:19 +0100 Subject: [PATCH 16/17] Fix Portal scrolling getting stuck (#4455) * Fix Portal scrolling getting stuck * DappCard container flex * Container height to 100% --- js/src/modals/AddDapps/addDapps.css | 4 ++++ js/src/ui/Container/container.css | 3 ++- js/src/ui/Portal/portal.css | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/js/src/modals/AddDapps/addDapps.css b/js/src/modals/AddDapps/addDapps.css index b26ffa623..1bca5b336 100644 --- a/js/src/modals/AddDapps/addDapps.css +++ b/js/src/modals/AddDapps/addDapps.css @@ -30,6 +30,10 @@ .list { margin-bottom: 1.5em; + &:last-child { + margin-bottom: 0; + } + .background { padding: 0.5em 0; } diff --git a/js/src/ui/Container/container.css b/js/src/ui/Container/container.css index 3ac8ac04e..d329744b6 100644 --- a/js/src/ui/Container/container.css +++ b/js/src/ui/Container/container.css @@ -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 { diff --git a/js/src/ui/Portal/portal.css b/js/src/ui/Portal/portal.css index d88150bfe..7e0e5a471 100644 --- a/js/src/ui/Portal/portal.css +++ b/js/src/ui/Portal/portal.css @@ -50,6 +50,7 @@ $popoverZ: 3600; left: 0; right: 0; opacity: 0.25; + z-index: -1; } .overlay { From d341b49badfd13d53f15ce42377c20d37df16fbd Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Tue, 7 Feb 2017 12:11:54 +0000 Subject: [PATCH 17/17] [ci skip] js-precompiled 20170207-120734 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 41210841b..053527bea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1574,7 +1574,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#ca02457735ef5a0c15ceb792830ffae71aecfdd7" +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)", ] diff --git a/js/package.json b/js/package.json index ffdea5431..28d65ba48 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.3.69", + "version": "0.3.70", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ",