Merge branch 'master' into softforktrigger
This commit is contained in:
commit
3ee3171b8c
90
Cargo.lock
generated
90
Cargo.lock
generated
@ -180,7 +180,7 @@ version = "1.1.1"
|
||||
source = "git+https://github.com/ethcore/rust-ctrlc.git#f4927770f89eca80ec250911eea3adcbf579ac48"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@ -189,7 +189,7 @@ name = "daemonize"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -231,7 +231,7 @@ source = "git+https://github.com/ethcore/rust-secp256k1#b6fdd43bbcf6d46adb72a92d
|
||||
dependencies = [
|
||||
"arrayvec 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gcc 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -261,8 +261,9 @@ dependencies = [
|
||||
"ethcore-ipc-codegen 1.2.0",
|
||||
"ethcore-util 1.2.0",
|
||||
"ethjson 0.1.0",
|
||||
"ethstore 0.1.0",
|
||||
"heapsize 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rayon 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -390,8 +391,8 @@ dependencies = [
|
||||
"igd 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"itertools 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"json-tests 0.1.0",
|
||||
"lazy_static 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.10 (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.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mio 0.5.1 (git+https://github.com/ethcore/mio?branch=v0.5.x)",
|
||||
"nix 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -421,6 +422,35 @@ dependencies = [
|
||||
"syntex 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ethkey"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"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)",
|
||||
"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)",
|
||||
"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)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ethstore"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ethkey 0.2.0",
|
||||
"libc 0.2.12 (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)",
|
||||
"serde 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_codegen 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syntex 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tiny-keccak 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ethsync"
|
||||
version = "1.2.0"
|
||||
@ -439,7 +469,7 @@ dependencies = [
|
||||
name = "fdlimit"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -570,12 +600,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
[[package]]
|
||||
name = "json-ipc-server"
|
||||
version = "0.2.3"
|
||||
source = "git+https://github.com/ethcore/json-ipc-server.git#b224bdbcb53cab349c278484bd3e5e22390167c9"
|
||||
source = "git+https://github.com/ethcore/json-ipc-server.git#bfe16b66b2e9412d153b1ea53bc078d74037da7f"
|
||||
dependencies = [
|
||||
"bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"jsonrpc-core 2.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"miow 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -632,12 +662,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "0.1.16"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.10"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
@ -655,7 +685,7 @@ name = "memchr"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -692,7 +722,7 @@ version = "0.5.0"
|
||||
source = "git+https://github.com/carllerche/mio.git#f4aa49a9d2c4507fb33a4533d5238e0365f67c99"
|
||||
dependencies = [
|
||||
"bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"miow 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -708,7 +738,7 @@ version = "0.5.1"
|
||||
source = "git+https://github.com/ethcore/mio?branch=v0.5.x#1fc881771fb8c2517317b4f805d7b88235be422b"
|
||||
dependencies = [
|
||||
"bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"miow 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -724,7 +754,7 @@ version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"miow 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -750,7 +780,7 @@ name = "nanomsg"
|
||||
version = "0.5.1"
|
||||
source = "git+https://github.com/ethcore/nanomsg.rs.git#c40fe442c9afaea5b38009a3d992ca044dcceb00"
|
||||
dependencies = [
|
||||
"libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"nanomsg-sys 0.5.0 (git+https://github.com/ethcore/nanomsg.rs.git)",
|
||||
]
|
||||
|
||||
@ -760,7 +790,7 @@ version = "0.5.0"
|
||||
source = "git+https://github.com/ethcore/nanomsg.rs.git#c40fe442c9afaea5b38009a3d992ca044dcceb00"
|
||||
dependencies = [
|
||||
"gcc 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -770,7 +800,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@ -781,7 +811,7 @@ version = "0.5.0-pre"
|
||||
source = "git+https://github.com/carllerche/nix-rust?rev=c4257f8a76#c4257f8a76b69b0d2e9a001d83e4bef67c03b23f"
|
||||
dependencies = [
|
||||
"bitflags 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -790,7 +820,7 @@ version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -877,7 +907,7 @@ name = "num_cpus"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1044,7 +1074,7 @@ name = "rand"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1079,7 +1109,7 @@ name = "rocksdb"
|
||||
version = "0.4.5"
|
||||
source = "git+https://github.com/ethcore/rust-rocksdb#9140e37ce0fdb748097f85653c01b0f7e3736ea9"
|
||||
dependencies = [
|
||||
"libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rocksdb-sys 0.3.0 (git+https://github.com/ethcore/rust-rocksdb)",
|
||||
]
|
||||
|
||||
@ -1089,7 +1119,7 @@ version = "0.3.0"
|
||||
source = "git+https://github.com/ethcore/rust-rocksdb#9140e37ce0fdb748097f85653c01b0f7e3736ea9"
|
||||
dependencies = [
|
||||
"gcc 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1110,7 +1140,7 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"termios 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@ -1121,7 +1151,7 @@ version = "0.2.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"gcc 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -1245,7 +1275,7 @@ version = "0.33.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"term 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -1271,7 +1301,7 @@ name = "termios"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1280,7 +1310,7 @@ version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1297,7 +1327,7 @@ version = "0.1.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
|
@ -24,12 +24,13 @@ ethash = { path = "../ethash" }
|
||||
num_cpus = "0.2"
|
||||
clippy = { version = "0.0.76", optional = true}
|
||||
crossbeam = "0.2.9"
|
||||
lazy_static = "0.1"
|
||||
lazy_static = "0.2"
|
||||
ethcore-devtools = { path = "../devtools" }
|
||||
ethjson = { path = "../json" }
|
||||
bloomchain = "0.1"
|
||||
"ethcore-ipc" = { path = "../ipc/rpc" }
|
||||
rayon = "0.3.1"
|
||||
ethstore = { path = "../ethstore" }
|
||||
|
||||
[features]
|
||||
jit = ["evmjit"]
|
||||
|
266
ethcore/src/account_provider.rs
Normal file
266
ethcore/src/account_provider.rs
Normal file
@ -0,0 +1,266 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Account management.
|
||||
|
||||
use std::fmt;
|
||||
use std::sync::RwLock;
|
||||
use std::collections::HashMap;
|
||||
use util::{Address as H160, H256, H520};
|
||||
use ethstore::{SecretStore, Error as SSError, SafeAccount, EthStore};
|
||||
use ethstore::dir::{KeyDirectory};
|
||||
use ethstore::ethkey::{Address as SSAddress, Message as SSMessage, Secret as SSSecret, Random, Generator};
|
||||
|
||||
/// Type of unlock.
|
||||
#[derive(Clone)]
|
||||
enum Unlock {
|
||||
/// If account is unlocked temporarily, it should be locked after first usage.
|
||||
Temp,
|
||||
/// Account unlocked permantently can always sign message.
|
||||
/// Use with caution.
|
||||
Perm,
|
||||
}
|
||||
|
||||
/// Data associated with account.
|
||||
#[derive(Clone)]
|
||||
struct AccountData {
|
||||
unlock: Unlock,
|
||||
password: String,
|
||||
}
|
||||
|
||||
/// `AccountProvider` errors.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Returned when account is not unlocked.
|
||||
NotUnlocked,
|
||||
/// Returned when signing fails.
|
||||
SStore(SSError),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
match *self {
|
||||
Error::NotUnlocked => write!(f, "Account is locked"),
|
||||
Error::SStore(ref e) => write!(f, "{}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SSError> for Error {
|
||||
fn from(e: SSError) -> Self {
|
||||
Error::SStore(e)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_bridge_type {
|
||||
($name: ident, $size: expr, $core: ident, $store: ident) => {
|
||||
/// Primitive
|
||||
pub struct $name([u8; $size]);
|
||||
|
||||
impl From<[u8; $size]> for $name {
|
||||
fn from(s: [u8; $size]) -> Self {
|
||||
$name(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$core> for $name {
|
||||
fn from(s: $core) -> Self {
|
||||
$name(s.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$store> for $name {
|
||||
fn from(s: $store) -> Self {
|
||||
$name(s.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<$core> for $name {
|
||||
fn into(self) -> $core {
|
||||
$core(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<$store> for $name {
|
||||
fn into(self) -> $store {
|
||||
$store::from(self.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_bridge_type!(Secret, 32, H256, SSSecret);
|
||||
impl_bridge_type!(Message, 32, H256, SSMessage);
|
||||
impl_bridge_type!(Address, 20, H160, SSAddress);
|
||||
|
||||
|
||||
struct NullDir;
|
||||
|
||||
impl KeyDirectory for NullDir {
|
||||
fn load(&self) -> Result<Vec<SafeAccount>, SSError> {
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
fn insert(&self, _account: SafeAccount) -> Result<(), SSError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove(&self, _address: &SSAddress) -> Result<(), SSError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Account management.
|
||||
/// Responsible for unlocking accounts.
|
||||
pub struct AccountProvider {
|
||||
unlocked: RwLock<HashMap<SSAddress, AccountData>>,
|
||||
sstore: Box<SecretStore>,
|
||||
}
|
||||
|
||||
impl AccountProvider {
|
||||
/// Creates new account provider.
|
||||
pub fn new(sstore: Box<SecretStore>) -> Self {
|
||||
AccountProvider {
|
||||
unlocked: RwLock::new(HashMap::new()),
|
||||
sstore: sstore,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates not disk backed provider.
|
||||
pub fn transient_provider() -> Self {
|
||||
AccountProvider {
|
||||
unlocked: RwLock::new(HashMap::new()),
|
||||
sstore: Box::new(EthStore::open(Box::new(NullDir)).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates new random account.
|
||||
pub fn new_account(&self, password: &str) -> Result<H160, Error> {
|
||||
let secret = Random.generate().unwrap().secret().clone();
|
||||
let address = try!(self.sstore.insert_account(secret, password));
|
||||
Ok(Address::from(address).into())
|
||||
}
|
||||
|
||||
/// Inserts new account into underlying store.
|
||||
/// Does not unlock account!
|
||||
pub fn insert_account<S>(&self, secret: S, password: &str) -> Result<H160, Error> where Secret: From<S> {
|
||||
let s = Secret::from(secret);
|
||||
let address = try!(self.sstore.insert_account(s.into(), password));
|
||||
Ok(Address::from(address).into())
|
||||
}
|
||||
|
||||
/// Returns addresses of all accounts.
|
||||
pub fn accounts(&self) -> Vec<H160> {
|
||||
self.sstore.accounts().into_iter().map(|a| H160(a.into())).collect()
|
||||
}
|
||||
|
||||
/// Helper method used for unlocking accounts.
|
||||
fn unlock_account<A>(&self, account: A, password: String, unlock: Unlock) -> Result<(), Error> where Address: From<A> {
|
||||
let a = Address::from(account);
|
||||
let account = a.into();
|
||||
// verify password by signing dump message
|
||||
// result may be discarded
|
||||
let _ = try!(self.sstore.sign(&account, &password, &Default::default()));
|
||||
|
||||
// check if account is already unlocked pernamently, if it is, do nothing
|
||||
{
|
||||
let unlocked = self.unlocked.read().unwrap();
|
||||
if let Some(data) = unlocked.get(&account) {
|
||||
if let Unlock::Perm = data.unlock {
|
||||
return Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let data = AccountData {
|
||||
unlock: unlock,
|
||||
password: password,
|
||||
};
|
||||
|
||||
let mut unlocked = self.unlocked.write().unwrap();
|
||||
unlocked.insert(account, data);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Unlocks account permanently.
|
||||
pub fn unlock_account_permanently<A>(&self, account: A, password: String) -> Result<(), Error> where Address: From<A> {
|
||||
self.unlock_account(account, password, Unlock::Perm)
|
||||
}
|
||||
|
||||
/// Unlocks account temporarily (for one signing).
|
||||
pub fn unlock_account_temporarily<A>(&self, account: A, password: String) -> Result<(), Error> where Address: From<A> {
|
||||
self.unlock_account(account, password, Unlock::Temp)
|
||||
}
|
||||
|
||||
/// Signs the message. Account must be unlocked.
|
||||
pub fn sign<A, M>(&self, account: A, message: M) -> Result<H520, Error> where Address: From<A>, Message: From<M> {
|
||||
let account = Address::from(account).into();
|
||||
let message = Message::from(message).into();
|
||||
|
||||
let data = {
|
||||
let unlocked = self.unlocked.read().unwrap();
|
||||
try!(unlocked.get(&account).ok_or(Error::NotUnlocked)).clone()
|
||||
};
|
||||
|
||||
if let Unlock::Temp = data.unlock {
|
||||
let mut unlocked = self.unlocked.write().unwrap();
|
||||
unlocked.remove(&account).expect("data exists: so key must exist: qed");
|
||||
}
|
||||
|
||||
let signature = try!(self.sstore.sign(&account, &data.password, &message));
|
||||
Ok(H520(signature.into()))
|
||||
}
|
||||
|
||||
/// Unlocks an account, signs the message, and locks it again.
|
||||
pub fn sign_with_password<A, M>(&self, account: A, password: String, message: M) -> Result<H520, Error> where Address: From<A>, Message: From<M> {
|
||||
let account = Address::from(account).into();
|
||||
let message = Message::from(message).into();
|
||||
let signature = try!(self.sstore.sign(&account, &password, &message));
|
||||
Ok(H520(signature.into()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::AccountProvider;
|
||||
use ethstore::SecretStore;
|
||||
use ethstore::ethkey::{Generator, Random};
|
||||
|
||||
#[test]
|
||||
fn unlock_account_temp() {
|
||||
let kp = Random.generate().unwrap();
|
||||
let ap = AccountProvider::transient_provider();
|
||||
assert!(ap.insert_account(kp.secret().clone(), "test").is_ok());
|
||||
assert!(ap.unlock_account_temporarily(kp.address(), "test1".into()).is_err());
|
||||
assert!(ap.unlock_account_temporarily(kp.address(), "test".into()).is_ok());
|
||||
assert!(ap.sign(kp.address(), [0u8; 32]).is_ok());
|
||||
assert!(ap.sign(kp.address(), [0u8; 32]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unlock_account_perm() {
|
||||
let kp = Random.generate().unwrap();
|
||||
let ap = AccountProvider::transient_provider();
|
||||
assert!(ap.insert_account(kp.secret().clone(), "test").is_ok());
|
||||
assert!(ap.unlock_account_permanently(kp.address(), "test1".into()).is_err());
|
||||
assert!(ap.unlock_account_permanently(kp.address(), "test".into()).is_ok());
|
||||
assert!(ap.sign(kp.address(), [0u8; 32]).is_ok());
|
||||
assert!(ap.sign(kp.address(), [0u8; 32]).is_ok());
|
||||
assert!(ap.unlock_account_temporarily(kp.address(), "test".into()).is_ok());
|
||||
assert!(ap.sign(kp.address(), [0u8; 32]).is_ok());
|
||||
assert!(ap.sign(kp.address(), [0u8; 32]).is_ok());
|
||||
}
|
||||
}
|
@ -17,7 +17,7 @@
|
||||
//! A blockchain engine that supports a basic, non-BFT proof-of-authority.
|
||||
|
||||
use common::*;
|
||||
use util::keys::store::AccountProvider;
|
||||
use account_provider::AccountProvider;
|
||||
use block::*;
|
||||
use spec::{CommonParams, Spec};
|
||||
use engine::*;
|
||||
@ -105,16 +105,14 @@ impl Engine for BasicAuthority {
|
||||
/// be returned.
|
||||
fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option<Vec<Bytes>> {
|
||||
if let Some(ap) = accounts {
|
||||
// check to see if author is contained in self.our_params.authorities
|
||||
if self.our_params.authorities.contains(block.header().author()) {
|
||||
if let Ok(secret) = ap.account_secret(block.header().author()) {
|
||||
return Some(block.header().author_seal(&secret));
|
||||
let header = block.header();
|
||||
let message = header.bare_hash();
|
||||
// account should be pernamently unlocked, otherwise sealing will fail
|
||||
if let Ok(signature) = ap.sign(*block.header().author(), message) {
|
||||
return Some(vec![encode(&signature).to_vec()]);
|
||||
} else {
|
||||
trace!(target: "basicauthority", "generate_seal: FAIL: accounts secret key unavailable");
|
||||
}
|
||||
} else {
|
||||
trace!(target: "basicauthority", "generate_seal: FAIL: block author {} isn't one of the authorized accounts {:?}", block.header().author(), self.our_params.authorities);
|
||||
}
|
||||
} else {
|
||||
trace!(target: "basicauthority", "generate_seal: FAIL: accounts not provided");
|
||||
}
|
||||
@ -176,16 +174,6 @@ impl Header {
|
||||
pub fn signature(&self) -> H520 {
|
||||
decode(&self.seal()[0])
|
||||
}
|
||||
|
||||
/// Generate a seal for the block with the given `secret`.
|
||||
pub fn author_seal(&self, secret: &Secret) -> Vec<Bytes> {
|
||||
vec![encode(&ec::sign(secret, &self.bare_hash()).unwrap_or(Signature::new())).to_vec()]
|
||||
}
|
||||
|
||||
/// Set the nonce and mix hash fields of the header.
|
||||
pub fn sign(&mut self, secret: &Secret) {
|
||||
self.seal = self.author_seal(secret);
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new test chain spec with `BasicAuthority` consensus engine.
|
||||
@ -197,7 +185,7 @@ mod tests {
|
||||
use common::*;
|
||||
use block::*;
|
||||
use tests::helpers::*;
|
||||
use util::keys::{TestAccountProvider, TestAccount};
|
||||
use account_provider::AccountProvider;
|
||||
|
||||
#[test]
|
||||
fn has_valid_metadata() {
|
||||
@ -252,24 +240,11 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_do_signature_verification() {
|
||||
let secret = "".sha3();
|
||||
let addr = KeyPair::from_secret("".sha3()).unwrap().address();
|
||||
|
||||
let engine = new_test_authority().engine;
|
||||
let mut header: Header = Header::default();
|
||||
header.set_author(addr);
|
||||
header.sign(&secret);
|
||||
|
||||
assert!(engine.verify_block_unordered(&header, None).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_generate_seal() {
|
||||
let addr = KeyPair::from_secret("".sha3()).unwrap().address();
|
||||
let accounts = hash_map![addr => TestAccount{unlocked: true, password: Default::default(), secret: "".sha3()}];
|
||||
let tap = TestAccountProvider::new(accounts);
|
||||
let tap = AccountProvider::transient_provider();
|
||||
let addr = tap.insert_account("".sha3(), "").unwrap();
|
||||
tap.unlock_account_permanently(addr, "".into()).unwrap();
|
||||
|
||||
let spec = new_test_authority();
|
||||
let engine = &spec.engine;
|
||||
@ -279,10 +254,9 @@ mod tests {
|
||||
spec.ensure_db_good(db.as_hashdb_mut());
|
||||
let last_hashes = vec![genesis_header.hash()];
|
||||
let vm_factory = Default::default();
|
||||
let b = OpenBlock::new(engine.deref(), &vm_factory, false, db, &genesis_header, last_hashes, None, addr.clone(), 3141562.into(), vec![]).unwrap();
|
||||
let b = OpenBlock::new(engine.deref(), &vm_factory, false, db, &genesis_header, last_hashes, None, addr, 3141562.into(), vec![]).unwrap();
|
||||
let b = b.close_and_lock();
|
||||
let seal = engine.generate_seal(b.block(), Some(&tap)).unwrap();
|
||||
|
||||
assert!(b.try_seal(engine.deref(), seal).is_ok());
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@
|
||||
//! Consensus engine specification
|
||||
|
||||
use common::*;
|
||||
use util::keys::store::AccountProvider;
|
||||
use account_provider::AccountProvider;
|
||||
use block::ExecutedBlock;
|
||||
use spec::CommonParams;
|
||||
use evm::Schedule;
|
||||
|
@ -91,10 +91,12 @@ extern crate ethjson;
|
||||
extern crate bloomchain;
|
||||
#[macro_use] extern crate ethcore_ipc as ipc;
|
||||
extern crate rayon;
|
||||
pub extern crate ethstore;
|
||||
|
||||
#[cfg(test)] extern crate ethcore_devtools as devtools;
|
||||
#[cfg(feature = "jit" )] extern crate evmjit;
|
||||
|
||||
pub mod account_provider;
|
||||
pub mod basic_authority;
|
||||
pub mod block;
|
||||
pub mod block_queue;
|
||||
|
@ -18,7 +18,7 @@ use rayon::prelude::*;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
|
||||
use util::*;
|
||||
use util::keys::store::{AccountProvider};
|
||||
use account_provider::AccountProvider;
|
||||
use views::{BlockView, HeaderView};
|
||||
use client::{MiningBlockChainClient, Executive, Executed, EnvInfo, TransactOptions, BlockID, CallAnalytics};
|
||||
use block::{ClosedBlock, IsBlock};
|
||||
@ -31,13 +31,14 @@ use miner::{MinerService, MinerStatus, TransactionQueue, AccountDetails, Transac
|
||||
|
||||
/// Keeps track of transactions using priority queue and holds currently mined block.
|
||||
pub struct Miner {
|
||||
// NOTE [ToDr] When locking always lock in this order!
|
||||
transaction_queue: Mutex<TransactionQueue>,
|
||||
sealing_work: Mutex<UsingQueue<ClosedBlock>>,
|
||||
|
||||
// for sealing...
|
||||
force_sealing: bool,
|
||||
sealing_enabled: AtomicBool,
|
||||
sealing_block_last_request: Mutex<u64>,
|
||||
sealing_work: Mutex<UsingQueue<ClosedBlock>>,
|
||||
gas_floor_target: RwLock<U256>,
|
||||
author: RwLock<Address>,
|
||||
extra_data: RwLock<Bytes>,
|
||||
@ -385,9 +386,13 @@ impl MinerService for Miner {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn import_own_transaction<T>(&self, chain: &MiningBlockChainClient, transaction: SignedTransaction, fetch_account: T) ->
|
||||
Result<TransactionImportResult, Error>
|
||||
where T: Fn(&Address) -> AccountDetails {
|
||||
fn import_own_transaction<T>(
|
||||
&self,
|
||||
chain: &MiningBlockChainClient,
|
||||
transaction: SignedTransaction,
|
||||
fetch_account: T
|
||||
) -> Result<TransactionImportResult, Error> where T: Fn(&Address) -> AccountDetails {
|
||||
|
||||
let hash = transaction.hash();
|
||||
trace!(target: "own_tx", "Importing transaction: {:?}", transaction);
|
||||
|
||||
@ -425,20 +430,20 @@ impl MinerService for Miner {
|
||||
}
|
||||
|
||||
fn pending_transactions_hashes(&self) -> Vec<H256> {
|
||||
let queue = self.transaction_queue.lock().unwrap();
|
||||
match (self.sealing_enabled.load(atomic::Ordering::Relaxed), self.sealing_work.lock().unwrap().peek_last_ref()) {
|
||||
(true, Some(pending)) => pending.transactions().iter().map(|t| t.hash()).collect(),
|
||||
_ => {
|
||||
let queue = self.transaction_queue.lock().unwrap();
|
||||
queue.pending_hashes()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn transaction(&self, hash: &H256) -> Option<SignedTransaction> {
|
||||
let queue = self.transaction_queue.lock().unwrap();
|
||||
match (self.sealing_enabled.load(atomic::Ordering::Relaxed), self.sealing_work.lock().unwrap().peek_last_ref()) {
|
||||
(true, Some(pending)) => pending.transactions().iter().find(|t| &t.hash() == hash).cloned(),
|
||||
_ => {
|
||||
let queue = self.transaction_queue.lock().unwrap();
|
||||
queue.find(hash)
|
||||
}
|
||||
}
|
||||
@ -450,11 +455,11 @@ impl MinerService for Miner {
|
||||
}
|
||||
|
||||
fn pending_transactions(&self) -> Vec<SignedTransaction> {
|
||||
let queue = self.transaction_queue.lock().unwrap();
|
||||
// TODO: should only use the sealing_work when it's current (it could be an old block)
|
||||
match (self.sealing_enabled.load(atomic::Ordering::Relaxed), self.sealing_work.lock().unwrap().peek_last_ref()) {
|
||||
(true, Some(pending)) => pending.transactions().clone(),
|
||||
_ => {
|
||||
let queue = self.transaction_queue.lock().unwrap();
|
||||
queue.top_transactions()
|
||||
}
|
||||
}
|
||||
|
@ -140,8 +140,13 @@ impl Transaction {
|
||||
|
||||
/// Signs the transaction as coming from `sender`.
|
||||
pub fn sign(self, secret: &Secret) -> SignedTransaction {
|
||||
let sig = ec::sign(secret, &self.hash());
|
||||
let (r, s, v) = sig.unwrap().to_rsv();
|
||||
let sig = ec::sign(secret, &self.hash()).unwrap();
|
||||
self.with_signature(sig)
|
||||
}
|
||||
|
||||
/// Signs the transaction with signature.
|
||||
pub fn with_signature(self, sig: H520) -> SignedTransaction {
|
||||
let (r, s, v) = sig.to_rsv();
|
||||
SignedTransaction {
|
||||
unsigned: self,
|
||||
r: r,
|
||||
|
2
ethkey/.gitignore
vendored
Normal file
2
ethkey/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
target
|
||||
*.swp
|
23
ethkey/.travis.yml
Normal file
23
ethkey/.travis.yml
Normal file
@ -0,0 +1,23 @@
|
||||
sudo: false
|
||||
language: rust
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
matrix:
|
||||
fast_finish: false
|
||||
include:
|
||||
- rust: stable
|
||||
- rust: beta
|
||||
- rust: nightly
|
||||
after_success: |
|
||||
[ $TRAVIS_BRANCH = master ] &&
|
||||
[ $TRAVIS_PULL_REQUEST = false ] &&
|
||||
[ $TRAVIS_RUST_VERSION = stable ] &&
|
||||
cargo doc --no-deps --verbose &&
|
||||
echo '<meta http-equiv=refresh content=0;url=ethkey/index.html>' > target/doc/index.html &&
|
||||
pip install --user ghp-import &&
|
||||
/home/travis/.local/bin/ghp-import -n target/doc &&
|
||||
git push -fq https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git gh-pages
|
||||
env:
|
||||
global:
|
||||
- secure: LBkFAH5fAhzHRP7kYQZnOCavOuPS2vEDv49KGfTsY/7MmW0De4c0sz0a3/0IdFqqIMLYLV2uMO86S0p6FBaq6/GIdobLsGZZ3cFReYFI+vb8sylYF/+D/aQ/UOjpEOD8HP6G3YmV5buSyL8uiPlmYbqwBAe4z6ELEbh/16gRuIqQLYQtpYPxMCD3tZzSux81b45K2khETZ7E+ap3LUG3rFTXxjEgx9leIZlVY+Qk4U5D9gFnJnjmxDPyIqzn2dORnw5jcpp3eSUEvSvSgjz4TAVg7Gw789jDl2dyr26U1wp1E5bB9AqZVYOb4l8vcQ6QiHrCvu7Wgl32O6XYkwMjDaDUB68bm5MTsUrwDWgKGx4xeurIBil5doHFlCGZ98RrzPxdgoCd6hCI459dA8jEwdXAfOkZ80RycZlryHCwn68x3dlnJoqVyg8viYo6H6G0GdH/dIhuwbnLDdWZsODehN8eJEy9KKQ4tPp+PjBcgKm1Wz5MzKFSIwfFInic7hjTVXGozHSvgvXJE0BI2bPbjVNCdZa5kGAAUAhBNXyTn7PbC7hYbmwAalzaOIjoYcdQLmUEz2J2gSOK8xW2gMU0Z2I+IylA0oh8xB/r2Q5sqLHT3LPLdzoETsyzaQjWFcFdXdsbbcG59DnFC9s2Jq7KqeODp6EJG4cw0ofKpBuDRes=
|
21
ethkey/Cargo.toml
Normal file
21
ethkey/Cargo.toml
Normal file
@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "ethkey"
|
||||
version = "0.2.0"
|
||||
authors = ["debris <marek.kotewicz@gmail.com>"]
|
||||
|
||||
[dependencies]
|
||||
rand = "0.3.14"
|
||||
lazy_static = "0.2.1"
|
||||
tiny-keccak = "1.0"
|
||||
eth-secp256k1 = { git = "https://github.com/ethcore/rust-secp256k1" }
|
||||
rustc-serialize = "0.3"
|
||||
docopt = { version = "0.6", optional = true }
|
||||
|
||||
[features]
|
||||
default = ["cli"]
|
||||
cli = ["docopt"]
|
||||
|
||||
[[bin]]
|
||||
name = "ethkey"
|
||||
path = "src/bin/main.rs"
|
||||
doc = false
|
150
ethkey/README.md
Normal file
150
ethkey/README.md
Normal file
@ -0,0 +1,150 @@
|
||||
# ethkey
|
||||
|
||||
[![Build Status][travis-image]][travis-url]
|
||||
|
||||
[travis-image]: https://travis-ci.org/ethcore/ethkey.svg?branch=master
|
||||
[travis-url]: https://travis-ci.org/ethcore/ethkey
|
||||
|
||||
Ethereum keys generator.
|
||||
|
||||
[Documentation](http://ethcore.github.io/ethkey/ethkey/index.html)
|
||||
|
||||
### Usage
|
||||
|
||||
```
|
||||
Ethereum keys generator.
|
||||
Copyright 2016 Ethcore (UK) Limited
|
||||
|
||||
Usage:
|
||||
ethkey info <secret> [options]
|
||||
ethkey generate random [options]
|
||||
ethkey generate prefix <prefix> <iterations> [options]
|
||||
ethkey generate brain <seed> [options]
|
||||
ethkey sign <secret> <message>
|
||||
ethkey verify <public> <signature> <message>
|
||||
ethkey [-h | --help]
|
||||
|
||||
Options:
|
||||
-h, --help Display this message and exit.
|
||||
-s, --secret Display only the secret.
|
||||
-p, --public Display only the public.
|
||||
-a, --address Display only the address.
|
||||
|
||||
Commands:
|
||||
info Display public and address of the secret.
|
||||
generate Generates new ethereum key.
|
||||
random Random generation.
|
||||
prefix Random generation, but address must start with a prefix
|
||||
brain Generate new key from string seed.
|
||||
sign Sign message using secret.
|
||||
verify Verify signer of the signature.
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
#### `info <secret>`
|
||||
*Display info about private key.*
|
||||
|
||||
- `<secret>` - ethereum secret, 32 bytes long
|
||||
|
||||
```
|
||||
ethkey info 17d08f5fe8c77af811caa0c9a187e668ce3b74a99acc3f6d976f075fa8e0be55
|
||||
```
|
||||
|
||||
```
|
||||
secret: 17d08f5fe8c77af811caa0c9a187e668ce3b74a99acc3f6d976f075fa8e0be55
|
||||
public: 689268c0ff57a20cd299fa60d3fb374862aff565b20b5f1767906a99e6e09f3ff04ca2b2a5cd22f62941db103c0356df1a8ed20ce322cab2483db67685afd124
|
||||
address: 26d1ec50b4e62c1d1a40d16e7cacc6a6580757d5
|
||||
```
|
||||
|
||||
--
|
||||
|
||||
#### `generate brain <seed>`
|
||||
*Generate new brain-wallet keypair using 16384 iterations.*
|
||||
|
||||
- `<seed>` - brain-wallet seed, any string
|
||||
|
||||
|
||||
```
|
||||
ethkey generate brain "this is sparta"
|
||||
```
|
||||
|
||||
```
|
||||
secret: 17d08f5fe8c77af811caa0c9a187e668ce3b74a99acc3f6d976f075fa8e0be55
|
||||
public: 689268c0ff57a20cd299fa60d3fb374862aff565b20b5f1767906a99e6e09f3ff04ca2b2a5cd22f62941db103c0356df1a8ed20ce322cab2483db67685afd124
|
||||
address: 26d1ec50b4e62c1d1a40d16e7cacc6a6580757d5
|
||||
```
|
||||
|
||||
--
|
||||
|
||||
#### `generate random`
|
||||
*Generate new keypair randomly.*
|
||||
|
||||
```
|
||||
ethkey generate random
|
||||
```
|
||||
|
||||
```
|
||||
secret: 7d29fab185a33e2cd955812397354c472d2b84615b645aa135ff539f6b0d70d5
|
||||
public: 35f222d88b80151857a2877826d940104887376a94c1cbd2c8c7c192eb701df88a18a4ecb8b05b1466c5b3706042027b5e079fe3a3683e66d822b0e047aa3418
|
||||
address: a8fa5dd30a87bb9e3288d604eb74949c515ab66e
|
||||
```
|
||||
|
||||
--
|
||||
|
||||
#### `generate prefix <prefix> <iterations>`
|
||||
*Generate new keypair randomly with address starting with prefix.*
|
||||
|
||||
- `<prefix>` - desired address prefix, 0 - 32 bytes long.
|
||||
- `<iterations>` - maximum number of tries before generation is assumed to be a failure.
|
||||
|
||||
```
|
||||
ethkey generate prefix ff 1000
|
||||
```
|
||||
|
||||
```
|
||||
secret: 2075b1d9c124ea673de7273758ed6de14802a9da8a73ceb74533d7c312ff6acd
|
||||
public: 48dbce4508566a05509980a5dd1335599fcdac6f9858ba67018cecb9f09b8c4066dc4c18ae2722112fd4d9ac36d626793fffffb26071dfeb0c2300df994bd173
|
||||
address: fff7e25dff2aa60f61f9d98130c8646a01f31649
|
||||
```
|
||||
|
||||
--
|
||||
|
||||
#### `sign <secret> <message>`
|
||||
*Sign a message with a secret.*
|
||||
|
||||
- `<secret>` - ethereum secret, 32 bytes long
|
||||
- `<message>` - message to sign, 32 bytes long
|
||||
|
||||
```
|
||||
ethkey sign 17d08f5fe8c77af811caa0c9a187e668ce3b74a99acc3f6d976f075fa8e0be55 bd50b7370c3f96733b31744c6c45079e7ae6c8d299613246d28ebcef507ec987
|
||||
```
|
||||
|
||||
```
|
||||
c1878cf60417151c766a712653d26ef350c8c75393458b7a9be715f053215af63dfd3b02c2ae65a8677917a8efa3172acb71cb90196e42106953ea0363c5aaf200
|
||||
```
|
||||
|
||||
--
|
||||
|
||||
#### `verify <public> <signature> <message>`
|
||||
*Verify the signature.*
|
||||
|
||||
- `<secret>` - ethereum public, 64 bytes long
|
||||
- `<signature>` - message signature, 65 bytes long
|
||||
- `<message>` - message, 32 bytes long
|
||||
|
||||
```
|
||||
ethkey verify 689268c0ff57a20cd299fa60d3fb374862aff565b20b5f1767906a99e6e09f3ff04ca2b2a5cd22f62941db103c0356df1a8ed20ce322cab2483db67685afd124 c1878cf60417151c766a712653d26ef350c8c75393458b7a9be715f053215af63dfd3b02c2ae65a8677917a8efa3172acb71cb90196e42106953ea0363c5aaf200 bd50b7370c3f96733b31744c6c45079e7ae6c8d299613246d28ebcef507ec987
|
||||
```
|
||||
|
||||
```
|
||||
true
|
||||
```
|
||||
|
||||
|
||||
# Ethcore toolchain
|
||||
*this project is a part of the ethcore toolchain*
|
||||
|
||||
- [**ethkey**](https://github.com/ethcore/ethkey) - Ethereum keys generator and signer.
|
||||
- [**ethstore**](https://github.com/ethcore/ethstore) - Ethereum key management.
|
||||
- [**ethabi**](https://github.com/ethcore/ethabi) - Ethereum function calls encoding.
|
274
ethkey/src/bin/ethkey.rs
Normal file
274
ethkey/src/bin/ethkey.rs
Normal file
@ -0,0 +1,274 @@
|
||||
extern crate docopt;
|
||||
extern crate rustc_serialize;
|
||||
extern crate ethkey;
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::{env, fmt, process};
|
||||
use std::num::ParseIntError;
|
||||
use docopt::Docopt;
|
||||
use rustc_serialize::hex::{FromHex, FromHexError};
|
||||
use ethkey::{KeyPair, Random, Brain, Prefix, Error as EthkeyError, Generator, Secret, Message, Public, Signature, sign, verify};
|
||||
|
||||
pub const USAGE: &'static str = r#"
|
||||
Ethereum keys generator.
|
||||
Copyright 2016 Ethcore (UK) Limited
|
||||
|
||||
Usage:
|
||||
ethkey info <secret> [options]
|
||||
ethkey generate random [options]
|
||||
ethkey generate prefix <prefix> <iterations> [options]
|
||||
ethkey generate brain <seed> [options]
|
||||
ethkey sign <secret> <message>
|
||||
ethkey verify <public> <signature> <message>
|
||||
ethkey [-h | --help]
|
||||
|
||||
Options:
|
||||
-h, --help Display this message and exit.
|
||||
-s, --secret Display only the secret.
|
||||
-p, --public Display only the public.
|
||||
-a, --address Display only the address.
|
||||
|
||||
Commands:
|
||||
info Display public and address of the secret.
|
||||
generate Generates new ethereum key.
|
||||
random Random generation.
|
||||
prefix Random generation, but address must start with a prefix
|
||||
brain Generate new key from string seed.
|
||||
sign Sign message using secret.
|
||||
verify Verify signer of the signature.
|
||||
"#;
|
||||
|
||||
#[derive(Debug, RustcDecodable)]
|
||||
struct Args {
|
||||
cmd_info: bool,
|
||||
cmd_generate: bool,
|
||||
cmd_random: bool,
|
||||
cmd_prefix: bool,
|
||||
cmd_brain: bool,
|
||||
cmd_sign: bool,
|
||||
cmd_verify: bool,
|
||||
arg_prefix: String,
|
||||
arg_iterations: String,
|
||||
arg_seed: String,
|
||||
arg_secret: String,
|
||||
arg_message: String,
|
||||
arg_public: String,
|
||||
arg_signature: String,
|
||||
flag_secret: bool,
|
||||
flag_public: bool,
|
||||
flag_address: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Error {
|
||||
Ethkey(EthkeyError),
|
||||
FromHex(FromHexError),
|
||||
ParseInt(ParseIntError),
|
||||
}
|
||||
|
||||
impl From<EthkeyError> for Error {
|
||||
fn from(err: EthkeyError) -> Self {
|
||||
Error::Ethkey(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FromHexError> for Error {
|
||||
fn from(err: FromHexError) -> Self {
|
||||
Error::FromHex(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseIntError> for Error {
|
||||
fn from(err: ParseIntError) -> Self {
|
||||
Error::ParseInt(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
match *self {
|
||||
Error::Ethkey(ref e) => write!(f, "{}", e),
|
||||
Error::FromHex(ref e) => write!(f, "{}", e),
|
||||
Error::ParseInt(ref e) => write!(f, "{}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum DisplayMode {
|
||||
KeyPair,
|
||||
Secret,
|
||||
Public,
|
||||
Address,
|
||||
}
|
||||
|
||||
impl DisplayMode {
|
||||
fn new(args: &Args) -> Self {
|
||||
if args.flag_secret {
|
||||
DisplayMode::Secret
|
||||
} else if args.flag_public {
|
||||
DisplayMode::Public
|
||||
} else if args.flag_address {
|
||||
DisplayMode::Address
|
||||
} else {
|
||||
DisplayMode::KeyPair
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
match execute(env::args()) {
|
||||
Ok(ok) => println!("{}", ok),
|
||||
Err(err) => {
|
||||
println!("{}", err);
|
||||
process::exit(1);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn display(keypair: KeyPair, mode: DisplayMode) -> String {
|
||||
match mode {
|
||||
DisplayMode::KeyPair => format!("{}", keypair),
|
||||
DisplayMode::Secret => format!("{}", keypair.secret()),
|
||||
DisplayMode::Public => format!("{}", keypair.public()),
|
||||
DisplayMode::Address => format!("{}", keypair.address()),
|
||||
}
|
||||
}
|
||||
|
||||
fn execute<S, I>(command: I) -> Result<String, Error> where I: IntoIterator<Item=S>, S: AsRef<str> {
|
||||
let args: Args = Docopt::new(USAGE)
|
||||
.and_then(|d| d.argv(command).decode())
|
||||
.unwrap_or_else(|e| e.exit());
|
||||
|
||||
return if args.cmd_info {
|
||||
let display_mode = DisplayMode::new(&args);
|
||||
let secret = try!(Secret::from_str(&args.arg_secret));
|
||||
let keypair = try!(KeyPair::from_secret(secret));
|
||||
Ok(display(keypair, display_mode))
|
||||
} else if args.cmd_generate {
|
||||
let display_mode = DisplayMode::new(&args);
|
||||
let keypair = if args.cmd_random {
|
||||
Random.generate()
|
||||
} else if args.cmd_prefix {
|
||||
let prefix = try!(args.arg_prefix.from_hex());
|
||||
let iterations = try!(usize::from_str_radix(&args.arg_iterations, 10));
|
||||
Prefix::new(prefix, iterations).generate()
|
||||
} else if args.cmd_brain {
|
||||
Brain::new(args.arg_seed).generate()
|
||||
} else {
|
||||
unreachable!();
|
||||
};
|
||||
Ok(display(try!(keypair), display_mode))
|
||||
} else if args.cmd_sign {
|
||||
let secret = try!(Secret::from_str(&args.arg_secret));
|
||||
let message = try!(Message::from_str(&args.arg_message));
|
||||
let signature = try!(sign(&secret, &message));
|
||||
Ok(format!("{}", signature))
|
||||
} else if args.cmd_verify {
|
||||
let public = try!(Public::from_str(&args.arg_public));
|
||||
let signature = try!(Signature::from_str(&args.arg_signature));
|
||||
let message = try!(Message::from_str(&args.arg_message));
|
||||
let ok = try!(verify(&public, &signature, &message));
|
||||
Ok(format!("{}", ok))
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::execute;
|
||||
|
||||
#[test]
|
||||
fn info() {
|
||||
let command = vec!["ethkey", "info", "17d08f5fe8c77af811caa0c9a187e668ce3b74a99acc3f6d976f075fa8e0be55"]
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let expected =
|
||||
"secret: 17d08f5fe8c77af811caa0c9a187e668ce3b74a99acc3f6d976f075fa8e0be55
|
||||
public: 689268c0ff57a20cd299fa60d3fb374862aff565b20b5f1767906a99e6e09f3ff04ca2b2a5cd22f62941db103c0356df1a8ed20ce322cab2483db67685afd124
|
||||
address: 26d1ec50b4e62c1d1a40d16e7cacc6a6580757d5".to_owned();
|
||||
assert_eq!(execute(command).unwrap(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn brain() {
|
||||
let command = vec!["ethkey", "generate", "brain", "this is sparta"]
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let expected =
|
||||
"secret: 17d08f5fe8c77af811caa0c9a187e668ce3b74a99acc3f6d976f075fa8e0be55
|
||||
public: 689268c0ff57a20cd299fa60d3fb374862aff565b20b5f1767906a99e6e09f3ff04ca2b2a5cd22f62941db103c0356df1a8ed20ce322cab2483db67685afd124
|
||||
address: 26d1ec50b4e62c1d1a40d16e7cacc6a6580757d5".to_owned();
|
||||
assert_eq!(execute(command).unwrap(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn secret() {
|
||||
let command = vec!["ethkey", "generate", "brain", "this is sparta", "--secret"]
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let expected = "17d08f5fe8c77af811caa0c9a187e668ce3b74a99acc3f6d976f075fa8e0be55".to_owned();
|
||||
assert_eq!(execute(command).unwrap(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public() {
|
||||
let command = vec!["ethkey", "generate", "brain", "this is sparta", "--public"]
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let expected = "689268c0ff57a20cd299fa60d3fb374862aff565b20b5f1767906a99e6e09f3ff04ca2b2a5cd22f62941db103c0356df1a8ed20ce322cab2483db67685afd124".to_owned();
|
||||
assert_eq!(execute(command).unwrap(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn address() {
|
||||
let command = vec!["ethkey", "generate", "brain", "this is sparta", "--address"]
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let expected = "26d1ec50b4e62c1d1a40d16e7cacc6a6580757d5".to_owned();
|
||||
assert_eq!(execute(command).unwrap(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sign() {
|
||||
let command = vec!["ethkey", "sign", "17d08f5fe8c77af811caa0c9a187e668ce3b74a99acc3f6d976f075fa8e0be55", "bd50b7370c3f96733b31744c6c45079e7ae6c8d299613246d28ebcef507ec987"]
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let expected = "c1878cf60417151c766a712653d26ef350c8c75393458b7a9be715f053215af63dfd3b02c2ae65a8677917a8efa3172acb71cb90196e42106953ea0363c5aaf200".to_owned();
|
||||
assert_eq!(execute(command).unwrap(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_valid() {
|
||||
let command = vec!["ethkey", "verify", "689268c0ff57a20cd299fa60d3fb374862aff565b20b5f1767906a99e6e09f3ff04ca2b2a5cd22f62941db103c0356df1a8ed20ce322cab2483db67685afd124", "c1878cf60417151c766a712653d26ef350c8c75393458b7a9be715f053215af63dfd3b02c2ae65a8677917a8efa3172acb71cb90196e42106953ea0363c5aaf200", "bd50b7370c3f96733b31744c6c45079e7ae6c8d299613246d28ebcef507ec987"]
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let expected = "true".to_owned();
|
||||
assert_eq!(execute(command).unwrap(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_invalid() {
|
||||
let command = vec!["ethkey", "verify", "689268c0ff57a20cd299fa60d3fb374862aff565b20b5f1767906a99e6e09f3ff04ca2b2a5cd22f62941db103c0356df1a8ed20ce322cab2483db67685afd124", "c1878cf60417151c766a712653d26ef350c8c75393458b7a9be715f053215af63dfd3b02c2ae65a8677917a8efa3172acb71cb90196e42106953ea0363c5aaf200", "bd50b7370c3f96733b31744c6c45079e7ae6c8d299613246d28ebcef507ec986"]
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let expected = "false".to_owned();
|
||||
assert_eq!(execute(command).unwrap(), expected);
|
||||
}
|
||||
}
|
5
ethkey/src/bin/main.rs
Normal file
5
ethkey/src/bin/main.rs
Normal file
@ -0,0 +1,5 @@
|
||||
#[cfg(feature = "cli")]
|
||||
include!("ethkey.rs");
|
||||
|
||||
#[cfg(not(feature = "cli"))]
|
||||
fn main() {}
|
46
ethkey/src/brain.rs
Normal file
46
ethkey/src/brain.rs
Normal file
@ -0,0 +1,46 @@
|
||||
use keccak::Keccak256;
|
||||
use super::{KeyPair, Error, Generator, Secret};
|
||||
|
||||
/// Simple brainwallet.
|
||||
pub struct Brain(String);
|
||||
|
||||
impl Brain {
|
||||
pub fn new(s: String) -> Self {
|
||||
Brain(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl Generator for Brain {
|
||||
fn generate(self) -> Result<KeyPair, Error> {
|
||||
let seed = self.0;
|
||||
let mut secret = seed.bytes().collect::<Vec<u8>>().keccak256();
|
||||
|
||||
let mut i = 0;
|
||||
loop {
|
||||
secret = secret.keccak256();
|
||||
|
||||
match i > 16384 {
|
||||
false => i += 1,
|
||||
true => {
|
||||
let result = KeyPair::from_secret(Secret::from(secret.clone()));
|
||||
if result.is_ok() {
|
||||
return result
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use {Brain, Generator};
|
||||
|
||||
#[test]
|
||||
fn test_brain() {
|
||||
let words = "this is sparta!".to_owned();
|
||||
let first_keypair = Brain(words.clone()).generate().unwrap();
|
||||
let second_keypair = Brain(words.clone()).generate().unwrap();
|
||||
assert_eq!(first_keypair.secret(), second_keypair.secret());
|
||||
}
|
||||
}
|
53
ethkey/src/error.rs
Normal file
53
ethkey/src/error.rs
Normal file
@ -0,0 +1,53 @@
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Crypto error
|
||||
pub enum Error {
|
||||
/// Invalid secret key
|
||||
InvalidSecret,
|
||||
/// Invalid public key
|
||||
InvalidPublic,
|
||||
/// Invalid address
|
||||
InvalidAddress,
|
||||
/// Invalid EC signature
|
||||
InvalidSignature,
|
||||
/// Invalid AES message
|
||||
InvalidMessage,
|
||||
/// IO Error
|
||||
Io(::std::io::Error),
|
||||
/// Custom
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let msg = match *self {
|
||||
Error::InvalidSecret => "Invalid secret key".into(),
|
||||
Error::InvalidPublic => "Invalid public key".into(),
|
||||
Error::InvalidAddress => "Invalid address".into(),
|
||||
Error::InvalidSignature => "Invalid EC signature".into(),
|
||||
Error::InvalidMessage => "Invalid AES message".into(),
|
||||
Error::Io(ref err) => format!("I/O error: {}", err),
|
||||
Error::Custom(ref s) => s.clone(),
|
||||
};
|
||||
|
||||
f.write_fmt(format_args!("Crypto error ({})", msg))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<::secp256k1::Error> for Error {
|
||||
fn from(e: ::secp256k1::Error) -> Error {
|
||||
match e {
|
||||
::secp256k1::Error::InvalidMessage => Error::InvalidMessage,
|
||||
::secp256k1::Error::InvalidPublicKey => Error::InvalidPublic,
|
||||
::secp256k1::Error::InvalidSecretKey => Error::InvalidSecret,
|
||||
_ => Error::InvalidSignature,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<::std::io::Error> for Error {
|
||||
fn from(err: ::std::io::Error) -> Error {
|
||||
Error::Io(err)
|
||||
}
|
||||
}
|
15
ethkey/src/keccak.rs
Normal file
15
ethkey/src/keccak.rs
Normal file
@ -0,0 +1,15 @@
|
||||
use tiny_keccak::Keccak;
|
||||
|
||||
pub trait Keccak256<T> {
|
||||
fn keccak256(&self) -> T where T: Sized;
|
||||
}
|
||||
|
||||
impl Keccak256<[u8; 32]> for [u8] {
|
||||
fn keccak256(&self) -> [u8; 32] {
|
||||
let mut keccak = Keccak::new_keccak256();
|
||||
let mut result = [0u8; 32];
|
||||
keccak.update(self);
|
||||
keccak.finalize(&mut result);
|
||||
result
|
||||
}
|
||||
}
|
91
ethkey/src/keypair.rs
Normal file
91
ethkey/src/keypair.rs
Normal file
@ -0,0 +1,91 @@
|
||||
use std::fmt;
|
||||
use secp256k1::key;
|
||||
use rustc_serialize::hex::ToHex;
|
||||
use keccak::Keccak256;
|
||||
use super::{Secret, Public, Address, SECP256K1, Error};
|
||||
|
||||
/// secp256k1 key pair
|
||||
pub struct KeyPair {
|
||||
secret: Secret,
|
||||
public: Public,
|
||||
}
|
||||
|
||||
impl fmt::Display for KeyPair {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
try!(writeln!(f, "secret: {}", self.secret.to_hex()));
|
||||
try!(writeln!(f, "public: {}", self.public.to_hex()));
|
||||
write!(f, "address: {}", self.address().to_hex())
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyPair {
|
||||
/// Create a pair from secret key
|
||||
pub fn from_secret(secret: Secret) -> Result<KeyPair, Error> {
|
||||
let context = &SECP256K1;
|
||||
let s: key::SecretKey = try!(key::SecretKey::from_slice(context, &secret[..]));
|
||||
let pub_key = try!(key::PublicKey::from_secret_key(context, &s));
|
||||
let serialized = pub_key.serialize_vec(context, false);
|
||||
|
||||
let mut public = Public::default();
|
||||
public.copy_from_slice(&serialized[1..65]);
|
||||
|
||||
let keypair = KeyPair {
|
||||
secret: secret,
|
||||
public: public,
|
||||
};
|
||||
|
||||
Ok(keypair)
|
||||
}
|
||||
|
||||
pub fn from_keypair(sec: key::SecretKey, publ: key::PublicKey) -> Self {
|
||||
let context = &SECP256K1;
|
||||
let serialized = publ.serialize_vec(context, false);
|
||||
let mut secret = Secret::default();
|
||||
secret.copy_from_slice(&sec[0..32]);
|
||||
let mut public = Public::default();
|
||||
public.copy_from_slice(&serialized[1..65]);
|
||||
|
||||
KeyPair {
|
||||
secret: secret,
|
||||
public: public,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn secret(&self) -> &Secret {
|
||||
&self.secret
|
||||
}
|
||||
|
||||
pub fn public(&self) -> &Public {
|
||||
&self.public
|
||||
}
|
||||
|
||||
pub fn address(&self) -> Address {
|
||||
let hash = self.public.keccak256();
|
||||
let mut result = Address::default();
|
||||
result.copy_from_slice(&hash[12..]);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::str::FromStr;
|
||||
use {KeyPair, Secret};
|
||||
|
||||
#[test]
|
||||
fn from_secret() {
|
||||
let secret = Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65").unwrap();
|
||||
let _ = KeyPair::from_secret(secret).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keypair_display() {
|
||||
let expected =
|
||||
"secret: a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65
|
||||
public: 8ce0db0b0359ffc5866ba61903cc2518c3675ef2cf380a7e54bde7ea20e6fa1ab45b7617346cd11b7610001ee6ae5b0155c41cad9527cbcdff44ec67848943a4
|
||||
address: 5b073e9233944b5e729e46d618f0d8edf3d9c34a".to_owned();
|
||||
let secret = Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65").unwrap();
|
||||
let kp = KeyPair::from_secret(secret).unwrap();
|
||||
assert_eq!(format!("{}", kp), expected);
|
||||
}
|
||||
}
|
33
ethkey/src/lib.rs
Normal file
33
ethkey/src/lib.rs
Normal file
@ -0,0 +1,33 @@
|
||||
extern crate rand;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
extern crate tiny_keccak;
|
||||
extern crate secp256k1;
|
||||
extern crate rustc_serialize;
|
||||
|
||||
mod brain;
|
||||
mod error;
|
||||
mod keypair;
|
||||
mod keccak;
|
||||
mod prefix;
|
||||
mod primitive;
|
||||
mod random;
|
||||
mod signature;
|
||||
|
||||
lazy_static! {
|
||||
static ref SECP256K1: secp256k1::Secp256k1 = secp256k1::Secp256k1::new();
|
||||
}
|
||||
|
||||
/// Generates new keypair.
|
||||
pub trait Generator {
|
||||
/// Should be called to generate new keypair.
|
||||
fn generate(self) -> Result<KeyPair, Error>;
|
||||
}
|
||||
|
||||
pub use self::brain::Brain;
|
||||
pub use self::error::Error;
|
||||
pub use self::keypair::KeyPair;
|
||||
pub use self::primitive::{Secret, Public, Address, Message};
|
||||
pub use self::prefix::Prefix;
|
||||
pub use self::random::Random;
|
||||
pub use self::signature::{sign, verify, Signature};
|
41
ethkey/src/prefix.rs
Normal file
41
ethkey/src/prefix.rs
Normal file
@ -0,0 +1,41 @@
|
||||
use super::{Random, Generator, KeyPair, Error};
|
||||
|
||||
/// Tries to find keypair with address starting with given prefix.
|
||||
pub struct Prefix {
|
||||
prefix: Vec<u8>,
|
||||
iterations: usize,
|
||||
}
|
||||
|
||||
impl Prefix {
|
||||
pub fn new(prefix: Vec<u8>, iterations: usize) -> Self {
|
||||
Prefix {
|
||||
prefix: prefix,
|
||||
iterations: iterations,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Generator for Prefix {
|
||||
fn generate(self) -> Result<KeyPair, Error> {
|
||||
for _ in 0..self.iterations {
|
||||
let keypair = try!(Random.generate());
|
||||
if keypair.address().starts_with(&self.prefix) {
|
||||
return Ok(keypair)
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::Custom("Could not find keypair".into()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use {Generator, Prefix};
|
||||
|
||||
#[test]
|
||||
fn prefix_generator() {
|
||||
let prefix = vec![0xffu8];
|
||||
let keypair = Prefix::new(prefix.clone(), usize::max_value()).generate().unwrap();
|
||||
assert!(keypair.address().starts_with(&prefix));
|
||||
}
|
||||
}
|
122
ethkey/src/primitive.rs
Normal file
122
ethkey/src/primitive.rs
Normal file
@ -0,0 +1,122 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::{fmt, cmp, hash};
|
||||
use std::str::FromStr;
|
||||
use rustc_serialize::hex::{ToHex, FromHex};
|
||||
use Error;
|
||||
|
||||
macro_rules! impl_primitive {
|
||||
($name: ident, $size: expr, $err: expr) => {
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Eq)]
|
||||
pub struct $name([u8; $size]);
|
||||
|
||||
impl fmt::Debug for $name {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
write!(f, "{}", self.to_hex())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for $name {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
write!(f, "{}", self.to_hex())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for $name {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.from_hex() {
|
||||
Ok(ref hex) if hex.len() == $size => {
|
||||
let mut res = $name::default();
|
||||
res.copy_from_slice(hex);
|
||||
Ok(res)
|
||||
},
|
||||
_ => Err($err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for $name {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
let self_ref: &[u8] = &self.0;
|
||||
let other_ref: &[u8] = &other.0;
|
||||
self_ref == other_ref
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for $name {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
|
||||
let self_ref: &[u8] = &self.0;
|
||||
let other_ref: &[u8] = &other.0;
|
||||
self_ref.partial_cmp(other_ref)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for $name {
|
||||
fn cmp(&self, other: &Self) -> cmp::Ordering {
|
||||
let self_ref: &[u8] = &self.0;
|
||||
let other_ref: &[u8] = &other.0;
|
||||
self_ref.cmp(other_ref)
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for $name {
|
||||
fn clone(&self) -> Self {
|
||||
let mut res = Self::default();
|
||||
res.copy_from_slice(&self.0);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for $name {
|
||||
fn default() -> Self {
|
||||
$name([0u8; $size])
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; $size]> for $name {
|
||||
fn from(s: [u8; $size]) -> Self {
|
||||
$name(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<[u8; $size]> for $name {
|
||||
fn into(self) -> [u8; $size] {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl hash::Hash for $name {
|
||||
fn hash<H>(&self, state: &mut H) where H: hash::Hasher {
|
||||
let self_ref: &[u8] = &self.0;
|
||||
self_ref.hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for $name {
|
||||
type Target = [u8; $size];
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for $name {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_primitive!(Address, 20, Error::InvalidAddress);
|
||||
impl_primitive!(Secret, 32, Error::InvalidSecret);
|
||||
impl_primitive!(Message, 32, Error::InvalidMessage);
|
||||
impl_primitive!(Public, 64, Error::InvalidPublic);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
}
|
16
ethkey/src/random.rs
Normal file
16
ethkey/src/random.rs
Normal file
@ -0,0 +1,16 @@
|
||||
use rand::os::OsRng;
|
||||
use super::{Generator, KeyPair, Error, SECP256K1};
|
||||
|
||||
/// Randomly generates new keypair.
|
||||
pub struct Random;
|
||||
|
||||
impl Generator for Random {
|
||||
fn generate(self) -> Result<KeyPair, Error> {
|
||||
let context = &SECP256K1;
|
||||
let mut rng = try!(OsRng::new());
|
||||
let (sec, publ) = try!(context.generate_keypair(&mut rng));
|
||||
|
||||
Ok(KeyPair::from_keypair(sec, publ))
|
||||
}
|
||||
}
|
||||
|
158
ethkey/src/signature.rs
Normal file
158
ethkey/src/signature.rs
Normal file
@ -0,0 +1,158 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::{mem, fmt};
|
||||
use std::str::FromStr;
|
||||
use secp256k1::{Message as SecpMessage, RecoverableSignature, RecoveryId, Error as SecpError};
|
||||
use secp256k1::key::{SecretKey, PublicKey};
|
||||
use rustc_serialize::hex::{ToHex, FromHex};
|
||||
use {Secret, Public, SECP256K1, Error, Message};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Eq)]
|
||||
pub struct Signature([u8; 65]);
|
||||
|
||||
impl Signature {
|
||||
/// Get a slice into the 'r' portion of the data.
|
||||
pub fn r(&self) -> &[u8] {
|
||||
&self.0[0..32]
|
||||
}
|
||||
|
||||
/// Get a slice into the 's' portion of the data.
|
||||
pub fn s(&self) -> &[u8] {
|
||||
&self.0[32..64]
|
||||
}
|
||||
|
||||
/// Get the recovery byte.
|
||||
pub fn v(&self) -> u8 {
|
||||
self.0[64]
|
||||
}
|
||||
}
|
||||
|
||||
// manual implementation large arrays don't have trait impls by default.
|
||||
// remove when integer generics exist
|
||||
impl ::std::cmp::PartialEq for Signature {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
&self.0[..] == &other.0[..]
|
||||
}
|
||||
}
|
||||
|
||||
// also manual for the same reason, but the pretty printing might be useful.
|
||||
impl fmt::Debug for Signature {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
f.debug_struct("Signature")
|
||||
.field("r", &self.0[0..32].to_hex())
|
||||
.field("s", &self.0[32..64].to_hex())
|
||||
.field("v", &self.0[64..65].to_hex())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Signature {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
write!(f, "{}", self.to_hex())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Signature {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.from_hex() {
|
||||
Ok(ref hex) if hex.len() == 65 => {
|
||||
let mut data = [0; 65];
|
||||
data.copy_from_slice(&hex[0..65]);
|
||||
Ok(Signature(data))
|
||||
},
|
||||
_ => Err(Error::InvalidSignature)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Signature {
|
||||
fn default() -> Self {
|
||||
Signature([0; 65])
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 65]> for Signature {
|
||||
fn from(s: [u8; 65]) -> Self {
|
||||
Signature(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<[u8; 65]> for Signature {
|
||||
fn into(self) -> [u8; 65] {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Signature {
|
||||
type Target = [u8; 65];
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Signature {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sign(secret: &Secret, message: &Message) -> Result<Signature, Error> {
|
||||
let context = &SECP256K1;
|
||||
// no way to create from raw byte array.
|
||||
let sec: &SecretKey = unsafe { mem::transmute(secret) };
|
||||
let s = try!(context.sign_recoverable(&try!(SecpMessage::from_slice(&message[..])), sec));
|
||||
let (rec_id, data) = s.serialize_compact(context);
|
||||
let mut data_arr = [0; 65];
|
||||
|
||||
// no need to check if s is low, it always is
|
||||
data_arr[0..64].copy_from_slice(&data[0..64]);
|
||||
data_arr[64] = rec_id.to_i32() as u8;
|
||||
Ok(Signature(data_arr))
|
||||
}
|
||||
|
||||
pub fn verify(public: &Public, signature: &Signature, message: &Message) -> Result<bool, Error> {
|
||||
let context = &SECP256K1;
|
||||
let rsig = try!(RecoverableSignature::from_compact(context, &signature[0..64], try!(RecoveryId::from_i32(signature[64] as i32))));
|
||||
let sig = rsig.to_standard(context);
|
||||
|
||||
let pdata: [u8; 65] = {
|
||||
let mut temp = [4u8; 65];
|
||||
temp[1..65].copy_from_slice(public.deref());
|
||||
temp
|
||||
};
|
||||
|
||||
let publ = try!(PublicKey::from_slice(context, &pdata));
|
||||
match context.verify(&try!(SecpMessage::from_slice(&message[..])), &sig, &publ) {
|
||||
Ok(_) => Ok(true),
|
||||
Err(SecpError::IncorrectSignature) => Ok(false),
|
||||
Err(x) => Err(Error::from(x))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::str::FromStr;
|
||||
use {Generator, Random, Message};
|
||||
use super::{sign, verify, Signature};
|
||||
|
||||
#[test]
|
||||
fn signature_to_and_from_str() {
|
||||
let keypair = Random.generate().unwrap();
|
||||
let message = Message::default();
|
||||
let signature = sign(keypair.secret(), &message).unwrap();
|
||||
let string = format!("{}", signature);
|
||||
let deserialized = Signature::from_str(&string).unwrap();
|
||||
assert_eq!(signature, deserialized);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sign_and_verify() {
|
||||
let keypair = Random.generate().unwrap();
|
||||
let message = Message::default();
|
||||
let signature = sign(keypair.secret(), &message).unwrap();
|
||||
assert!(verify(keypair.public(), &signature, &message).unwrap());
|
||||
}
|
||||
}
|
11
ethstore/.editorconfig
Normal file
11
ethstore/.editorconfig
Normal file
@ -0,0 +1,11 @@
|
||||
root = true
|
||||
[*]
|
||||
indent_style=tab
|
||||
indent_size=tab
|
||||
tab_width=4
|
||||
end_of_line=lf
|
||||
charset=utf-8
|
||||
trim_trailing_whitespace=true
|
||||
max_line_length=120
|
||||
insert_final_newline=true
|
||||
|
2
ethstore/.gitignore
vendored
Normal file
2
ethstore/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
target
|
||||
*.swp
|
23
ethstore/.travis.yml
Normal file
23
ethstore/.travis.yml
Normal file
@ -0,0 +1,23 @@
|
||||
sudo: false
|
||||
language: rust
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
matrix:
|
||||
fast_finish: false
|
||||
include:
|
||||
- rust: stable
|
||||
- rust: beta
|
||||
- rust: nightly
|
||||
after_success: |
|
||||
[ $TRAVIS_BRANCH = master ] &&
|
||||
[ $TRAVIS_PULL_REQUEST = false ] &&
|
||||
[ $TRAVIS_RUST_VERSION = stable ] &&
|
||||
cargo doc --no-deps --verbose &&
|
||||
echo '<meta http-equiv=refresh content=0;url=ethkey/index.html>' > target/doc/index.html &&
|
||||
pip install --user ghp-import &&
|
||||
/home/travis/.local/bin/ghp-import -n target/doc &&
|
||||
git push -fq https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git gh-pages
|
||||
env:
|
||||
global:
|
||||
- secure: C4l7WR0jS84WNmd3MvmpPXQz4wRh4CLDS6bP3BqSHXadz8FPKejtMJZscLYAk5kIkDcVsTAYb88RsEFRrYOA4wkS6vhZBtryYRaJ68MlkyEU/77SYwm86rkQINIDw65O73dUD5LbWWCUoYkenGu26u/UnfayHfJBAyKw5IHkVKf6eDqu7E8ojKSEOXbWgBHjq6uixI8IESb15UjIE0AQ1Od+6cqhsz/caPhTMT3CJGjoCoVGWChwWSQZ+Ppb+xB83C/1h58UVwE9sZEyIPKwVP6socnHPmtR+VEUI6a7YIsOk6ZadKLtyy4523w4HqHNx1/dYjmsknbGpkF4D0DRp5L3D4t4J6URCkJIHfSRrBF5l2QbLMMuSf+KWMWuFOrOF5DBryobRKAVmIL5AjfvFsxtBNzYLPyVBs0ntbPuN5WeUPhadam00za9Z1ZvOUJxfNfyy9R67u6FdD9xkw2m/9hO7KJLDeZ4TSCRFrzfl/7WQprfjCwhZ+reKPgHH0Ufy1/Kh/WEuEBfZDa+z3mWWHlslqH2uBPH3+pvhzdVQGLB/5GZdJNeg/nJYJDCqHyWUKxkw+OMSvI0J8W0GiHV4TuY9V3p+rYjU2Zj69u3/xO/IvKrFtB9xdeJMrLiFQ2cD5vgzQOLCKo80f53NitUjdVSoWrY/NcYopBU4VHZMlk=
|
30
ethstore/Cargo.toml
Normal file
30
ethstore/Cargo.toml
Normal file
@ -0,0 +1,30 @@
|
||||
[package]
|
||||
name = "ethstore"
|
||||
version = "0.1.0"
|
||||
authors = ["debris <marek.kotewicz@gmail.com>"]
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.11"
|
||||
rand = "0.3.14"
|
||||
ethkey = { path = "../ethkey" }
|
||||
serde = "0.7"
|
||||
serde_json = "0.7"
|
||||
serde_macros = { version = "0.7", optional = true }
|
||||
rustc-serialize = "0.3"
|
||||
rust-crypto = "0.2.36"
|
||||
tiny-keccak = "1.0"
|
||||
docopt = { version = "0.6", optional = true }
|
||||
|
||||
[build-dependencies]
|
||||
serde_codegen = { version = "0.7", optional = true }
|
||||
syntex = "0.33.0"
|
||||
|
||||
[features]
|
||||
default = ["cli", "serde_codegen"]
|
||||
nightly = ["serde_macros"]
|
||||
cli = ["docopt"]
|
||||
|
||||
[[bin]]
|
||||
name = "ethstore"
|
||||
doc = false
|
162
ethstore/README.md
Normal file
162
ethstore/README.md
Normal file
@ -0,0 +1,162 @@
|
||||
# ethstore
|
||||
|
||||
[![Build Status][travis-image]][travis-url]
|
||||
|
||||
[travis-image]: https://travis-ci.org/ethcore/ethstore.svg?branch=master
|
||||
[travis-url]: https://travis-ci.org/ethcore/ethstore
|
||||
|
||||
Ethereum key management.
|
||||
|
||||
[Documentation](http://ethcore.github.io/ethstore/ethstore/index.html)
|
||||
|
||||
### Usage
|
||||
|
||||
```
|
||||
Ethereum key management.
|
||||
Copyright 2016 Ethcore (UK) Limited
|
||||
|
||||
Usage:
|
||||
ethstore insert <secret> <password> [--dir DIR]
|
||||
ethstore change-pwd <address> <old-pwd> <new-pwd> [--dir DIR]
|
||||
ethstore list [--dir DIR]
|
||||
ethstore import [--src DIR] [--dir DIR]
|
||||
ethstore remove <address> <password> [--dir DIR]
|
||||
ethstore sign <address> <password> <message> [--dir DIR]
|
||||
ethstore [-h | --help]
|
||||
|
||||
Options:
|
||||
-h, --help Display this message and exit.
|
||||
--dir DIR Specify the secret store directory. It may be either
|
||||
parity, parity-test, geth, geth-test
|
||||
or a path [default: parity].
|
||||
--src DIR Specify import source. It may be either
|
||||
parity, parity-test, get, geth-test
|
||||
or a path [default: geth].
|
||||
|
||||
Commands:
|
||||
insert Save account with password.
|
||||
change-pwd Change account password.
|
||||
list List accounts.
|
||||
import Import accounts from src.
|
||||
remove Remove account.
|
||||
sign Sign message.
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
#### `insert <secret> <password> [--dir DIR]`
|
||||
*Encrypt secret with a password and save it in secret store.*
|
||||
|
||||
- `<secret>` - ethereum secret, 32 bytes long
|
||||
- `<password>` - account password, any string
|
||||
- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity
|
||||
|
||||
```
|
||||
ethstore insert 7d29fab185a33e2cd955812397354c472d2b84615b645aa135ff539f6b0d70d5 "this is sparta"
|
||||
```
|
||||
|
||||
```
|
||||
a8fa5dd30a87bb9e3288d604eb74949c515ab66e
|
||||
```
|
||||
|
||||
--
|
||||
|
||||
```
|
||||
ethstore insert `ethkey generate random -s` "this is sparta"
|
||||
```
|
||||
|
||||
```
|
||||
24edfff680d536a5f6fe862d36df6f8f6f40f115
|
||||
```
|
||||
|
||||
--
|
||||
|
||||
#### `change-pwd <address> <old-pwd> <new-pwd> [--dir DIR]`
|
||||
*Change account password.*
|
||||
|
||||
- `<address>` - ethereum address, 20 bytes long
|
||||
- `<old-pwd>` - old account password, any string
|
||||
- `<new-pwd>` - new account password, any string
|
||||
- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity
|
||||
|
||||
```
|
||||
ethstore change-pwd a8fa5dd30a87bb9e3288d604eb74949c515ab66e "this is sparta" "hello world"
|
||||
```
|
||||
|
||||
```
|
||||
true
|
||||
```
|
||||
|
||||
--
|
||||
|
||||
#### `list [--dir DIR]`
|
||||
*List secret store accounts.*
|
||||
|
||||
- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity
|
||||
|
||||
```
|
||||
ethstore list
|
||||
```
|
||||
|
||||
```
|
||||
0: 24edfff680d536a5f6fe862d36df6f8f6f40f115
|
||||
1: 6edddfc6349aff20bc6467ccf276c5b52487f7a8
|
||||
2: e6a3d25a7cb7cd21cb720df5b5e8afd154af1bbb
|
||||
```
|
||||
|
||||
--
|
||||
|
||||
#### `import [--src DIR] [--dir DIR]`
|
||||
*Import accounts from src.*
|
||||
|
||||
- `[--src DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: geth
|
||||
- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity
|
||||
|
||||
```
|
||||
0: e6a3d25a7cb7cd21cb720df5b5e8afd154af1bbb
|
||||
1: 6edddfc6349aff20bc6467ccf276c5b52487f7a8
|
||||
```
|
||||
|
||||
--
|
||||
|
||||
#### `remove <address> <password> [--dir DIR]`
|
||||
*Remove account from secret store.*
|
||||
|
||||
- `<address>` - ethereum address, 20 bytes long
|
||||
- `<password>` - account password, any string
|
||||
- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity
|
||||
|
||||
```
|
||||
ethstore remove a8fa5dd30a87bb9e3288d604eb74949c515ab66e "hello world"
|
||||
```
|
||||
|
||||
```
|
||||
true
|
||||
```
|
||||
|
||||
--
|
||||
|
||||
#### `sign <address> <password> <message> [--dir DIR]`
|
||||
*Sign message with account's secret.*
|
||||
|
||||
- `<address>` - ethereum address, 20 bytes long
|
||||
- `<password>` - account password, any string
|
||||
- `<message>` - message to sign, 32 bytes long
|
||||
- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity
|
||||
|
||||
```
|
||||
ethstore sign 24edfff680d536a5f6fe862d36df6f8f6f40f115 "this is sparta" 7d29fab185a33e2cd955812397354c472d2b84615b645aa135ff539f6b0d70d5
|
||||
```
|
||||
|
||||
```
|
||||
c6649f9555232d90ff716d7e552a744c5af771574425a74860e12f763479eb1b708c1f3a7dc0a0a7f7a81e0a0ca88c6deacf469222bb3d9c5bf0847f98bae54901
|
||||
```
|
||||
|
||||
--
|
||||
|
||||
# Ethcore toolchain
|
||||
*this project is a part of the ethcore toolchain*
|
||||
|
||||
- [**ethkey**](https://github.com/ethcore/ethkey) - Ethereum keys generator and signer.
|
||||
- [**ethstore**](https://github.com/ethcore/ethstore) - Ethereum key management.
|
||||
- [**ethabi**](https://github.com/ethcore/ethabi) - Ethereum function calls encoding.
|
29
ethstore/build.rs
Normal file
29
ethstore/build.rs
Normal file
@ -0,0 +1,29 @@
|
||||
#[cfg(not(feature = "serde_macros"))]
|
||||
mod inner {
|
||||
extern crate syntex;
|
||||
extern crate serde_codegen;
|
||||
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
|
||||
pub fn main() {
|
||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||
|
||||
let src = Path::new("src/json/mod.rs.in");
|
||||
let dst = Path::new(&out_dir).join("mod.rs");
|
||||
|
||||
let mut registry = syntex::Registry::new();
|
||||
|
||||
serde_codegen::register(&mut registry);
|
||||
registry.expand("", &src, &dst).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde_macros")]
|
||||
mod inner {
|
||||
pub fn main() {}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
inner::main();
|
||||
}
|
43
ethstore/src/account/cipher.rs
Normal file
43
ethstore/src/account/cipher.rs
Normal file
@ -0,0 +1,43 @@
|
||||
use json;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Aes128Ctr {
|
||||
pub iv: [u8; 16],
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Cipher {
|
||||
Aes128Ctr(Aes128Ctr),
|
||||
}
|
||||
|
||||
impl From<json::Aes128Ctr> for Aes128Ctr {
|
||||
fn from(json: json::Aes128Ctr) -> Self {
|
||||
Aes128Ctr {
|
||||
iv: json.iv.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<json::Aes128Ctr> for Aes128Ctr {
|
||||
fn into(self) -> json::Aes128Ctr {
|
||||
json::Aes128Ctr {
|
||||
iv: From::from(self.iv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<json::Cipher> for Cipher {
|
||||
fn from(json: json::Cipher) -> Self {
|
||||
match json {
|
||||
json::Cipher::Aes128Ctr(params) => Cipher::Aes128Ctr(From::from(params)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<json::Cipher> for Cipher {
|
||||
fn into(self) -> json::Cipher {
|
||||
match self {
|
||||
Cipher::Aes128Ctr(params) => json::Cipher::Aes128Ctr(params.into()),
|
||||
}
|
||||
}
|
||||
}
|
109
ethstore/src/account/kdf.rs
Normal file
109
ethstore/src/account/kdf.rs
Normal file
@ -0,0 +1,109 @@
|
||||
use json;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Prf {
|
||||
HmacSha256,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Pbkdf2 {
|
||||
pub c: u32,
|
||||
pub dklen: u32,
|
||||
pub prf: Prf,
|
||||
pub salt: [u8; 32],
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Scrypt {
|
||||
pub dklen: u32,
|
||||
pub p: u32,
|
||||
pub n: u32,
|
||||
pub r: u32,
|
||||
pub salt: [u8; 32],
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Kdf {
|
||||
Pbkdf2(Pbkdf2),
|
||||
Scrypt(Scrypt),
|
||||
}
|
||||
|
||||
impl From<json::Prf> for Prf {
|
||||
fn from(json: json::Prf) -> Self {
|
||||
match json {
|
||||
json::Prf::HmacSha256 => Prf::HmacSha256,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<json::Prf> for Prf {
|
||||
fn into(self) -> json::Prf {
|
||||
match self {
|
||||
Prf::HmacSha256 => json::Prf::HmacSha256,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<json::Pbkdf2> for Pbkdf2 {
|
||||
fn from(json: json::Pbkdf2) -> Self {
|
||||
Pbkdf2 {
|
||||
c: json.c,
|
||||
dklen: json.dklen,
|
||||
prf: From::from(json.prf),
|
||||
salt: json.salt.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<json::Pbkdf2> for Pbkdf2 {
|
||||
fn into(self) -> json::Pbkdf2 {
|
||||
json::Pbkdf2 {
|
||||
c: self.c,
|
||||
dklen: self.dklen,
|
||||
prf: self.prf.into(),
|
||||
salt: From::from(self.salt),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<json::Scrypt> for Scrypt {
|
||||
fn from(json: json::Scrypt) -> Self {
|
||||
Scrypt {
|
||||
dklen: json.dklen,
|
||||
p: json.p,
|
||||
n: json.n,
|
||||
r: json.r,
|
||||
salt: json.salt.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<json::Scrypt> for Scrypt {
|
||||
fn into(self) -> json::Scrypt {
|
||||
json::Scrypt {
|
||||
dklen: self.dklen,
|
||||
p: self.p,
|
||||
n: self.n,
|
||||
r: self.r,
|
||||
salt: From::from(self.salt),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<json::Kdf> for Kdf {
|
||||
fn from(json: json::Kdf) -> Self {
|
||||
match json {
|
||||
json::Kdf::Pbkdf2(params) => Kdf::Pbkdf2(From::from(params)),
|
||||
json::Kdf::Scrypt(params) => Kdf::Scrypt(From::from(params)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<json::Kdf> for Kdf {
|
||||
fn into(self) -> json::Kdf {
|
||||
match self {
|
||||
Kdf::Pbkdf2(params) => json::Kdf::Pbkdf2(params.into()),
|
||||
Kdf::Scrypt(params) => json::Kdf::Scrypt(params.into()),
|
||||
}
|
||||
}
|
||||
}
|
9
ethstore/src/account/mod.rs
Normal file
9
ethstore/src/account/mod.rs
Normal file
@ -0,0 +1,9 @@
|
||||
mod cipher;
|
||||
mod kdf;
|
||||
mod safe_account;
|
||||
mod version;
|
||||
|
||||
pub use self::cipher::{Cipher, Aes128Ctr};
|
||||
pub use self::kdf::{Kdf, Pbkdf2, Scrypt, Prf};
|
||||
pub use self::safe_account::{SafeAccount, Crypto};
|
||||
pub use self::version::Version;
|
200
ethstore/src/account/safe_account.rs
Normal file
200
ethstore/src/account/safe_account.rs
Normal file
@ -0,0 +1,200 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use ethkey::{KeyPair, sign, Address, Secret, Signature, Message};
|
||||
use {json, Error, crypto};
|
||||
use crypto::Keccak256;
|
||||
use random::Random;
|
||||
use account::{Version, Cipher, Kdf, Aes128Ctr, Pbkdf2, Prf};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Crypto {
|
||||
pub cipher: Cipher,
|
||||
pub ciphertext: [u8; 32],
|
||||
pub kdf: Kdf,
|
||||
pub mac: [u8; 32],
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct SafeAccount {
|
||||
pub id: [u8; 16],
|
||||
pub version: Version,
|
||||
pub address: Address,
|
||||
pub crypto: Crypto,
|
||||
}
|
||||
|
||||
impl From<json::Crypto> for Crypto {
|
||||
fn from(json: json::Crypto) -> Self {
|
||||
Crypto {
|
||||
cipher: From::from(json.cipher),
|
||||
ciphertext: json.ciphertext.into(),
|
||||
kdf: From::from(json.kdf),
|
||||
mac: json.mac.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<json::Crypto> for Crypto {
|
||||
fn into(self) -> json::Crypto {
|
||||
json::Crypto {
|
||||
cipher: self.cipher.into(),
|
||||
ciphertext: From::from(self.ciphertext),
|
||||
kdf: self.kdf.into(),
|
||||
mac: From::from(self.mac),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<json::KeyFile> for SafeAccount {
|
||||
fn from(json: json::KeyFile) -> Self {
|
||||
SafeAccount {
|
||||
id: json.id.into(),
|
||||
version: From::from(json.version),
|
||||
address: From::from(json.address), //json.address.into(),
|
||||
crypto: From::from(json.crypto),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<json::KeyFile> for SafeAccount {
|
||||
fn into(self) -> json::KeyFile {
|
||||
json::KeyFile {
|
||||
id: From::from(self.id),
|
||||
version: self.version.into(),
|
||||
address: self.address.into(), //From::from(self.address),
|
||||
crypto: self.crypto.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Crypto {
|
||||
pub fn create(secret: &Secret, password: &str, iterations: u32) -> Self {
|
||||
let salt: [u8; 32] = Random::random();
|
||||
let iv: [u8; 16] = Random::random();
|
||||
|
||||
// two parts of derived key
|
||||
// DK = [ DK[0..15] DK[16..31] ] = [derived_left_bits, derived_right_bits]
|
||||
let (derived_left_bits, derived_right_bits) = crypto::derive_key_iterations(password, &salt, iterations);
|
||||
|
||||
let mut ciphertext = [0u8; 32];
|
||||
|
||||
// aes-128-ctr with initial vector of iv
|
||||
crypto::aes::encrypt(&derived_left_bits, &iv, secret.deref(), &mut ciphertext);
|
||||
|
||||
// KECCAK(DK[16..31] ++ <ciphertext>), where DK[16..31] - derived_right_bits
|
||||
let mac = crypto::derive_mac(&derived_right_bits, &ciphertext).keccak256();
|
||||
|
||||
Crypto {
|
||||
cipher: Cipher::Aes128Ctr(Aes128Ctr {
|
||||
iv: iv,
|
||||
}),
|
||||
ciphertext: ciphertext,
|
||||
kdf: Kdf::Pbkdf2(Pbkdf2 {
|
||||
dklen: crypto::KEY_LENGTH as u32,
|
||||
salt: salt,
|
||||
c: iterations,
|
||||
prf: Prf::HmacSha256,
|
||||
}),
|
||||
mac: mac,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn secret(&self, password: &str) -> Result<Secret, Error> {
|
||||
let (derived_left_bits, derived_right_bits) = match self.kdf {
|
||||
Kdf::Pbkdf2(ref params) => crypto::derive_key_iterations(password, ¶ms.salt, params.c),
|
||||
Kdf::Scrypt(ref params) => crypto::derive_key_scrypt(password, ¶ms.salt, params.n, params.p, params.r),
|
||||
};
|
||||
|
||||
let mac = crypto::derive_mac(&derived_right_bits, &self.ciphertext).keccak256();
|
||||
|
||||
if mac != self.mac {
|
||||
return Err(Error::InvalidPassword);
|
||||
}
|
||||
|
||||
let mut secret = Secret::default();
|
||||
|
||||
match self.cipher {
|
||||
Cipher::Aes128Ctr(ref params) => {
|
||||
crypto::aes::decrypt(&derived_left_bits, ¶ms.iv, &self.ciphertext, secret.deref_mut())
|
||||
},
|
||||
}
|
||||
|
||||
Ok(secret)
|
||||
}
|
||||
}
|
||||
|
||||
impl SafeAccount {
|
||||
pub fn create(keypair: &KeyPair, id: [u8; 16], password: &str, iterations: u32) -> Self {
|
||||
SafeAccount {
|
||||
id: id,
|
||||
version: Version::V3,
|
||||
crypto: Crypto::create(keypair.secret(), password, iterations),
|
||||
address: keypair.address(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sign(&self, password: &str, message: &Message) -> Result<Signature, Error> {
|
||||
let secret = try!(self.crypto.secret(password));
|
||||
sign(&secret, message).map_err(From::from)
|
||||
}
|
||||
|
||||
pub fn change_password(&self, old_password: &str, new_password: &str, iterations: u32) -> Result<Self, Error> {
|
||||
let secret = try!(self.crypto.secret(old_password));
|
||||
let result = SafeAccount {
|
||||
id: self.id.clone(),
|
||||
version: self.version.clone(),
|
||||
crypto: Crypto::create(&secret, new_password, iterations),
|
||||
address: self.address.clone(),
|
||||
};
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn check_password(&self, password: &str) -> bool {
|
||||
self.crypto.secret(password).is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ethkey::{Generator, Random, verify, Message};
|
||||
use super::{Crypto, SafeAccount};
|
||||
|
||||
#[test]
|
||||
fn crypto_create() {
|
||||
let keypair = Random.generate().unwrap();
|
||||
let crypto = Crypto::create(keypair.secret(), "this is sparta", 10240);
|
||||
let secret = crypto.secret("this is sparta").unwrap();
|
||||
assert_eq!(keypair.secret(), &secret);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn crypto_invalid_password() {
|
||||
let keypair = Random.generate().unwrap();
|
||||
let crypto = Crypto::create(keypair.secret(), "this is sparta", 10240);
|
||||
let _ = crypto.secret("this is sparta!").unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sign_and_verify() {
|
||||
let keypair = Random.generate().unwrap();
|
||||
let password = "hello world";
|
||||
let message = Message::default();
|
||||
let account = SafeAccount::create(&keypair, [0u8; 16], password, 10240);
|
||||
let signature = account.sign(password, &message).unwrap();
|
||||
assert!(verify(keypair.public(), &signature, &message).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_password() {
|
||||
let keypair = Random.generate().unwrap();
|
||||
let first_password = "hello world";
|
||||
let sec_password = "this is sparta";
|
||||
let i = 10240;
|
||||
let message = Message::default();
|
||||
let account = SafeAccount::create(&keypair, [0u8; 16], first_password, i);
|
||||
let new_account = account.change_password(first_password, sec_password, i).unwrap();
|
||||
assert!(account.sign(first_password, &message).is_ok());
|
||||
assert!(account.sign(sec_password, &message).is_err());
|
||||
assert!(new_account.sign(first_password, &message).is_err());
|
||||
assert!(new_account.sign(sec_password, &message).is_ok());
|
||||
}
|
||||
}
|
22
ethstore/src/account/version.rs
Normal file
22
ethstore/src/account/version.rs
Normal file
@ -0,0 +1,22 @@
|
||||
use json;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Version {
|
||||
V3,
|
||||
}
|
||||
|
||||
impl From<json::Version> for Version {
|
||||
fn from(json: json::Version) -> Self {
|
||||
match json {
|
||||
json::Version::V3 => Version::V3,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<json::Version> for Version {
|
||||
fn into(self) -> json::Version {
|
||||
match self {
|
||||
Version::V3 => json::Version::V3,
|
||||
}
|
||||
}
|
||||
}
|
128
ethstore/src/bin/ethstore.rs
Normal file
128
ethstore/src/bin/ethstore.rs
Normal file
@ -0,0 +1,128 @@
|
||||
extern crate rustc_serialize;
|
||||
extern crate docopt;
|
||||
extern crate ethstore;
|
||||
|
||||
use std::{env, process};
|
||||
use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
use docopt::Docopt;
|
||||
use ethstore::ethkey::{Secret, Address, Message};
|
||||
use ethstore::dir::{KeyDirectory, ParityDirectory, DiskDirectory, GethDirectory, DirectoryType};
|
||||
use ethstore::{EthStore, SecretStore, import_accounts, Error};
|
||||
|
||||
pub const USAGE: &'static str = r#"
|
||||
Ethereum key management.
|
||||
Copyright 2016 Ethcore (UK) Limited
|
||||
|
||||
Usage:
|
||||
ethstore insert <secret> <password> [--dir DIR]
|
||||
ethstore change-pwd <address> <old-pwd> <new-pwd> [--dir DIR]
|
||||
ethstore list [--dir DIR]
|
||||
ethstore import [--src DIR] [--dir DIR]
|
||||
ethstore remove <address> <password> [--dir DIR]
|
||||
ethstore sign <address> <password> <message> [--dir DIR]
|
||||
ethstore [-h | --help]
|
||||
|
||||
Options:
|
||||
-h, --help Display this message and exit.
|
||||
--dir DIR Specify the secret store directory. It may be either
|
||||
parity, parity-test, geth, geth-test
|
||||
or a path [default: parity].
|
||||
--src DIR Specify import source. It may be either
|
||||
parity, parity-test, get, geth-test
|
||||
or a path [default: geth].
|
||||
|
||||
Commands:
|
||||
insert Save account with password.
|
||||
change-pwd Change password.
|
||||
list List accounts.
|
||||
import Import accounts from src.
|
||||
remove Remove account.
|
||||
sign Sign message.
|
||||
"#;
|
||||
|
||||
#[derive(Debug, RustcDecodable)]
|
||||
struct Args {
|
||||
cmd_insert: bool,
|
||||
cmd_change_pwd: bool,
|
||||
cmd_list: bool,
|
||||
cmd_import: bool,
|
||||
cmd_remove: bool,
|
||||
cmd_sign: bool,
|
||||
arg_secret: String,
|
||||
arg_password: String,
|
||||
arg_old_pwd: String,
|
||||
arg_new_pwd: String,
|
||||
arg_address: String,
|
||||
arg_message: String,
|
||||
flag_src: String,
|
||||
flag_dir: String,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
match execute(env::args()) {
|
||||
Ok(result) => println!("{}", result),
|
||||
Err(err) => {
|
||||
println!("{}", err);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn key_dir(location: &str) -> Result<Box<KeyDirectory>, Error> {
|
||||
let dir: Box<KeyDirectory> = match location {
|
||||
"parity" => Box::new(try!(ParityDirectory::create(DirectoryType::Main))),
|
||||
"parity-test" => Box::new(try!(ParityDirectory::create(DirectoryType::Testnet))),
|
||||
"geth" => Box::new(try!(GethDirectory::create(DirectoryType::Main))),
|
||||
"geth-test" => Box::new(try!(GethDirectory::create(DirectoryType::Testnet))),
|
||||
path => Box::new(try!(DiskDirectory::create(path))),
|
||||
};
|
||||
|
||||
Ok(dir)
|
||||
}
|
||||
|
||||
fn format_accounts(accounts: &[Address]) -> String {
|
||||
accounts.iter()
|
||||
.enumerate()
|
||||
.map(|(i, a)| format!("{:2}: {}", i, a))
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
fn execute<S, I>(command: I) -> Result<String, Error> where I: IntoIterator<Item=S>, S: AsRef<str> {
|
||||
let args: Args = Docopt::new(USAGE)
|
||||
.and_then(|d| d.argv(command).decode())
|
||||
.unwrap_or_else(|e| e.exit());
|
||||
|
||||
let store = try!(EthStore::open(try!(key_dir(&args.flag_dir))));
|
||||
|
||||
return if args.cmd_insert {
|
||||
let secret = try!(Secret::from_str(&args.arg_secret));
|
||||
let address = try!(store.insert_account(secret, &args.arg_password));
|
||||
Ok(format!("{}", address))
|
||||
} else if args.cmd_change_pwd {
|
||||
let address = try!(Address::from_str(&args.arg_address));
|
||||
let ok = store.change_password(&address, &args.arg_old_pwd, &args.arg_new_pwd).is_ok();
|
||||
Ok(format!("{}", ok))
|
||||
} else if args.cmd_list {
|
||||
let accounts = store.accounts();
|
||||
Ok(format_accounts(&accounts))
|
||||
} else if args.cmd_import {
|
||||
let src = try!(key_dir(&args.flag_src));
|
||||
let dst = try!(key_dir(&args.flag_dir));
|
||||
let accounts = try!(import_accounts(src.deref(), dst.deref()));
|
||||
Ok(format_accounts(&accounts))
|
||||
} else if args.cmd_remove {
|
||||
let address = try!(Address::from_str(&args.arg_address));
|
||||
let ok = store.remove_account(&address, &args.arg_password).is_ok();
|
||||
Ok(format!("{}", ok))
|
||||
} else if args.cmd_sign {
|
||||
let address = try!(Address::from_str(&args.arg_address));
|
||||
let message = try!(Message::from_str(&args.arg_message));
|
||||
let signature = try!(store.sign(&address, &args.arg_password, &message));
|
||||
Ok(format!("{}", signature))
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
5
ethstore/src/bin/main.rs
Normal file
5
ethstore/src/bin/main.rs
Normal file
@ -0,0 +1,5 @@
|
||||
#[cfg(feature = "cli")]
|
||||
include!("ethstore.rs");
|
||||
|
||||
#[cfg(not(feature = "cli"))]
|
||||
fn main() {}
|
69
ethstore/src/crypto.rs
Normal file
69
ethstore/src/crypto.rs
Normal file
@ -0,0 +1,69 @@
|
||||
use tiny_keccak::Keccak;
|
||||
use rcrypto::pbkdf2::pbkdf2;
|
||||
use rcrypto::scrypt::{scrypt, ScryptParams};
|
||||
use rcrypto::sha2::Sha256;
|
||||
use rcrypto::hmac::Hmac;
|
||||
|
||||
pub const KEY_LENGTH: usize = 32;
|
||||
pub const KEY_ITERATIONS: usize = 10240;
|
||||
pub const KEY_LENGTH_AES: usize = KEY_LENGTH / 2;
|
||||
|
||||
pub fn derive_key_iterations(password: &str, salt: &[u8; 32], c: u32) -> (Vec<u8>, Vec<u8>) {
|
||||
let mut h_mac = Hmac::new(Sha256::new(), password.as_bytes());
|
||||
let mut derived_key = vec![0u8; KEY_LENGTH];
|
||||
pbkdf2(&mut h_mac, salt, c, &mut derived_key);
|
||||
let derived_right_bits = &derived_key[0..KEY_LENGTH_AES];
|
||||
let derived_left_bits = &derived_key[KEY_LENGTH_AES..KEY_LENGTH];
|
||||
(derived_right_bits.to_vec(), derived_left_bits.to_vec())
|
||||
}
|
||||
|
||||
pub fn derive_key_scrypt(password: &str, salt: &[u8; 32], n: u32, p: u32, r: u32) -> (Vec<u8>, Vec<u8>) {
|
||||
let mut derived_key = vec![0u8; KEY_LENGTH];
|
||||
let scrypt_params = ScryptParams::new(n.trailing_zeros() as u8, r, p);
|
||||
scrypt(password.as_bytes(), salt, &scrypt_params, &mut derived_key);
|
||||
let derived_right_bits = &derived_key[0..KEY_LENGTH_AES];
|
||||
let derived_left_bits = &derived_key[KEY_LENGTH_AES..KEY_LENGTH];
|
||||
(derived_right_bits.to_vec(), derived_left_bits.to_vec())
|
||||
}
|
||||
|
||||
pub fn derive_mac(derived_left_bits: &[u8], cipher_text: &[u8]) -> Vec<u8> {
|
||||
let mut mac = vec![0u8; KEY_LENGTH_AES + cipher_text.len()];
|
||||
mac[0..KEY_LENGTH_AES].copy_from_slice(derived_left_bits);
|
||||
mac[KEY_LENGTH_AES..cipher_text.len() + KEY_LENGTH_AES].copy_from_slice(cipher_text);
|
||||
mac
|
||||
}
|
||||
|
||||
pub trait Keccak256<T> {
|
||||
fn keccak256(&self) -> T where T: Sized;
|
||||
}
|
||||
|
||||
impl Keccak256<[u8; 32]> for [u8] {
|
||||
fn keccak256(&self) -> [u8; 32] {
|
||||
let mut keccak = Keccak::new_keccak256();
|
||||
let mut result = [0u8; 32];
|
||||
keccak.update(self);
|
||||
keccak.finalize(&mut result);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
/// AES encryption
|
||||
pub mod aes {
|
||||
use rcrypto::blockmodes::CtrMode;
|
||||
use rcrypto::aessafe::AesSafe128Encryptor;
|
||||
use rcrypto::symmetriccipher::{Encryptor, Decryptor};
|
||||
use rcrypto::buffer::{RefReadBuffer, RefWriteBuffer};
|
||||
|
||||
/// Encrypt a message
|
||||
pub fn encrypt(k: &[u8], iv: &[u8], plain: &[u8], dest: &mut [u8]) {
|
||||
let mut encryptor = CtrMode::new(AesSafe128Encryptor::new(k), iv.to_vec());
|
||||
encryptor.encrypt(&mut RefReadBuffer::new(plain), &mut RefWriteBuffer::new(dest), true).expect("Invalid length or padding");
|
||||
}
|
||||
|
||||
/// Decrypt a message
|
||||
pub fn decrypt(k: &[u8], iv: &[u8], encrypted: &[u8], dest: &mut [u8]) {
|
||||
let mut encryptor = CtrMode::new(AesSafe128Encryptor::new(k), iv.to_vec());
|
||||
encryptor.decrypt(&mut RefReadBuffer::new(encrypted), &mut RefWriteBuffer::new(dest), true).expect("Invalid length or padding");
|
||||
}
|
||||
}
|
||||
|
109
ethstore/src/dir/disk.rs
Normal file
109
ethstore/src/dir/disk.rs
Normal file
@ -0,0 +1,109 @@
|
||||
use std::{fs, ffi, io};
|
||||
use std::path::{PathBuf, Path};
|
||||
use std::collections::HashMap;
|
||||
use ethkey::Address;
|
||||
use {libc, json, SafeAccount, Error};
|
||||
use super::KeyDirectory;
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn restrict_permissions_to_owner(file_path: &Path) -> Result<(), i32> {
|
||||
let cstr = ffi::CString::new(file_path.to_str().unwrap()).unwrap();
|
||||
match unsafe { libc::chmod(cstr.as_ptr(), libc::S_IWUSR | libc::S_IRUSR) } {
|
||||
0 => Ok(()),
|
||||
x => Err(x),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn restrict_permissions_to_owner(file_path: &Path) -> Result<(), i32> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct DiskDirectory {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl DiskDirectory {
|
||||
pub fn create<P>(path: P) -> Result<Self, Error> where P: AsRef<Path> {
|
||||
try!(fs::create_dir_all(&path));
|
||||
Ok(Self::at(path))
|
||||
}
|
||||
|
||||
pub fn at<P>(path: P) -> Self where P: AsRef<Path> {
|
||||
DiskDirectory {
|
||||
path: path.as_ref().to_path_buf(),
|
||||
}
|
||||
}
|
||||
|
||||
/// all accounts found in keys directory
|
||||
fn files(&self) -> Result<HashMap<PathBuf, SafeAccount>, Error> {
|
||||
// it's not done using one iterator cause
|
||||
// there is an issue with rustc and it takes tooo much time to compile
|
||||
let paths = try!(fs::read_dir(&self.path))
|
||||
.flat_map(Result::ok)
|
||||
.filter(|entry| {
|
||||
let metadata = entry.metadata();
|
||||
metadata.is_ok() && !metadata.unwrap().is_dir()
|
||||
})
|
||||
.map(|entry| entry.path())
|
||||
.collect::<Vec<PathBuf>>();
|
||||
|
||||
let files: Result<Vec<_>, _> = paths.iter()
|
||||
.map(fs::File::open)
|
||||
.collect();
|
||||
|
||||
let files = try!(files);
|
||||
|
||||
let accounts = files.into_iter()
|
||||
.map(json::KeyFile::load)
|
||||
.zip(paths.into_iter())
|
||||
.filter_map(|(file, path)| file.ok().map(|file| (path, SafeAccount::from(file))))
|
||||
.collect();
|
||||
|
||||
Ok(accounts)
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyDirectory for DiskDirectory {
|
||||
fn load(&self) -> Result<Vec<SafeAccount>, Error> {
|
||||
let accounts = try!(self.files())
|
||||
.into_iter()
|
||||
.map(|(_, account)| account)
|
||||
.collect();
|
||||
Ok(accounts)
|
||||
}
|
||||
|
||||
fn insert(&self, account: SafeAccount) -> Result<(), Error> {
|
||||
// transform account into key file
|
||||
let keyfile: json::KeyFile = account.into();
|
||||
|
||||
// build file path
|
||||
let mut keyfile_path = self.path.clone();
|
||||
keyfile_path.push(format!("{}", keyfile.id));
|
||||
|
||||
// save the file
|
||||
let mut file = try!(fs::File::create(&keyfile_path));
|
||||
try!(keyfile.write(&mut file).map_err(|e| Error::Custom(format!("{:?}", e))));
|
||||
|
||||
if let Err(_) = restrict_permissions_to_owner(&keyfile_path) {
|
||||
fs::remove_file(&keyfile_path).expect("Expected to remove recently created file");
|
||||
return Err(Error::Io(io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove(&self, address: &Address) -> Result<(), Error> {
|
||||
// enumerate all entries in keystore
|
||||
// and find entry with given address
|
||||
let to_remove = try!(self.files())
|
||||
.into_iter()
|
||||
.find(|&(_, ref account)| &account.address == address);
|
||||
|
||||
// remove it
|
||||
match to_remove {
|
||||
None => Err(Error::InvalidAccount),
|
||||
Some((path, _)) => fs::remove_file(path).map_err(From::from)
|
||||
}
|
||||
}
|
||||
}
|
79
ethstore/src/dir/geth.rs
Normal file
79
ethstore/src/dir/geth.rs
Normal file
@ -0,0 +1,79 @@
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use ethkey::Address;
|
||||
use {SafeAccount, Error};
|
||||
use super::{KeyDirectory, DiskDirectory, DirectoryType};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn geth_dir_path() -> PathBuf {
|
||||
let mut home = env::home_dir().expect("Failed to get home dir");
|
||||
home.push("Library");
|
||||
home.push("Ethereum");
|
||||
home
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
/// Default path for ethereum installation on Windows
|
||||
pub fn geth_dir_path() -> PathBuf {
|
||||
let mut home = env::home_dir().expect("Failed to get home dir");
|
||||
home.push("AppData");
|
||||
home.push("Roaming");
|
||||
home.push("Ethereum");
|
||||
home
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "macos", windows)))]
|
||||
/// Default path for ethereum installation on posix system which is not Mac OS
|
||||
pub fn geth_dir_path() -> PathBuf {
|
||||
let mut home = env::home_dir().expect("Failed to get home dir");
|
||||
home.push(".ethereum");
|
||||
home
|
||||
}
|
||||
|
||||
fn geth_keystore(t: DirectoryType) -> PathBuf {
|
||||
let mut dir = geth_dir_path();
|
||||
match t {
|
||||
DirectoryType::Testnet => {
|
||||
dir.push("testnet");
|
||||
dir.push("keystore");
|
||||
},
|
||||
DirectoryType::Main => {
|
||||
dir.push("keystore");
|
||||
}
|
||||
}
|
||||
dir
|
||||
}
|
||||
|
||||
pub struct GethDirectory {
|
||||
dir: DiskDirectory,
|
||||
}
|
||||
|
||||
impl GethDirectory {
|
||||
pub fn create(t: DirectoryType) -> Result<Self, Error> {
|
||||
let result = GethDirectory {
|
||||
dir: try!(DiskDirectory::create(geth_keystore(t))),
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn open(t: DirectoryType) -> Self {
|
||||
GethDirectory {
|
||||
dir: DiskDirectory::at(geth_keystore(t)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyDirectory for GethDirectory {
|
||||
fn load(&self) -> Result<Vec<SafeAccount>, Error> {
|
||||
self.dir.load()
|
||||
}
|
||||
|
||||
fn insert(&self, account: SafeAccount) -> Result<(), Error> {
|
||||
self.dir.insert(account)
|
||||
}
|
||||
|
||||
fn remove(&self, address: &Address) -> Result<(), Error> {
|
||||
self.dir.remove(address)
|
||||
}
|
||||
}
|
21
ethstore/src/dir/mod.rs
Normal file
21
ethstore/src/dir/mod.rs
Normal file
@ -0,0 +1,21 @@
|
||||
use ethkey::Address;
|
||||
use {SafeAccount, Error};
|
||||
|
||||
mod disk;
|
||||
mod geth;
|
||||
mod parity;
|
||||
|
||||
pub enum DirectoryType {
|
||||
Testnet,
|
||||
Main,
|
||||
}
|
||||
|
||||
pub trait KeyDirectory: Send + Sync {
|
||||
fn load(&self) -> Result<Vec<SafeAccount>, Error>;
|
||||
fn insert(&self, account: SafeAccount) -> Result<(), Error>;
|
||||
fn remove(&self, address: &Address) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
pub use self::disk::DiskDirectory;
|
||||
pub use self::geth::GethDirectory;
|
||||
pub use self::parity::ParityDirectory;
|
58
ethstore/src/dir/parity.rs
Normal file
58
ethstore/src/dir/parity.rs
Normal file
@ -0,0 +1,58 @@
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use ethkey::Address;
|
||||
use {SafeAccount, Error};
|
||||
use super::{KeyDirectory, DiskDirectory, DirectoryType};
|
||||
|
||||
fn parity_dir_path() -> PathBuf {
|
||||
let mut home = env::home_dir().expect("Failed to get home dir");
|
||||
home.push(".parity");
|
||||
home
|
||||
}
|
||||
|
||||
fn parity_keystore(t: DirectoryType) -> PathBuf {
|
||||
let mut dir = parity_dir_path();
|
||||
match t {
|
||||
DirectoryType::Testnet => {
|
||||
dir.push("testnet_keys");
|
||||
},
|
||||
DirectoryType::Main => {
|
||||
dir.push("keys");
|
||||
}
|
||||
}
|
||||
dir
|
||||
}
|
||||
|
||||
pub struct ParityDirectory {
|
||||
dir: DiskDirectory,
|
||||
}
|
||||
|
||||
impl ParityDirectory {
|
||||
pub fn create(t: DirectoryType) -> Result<Self, Error> {
|
||||
let result = ParityDirectory {
|
||||
dir: try!(DiskDirectory::create(parity_keystore(t))),
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn open(t: DirectoryType) -> Self {
|
||||
ParityDirectory {
|
||||
dir: DiskDirectory::at(parity_keystore(t)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyDirectory for ParityDirectory {
|
||||
fn load(&self) -> Result<Vec<SafeAccount>, Error> {
|
||||
self.dir.load()
|
||||
}
|
||||
|
||||
fn insert(&self, account: SafeAccount) -> Result<(), Error> {
|
||||
self.dir.insert(account)
|
||||
}
|
||||
|
||||
fn remove(&self, address: &Address) -> Result<(), Error> {
|
||||
self.dir.remove(address)
|
||||
}
|
||||
}
|
42
ethstore/src/error.rs
Normal file
42
ethstore/src/error.rs
Normal file
@ -0,0 +1,42 @@
|
||||
use std::fmt;
|
||||
use std::io::Error as IoError;
|
||||
use ethkey::Error as EthKeyError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Io(IoError),
|
||||
InvalidPassword,
|
||||
InvalidSecret,
|
||||
InvalidAccount,
|
||||
CreationFailed,
|
||||
EthKey(EthKeyError),
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
let s = match *self {
|
||||
Error::Io(ref err) => format!("{}", err),
|
||||
Error::InvalidPassword => "Invalid password".into(),
|
||||
Error::InvalidSecret => "Invalid secret".into(),
|
||||
Error::InvalidAccount => "Invalid account".into(),
|
||||
Error::CreationFailed => "Account creation failed".into(),
|
||||
Error::EthKey(ref err) => format!("{}", err),
|
||||
Error::Custom(ref s) => s.clone(),
|
||||
};
|
||||
|
||||
write!(f, "{}", s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IoError> for Error {
|
||||
fn from(err: IoError) -> Self {
|
||||
Error::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EthKeyError> for Error {
|
||||
fn from(err: EthKeyError) -> Self {
|
||||
Error::EthKey(err)
|
||||
}
|
||||
}
|
17
ethstore/src/ethkey.rs
Normal file
17
ethstore/src/ethkey.rs
Normal file
@ -0,0 +1,17 @@
|
||||
//! ethkey reexport to make documentation look pretty.
|
||||
pub use _ethkey::{Address, Message, Signature, Public, Secret, Generator, sign, verify, Error, KeyPair, Random, Prefix};
|
||||
use json;
|
||||
|
||||
impl Into<json::H160> for Address {
|
||||
fn into(self) -> json::H160 {
|
||||
let a: [u8; 20] = self.into();
|
||||
From::from(a)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<json::H160> for Address {
|
||||
fn from(json: json::H160) -> Self {
|
||||
let a: [u8; 20] = json.into();
|
||||
From::from(a)
|
||||
}
|
||||
}
|
92
ethstore/src/ethstore.rs
Normal file
92
ethstore/src/ethstore.rs
Normal file
@ -0,0 +1,92 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::RwLock;
|
||||
use ethkey::KeyPair;
|
||||
use crypto::KEY_ITERATIONS;
|
||||
use random::Random;
|
||||
use ethkey::{Signature, Address, Message, Secret};
|
||||
use dir::KeyDirectory;
|
||||
use account::SafeAccount;
|
||||
use {Error, SecretStore};
|
||||
|
||||
pub struct EthStore {
|
||||
dir: Box<KeyDirectory>,
|
||||
iterations: u32,
|
||||
cache: RwLock<BTreeMap<Address, SafeAccount>>,
|
||||
}
|
||||
|
||||
impl EthStore {
|
||||
pub fn open(directory: Box<KeyDirectory>) -> Result<Self, Error> {
|
||||
Self::open_with_iterations(directory, KEY_ITERATIONS as u32)
|
||||
}
|
||||
|
||||
pub fn open_with_iterations(directory: Box<KeyDirectory>, iterations: u32) -> Result<Self, Error> {
|
||||
let accounts = try!(directory.load());
|
||||
let cache = accounts.into_iter().map(|account| (account.address.clone(), account)).collect();
|
||||
let store = EthStore {
|
||||
dir: directory,
|
||||
iterations: iterations,
|
||||
cache: RwLock::new(cache),
|
||||
};
|
||||
Ok(store)
|
||||
}
|
||||
|
||||
fn save(&self, account: SafeAccount) -> Result<(), Error> {
|
||||
// save to file
|
||||
try!(self.dir.insert(account.clone()));
|
||||
|
||||
// update cache
|
||||
let mut cache = self.cache.write().unwrap();
|
||||
cache.insert(account.address.clone(), account);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl SecretStore for EthStore {
|
||||
fn insert_account(&self, secret: Secret, password: &str) -> Result<Address, Error> {
|
||||
let keypair = try!(KeyPair::from_secret(secret).map_err(|_| Error::CreationFailed));
|
||||
let id: [u8; 16] = Random::random();
|
||||
let account = SafeAccount::create(&keypair, id, password, self.iterations);
|
||||
let address = account.address.clone();
|
||||
try!(self.save(account));
|
||||
Ok(address)
|
||||
}
|
||||
|
||||
fn accounts(&self) -> Vec<Address> {
|
||||
self.cache.read().unwrap().keys().cloned().collect()
|
||||
}
|
||||
|
||||
fn change_password(&self, address: &Address, old_password: &str, new_password: &str) -> Result<(), Error> {
|
||||
// change password
|
||||
let account = {
|
||||
let cache = self.cache.read().unwrap();
|
||||
let account = try!(cache.get(address).ok_or(Error::InvalidAccount));
|
||||
try!(account.change_password(old_password, new_password, self.iterations))
|
||||
};
|
||||
|
||||
// save to file
|
||||
self.save(account)
|
||||
}
|
||||
|
||||
fn remove_account(&self, address: &Address, password: &str) -> Result<(), Error> {
|
||||
let can_remove = {
|
||||
let cache = self.cache.read().unwrap();
|
||||
let account = try!(cache.get(address).ok_or(Error::InvalidAccount));
|
||||
account.check_password(password)
|
||||
};
|
||||
|
||||
if can_remove {
|
||||
try!(self.dir.remove(address));
|
||||
let mut cache = self.cache.write().unwrap();
|
||||
cache.remove(address);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::InvalidPassword)
|
||||
}
|
||||
}
|
||||
|
||||
fn sign(&self, account: &Address, password: &str, message: &Message) -> Result<Signature, Error> {
|
||||
let cache = self.cache.read().unwrap();
|
||||
let account = try!(cache.get(account).ok_or(Error::InvalidAccount));
|
||||
account.sign(password, message)
|
||||
}
|
||||
}
|
12
ethstore/src/import.rs
Normal file
12
ethstore/src/import.rs
Normal file
@ -0,0 +1,12 @@
|
||||
use ethkey::Address;
|
||||
use dir::KeyDirectory;
|
||||
use Error;
|
||||
|
||||
pub fn import_accounts(src: &KeyDirectory, dst: &KeyDirectory) -> Result<Vec<Address>, Error> {
|
||||
let accounts = try!(src.load());
|
||||
accounts.into_iter().map(|a| {
|
||||
let address = a.address.clone();
|
||||
try!(dst.insert(a));
|
||||
Ok(address)
|
||||
}).collect()
|
||||
}
|
75
ethstore/src/json/cipher.rs
Normal file
75
ethstore/src/json/cipher.rs
Normal file
@ -0,0 +1,75 @@
|
||||
use serde::{Serialize, Serializer, Deserialize, Deserializer, Error as SerdeError};
|
||||
use serde::de::Visitor;
|
||||
use super::{Error, H128};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum CipherSer {
|
||||
Aes128Ctr,
|
||||
}
|
||||
|
||||
impl Serialize for CipherSer {
|
||||
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
|
||||
where S: Serializer {
|
||||
match *self {
|
||||
CipherSer::Aes128Ctr => serializer.serialize_str("aes-128-ctr"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserialize for CipherSer {
|
||||
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
|
||||
where D: Deserializer {
|
||||
deserializer.deserialize(CipherSerVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
struct CipherSerVisitor;
|
||||
|
||||
impl Visitor for CipherSerVisitor {
|
||||
type Value = CipherSer;
|
||||
|
||||
fn visit_str<E>(&mut self, value: &str) -> Result<Self::Value, E> where E: SerdeError {
|
||||
match value {
|
||||
"aes-128-ctr" => Ok(CipherSer::Aes128Ctr),
|
||||
_ => Err(SerdeError::custom(Error::UnsupportedCipher))
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_string<E>(&mut self, value: String) -> Result<Self::Value, E> where E: SerdeError {
|
||||
self.visit_str(value.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Aes128Ctr {
|
||||
pub iv: H128,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum CipherSerParams {
|
||||
Aes128Ctr(Aes128Ctr),
|
||||
}
|
||||
|
||||
impl Serialize for CipherSerParams {
|
||||
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
|
||||
where S: Serializer {
|
||||
match *self {
|
||||
CipherSerParams::Aes128Ctr(ref params) => params.serialize(serializer),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserialize for CipherSerParams {
|
||||
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
|
||||
where D: Deserializer {
|
||||
Aes128Ctr::deserialize(deserializer)
|
||||
.map(CipherSerParams::Aes128Ctr)
|
||||
.map_err(|_| Error::InvalidCipherParams)
|
||||
.map_err(SerdeError::custom)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Cipher {
|
||||
Aes128Ctr(Aes128Ctr),
|
||||
}
|
184
ethstore/src/json/crypto.rs
Normal file
184
ethstore/src/json/crypto.rs
Normal file
@ -0,0 +1,184 @@
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer, Error};
|
||||
use serde::de::{Visitor, MapVisitor};
|
||||
use serde::ser;
|
||||
use super::{Cipher, CipherSer, CipherSerParams, Kdf, KdfSer, KdfSerParams, H256};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Crypto {
|
||||
pub cipher: Cipher,
|
||||
pub ciphertext: H256,
|
||||
pub kdf: Kdf,
|
||||
pub mac: H256,
|
||||
}
|
||||
|
||||
enum CryptoField {
|
||||
Cipher,
|
||||
CipherParams,
|
||||
CipherText,
|
||||
Kdf,
|
||||
KdfParams,
|
||||
Mac,
|
||||
}
|
||||
|
||||
impl Deserialize for CryptoField {
|
||||
fn deserialize<D>(deserializer: &mut D) -> Result<CryptoField, D::Error>
|
||||
where D: Deserializer
|
||||
{
|
||||
deserializer.deserialize(CryptoFieldVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
struct CryptoFieldVisitor;
|
||||
|
||||
impl Visitor for CryptoFieldVisitor {
|
||||
type Value = CryptoField;
|
||||
|
||||
fn visit_str<E>(&mut self, value: &str) -> Result<Self::Value, E>
|
||||
where E: Error
|
||||
{
|
||||
match value {
|
||||
"cipher" => Ok(CryptoField::Cipher),
|
||||
"cipherparams" => Ok(CryptoField::CipherParams),
|
||||
"ciphertext" => Ok(CryptoField::CipherText),
|
||||
"kdf" => Ok(CryptoField::Kdf),
|
||||
"kdfparams" => Ok(CryptoField::KdfParams),
|
||||
"mac" => Ok(CryptoField::Mac),
|
||||
_ => Err(Error::custom(format!("Unknown field: '{}'", value))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserialize for Crypto {
|
||||
fn deserialize<D>(deserializer: &mut D) -> Result<Crypto, D::Error>
|
||||
where D: Deserializer
|
||||
{
|
||||
static FIELDS: &'static [&'static str] = &["id", "version", "crypto", "Crypto", "address"];
|
||||
deserializer.deserialize_struct("Crypto", FIELDS, CryptoVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
struct CryptoVisitor;
|
||||
|
||||
impl Visitor for CryptoVisitor {
|
||||
type Value = Crypto;
|
||||
|
||||
fn visit_map<V>(&mut self, mut visitor: V) -> Result<Self::Value, V::Error>
|
||||
where V: MapVisitor
|
||||
{
|
||||
let mut cipher = None;
|
||||
let mut cipherparams = None;
|
||||
let mut ciphertext = None;
|
||||
let mut kdf = None;
|
||||
let mut kdfparams = None;
|
||||
let mut mac = None;
|
||||
|
||||
loop {
|
||||
match try!(visitor.visit_key()) {
|
||||
Some(CryptoField::Cipher) => { cipher = Some(try!(visitor.visit_value())); }
|
||||
Some(CryptoField::CipherParams) => { cipherparams = Some(try!(visitor.visit_value())); }
|
||||
Some(CryptoField::CipherText) => { ciphertext = Some(try!(visitor.visit_value())); }
|
||||
Some(CryptoField::Kdf) => { kdf = Some(try!(visitor.visit_value())); }
|
||||
Some(CryptoField::KdfParams) => { kdfparams = Some(try!(visitor.visit_value())); }
|
||||
Some(CryptoField::Mac) => { mac = Some(try!(visitor.visit_value())); }
|
||||
None => { break; }
|
||||
}
|
||||
}
|
||||
|
||||
let cipher = match (cipher, cipherparams) {
|
||||
(Some(CipherSer::Aes128Ctr), Some(CipherSerParams::Aes128Ctr(params))) => Cipher::Aes128Ctr(params),
|
||||
(None, _) => return Err(Error::missing_field("cipher")),
|
||||
(Some(_), None) => return Err(Error::missing_field("cipherparams")),
|
||||
};
|
||||
|
||||
let ciphertext = match ciphertext {
|
||||
Some(ciphertext) => ciphertext,
|
||||
None => try!(visitor.missing_field("ciphertext")),
|
||||
};
|
||||
|
||||
let kdf = match (kdf, kdfparams) {
|
||||
(Some(KdfSer::Pbkdf2), Some(KdfSerParams::Pbkdf2(params))) => Kdf::Pbkdf2(params),
|
||||
(Some(KdfSer::Scrypt), Some(KdfSerParams::Scrypt(params))) => Kdf::Scrypt(params),
|
||||
(Some(_), Some(_)) => return Err(Error::custom("Invalid cipherparams")),
|
||||
(None, _) => return Err(Error::missing_field("kdf")),
|
||||
(Some(_), None) => return Err(Error::missing_field("kdfparams")),
|
||||
};
|
||||
|
||||
let mac = match mac {
|
||||
Some(mac) => mac,
|
||||
None => try!(visitor.missing_field("mac")),
|
||||
};
|
||||
|
||||
try!(visitor.end());
|
||||
|
||||
let result = Crypto {
|
||||
cipher: cipher,
|
||||
ciphertext: ciphertext,
|
||||
kdf: kdf,
|
||||
mac: mac,
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Crypto {
|
||||
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
|
||||
where S: Serializer
|
||||
{
|
||||
serializer.serialize_struct("Crypto", CryptoMapVisitor {
|
||||
value: self,
|
||||
state: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct CryptoMapVisitor<'a> {
|
||||
value: &'a Crypto,
|
||||
state: u8,
|
||||
}
|
||||
|
||||
impl<'a> ser::MapVisitor for CryptoMapVisitor<'a> {
|
||||
fn visit<S>(&mut self, serializer: &mut S) -> Result<Option<()>, S::Error>
|
||||
where S: Serializer
|
||||
{
|
||||
match self.state {
|
||||
0 => {
|
||||
self.state += 1;
|
||||
match self.value.cipher {
|
||||
Cipher::Aes128Ctr(_) => Ok(Some(try!(serializer.serialize_struct_elt("cipher", &CipherSer::Aes128Ctr)))),
|
||||
}
|
||||
},
|
||||
1 => {
|
||||
self.state += 1;
|
||||
match self.value.cipher {
|
||||
Cipher::Aes128Ctr(ref params) => Ok(Some(try!(serializer.serialize_struct_elt("cipherparams", params)))),
|
||||
}
|
||||
},
|
||||
2 => {
|
||||
self.state += 1;
|
||||
Ok(Some(try!(serializer.serialize_struct_elt("ciphertext", &self.value.ciphertext))))
|
||||
},
|
||||
3 => {
|
||||
self.state += 1;
|
||||
match self.value.kdf {
|
||||
Kdf::Pbkdf2(_) => Ok(Some(try!(serializer.serialize_struct_elt("kdf", &KdfSer::Pbkdf2)))),
|
||||
Kdf::Scrypt(_) => Ok(Some(try!(serializer.serialize_struct_elt("kdf", &KdfSer::Scrypt)))),
|
||||
}
|
||||
},
|
||||
4 => {
|
||||
self.state += 1;
|
||||
match self.value.kdf {
|
||||
Kdf::Pbkdf2(ref params) => Ok(Some(try!(serializer.serialize_struct_elt("kdfparams", params)))),
|
||||
Kdf::Scrypt(ref params) => Ok(Some(try!(serializer.serialize_struct_elt("kdfparams", params)))),
|
||||
}
|
||||
},
|
||||
5 => {
|
||||
self.state += 1;
|
||||
Ok(Some(try!(serializer.serialize_struct_elt("mac", &self.value.mac))))
|
||||
},
|
||||
_ => {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
34
ethstore/src/json/error.rs
Normal file
34
ethstore/src/json/error.rs
Normal file
@ -0,0 +1,34 @@
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
UnsupportedCipher,
|
||||
InvalidCipherParams,
|
||||
UnsupportedKdf,
|
||||
InvalidUUID,
|
||||
UnsupportedVersion,
|
||||
InvalidCiphertext,
|
||||
InvalidH256,
|
||||
InvalidPrf,
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
match *self {
|
||||
Error::InvalidUUID => write!(f, "Invalid UUID"),
|
||||
Error::UnsupportedVersion => write!(f, "Unsupported version"),
|
||||
Error::UnsupportedKdf => write!(f, "Unsupported kdf"),
|
||||
Error::InvalidCiphertext => write!(f, "Invalid ciphertext"),
|
||||
Error::UnsupportedCipher => write!(f, "Unsupported cipher"),
|
||||
Error::InvalidCipherParams => write!(f, "Invalid cipher params"),
|
||||
Error::InvalidH256 => write!(f, "Invalid hash"),
|
||||
Error::InvalidPrf => write!(f, "Invalid prf"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<String> for Error {
|
||||
fn into(self) -> String {
|
||||
format!("{}", self)
|
||||
}
|
||||
}
|
71
ethstore/src/json/hash.rs
Normal file
71
ethstore/src/json/hash.rs
Normal file
@ -0,0 +1,71 @@
|
||||
use std::str::FromStr;
|
||||
use rustc_serialize::hex::{FromHex, ToHex};
|
||||
use serde::{Serialize, Serializer, Deserialize, Deserializer, Error as SerdeError};
|
||||
use serde::de::Visitor;
|
||||
use super::Error;
|
||||
|
||||
macro_rules! impl_hash {
|
||||
($name: ident, $size: expr) => {
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct $name([u8; $size]);
|
||||
|
||||
impl Serialize for $name {
|
||||
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
|
||||
where S: Serializer {
|
||||
serializer.serialize_str(&self.0.to_hex())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserialize for $name {
|
||||
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
|
||||
where D: Deserializer {
|
||||
struct HashVisitor;
|
||||
|
||||
impl Visitor for HashVisitor {
|
||||
type Value = $name;
|
||||
|
||||
fn visit_str<E>(&mut self, value: &str) -> Result<Self::Value, E> where E: SerdeError {
|
||||
FromStr::from_str(value).map_err(SerdeError::custom)
|
||||
}
|
||||
|
||||
fn visit_string<E>(&mut self, value: String) -> Result<Self::Value, E> where E: SerdeError {
|
||||
self.visit_str(value.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize(HashVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for $name {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||
match value.from_hex() {
|
||||
Ok(ref hex) if hex.len() == $size => {
|
||||
let mut hash = [0u8; $size];
|
||||
hash.clone_from_slice(hex);
|
||||
Ok($name(hash))
|
||||
}
|
||||
_ => Err(Error::InvalidH256),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; $size]> for $name {
|
||||
fn from(bytes: [u8; $size]) -> Self {
|
||||
$name(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<[u8; $size]> for $name {
|
||||
fn into(self) -> [u8; $size] {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_hash!(H128, 16);
|
||||
impl_hash!(H160, 20);
|
||||
impl_hash!(H256, 32);
|
129
ethstore/src/json/id.rs
Normal file
129
ethstore/src/json/id.rs
Normal file
@ -0,0 +1,129 @@
|
||||
//! Universaly unique identifier.
|
||||
use std::str::FromStr;
|
||||
use std::fmt;
|
||||
use rustc_serialize::hex::{ToHex, FromHex};
|
||||
use serde::{Deserialize, Serialize, Deserializer, Serializer, Error as SerdeError};
|
||||
use serde::de::Visitor;
|
||||
use super::Error;
|
||||
|
||||
/// Universaly unique identifier.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct UUID([u8; 16]);
|
||||
|
||||
impl From<[u8; 16]> for UUID {
|
||||
fn from(uuid: [u8; 16]) -> Self {
|
||||
UUID(uuid)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Into<String> for &'a UUID {
|
||||
fn into(self) -> String {
|
||||
let d1 = &self.0[0..4];
|
||||
let d2 = &self.0[4..6];
|
||||
let d3 = &self.0[6..8];
|
||||
let d4 = &self.0[8..10];
|
||||
let d5 = &self.0[10..16];
|
||||
[d1, d2, d3, d4, d5].into_iter().map(|d| d.to_hex()).collect::<Vec<String>>().join("-")
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<String> for UUID {
|
||||
fn into(self) -> String {
|
||||
Into::into(&self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<[u8; 16]> for UUID {
|
||||
fn into(self) -> [u8; 16] {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for UUID {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
let s: String = (self as &UUID).into();
|
||||
write!(f, "{}", s)
|
||||
}
|
||||
}
|
||||
|
||||
fn copy_into(from: &str, into: &mut [u8]) -> Result<(), Error> {
|
||||
let from = try!(from.from_hex().map_err(|_| Error::InvalidUUID));
|
||||
|
||||
if from.len() != into.len() {
|
||||
return Err(Error::InvalidUUID);
|
||||
}
|
||||
|
||||
into.copy_from_slice(&from);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl FromStr for UUID {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let parts: Vec<&str> = s.split("-").collect();
|
||||
|
||||
if parts.len() != 5 {
|
||||
return Err(Error::InvalidUUID);
|
||||
}
|
||||
|
||||
let mut uuid = [0u8; 16];
|
||||
|
||||
try!(copy_into(parts[0], &mut uuid[0..4]));
|
||||
try!(copy_into(parts[1], &mut uuid[4..6]));
|
||||
try!(copy_into(parts[2], &mut uuid[6..8]));
|
||||
try!(copy_into(parts[3], &mut uuid[8..10]));
|
||||
try!(copy_into(parts[4], &mut uuid[10..16]));
|
||||
|
||||
Ok(UUID(uuid))
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for UUID {
|
||||
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
|
||||
where S: Serializer {
|
||||
let s: String = self.into();
|
||||
serializer.serialize_str(&s)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserialize for UUID {
|
||||
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
|
||||
where D: Deserializer {
|
||||
deserializer.deserialize(UUIDVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
struct UUIDVisitor;
|
||||
|
||||
impl Visitor for UUIDVisitor {
|
||||
type Value = UUID;
|
||||
|
||||
fn visit_str<E>(&mut self, value: &str) -> Result<Self::Value, E> where E: SerdeError {
|
||||
UUID::from_str(value).map_err(SerdeError::custom)
|
||||
}
|
||||
|
||||
fn visit_string<E>(&mut self, value: String) -> Result<Self::Value, E> where E: SerdeError {
|
||||
self.visit_str(value.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::str::FromStr;
|
||||
use super::UUID;
|
||||
|
||||
#[test]
|
||||
fn uuid_from_str() {
|
||||
let uuid = UUID::from_str("3198bc9c-6672-5ab3-d995-4942343ae5b6").unwrap();
|
||||
assert_eq!(uuid, UUID::from([0x31, 0x98, 0xbc, 0x9c, 0x66, 0x72, 0x5a, 0xb3, 0xd9, 0x95, 0x49, 0x42, 0x34, 0x3a, 0xe5, 0xb6]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uuid_from_and_to_str() {
|
||||
let from = "3198bc9c-6672-5ab3-d995-4942343ae5b6";
|
||||
let uuid = UUID::from_str(from).unwrap();
|
||||
let to: String = uuid.into();
|
||||
assert_eq!(from, &to);
|
||||
}
|
||||
}
|
133
ethstore/src/json/kdf.rs
Normal file
133
ethstore/src/json/kdf.rs
Normal file
@ -0,0 +1,133 @@
|
||||
use serde::{Serialize, Serializer, Deserialize, Deserializer, Error as SerdeError};
|
||||
use serde::de::Visitor;
|
||||
use serde_json::{Value, value};
|
||||
use super::{Error, H256};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum KdfSer {
|
||||
Pbkdf2,
|
||||
Scrypt,
|
||||
}
|
||||
|
||||
impl Serialize for KdfSer {
|
||||
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
|
||||
where S: Serializer {
|
||||
match *self {
|
||||
KdfSer::Pbkdf2 => serializer.serialize_str("pbkdf2"),
|
||||
KdfSer::Scrypt => serializer.serialize_str("scrypt"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserialize for KdfSer {
|
||||
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
|
||||
where D: Deserializer {
|
||||
deserializer.deserialize(KdfSerVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
struct KdfSerVisitor;
|
||||
|
||||
impl Visitor for KdfSerVisitor {
|
||||
type Value = KdfSer;
|
||||
|
||||
fn visit_str<E>(&mut self, value: &str) -> Result<Self::Value, E> where E: SerdeError {
|
||||
match value {
|
||||
"pbkdf2" => Ok(KdfSer::Pbkdf2),
|
||||
"scrypt" => Ok(KdfSer::Scrypt),
|
||||
_ => Err(SerdeError::custom(Error::UnsupportedKdf))
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_string<E>(&mut self, value: String) -> Result<Self::Value, E> where E: SerdeError {
|
||||
self.visit_str(value.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Prf {
|
||||
HmacSha256,
|
||||
}
|
||||
|
||||
impl Serialize for Prf {
|
||||
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
|
||||
where S: Serializer {
|
||||
match *self {
|
||||
Prf::HmacSha256 => serializer.serialize_str("hmac-sha256"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserialize for Prf {
|
||||
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
|
||||
where D: Deserializer {
|
||||
deserializer.deserialize(PrfVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
struct PrfVisitor;
|
||||
|
||||
impl Visitor for PrfVisitor {
|
||||
type Value = Prf;
|
||||
|
||||
fn visit_str<E>(&mut self, value: &str) -> Result<Self::Value, E> where E: SerdeError {
|
||||
match value {
|
||||
"hmac-sha256" => Ok(Prf::HmacSha256),
|
||||
_ => Err(SerdeError::custom(Error::InvalidPrf)),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_string<E>(&mut self, value: String) -> Result<Self::Value, E> where E: SerdeError {
|
||||
self.visit_str(value.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Pbkdf2 {
|
||||
pub c: u32,
|
||||
pub dklen: u32,
|
||||
pub prf: Prf,
|
||||
pub salt: H256,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Scrypt {
|
||||
pub dklen: u32,
|
||||
pub p: u32,
|
||||
pub n: u32,
|
||||
pub r: u32,
|
||||
pub salt: H256,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum KdfSerParams {
|
||||
Pbkdf2(Pbkdf2),
|
||||
Scrypt(Scrypt),
|
||||
}
|
||||
|
||||
impl Serialize for KdfSerParams {
|
||||
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
|
||||
where S: Serializer {
|
||||
match *self {
|
||||
KdfSerParams::Pbkdf2(ref params) => params.serialize(serializer),
|
||||
KdfSerParams::Scrypt(ref params) => params.serialize(serializer),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserialize for KdfSerParams {
|
||||
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
|
||||
where D: Deserializer {
|
||||
let v = try!(Value::deserialize(deserializer));
|
||||
|
||||
Deserialize::deserialize(&mut value::Deserializer::new(v.clone())).map(KdfSerParams::Pbkdf2)
|
||||
.or_else(|_| Deserialize::deserialize(&mut value::Deserializer::new(v)).map(KdfSerParams::Scrypt))
|
||||
.map_err(|e| D::Error::custom(format!("{}", e)))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Kdf {
|
||||
Pbkdf2(Pbkdf2),
|
||||
Scrypt(Scrypt),
|
||||
}
|
255
ethstore/src/json/key_file.rs
Normal file
255
ethstore/src/json/key_file.rs
Normal file
@ -0,0 +1,255 @@
|
||||
use std::io::{Read, Write};
|
||||
use serde::{Deserialize, Deserializer, Error};
|
||||
use serde::de::{Visitor, MapVisitor};
|
||||
use serde_json;
|
||||
use super::{UUID, Version, Crypto, H160};
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize)]
|
||||
pub struct KeyFile {
|
||||
pub id: UUID,
|
||||
pub version: Version,
|
||||
pub crypto: Crypto,
|
||||
pub address: H160,
|
||||
}
|
||||
|
||||
enum KeyFileField {
|
||||
ID,
|
||||
Version,
|
||||
Crypto,
|
||||
Address,
|
||||
}
|
||||
|
||||
impl Deserialize for KeyFileField {
|
||||
fn deserialize<D>(deserializer: &mut D) -> Result<KeyFileField, D::Error>
|
||||
where D: Deserializer
|
||||
{
|
||||
deserializer.deserialize(KeyFileFieldVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
struct KeyFileFieldVisitor;
|
||||
|
||||
impl Visitor for KeyFileFieldVisitor {
|
||||
type Value = KeyFileField;
|
||||
|
||||
fn visit_str<E>(&mut self, value: &str) -> Result<Self::Value, E>
|
||||
where E: Error
|
||||
{
|
||||
match value {
|
||||
"id" => Ok(KeyFileField::ID),
|
||||
"version" => Ok(KeyFileField::Version),
|
||||
"crypto" => Ok(KeyFileField::Crypto),
|
||||
"Crypto" => Ok(KeyFileField::Crypto),
|
||||
"address" => Ok(KeyFileField::Address),
|
||||
_ => Err(Error::custom(format!("Unknown field: '{}'", value))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserialize for KeyFile {
|
||||
fn deserialize<D>(deserializer: &mut D) -> Result<KeyFile, D::Error>
|
||||
where D: Deserializer
|
||||
{
|
||||
static FIELDS: &'static [&'static str] = &["id", "version", "crypto", "Crypto", "address"];
|
||||
deserializer.deserialize_struct("KeyFile", FIELDS, KeyFileVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
struct KeyFileVisitor;
|
||||
|
||||
impl Visitor for KeyFileVisitor {
|
||||
type Value = KeyFile;
|
||||
|
||||
fn visit_map<V>(&mut self, mut visitor: V) -> Result<Self::Value, V::Error>
|
||||
where V: MapVisitor
|
||||
{
|
||||
let mut id = None;
|
||||
let mut version = None;
|
||||
let mut crypto = None;
|
||||
let mut address = None;
|
||||
|
||||
loop {
|
||||
match try!(visitor.visit_key()) {
|
||||
Some(KeyFileField::ID) => { id = Some(try!(visitor.visit_value())); }
|
||||
Some(KeyFileField::Version) => { version = Some(try!(visitor.visit_value())); }
|
||||
Some(KeyFileField::Crypto) => { crypto = Some(try!(visitor.visit_value())); }
|
||||
Some(KeyFileField::Address) => { address = Some(try!(visitor.visit_value())); }
|
||||
None => { break; }
|
||||
}
|
||||
}
|
||||
|
||||
let id = match id {
|
||||
Some(id) => id,
|
||||
None => try!(visitor.missing_field("id")),
|
||||
};
|
||||
|
||||
let version = match version {
|
||||
Some(version) => version,
|
||||
None => try!(visitor.missing_field("version")),
|
||||
};
|
||||
|
||||
let crypto = match crypto {
|
||||
Some(crypto) => crypto,
|
||||
None => try!(visitor.missing_field("crypto")),
|
||||
};
|
||||
|
||||
let address = match address {
|
||||
Some(address) => address,
|
||||
None => try!(visitor.missing_field("address")),
|
||||
};
|
||||
|
||||
try!(visitor.end());
|
||||
|
||||
let result = KeyFile {
|
||||
id: id,
|
||||
version: version,
|
||||
crypto: crypto,
|
||||
address: address,
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyFile {
|
||||
pub fn load<R>(reader: R) -> Result<Self, serde_json::Error> where R: Read {
|
||||
serde_json::from_reader(reader)
|
||||
}
|
||||
|
||||
pub fn write<W>(&self, writer: &mut W) -> Result<(), serde_json::Error> where W: Write {
|
||||
serde_json::to_writer(writer, self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::str::FromStr;
|
||||
use serde_json;
|
||||
use json::{KeyFile, UUID, Version, Crypto, Cipher, Aes128Ctr, Kdf, Scrypt, H128, H160, H256};
|
||||
|
||||
#[test]
|
||||
fn basic_keyfile() {
|
||||
let json = r#"
|
||||
{
|
||||
"address": "6edddfc6349aff20bc6467ccf276c5b52487f7a8",
|
||||
"crypto": {
|
||||
"cipher": "aes-128-ctr",
|
||||
"ciphertext": "7203da0676d141b138cd7f8e1a4365f59cc1aa6978dc5443f364ca943d7cb4bc",
|
||||
"cipherparams": {
|
||||
"iv": "b5a7ec855ec9e2c405371356855fec83"
|
||||
},
|
||||
"kdf": "scrypt",
|
||||
"kdfparams": {
|
||||
"dklen": 32,
|
||||
"n": 262144,
|
||||
"p": 1,
|
||||
"r": 8,
|
||||
"salt": "1e8642fdf1f87172492c1412fc62f8db75d796cdfa9c53c3f2b11e44a2a1b209"
|
||||
},
|
||||
"mac": "46325c5d4e8c991ad2683d525c7854da387138b6ca45068985aa4959fa2b8c8f"
|
||||
},
|
||||
"id": "8777d9f6-7860-4b9b-88b7-0b57ee6b3a73",
|
||||
"version": 3
|
||||
}"#;
|
||||
|
||||
let expected = KeyFile {
|
||||
id: UUID::from_str("8777d9f6-7860-4b9b-88b7-0b57ee6b3a73").unwrap(),
|
||||
version: Version::V3,
|
||||
address: H160::from_str("6edddfc6349aff20bc6467ccf276c5b52487f7a8").unwrap(),
|
||||
crypto: Crypto {
|
||||
cipher: Cipher::Aes128Ctr(Aes128Ctr {
|
||||
iv: H128::from_str("b5a7ec855ec9e2c405371356855fec83").unwrap(),
|
||||
}),
|
||||
ciphertext: H256::from_str("7203da0676d141b138cd7f8e1a4365f59cc1aa6978dc5443f364ca943d7cb4bc").unwrap(),
|
||||
kdf: Kdf::Scrypt(Scrypt {
|
||||
n: 262144,
|
||||
dklen: 32,
|
||||
p: 1,
|
||||
r: 8,
|
||||
salt: H256::from_str("1e8642fdf1f87172492c1412fc62f8db75d796cdfa9c53c3f2b11e44a2a1b209").unwrap(),
|
||||
}),
|
||||
mac: H256::from_str("46325c5d4e8c991ad2683d525c7854da387138b6ca45068985aa4959fa2b8c8f").unwrap(),
|
||||
},
|
||||
};
|
||||
|
||||
let keyfile: KeyFile = serde_json::from_str(json).unwrap();
|
||||
assert_eq!(keyfile, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn capital_crypto_keyfile() {
|
||||
let json = r#"
|
||||
{
|
||||
"address": "6edddfc6349aff20bc6467ccf276c5b52487f7a8",
|
||||
"Crypto": {
|
||||
"cipher": "aes-128-ctr",
|
||||
"ciphertext": "7203da0676d141b138cd7f8e1a4365f59cc1aa6978dc5443f364ca943d7cb4bc",
|
||||
"cipherparams": {
|
||||
"iv": "b5a7ec855ec9e2c405371356855fec83"
|
||||
},
|
||||
"kdf": "scrypt",
|
||||
"kdfparams": {
|
||||
"dklen": 32,
|
||||
"n": 262144,
|
||||
"p": 1,
|
||||
"r": 8,
|
||||
"salt": "1e8642fdf1f87172492c1412fc62f8db75d796cdfa9c53c3f2b11e44a2a1b209"
|
||||
},
|
||||
"mac": "46325c5d4e8c991ad2683d525c7854da387138b6ca45068985aa4959fa2b8c8f"
|
||||
},
|
||||
"id": "8777d9f6-7860-4b9b-88b7-0b57ee6b3a73",
|
||||
"version": 3
|
||||
}"#;
|
||||
|
||||
let expected = KeyFile {
|
||||
id: UUID::from_str("8777d9f6-7860-4b9b-88b7-0b57ee6b3a73").unwrap(),
|
||||
version: Version::V3,
|
||||
address: H160::from_str("6edddfc6349aff20bc6467ccf276c5b52487f7a8").unwrap(),
|
||||
crypto: Crypto {
|
||||
cipher: Cipher::Aes128Ctr(Aes128Ctr {
|
||||
iv: H128::from_str("b5a7ec855ec9e2c405371356855fec83").unwrap(),
|
||||
}),
|
||||
ciphertext: H256::from_str("7203da0676d141b138cd7f8e1a4365f59cc1aa6978dc5443f364ca943d7cb4bc").unwrap(),
|
||||
kdf: Kdf::Scrypt(Scrypt {
|
||||
n: 262144,
|
||||
dklen: 32,
|
||||
p: 1,
|
||||
r: 8,
|
||||
salt: H256::from_str("1e8642fdf1f87172492c1412fc62f8db75d796cdfa9c53c3f2b11e44a2a1b209").unwrap(),
|
||||
}),
|
||||
mac: H256::from_str("46325c5d4e8c991ad2683d525c7854da387138b6ca45068985aa4959fa2b8c8f").unwrap(),
|
||||
},
|
||||
};
|
||||
|
||||
let keyfile: KeyFile = serde_json::from_str(json).unwrap();
|
||||
assert_eq!(keyfile, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_and_from_json() {
|
||||
let file = KeyFile {
|
||||
id: UUID::from_str("8777d9f6-7860-4b9b-88b7-0b57ee6b3a73").unwrap(),
|
||||
version: Version::V3,
|
||||
address: H160::from_str("6edddfc6349aff20bc6467ccf276c5b52487f7a8").unwrap(),
|
||||
crypto: Crypto {
|
||||
cipher: Cipher::Aes128Ctr(Aes128Ctr {
|
||||
iv: H128::from_str("b5a7ec855ec9e2c405371356855fec83").unwrap(),
|
||||
}),
|
||||
ciphertext: H256::from_str("7203da0676d141b138cd7f8e1a4365f59cc1aa6978dc5443f364ca943d7cb4bc").unwrap(),
|
||||
kdf: Kdf::Scrypt(Scrypt {
|
||||
n: 262144,
|
||||
dklen: 32,
|
||||
p: 1,
|
||||
r: 8,
|
||||
salt: H256::from_str("1e8642fdf1f87172492c1412fc62f8db75d796cdfa9c53c3f2b11e44a2a1b209").unwrap(),
|
||||
}),
|
||||
mac: H256::from_str("46325c5d4e8c991ad2683d525c7854da387138b6ca45068985aa4959fa2b8c8f").unwrap(),
|
||||
},
|
||||
};
|
||||
|
||||
let serialized = serde_json::to_string(&file).unwrap();
|
||||
let deserialized = serde_json::from_str(&serialized).unwrap();
|
||||
|
||||
assert_eq!(file, deserialized);
|
||||
}
|
||||
}
|
8
ethstore/src/json/mod.rs
Normal file
8
ethstore/src/json/mod.rs
Normal file
@ -0,0 +1,8 @@
|
||||
//! Contract interface specification.
|
||||
|
||||
#[cfg(feature = "serde_macros")]
|
||||
include!("mod.rs.in");
|
||||
|
||||
#[cfg(not(feature = "serde_macros"))]
|
||||
include!(concat!(env!("OUT_DIR"), "/mod.rs"));
|
||||
|
18
ethstore/src/json/mod.rs.in
Normal file
18
ethstore/src/json/mod.rs.in
Normal file
@ -0,0 +1,18 @@
|
||||
mod cipher;
|
||||
mod crypto;
|
||||
mod error;
|
||||
mod hash;
|
||||
mod id;
|
||||
mod kdf;
|
||||
mod key_file;
|
||||
mod version;
|
||||
|
||||
pub use self::cipher::{Cipher, CipherSer, CipherSerParams, Aes128Ctr};
|
||||
pub use self::crypto::Crypto;
|
||||
pub use self::error::Error;
|
||||
pub use self::hash::{H128, H160, H256};
|
||||
pub use self::id::UUID;
|
||||
pub use self::kdf::{Kdf, KdfSer, Prf, Pbkdf2, Scrypt, KdfSerParams};
|
||||
pub use self::key_file::KeyFile;
|
||||
pub use self::version::Version;
|
||||
|
38
ethstore/src/json/version.rs
Normal file
38
ethstore/src/json/version.rs
Normal file
@ -0,0 +1,38 @@
|
||||
use serde::{Serialize, Serializer, Deserialize, Deserializer, Error as SerdeError};
|
||||
use serde::de::Visitor;
|
||||
use super::Error;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Version {
|
||||
V3,
|
||||
}
|
||||
|
||||
impl Serialize for Version {
|
||||
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
|
||||
where S: Serializer {
|
||||
match *self {
|
||||
Version::V3 => serializer.serialize_u64(3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserialize for Version {
|
||||
fn deserialize<D>(deserializer: &mut D) -> Result<Version, D::Error>
|
||||
where D: Deserializer {
|
||||
deserializer.deserialize(VersionVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
struct VersionVisitor;
|
||||
|
||||
impl Visitor for VersionVisitor {
|
||||
type Value = Version;
|
||||
|
||||
fn visit_u64<E>(&mut self, value: u64) -> Result<Self::Value, E> where E: SerdeError {
|
||||
match value {
|
||||
3 => Ok(Version::V3),
|
||||
_ => Err(SerdeError::custom(Error::UnsupportedVersion))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
32
ethstore/src/lib.rs
Normal file
32
ethstore/src/lib.rs
Normal file
@ -0,0 +1,32 @@
|
||||
#![cfg_attr(feature="nightly", feature(custom_derive, plugin))]
|
||||
#![cfg_attr(feature="nightly", plugin(serde_macros))]
|
||||
|
||||
extern crate libc;
|
||||
extern crate rand;
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
extern crate rustc_serialize;
|
||||
extern crate crypto as rcrypto;
|
||||
extern crate tiny_keccak;
|
||||
// reexport it nicely
|
||||
extern crate ethkey as _ethkey;
|
||||
|
||||
pub mod dir;
|
||||
pub mod ethkey;
|
||||
|
||||
mod account;
|
||||
mod json;
|
||||
mod crypto;
|
||||
|
||||
mod error;
|
||||
mod ethstore;
|
||||
mod import;
|
||||
mod random;
|
||||
mod secret_store;
|
||||
|
||||
pub use self::account::SafeAccount;
|
||||
pub use self::error::Error;
|
||||
pub use self::ethstore::EthStore;
|
||||
pub use self::import::import_accounts;
|
||||
pub use self::secret_store::SecretStore;
|
||||
|
23
ethstore/src/random.rs
Normal file
23
ethstore/src/random.rs
Normal file
@ -0,0 +1,23 @@
|
||||
use rand::{Rng, OsRng};
|
||||
|
||||
pub trait Random {
|
||||
fn random() -> Self where Self: Sized;
|
||||
}
|
||||
|
||||
impl Random for [u8; 16] {
|
||||
fn random() -> Self {
|
||||
let mut result = [0u8; 16];
|
||||
let mut rng = OsRng::new().unwrap();
|
||||
rng.fill_bytes(&mut result);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl Random for [u8; 32] {
|
||||
fn random() -> Self {
|
||||
let mut result = [0u8; 32];
|
||||
let mut rng = OsRng::new().unwrap();
|
||||
rng.fill_bytes(&mut result);
|
||||
result
|
||||
}
|
||||
}
|
15
ethstore/src/secret_store.rs
Normal file
15
ethstore/src/secret_store.rs
Normal file
@ -0,0 +1,15 @@
|
||||
use ethkey::{Address, Message, Signature, Secret};
|
||||
use Error;
|
||||
|
||||
pub trait SecretStore: Send + Sync {
|
||||
fn insert_account(&self, secret: Secret, password: &str) -> Result<Address, Error>;
|
||||
|
||||
fn accounts(&self) -> Vec<Address>;
|
||||
|
||||
fn change_password(&self, account: &Address, old_password: &str, new_password: &str) -> Result<(), Error>;
|
||||
|
||||
fn remove_account(&self, account: &Address, password: &str) -> Result<(), Error>;
|
||||
|
||||
fn sign(&self, account: &Address, password: &str, message: &Message) -> Result<Signature, Error>;
|
||||
}
|
||||
|
72
ethstore/tests/api.rs
Normal file
72
ethstore/tests/api.rs
Normal file
@ -0,0 +1,72 @@
|
||||
extern crate rand;
|
||||
extern crate ethstore;
|
||||
|
||||
mod util;
|
||||
|
||||
use ethstore::{SecretStore, EthStore};
|
||||
use ethstore::ethkey::{Random, Generator, Secret};
|
||||
use util::TransientDir;
|
||||
|
||||
#[test]
|
||||
fn secret_store_create() {
|
||||
let dir = TransientDir::create().unwrap();
|
||||
let _ = EthStore::open(Box::new(dir)).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn secret_store_open_not_existing() {
|
||||
let dir = TransientDir::open();
|
||||
let _ = EthStore::open(Box::new(dir)).unwrap();
|
||||
}
|
||||
|
||||
fn random_secret() -> Secret {
|
||||
Random.generate().unwrap().secret().clone()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn secret_store_create_account() {
|
||||
let dir = TransientDir::create().unwrap();
|
||||
let store = EthStore::open(Box::new(dir)).unwrap();
|
||||
assert_eq!(store.accounts().len(), 0);
|
||||
assert!(store.insert_account(random_secret(), "").is_ok());
|
||||
assert_eq!(store.accounts().len(), 1);
|
||||
assert!(store.insert_account(random_secret(), "").is_ok());
|
||||
assert_eq!(store.accounts().len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn secret_store_sign() {
|
||||
let dir = TransientDir::create().unwrap();
|
||||
let store = EthStore::open(Box::new(dir)).unwrap();
|
||||
assert!(store.insert_account(random_secret(), "").is_ok());
|
||||
let accounts = store.accounts();
|
||||
assert_eq!(accounts.len(), 1);
|
||||
assert!(store.sign(&accounts[0], "", &Default::default()).is_ok());
|
||||
assert!(store.sign(&accounts[0], "1", &Default::default()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn secret_store_change_password() {
|
||||
let dir = TransientDir::create().unwrap();
|
||||
let store = EthStore::open(Box::new(dir)).unwrap();
|
||||
assert!(store.insert_account(random_secret(), "").is_ok());
|
||||
let accounts = store.accounts();
|
||||
assert_eq!(accounts.len(), 1);
|
||||
assert!(store.sign(&accounts[0], "", &Default::default()).is_ok());
|
||||
assert!(store.change_password(&accounts[0], "", "1").is_ok());
|
||||
assert!(store.sign(&accounts[0], "", &Default::default()).is_err());
|
||||
assert!(store.sign(&accounts[0], "1", &Default::default()).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn secret_store_remove_account() {
|
||||
let dir = TransientDir::create().unwrap();
|
||||
let store = EthStore::open(Box::new(dir)).unwrap();
|
||||
assert!(store.insert_account(random_secret(), "").is_ok());
|
||||
let accounts = store.accounts();
|
||||
assert_eq!(accounts.len(), 1);
|
||||
assert!(store.remove_account(&accounts[0], "").is_ok());
|
||||
assert_eq!(store.accounts().len(), 0);
|
||||
assert!(store.remove_account(&accounts[0], "").is_err());
|
||||
}
|
0
ethstore/tests/cli.rs
Normal file
0
ethstore/tests/cli.rs
Normal file
3
ethstore/tests/util/mod.rs
Normal file
3
ethstore/tests/util/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod transient_dir;
|
||||
|
||||
pub use self::transient_dir::TransientDir;
|
58
ethstore/tests/util/transient_dir.rs
Normal file
58
ethstore/tests/util/transient_dir.rs
Normal file
@ -0,0 +1,58 @@
|
||||
use std::path::PathBuf;
|
||||
use std::{env, fs};
|
||||
use rand::{Rng, OsRng};
|
||||
use ethstore::dir::{KeyDirectory, DiskDirectory};
|
||||
use ethstore::ethkey::Address;
|
||||
use ethstore::{Error, SafeAccount};
|
||||
|
||||
pub fn random_dir() -> PathBuf {
|
||||
let mut rng = OsRng::new().unwrap();
|
||||
let mut dir = env::temp_dir();
|
||||
dir.push(format!("{:x}-{:x}", rng.next_u64(), rng.next_u64()));
|
||||
dir
|
||||
}
|
||||
|
||||
pub struct TransientDir {
|
||||
dir: DiskDirectory,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl TransientDir {
|
||||
pub fn create() -> Result<Self, Error> {
|
||||
let path = random_dir();
|
||||
let result = TransientDir {
|
||||
dir: try!(DiskDirectory::create(&path)),
|
||||
path: path,
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn open() -> Self {
|
||||
let path = random_dir();
|
||||
TransientDir {
|
||||
dir: DiskDirectory::at(&path),
|
||||
path: path,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TransientDir {
|
||||
fn drop(&mut self) {
|
||||
fs::remove_dir_all(&self.path).expect("Expected to remove temp dir");
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyDirectory for TransientDir {
|
||||
fn load(&self) -> Result<Vec<SafeAccount>, Error> {
|
||||
self.dir.load()
|
||||
}
|
||||
|
||||
fn insert(&self, account: SafeAccount) -> Result<(), Error> {
|
||||
self.dir.insert(account)
|
||||
}
|
||||
|
||||
fn remove(&self, address: &Address) -> Result<(), Error> {
|
||||
self.dir.remove(address)
|
||||
}
|
||||
}
|
@ -176,9 +176,10 @@ Virtual Machine Options:
|
||||
--jitvm Enable the JIT VM.
|
||||
|
||||
Legacy Options:
|
||||
--geth Run in Geth-compatibility mode. Currently just sets
|
||||
the IPC path to be the same as Geth's. Overrides
|
||||
the --ipc-path/--ipcpath options.
|
||||
--geth Run in Geth-compatibility mode. Sets the IPC path
|
||||
to be the same as Geth's. Overrides the --ipc-path
|
||||
and --ipcpath options. Alters RPCs to reflect Geth
|
||||
bugs.
|
||||
--testnet Geth-compatible testnet mode. Equivalent to --chain
|
||||
testnet --keys-path $HOME/parity/testnet-keys.
|
||||
Overrides the --keys-path option.
|
||||
|
@ -24,7 +24,7 @@ use docopt::Docopt;
|
||||
|
||||
use die::*;
|
||||
use util::*;
|
||||
use util::keys::store::{ImportKeySet, AccountService};
|
||||
use ethcore::account_provider::AccountProvider;
|
||||
use util::network_settings::NetworkSettings;
|
||||
use ethcore::client::{append_path, get_db_path, ClientConfig, Switch, VMType};
|
||||
use ethcore::ethereum;
|
||||
@ -255,7 +255,10 @@ impl Configuration {
|
||||
sync_config
|
||||
}
|
||||
|
||||
pub fn account_service(&self) -> AccountService {
|
||||
pub fn account_service(&self) -> AccountProvider {
|
||||
use ethcore::ethstore::{import_accounts, EthStore};
|
||||
use ethcore::ethstore::dir::{GethDirectory, DirectoryType, DiskDirectory};
|
||||
|
||||
// Secret Store
|
||||
let passwords = self.args.flag_password.iter().flat_map(|filename| {
|
||||
BufReader::new(&File::open(filename).unwrap_or_else(|_| die!("{} Unable to read password file. Ensure it exists and permissions are correct.", filename)))
|
||||
@ -264,18 +267,30 @@ impl Configuration {
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
}).collect::<Vec<_>>();
|
||||
let import_keys = match (self.args.flag_no_import_keys, self.args.flag_testnet) {
|
||||
(true, _) => ImportKeySet::None,
|
||||
(false, false) => ImportKeySet::Legacy,
|
||||
(false, true) => ImportKeySet::LegacyTestnet,
|
||||
|
||||
if !self.args.flag_no_import_keys {
|
||||
let dir_type = match self.args.flag_testnet {
|
||||
true => DirectoryType::Testnet,
|
||||
false => DirectoryType::Main,
|
||||
};
|
||||
let account_service = AccountService::with_security(Path::new(&self.keys_path()), self.keys_iterations(), import_keys);
|
||||
|
||||
let from = GethDirectory::open(dir_type);
|
||||
let to = DiskDirectory::create(self.keys_path()).unwrap();
|
||||
if let Err(e) = import_accounts(&from, &to) {
|
||||
warn!("Could not import accounts {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
let dir = Box::new(DiskDirectory::create(self.keys_path()).unwrap());
|
||||
let iterations = self.keys_iterations();
|
||||
let account_service = AccountProvider::new(Box::new(EthStore::open_with_iterations(dir, iterations).unwrap()));
|
||||
|
||||
if let Some(ref unlocks) = self.args.flag_unlock {
|
||||
for d in unlocks.split(',') {
|
||||
let a = Address::from_str(clean_0x(d)).unwrap_or_else(|_| {
|
||||
die!("{}: Invalid address for --unlock. Must be 40 hex characters, without the 0x at the beginning.", d)
|
||||
});
|
||||
if passwords.iter().find(|p| account_service.unlock_account_no_expire(&a, p).is_ok()).is_none() {
|
||||
if passwords.iter().find(|p| account_service.unlock_account_permanently(a, (*p).clone()).is_ok()).is_none() {
|
||||
die!("No password given to unlock account {}. Pass the password using `--password`.", a);
|
||||
}
|
||||
}
|
||||
|
@ -18,20 +18,17 @@ use std::sync::Arc;
|
||||
use ethcore::client::Client;
|
||||
use ethcore::service::{NetSyncMessage, SyncMessage};
|
||||
use ethsync::EthSync;
|
||||
use util::keys::store::AccountService;
|
||||
use ethcore::account_provider::AccountProvider;
|
||||
use util::{TimerToken, IoHandler, IoContext, NetworkService, NetworkIoMessage};
|
||||
|
||||
use informant::Informant;
|
||||
|
||||
const INFO_TIMER: TimerToken = 0;
|
||||
|
||||
const ACCOUNT_TICK_TIMER: TimerToken = 10;
|
||||
const ACCOUNT_TICK_MS: u64 = 60000;
|
||||
|
||||
pub struct ClientIoHandler {
|
||||
pub client: Arc<Client>,
|
||||
pub sync: Arc<EthSync>,
|
||||
pub accounts: Arc<AccountService>,
|
||||
pub accounts: Arc<AccountProvider>,
|
||||
pub info: Informant,
|
||||
pub network: Arc<NetworkService<SyncMessage>>,
|
||||
}
|
||||
@ -39,13 +36,11 @@ pub struct ClientIoHandler {
|
||||
impl IoHandler<NetSyncMessage> for ClientIoHandler {
|
||||
fn initialize(&self, io: &IoContext<NetSyncMessage>) {
|
||||
io.register_timer(INFO_TIMER, 5000).expect("Error registering timer");
|
||||
io.register_timer(ACCOUNT_TICK_TIMER, ACCOUNT_TICK_MS).expect("Error registering account timer");
|
||||
}
|
||||
|
||||
fn timeout(&self, _io: &IoContext<NetSyncMessage>, timer: TimerToken) {
|
||||
match timer {
|
||||
INFO_TIMER => { self.info.tick(&self.client, Some(&self.sync)); }
|
||||
ACCOUNT_TICK_TIMER => { self.accounts.tick(); },
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -222,6 +222,7 @@ fn execute_client(conf: Configuration, spec: Spec, client_config: ClientConfig)
|
||||
external_miner: external_miner.clone(),
|
||||
logger: logger.clone(),
|
||||
settings: network_settings.clone(),
|
||||
allow_pending_receipt_query: !conf.args.flag_geth,
|
||||
});
|
||||
|
||||
let dependencies = rpc::Dependencies {
|
||||
@ -477,9 +478,15 @@ fn execute_signer(conf: Configuration) {
|
||||
}
|
||||
|
||||
fn execute_account_cli(conf: Configuration) {
|
||||
use util::keys::store::SecretStore;
|
||||
use ethcore::ethstore::{SecretStore, EthStore, import_accounts};
|
||||
use ethcore::ethstore::dir::DiskDirectory;
|
||||
use ethcore::account_provider::AccountProvider;
|
||||
use rpassword::read_password;
|
||||
let mut secret_store = SecretStore::with_security(Path::new(&conf.keys_path()), conf.keys_iterations());
|
||||
|
||||
let dir = Box::new(DiskDirectory::create(conf.keys_path()).unwrap());
|
||||
let iterations = conf.keys_iterations();
|
||||
let secret_store = AccountProvider::new(Box::new(EthStore::open_with_iterations(dir, iterations).unwrap()));
|
||||
|
||||
if conf.args.cmd_new {
|
||||
println!("Please note that password is NOT RECOVERABLE.");
|
||||
print!("Type password: ");
|
||||
@ -497,15 +504,22 @@ fn execute_account_cli(conf: Configuration) {
|
||||
println!("{:?}", new_address);
|
||||
return;
|
||||
}
|
||||
|
||||
if conf.args.cmd_list {
|
||||
println!("Known addresses:");
|
||||
for &(addr, _) in &secret_store.accounts().unwrap() {
|
||||
for addr in &secret_store.accounts() {
|
||||
println!("{:?}", addr);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if conf.args.cmd_import {
|
||||
let imported = util::keys::import_keys_paths(&mut secret_store, &conf.args.arg_path).unwrap();
|
||||
let to = DiskDirectory::create(conf.keys_path()).unwrap();
|
||||
let mut imported = 0;
|
||||
for path in &conf.args.arg_path {
|
||||
let from = DiskDirectory::at(path);
|
||||
imported += import_accounts(&from, &to).unwrap_or_else(|e| die!("Could not import accounts {}", e)).len();
|
||||
}
|
||||
println!("Imported {} keys", imported);
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ use ethsync::EthSync;
|
||||
use ethcore::miner::{Miner, ExternalMiner};
|
||||
use ethcore::client::Client;
|
||||
use util::RotatingLogger;
|
||||
use util::keys::store::AccountService;
|
||||
use ethcore::account_provider::AccountProvider;
|
||||
use util::network_settings::NetworkSettings;
|
||||
|
||||
#[cfg(feature="rpc")]
|
||||
@ -83,11 +83,12 @@ pub struct Dependencies {
|
||||
pub signer_queue: Arc<ConfirmationsQueue>,
|
||||
pub client: Arc<Client>,
|
||||
pub sync: Arc<EthSync>,
|
||||
pub secret_store: Arc<AccountService>,
|
||||
pub secret_store: Arc<AccountProvider>,
|
||||
pub miner: Arc<Miner>,
|
||||
pub external_miner: Arc<ExternalMiner>,
|
||||
pub logger: Arc<RotatingLogger>,
|
||||
pub settings: Arc<NetworkSettings>,
|
||||
pub allow_pending_receipt_query: bool,
|
||||
}
|
||||
|
||||
fn to_modules(apis: &[Api]) -> BTreeMap<String, String> {
|
||||
@ -143,7 +144,7 @@ pub fn setup_rpc<T: Extendable>(server: T, deps: Arc<Dependencies>, apis: ApiSet
|
||||
server.add_delegate(NetClient::new(&deps.sync).to_delegate());
|
||||
},
|
||||
Api::Eth => {
|
||||
server.add_delegate(EthClient::new(&deps.client, &deps.sync, &deps.secret_store, &deps.miner, &deps.external_miner).to_delegate());
|
||||
server.add_delegate(EthClient::new(&deps.client, &deps.sync, &deps.secret_store, &deps.miner, &deps.external_miner, deps.allow_pending_receipt_query).to_delegate());
|
||||
server.add_delegate(EthFilterClient::new(&deps.client, &deps.miner).to_delegate());
|
||||
|
||||
if deps.signer_port.is_some() {
|
||||
|
@ -123,7 +123,7 @@ impl Configuration {
|
||||
accs.insert(Address::from(1), TestAccount::new("test"));
|
||||
let accounts = Arc::new(TestAccountProvider::new(accs));
|
||||
let server = rpc::RpcServer::new();
|
||||
server.add_delegate(EthClient::new(&client, &sync, &accounts, &miner).to_delegate());
|
||||
server.add_delegate(EthClient::new(&client, &sync, &accounts, &miner, true).to_delegate());
|
||||
server.add_delegate(EthFilterClient::new(&client, &miner).to_delegate());
|
||||
|
||||
let url = format!("{}:{}", self.args.flag_jsonrpc_addr, self.args.flag_jsonrpc_port);
|
||||
|
@ -26,7 +26,7 @@ use jsonrpc_core::*;
|
||||
use util::numbers::*;
|
||||
use util::sha3::*;
|
||||
use util::rlp::{encode, decode, UntrustedRlp, View};
|
||||
use util::keys::store::AccountProvider;
|
||||
use ethcore::account_provider::AccountProvider;
|
||||
use ethcore::client::{MiningBlockChainClient, BlockID, TransactionID, UncleID};
|
||||
use ethcore::block::IsBlock;
|
||||
use ethcore::views::*;
|
||||
@ -41,31 +41,30 @@ use v1::impls::{dispatch_transaction, error_codes};
|
||||
use serde;
|
||||
|
||||
/// Eth rpc implementation.
|
||||
pub struct EthClient<C, S, A, M, EM> where
|
||||
pub struct EthClient<C, S, M, EM> where
|
||||
C: MiningBlockChainClient,
|
||||
S: SyncProvider,
|
||||
A: AccountProvider,
|
||||
M: MinerService,
|
||||
EM: ExternalMinerService {
|
||||
|
||||
client: Weak<C>,
|
||||
sync: Weak<S>,
|
||||
accounts: Weak<A>,
|
||||
accounts: Weak<AccountProvider>,
|
||||
miner: Weak<M>,
|
||||
external_miner: Arc<EM>,
|
||||
seed_compute: Mutex<SeedHashCompute>,
|
||||
allow_pending_receipt_query: bool,
|
||||
}
|
||||
|
||||
impl<C, S, A, M, EM> EthClient<C, S, A, M, EM> where
|
||||
impl<C, S, M, EM> EthClient<C, S, M, EM> where
|
||||
C: MiningBlockChainClient,
|
||||
S: SyncProvider,
|
||||
A: AccountProvider,
|
||||
M: MinerService,
|
||||
EM: ExternalMinerService {
|
||||
|
||||
/// Creates new EthClient.
|
||||
pub fn new(client: &Arc<C>, sync: &Arc<S>, accounts: &Arc<A>, miner: &Arc<M>, em: &Arc<EM>)
|
||||
-> EthClient<C, S, A, M, EM> {
|
||||
pub fn new(client: &Arc<C>, sync: &Arc<S>, accounts: &Arc<AccountProvider>, miner: &Arc<M>, em: &Arc<EM>, allow_pending_receipt_query: bool)
|
||||
-> EthClient<C, S, M, EM> {
|
||||
EthClient {
|
||||
client: Arc::downgrade(client),
|
||||
sync: Arc::downgrade(sync),
|
||||
@ -73,6 +72,7 @@ impl<C, S, A, M, EM> EthClient<C, S, A, M, EM> where
|
||||
accounts: Arc::downgrade(accounts),
|
||||
external_miner: em.clone(),
|
||||
seed_compute: Mutex::new(SeedHashCompute::new()),
|
||||
allow_pending_receipt_query: allow_pending_receipt_query,
|
||||
}
|
||||
}
|
||||
|
||||
@ -234,10 +234,9 @@ fn no_work_err() -> Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, S, A, M, EM> Eth for EthClient<C, S, A, M, EM> where
|
||||
impl<C, S, M, EM> Eth for EthClient<C, S, M, EM> where
|
||||
C: MiningBlockChainClient + 'static,
|
||||
S: SyncProvider + 'static,
|
||||
A: AccountProvider + 'static,
|
||||
M: MinerService + 'static,
|
||||
EM: ExternalMinerService + 'static {
|
||||
|
||||
@ -304,10 +303,7 @@ impl<C, S, A, M, EM> Eth for EthClient<C, S, A, M, EM> where
|
||||
|
||||
fn accounts(&self, _: Params) -> Result<Value, Error> {
|
||||
let store = take_weak!(self.accounts);
|
||||
match store.accounts() {
|
||||
Ok(account_list) => to_value(&account_list),
|
||||
Err(_) => Err(Error::internal_error())
|
||||
}
|
||||
to_value(&store.accounts())
|
||||
}
|
||||
|
||||
fn block_number(&self, params: Params) -> Result<Value, Error> {
|
||||
@ -423,8 +419,8 @@ impl<C, S, A, M, EM> Eth for EthClient<C, S, A, M, EM> where
|
||||
.and_then(|(hash,)| {
|
||||
let miner = take_weak!(self.miner);
|
||||
match miner.pending_receipts().get(&hash) {
|
||||
Some(receipt) => to_value(&Receipt::from(receipt.clone())),
|
||||
None => {
|
||||
Some(receipt) if self.allow_pending_receipt_query => to_value(&Receipt::from(receipt.clone())),
|
||||
_ => {
|
||||
let client = take_weak!(self.client);
|
||||
let receipt = client.transaction_receipt(TransactionID::Hash(hash));
|
||||
to_value(&receipt.map(Receipt::from))
|
||||
|
@ -21,12 +21,11 @@ use jsonrpc_core::*;
|
||||
use ethcore::miner::MinerService;
|
||||
use ethcore::client::MiningBlockChainClient;
|
||||
use util::numbers::*;
|
||||
use util::keys::store::AccountProvider;
|
||||
use ethcore::account_provider::AccountProvider;
|
||||
use v1::helpers::{SigningQueue, ConfirmationsQueue};
|
||||
use v1::traits::EthSigning;
|
||||
use v1::types::{TransactionRequest, Bytes};
|
||||
use v1::impls::{sign_and_dispatch, error_codes};
|
||||
|
||||
use v1::impls::sign_and_dispatch;
|
||||
|
||||
/// Implementation of functions that require signing when no trusted signer is used.
|
||||
pub struct EthSigningQueueClient<M: MinerService> {
|
||||
@ -79,22 +78,20 @@ impl<M: MinerService + 'static> EthSigning for EthSigningQueueClient<M> {
|
||||
}
|
||||
|
||||
/// Implementation of functions that require signing when no trusted signer is used.
|
||||
pub struct EthSigningUnsafeClient<C, A, M> where
|
||||
pub struct EthSigningUnsafeClient<C, M> where
|
||||
C: MiningBlockChainClient,
|
||||
A: AccountProvider,
|
||||
M: MinerService {
|
||||
client: Weak<C>,
|
||||
accounts: Weak<A>,
|
||||
accounts: Weak<AccountProvider>,
|
||||
miner: Weak<M>,
|
||||
}
|
||||
|
||||
impl<C, A, M> EthSigningUnsafeClient<C, A, M> where
|
||||
impl<C, M> EthSigningUnsafeClient<C, M> where
|
||||
C: MiningBlockChainClient,
|
||||
A: AccountProvider,
|
||||
M: MinerService {
|
||||
|
||||
/// Creates new EthClient.
|
||||
pub fn new(client: &Arc<C>, accounts: &Arc<A>, miner: &Arc<M>)
|
||||
pub fn new(client: &Arc<C>, accounts: &Arc<AccountProvider>, miner: &Arc<M>)
|
||||
-> Self {
|
||||
EthSigningUnsafeClient {
|
||||
client: Arc::downgrade(client),
|
||||
@ -104,35 +101,24 @@ impl<C, A, M> EthSigningUnsafeClient<C, A, M> where
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, A, M> EthSigning for EthSigningUnsafeClient<C, A, M> where
|
||||
impl<C, M> EthSigning for EthSigningUnsafeClient<C, M> where
|
||||
C: MiningBlockChainClient + 'static,
|
||||
A: AccountProvider + 'static,
|
||||
M: MinerService + 'static {
|
||||
|
||||
fn sign(&self, params: Params) -> Result<Value, Error> {
|
||||
from_params::<(Address, H256)>(params).and_then(|(addr, msg)| {
|
||||
take_weak!(self.accounts).sign(&addr, &msg)
|
||||
.map(|v| to_value(&v))
|
||||
.unwrap_or_else(|e| Err(account_locked(format!("Error: {:?}", e))))
|
||||
to_value(&take_weak!(self.accounts).sign(addr, msg).unwrap_or(H520::zero()))
|
||||
})
|
||||
}
|
||||
|
||||
fn send_transaction(&self, params: Params) -> Result<Value, Error> {
|
||||
from_params::<(TransactionRequest, )>(params)
|
||||
.and_then(|(request, )| {
|
||||
let accounts = take_weak!(self.accounts);
|
||||
match accounts.account_secret(&request.from) {
|
||||
Ok(secret) => sign_and_dispatch(&*take_weak!(self.client), &*take_weak!(self.miner), request, secret),
|
||||
Err(e) => Err(account_locked(format!("Error: {:?}", e))),
|
||||
let sender = request.from;
|
||||
match sign_and_dispatch(&*take_weak!(self.client), &*take_weak!(self.miner), request, &*take_weak!(self.accounts), sender) {
|
||||
Ok(hash) => to_value(&hash),
|
||||
_ => to_value(&H256::zero()),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn account_locked(data: String) -> Error {
|
||||
Error {
|
||||
code: ErrorCode::ServerError(error_codes::ACCOUNT_LOCKED),
|
||||
message: "Your account is locked. Unlock the account via CLI, personal_unlockAccount or use Trusted Signer.".into(),
|
||||
data: Some(Value::String(data)),
|
||||
}
|
||||
}
|
||||
|
@ -58,6 +58,7 @@ use ethcore::error::Error as EthcoreError;
|
||||
use ethcore::miner::{AccountDetails, MinerService};
|
||||
use ethcore::client::MiningBlockChainClient;
|
||||
use ethcore::transaction::{Action, SignedTransaction, Transaction};
|
||||
use ethcore::account_provider::{AccountProvider, Error as AccountError};
|
||||
use util::numbers::*;
|
||||
use util::rlp::encode;
|
||||
use util::bytes::ToPretty;
|
||||
@ -88,10 +89,7 @@ fn dispatch_transaction<C, M>(client: &C, miner: &M, signed_transaction: SignedT
|
||||
.and_then(|_| to_value(&hash))
|
||||
}
|
||||
|
||||
fn sign_and_dispatch<C, M>(client: &C, miner: &M, request: TransactionRequest, secret: H256) -> Result<Value, Error>
|
||||
where C: MiningBlockChainClient, M: MinerService {
|
||||
|
||||
let signed_transaction = {
|
||||
fn prepare_transaction<C, M>(client: &C, miner: &M, request: TransactionRequest) -> Transaction where C: MiningBlockChainClient, M: MinerService {
|
||||
Transaction {
|
||||
nonce: request.nonce
|
||||
.or_else(|| miner
|
||||
@ -104,13 +102,45 @@ fn sign_and_dispatch<C, M>(client: &C, miner: &M, request: TransactionRequest, s
|
||||
gas_price: request.gas_price.unwrap_or_else(|| miner.sensible_gas_price()),
|
||||
value: request.value.unwrap_or_else(U256::zero),
|
||||
data: request.data.map_or_else(Vec::new, |b| b.to_vec()),
|
||||
}.sign(&secret)
|
||||
}
|
||||
}
|
||||
|
||||
fn unlock_sign_and_dispatch<C, M>(client: &C, miner: &M, request: TransactionRequest, account_provider: &AccountProvider, address: Address, password: String) -> Result<Value, Error>
|
||||
where C: MiningBlockChainClient, M: MinerService {
|
||||
|
||||
let signed_transaction = {
|
||||
let t = prepare_transaction(client, miner, request);
|
||||
let hash = t.hash();
|
||||
let signature = try!(account_provider.sign_with_password(address, password, hash).map_err(signing_error));
|
||||
t.with_signature(signature)
|
||||
};
|
||||
|
||||
trace!(target: "miner", "send_transaction: dispatching tx: {}", encode(&signed_transaction).to_vec().pretty());
|
||||
dispatch_transaction(&*client, &*miner, signed_transaction)
|
||||
}
|
||||
|
||||
fn sign_and_dispatch<C, M>(client: &C, miner: &M, request: TransactionRequest, account_provider: &AccountProvider, address: Address) -> Result<Value, Error>
|
||||
where C: MiningBlockChainClient, M: MinerService {
|
||||
|
||||
let signed_transaction = {
|
||||
let t = prepare_transaction(client, miner, request);
|
||||
let hash = t.hash();
|
||||
let signature = try!(account_provider.sign(address, hash).map_err(signing_error));
|
||||
t.with_signature(signature)
|
||||
};
|
||||
|
||||
trace!(target: "miner", "send_transaction: dispatching tx: {}", encode(&signed_transaction).to_vec().pretty());
|
||||
dispatch_transaction(&*client, &*miner, signed_transaction)
|
||||
}
|
||||
|
||||
fn signing_error(error: AccountError) -> Error {
|
||||
Error {
|
||||
code: ErrorCode::ServerError(error_codes::ACCOUNT_LOCKED),
|
||||
message: "Your account is locked. Unlock the account via CLI, personal_unlockAccount or use Trusted Signer.".into(),
|
||||
data: Some(Value::String(format!("{:?}", error))),
|
||||
}
|
||||
}
|
||||
|
||||
fn transaction_error(error: EthcoreError) -> Error {
|
||||
use ethcore::error::TransactionError::*;
|
||||
|
||||
|
@ -19,25 +19,23 @@ use std::sync::{Arc, Weak};
|
||||
use jsonrpc_core::*;
|
||||
use v1::traits::Personal;
|
||||
use v1::types::TransactionRequest;
|
||||
use v1::impls::sign_and_dispatch;
|
||||
use util::keys::store::AccountProvider;
|
||||
use v1::impls::unlock_sign_and_dispatch;
|
||||
use ethcore::account_provider::AccountProvider;
|
||||
use util::numbers::*;
|
||||
use ethcore::client::MiningBlockChainClient;
|
||||
use ethcore::miner::MinerService;
|
||||
|
||||
/// Account management (personal) rpc implementation.
|
||||
pub struct PersonalClient<A, C, M>
|
||||
where A: AccountProvider, C: MiningBlockChainClient, M: MinerService {
|
||||
accounts: Weak<A>,
|
||||
pub struct PersonalClient<C, M> where C: MiningBlockChainClient, M: MinerService {
|
||||
accounts: Weak<AccountProvider>,
|
||||
client: Weak<C>,
|
||||
miner: Weak<M>,
|
||||
signer_port: Option<u16>,
|
||||
}
|
||||
|
||||
impl<A, C, M> PersonalClient<A, C, M>
|
||||
where A: AccountProvider, C: MiningBlockChainClient, M: MinerService {
|
||||
impl<C, M> PersonalClient<C, M> where C: MiningBlockChainClient, M: MinerService {
|
||||
/// Creates new PersonalClient
|
||||
pub fn new(store: &Arc<A>, client: &Arc<C>, miner: &Arc<M>, signer_port: Option<u16>) -> Self {
|
||||
pub fn new(store: &Arc<AccountProvider>, client: &Arc<C>, miner: &Arc<M>, signer_port: Option<u16>) -> Self {
|
||||
PersonalClient {
|
||||
accounts: Arc::downgrade(store),
|
||||
client: Arc::downgrade(client),
|
||||
@ -47,8 +45,7 @@ impl<A, C, M> PersonalClient<A, C, M>
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: 'static, C: 'static, M: 'static> Personal for PersonalClient<A, C, M>
|
||||
where A: AccountProvider, C: MiningBlockChainClient, M: MinerService {
|
||||
impl<C: 'static, M: 'static> Personal for PersonalClient<C, M> where C: MiningBlockChainClient, M: MinerService {
|
||||
|
||||
fn signer_enabled(&self, _: Params) -> Result<Value, Error> {
|
||||
self.signer_port
|
||||
@ -58,10 +55,7 @@ impl<A: 'static, C: 'static, M: 'static> Personal for PersonalClient<A, C, M>
|
||||
|
||||
fn accounts(&self, _: Params) -> Result<Value, Error> {
|
||||
let store = take_weak!(self.accounts);
|
||||
match store.accounts() {
|
||||
Ok(account_list) => to_value(&account_list),
|
||||
Err(_) => Err(Error::internal_error())
|
||||
}
|
||||
to_value(&store.accounts())
|
||||
}
|
||||
|
||||
fn new_account(&self, params: Params) -> Result<Value, Error> {
|
||||
@ -80,7 +74,7 @@ impl<A: 'static, C: 'static, M: 'static> Personal for PersonalClient<A, C, M>
|
||||
from_params::<(Address, String, u64)>(params).and_then(
|
||||
|(account, account_pass, _)|{
|
||||
let store = take_weak!(self.accounts);
|
||||
match store.unlock_account_temp(&account, &account_pass) {
|
||||
match store.unlock_account_temporarily(account, account_pass) {
|
||||
Ok(_) => Ok(Value::Bool(true)),
|
||||
Err(_) => Ok(Value::Bool(false)),
|
||||
}
|
||||
@ -90,10 +84,12 @@ impl<A: 'static, C: 'static, M: 'static> Personal for PersonalClient<A, C, M>
|
||||
fn sign_and_send_transaction(&self, params: Params) -> Result<Value, Error> {
|
||||
from_params::<(TransactionRequest, String)>(params)
|
||||
.and_then(|(request, password)| {
|
||||
let sender = request.from;
|
||||
let accounts = take_weak!(self.accounts);
|
||||
match accounts.locked_account_secret(&request.from, &password) {
|
||||
Ok(secret) => sign_and_dispatch(&*take_weak!(self.client), &*take_weak!(self.miner), request, secret),
|
||||
Err(_) => to_value(&H256::zero()),
|
||||
|
||||
match unlock_sign_and_dispatch(&*take_weak!(self.client), &*take_weak!(self.miner), request, &*accounts, sender, password) {
|
||||
Ok(hash) => to_value(&hash),
|
||||
_ => to_value(&H256::zero()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -20,27 +20,25 @@ use std::sync::{Arc, Weak};
|
||||
use jsonrpc_core::*;
|
||||
use v1::traits::PersonalSigner;
|
||||
use v1::types::TransactionModification;
|
||||
use v1::impls::sign_and_dispatch;
|
||||
use v1::impls::unlock_sign_and_dispatch;
|
||||
use v1::helpers::{SigningQueue, ConfirmationsQueue};
|
||||
use util::keys::store::AccountProvider;
|
||||
use ethcore::account_provider::AccountProvider;
|
||||
use util::numbers::*;
|
||||
use ethcore::client::MiningBlockChainClient;
|
||||
use ethcore::miner::MinerService;
|
||||
|
||||
/// Transactions confirmation (personal) rpc implementation.
|
||||
pub struct SignerClient<A, C, M>
|
||||
where A: AccountProvider, C: MiningBlockChainClient, M: MinerService {
|
||||
pub struct SignerClient<C, M> where C: MiningBlockChainClient, M: MinerService {
|
||||
queue: Weak<ConfirmationsQueue>,
|
||||
accounts: Weak<A>,
|
||||
accounts: Weak<AccountProvider>,
|
||||
client: Weak<C>,
|
||||
miner: Weak<M>,
|
||||
}
|
||||
|
||||
impl<A: 'static, C: 'static, M: 'static> SignerClient<A, C, M>
|
||||
where A: AccountProvider, C: MiningBlockChainClient, M: MinerService {
|
||||
impl<C: 'static, M: 'static> SignerClient<C, M> where C: MiningBlockChainClient, M: MinerService {
|
||||
|
||||
/// Create new instance of signer client.
|
||||
pub fn new(store: &Arc<A>, client: &Arc<C>, miner: &Arc<M>, queue: &Arc<ConfirmationsQueue>) -> Self {
|
||||
pub fn new(store: &Arc<AccountProvider>, client: &Arc<C>, miner: &Arc<M>, queue: &Arc<ConfirmationsQueue>) -> Self {
|
||||
SignerClient {
|
||||
queue: Arc::downgrade(queue),
|
||||
accounts: Arc::downgrade(store),
|
||||
@ -50,8 +48,7 @@ impl<A: 'static, C: 'static, M: 'static> SignerClient<A, C, M>
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: 'static, C: 'static, M: 'static> PersonalSigner for SignerClient<A, C, M>
|
||||
where A: AccountProvider, C: MiningBlockChainClient, M: MinerService {
|
||||
impl<C: 'static, M: 'static> PersonalSigner for SignerClient<C, M> where C: MiningBlockChainClient, M: MinerService {
|
||||
|
||||
fn transactions_to_confirm(&self, _params: Params) -> Result<Value, Error> {
|
||||
let queue = take_weak!(self.queue);
|
||||
@ -71,13 +68,15 @@ impl<A: 'static, C: 'static, M: 'static> PersonalSigner for SignerClient<A, C, M
|
||||
if let Some(gas_price) = modification.gas_price {
|
||||
request.gas_price = Some(gas_price);
|
||||
}
|
||||
match accounts.locked_account_secret(&request.from, &pass) {
|
||||
Ok(secret) => {
|
||||
let res = sign_and_dispatch(&*client, &*miner, request, secret);
|
||||
queue.request_confirmed(id, res.clone());
|
||||
Some(res)
|
||||
|
||||
let sender = request.from;
|
||||
|
||||
match unlock_sign_and_dispatch(&*client, &*miner, request, &*accounts, sender, pass) {
|
||||
Ok(hash) => {
|
||||
queue.request_confirmed(id, Ok(hash.clone()));
|
||||
Some(to_value(&hash))
|
||||
},
|
||||
Err(_) => None
|
||||
_ => None
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
|
@ -15,7 +15,6 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! rpc integration tests.
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::str::FromStr;
|
||||
|
||||
@ -26,12 +25,11 @@ use ethcore::block::Block;
|
||||
use ethcore::views::BlockView;
|
||||
use ethcore::ethereum;
|
||||
use ethcore::miner::{MinerService, ExternalMiner, Miner};
|
||||
use ethcore::account_provider::AccountProvider;
|
||||
use devtools::RandomTempPath;
|
||||
use util::Hashable;
|
||||
use util::io::IoChannel;
|
||||
use util::hash::{Address, H256};
|
||||
use util::numbers::U256;
|
||||
use util::keys::{AccountProvider, TestAccount, TestAccountProvider};
|
||||
use util::{U256, H256};
|
||||
use jsonrpc_core::IoHandler;
|
||||
use ethjson::blockchain::BlockChain;
|
||||
|
||||
@ -39,11 +37,8 @@ use v1::traits::eth::{Eth, EthSigning};
|
||||
use v1::impls::{EthClient, EthSigningUnsafeClient};
|
||||
use v1::tests::helpers::{TestSyncProvider, Config};
|
||||
|
||||
fn account_provider() -> Arc<TestAccountProvider> {
|
||||
let mut accounts = HashMap::new();
|
||||
accounts.insert(Address::from(1), TestAccount::new("test"));
|
||||
let ap = TestAccountProvider::new(accounts);
|
||||
Arc::new(ap)
|
||||
fn account_provider() -> Arc<AccountProvider> {
|
||||
Arc::new(AccountProvider::transient_provider())
|
||||
}
|
||||
|
||||
fn sync_provider() -> Arc<TestSyncProvider> {
|
||||
@ -70,7 +65,7 @@ fn make_spec(chain: &BlockChain) -> Spec {
|
||||
struct EthTester {
|
||||
client: Arc<Client>,
|
||||
_miner: Arc<MinerService>,
|
||||
accounts: Arc<TestAccountProvider>,
|
||||
accounts: Arc<AccountProvider>,
|
||||
handler: IoHandler,
|
||||
}
|
||||
|
||||
@ -107,7 +102,8 @@ impl EthTester {
|
||||
&sync_provider,
|
||||
&account_provider,
|
||||
&miner_service,
|
||||
&external_miner
|
||||
&external_miner,
|
||||
true
|
||||
);
|
||||
let eth_sign = EthSigningUnsafeClient::new(
|
||||
&client,
|
||||
@ -226,15 +222,10 @@ const TRANSACTION_COUNT_SPEC: &'static [u8] = br#"{
|
||||
fn eth_transaction_count() {
|
||||
use util::crypto::Secret;
|
||||
|
||||
let address = Address::from_str("faa34835af5c2ea724333018a515fbb7d5bc0b33").unwrap();
|
||||
let secret = Secret::from_str("8a283037bb19c4fed7b1c569e40c7dcff366165eb869110a1b11532963eb9cb2").unwrap();
|
||||
|
||||
let tester = EthTester::from_spec_provider(|| Spec::load(TRANSACTION_COUNT_SPEC));
|
||||
tester.accounts.accounts.write().unwrap().insert(address, TestAccount {
|
||||
unlocked: false,
|
||||
password: "123".into(),
|
||||
secret: secret
|
||||
});
|
||||
let address = tester.accounts.insert_account(secret, "").unwrap();
|
||||
tester.accounts.unlock_account_permanently(address, "".into()).unwrap();
|
||||
|
||||
let req_before = r#"{
|
||||
"jsonrpc": "2.0",
|
||||
|
@ -20,7 +20,7 @@ use std::sync::{Arc, RwLock};
|
||||
use jsonrpc_core::IoHandler;
|
||||
use util::hash::{Address, H256, FixedHash};
|
||||
use util::numbers::{Uint, U256};
|
||||
use util::keys::{AccountProvider, TestAccount, TestAccountProvider};
|
||||
use ethcore::account_provider::AccountProvider;
|
||||
use ethcore::client::{TestBlockChainClient, EachBlockWith, Executed, TransactionID};
|
||||
use ethcore::log_entry::{LocalizedLogEntry, LogEntry};
|
||||
use ethcore::receipt::LocalizedReceipt;
|
||||
@ -36,11 +36,8 @@ fn blockchain_client() -> Arc<TestBlockChainClient> {
|
||||
Arc::new(client)
|
||||
}
|
||||
|
||||
fn accounts_provider() -> Arc<TestAccountProvider> {
|
||||
let mut accounts = HashMap::new();
|
||||
accounts.insert(Address::from(1), TestAccount::new("test"));
|
||||
let ap = TestAccountProvider::new(accounts);
|
||||
Arc::new(ap)
|
||||
fn accounts_provider() -> Arc<AccountProvider> {
|
||||
Arc::new(AccountProvider::transient_provider())
|
||||
}
|
||||
|
||||
fn sync_provider() -> Arc<TestSyncProvider> {
|
||||
@ -57,7 +54,7 @@ fn miner_service() -> Arc<TestMinerService> {
|
||||
struct EthTester {
|
||||
pub client: Arc<TestBlockChainClient>,
|
||||
pub sync: Arc<TestSyncProvider>,
|
||||
pub accounts_provider: Arc<TestAccountProvider>,
|
||||
pub accounts_provider: Arc<AccountProvider>,
|
||||
miner: Arc<TestMinerService>,
|
||||
hashrates: Arc<RwLock<HashMap<H256, U256>>>,
|
||||
pub io: IoHandler,
|
||||
@ -71,7 +68,7 @@ impl Default for EthTester {
|
||||
let miner = miner_service();
|
||||
let hashrates = Arc::new(RwLock::new(HashMap::new()));
|
||||
let external_miner = Arc::new(ExternalMiner::new(hashrates.clone()));
|
||||
let eth = EthClient::new(&client, &sync, &ap, &miner, &external_miner).to_delegate();
|
||||
let eth = EthClient::new(&client, &sync, &ap, &miner, &external_miner, true).to_delegate();
|
||||
let sign = EthSigningUnsafeClient::new(&client, &ap, &miner).to_delegate();
|
||||
let io = IoHandler::new();
|
||||
io.add_delegate(eth);
|
||||
@ -169,8 +166,9 @@ fn rpc_eth_sign() {
|
||||
let tester = EthTester::default();
|
||||
|
||||
let account = tester.accounts_provider.new_account("abcd").unwrap();
|
||||
tester.accounts_provider.unlock_account_permanently(account, "abcd".into()).unwrap();
|
||||
let message = H256::from("0x0cc175b9c0f1b6a831c399e26977266192eb5ffee6ae2fec3ad71c777531578f");
|
||||
let signed = tester.accounts_provider.sign(&account, &message).unwrap();
|
||||
let signed = tester.accounts_provider.sign(account, message).unwrap();
|
||||
|
||||
let req = r#"{
|
||||
"jsonrpc": "2.0",
|
||||
@ -233,10 +231,13 @@ fn rpc_eth_gas_price() {
|
||||
|
||||
#[test]
|
||||
fn rpc_eth_accounts() {
|
||||
let request = r#"{"jsonrpc": "2.0", "method": "eth_accounts", "params": [], "id": 1}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","result":["0x0000000000000000000000000000000000000001"],"id":1}"#;
|
||||
let tester = EthTester::default();
|
||||
let address = tester.accounts_provider.new_account("").unwrap();
|
||||
|
||||
assert_eq!(EthTester::default().io.handle_request(request), Some(response.to_owned()));
|
||||
let request = r#"{"jsonrpc": "2.0", "method": "eth_accounts", "params": [], "id": 1}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","result":[""#.to_owned() + &format!("0x{:?}", address) + r#""],"id":1}"#;
|
||||
|
||||
assert_eq!(tester.io.handle_request(request), Some(response.to_owned()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -558,12 +559,9 @@ fn rpc_eth_estimate_gas_default_block() {
|
||||
|
||||
#[test]
|
||||
fn rpc_eth_send_transaction() {
|
||||
let account = TestAccount::new("123");
|
||||
let address = account.address();
|
||||
let secret = account.secret.clone();
|
||||
|
||||
let tester = EthTester::default();
|
||||
tester.accounts_provider.accounts.write().unwrap().insert(address.clone(), account);
|
||||
let address = tester.accounts_provider.new_account("").unwrap();
|
||||
tester.accounts_provider.unlock_account_permanently(address, "".into()).unwrap();
|
||||
let request = r#"{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "eth_sendTransaction",
|
||||
@ -584,7 +582,9 @@ fn rpc_eth_send_transaction() {
|
||||
action: Action::Call(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()),
|
||||
value: U256::from(0x9184e72au64),
|
||||
data: vec![]
|
||||
}.sign(&secret);
|
||||
};
|
||||
let signature = tester.accounts_provider.sign(address, t.hash()).unwrap();
|
||||
let t = t.with_signature(signature);
|
||||
|
||||
let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":1}"#;
|
||||
|
||||
@ -599,7 +599,9 @@ fn rpc_eth_send_transaction() {
|
||||
action: Action::Call(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()),
|
||||
value: U256::from(0x9184e72au64),
|
||||
data: vec![]
|
||||
}.sign(&secret);
|
||||
};
|
||||
let signature = tester.accounts_provider.sign(address, t.hash()).unwrap();
|
||||
let t = t.with_signature(signature);
|
||||
|
||||
let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":1}"#;
|
||||
|
||||
@ -610,7 +612,7 @@ fn rpc_eth_send_transaction() {
|
||||
fn rpc_eth_send_raw_transaction() {
|
||||
let tester = EthTester::default();
|
||||
let address = tester.accounts_provider.new_account("abcd").unwrap();
|
||||
let secret = tester.accounts_provider.account_secret(&address).unwrap();
|
||||
tester.accounts_provider.unlock_account_permanently(address, "abcd".into()).unwrap();
|
||||
|
||||
let t = Transaction {
|
||||
nonce: U256::zero(),
|
||||
@ -619,7 +621,9 @@ fn rpc_eth_send_raw_transaction() {
|
||||
action: Action::Call(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()),
|
||||
value: U256::from(0x9184e72au64),
|
||||
data: vec![]
|
||||
}.sign(&secret);
|
||||
};
|
||||
let signature = tester.accounts_provider.sign(address, t.hash()).unwrap();
|
||||
let t = t.with_signature(signature);
|
||||
|
||||
let rlp = ::util::rlp::encode(&t).to_vec().to_hex();
|
||||
|
||||
|
@ -20,7 +20,7 @@ use v1::impls::EthSigningQueueClient;
|
||||
use v1::traits::EthSigning;
|
||||
use v1::helpers::{ConfirmationsQueue, SigningQueue};
|
||||
use v1::tests::helpers::TestMinerService;
|
||||
use util::keys::TestAccount;
|
||||
use util::{Address, FixedHash};
|
||||
|
||||
struct EthSigningTester {
|
||||
pub queue: Arc<ConfirmationsQueue>,
|
||||
@ -52,8 +52,7 @@ fn eth_signing() -> EthSigningTester {
|
||||
fn should_add_transaction_to_queue() {
|
||||
// given
|
||||
let tester = eth_signing();
|
||||
let account = TestAccount::new("123");
|
||||
let address = account.address();
|
||||
let address = Address::random();
|
||||
assert_eq!(tester.queue.requests().len(), 0);
|
||||
|
||||
// when
|
||||
|
@ -16,17 +16,16 @@
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::str::FromStr;
|
||||
use std::collections::HashMap;
|
||||
use jsonrpc_core::IoHandler;
|
||||
use util::numbers::*;
|
||||
use util::keys::{TestAccount, TestAccountProvider};
|
||||
use ethcore::account_provider::AccountProvider;
|
||||
use v1::{PersonalClient, Personal};
|
||||
use v1::tests::helpers::TestMinerService;
|
||||
use ethcore::client::TestBlockChainClient;
|
||||
use ethcore::transaction::{Action, Transaction};
|
||||
|
||||
struct PersonalTester {
|
||||
accounts: Arc<TestAccountProvider>,
|
||||
accounts: Arc<AccountProvider>,
|
||||
io: IoHandler,
|
||||
miner: Arc<TestMinerService>,
|
||||
// these unused fields are necessary to keep the data alive
|
||||
@ -39,10 +38,8 @@ fn blockchain_client() -> Arc<TestBlockChainClient> {
|
||||
Arc::new(client)
|
||||
}
|
||||
|
||||
fn accounts_provider() -> Arc<TestAccountProvider> {
|
||||
let accounts = HashMap::new();
|
||||
let ap = TestAccountProvider::new(accounts);
|
||||
Arc::new(ap)
|
||||
fn accounts_provider() -> Arc<AccountProvider> {
|
||||
Arc::new(AccountProvider::transient_provider())
|
||||
}
|
||||
|
||||
fn miner_service() -> Arc<TestMinerService> {
|
||||
@ -99,13 +96,9 @@ fn should_return_port_number_if_signer_is_enabled() {
|
||||
#[test]
|
||||
fn accounts() {
|
||||
let tester = setup(None);
|
||||
tester.accounts.accounts
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(Address::from(1), TestAccount::new("test"));
|
||||
|
||||
let address = tester.accounts.new_account("").unwrap();
|
||||
let request = r#"{"jsonrpc": "2.0", "method": "personal_listAccounts", "params": [], "id": 1}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","result":["0x0000000000000000000000000000000000000001"],"id":1}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","result":[""#.to_owned() + &format!("0x{:?}", address) + r#""],"id":1}"#;
|
||||
|
||||
assert_eq!(tester.io.handle_request(request), Some(response.to_owned()));
|
||||
}
|
||||
@ -117,15 +110,9 @@ fn new_account() {
|
||||
|
||||
let res = tester.io.handle_request(request);
|
||||
|
||||
let accounts = tester.accounts.accounts.read().unwrap();
|
||||
let accounts = tester.accounts.accounts();
|
||||
assert_eq!(accounts.len(), 1);
|
||||
|
||||
let address = accounts
|
||||
.keys()
|
||||
.nth(0)
|
||||
.cloned()
|
||||
.unwrap();
|
||||
|
||||
let address = accounts[0];
|
||||
let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"","id":1}"#;
|
||||
|
||||
assert_eq!(res, Some(response));
|
||||
@ -133,11 +120,8 @@ fn new_account() {
|
||||
|
||||
#[test]
|
||||
fn sign_and_send_transaction_with_invalid_password() {
|
||||
let account = TestAccount::new("password123");
|
||||
let address = account.address();
|
||||
|
||||
let tester = setup(None);
|
||||
tester.accounts.accounts.write().unwrap().insert(address.clone(), account);
|
||||
let address = tester.accounts.new_account("password123").unwrap();
|
||||
let request = r#"{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "personal_signAndSendTransaction",
|
||||
@ -158,12 +142,9 @@ fn sign_and_send_transaction_with_invalid_password() {
|
||||
|
||||
#[test]
|
||||
fn sign_and_send_transaction() {
|
||||
let account = TestAccount::new("password123");
|
||||
let address = account.address();
|
||||
let secret = account.secret.clone();
|
||||
|
||||
let tester = setup(None);
|
||||
tester.accounts.accounts.write().unwrap().insert(address.clone(), account);
|
||||
let address = tester.accounts.new_account("password123").unwrap();
|
||||
|
||||
let request = r#"{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "personal_signAndSendTransaction",
|
||||
@ -184,7 +165,10 @@ fn sign_and_send_transaction() {
|
||||
action: Action::Call(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()),
|
||||
value: U256::from(0x9184e72au64),
|
||||
data: vec![]
|
||||
}.sign(&secret);
|
||||
};
|
||||
tester.accounts.unlock_account_temporarily(address, "password123".into()).unwrap();
|
||||
let signature = tester.accounts.sign(address, t.hash()).unwrap();
|
||||
let t = t.with_signature(signature);
|
||||
|
||||
let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":1}"#;
|
||||
|
||||
@ -199,7 +183,10 @@ fn sign_and_send_transaction() {
|
||||
action: Action::Call(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()),
|
||||
value: U256::from(0x9184e72au64),
|
||||
data: vec![]
|
||||
}.sign(&secret);
|
||||
};
|
||||
tester.accounts.unlock_account_temporarily(address, "password123".into()).unwrap();
|
||||
let signature = tester.accounts.sign(address, t.hash()).unwrap();
|
||||
let t = t.with_signature(signature);
|
||||
|
||||
let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":1}"#;
|
||||
|
||||
|
@ -16,10 +16,9 @@
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::str::FromStr;
|
||||
use std::collections::HashMap;
|
||||
use jsonrpc_core::IoHandler;
|
||||
use util::numbers::*;
|
||||
use util::keys::{TestAccount, TestAccountProvider};
|
||||
use ethcore::account_provider::AccountProvider;
|
||||
use ethcore::client::TestBlockChainClient;
|
||||
use ethcore::transaction::{Transaction, Action};
|
||||
use v1::{SignerClient, PersonalSigner};
|
||||
@ -30,7 +29,7 @@ use v1::types::TransactionRequest;
|
||||
|
||||
struct PersonalSignerTester {
|
||||
queue: Arc<ConfirmationsQueue>,
|
||||
accounts: Arc<TestAccountProvider>,
|
||||
accounts: Arc<AccountProvider>,
|
||||
io: IoHandler,
|
||||
miner: Arc<TestMinerService>,
|
||||
// these unused fields are necessary to keep the data alive
|
||||
@ -43,10 +42,8 @@ fn blockchain_client() -> Arc<TestBlockChainClient> {
|
||||
Arc::new(client)
|
||||
}
|
||||
|
||||
fn accounts_provider() -> Arc<TestAccountProvider> {
|
||||
let accounts = HashMap::new();
|
||||
let ap = TestAccountProvider::new(accounts);
|
||||
Arc::new(ap)
|
||||
fn accounts_provider() -> Arc<AccountProvider> {
|
||||
Arc::new(AccountProvider::transient_provider())
|
||||
}
|
||||
|
||||
fn miner_service() -> Arc<TestMinerService> {
|
||||
@ -146,16 +143,10 @@ fn should_not_remove_transaction_if_password_is_invalid() {
|
||||
|
||||
#[test]
|
||||
fn should_confirm_transaction_and_dispatch() {
|
||||
// given
|
||||
//// given
|
||||
let tester = signer_tester();
|
||||
let account = TestAccount::new("test");
|
||||
let address = account.address();
|
||||
let secret = account.secret.clone();
|
||||
let address = tester.accounts.new_account("test").unwrap();
|
||||
let recipient = Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap();
|
||||
tester.accounts.accounts
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(address, account);
|
||||
tester.queue.add_request(TransactionRequest {
|
||||
from: address,
|
||||
to: Some(recipient),
|
||||
@ -165,6 +156,7 @@ fn should_confirm_transaction_and_dispatch() {
|
||||
data: None,
|
||||
nonce: None,
|
||||
});
|
||||
|
||||
let t = Transaction {
|
||||
nonce: U256::zero(),
|
||||
gas_price: U256::from(0x1000),
|
||||
@ -172,7 +164,10 @@ fn should_confirm_transaction_and_dispatch() {
|
||||
action: Action::Call(recipient),
|
||||
value: U256::from(0x1),
|
||||
data: vec![]
|
||||
}.sign(&secret);
|
||||
};
|
||||
tester.accounts.unlock_account_temporarily(address, "test".into()).unwrap();
|
||||
let signature = tester.accounts.sign(address, t.hash()).unwrap();
|
||||
let t = t.with_signature(signature);
|
||||
|
||||
assert_eq!(tester.queue.requests().len(), 1);
|
||||
|
||||
|
@ -460,6 +460,7 @@ impl ChainSync {
|
||||
trace!(target: "sync", "{} Deactivated for invalid headers response", peer_id);
|
||||
self.deactivate_peer(io, peer_id);
|
||||
}
|
||||
|
||||
if headers.is_empty() {
|
||||
// Peer does not have any new subchain heads, deactivate it nd try with another
|
||||
trace!(target: "sync", "{} Deactivated for no data", peer_id);
|
||||
|
@ -18,7 +18,7 @@ rand = "0.3.12"
|
||||
time = "0.1.34"
|
||||
tiny-keccak = "1.0"
|
||||
rocksdb = { git = "https://github.com/ethcore/rust-rocksdb" }
|
||||
lazy_static = "0.1"
|
||||
lazy_static = "0.2"
|
||||
eth-secp256k1 = { git = "https://github.com/ethcore/rust-secp256k1" }
|
||||
rust-crypto = "0.2.34"
|
||||
elastic-array = "0.4"
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,261 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Geth keys import/export tool
|
||||
|
||||
use common::*;
|
||||
use keys::store::SecretStore;
|
||||
use keys::directory::KeyFileContent;
|
||||
use std::path::PathBuf;
|
||||
use path;
|
||||
|
||||
/// Enumerates all geth keys in the directory and returns collection of tuples `(accountId, filename)`
|
||||
pub fn enumerate_geth_keys(path: &Path) -> Result<Vec<(Address, String)>, ImportError> {
|
||||
let mut entries = Vec::new();
|
||||
for entry in try!(fs::read_dir(path)) {
|
||||
let entry = try!(entry);
|
||||
if !try!(fs::metadata(entry.path())).is_dir() {
|
||||
match entry.file_name().to_str() {
|
||||
Some(name) => {
|
||||
let parts: Vec<&str> = name.split("--").collect();
|
||||
if parts.len() != 3 { continue; }
|
||||
let account_id = try!(Address::from_str(parts[2]).map_err(|_| ImportError::Format));
|
||||
entries.push((account_id, name.to_owned()));
|
||||
},
|
||||
None => { continue; }
|
||||
};
|
||||
}
|
||||
}
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
/// Geth import error
|
||||
#[derive(Debug)]
|
||||
pub enum ImportError {
|
||||
/// Io error reading geth file
|
||||
Io(io::Error),
|
||||
/// format error
|
||||
Format,
|
||||
}
|
||||
|
||||
impl From<io::Error> for ImportError {
|
||||
fn from (err: io::Error) -> ImportError {
|
||||
ImportError::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// Imports one geth key to the store
|
||||
pub fn import_geth_key(secret_store: &mut SecretStore, geth_keyfile_path: &Path) -> Result<(), ImportError> {
|
||||
let mut file = try!(fs::File::open(geth_keyfile_path));
|
||||
let mut buf = String::new();
|
||||
try!(file.read_to_string(&mut buf));
|
||||
|
||||
let mut json_result = Json::from_str(&buf);
|
||||
let mut json = match json_result {
|
||||
Ok(ref mut parsed_json) => try!(parsed_json.as_object_mut().ok_or(ImportError::Format)),
|
||||
Err(_) => { return Err(ImportError::Format); }
|
||||
};
|
||||
if let Some(crypto_object) = json.get("Crypto").and_then(|crypto| crypto.as_object()).cloned() {
|
||||
json.insert("crypto".to_owned(), Json::Object(crypto_object));
|
||||
json.remove("Crypto");
|
||||
}
|
||||
match KeyFileContent::load(&Json::Object(json.clone())) {
|
||||
Ok(key_file) => try!(secret_store.import_key(key_file)),
|
||||
Err(_) => { return Err(ImportError::Format); }
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Imports all geth keys in the directory
|
||||
pub fn import_geth_keys(secret_store: &mut SecretStore, geth_keyfiles_directory: &Path) -> Result<usize, ImportError> {
|
||||
use std::path::PathBuf;
|
||||
let geth_files = try!(enumerate_geth_keys(geth_keyfiles_directory));
|
||||
let mut total = 0;
|
||||
for &(ref address, ref file_path) in &geth_files {
|
||||
let mut path = PathBuf::new();
|
||||
path.push(geth_keyfiles_directory);
|
||||
path.push(file_path);
|
||||
if let Err(e) = import_geth_key(secret_store, Path::new(&path)) {
|
||||
warn!("Skipped geth address {}, error importing: {:?}", address, e)
|
||||
}
|
||||
else { total = total + 1}
|
||||
}
|
||||
Ok(total)
|
||||
}
|
||||
|
||||
|
||||
/// Gets the default geth keystore directory.
|
||||
pub fn keystore_dir(is_testnet: bool) -> PathBuf {
|
||||
path::ethereum::with_default(if is_testnet {"testnet/keystore"} else {"keystore"})
|
||||
}
|
||||
|
||||
/// Imports key(s) from provided file/directory
|
||||
pub fn import_keys_path(secret_store: &mut SecretStore, path: &str) -> Result<usize, ImportError> {
|
||||
// check if it is just one file or directory
|
||||
if let Ok(meta) = fs::metadata(path) {
|
||||
if meta.is_file() {
|
||||
try!(import_geth_key(secret_store, Path::new(path)));
|
||||
return Ok(1);
|
||||
}
|
||||
else if meta.is_dir() {
|
||||
return Ok(try!(fs::read_dir(path)).fold(
|
||||
0,
|
||||
|total, p|
|
||||
total +
|
||||
match p {
|
||||
Ok(dir_entry) => import_keys_path(secret_store, dir_entry.path().to_str().unwrap()).unwrap_or_else(|_| 0),
|
||||
Err(e) => { warn!("Error importing dir entry: {:?}", e); 0 },
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
/// Imports all keys from list of provided files/directories
|
||||
pub fn import_keys_paths(secret_store: &mut SecretStore, path: &[String]) -> Result<usize, ImportError> {
|
||||
Ok(path.iter().fold(0, |total, ref p| total + import_keys_path(secret_store, &p).unwrap_or_else(|_| 0)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use common::*;
|
||||
use keys::store::SecretStore;
|
||||
|
||||
fn test_path() -> &'static str {
|
||||
match ::std::fs::metadata("res") {
|
||||
Ok(_) => "res/geth_keystore",
|
||||
Err(_) => "util/res/geth_keystore"
|
||||
}
|
||||
}
|
||||
|
||||
fn pat_path() -> &'static str {
|
||||
match ::std::fs::metadata("res") {
|
||||
Ok(_) => "res/pat",
|
||||
Err(_) => "util/res/pat"
|
||||
}
|
||||
}
|
||||
|
||||
fn test_path_param(param_val: &'static str) -> String {
|
||||
test_path().to_owned() + param_val
|
||||
}
|
||||
|
||||
fn pat_path_param(param_val: &'static str) -> String {
|
||||
pat_path().to_owned() + param_val
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_enumerate() {
|
||||
let keys = enumerate_geth_keys(Path::new(test_path())).unwrap();
|
||||
assert_eq!(3, keys.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_import_geth_old() {
|
||||
let temp = ::devtools::RandomTempPath::create_dir();
|
||||
let mut secret_store = SecretStore::new_in(temp.as_path());
|
||||
import_geth_key(&mut secret_store, Path::new(&test_path_param("/UTC--2016-02-17T09-20-45.721400158Z--3f49624084b67849c7b4e805c5988c21a430f9d9"))).unwrap();
|
||||
let key = secret_store.account(&Address::from_str("3f49624084b67849c7b4e805c5988c21a430f9d9").unwrap());
|
||||
assert!(key.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_import_geth140() {
|
||||
let temp = ::devtools::RandomTempPath::create_dir();
|
||||
let mut secret_store = SecretStore::new_in(temp.as_path());
|
||||
import_geth_key(&mut secret_store, Path::new(&test_path_param("/UTC--2016-04-03T08-58-49.834202900Z--63121b431a52f8043c16fcf0d1df9cb7b5f66649"))).unwrap();
|
||||
let key = secret_store.account(&Address::from_str("63121b431a52f8043c16fcf0d1df9cb7b5f66649").unwrap());
|
||||
assert!(key.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_import_directory() {
|
||||
let temp = ::devtools::RandomTempPath::create_dir();
|
||||
let mut secret_store = SecretStore::new_in(temp.as_path());
|
||||
import_geth_keys(&mut secret_store, Path::new(test_path())).unwrap();
|
||||
|
||||
let key = secret_store.account(&Address::from_str("3f49624084b67849c7b4e805c5988c21a430f9d9").unwrap());
|
||||
assert!(key.is_some());
|
||||
|
||||
let key = secret_store.account(&Address::from_str("5ba4dcf897e97c2bdf8315b9ef26c13c085988cf").unwrap());
|
||||
assert!(key.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn imports_as_scrypt_keys() {
|
||||
use keys::directory::{KeyDirectory, KeyFileKdf};
|
||||
let temp = ::devtools::RandomTempPath::create_dir();
|
||||
{
|
||||
let mut secret_store = SecretStore::new_in(temp.as_path());
|
||||
import_geth_keys(&mut secret_store, Path::new(test_path())).unwrap();
|
||||
}
|
||||
|
||||
let key_directory = KeyDirectory::new(&temp.as_path());
|
||||
let key_file = key_directory.get(&H128::from_str("62a0ad73556d496a8e1c0783d30d3ace").unwrap()).unwrap();
|
||||
|
||||
match key_file.crypto.kdf {
|
||||
KeyFileKdf::Scrypt(scrypt_params) => {
|
||||
assert_eq!(262144, scrypt_params.n);
|
||||
assert_eq!(8, scrypt_params.r);
|
||||
assert_eq!(1, scrypt_params.p);
|
||||
},
|
||||
_ => { panic!("expected kdf params of crypto to be of scrypt type" ); }
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature="heavy-tests")]
|
||||
fn can_decrypt_with_imported() {
|
||||
use keys::store::EncryptedHashMap;
|
||||
|
||||
let temp = ::devtools::RandomTempPath::create_dir();
|
||||
let mut secret_store = SecretStore::new_in(temp.as_path());
|
||||
import_geth_keys(&mut secret_store, Path::new(test_path())).unwrap();
|
||||
|
||||
let val = secret_store.get::<Bytes>(&H128::from_str("62a0ad73556d496a8e1c0783d30d3ace").unwrap(), "123");
|
||||
assert!(val.is_ok());
|
||||
assert_eq!(32, val.unwrap().len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_import_by_filename() {
|
||||
let temp = ::devtools::RandomTempPath::create_dir();
|
||||
let mut secret_store = SecretStore::new_in(temp.as_path());
|
||||
|
||||
let amount = import_keys_path(&mut secret_store, &pat_path_param("/p1.json")).unwrap();
|
||||
assert_eq!(1, amount);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_import_by_dir() {
|
||||
let temp = ::devtools::RandomTempPath::create_dir();
|
||||
let mut secret_store = SecretStore::new_in(temp.as_path());
|
||||
|
||||
let amount = import_keys_path(&mut secret_store, pat_path()).unwrap();
|
||||
assert_eq!(2, amount);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_import_mulitple() {
|
||||
let temp = ::devtools::RandomTempPath::create_dir();
|
||||
let mut secret_store = SecretStore::new_in(temp.as_path());
|
||||
|
||||
let amount = import_keys_paths(&mut secret_store, &[pat_path_param("/p1.json"), pat_path_param("/p2.json")]).unwrap();
|
||||
assert_eq!(2, amount);
|
||||
}
|
||||
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Key management module
|
||||
|
||||
pub mod directory;
|
||||
pub mod store;
|
||||
mod geth_import;
|
||||
mod test_account_provider;
|
||||
|
||||
pub use self::store::AccountProvider;
|
||||
pub use self::test_account_provider::{TestAccount, TestAccountProvider};
|
||||
pub use self::geth_import::import_keys_paths;
|
@ -1,754 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Secret Store
|
||||
|
||||
use keys::directory::*;
|
||||
use common::*;
|
||||
use rcrypto::pbkdf2::*;
|
||||
use rcrypto::scrypt::*;
|
||||
use rcrypto::hmac::*;
|
||||
use crypto;
|
||||
use chrono::*;
|
||||
|
||||
const KEY_LENGTH: u32 = 32;
|
||||
const KEY_ITERATIONS: u32 = 10240;
|
||||
const KEY_LENGTH_AES: u32 = KEY_LENGTH/2;
|
||||
|
||||
const KEY_LENGTH_USIZE: usize = KEY_LENGTH as usize;
|
||||
const KEY_LENGTH_AES_USIZE: usize = KEY_LENGTH_AES as usize;
|
||||
|
||||
// TODO: this file needs repotting into several separate files.
|
||||
|
||||
/// Encrypted hash-map, each request should contain password
|
||||
pub trait EncryptedHashMap<Key: Hash + Eq> {
|
||||
/// Returns existing value for the key, if any
|
||||
fn get<Value: FromRawBytesVariable + BytesConvertable>(&self, key: &Key, password: &str) -> Result<Value, EncryptedHashMapError>;
|
||||
/// Insert new encrypted key-value and returns previous if there was any
|
||||
fn insert<Value: FromRawBytesVariable + BytesConvertable>(&mut self, key: Key, value: Value, password: &str) -> Option<Value>;
|
||||
/// Removes key-value by key and returns the removed one, if any exists and password was provided
|
||||
fn remove<Value: FromRawBytesVariable + BytesConvertable> (&mut self, key: &Key, password: Option<&str>) -> Option<Value>;
|
||||
/// Deletes key-value by key and returns if the key-value existed
|
||||
fn delete(&mut self, key: &Key) -> bool {
|
||||
self.remove::<Bytes>(key, None).is_some()
|
||||
}
|
||||
}
|
||||
|
||||
/// Error retrieving value from encrypted hashmap
|
||||
#[derive(Debug)]
|
||||
pub enum EncryptedHashMapError {
|
||||
/// Encryption failed
|
||||
InvalidPassword,
|
||||
/// No key in the hashmap
|
||||
UnknownIdentifier,
|
||||
/// Stored value is not well formed for the requested type
|
||||
InvalidValueFormat(FromBytesError),
|
||||
}
|
||||
|
||||
/// Error while signing a message
|
||||
#[derive(Debug)]
|
||||
pub enum SigningError {
|
||||
/// Account passed does not exist
|
||||
NoAccount,
|
||||
/// Account passed is not unlocked
|
||||
AccountNotUnlocked,
|
||||
/// Invalid passphrase
|
||||
InvalidPassword,
|
||||
/// Invalid secret in store
|
||||
InvalidSecret
|
||||
}
|
||||
|
||||
/// Represent service for storing encrypted arbitrary data
|
||||
pub struct SecretStore {
|
||||
directory: KeyDirectory,
|
||||
unlocks: RwLock<HashMap<Address, AccountUnlock>>,
|
||||
key_iterations: u32,
|
||||
}
|
||||
|
||||
struct AccountUnlock {
|
||||
secret: H256,
|
||||
/// expiration datetime (None - never)
|
||||
expires: Option<DateTime<UTC>>,
|
||||
/// Sccount should be relocked after first use.
|
||||
relock_on_use: bool,
|
||||
}
|
||||
|
||||
/// Basic account management trait
|
||||
pub trait AccountProvider: Send + Sync {
|
||||
/// Lists all accounts
|
||||
fn accounts(&self) -> Result<Vec<Address>, ::std::io::Error>;
|
||||
/// Unlocks account with the password provided
|
||||
fn unlock_account(&self, account: &Address, pass: &str) -> Result<(), EncryptedHashMapError>;
|
||||
/// Unlocks account with the password provided; relocks it on the next call to `account_secret` or `sign`.
|
||||
fn unlock_account_temp(&self, account: &Address, pass: &str) -> Result<(), EncryptedHashMapError>;
|
||||
/// Creates account
|
||||
fn new_account(&self, pass: &str) -> Result<Address, ::std::io::Error>;
|
||||
/// Returns secret for unlocked `account`.
|
||||
fn account_secret(&self, account: &Address) -> Result<crypto::Secret, SigningError>;
|
||||
/// Returns secret for locked account given passphrase.
|
||||
fn locked_account_secret(&self, account: &Address, pass: &str) -> Result<crypto::Secret, SigningError>;
|
||||
/// Returns signature when unlocked `account` signs `message`.
|
||||
fn sign(&self, account: &Address, message: &H256) -> Result<crypto::Signature, SigningError> {
|
||||
self.account_secret(account).and_then(|s| crypto::ec::sign(&s, message).map_err(|_| SigningError::InvalidSecret))
|
||||
}
|
||||
}
|
||||
|
||||
/// Thread-safe accounts management
|
||||
pub struct AccountService {
|
||||
secret_store: RwLock<SecretStore>,
|
||||
}
|
||||
|
||||
impl AccountProvider for AccountService {
|
||||
/// Lists all accounts
|
||||
fn accounts(&self) -> Result<Vec<Address>, ::std::io::Error> {
|
||||
Ok(try!(self.secret_store.read().unwrap().accounts()).iter().map(|&(addr, _)| addr).collect::<Vec<Address>>())
|
||||
}
|
||||
/// Unlocks account with the password provided
|
||||
fn unlock_account(&self, account: &Address, pass: &str) -> Result<(), EncryptedHashMapError> {
|
||||
self.secret_store.read().unwrap().unlock_account(account, pass)
|
||||
}
|
||||
fn unlock_account_temp(&self, account: &Address, pass: &str) -> Result<(), EncryptedHashMapError> {
|
||||
self.secret_store.read().unwrap().unlock_account_temp(account, pass)
|
||||
}
|
||||
/// Creates account
|
||||
fn new_account(&self, pass: &str) -> Result<Address, ::std::io::Error> {
|
||||
self.secret_store.write().unwrap().new_account(pass)
|
||||
}
|
||||
/// Returns secret for unlocked account
|
||||
fn account_secret(&self, account: &Address) -> Result<crypto::Secret, SigningError> {
|
||||
self.secret_store.read().unwrap().account_secret(account)
|
||||
}
|
||||
/// Returns secret for locked account given passphrase.
|
||||
fn locked_account_secret(&self, account: &Address, pass: &str) -> Result<crypto::Secret, SigningError> {
|
||||
self.secret_store.read().unwrap().locked_account_secret(account, pass)
|
||||
}
|
||||
/// Signs a message using key of given unlocked account address.
|
||||
fn sign(&self, account: &Address, message: &H256) -> Result<crypto::Signature, SigningError> {
|
||||
self.secret_store.read().unwrap().sign(account, message)
|
||||
}
|
||||
}
|
||||
|
||||
/// Which set of keys to import.
|
||||
#[derive(PartialEq)]
|
||||
pub enum ImportKeySet {
|
||||
/// Empty set.
|
||||
None,
|
||||
/// Import legacy client's general keys.
|
||||
Legacy,
|
||||
/// Import legacy client's testnet keys.
|
||||
LegacyTestnet,
|
||||
}
|
||||
|
||||
impl AccountService {
|
||||
/// New account service with the keys store in specific location and configured security parameters.
|
||||
pub fn with_security(path: &Path, key_iterations: u32, import_keys: ImportKeySet) -> Self {
|
||||
let secret_store = RwLock::new(SecretStore::with_security(path, key_iterations));
|
||||
match import_keys {
|
||||
ImportKeySet::None => {}
|
||||
_ => { secret_store.write().unwrap().try_import_existing(import_keys == ImportKeySet::LegacyTestnet); }
|
||||
}
|
||||
AccountService {
|
||||
secret_store: secret_store,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn new_test(temp: &::devtools::RandomTempPath) -> Self {
|
||||
let secret_store = RwLock::new(SecretStore::new_test(temp));
|
||||
AccountService {
|
||||
secret_store: secret_store
|
||||
}
|
||||
}
|
||||
|
||||
/// Ticks the account service
|
||||
pub fn tick(&self) {
|
||||
self.secret_store.write().unwrap().collect_garbage();
|
||||
}
|
||||
|
||||
/// Unlocks account for use (no expiration of unlock)
|
||||
pub fn unlock_account_no_expire(&self, account: &Address, pass: &str) -> Result<(), EncryptedHashMapError> {
|
||||
self.secret_store.write().unwrap().unlock_account_with_expiration(account, pass, None, false)
|
||||
}
|
||||
}
|
||||
|
||||
impl SecretStore {
|
||||
/// new instance of Secret Store in specific directory
|
||||
pub fn new_in(path: &Path) -> Self {
|
||||
SecretStore::with_security(path, KEY_ITERATIONS)
|
||||
}
|
||||
|
||||
/// new instance of Secret Store in specific directory and configured security parameters
|
||||
pub fn with_security(path: &Path, key_iterations: u32) -> Self {
|
||||
::std::fs::create_dir_all(&path).expect("Cannot access requested key directory - critical");
|
||||
SecretStore {
|
||||
directory: KeyDirectory::new(path),
|
||||
unlocks: RwLock::new(HashMap::new()),
|
||||
key_iterations: key_iterations,
|
||||
}
|
||||
}
|
||||
|
||||
/// trys to import keys in the known locations
|
||||
pub fn try_import_existing(&mut self, is_testnet: bool) {
|
||||
use keys::geth_import;
|
||||
|
||||
let import_path = geth_import::keystore_dir(is_testnet);
|
||||
if let Err(e) = geth_import::import_geth_keys(self, &import_path) {
|
||||
trace!(target: "sstore", "Geth key not imported: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
/// Lists all accounts and corresponding key ids
|
||||
pub fn accounts(&self) -> Result<Vec<(Address, H128)>, ::std::io::Error> {
|
||||
let accounts = try!(self.directory.list()).iter().map(|key_id| self.directory.get(key_id))
|
||||
.filter(|key| key.is_some())
|
||||
.map(|key| { let some_key = key.unwrap(); (some_key.account, some_key.id) })
|
||||
.filter(|&(ref account, _)| account.is_some())
|
||||
.map(|(account, id)| (account.unwrap(), id))
|
||||
.collect::<Vec<(Address, H128)>>();
|
||||
Ok(accounts)
|
||||
}
|
||||
|
||||
/// Resolves key_id by account address
|
||||
pub fn account(&self, account: &Address) -> Option<H128> {
|
||||
let mut accounts = match self.accounts() {
|
||||
Ok(accounts) => accounts,
|
||||
Err(e) => { warn!(target: "sstore", "Failed to load accounts: {}", e); return None; }
|
||||
};
|
||||
accounts.retain(|&(ref store_account, _)| account == store_account);
|
||||
accounts.first().and_then(|&(_, ref key_id)| Some(key_id.clone()))
|
||||
}
|
||||
|
||||
/// Imports pregenerated key, returns error if not saved correctly
|
||||
pub fn import_key(&mut self, key_file: KeyFileContent) -> Result<(), ::std::io::Error> {
|
||||
try!(self.directory.save(key_file));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn new_test(path: &::devtools::RandomTempPath) -> SecretStore {
|
||||
SecretStore {
|
||||
directory: KeyDirectory::new(path.as_path()),
|
||||
unlocks: RwLock::new(HashMap::new()),
|
||||
key_iterations: KEY_ITERATIONS,
|
||||
}
|
||||
}
|
||||
|
||||
/// Unlocks account for use
|
||||
pub fn unlock_account(&self, account: &Address, pass: &str) -> Result<(), EncryptedHashMapError> {
|
||||
self.unlock_account_with_expiration(account, pass, Some(UTC::now() + Duration::minutes(20)), false)
|
||||
}
|
||||
|
||||
/// Unlocks account for use (no expiration of unlock)
|
||||
pub fn unlock_account_no_expire(&self, account: &Address, pass: &str) -> Result<(), EncryptedHashMapError> {
|
||||
self.unlock_account_with_expiration(account, pass, None, false)
|
||||
}
|
||||
|
||||
/// Unlocks account for use (no expiration of unlock)
|
||||
pub fn unlock_account_temp(&self, account: &Address, pass: &str) -> Result<(), EncryptedHashMapError> {
|
||||
self.unlock_account_with_expiration(account, pass, None, true)
|
||||
}
|
||||
|
||||
fn unlock_account_with_expiration(&self, account: &Address, pass: &str, expiration: Option<DateTime<UTC>>, relock_on_use: bool) -> Result<(), EncryptedHashMapError> {
|
||||
let secret_id = try!(self.account(&account).ok_or(EncryptedHashMapError::UnknownIdentifier));
|
||||
let secret = try!(self.get(&secret_id, pass));
|
||||
{
|
||||
let mut write_lock = self.unlocks.write().unwrap();
|
||||
let mut unlock = write_lock.entry(*account)
|
||||
.or_insert_with(|| AccountUnlock { secret: secret, expires: Some(UTC::now()), relock_on_use: relock_on_use });
|
||||
unlock.secret = secret;
|
||||
unlock.expires = expiration;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Creates new account
|
||||
pub fn new_account(&mut self, pass: &str) -> Result<Address, ::std::io::Error> {
|
||||
let key_pair = crypto::KeyPair::create().expect("Error creating key-pair. Something wrong with crypto libraries?");
|
||||
let address = Address::from(key_pair.public().sha3());
|
||||
let key_id = H128::random();
|
||||
self.insert(key_id.clone(), key_pair.secret().clone(), pass);
|
||||
|
||||
let mut key_file = self.directory.get(&key_id).expect("the key was just inserted");
|
||||
key_file.account = Some(address);
|
||||
try!(self.directory.save(key_file));
|
||||
Ok(address)
|
||||
}
|
||||
|
||||
/// Signs message with unlocked account.
|
||||
pub fn sign(&self, account: &Address, message: &H256) -> Result<crypto::Signature, SigningError> {
|
||||
let (relock, ret) = {
|
||||
let read_lock = self.unlocks.read().unwrap();
|
||||
if let Some(unlock) = read_lock.get(account) {
|
||||
(unlock.relock_on_use, match crypto::KeyPair::from_secret(unlock.secret) {
|
||||
Ok(pair) => match pair.sign(message) {
|
||||
Ok(signature) => Ok(signature),
|
||||
Err(_) => Err(SigningError::InvalidSecret)
|
||||
},
|
||||
Err(_) => Err(SigningError::InvalidSecret)
|
||||
})
|
||||
} else {
|
||||
(false, Err(SigningError::AccountNotUnlocked))
|
||||
}
|
||||
};
|
||||
if relock {
|
||||
self.unlocks.write().unwrap().remove(account);
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
/// Returns secret for unlocked account.
|
||||
pub fn account_secret(&self, account: &Address) -> Result<crypto::Secret, SigningError> {
|
||||
let (relock, ret) = {
|
||||
let read_lock = self.unlocks.read().unwrap();
|
||||
if let Some(unlock) = read_lock.get(account) {
|
||||
(unlock.relock_on_use, Ok(unlock.secret as crypto::Secret))
|
||||
} else {
|
||||
(false, Err(SigningError::AccountNotUnlocked))
|
||||
}
|
||||
};
|
||||
if relock {
|
||||
self.unlocks.write().unwrap().remove(account);
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
/// Returns secret for locked account.
|
||||
pub fn locked_account_secret(&self, account: &Address, pass: &str) -> Result<crypto::Secret, SigningError> {
|
||||
let secret_id = try!(self.account(&account).ok_or(SigningError::NoAccount));
|
||||
self.get(&secret_id, pass).or_else(|e| Err(match e {
|
||||
EncryptedHashMapError::InvalidPassword => SigningError::InvalidPassword,
|
||||
EncryptedHashMapError::UnknownIdentifier => SigningError::NoAccount,
|
||||
EncryptedHashMapError::InvalidValueFormat(_) => SigningError::InvalidSecret,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Makes account unlocks expire and removes unused key files from memory
|
||||
pub fn collect_garbage(&mut self) {
|
||||
let mut garbage_lock = self.unlocks.write().unwrap();
|
||||
self.directory.collect_garbage();
|
||||
let utc = UTC::now();
|
||||
let expired_addresses = garbage_lock.iter()
|
||||
.filter(|&(_, unlock)| match unlock.expires { Some(ref expire_val) => expire_val < &utc, _ => false })
|
||||
.map(|(address, _)| address.clone()).collect::<Vec<Address>>();
|
||||
|
||||
for expired in expired_addresses { garbage_lock.remove(&expired); }
|
||||
|
||||
garbage_lock.shrink_to_fit();
|
||||
}
|
||||
|
||||
fn exists(&self, key: &H128) -> bool {
|
||||
self.directory.exists(key)
|
||||
}
|
||||
}
|
||||
|
||||
fn derive_key_iterations(password: &str, salt: &H256, c: u32) -> (Bytes, Bytes) {
|
||||
let mut h_mac = Hmac::new(::rcrypto::sha2::Sha256::new(), password.as_bytes());
|
||||
let mut derived_key = vec![0u8; KEY_LENGTH_USIZE];
|
||||
pbkdf2(&mut h_mac, &salt.as_slice(), c, &mut derived_key);
|
||||
let derived_right_bits = &derived_key[0..KEY_LENGTH_AES_USIZE];
|
||||
let derived_left_bits = &derived_key[KEY_LENGTH_AES_USIZE..KEY_LENGTH_USIZE];
|
||||
(derived_right_bits.to_vec(), derived_left_bits.to_vec())
|
||||
}
|
||||
|
||||
fn derive_key(password: &str, salt: &H256, iterations: u32) -> (Bytes, Bytes) {
|
||||
derive_key_iterations(password, salt, iterations)
|
||||
}
|
||||
|
||||
fn derive_key_scrypt(password: &str, salt: &H256, n: u32, p: u32, r: u32) -> (Bytes, Bytes) {
|
||||
let mut derived_key = vec![0u8; KEY_LENGTH_USIZE];
|
||||
let scrypt_params = ScryptParams::new(n.trailing_zeros() as u8, r, p);
|
||||
scrypt(password.as_bytes(), &salt.as_slice(), &scrypt_params, &mut derived_key);
|
||||
let derived_right_bits = &derived_key[0..KEY_LENGTH_AES_USIZE];
|
||||
let derived_left_bits = &derived_key[KEY_LENGTH_AES_USIZE..KEY_LENGTH_USIZE];
|
||||
(derived_right_bits.to_vec(), derived_left_bits.to_vec())
|
||||
}
|
||||
|
||||
fn derive_mac(derived_left_bits: &[u8], cipher_text: &[u8]) -> Bytes {
|
||||
let mut mac = vec![0u8; KEY_LENGTH_AES_USIZE + cipher_text.len()];
|
||||
mac[0..KEY_LENGTH_AES_USIZE].clone_from_slice(derived_left_bits);
|
||||
mac[KEY_LENGTH_AES_USIZE..cipher_text.len()+KEY_LENGTH_AES_USIZE].clone_from_slice(cipher_text);
|
||||
mac
|
||||
}
|
||||
|
||||
impl EncryptedHashMap<H128> for SecretStore {
|
||||
fn get<Value: FromRawBytesVariable + BytesConvertable>(&self, key: &H128, password: &str) -> Result<Value, EncryptedHashMapError> {
|
||||
match self.directory.get(key) {
|
||||
Some(key_file) => {
|
||||
let (derived_left_bits, derived_right_bits) = match key_file.crypto.kdf {
|
||||
KeyFileKdf::Pbkdf2(ref params) => derive_key_iterations(password, ¶ms.salt, params.c),
|
||||
KeyFileKdf::Scrypt(ref params) => derive_key_scrypt(password, ¶ms.salt, params.n, params.p, params.r)
|
||||
};
|
||||
|
||||
if derive_mac(&derived_right_bits, &key_file.crypto.cipher_text)
|
||||
.sha3() != key_file.crypto.mac { return Err(EncryptedHashMapError::InvalidPassword); }
|
||||
|
||||
let mut val = vec![0u8; key_file.crypto.cipher_text.len()];
|
||||
match key_file.crypto.cipher_type {
|
||||
CryptoCipherType::Aes128Ctr(ref iv) => {
|
||||
crypto::aes::decrypt(&derived_left_bits, &iv.as_slice(), &key_file.crypto.cipher_text, &mut val);
|
||||
}
|
||||
};
|
||||
|
||||
match Value::from_bytes_variable(&val) {
|
||||
Ok(value) => Ok(value),
|
||||
Err(bytes_error) => Err(EncryptedHashMapError::InvalidValueFormat(bytes_error))
|
||||
}
|
||||
},
|
||||
None => Err(EncryptedHashMapError::UnknownIdentifier)
|
||||
}
|
||||
}
|
||||
|
||||
fn insert<Value: FromRawBytesVariable + BytesConvertable>(&mut self, key: H128, value: Value, password: &str) -> Option<Value> {
|
||||
let previous = if !self.exists(&key) { None } else { self.get(&key, password).ok() };
|
||||
|
||||
// crypto random initiators
|
||||
let salt = H256::random();
|
||||
let iv = H128::random();
|
||||
|
||||
// two parts of derived key
|
||||
// DK = [ DK[0..15] DK[16..31] ] = [derived_left_bits, derived_right_bits]
|
||||
let (derived_left_bits, derived_right_bits) = derive_key(password, &salt, self.key_iterations);
|
||||
|
||||
let mut cipher_text = vec![0u8; value.as_slice().len()];
|
||||
// aes-128-ctr with initial vector of iv
|
||||
crypto::aes::encrypt(&derived_left_bits, &iv.clone(), &value.as_slice(), &mut cipher_text);
|
||||
|
||||
// KECCAK(DK[16..31] ++ <ciphertext>), where DK[16..31] - derived_right_bits
|
||||
let mac = derive_mac(&derived_right_bits, &cipher_text.clone()).sha3();
|
||||
|
||||
let mut key_file = KeyFileContent::new(
|
||||
KeyFileCrypto::new_pbkdf2(
|
||||
cipher_text,
|
||||
iv,
|
||||
salt,
|
||||
mac,
|
||||
self.key_iterations,
|
||||
KEY_LENGTH));
|
||||
key_file.id = key;
|
||||
if let Err(io_error) = self.directory.save(key_file) {
|
||||
warn!("Error saving key file: {:?}", io_error);
|
||||
}
|
||||
previous
|
||||
}
|
||||
|
||||
fn remove<Value: FromRawBytesVariable + BytesConvertable>(&mut self, key: &H128, password: Option<&str>) -> Option<Value> {
|
||||
let previous = if let Some(pass) = password {
|
||||
if let Ok(previous_value) = self.get(&key, pass) { Some(previous_value) } else { None }
|
||||
}
|
||||
else { None };
|
||||
|
||||
if let Err(io_error) = self.directory.delete(key) {
|
||||
warn!("Error saving key file: {:?}", io_error);
|
||||
}
|
||||
previous
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature="heavy-tests"))]
|
||||
mod vector_tests {
|
||||
use super::{derive_mac,derive_key_iterations};
|
||||
use common::*;
|
||||
|
||||
#[test]
|
||||
fn mac_vector() {
|
||||
let password = "testpassword";
|
||||
let salt = H256::from_str("ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd").unwrap();
|
||||
let cipher_text = FromHex::from_hex("5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46").unwrap();
|
||||
let iterations = 262144u32;
|
||||
|
||||
let (derived_left_bits, derived_right_bits) = derive_key_iterations(password, &salt, iterations);
|
||||
assert_eq!("f06d69cdc7da0faffb1008270bca38f5", derived_left_bits.to_hex());
|
||||
assert_eq!("e31891a3a773950e6d0fea48a7188551", derived_right_bits.to_hex());
|
||||
|
||||
let mac_body = derive_mac(&derived_right_bits, &cipher_text);
|
||||
assert_eq!("e31891a3a773950e6d0fea48a71885515318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46", mac_body.to_hex());
|
||||
|
||||
let mac = mac_body.sha3();
|
||||
assert_eq!("517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2", format!("{:?}", mac));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use devtools::*;
|
||||
use common::*;
|
||||
use crypto::KeyPair;
|
||||
use chrono::*;
|
||||
|
||||
#[test]
|
||||
fn can_insert() {
|
||||
let temp = RandomTempPath::create_dir();
|
||||
let mut sstore = SecretStore::new_test(&temp);
|
||||
|
||||
let id = H128::random();
|
||||
sstore.insert(id.clone(), "Cat".to_owned(), "pass");
|
||||
|
||||
assert!(sstore.get::<String>(&id, "pass").is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_get_fail() {
|
||||
let temp = RandomTempPath::create_dir();
|
||||
{
|
||||
use keys::directory::{KeyFileContent, KeyFileCrypto};
|
||||
let mut write_sstore = SecretStore::new_test(&temp);
|
||||
write_sstore.directory.save(
|
||||
KeyFileContent::new(
|
||||
KeyFileCrypto::new_pbkdf2(
|
||||
FromHex::from_hex("5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46").unwrap(),
|
||||
H128::from_str("6087dab2f9fdbbfaddc31a909735c1e6").unwrap(),
|
||||
H256::from_str("ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd").unwrap(),
|
||||
H256::from_str("517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2").unwrap(),
|
||||
262144,
|
||||
32)))
|
||||
.unwrap();
|
||||
}
|
||||
let sstore = SecretStore::new_test(&temp);
|
||||
if let Ok(_) = sstore.get::<Bytes>(&H128::from_str("3198bc9c66725ab3d9954942343ae5b6").unwrap(), "testpassword") {
|
||||
panic!("should be error loading key, we requested the wrong key");
|
||||
}
|
||||
}
|
||||
|
||||
fn pregenerate_keys(temp: &RandomTempPath, count: usize) -> Vec<H128> {
|
||||
use keys::directory::{KeyFileContent, KeyFileCrypto};
|
||||
let mut write_sstore = SecretStore::new_test(&temp);
|
||||
let mut result = Vec::new();
|
||||
for _ in 0..count {
|
||||
result.push(write_sstore.directory.save(
|
||||
KeyFileContent::new(
|
||||
KeyFileCrypto::new_pbkdf2(
|
||||
FromHex::from_hex("5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46").unwrap(),
|
||||
H128::from_str("6087dab2f9fdbbfaddc31a909735c1e6").unwrap(),
|
||||
H256::from_str("ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd").unwrap(),
|
||||
H256::from_str("517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2").unwrap(),
|
||||
262144,
|
||||
32)))
|
||||
.unwrap());
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn pregenerate_accounts(temp: &RandomTempPath, count: usize) -> Vec<H128> {
|
||||
use keys::directory::{KeyFileContent, KeyFileCrypto};
|
||||
let mut write_sstore = SecretStore::new_test(&temp);
|
||||
let mut result = Vec::new();
|
||||
for i in 0..count {
|
||||
let mut key_file =
|
||||
KeyFileContent::new(
|
||||
KeyFileCrypto::new_pbkdf2(
|
||||
FromHex::from_hex("5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46").unwrap(),
|
||||
H128::from_str("6087dab2f9fdbbfaddc31a909735c1e6").unwrap(),
|
||||
H256::from_str("ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd").unwrap(),
|
||||
H256::from_str("517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2").unwrap(),
|
||||
262144,
|
||||
32));
|
||||
key_file.account = Some((i as u64).into());
|
||||
result.push(key_file.id.clone());
|
||||
write_sstore.import_key(key_file).unwrap();
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature="heavy-tests")]
|
||||
fn can_get() {
|
||||
let temp = RandomTempPath::create_dir();
|
||||
let key_id = {
|
||||
use keys::directory::{KeyFileContent, KeyFileCrypto};
|
||||
let mut write_sstore = SecretStore::new_test(&temp);
|
||||
write_sstore.directory.save(
|
||||
KeyFileContent::new(
|
||||
KeyFileCrypto::new_pbkdf2(
|
||||
FromHex::from_hex("5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46").unwrap(),
|
||||
H128::from_str("6087dab2f9fdbbfaddc31a909735c1e6").unwrap(),
|
||||
H256::from_str("ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd").unwrap(),
|
||||
H256::from_str("517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2").unwrap(),
|
||||
262144,
|
||||
32)))
|
||||
.unwrap()
|
||||
};
|
||||
let sstore = SecretStore::new_test(&temp);
|
||||
if let Err(e) = sstore.get::<Bytes>(&key_id, "testpassword") {
|
||||
panic!("got no key: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_delete() {
|
||||
let temp = RandomTempPath::create_dir();
|
||||
let keys = pregenerate_keys(&temp, 5);
|
||||
|
||||
let mut sstore = SecretStore::new_test(&temp);
|
||||
sstore.delete(&keys[2]);
|
||||
|
||||
assert_eq!(4, sstore.directory.list().unwrap().len())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_create_account() {
|
||||
let temp = RandomTempPath::create_dir();
|
||||
let mut sstore = SecretStore::new_test(&temp);
|
||||
sstore.new_account("123").unwrap();
|
||||
assert_eq!(1, sstore.accounts().unwrap().len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_unlock_account() {
|
||||
let temp = RandomTempPath::create_dir();
|
||||
let mut sstore = SecretStore::new_test(&temp);
|
||||
let address = sstore.new_account("123").unwrap();
|
||||
|
||||
let secret = sstore.unlock_account(&address, "123");
|
||||
assert!(secret.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_sign_data() {
|
||||
let temp = RandomTempPath::create_dir();
|
||||
let address = {
|
||||
let mut sstore = SecretStore::new_test(&temp);
|
||||
sstore.new_account("334").unwrap()
|
||||
};
|
||||
let signature = {
|
||||
let sstore = SecretStore::new_test(&temp);
|
||||
sstore.unlock_account(&address, "334").unwrap();
|
||||
sstore.sign(&address, &H256::random()).unwrap()
|
||||
};
|
||||
|
||||
assert!(signature != 0.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_relock_temp_account() {
|
||||
let temp = RandomTempPath::create_dir();
|
||||
let address = {
|
||||
let mut sstore = SecretStore::new_test(&temp);
|
||||
sstore.new_account("334").unwrap()
|
||||
};
|
||||
let signature = {
|
||||
let sstore = SecretStore::new_test(&temp);
|
||||
sstore.unlock_account_temp(&address, "334").unwrap();
|
||||
sstore.sign(&address, &H256::random()).unwrap();
|
||||
sstore.sign(&address, &H256::random())
|
||||
};
|
||||
assert!(signature.is_err());
|
||||
|
||||
let secret = {
|
||||
let sstore = SecretStore::new_test(&temp);
|
||||
sstore.unlock_account_temp(&address, "334").unwrap();
|
||||
sstore.account_secret(&address).unwrap();
|
||||
sstore.account_secret(&address)
|
||||
};
|
||||
assert!(secret.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_import_account() {
|
||||
use keys::directory::{KeyFileContent, KeyFileCrypto};
|
||||
let temp = RandomTempPath::create_dir();
|
||||
let mut key_file =
|
||||
KeyFileContent::new(
|
||||
KeyFileCrypto::new_pbkdf2(
|
||||
FromHex::from_hex("5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46").unwrap(),
|
||||
H128::from_str("6087dab2f9fdbbfaddc31a909735c1e6").unwrap(),
|
||||
H256::from_str("ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd").unwrap(),
|
||||
H256::from_str("517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2").unwrap(),
|
||||
262144,
|
||||
32));
|
||||
key_file.account = Some(Address::from_str("3f49624084b67849c7b4e805c5988c21a430f9d9").unwrap());
|
||||
|
||||
let mut sstore = SecretStore::new_test(&temp);
|
||||
|
||||
sstore.import_key(key_file).unwrap();
|
||||
|
||||
assert_eq!(1, sstore.accounts().unwrap().len());
|
||||
assert!(sstore.account(&Address::from_str("3f49624084b67849c7b4e805c5988c21a430f9d9").unwrap()).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_list_accounts() {
|
||||
let temp = RandomTempPath::create_dir();
|
||||
pregenerate_accounts(&temp, 30);
|
||||
let sstore = SecretStore::new_test(&temp);
|
||||
let accounts = sstore.accounts().unwrap();
|
||||
assert_eq!(30, accounts.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_generated_addresses() {
|
||||
let temp = RandomTempPath::create_dir();
|
||||
let mut sstore = SecretStore::new_test(&temp);
|
||||
let addr = sstore.new_account("test").unwrap();
|
||||
sstore.unlock_account(&addr, "test").unwrap();
|
||||
let secret = sstore.account_secret(&addr).unwrap();
|
||||
let kp = KeyPair::from_secret(secret).unwrap();
|
||||
assert_eq!(Address::from(kp.public().sha3()), addr);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn secret_for_locked_account() {
|
||||
// given
|
||||
let temp = RandomTempPath::create_dir();
|
||||
let mut sstore = SecretStore::new_test(&temp);
|
||||
let addr = sstore.new_account("test-pass").unwrap();
|
||||
|
||||
// when
|
||||
// Invalid pass
|
||||
let secret1 = sstore.locked_account_secret(&addr, "test-pass123");
|
||||
// Valid pass
|
||||
let secret2 = sstore.locked_account_secret(&addr, "test-pass");
|
||||
// Account not unlocked
|
||||
let secret3 = sstore.account_secret(&addr);
|
||||
|
||||
|
||||
assert!(secret1.is_err(), "Invalid password should not return secret.");
|
||||
assert!(secret2.is_ok(), "Should return secret provided valid passphrase.");
|
||||
assert!(secret3.is_err(), "Account should still be locked.");
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn can_create_service() {
|
||||
let temp = RandomTempPath::create_dir();
|
||||
let svc = AccountService::new_test(&temp);
|
||||
assert!(svc.accounts().unwrap().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accounts_expire() {
|
||||
use std::collections::hash_map::*;
|
||||
|
||||
let temp = RandomTempPath::create_dir();
|
||||
let svc = AccountService::new_test(&temp);
|
||||
let address = svc.new_account("pass").unwrap();
|
||||
svc.unlock_account(&address, "pass").unwrap();
|
||||
assert!(svc.account_secret(&address).is_ok());
|
||||
{
|
||||
let ss_rw = svc.secret_store.write().unwrap();
|
||||
let mut ua_rw = ss_rw.unlocks.write().unwrap();
|
||||
let entry = ua_rw.entry(address);
|
||||
if let Entry::Occupied(mut occupied) = entry { occupied.get_mut().expires = Some(UTC::now() - Duration::minutes(1)) }
|
||||
}
|
||||
|
||||
svc.tick();
|
||||
|
||||
assert!(svc.account_secret(&address).is_err());
|
||||
}
|
||||
}
|
@ -1,118 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Test implementation of account provider.
|
||||
|
||||
use std::sync::RwLock;
|
||||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
use hash::Address;
|
||||
use crypto::{Secret, KeyPair};
|
||||
use super::store::{AccountProvider, SigningError, EncryptedHashMapError};
|
||||
|
||||
/// Account mock.
|
||||
#[derive(Clone)]
|
||||
pub struct TestAccount {
|
||||
/// True if account is unlocked.
|
||||
pub unlocked: bool,
|
||||
/// Account's password.
|
||||
pub password: String,
|
||||
/// Account's secret.
|
||||
pub secret: Secret,
|
||||
}
|
||||
|
||||
impl TestAccount {
|
||||
/// Creates new test account.
|
||||
pub fn new(password: &str) -> Self {
|
||||
let pair = KeyPair::create().unwrap();
|
||||
TestAccount {
|
||||
unlocked: false,
|
||||
password: password.to_owned(),
|
||||
secret: pair.secret().clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns account address.
|
||||
pub fn address(&self) -> Address {
|
||||
KeyPair::from_secret(self.secret.clone()).unwrap().address()
|
||||
}
|
||||
}
|
||||
|
||||
/// Test account provider.
|
||||
pub struct TestAccountProvider {
|
||||
/// Test provider accounts.
|
||||
pub accounts: RwLock<HashMap<Address, TestAccount>>,
|
||||
}
|
||||
|
||||
impl TestAccountProvider {
|
||||
/// Basic constructor.
|
||||
pub fn new(accounts: HashMap<Address, TestAccount>) -> Self {
|
||||
TestAccountProvider {
|
||||
accounts: RwLock::new(accounts),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AccountProvider for TestAccountProvider {
|
||||
fn accounts(&self) -> Result<Vec<Address>, io::Error> {
|
||||
Ok(self.accounts.read().unwrap().keys().cloned().collect())
|
||||
}
|
||||
|
||||
fn unlock_account(&self, account: &Address, pass: &str) -> Result<(), EncryptedHashMapError> {
|
||||
match self.accounts.write().unwrap().get_mut(account) {
|
||||
Some(ref mut acc) if acc.password == pass => {
|
||||
acc.unlocked = true;
|
||||
Ok(())
|
||||
},
|
||||
Some(_) => Err(EncryptedHashMapError::InvalidPassword),
|
||||
None => Err(EncryptedHashMapError::UnknownIdentifier),
|
||||
}
|
||||
}
|
||||
|
||||
fn unlock_account_temp(&self, account: &Address, pass: &str) -> Result<(), EncryptedHashMapError> {
|
||||
// TODO; actually make it relock on use
|
||||
self.unlock_account(account, pass)
|
||||
}
|
||||
|
||||
fn new_account(&self, pass: &str) -> Result<Address, io::Error> {
|
||||
let account = TestAccount::new(pass);
|
||||
let address = KeyPair::from_secret(account.secret.clone()).unwrap().address();
|
||||
self.accounts.write().unwrap().insert(address.clone(), account);
|
||||
Ok(address)
|
||||
}
|
||||
|
||||
fn account_secret(&self, address: &Address) -> Result<Secret, SigningError> {
|
||||
// todo: consider checking if account is unlock. some test may need alteration then.
|
||||
self.accounts
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(address)
|
||||
.ok_or(SigningError::NoAccount)
|
||||
.map(|acc| acc.secret.clone())
|
||||
}
|
||||
|
||||
fn locked_account_secret(&self, address: &Address, pass: &str) -> Result<Secret, SigningError> {
|
||||
let accounts = self.accounts.read().unwrap();
|
||||
match accounts.get(address) {
|
||||
Some(ref acc) if acc.password == pass => {
|
||||
Ok(acc.secret.clone())
|
||||
},
|
||||
Some(ref _acc) => Err(SigningError::InvalidPassword),
|
||||
_ => Err(SigningError::NoAccount),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -151,7 +151,6 @@ pub mod io;
|
||||
pub mod network;
|
||||
pub mod log;
|
||||
pub mod panics;
|
||||
pub mod keys;
|
||||
pub mod table;
|
||||
pub mod network_settings;
|
||||
pub mod path;
|
||||
|
Loading…
Reference in New Issue
Block a user