Reformat the source code
This commit is contained in:
parent
253ff3f37b
commit
610d9baba4
@ -26,11 +26,13 @@ extern crate threadpool;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
use std::num::ParseIntError;
|
||||
use std::{env, fmt, process, io, sync};
|
||||
use std::{env, fmt, io, num::ParseIntError, process, sync};
|
||||
|
||||
use docopt::Docopt;
|
||||
use ethkey::{KeyPair, Random, Brain, BrainPrefix, Prefix, Error as EthkeyError, Generator, sign, verify_public, verify_address, brain_recover};
|
||||
use ethkey::{
|
||||
brain_recover, sign, verify_address, verify_public, Brain, BrainPrefix, Error as EthkeyError,
|
||||
Generator, KeyPair, Prefix, Random,
|
||||
};
|
||||
use rustc_hex::{FromHex, FromHexError};
|
||||
|
||||
const USAGE: &'static str = r#"
|
||||
@ -179,7 +181,7 @@ fn display(result: (KeyPair, Option<String>), mode: DisplayMode) -> String {
|
||||
match mode {
|
||||
DisplayMode::KeyPair => match result.1 {
|
||||
Some(extra_data) => format!("{}\n{}", extra_data, keypair),
|
||||
None => format!("{}", keypair)
|
||||
None => format!("{}", keypair),
|
||||
},
|
||||
DisplayMode::Secret => format!("{:x}", keypair.secret()),
|
||||
DisplayMode::Public => format!("{:x}", keypair.public()),
|
||||
@ -187,9 +189,12 @@ fn display(result: (KeyPair, Option<String>), mode: DisplayMode) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
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).deserialize())?;
|
||||
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).deserialize())?;
|
||||
|
||||
return if args.cmd_info {
|
||||
let display_mode = DisplayMode::new(&args);
|
||||
@ -197,10 +202,15 @@ fn execute<S, I>(command: I) -> Result<String, Error> where I: IntoIterator<Item
|
||||
let result = if args.flag_brain {
|
||||
let phrase = args.arg_secret_or_phrase;
|
||||
let phrase_info = validate_phrase(&phrase);
|
||||
let keypair = Brain::new(phrase).generate().expect("Brain wallet generator is infallible; qed");
|
||||
let keypair = Brain::new(phrase)
|
||||
.generate()
|
||||
.expect("Brain wallet generator is infallible; qed");
|
||||
(keypair, Some(phrase_info))
|
||||
} else {
|
||||
let secret = args.arg_secret_or_phrase.parse().map_err(|_| EthkeyError::InvalidSecret)?;
|
||||
let secret = args
|
||||
.arg_secret_or_phrase
|
||||
.parse()
|
||||
.map_err(|_| EthkeyError::InvalidSecret)?;
|
||||
(KeyPair::from_secret(secret)?, None)
|
||||
};
|
||||
Ok(display(result, display_mode))
|
||||
@ -237,33 +247,55 @@ fn execute<S, I>(command: I) -> Result<String, Error> where I: IntoIterator<Item
|
||||
}
|
||||
})?
|
||||
} else {
|
||||
return Ok(format!("{}", USAGE))
|
||||
return Ok(format!("{}", USAGE));
|
||||
};
|
||||
Ok(display(result, display_mode))
|
||||
} else if args.cmd_sign {
|
||||
let secret = args.arg_secret.parse().map_err(|_| EthkeyError::InvalidSecret)?;
|
||||
let message = args.arg_message.parse().map_err(|_| EthkeyError::InvalidMessage)?;
|
||||
let secret = args
|
||||
.arg_secret
|
||||
.parse()
|
||||
.map_err(|_| EthkeyError::InvalidSecret)?;
|
||||
let message = args
|
||||
.arg_message
|
||||
.parse()
|
||||
.map_err(|_| EthkeyError::InvalidMessage)?;
|
||||
let signature = sign(&secret, &message)?;
|
||||
Ok(format!("{}", signature))
|
||||
} else if args.cmd_verify {
|
||||
let signature = args.arg_signature.parse().map_err(|_| EthkeyError::InvalidSignature)?;
|
||||
let message = args.arg_message.parse().map_err(|_| EthkeyError::InvalidMessage)?;
|
||||
let signature = args
|
||||
.arg_signature
|
||||
.parse()
|
||||
.map_err(|_| EthkeyError::InvalidSignature)?;
|
||||
let message = args
|
||||
.arg_message
|
||||
.parse()
|
||||
.map_err(|_| EthkeyError::InvalidMessage)?;
|
||||
let ok = if args.cmd_public {
|
||||
let public = args.arg_public.parse().map_err(|_| EthkeyError::InvalidPublic)?;
|
||||
let public = args
|
||||
.arg_public
|
||||
.parse()
|
||||
.map_err(|_| EthkeyError::InvalidPublic)?;
|
||||
verify_public(&public, &signature, &message)?
|
||||
} else if args.cmd_address {
|
||||
let address = args.arg_address.parse().map_err(|_| EthkeyError::InvalidAddress)?;
|
||||
let address = args
|
||||
.arg_address
|
||||
.parse()
|
||||
.map_err(|_| EthkeyError::InvalidAddress)?;
|
||||
verify_address(&address, &signature, &message)?
|
||||
} else {
|
||||
return Ok(format!("{}", USAGE))
|
||||
return Ok(format!("{}", USAGE));
|
||||
};
|
||||
Ok(format!("{}", ok))
|
||||
} else if args.cmd_recover {
|
||||
let display_mode = DisplayMode::new(&args);
|
||||
let known_phrase = args.arg_known_phrase;
|
||||
let address = args.arg_address.parse().map_err(|_| EthkeyError::InvalidAddress)?;
|
||||
let address = args
|
||||
.arg_address
|
||||
.parse()
|
||||
.map_err(|_| EthkeyError::InvalidAddress)?;
|
||||
let (phrase, keypair) = in_threads(move || {
|
||||
let mut it = brain_recover::PhrasesIterator::from_known_phrase(&known_phrase, BRAIN_WORDS);
|
||||
let mut it =
|
||||
brain_recover::PhrasesIterator::from_known_phrase(&known_phrase, BRAIN_WORDS);
|
||||
move || {
|
||||
let mut i = 0;
|
||||
while let Some(phrase) = it.next() {
|
||||
@ -271,11 +303,11 @@ fn execute<S, I>(command: I) -> Result<String, Error> where I: IntoIterator<Item
|
||||
|
||||
let keypair = Brain::new(phrase.clone()).generate().unwrap();
|
||||
if keypair.address() == address {
|
||||
return Ok(Some((phrase, keypair)))
|
||||
return Ok(Some((phrase, keypair)));
|
||||
}
|
||||
|
||||
if i >= 1024 {
|
||||
return Ok(None)
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
@ -285,7 +317,7 @@ fn execute<S, I>(command: I) -> Result<String, Error> where I: IntoIterator<Item
|
||||
Ok(display((keypair, Some(phrase)), display_mode))
|
||||
} else {
|
||||
Ok(format!("{}", USAGE))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const BRAIN_WORDS: usize = 12;
|
||||
@ -293,11 +325,12 @@ const BRAIN_WORDS: usize = 12;
|
||||
fn validate_phrase(phrase: &str) -> String {
|
||||
match Brain::validate_phrase(phrase, BRAIN_WORDS) {
|
||||
Ok(()) => format!("The recovery phrase looks correct.\n"),
|
||||
Err(err) => format!("The recover phrase was not generated by Parity: {}", err)
|
||||
Err(err) => format!("The recover phrase was not generated by Parity: {}", err),
|
||||
}
|
||||
}
|
||||
|
||||
fn in_threads<F, X, O>(prepare: F) -> Result<O, EthkeyError> where
|
||||
fn in_threads<F, X, O>(prepare: F) -> Result<O, EthkeyError>
|
||||
where
|
||||
O: Send + 'static,
|
||||
X: Send + 'static,
|
||||
F: Fn() -> X,
|
||||
@ -344,7 +377,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn info() {
|
||||
let command = vec!["ethkey", "info", "17d08f5fe8c77af811caa0c9a187e668ce3b74a99acc3f6d976f075fa8e0be55"]
|
||||
let command = vec![
|
||||
"ethkey",
|
||||
"info",
|
||||
"17d08f5fe8c77af811caa0c9a187e668ce3b74a99acc3f6d976f075fa8e0be55",
|
||||
]
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<String>>();
|
||||
@ -379,7 +416,8 @@ address: 006e27b6a72e1f34c626762f3c4761547aff1421".to_owned();
|
||||
.map(Into::into)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let expected = "aa22b54c0cb43ee30a014afe5ef3664b1cde299feabca46cd3167a85a57c39f2".to_owned();
|
||||
let expected =
|
||||
"aa22b54c0cb43ee30a014afe5ef3664b1cde299feabca46cd3167a85a57c39f2".to_owned();
|
||||
assert_eq!(execute(command).unwrap(), expected);
|
||||
}
|
||||
|
||||
@ -407,7 +445,12 @@ address: 006e27b6a72e1f34c626762f3c4761547aff1421".to_owned();
|
||||
|
||||
#[test]
|
||||
fn sign() {
|
||||
let command = vec!["ethkey", "sign", "17d08f5fe8c77af811caa0c9a187e668ce3b74a99acc3f6d976f075fa8e0be55", "bd50b7370c3f96733b31744c6c45079e7ae6c8d299613246d28ebcef507ec987"]
|
||||
let command = vec![
|
||||
"ethkey",
|
||||
"sign",
|
||||
"17d08f5fe8c77af811caa0c9a187e668ce3b74a99acc3f6d976f075fa8e0be55",
|
||||
"bd50b7370c3f96733b31744c6c45079e7ae6c8d299613246d28ebcef507ec987",
|
||||
]
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<String>>();
|
||||
|
@ -14,8 +14,8 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::{Generator, KeyPair, Secret};
|
||||
use keccak::Keccak256;
|
||||
use super::{KeyPair, Generator, Secret};
|
||||
use parity_wordlist;
|
||||
|
||||
/// Simple brainwallet.
|
||||
@ -45,15 +45,15 @@ impl Generator for Brain {
|
||||
match i > 16384 {
|
||||
false => i += 1,
|
||||
true => {
|
||||
if let Ok(pair) = Secret::from_unsafe_slice(&secret)
|
||||
.and_then(KeyPair::from_secret)
|
||||
if let Ok(pair) =
|
||||
Secret::from_unsafe_slice(&secret).and_then(KeyPair::from_secret)
|
||||
{
|
||||
if pair.address()[0] == 0 {
|
||||
trace!("Testing: {}, got: {:?}", self.0, pair.address());
|
||||
return Ok(pair)
|
||||
return Ok(pair);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -61,7 +61,8 @@ impl Generator for Brain {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use {Brain, Generator};
|
||||
use Brain;
|
||||
use Generator;
|
||||
|
||||
#[test]
|
||||
fn test_brain() {
|
||||
|
@ -14,7 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::{Generator, KeyPair, Error, Brain};
|
||||
use super::{Brain, Error, Generator, KeyPair};
|
||||
use parity_wordlist as wordlist;
|
||||
|
||||
/// Tries to find brain-seed keypair with address starting with given prefix.
|
||||
@ -49,7 +49,7 @@ impl Generator for BrainPrefix {
|
||||
let keypair = Brain::new(phrase.clone()).generate().unwrap();
|
||||
if keypair.address().starts_with(&self.prefix) {
|
||||
self.last_phrase = phrase;
|
||||
return Ok(keypair)
|
||||
return Ok(keypair);
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,12 +59,15 @@ impl Generator for BrainPrefix {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use {Generator, BrainPrefix};
|
||||
use BrainPrefix;
|
||||
use Generator;
|
||||
|
||||
#[test]
|
||||
fn prefix_generator() {
|
||||
let prefix = vec![0x00u8];
|
||||
let keypair = BrainPrefix::new(prefix.clone(), usize::max_value(), 12).generate().unwrap();
|
||||
let keypair = BrainPrefix::new(prefix.clone(), usize::max_value(), 12)
|
||||
.generate()
|
||||
.unwrap();
|
||||
assert!(keypair.address().starts_with(&prefix));
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,9 @@ pub fn brain_recover(
|
||||
) -> Option<String> {
|
||||
let it = PhrasesIterator::from_known_phrase(known_phrase, expected_words);
|
||||
for phrase in it {
|
||||
let keypair = Brain::new(phrase.clone()).generate().expect("Brain wallets are infallible; qed");
|
||||
let keypair = Brain::new(phrase.clone())
|
||||
.generate()
|
||||
.expect("Brain wallets are infallible; qed");
|
||||
trace!("Testing: {}, got: {:?}", phrase, keypair.address());
|
||||
if &keypair.address() == address {
|
||||
return Some(phrase);
|
||||
@ -43,14 +45,14 @@ pub fn brain_recover(
|
||||
}
|
||||
|
||||
fn generate_substitutions(word: &str) -> Vec<&'static str> {
|
||||
let mut words = parity_wordlist::WORDS.iter().cloned()
|
||||
let mut words = parity_wordlist::WORDS
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|w| (edit_distance(w, word), w))
|
||||
.collect::<Vec<_>>();
|
||||
words.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
|
||||
words.into_iter()
|
||||
.map(|pair| pair.1)
|
||||
.collect()
|
||||
words.into_iter().map(|pair| pair.1).collect()
|
||||
}
|
||||
|
||||
/// Iterator over possible
|
||||
@ -63,15 +65,22 @@ pub struct PhrasesIterator {
|
||||
|
||||
impl PhrasesIterator {
|
||||
pub fn from_known_phrase(known_phrase: &str, expected_words: usize) -> Self {
|
||||
let known_words = parity_wordlist::WORDS.iter().cloned().collect::<HashSet<_>>();
|
||||
let mut words = known_phrase.split(' ')
|
||||
let known_words = parity_wordlist::WORDS
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect::<HashSet<_>>();
|
||||
let mut words = known_phrase
|
||||
.split(' ')
|
||||
.map(|word| match known_words.get(word) {
|
||||
None => {
|
||||
info!("Invalid word '{}', looking for potential substitutions.", word);
|
||||
info!(
|
||||
"Invalid word '{}', looking for potential substitutions.",
|
||||
word
|
||||
);
|
||||
let substitutions = generate_substitutions(word);
|
||||
info!("Closest words: {:?}", &substitutions[..10]);
|
||||
substitutions
|
||||
},
|
||||
}
|
||||
Some(word) => vec![*word],
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
@ -151,11 +160,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn should_generate_possible_combinations() {
|
||||
let mut it = PhrasesIterator::new(vec![
|
||||
vec!["1", "2", "3"],
|
||||
vec!["test"],
|
||||
vec!["a", "b", "c"],
|
||||
]);
|
||||
let mut it =
|
||||
PhrasesIterator::new(vec![vec!["1", "2", "3"], vec!["test"], vec!["a", "b", "c"]]);
|
||||
|
||||
assert_eq!(it.combinations(), 9);
|
||||
assert_eq!(it.next(), Some("1 test a".to_owned()));
|
||||
@ -169,5 +175,4 @@ mod tests {
|
||||
assert_eq!(it.next(), Some("3 test c".to_owned()));
|
||||
assert_eq!(it.next(), None);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -14,9 +14,9 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use parity_crypto::error::SymmError;
|
||||
use secp256k1;
|
||||
use std::io;
|
||||
use parity_crypto::error::SymmError;
|
||||
|
||||
quick_error! {
|
||||
#[derive(Debug)]
|
||||
@ -43,9 +43,11 @@ quick_error! {
|
||||
|
||||
/// ECDH functions
|
||||
pub mod ecdh {
|
||||
use secp256k1::{self, ecdh, key};
|
||||
use super::Error;
|
||||
use {Secret, Public, SECP256K1};
|
||||
use secp256k1::{self, ecdh, key};
|
||||
use Public;
|
||||
use Secret;
|
||||
use SECP256K1;
|
||||
|
||||
/// Agree on a shared secret
|
||||
pub fn agree(secret: &Secret, public: &Public) -> Result<Secret, Error> {
|
||||
@ -67,10 +69,13 @@ pub mod ecdh {
|
||||
|
||||
/// ECIES function
|
||||
pub mod ecies {
|
||||
use parity_crypto::{aes, digest, hmac, is_equal};
|
||||
use ethereum_types::H128;
|
||||
use super::{ecdh, Error};
|
||||
use {Random, Generator, Public, Secret};
|
||||
use ethereum_types::H128;
|
||||
use parity_crypto::{aes, digest, hmac, is_equal};
|
||||
use Generator;
|
||||
use Public;
|
||||
use Random;
|
||||
use Secret;
|
||||
|
||||
/// Encrypt a message with a public key, writing an HMAC covering both
|
||||
/// the plaintext and authenticated data.
|
||||
@ -126,10 +131,10 @@ pub mod ecies {
|
||||
let mkey = hmac::SigKey::sha256(&digest::sha256(&key[16..32]));
|
||||
|
||||
let clen = encrypted.len() - meta_len;
|
||||
let cipher_with_iv = &e[64..(64+16+clen)];
|
||||
let cipher_with_iv = &e[64..(64 + 16 + clen)];
|
||||
let cipher_iv = &cipher_with_iv[0..16];
|
||||
let cipher_no_iv = &cipher_with_iv[16..];
|
||||
let msg_mac = &e[(64+16+clen)..];
|
||||
let msg_mac = &e[(64 + 16 + clen)..];
|
||||
|
||||
// Verify tag
|
||||
let mut hmac = hmac::Signer::with(&mkey);
|
||||
@ -154,7 +159,12 @@ pub mod ecies {
|
||||
let mut written = 0usize;
|
||||
while written < dest.len() {
|
||||
let mut hasher = digest::Hasher::sha256();
|
||||
let ctrs = [(ctr >> 24) as u8, (ctr >> 16) as u8, (ctr >> 8) as u8, ctr as u8];
|
||||
let ctrs = [
|
||||
(ctr >> 24) as u8,
|
||||
(ctr >> 16) as u8,
|
||||
(ctr >> 8) as u8,
|
||||
ctr as u8,
|
||||
];
|
||||
hasher.update(&ctrs);
|
||||
hasher.update(secret);
|
||||
hasher.update(s1);
|
||||
@ -169,7 +179,8 @@ pub mod ecies {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ecies;
|
||||
use {Random, Generator};
|
||||
use Generator;
|
||||
use Random;
|
||||
|
||||
#[test]
|
||||
fn ecies_shared() {
|
||||
|
@ -14,7 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::{fmt, error};
|
||||
use std::{error, fmt};
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Crypto error
|
||||
|
@ -16,10 +16,10 @@
|
||||
|
||||
//! Extended keys
|
||||
|
||||
pub use self::derivation::Error as DerivationError;
|
||||
use ethereum_types::H256;
|
||||
use secret::Secret;
|
||||
use Public;
|
||||
use ethereum_types::H256;
|
||||
pub use self::derivation::Error as DerivationError;
|
||||
|
||||
/// Represents label that can be stored as a part of key derivation
|
||||
pub trait Label {
|
||||
@ -32,7 +32,9 @@ pub trait Label {
|
||||
}
|
||||
|
||||
impl Label for u32 {
|
||||
fn len() -> usize { 4 }
|
||||
fn len() -> usize {
|
||||
4
|
||||
}
|
||||
|
||||
fn store(&self, target: &mut [u8]) {
|
||||
let bytes = self.to_be_bytes();
|
||||
@ -52,15 +54,16 @@ impl From<u32> for Derivation<u32> {
|
||||
fn from(index: u32) -> Self {
|
||||
if index < (2 << 30) {
|
||||
Derivation::Soft(index)
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
Derivation::Hard(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Label for H256 {
|
||||
fn len() -> usize { 32 }
|
||||
fn len() -> usize {
|
||||
32
|
||||
}
|
||||
|
||||
fn store(&self, target: &mut [u8]) {
|
||||
self.copy_to(&mut target[0..32]);
|
||||
@ -95,8 +98,12 @@ impl ExtendedSecret {
|
||||
}
|
||||
|
||||
/// Derive new private key
|
||||
pub fn derive<T>(&self, index: Derivation<T>) -> ExtendedSecret where T: Label {
|
||||
let (derived_key, next_chain_code) = derivation::private(*self.secret, self.chain_code, index);
|
||||
pub fn derive<T>(&self, index: Derivation<T>) -> ExtendedSecret
|
||||
where
|
||||
T: Label,
|
||||
{
|
||||
let (derived_key, next_chain_code) =
|
||||
derivation::private(*self.secret, self.chain_code, index);
|
||||
|
||||
let derived_secret = Secret::from(derived_key.0);
|
||||
|
||||
@ -118,23 +125,28 @@ pub struct ExtendedPublic {
|
||||
impl ExtendedPublic {
|
||||
/// New extended public key from known parent and chain code
|
||||
pub fn new(public: Public, chain_code: H256) -> Self {
|
||||
ExtendedPublic { public: public, chain_code: chain_code }
|
||||
ExtendedPublic {
|
||||
public: public,
|
||||
chain_code: chain_code,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create new extended public key from known secret
|
||||
pub fn from_secret(secret: &ExtendedSecret) -> Result<Self, DerivationError> {
|
||||
Ok(
|
||||
ExtendedPublic::new(
|
||||
Ok(ExtendedPublic::new(
|
||||
derivation::point(**secret.as_raw())?,
|
||||
secret.chain_code.clone(),
|
||||
)
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
/// Derive new public key
|
||||
/// Operation is defined only for index belongs [0..2^31)
|
||||
pub fn derive<T>(&self, index: Derivation<T>) -> Result<Self, DerivationError> where T: Label {
|
||||
let (derived_key, next_chain_code) = derivation::public(self.public, self.chain_code, index)?;
|
||||
pub fn derive<T>(&self, index: Derivation<T>) -> Result<Self, DerivationError>
|
||||
where
|
||||
T: Label,
|
||||
{
|
||||
let (derived_key, next_chain_code) =
|
||||
derivation::public(self.public, self.chain_code, index)?;
|
||||
Ok(ExtendedPublic::new(derived_key, next_chain_code))
|
||||
}
|
||||
|
||||
@ -192,7 +204,10 @@ impl ExtendedKeyPair {
|
||||
&self.public
|
||||
}
|
||||
|
||||
pub fn derive<T>(&self, index: Derivation<T>) -> Result<Self, DerivationError> where T: Label {
|
||||
pub fn derive<T>(&self, index: Derivation<T>) -> Result<Self, DerivationError>
|
||||
where
|
||||
T: Label,
|
||||
{
|
||||
let derived = self.secret.derive(index);
|
||||
|
||||
Ok(ExtendedKeyPair {
|
||||
@ -206,13 +221,13 @@ impl ExtendedKeyPair {
|
||||
// Work is based on BIP0032
|
||||
// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
|
||||
mod derivation {
|
||||
use parity_crypto::hmac;
|
||||
use ethereum_types::{U256, U512, H512, H256};
|
||||
use secp256k1::key::{SecretKey, PublicKey};
|
||||
use SECP256K1;
|
||||
use super::{Derivation, Label};
|
||||
use ethereum_types::{H256, H512, U256, U512};
|
||||
use keccak;
|
||||
use math::curve_order;
|
||||
use super::{Label, Derivation};
|
||||
use parity_crypto::hmac;
|
||||
use secp256k1::key::{PublicKey, SecretKey};
|
||||
use SECP256K1;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
@ -228,7 +243,10 @@ mod derivation {
|
||||
//
|
||||
// Can panic if passed `private_key` is not a valid secp256k1 private key
|
||||
// (outside of (0..curve_order()]) field
|
||||
pub fn private<T>(private_key: H256, chain_code: H256, index: Derivation<T>) -> (H256, H256) where T: Label {
|
||||
pub fn private<T>(private_key: H256, chain_code: H256, index: Derivation<T>) -> (H256, H256)
|
||||
where
|
||||
T: Label,
|
||||
{
|
||||
match index {
|
||||
Derivation::Soft(index) => private_soft(private_key, chain_code, index),
|
||||
Derivation::Hard(index) => private_hard(private_key, chain_code, index),
|
||||
@ -253,7 +271,10 @@ mod derivation {
|
||||
|
||||
// Can panic if passed `private_key` is not a valid secp256k1 private key
|
||||
// (outside of (0..curve_order()]) field
|
||||
fn private_soft<T>(private_key: H256, chain_code: H256, index: T) -> (H256, H256) where T: Label {
|
||||
fn private_soft<T>(private_key: H256, chain_code: H256, index: T) -> (H256, H256)
|
||||
where
|
||||
T: Label,
|
||||
{
|
||||
let mut data = vec![0u8; 33 + T::len()];
|
||||
|
||||
let sec_private = SecretKey::from_slice(&SECP256K1, &*private_key)
|
||||
@ -273,7 +294,10 @@ mod derivation {
|
||||
// Deterministic derivation of the key using secp256k1 elliptic curve
|
||||
// This is hardened derivation and does not allow to associate
|
||||
// corresponding public keys of the original and derived private keys
|
||||
fn private_hard<T>(private_key: H256, chain_code: H256, index: T) -> (H256, H256) where T: Label {
|
||||
fn private_hard<T>(private_key: H256, chain_code: H256, index: T) -> (H256, H256)
|
||||
where
|
||||
T: Label,
|
||||
{
|
||||
let mut data: Vec<u8> = vec![0u8; 33 + T::len()];
|
||||
let private: U256 = private_key.into();
|
||||
|
||||
@ -297,16 +321,26 @@ mod derivation {
|
||||
md.into()
|
||||
}
|
||||
|
||||
pub fn public<T>(public_key: H512, chain_code: H256, derivation: Derivation<T>) -> Result<(H512, H256), Error> where T: Label {
|
||||
pub fn public<T>(
|
||||
public_key: H512,
|
||||
chain_code: H256,
|
||||
derivation: Derivation<T>,
|
||||
) -> Result<(H512, H256), Error>
|
||||
where
|
||||
T: Label,
|
||||
{
|
||||
let index = match derivation {
|
||||
Derivation::Soft(index) => index,
|
||||
Derivation::Hard(_) => { return Err(Error::InvalidHardenedUse); }
|
||||
Derivation::Hard(_) => {
|
||||
return Err(Error::InvalidHardenedUse);
|
||||
}
|
||||
};
|
||||
|
||||
let mut public_sec_raw = [0u8; 65];
|
||||
public_sec_raw[0] = 4;
|
||||
public_sec_raw[1..65].copy_from_slice(&*public_key);
|
||||
let public_sec = PublicKey::from_slice(&SECP256K1, &public_sec_raw).map_err(|_| Error::InvalidPoint)?;
|
||||
let public_sec =
|
||||
PublicKey::from_slice(&SECP256K1, &public_sec_raw).map_err(|_| Error::InvalidPoint)?;
|
||||
let public_serialized = public_sec.serialize_vec(&SECP256K1, true);
|
||||
|
||||
let mut data = vec![0u8; 33 + T::len()];
|
||||
@ -323,22 +357,22 @@ mod derivation {
|
||||
let new_chain_code = H256::from(&i_512[32..64]);
|
||||
|
||||
// Generated private key can (extremely rarely) be out of secp256k1 key field
|
||||
if curve_order() <= new_private.clone().into() { return Err(Error::MissingIndex); }
|
||||
if curve_order() <= new_private.clone().into() {
|
||||
return Err(Error::MissingIndex);
|
||||
}
|
||||
let new_private_sec = SecretKey::from_slice(&SECP256K1, &*new_private)
|
||||
.expect("Private key belongs to the field [0..CURVE_ORDER) (checked above); So initializing can never fail; qed");
|
||||
let mut new_public = PublicKey::from_secret_key(&SECP256K1, &new_private_sec)
|
||||
.expect("Valid private key produces valid public key");
|
||||
|
||||
// Adding two points on the elliptic curves (combining two public keys)
|
||||
new_public.add_assign(&SECP256K1, &public_sec)
|
||||
new_public
|
||||
.add_assign(&SECP256K1, &public_sec)
|
||||
.expect("Addition of two valid points produce valid point");
|
||||
|
||||
let serialized = new_public.serialize_vec(&SECP256K1, false);
|
||||
|
||||
Ok((
|
||||
H512::from(&serialized[1..65]),
|
||||
new_chain_code,
|
||||
))
|
||||
Ok((H512::from(&serialized[1..65]), new_chain_code))
|
||||
}
|
||||
|
||||
fn sha3(slc: &[u8]) -> H256 {
|
||||
@ -348,15 +382,16 @@ mod derivation {
|
||||
pub fn chain_code(secret: H256) -> H256 {
|
||||
// 10,000 rounds of sha3
|
||||
let mut running_sha3 = sha3(&*secret);
|
||||
for _ in 0..99999 { running_sha3 = sha3(&*running_sha3); }
|
||||
for _ in 0..99999 {
|
||||
running_sha3 = sha3(&*running_sha3);
|
||||
}
|
||||
running_sha3
|
||||
}
|
||||
|
||||
pub fn point(secret: H256) -> Result<H512, Error> {
|
||||
let sec = SecretKey::from_slice(&SECP256K1, &*secret)
|
||||
.map_err(|_| Error::InvalidPoint)?;
|
||||
let public_sec = PublicKey::from_secret_key(&SECP256K1, &sec)
|
||||
.map_err(|_| Error::InvalidPoint)?;
|
||||
let sec = SecretKey::from_slice(&SECP256K1, &*secret).map_err(|_| Error::InvalidPoint)?;
|
||||
let public_sec =
|
||||
PublicKey::from_secret_key(&SECP256K1, &sec).map_err(|_| Error::InvalidPoint)?;
|
||||
let serialized = public_sec.serialize_vec(&SECP256K1, false);
|
||||
Ok(H512::from(&serialized[1..65]))
|
||||
}
|
||||
@ -374,11 +409,10 @@ mod derivation {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{ExtendedSecret, ExtendedPublic, ExtendedKeyPair};
|
||||
use super::{derivation, Derivation, ExtendedKeyPair, ExtendedPublic, ExtendedSecret};
|
||||
use ethereum_types::{H128, H256};
|
||||
use secret::Secret;
|
||||
use std::str::FromStr;
|
||||
use ethereum_types::{H128, H256};
|
||||
use super::{derivation, Derivation};
|
||||
|
||||
fn master_chain_basic() -> (H256, H256) {
|
||||
let seed = H128::from_str("000102030405060708090a0b0c0d0e0f")
|
||||
@ -388,7 +422,10 @@ mod tests {
|
||||
derivation::seed_pair(&*seed)
|
||||
}
|
||||
|
||||
fn test_extended<F>(f: F, test_private: H256) where F: Fn(ExtendedSecret) -> ExtendedSecret {
|
||||
fn test_extended<F>(f: F, test_private: H256)
|
||||
where
|
||||
F: Fn(ExtendedSecret) -> ExtendedSecret,
|
||||
{
|
||||
let (private_seed, chain_code) = master_chain_basic();
|
||||
let extended_secret = ExtendedSecret::with_code(Secret::from(private_seed.0), chain_code);
|
||||
let derived = f(extended_secret);
|
||||
@ -397,65 +434,116 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn smoky() {
|
||||
let secret = Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65").unwrap();
|
||||
let secret =
|
||||
Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65")
|
||||
.unwrap();
|
||||
let extended_secret = ExtendedSecret::with_code(secret.clone(), 0u64.into());
|
||||
|
||||
// hardened
|
||||
assert_eq!(&**extended_secret.as_raw(), &*secret);
|
||||
assert_eq!(&**extended_secret.derive(2147483648.into()).as_raw(), &"0927453daed47839608e414a3738dfad10aed17c459bbd9ab53f89b026c834b6".into());
|
||||
assert_eq!(&**extended_secret.derive(2147483649.into()).as_raw(), &"44238b6a29c6dcbe9b401364141ba11e2198c289a5fed243a1c11af35c19dc0f".into());
|
||||
assert_eq!(
|
||||
&**extended_secret.derive(2147483648.into()).as_raw(),
|
||||
&"0927453daed47839608e414a3738dfad10aed17c459bbd9ab53f89b026c834b6".into()
|
||||
);
|
||||
assert_eq!(
|
||||
&**extended_secret.derive(2147483649.into()).as_raw(),
|
||||
&"44238b6a29c6dcbe9b401364141ba11e2198c289a5fed243a1c11af35c19dc0f".into()
|
||||
);
|
||||
|
||||
// normal
|
||||
assert_eq!(&**extended_secret.derive(0.into()).as_raw(), &"bf6a74e3f7b36fc4c96a1e12f31abc817f9f5904f5a8fc27713163d1f0b713f6".into());
|
||||
assert_eq!(&**extended_secret.derive(1.into()).as_raw(), &"bd4fca9eb1f9c201e9448c1eecd66e302d68d4d313ce895b8c134f512205c1bc".into());
|
||||
assert_eq!(&**extended_secret.derive(2.into()).as_raw(), &"86932b542d6cab4d9c65490c7ef502d89ecc0e2a5f4852157649e3251e2a3268".into());
|
||||
assert_eq!(
|
||||
&**extended_secret.derive(0.into()).as_raw(),
|
||||
&"bf6a74e3f7b36fc4c96a1e12f31abc817f9f5904f5a8fc27713163d1f0b713f6".into()
|
||||
);
|
||||
assert_eq!(
|
||||
&**extended_secret.derive(1.into()).as_raw(),
|
||||
&"bd4fca9eb1f9c201e9448c1eecd66e302d68d4d313ce895b8c134f512205c1bc".into()
|
||||
);
|
||||
assert_eq!(
|
||||
&**extended_secret.derive(2.into()).as_raw(),
|
||||
&"86932b542d6cab4d9c65490c7ef502d89ecc0e2a5f4852157649e3251e2a3268".into()
|
||||
);
|
||||
|
||||
let extended_public = ExtendedPublic::from_secret(&extended_secret).expect("Extended public should be created");
|
||||
let derived_public = extended_public.derive(0.into()).expect("First derivation of public should succeed");
|
||||
let extended_public = ExtendedPublic::from_secret(&extended_secret)
|
||||
.expect("Extended public should be created");
|
||||
let derived_public = extended_public
|
||||
.derive(0.into())
|
||||
.expect("First derivation of public should succeed");
|
||||
assert_eq!(&*derived_public.public(), &"f7b3244c96688f92372bfd4def26dc4151529747bab9f188a4ad34e141d47bd66522ff048bc6f19a0a4429b04318b1a8796c000265b4fa200dae5f6dda92dd94".into());
|
||||
|
||||
let keypair = ExtendedKeyPair::with_secret(
|
||||
Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65").unwrap(),
|
||||
Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65")
|
||||
.unwrap(),
|
||||
064.into(),
|
||||
);
|
||||
assert_eq!(&**keypair.derive(2147483648u32.into()).expect("Derivation of keypair should succeed").secret().as_raw(), &"edef54414c03196557cf73774bc97a645c9a1df2164ed34f0c2a78d1375a930c".into());
|
||||
assert_eq!(
|
||||
&**keypair
|
||||
.derive(2147483648u32.into())
|
||||
.expect("Derivation of keypair should succeed")
|
||||
.secret()
|
||||
.as_raw(),
|
||||
&"edef54414c03196557cf73774bc97a645c9a1df2164ed34f0c2a78d1375a930c".into()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn h256_soft_match() {
|
||||
let secret = Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65").unwrap();
|
||||
let derivation_secret = H256::from_str("51eaf04f9dbbc1417dc97e789edd0c37ecda88bac490434e367ea81b71b7b015").unwrap();
|
||||
let secret =
|
||||
Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65")
|
||||
.unwrap();
|
||||
let derivation_secret =
|
||||
H256::from_str("51eaf04f9dbbc1417dc97e789edd0c37ecda88bac490434e367ea81b71b7b015")
|
||||
.unwrap();
|
||||
|
||||
let extended_secret = ExtendedSecret::with_code(secret.clone(), 0u64.into());
|
||||
let extended_public = ExtendedPublic::from_secret(&extended_secret).expect("Extended public should be created");
|
||||
let extended_public = ExtendedPublic::from_secret(&extended_secret)
|
||||
.expect("Extended public should be created");
|
||||
|
||||
let derived_secret0 = extended_secret.derive(Derivation::Soft(derivation_secret));
|
||||
let derived_public0 = extended_public.derive(Derivation::Soft(derivation_secret)).expect("First derivation of public should succeed");
|
||||
let derived_public0 = extended_public
|
||||
.derive(Derivation::Soft(derivation_secret))
|
||||
.expect("First derivation of public should succeed");
|
||||
|
||||
let public_from_secret0 = ExtendedPublic::from_secret(&derived_secret0).expect("Extended public should be created");
|
||||
let public_from_secret0 = ExtendedPublic::from_secret(&derived_secret0)
|
||||
.expect("Extended public should be created");
|
||||
|
||||
assert_eq!(public_from_secret0.public(), derived_public0.public());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn h256_hard() {
|
||||
let secret = Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65").unwrap();
|
||||
let derivation_secret = H256::from_str("51eaf04f9dbbc1417dc97e789edd0c37ecda88bac490434e367ea81b71b7b015").unwrap();
|
||||
let secret =
|
||||
Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65")
|
||||
.unwrap();
|
||||
let derivation_secret =
|
||||
H256::from_str("51eaf04f9dbbc1417dc97e789edd0c37ecda88bac490434e367ea81b71b7b015")
|
||||
.unwrap();
|
||||
let extended_secret = ExtendedSecret::with_code(secret.clone(), 1u64.into());
|
||||
|
||||
assert_eq!(&**extended_secret.derive(Derivation::Hard(derivation_secret)).as_raw(), &"2bc2d696fb744d77ff813b4a1ef0ad64e1e5188b622c54ba917acc5ebc7c5486".into());
|
||||
assert_eq!(
|
||||
&**extended_secret
|
||||
.derive(Derivation::Hard(derivation_secret))
|
||||
.as_raw(),
|
||||
&"2bc2d696fb744d77ff813b4a1ef0ad64e1e5188b622c54ba917acc5ebc7c5486".into()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn match_() {
|
||||
let secret = Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65").unwrap();
|
||||
let secret =
|
||||
Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65")
|
||||
.unwrap();
|
||||
let extended_secret = ExtendedSecret::with_code(secret.clone(), 1.into());
|
||||
let extended_public = ExtendedPublic::from_secret(&extended_secret).expect("Extended public should be created");
|
||||
let extended_public = ExtendedPublic::from_secret(&extended_secret)
|
||||
.expect("Extended public should be created");
|
||||
|
||||
let derived_secret0 = extended_secret.derive(0.into());
|
||||
let derived_public0 = extended_public.derive(0.into()).expect("First derivation of public should succeed");
|
||||
let derived_public0 = extended_public
|
||||
.derive(0.into())
|
||||
.expect("First derivation of public should succeed");
|
||||
|
||||
let public_from_secret0 = ExtendedPublic::from_secret(&derived_secret0).expect("Extended public should be created");
|
||||
let public_from_secret0 = ExtendedPublic::from_secret(&derived_secret0)
|
||||
.expect("Extended public should be created");
|
||||
|
||||
assert_eq!(public_from_secret0.public(), derived_public0.public());
|
||||
}
|
||||
@ -468,7 +556,8 @@ mod tests {
|
||||
|
||||
// private key from bitcoin test vector
|
||||
// xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs
|
||||
let test_private = H256::from_str("e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35")
|
||||
let test_private =
|
||||
H256::from_str("e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35")
|
||||
.expect("Private should be decoded ok");
|
||||
|
||||
let (private_seed, _) = derivation::seed_pair(&*seed);
|
||||
@ -483,7 +572,7 @@ mod tests {
|
||||
test_extended(
|
||||
|secret| secret.derive(2147483648.into()),
|
||||
H256::from_str("edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea")
|
||||
.expect("Private should be decoded ok")
|
||||
.expect("Private should be decoded ok"),
|
||||
);
|
||||
}
|
||||
|
||||
@ -494,7 +583,7 @@ mod tests {
|
||||
test_extended(
|
||||
|secret| secret.derive(2147483648.into()).derive(1.into()),
|
||||
H256::from_str("3c6cb8d0f6a264c91ea8b5030fadaa8e538b020f0a387421a12de9319dc93368")
|
||||
.expect("Private should be decoded ok")
|
||||
.expect("Private should be decoded ok"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,9 @@
|
||||
use tiny_keccak::Keccak;
|
||||
|
||||
pub trait Keccak256<T> {
|
||||
fn keccak256(&self) -> T where T: Sized;
|
||||
fn keccak256(&self) -> T
|
||||
where
|
||||
T: Sized;
|
||||
}
|
||||
|
||||
impl Keccak256<[u8; 32]> for [u8] {
|
||||
|
@ -14,11 +14,11 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::fmt;
|
||||
use secp256k1::key;
|
||||
use rustc_hex::ToHex;
|
||||
use super::{Address, Error, Public, Secret, SECP256K1};
|
||||
use keccak::Keccak256;
|
||||
use super::{Secret, Public, Address, SECP256K1, Error};
|
||||
use rustc_hex::ToHex;
|
||||
use secp256k1::key;
|
||||
use std::fmt;
|
||||
|
||||
pub fn public_to_address(public: &Public) -> Address {
|
||||
let hash = public.keccak256();
|
||||
@ -94,11 +94,14 @@ impl KeyPair {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::str::FromStr;
|
||||
use {KeyPair, Secret};
|
||||
use KeyPair;
|
||||
use Secret;
|
||||
|
||||
#[test]
|
||||
fn from_secret() {
|
||||
let secret = Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65").unwrap();
|
||||
let secret =
|
||||
Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65")
|
||||
.unwrap();
|
||||
let _ = KeyPair::from_secret(secret).unwrap();
|
||||
}
|
||||
|
||||
@ -108,7 +111,9 @@ mod tests {
|
||||
"secret: a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65
|
||||
public: 8ce0db0b0359ffc5866ba61903cc2518c3675ef2cf380a7e54bde7ea20e6fa1ab45b7617346cd11b7610001ee6ae5b0155c41cad9527cbcdff44ec67848943a4
|
||||
address: 5b073e9233944b5e729e46d618f0d8edf3d9c34a".to_owned();
|
||||
let secret = Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65").unwrap();
|
||||
let secret =
|
||||
Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65")
|
||||
.unwrap();
|
||||
let kp = KeyPair::from_secret(secret).unwrap();
|
||||
assert_eq!(format!("{}", kp), expected);
|
||||
}
|
||||
|
@ -17,9 +17,9 @@
|
||||
// #![warn(missing_docs)]
|
||||
|
||||
extern crate edit_distance;
|
||||
extern crate parity_crypto;
|
||||
extern crate ethereum_types;
|
||||
extern crate memzero;
|
||||
extern crate parity_crypto;
|
||||
extern crate parity_wordlist;
|
||||
#[macro_use]
|
||||
extern crate quick_error;
|
||||
@ -39,31 +39,33 @@ extern crate serde_derive;
|
||||
mod brain;
|
||||
mod brain_prefix;
|
||||
mod error;
|
||||
mod keypair;
|
||||
mod extended;
|
||||
mod keccak;
|
||||
mod keypair;
|
||||
mod password;
|
||||
mod prefix;
|
||||
mod random;
|
||||
mod signature;
|
||||
mod secret;
|
||||
mod extended;
|
||||
mod signature;
|
||||
|
||||
pub mod brain_recover;
|
||||
pub mod crypto;
|
||||
pub mod math;
|
||||
|
||||
pub use self::parity_wordlist::Error as WordlistError;
|
||||
pub use self::brain::Brain;
|
||||
pub use self::brain_prefix::BrainPrefix;
|
||||
pub use self::error::Error;
|
||||
pub use self::keypair::{KeyPair, public_to_address};
|
||||
pub use self::math::public_is_valid;
|
||||
pub use self::password::Password;
|
||||
pub use self::prefix::Prefix;
|
||||
pub use self::random::Random;
|
||||
pub use self::signature::{sign, verify_public, verify_address, recover, Signature};
|
||||
pub use self::secret::Secret;
|
||||
pub use self::extended::{ExtendedPublic, ExtendedSecret, ExtendedKeyPair, DerivationError, Derivation};
|
||||
pub use self::{
|
||||
brain::Brain,
|
||||
brain_prefix::BrainPrefix,
|
||||
error::Error,
|
||||
extended::{Derivation, DerivationError, ExtendedKeyPair, ExtendedPublic, ExtendedSecret},
|
||||
keypair::{public_to_address, KeyPair},
|
||||
math::public_is_valid,
|
||||
parity_wordlist::Error as WordlistError,
|
||||
password::Password,
|
||||
prefix::Prefix,
|
||||
random::Random,
|
||||
secret::Secret,
|
||||
signature::{recover, sign, verify_address, verify_public, Signature},
|
||||
};
|
||||
|
||||
use ethereum_types::H256;
|
||||
|
||||
|
@ -14,14 +14,17 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::{SECP256K1, Public, Secret, Error};
|
||||
use secp256k1::key;
|
||||
use secp256k1::constants::{GENERATOR_X, GENERATOR_Y, CURVE_ORDER};
|
||||
use ethereum_types::{U256, H256};
|
||||
use super::{Error, Public, Secret, SECP256K1};
|
||||
use ethereum_types::{H256, U256};
|
||||
use secp256k1::{
|
||||
constants::{CURVE_ORDER, GENERATOR_X, GENERATOR_Y},
|
||||
key,
|
||||
};
|
||||
|
||||
/// Whether the public key is valid.
|
||||
pub fn public_is_valid(public: &Public) -> bool {
|
||||
to_secp256k1_public(public).ok()
|
||||
to_secp256k1_public(public)
|
||||
.ok()
|
||||
.map_or(false, |p| p.is_valid())
|
||||
}
|
||||
|
||||
@ -98,8 +101,10 @@ fn set_public(public: &mut Public, key_public: &key::PublicKey) {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::{Random, Generator};
|
||||
use super::{public_add, public_sub};
|
||||
use super::{
|
||||
super::{Generator, Random},
|
||||
public_add, public_sub,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn public_addition_is_commutative() {
|
||||
|
@ -14,7 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::{Random, Generator, KeyPair, Error};
|
||||
use super::{Error, Generator, KeyPair, Random};
|
||||
|
||||
/// Tries to find keypair with address starting with given prefix.
|
||||
pub struct Prefix {
|
||||
@ -38,7 +38,7 @@ impl Generator for Prefix {
|
||||
for _ in 0..self.iterations {
|
||||
let keypair = Random.generate()?;
|
||||
if keypair.address().starts_with(&self.prefix) {
|
||||
return Ok(keypair)
|
||||
return Ok(keypair);
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,12 +48,15 @@ impl Generator for Prefix {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use {Generator, Prefix};
|
||||
use Generator;
|
||||
use Prefix;
|
||||
|
||||
#[test]
|
||||
fn prefix_generator() {
|
||||
let prefix = vec![0xffu8];
|
||||
let keypair = Prefix::new(prefix.clone(), usize::max_value()).generate().unwrap();
|
||||
let keypair = Prefix::new(prefix.clone(), usize::max_value())
|
||||
.generate()
|
||||
.unwrap();
|
||||
assert!(keypair.address().starts_with(&prefix));
|
||||
}
|
||||
}
|
||||
|
@ -14,8 +14,8 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use rand::os::OsRng;
|
||||
use super::{Generator, KeyPair, SECP256K1};
|
||||
use rand::os::OsRng;
|
||||
|
||||
/// Randomly generates new keypair, instantiating the RNG each time.
|
||||
pub struct Random;
|
||||
@ -36,7 +36,8 @@ impl Generator for OsRng {
|
||||
type Error = ::Void;
|
||||
|
||||
fn generate(&mut self) -> Result<KeyPair, Self::Error> {
|
||||
let (sec, publ) = SECP256K1.generate_keypair(self)
|
||||
let (sec, publ) = SECP256K1
|
||||
.generate_keypair(self)
|
||||
.expect("context always created with full capabilities; qed");
|
||||
|
||||
Ok(KeyPair::from_keypair(sec, publ))
|
||||
|
@ -14,15 +14,13 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::fmt;
|
||||
use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
use rustc_hex::ToHex;
|
||||
use secp256k1::constants::{SECRET_KEY_SIZE as SECP256K1_SECRET_KEY_SIZE};
|
||||
use secp256k1::key;
|
||||
use ethereum_types::H256;
|
||||
use memzero::Memzero;
|
||||
use {Error, SECP256K1};
|
||||
use rustc_hex::ToHex;
|
||||
use secp256k1::{constants::SECRET_KEY_SIZE as SECP256K1_SECRET_KEY_SIZE, key};
|
||||
use std::{fmt, ops::Deref, str::FromStr};
|
||||
use Error;
|
||||
use SECP256K1;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct Secret {
|
||||
@ -49,7 +47,11 @@ impl fmt::Debug for Secret {
|
||||
|
||||
impl fmt::Display for Secret {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(fmt, "Secret: 0x{:x}{:x}..{:x}{:x}", self.inner[0], self.inner[1], self.inner[30], self.inner[31])
|
||||
write!(
|
||||
fmt,
|
||||
"Secret: 0x{:x}{:x}..{:x}{:x}",
|
||||
self.inner[0], self.inner[1], self.inner[30], self.inner[31]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,16 +59,20 @@ impl Secret {
|
||||
/// Creates a `Secret` from the given slice, returning `None` if the slice length != 32.
|
||||
pub fn from_slice(key: &[u8]) -> Option<Self> {
|
||||
if key.len() != 32 {
|
||||
return None
|
||||
return None;
|
||||
}
|
||||
let mut h = H256::default();
|
||||
h.copy_from_slice(&key[0..32]);
|
||||
Some(Secret { inner: Memzero::from(h) })
|
||||
Some(Secret {
|
||||
inner: Memzero::from(h),
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates zero key, which is invalid for crypto operations, but valid for math operation.
|
||||
pub fn zero() -> Self {
|
||||
Secret { inner: Memzero::from(H256::default()) }
|
||||
Secret {
|
||||
inner: Memzero::from(H256::default()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Imports and validates the key.
|
||||
@ -87,7 +93,7 @@ impl Secret {
|
||||
(true, false) => {
|
||||
*self = other.clone();
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
(false, false) => {
|
||||
let mut key_secret = self.to_secp256k1_secret()?;
|
||||
let other_secret = other.to_secp256k1_secret()?;
|
||||
@ -95,7 +101,7 @@ impl Secret {
|
||||
|
||||
*self = key_secret.into();
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,7 +112,7 @@ impl Secret {
|
||||
(true, false) => {
|
||||
*self = other.clone();
|
||||
self.neg()
|
||||
},
|
||||
}
|
||||
(false, false) => {
|
||||
let mut key_secret = self.to_secp256k1_secret()?;
|
||||
let mut other_secret = other.to_secp256k1_secret()?;
|
||||
@ -115,7 +121,7 @@ impl Secret {
|
||||
|
||||
*self = key_secret.into();
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,14 +131,14 @@ impl Secret {
|
||||
true => {
|
||||
*self = key::MINUS_ONE_KEY.into();
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
false => {
|
||||
let mut key_secret = self.to_secp256k1_secret()?;
|
||||
key_secret.add_assign(&SECP256K1, &key::MINUS_ONE_KEY)?;
|
||||
|
||||
*self = key_secret.into();
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,7 +149,7 @@ impl Secret {
|
||||
(false, true) => {
|
||||
*self = Self::zero();
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
(false, false) => {
|
||||
let mut key_secret = self.to_secp256k1_secret()?;
|
||||
let other_secret = other.to_secp256k1_secret()?;
|
||||
@ -151,7 +157,7 @@ impl Secret {
|
||||
|
||||
*self = key_secret.into();
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -165,7 +171,7 @@ impl Secret {
|
||||
|
||||
*self = key_secret.into();
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,7 +199,7 @@ impl Secret {
|
||||
for _ in 1..pow {
|
||||
self.mul(&c)?;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -208,13 +214,17 @@ impl Secret {
|
||||
impl FromStr for Secret {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(H256::from_str(s).map_err(|e| Error::Custom(format!("{:?}", e)))?.into())
|
||||
Ok(H256::from_str(s)
|
||||
.map_err(|e| Error::Custom(format!("{:?}", e)))?
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 32]> for Secret {
|
||||
fn from(k: [u8; 32]) -> Self {
|
||||
Secret { inner: Memzero::from(H256(k)) }
|
||||
Secret {
|
||||
inner: Memzero::from(H256(k)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -226,14 +236,18 @@ impl From<H256> for Secret {
|
||||
|
||||
impl From<&'static str> for Secret {
|
||||
fn from(s: &'static str) -> Self {
|
||||
s.parse().expect(&format!("invalid string literal for {}: '{}'", stringify!(Self), s))
|
||||
s.parse().expect(&format!(
|
||||
"invalid string literal for {}: '{}'",
|
||||
stringify!(Self),
|
||||
s
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<key::SecretKey> for Secret {
|
||||
fn from(key: key::SecretKey) -> Self {
|
||||
let mut a = [0; SECP256K1_SECRET_KEY_SIZE];
|
||||
a.copy_from_slice(&key[0 .. SECP256K1_SECRET_KEY_SIZE]);
|
||||
a.copy_from_slice(&key[0..SECP256K1_SECRET_KEY_SIZE]);
|
||||
a.into()
|
||||
}
|
||||
}
|
||||
@ -248,9 +262,11 @@ impl Deref for Secret {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
super::{Generator, Random},
|
||||
Secret,
|
||||
};
|
||||
use std::str::FromStr;
|
||||
use super::super::{Random, Generator};
|
||||
use super::Secret;
|
||||
|
||||
#[test]
|
||||
fn multiplicating_secret_inversion_with_secret_gives_one() {
|
||||
@ -258,7 +274,11 @@ mod tests {
|
||||
let mut inversion = secret.clone();
|
||||
inversion.inv().unwrap();
|
||||
inversion.mul(&secret).unwrap();
|
||||
assert_eq!(inversion, Secret::from_str("0000000000000000000000000000000000000000000000000000000000000001").unwrap());
|
||||
assert_eq!(
|
||||
inversion,
|
||||
Secret::from_str("0000000000000000000000000000000000000000000000000000000000000001")
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -276,7 +296,11 @@ mod tests {
|
||||
|
||||
let mut pow0 = secret.clone();
|
||||
pow0.pow(0).unwrap();
|
||||
assert_eq!(pow0, Secret::from_str("0000000000000000000000000000000000000000000000000000000000000001").unwrap());
|
||||
assert_eq!(
|
||||
pow0,
|
||||
Secret::from_str("0000000000000000000000000000000000000000000000000000000000000001")
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
let mut pow1 = secret.clone();
|
||||
pow1.pow(1).unwrap();
|
||||
|
@ -14,16 +14,26 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::cmp::PartialEq;
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use secp256k1::{Message as SecpMessage, RecoverableSignature, RecoveryId, Error as SecpError};
|
||||
use secp256k1::key::{SecretKey, PublicKey};
|
||||
use rustc_hex::{ToHex, FromHex};
|
||||
use ethereum_types::{H520, H256};
|
||||
use {Secret, Public, SECP256K1, Error, Message, public_to_address, Address};
|
||||
use ethereum_types::{H256, H520};
|
||||
use public_to_address;
|
||||
use rustc_hex::{FromHex, ToHex};
|
||||
use secp256k1::{
|
||||
key::{PublicKey, SecretKey},
|
||||
Error as SecpError, Message as SecpMessage, RecoverableSignature, RecoveryId,
|
||||
};
|
||||
use std::{
|
||||
cmp::PartialEq,
|
||||
fmt,
|
||||
hash::{Hash, Hasher},
|
||||
ops::{Deref, DerefMut},
|
||||
str::FromStr,
|
||||
};
|
||||
use Address;
|
||||
use Error;
|
||||
use Message;
|
||||
use Public;
|
||||
use Secret;
|
||||
use SECP256K1;
|
||||
|
||||
/// Signature encoded as RSV components
|
||||
#[repr(C)]
|
||||
@ -76,16 +86,19 @@ impl Signature {
|
||||
|
||||
/// Check if this is a "low" signature.
|
||||
pub fn is_low_s(&self) -> bool {
|
||||
H256::from_slice(self.s()) <= "7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0".into()
|
||||
H256::from_slice(self.s())
|
||||
<= "7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0".into()
|
||||
}
|
||||
|
||||
/// Check if each component of the signature is in range.
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.v() <= 1 &&
|
||||
H256::from_slice(self.r()) < "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141".into() &&
|
||||
H256::from_slice(self.r()) >= 1.into() &&
|
||||
H256::from_slice(self.s()) < "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141".into() &&
|
||||
H256::from_slice(self.s()) >= 1.into()
|
||||
self.v() <= 1
|
||||
&& H256::from_slice(self.r())
|
||||
< "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141".into()
|
||||
&& H256::from_slice(self.r()) >= 1.into()
|
||||
&& H256::from_slice(self.s())
|
||||
< "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141".into()
|
||||
&& H256::from_slice(self.s()) >= 1.into()
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,7 +111,7 @@ impl PartialEq for Signature {
|
||||
}
|
||||
|
||||
// manual implementation required in Rust 1.13+, see `std::cmp::AssertParamIsEq`.
|
||||
impl Eq for Signature { }
|
||||
impl Eq for Signature {}
|
||||
|
||||
// also manual for the same reason, but the pretty printing might be useful.
|
||||
impl fmt::Debug for Signature {
|
||||
@ -126,8 +139,8 @@ impl FromStr for Signature {
|
||||
let mut data = [0; 65];
|
||||
data.copy_from_slice(&hex[0..65]);
|
||||
Ok(Signature(data))
|
||||
},
|
||||
_ => Err(Error::InvalidSignature)
|
||||
}
|
||||
_ => Err(Error::InvalidSignature),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -201,9 +214,17 @@ pub fn sign(secret: &Secret, message: &Message) -> Result<Signature, Error> {
|
||||
Ok(Signature(data_arr))
|
||||
}
|
||||
|
||||
pub fn verify_public(public: &Public, signature: &Signature, message: &Message) -> Result<bool, Error> {
|
||||
pub fn verify_public(
|
||||
public: &Public,
|
||||
signature: &Signature,
|
||||
message: &Message,
|
||||
) -> Result<bool, Error> {
|
||||
let context = &SECP256K1;
|
||||
let rsig = RecoverableSignature::from_compact(context, &signature[0..64], RecoveryId::from_i32(signature[64] as i32)?)?;
|
||||
let rsig = RecoverableSignature::from_compact(
|
||||
context,
|
||||
&signature[0..64],
|
||||
RecoveryId::from_i32(signature[64] as i32)?,
|
||||
)?;
|
||||
let sig = rsig.to_standard(context);
|
||||
|
||||
let pdata: [u8; 65] = {
|
||||
@ -216,11 +237,15 @@ pub fn verify_public(public: &Public, signature: &Signature, message: &Message)
|
||||
match context.verify(&SecpMessage::from_slice(&message[..])?, &sig, &publ) {
|
||||
Ok(_) => Ok(true),
|
||||
Err(SecpError::IncorrectSignature) => Ok(false),
|
||||
Err(x) => Err(Error::from(x))
|
||||
Err(x) => Err(Error::from(x)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify_address(address: &Address, signature: &Signature, message: &Message) -> Result<bool, Error> {
|
||||
pub fn verify_address(
|
||||
address: &Address,
|
||||
signature: &Signature,
|
||||
message: &Message,
|
||||
) -> Result<bool, Error> {
|
||||
let public = recover(signature, message)?;
|
||||
let recovered_address = public_to_address(&public);
|
||||
Ok(address == &recovered_address)
|
||||
@ -228,7 +253,11 @@ pub fn verify_address(address: &Address, signature: &Signature, message: &Messag
|
||||
|
||||
pub fn recover(signature: &Signature, message: &Message) -> Result<Public, Error> {
|
||||
let context = &SECP256K1;
|
||||
let rsig = RecoverableSignature::from_compact(context, &signature[0..64], RecoveryId::from_i32(signature[64] as i32)?)?;
|
||||
let rsig = RecoverableSignature::from_compact(
|
||||
context,
|
||||
&signature[0..64],
|
||||
RecoveryId::from_i32(signature[64] as i32)?,
|
||||
)?;
|
||||
let pubkey = context.recover(&SecpMessage::from_slice(&message[..])?, &rsig)?;
|
||||
let serialized = pubkey.serialize_vec(context, false);
|
||||
|
||||
@ -239,9 +268,11 @@ pub fn recover(signature: &Signature, message: &Message) -> Result<Public, Error
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{recover, sign, verify_address, verify_public, Signature};
|
||||
use std::str::FromStr;
|
||||
use {Generator, Random, Message};
|
||||
use super::{sign, verify_public, verify_address, recover, Signature};
|
||||
use Generator;
|
||||
use Message;
|
||||
use Random;
|
||||
|
||||
#[test]
|
||||
fn vrs_conversion() {
|
||||
|
@ -14,12 +14,10 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::{cmp, thread};
|
||||
use std::sync::Arc;
|
||||
use std::collections::VecDeque;
|
||||
use parking_lot::Mutex;
|
||||
use std::{cmp, collections::VecDeque, sync::Arc, thread};
|
||||
|
||||
use ethstore::{ethkey::Password, PresaleWallet, Error};
|
||||
use ethstore::{ethkey::Password, Error, PresaleWallet};
|
||||
use num_cpus;
|
||||
|
||||
pub fn run(passwords: VecDeque<Password>, wallet_path: &str) -> Result<(), Error> {
|
||||
@ -36,7 +34,9 @@ pub fn run(passwords: VecDeque<Password>, wallet_path: &str) -> Result<(), Error
|
||||
}
|
||||
|
||||
for handle in handles {
|
||||
handle.join().map_err(|err| Error::Custom(format!("Error finishing thread: {:?}", err)))?;
|
||||
handle
|
||||
.join()
|
||||
.map_err(|err| Error::Custom(format!("Error finishing thread: {:?}", err)))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -57,9 +57,9 @@ fn look_for_password(passwords: Arc<Mutex<VecDeque<Password>>>, wallet: PresaleW
|
||||
println!("Found password: {}", pass.as_str());
|
||||
passwords.lock().clear();
|
||||
return;
|
||||
},
|
||||
}
|
||||
_ if counter % 100 == 0 => print!("."),
|
||||
_ => {},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,14 +28,15 @@ extern crate env_logger;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::io::Read;
|
||||
use std::{env, process, fs, fmt};
|
||||
use std::{collections::VecDeque, env, fmt, fs, io::Read, process};
|
||||
|
||||
use docopt::Docopt;
|
||||
use ethstore::accounts_dir::{KeyDirectory, RootDiskDirectory};
|
||||
use ethstore::ethkey::{Address, Password};
|
||||
use ethstore::{EthStore, SimpleSecretStore, SecretStore, import_accounts, PresaleWallet, SecretVaultRef, StoreAccountRef};
|
||||
use ethstore::{
|
||||
accounts_dir::{KeyDirectory, RootDiskDirectory},
|
||||
ethkey::{Address, Password},
|
||||
import_accounts, EthStore, PresaleWallet, SecretStore, SecretVaultRef, SimpleSecretStore,
|
||||
StoreAccountRef,
|
||||
};
|
||||
|
||||
mod crack;
|
||||
|
||||
@ -171,7 +172,7 @@ fn key_dir(location: &str, password: Option<Password>) -> Result<Box<KeyDirector
|
||||
let chain = path.split('-').nth(1).unwrap_or("ethereum");
|
||||
let path = dir::parity(chain);
|
||||
RootDiskDirectory::create(path)?
|
||||
},
|
||||
}
|
||||
path => RootDiskDirectory::create(path)?,
|
||||
};
|
||||
|
||||
@ -188,7 +189,11 @@ fn open_args_vault(store: &EthStore, args: &Args) -> Result<SecretVaultRef, Erro
|
||||
Ok(SecretVaultRef::Vault(args.flag_vault.clone()))
|
||||
}
|
||||
|
||||
fn open_args_vault_account(store: &EthStore, address: Address, args: &Args) -> Result<StoreAccountRef, Error> {
|
||||
fn open_args_vault_account(
|
||||
store: &EthStore,
|
||||
address: Address,
|
||||
args: &Args,
|
||||
) -> Result<StoreAccountRef, Error> {
|
||||
match open_args_vault(store, args)? {
|
||||
SecretVaultRef::Root => Ok(StoreAccountRef::root(address)),
|
||||
SecretVaultRef::Vault(name) => Ok(StoreAccountRef::vault(&name, address)),
|
||||
@ -196,7 +201,8 @@ fn open_args_vault_account(store: &EthStore, address: Address, args: &Args) -> R
|
||||
}
|
||||
|
||||
fn format_accounts(accounts: &[Address]) -> String {
|
||||
accounts.iter()
|
||||
accounts
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, a)| format!("{:2}: 0x{:x}", i, a))
|
||||
.collect::<Vec<String>>()
|
||||
@ -208,32 +214,47 @@ fn format_vaults(vaults: &[String]) -> String {
|
||||
}
|
||||
|
||||
fn load_password(path: &str) -> Result<Password, Error> {
|
||||
let mut file = fs::File::open(path).map_err(|e| ethstore::Error::Custom(format!("Error opening password file '{}': {}", path, e)))?;
|
||||
let mut file = fs::File::open(path).map_err(|e| {
|
||||
ethstore::Error::Custom(format!("Error opening password file '{}': {}", path, e))
|
||||
})?;
|
||||
let mut password = String::new();
|
||||
file.read_to_string(&mut password).map_err(|e| ethstore::Error::Custom(format!("Error reading password file '{}': {}", path, e)))?;
|
||||
file.read_to_string(&mut password).map_err(|e| {
|
||||
ethstore::Error::Custom(format!("Error reading password file '{}': {}", path, e))
|
||||
})?;
|
||||
// drop EOF
|
||||
let _ = password.pop();
|
||||
Ok(password.into())
|
||||
}
|
||||
|
||||
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).deserialize())?;
|
||||
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).deserialize())?;
|
||||
|
||||
let store = EthStore::open(key_dir(&args.flag_dir, None)?)?;
|
||||
|
||||
return if args.cmd_insert {
|
||||
let secret = args.arg_secret.parse().map_err(|_| ethstore::Error::InvalidSecret)?;
|
||||
let secret = args
|
||||
.arg_secret
|
||||
.parse()
|
||||
.map_err(|_| ethstore::Error::InvalidSecret)?;
|
||||
let password = load_password(&args.arg_password)?;
|
||||
let vault_ref = open_args_vault(&store, &args)?;
|
||||
let account_ref = store.insert_account(vault_ref, secret, &password)?;
|
||||
Ok(format!("0x{:x}", account_ref.address))
|
||||
} else if args.cmd_change_pwd {
|
||||
let address = args.arg_address.parse().map_err(|_| ethstore::Error::InvalidAccount)?;
|
||||
let address = args
|
||||
.arg_address
|
||||
.parse()
|
||||
.map_err(|_| ethstore::Error::InvalidAccount)?;
|
||||
let old_pwd = load_password(&args.arg_old_pwd)?;
|
||||
let new_pwd = load_password(&args.arg_new_pwd)?;
|
||||
let account_ref = open_args_vault_account(&store, address, &args)?;
|
||||
let ok = store.change_password(&account_ref, &old_pwd, &new_pwd).is_ok();
|
||||
let ok = store
|
||||
.change_password(&account_ref, &old_pwd, &new_pwd)
|
||||
.is_ok();
|
||||
Ok(format!("{}", ok))
|
||||
} else if args.cmd_list {
|
||||
let vault_ref = open_args_vault(&store, &args)?;
|
||||
@ -247,7 +268,7 @@ fn execute<S, I>(command: I) -> Result<String, Error> where I: IntoIterator<Item
|
||||
} else if args.cmd_import {
|
||||
let password = match args.arg_password.as_ref() {
|
||||
"" => None,
|
||||
_ => Some(load_password(&args.arg_password)?)
|
||||
_ => Some(load_password(&args.arg_password)?),
|
||||
};
|
||||
let src = key_dir(&args.flag_src, password)?;
|
||||
let dst = key_dir(&args.flag_dir, None)?;
|
||||
@ -263,24 +284,40 @@ fn execute<S, I>(command: I) -> Result<String, Error> where I: IntoIterator<Item
|
||||
Ok(format!("0x{:x}", account_ref.address))
|
||||
} else if args.cmd_find_wallet_pass {
|
||||
let passwords = load_password(&args.arg_password)?;
|
||||
let passwords = passwords.as_str().lines().map(|line| str::to_owned(line).into()).collect::<VecDeque<_>>();
|
||||
let passwords = passwords
|
||||
.as_str()
|
||||
.lines()
|
||||
.map(|line| str::to_owned(line).into())
|
||||
.collect::<VecDeque<_>>();
|
||||
crack::run(passwords, &args.arg_path)?;
|
||||
Ok(format!("Password not found."))
|
||||
} else if args.cmd_remove {
|
||||
let address = args.arg_address.parse().map_err(|_| ethstore::Error::InvalidAccount)?;
|
||||
let address = args
|
||||
.arg_address
|
||||
.parse()
|
||||
.map_err(|_| ethstore::Error::InvalidAccount)?;
|
||||
let password = load_password(&args.arg_password)?;
|
||||
let account_ref = open_args_vault_account(&store, address, &args)?;
|
||||
let ok = store.remove_account(&account_ref, &password).is_ok();
|
||||
Ok(format!("{}", ok))
|
||||
} else if args.cmd_sign {
|
||||
let address = args.arg_address.parse().map_err(|_| ethstore::Error::InvalidAccount)?;
|
||||
let message = args.arg_message.parse().map_err(|_| ethstore::Error::InvalidMessage)?;
|
||||
let address = args
|
||||
.arg_address
|
||||
.parse()
|
||||
.map_err(|_| ethstore::Error::InvalidAccount)?;
|
||||
let message = args
|
||||
.arg_message
|
||||
.parse()
|
||||
.map_err(|_| ethstore::Error::InvalidMessage)?;
|
||||
let password = load_password(&args.arg_password)?;
|
||||
let account_ref = open_args_vault_account(&store, address, &args)?;
|
||||
let signature = store.sign(&account_ref, &password, &message)?;
|
||||
Ok(format!("0x{}", signature))
|
||||
} else if args.cmd_public {
|
||||
let address = args.arg_address.parse().map_err(|_| ethstore::Error::InvalidAccount)?;
|
||||
let address = args
|
||||
.arg_address
|
||||
.parse()
|
||||
.map_err(|_| ethstore::Error::InvalidAccount)?;
|
||||
let password = load_password(&args.arg_password)?;
|
||||
let account_ref = open_args_vault_account(&store, address, &args)?;
|
||||
let public = store.public(&account_ref, &password)?;
|
||||
@ -299,19 +336,28 @@ fn execute<S, I>(command: I) -> Result<String, Error> where I: IntoIterator<Item
|
||||
store.change_vault_password(&args.arg_vault, &new_pwd)?;
|
||||
Ok("OK".to_owned())
|
||||
} else if args.cmd_move_to_vault {
|
||||
let address = args.arg_address.parse().map_err(|_| ethstore::Error::InvalidAccount)?;
|
||||
let address = args
|
||||
.arg_address
|
||||
.parse()
|
||||
.map_err(|_| ethstore::Error::InvalidAccount)?;
|
||||
let password = load_password(&args.arg_password)?;
|
||||
let account_ref = open_args_vault_account(&store, address, &args)?;
|
||||
store.open_vault(&args.arg_vault, &password)?;
|
||||
store.change_account_vault(SecretVaultRef::Vault(args.arg_vault), account_ref)?;
|
||||
Ok("OK".to_owned())
|
||||
} else if args.cmd_move_from_vault {
|
||||
let address = args.arg_address.parse().map_err(|_| ethstore::Error::InvalidAccount)?;
|
||||
let address = args
|
||||
.arg_address
|
||||
.parse()
|
||||
.map_err(|_| ethstore::Error::InvalidAccount)?;
|
||||
let password = load_password(&args.arg_password)?;
|
||||
store.open_vault(&args.arg_vault, &password)?;
|
||||
store.change_account_vault(SecretVaultRef::Root, StoreAccountRef::vault(&args.arg_vault, address))?;
|
||||
store.change_account_vault(
|
||||
SecretVaultRef::Root,
|
||||
StoreAccountRef::vault(&args.arg_vault, address),
|
||||
)?;
|
||||
Ok("OK".to_owned())
|
||||
} else {
|
||||
Ok(format!("{}", USAGE))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -15,10 +15,8 @@
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
extern crate tempdir;
|
||||
use std::process::Command;
|
||||
use std::{fs::File, io::Write, process::Command};
|
||||
use tempdir::TempDir;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
|
||||
fn run(args: &[&str]) -> String {
|
||||
let output = Command::new("cargo")
|
||||
@ -32,10 +30,7 @@ fn run(args: &[&str]) -> String {
|
||||
|
||||
#[test]
|
||||
fn cli_cmd() {
|
||||
Command::new("cargo")
|
||||
.arg("build")
|
||||
.output()
|
||||
.unwrap();
|
||||
Command::new("cargo").arg("build").output().unwrap();
|
||||
|
||||
let dir = TempDir::new("test-vault").unwrap();
|
||||
|
||||
@ -51,32 +46,62 @@ fn cli_cmd() {
|
||||
let test_vault_addr_buf = dir.path().join("test-vault-addr");
|
||||
let test_vault_addr = test_vault_addr_buf.to_str().unwrap();
|
||||
|
||||
run(&["create-vault", "test-vault", test_password, "--dir", dir_str]);
|
||||
run(&[
|
||||
"create-vault",
|
||||
"test-vault",
|
||||
test_password,
|
||||
"--dir",
|
||||
dir_str,
|
||||
]);
|
||||
|
||||
let output = run(&["insert", "7d29fab185a33e2cd955812397354c472d2b84615b645aa135ff539f6b0d70d5",
|
||||
let output = run(&[
|
||||
"insert",
|
||||
"7d29fab185a33e2cd955812397354c472d2b84615b645aa135ff539f6b0d70d5",
|
||||
test_vault_addr,
|
||||
"--dir", dir_str,
|
||||
"--vault", "test-vault",
|
||||
"--vault-pwd", test_password]);
|
||||
"--dir",
|
||||
dir_str,
|
||||
"--vault",
|
||||
"test-vault",
|
||||
"--vault-pwd",
|
||||
test_password,
|
||||
]);
|
||||
let address = output.trim();
|
||||
|
||||
let output = run(&["list",
|
||||
"--dir", dir_str,
|
||||
"--vault", "test-vault",
|
||||
"--vault-pwd", test_password]);
|
||||
let output = run(&[
|
||||
"list",
|
||||
"--dir",
|
||||
dir_str,
|
||||
"--vault",
|
||||
"test-vault",
|
||||
"--vault-pwd",
|
||||
test_password,
|
||||
]);
|
||||
assert_eq!(output, " 0: 0xa8fa5dd30a87bb9e3288d604eb74949c515ab66e\n");
|
||||
|
||||
let output = run(&["sign", &address[2..],
|
||||
let output = run(&[
|
||||
"sign",
|
||||
&address[2..],
|
||||
test_vault_addr,
|
||||
"7d29fab185a33e2cd955812397354c472d2b84615b645aa135ff539f6b0d70d5",
|
||||
"--dir", dir_str,
|
||||
"--vault", "test-vault",
|
||||
"--vault-pwd", test_password]);
|
||||
"--dir",
|
||||
dir_str,
|
||||
"--vault",
|
||||
"test-vault",
|
||||
"--vault-pwd",
|
||||
test_password,
|
||||
]);
|
||||
assert_eq!(output, "0x54ab6e5cf0c5cb40043fdca5d15d611a3a94285414a076dafecc8dc9c04183f413296a3defff61092c0bb478dc9887ec01070e1275234211208fb8f4be4a9b0101\n");
|
||||
|
||||
let output = run(&["public", &address[2..], test_vault_addr,
|
||||
"--dir", dir_str,
|
||||
"--vault", "test-vault",
|
||||
"--vault-pwd", test_password]);
|
||||
let output = run(&[
|
||||
"public",
|
||||
&address[2..],
|
||||
test_vault_addr,
|
||||
"--dir",
|
||||
dir_str,
|
||||
"--vault",
|
||||
"test-vault",
|
||||
"--vault-pwd",
|
||||
test_password,
|
||||
]);
|
||||
assert_eq!(output, "0x35f222d88b80151857a2877826d940104887376a94c1cbd2c8c7c192eb701df88a18a4ecb8b05b1466c5b3706042027b5e079fe3a3683e66d822b0e047aa3418\n");
|
||||
}
|
||||
|
@ -28,16 +28,14 @@ pub enum Cipher {
|
||||
|
||||
impl From<json::Aes128Ctr> for Aes128Ctr {
|
||||
fn from(json: json::Aes128Ctr) -> Self {
|
||||
Aes128Ctr {
|
||||
iv: json.iv.into()
|
||||
}
|
||||
Aes128Ctr { iv: json.iv.into() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<json::Aes128Ctr> for Aes128Ctr {
|
||||
fn into(self) -> json::Aes128Ctr {
|
||||
json::Aes128Ctr {
|
||||
iv: From::from(self.iv)
|
||||
iv: From::from(self.iv),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,14 +14,14 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::str;
|
||||
use std::num::NonZeroU32;
|
||||
use account::{Aes128Ctr, Cipher, Kdf, Pbkdf2, Prf};
|
||||
use crypto::{self, Keccak256};
|
||||
use ethkey::{Password, Secret};
|
||||
use {json, Error, crypto};
|
||||
use crypto::Keccak256;
|
||||
use json;
|
||||
use random::Random;
|
||||
use smallvec::SmallVec;
|
||||
use account::{Cipher, Kdf, Aes128Ctr, Pbkdf2, Prf};
|
||||
use std::{num::NonZeroU32, str};
|
||||
use Error;
|
||||
|
||||
/// Encrypted data
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
@ -74,12 +74,20 @@ impl From<Crypto> for String {
|
||||
|
||||
impl Crypto {
|
||||
/// Encrypt account secret
|
||||
pub fn with_secret(secret: &Secret, password: &Password, iterations: NonZeroU32) -> Result<Self, crypto::Error> {
|
||||
pub fn with_secret(
|
||||
secret: &Secret,
|
||||
password: &Password,
|
||||
iterations: NonZeroU32,
|
||||
) -> Result<Self, crypto::Error> {
|
||||
Crypto::with_plain(&*secret, password, iterations)
|
||||
}
|
||||
|
||||
/// Encrypt custom plain data
|
||||
pub fn with_plain(plain: &[u8], password: &Password, iterations: NonZeroU32) -> Result<Self, crypto::Error> {
|
||||
pub fn with_plain(
|
||||
plain: &[u8],
|
||||
password: &Password,
|
||||
iterations: NonZeroU32,
|
||||
) -> Result<Self, crypto::Error> {
|
||||
let salt: [u8; 32] = Random::random();
|
||||
let iv: [u8; 16] = Random::random();
|
||||
|
||||
@ -100,9 +108,7 @@ impl Crypto {
|
||||
let mac = crypto::derive_mac(&derived_right_bits, &*ciphertext).keccak256();
|
||||
|
||||
Ok(Crypto {
|
||||
cipher: Cipher::Aes128Ctr(Aes128Ctr {
|
||||
iv: iv,
|
||||
}),
|
||||
cipher: Cipher::Aes128Ctr(Aes128Ctr { iv: iv }),
|
||||
ciphertext: ciphertext.into_vec(),
|
||||
kdf: Kdf::Pbkdf2(Pbkdf2 {
|
||||
dklen: crypto::KEY_LENGTH as u32,
|
||||
@ -132,14 +138,22 @@ impl Crypto {
|
||||
|
||||
fn do_decrypt(&self, password: &Password, expected_len: usize) -> Result<Vec<u8>, Error> {
|
||||
let (derived_left_bits, derived_right_bits) = match self.kdf {
|
||||
Kdf::Pbkdf2(ref params) => crypto::derive_key_iterations(password.as_bytes(), ¶ms.salt, params.c),
|
||||
Kdf::Scrypt(ref params) => crypto::scrypt::derive_key(password.as_bytes(), ¶ms.salt, params.n, params.p, params.r)?,
|
||||
Kdf::Pbkdf2(ref params) => {
|
||||
crypto::derive_key_iterations(password.as_bytes(), ¶ms.salt, params.c)
|
||||
}
|
||||
Kdf::Scrypt(ref params) => crypto::scrypt::derive_key(
|
||||
password.as_bytes(),
|
||||
¶ms.salt,
|
||||
params.n,
|
||||
params.p,
|
||||
params.r,
|
||||
)?,
|
||||
};
|
||||
|
||||
let mac = crypto::derive_mac(&derived_right_bits, &self.ciphertext).keccak256();
|
||||
|
||||
if !crypto::is_equal(&mac, &self.mac) {
|
||||
return Err(Error::InvalidPassword)
|
||||
return Err(Error::InvalidPassword);
|
||||
}
|
||||
|
||||
let mut plain: SmallVec<[u8; 32]> = SmallVec::from_vec(vec![0; expected_len]);
|
||||
@ -150,17 +164,22 @@ impl Crypto {
|
||||
debug_assert!(expected_len >= self.ciphertext.len());
|
||||
|
||||
let from = expected_len - self.ciphertext.len();
|
||||
crypto::aes::decrypt_128_ctr(&derived_left_bits, ¶ms.iv, &self.ciphertext, &mut plain[from..])?;
|
||||
crypto::aes::decrypt_128_ctr(
|
||||
&derived_left_bits,
|
||||
¶ms.iv,
|
||||
&self.ciphertext,
|
||||
&mut plain[from..],
|
||||
)?;
|
||||
Ok(plain.into_iter().collect())
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ethkey::{Generator, Random};
|
||||
use super::{Crypto, Error, NonZeroU32};
|
||||
use ethkey::{Generator, Random};
|
||||
|
||||
lazy_static! {
|
||||
static ref ITERATIONS: NonZeroU32 = NonZeroU32::new(10240).expect("10240 > 0; qed");
|
||||
@ -178,8 +197,12 @@ mod tests {
|
||||
#[test]
|
||||
fn crypto_with_secret_invalid_password() {
|
||||
let keypair = Random.generate().unwrap();
|
||||
let crypto = Crypto::with_secret(keypair.secret(), &"this is sparta".into(), *ITERATIONS).unwrap();
|
||||
assert_matches!(crypto.secret(&"this is sparta!".into()), Err(Error::InvalidPassword))
|
||||
let crypto =
|
||||
Crypto::with_secret(keypair.secret(), &"this is sparta".into(), *ITERATIONS).unwrap();
|
||||
assert_matches!(
|
||||
crypto.secret(&"this is sparta!".into()),
|
||||
Err(Error::InvalidPassword)
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -20,8 +20,10 @@ mod kdf;
|
||||
mod safe_account;
|
||||
mod version;
|
||||
|
||||
pub use self::cipher::{Cipher, Aes128Ctr};
|
||||
pub use self::crypto::Crypto;
|
||||
pub use self::kdf::{Kdf, Pbkdf2, Scrypt, Prf};
|
||||
pub use self::safe_account::SafeAccount;
|
||||
pub use self::version::Version;
|
||||
pub use self::{
|
||||
cipher::{Aes128Ctr, Cipher},
|
||||
crypto::Crypto,
|
||||
kdf::{Kdf, Pbkdf2, Prf, Scrypt},
|
||||
safe_account::SafeAccount,
|
||||
version::Version,
|
||||
};
|
||||
|
@ -14,13 +14,15 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use ethkey::{self, KeyPair, sign, Address, Password, Signature, Message, Public, Secret};
|
||||
use ethkey::crypto::ecdh::agree;
|
||||
use {json, Error};
|
||||
use super::crypto::Crypto;
|
||||
use account::Version;
|
||||
use crypto;
|
||||
use super::crypto::Crypto;
|
||||
use ethkey::{
|
||||
self, crypto::ecdh::agree, sign, Address, KeyPair, Message, Password, Public, Secret, Signature,
|
||||
};
|
||||
use json;
|
||||
use std::num::NonZeroU32;
|
||||
use Error;
|
||||
|
||||
/// Account representation.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
@ -62,7 +64,7 @@ impl SafeAccount {
|
||||
password: &Password,
|
||||
iterations: NonZeroU32,
|
||||
name: String,
|
||||
meta: String
|
||||
meta: String,
|
||||
) -> Result<Self, crypto::Error> {
|
||||
Ok(SafeAccount {
|
||||
id: id,
|
||||
@ -81,16 +83,25 @@ impl SafeAccount {
|
||||
/// In case `password` is provided, we will attempt to read the secret from the keyfile
|
||||
/// and derive the address from it instead of reading it directly.
|
||||
/// Providing password is required for `json::KeyFile`s with no address.
|
||||
pub fn from_file(json: json::KeyFile, filename: Option<String>, password: &Option<Password>) -> Result<Self, Error> {
|
||||
pub fn from_file(
|
||||
json: json::KeyFile,
|
||||
filename: Option<String>,
|
||||
password: &Option<Password>,
|
||||
) -> Result<Self, Error> {
|
||||
let crypto = Crypto::from(json.crypto);
|
||||
let address = match (password, &json.address) {
|
||||
(None, Some(json_address)) => json_address.into(),
|
||||
(None, None) => Err(Error::Custom(
|
||||
"This keystore does not contain address. You need to provide password to import it".into()))?,
|
||||
"This keystore does not contain address. You need to provide password to import it"
|
||||
.into(),
|
||||
))?,
|
||||
(Some(password), json_address) => {
|
||||
let derived_address = KeyPair::from_secret(
|
||||
crypto.secret(&password).map_err(|_| Error::InvalidPassword)?
|
||||
)?.address();
|
||||
crypto
|
||||
.secret(&password)
|
||||
.map_err(|_| Error::InvalidPassword)?,
|
||||
)?
|
||||
.address();
|
||||
|
||||
match json_address {
|
||||
Some(json_address) => {
|
||||
@ -99,8 +110,8 @@ impl SafeAccount {
|
||||
warn!("Detected address mismatch when opening an account. Derived: {:?}, in json got: {:?}",
|
||||
derived_address, json_address);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
derived_address
|
||||
}
|
||||
@ -120,29 +131,44 @@ impl SafeAccount {
|
||||
/// Create a new `SafeAccount` from the given vault `json`; if it was read from a
|
||||
/// file, the `filename` should be `Some` name. If it is as yet anonymous, then it
|
||||
/// can be left `None`.
|
||||
pub fn from_vault_file(password: &Password, json: json::VaultKeyFile, filename: Option<String>) -> Result<Self, Error> {
|
||||
pub fn from_vault_file(
|
||||
password: &Password,
|
||||
json: json::VaultKeyFile,
|
||||
filename: Option<String>,
|
||||
) -> Result<Self, Error> {
|
||||
let meta_crypto: Crypto = json.metacrypto.into();
|
||||
let meta_plain = meta_crypto.decrypt(password)?;
|
||||
let meta_plain = json::VaultKeyMeta::load(&meta_plain).map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
||||
let meta_plain =
|
||||
json::VaultKeyMeta::load(&meta_plain).map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
||||
|
||||
SafeAccount::from_file(json::KeyFile {
|
||||
SafeAccount::from_file(
|
||||
json::KeyFile {
|
||||
id: json.id,
|
||||
version: json.version,
|
||||
crypto: json.crypto,
|
||||
address: Some(meta_plain.address),
|
||||
name: meta_plain.name,
|
||||
meta: meta_plain.meta,
|
||||
}, filename, &None)
|
||||
},
|
||||
filename,
|
||||
&None,
|
||||
)
|
||||
}
|
||||
|
||||
/// Create a new `VaultKeyFile` from the given `self`
|
||||
pub fn into_vault_file(self, iterations: NonZeroU32, password: &Password) -> Result<json::VaultKeyFile, Error> {
|
||||
pub fn into_vault_file(
|
||||
self,
|
||||
iterations: NonZeroU32,
|
||||
password: &Password,
|
||||
) -> Result<json::VaultKeyFile, Error> {
|
||||
let meta_plain = json::VaultKeyMeta {
|
||||
address: self.address.into(),
|
||||
name: Some(self.name),
|
||||
meta: Some(self.meta),
|
||||
};
|
||||
let meta_plain = meta_plain.write().map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
||||
let meta_plain = meta_plain
|
||||
.write()
|
||||
.map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
||||
let meta_crypto = Crypto::with_plain(&meta_plain, password, iterations)?;
|
||||
|
||||
Ok(json::VaultKeyFile {
|
||||
@ -160,7 +186,12 @@ impl SafeAccount {
|
||||
}
|
||||
|
||||
/// Decrypt a message.
|
||||
pub fn decrypt(&self, password: &Password, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error> {
|
||||
pub fn decrypt(
|
||||
&self,
|
||||
password: &Password,
|
||||
shared_mac: &[u8],
|
||||
message: &[u8],
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
let secret = self.crypto.secret(password)?;
|
||||
ethkey::crypto::ecies::decrypt(&secret, shared_mac, message).map_err(From::from)
|
||||
}
|
||||
@ -178,7 +209,12 @@ impl SafeAccount {
|
||||
}
|
||||
|
||||
/// Change account's password.
|
||||
pub fn change_password(&self, old_password: &Password, new_password: &Password, iterations: NonZeroU32) -> Result<Self, Error> {
|
||||
pub fn change_password(
|
||||
&self,
|
||||
old_password: &Password,
|
||||
new_password: &Password,
|
||||
iterations: NonZeroU32,
|
||||
) -> Result<Self, Error> {
|
||||
let secret = self.crypto.secret(old_password)?;
|
||||
let result = SafeAccount {
|
||||
id: self.id.clone(),
|
||||
@ -200,20 +236,26 @@ impl SafeAccount {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ethkey::{Generator, Random, verify_public, Message};
|
||||
use super::{SafeAccount, NonZeroU32};
|
||||
use super::{NonZeroU32, SafeAccount};
|
||||
use ethkey::{verify_public, Generator, Message, Random};
|
||||
|
||||
lazy_static! {
|
||||
static ref ITERATIONS: NonZeroU32 = NonZeroU32::new(10240).expect("10240 > 0; qed");
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn sign_and_verify_public() {
|
||||
let keypair = Random.generate().unwrap();
|
||||
let password = "hello world".into();
|
||||
let message = Message::default();
|
||||
let account = SafeAccount::create(&keypair, [0u8; 16], &password, *ITERATIONS, "Test".to_owned(), "{}".to_owned());
|
||||
let account = SafeAccount::create(
|
||||
&keypair,
|
||||
[0u8; 16],
|
||||
&password,
|
||||
*ITERATIONS,
|
||||
"Test".to_owned(),
|
||||
"{}".to_owned(),
|
||||
);
|
||||
let signature = account.unwrap().sign(&password, &message).unwrap();
|
||||
assert!(verify_public(keypair.public(), &signature, &message).unwrap());
|
||||
}
|
||||
@ -224,8 +266,18 @@ mod tests {
|
||||
let first_password = "hello world".into();
|
||||
let sec_password = "this is sparta".into();
|
||||
let message = Message::default();
|
||||
let account = SafeAccount::create(&keypair, [0u8; 16], &first_password, *ITERATIONS, "Test".to_owned(), "{}".to_owned()).unwrap();
|
||||
let new_account = account.change_password(&first_password, &sec_password, *ITERATIONS).unwrap();
|
||||
let account = SafeAccount::create(
|
||||
&keypair,
|
||||
[0u8; 16],
|
||||
&first_password,
|
||||
*ITERATIONS,
|
||||
"Test".to_owned(),
|
||||
"{}".to_owned(),
|
||||
)
|
||||
.unwrap();
|
||||
let new_account = account
|
||||
.change_password(&first_password, &sec_password, *ITERATIONS)
|
||||
.unwrap();
|
||||
assert!(account.sign(&first_password, &message).is_ok());
|
||||
assert!(account.sign(&sec_password, &message).is_err());
|
||||
assert!(new_account.sign(&first_password, &message).is_err());
|
||||
|
@ -14,16 +14,21 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::{fs, io};
|
||||
use std::io::Write;
|
||||
use std::path::{PathBuf, Path};
|
||||
use std::collections::HashMap;
|
||||
use time;
|
||||
use {json, SafeAccount, Error};
|
||||
use json::Uuid;
|
||||
use super::{KeyDirectory, VaultKeyDirectory, VaultKeyDirectoryProvider, VaultKey};
|
||||
use super::vault::{VAULT_FILE_NAME, VaultDiskDirectory};
|
||||
use super::{
|
||||
vault::{VaultDiskDirectory, VAULT_FILE_NAME},
|
||||
KeyDirectory, VaultKey, VaultKeyDirectory, VaultKeyDirectoryProvider,
|
||||
};
|
||||
use ethkey::Password;
|
||||
use json::{self, Uuid};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs, io,
|
||||
io::Write,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use time;
|
||||
use Error;
|
||||
use SafeAccount;
|
||||
|
||||
const IGNORED_FILES: &'static [&'static str] = &[
|
||||
"thumbs.db",
|
||||
@ -35,7 +40,10 @@ const IGNORED_FILES: &'static [&'static str] = &[
|
||||
];
|
||||
|
||||
/// Find a unique filename that does not exist using four-letter random suffix.
|
||||
pub fn find_unique_filename_using_random_suffix(parent_path: &Path, original_filename: &str) -> io::Result<String> {
|
||||
pub fn find_unique_filename_using_random_suffix(
|
||||
parent_path: &Path,
|
||||
original_filename: &str,
|
||||
) -> io::Result<String> {
|
||||
let mut path = parent_path.join(original_filename);
|
||||
let mut deduped_filename = original_filename.to_string();
|
||||
|
||||
@ -45,7 +53,10 @@ pub fn find_unique_filename_using_random_suffix(parent_path: &Path, original_fil
|
||||
|
||||
while path.exists() {
|
||||
if retries >= MAX_RETRIES {
|
||||
return Err(io::Error::new(io::ErrorKind::Other, "Exceeded maximum retries when deduplicating filename."));
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Exceeded maximum retries when deduplicating filename.",
|
||||
));
|
||||
}
|
||||
|
||||
let suffix = ::random::random_string(4);
|
||||
@ -106,14 +117,21 @@ pub type RootDiskDirectory = DiskDirectory<DiskKeyFileManager>;
|
||||
/// Disk directory key file manager
|
||||
pub trait KeyFileManager: Send + Sync {
|
||||
/// Read `SafeAccount` from given key file stream
|
||||
fn read<T>(&self, filename: Option<String>, reader: T) -> Result<SafeAccount, Error> where T: io::Read;
|
||||
fn read<T>(&self, filename: Option<String>, reader: T) -> Result<SafeAccount, Error>
|
||||
where
|
||||
T: io::Read;
|
||||
|
||||
/// Write `SafeAccount` to given key file stream
|
||||
fn write<T>(&self, account: SafeAccount, writer: &mut T) -> Result<(), Error> where T: io::Write;
|
||||
fn write<T>(&self, account: SafeAccount, writer: &mut T) -> Result<(), Error>
|
||||
where
|
||||
T: io::Write;
|
||||
}
|
||||
|
||||
/// Disk-based keys directory implementation
|
||||
pub struct DiskDirectory<T> where T: KeyFileManager {
|
||||
pub struct DiskDirectory<T>
|
||||
where
|
||||
T: KeyFileManager,
|
||||
{
|
||||
path: PathBuf,
|
||||
key_manager: T,
|
||||
}
|
||||
@ -125,7 +143,10 @@ pub struct DiskKeyFileManager {
|
||||
}
|
||||
|
||||
impl RootDiskDirectory {
|
||||
pub fn create<P>(path: P) -> Result<Self, Error> where P: AsRef<Path> {
|
||||
pub fn create<P>(path: P) -> Result<Self, Error>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
fs::create_dir_all(&path)?;
|
||||
Ok(Self::at(path))
|
||||
}
|
||||
@ -135,14 +156,23 @@ impl RootDiskDirectory {
|
||||
DiskDirectory::new(&self.path, DiskKeyFileManager { password })
|
||||
}
|
||||
|
||||
pub fn at<P>(path: P) -> Self where P: AsRef<Path> {
|
||||
pub fn at<P>(path: P) -> Self
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
DiskDirectory::new(path, DiskKeyFileManager::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DiskDirectory<T> where T: KeyFileManager {
|
||||
impl<T> DiskDirectory<T>
|
||||
where
|
||||
T: KeyFileManager,
|
||||
{
|
||||
/// Create new disk directory instance
|
||||
pub fn new<P>(path: P, key_manager: T) -> Self where P: AsRef<Path> {
|
||||
pub fn new<P>(path: P, key_manager: T) -> Self
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
DiskDirectory {
|
||||
path: path.as_ref().to_path_buf(),
|
||||
key_manager: key_manager,
|
||||
@ -164,13 +194,11 @@ impl<T> DiskDirectory<T> where T: KeyFileManager {
|
||||
!IGNORED_FILES.contains(&&*name)
|
||||
})
|
||||
.map(|entry| entry.path())
|
||||
.collect::<Vec<PathBuf>>()
|
||||
)
|
||||
.collect::<Vec<PathBuf>>())
|
||||
}
|
||||
|
||||
pub fn files_hash(&self) -> Result<u64, Error> {
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::Hasher;
|
||||
use std::{collections::hash_map::DefaultHasher, hash::Hasher};
|
||||
|
||||
let mut hasher = DefaultHasher::new();
|
||||
let files = self.files()?;
|
||||
@ -183,7 +211,10 @@ impl<T> DiskDirectory<T> where T: KeyFileManager {
|
||||
|
||||
fn last_modification_date(&self) -> Result<u64, Error> {
|
||||
use std::time::{Duration, UNIX_EPOCH};
|
||||
let duration = fs::metadata(&self.path)?.modified()?.duration_since(UNIX_EPOCH).unwrap_or(Duration::default());
|
||||
let duration = fs::metadata(&self.path)?
|
||||
.modified()?
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or(Duration::default());
|
||||
let timestamp = duration.as_secs() ^ (duration.subsec_nanos() as u64);
|
||||
Ok(timestamp)
|
||||
}
|
||||
@ -196,7 +227,12 @@ impl<T> DiskDirectory<T> where T: KeyFileManager {
|
||||
Ok(paths
|
||||
.into_iter()
|
||||
.filter_map(|path| {
|
||||
let filename = Some(path.file_name().and_then(|n| n.to_str()).expect("Keys have valid UTF8 names only.").to_owned());
|
||||
let filename = Some(
|
||||
path.file_name()
|
||||
.and_then(|n| n.to_str())
|
||||
.expect("Keys have valid UTF8 names only.")
|
||||
.to_owned(),
|
||||
);
|
||||
fs::File::open(path.clone())
|
||||
.map_err(Into::into)
|
||||
.and_then(|file| self.key_manager.read(filename, file))
|
||||
@ -207,13 +243,17 @@ impl<T> DiskDirectory<T> where T: KeyFileManager {
|
||||
.map(|account| (path, account))
|
||||
.ok()
|
||||
})
|
||||
.collect()
|
||||
)
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// insert account with given filename. if the filename is a duplicate of any stored account and dedup is set to
|
||||
/// true, a random suffix is appended to the filename.
|
||||
pub fn insert_with_filename(&self, account: SafeAccount, mut filename: String, dedup: bool) -> Result<SafeAccount, Error> {
|
||||
pub fn insert_with_filename(
|
||||
&self,
|
||||
account: SafeAccount,
|
||||
mut filename: String,
|
||||
dedup: bool,
|
||||
) -> Result<SafeAccount, Error> {
|
||||
if dedup {
|
||||
filename = find_unique_filename_using_random_suffix(&self.path, &filename)?;
|
||||
}
|
||||
@ -235,7 +275,9 @@ impl<T> DiskDirectory<T> where T: KeyFileManager {
|
||||
};
|
||||
|
||||
// write key content
|
||||
self.key_manager.write(original_account, &mut file).map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
||||
self.key_manager
|
||||
.write(original_account, &mut file)
|
||||
.map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
||||
|
||||
file.flush()?;
|
||||
file.sync_all()?;
|
||||
@ -250,9 +292,13 @@ impl<T> DiskDirectory<T> where T: KeyFileManager {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> KeyDirectory for DiskDirectory<T> where T: KeyFileManager {
|
||||
impl<T> KeyDirectory for DiskDirectory<T>
|
||||
where
|
||||
T: KeyFileManager,
|
||||
{
|
||||
fn load(&self) -> Result<Vec<SafeAccount>, Error> {
|
||||
let accounts = self.files_content()?
|
||||
let accounts = self
|
||||
.files_content()?
|
||||
.into_iter()
|
||||
.map(|(_, account)| account)
|
||||
.collect();
|
||||
@ -273,18 +319,21 @@ impl<T> KeyDirectory for DiskDirectory<T> where T: KeyFileManager {
|
||||
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
|
||||
// enumerate all entries in keystore
|
||||
// and find entry with given address
|
||||
let to_remove = self.files_content()?
|
||||
let to_remove = self
|
||||
.files_content()?
|
||||
.into_iter()
|
||||
.find(|&(_, ref acc)| acc.id == account.id && acc.address == account.address);
|
||||
|
||||
// remove it
|
||||
match to_remove {
|
||||
None => Err(Error::InvalidAccount),
|
||||
Some((path, _)) => fs::remove_file(path).map_err(From::from)
|
||||
Some((path, _)) => fs::remove_file(path).map_err(From::from),
|
||||
}
|
||||
}
|
||||
|
||||
fn path(&self) -> Option<&PathBuf> { Some(&self.path) }
|
||||
fn path(&self) -> Option<&PathBuf> {
|
||||
Some(&self.path)
|
||||
}
|
||||
|
||||
fn as_vault_provider(&self) -> Option<&VaultKeyDirectoryProvider> {
|
||||
Some(self)
|
||||
@ -295,7 +344,10 @@ impl<T> KeyDirectory for DiskDirectory<T> where T: KeyFileManager {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> VaultKeyDirectoryProvider for DiskDirectory<T> where T: KeyFileManager {
|
||||
impl<T> VaultKeyDirectoryProvider for DiskDirectory<T>
|
||||
where
|
||||
T: KeyFileManager,
|
||||
{
|
||||
fn create(&self, name: &str, key: VaultKey) -> Result<Box<VaultKeyDirectory>, Error> {
|
||||
let vault_dir = VaultDiskDirectory::create(&self.path, name, key)?;
|
||||
Ok(Box::new(vault_dir))
|
||||
@ -313,7 +365,9 @@ impl<T> VaultKeyDirectoryProvider for DiskDirectory<T> where T: KeyFileManager {
|
||||
let mut vault_file_path = path.clone();
|
||||
vault_file_path.push(VAULT_FILE_NAME);
|
||||
if vault_file_path.is_file() {
|
||||
path.file_name().and_then(|f| f.to_str()).map(|f| f.to_owned())
|
||||
path.file_name()
|
||||
.and_then(|f| f.to_str())
|
||||
.map(|f| f.to_owned())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@ -327,26 +381,36 @@ impl<T> VaultKeyDirectoryProvider for DiskDirectory<T> where T: KeyFileManager {
|
||||
}
|
||||
|
||||
impl KeyFileManager for DiskKeyFileManager {
|
||||
fn read<T>(&self, filename: Option<String>, reader: T) -> Result<SafeAccount, Error> where T: io::Read {
|
||||
let key_file = json::KeyFile::load(reader).map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
||||
fn read<T>(&self, filename: Option<String>, reader: T) -> Result<SafeAccount, Error>
|
||||
where
|
||||
T: io::Read,
|
||||
{
|
||||
let key_file =
|
||||
json::KeyFile::load(reader).map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
||||
SafeAccount::from_file(key_file, filename, &self.password)
|
||||
}
|
||||
|
||||
fn write<T>(&self, mut account: SafeAccount, writer: &mut T) -> Result<(), Error> where T: io::Write {
|
||||
fn write<T>(&self, mut account: SafeAccount, writer: &mut T) -> Result<(), Error>
|
||||
where
|
||||
T: io::Write,
|
||||
{
|
||||
// when account is moved back to root directory from vault
|
||||
// => remove vault field from meta
|
||||
account.meta = json::remove_vault_name_from_json_meta(&account.meta)
|
||||
.map_err(|err| Error::Custom(format!("{:?}", err)))?;
|
||||
|
||||
let key_file: json::KeyFile = account.into();
|
||||
key_file.write(writer).map_err(|e| Error::Custom(format!("{:?}", e)))
|
||||
key_file
|
||||
.write(writer)
|
||||
.map_err(|e| Error::Custom(format!("{:?}", e)))
|
||||
}
|
||||
}
|
||||
|
||||
fn account_filename(account: &SafeAccount) -> String {
|
||||
// build file path
|
||||
account.filename.clone().unwrap_or_else(|| {
|
||||
let timestamp = time::strftime("%Y-%m-%dT%H-%M-%S", &time::now_utc()).expect("Time-format string is valid.");
|
||||
let timestamp = time::strftime("%Y-%m-%dT%H-%M-%S", &time::now_utc())
|
||||
.expect("Time-format string is valid.");
|
||||
format!("UTC--{}Z--{}", timestamp, Uuid::from(account.id))
|
||||
})
|
||||
}
|
||||
@ -355,12 +419,11 @@ fn account_filename(account: &SafeAccount) -> String {
|
||||
mod test {
|
||||
extern crate tempdir;
|
||||
|
||||
use std::{env, fs};
|
||||
use std::num::NonZeroU32;
|
||||
use self::tempdir::TempDir;
|
||||
use super::{KeyDirectory, RootDiskDirectory, VaultKey};
|
||||
use account::SafeAccount;
|
||||
use ethkey::{Random, Generator};
|
||||
use self::tempdir::TempDir;
|
||||
use ethkey::{Generator, Random};
|
||||
use std::{env, fs, num::NonZeroU32};
|
||||
|
||||
lazy_static! {
|
||||
static ref ITERATIONS: NonZeroU32 = NonZeroU32::new(1024).expect("1024 > 0; qed");
|
||||
@ -376,12 +439,22 @@ mod test {
|
||||
let directory = RootDiskDirectory::create(dir.clone()).unwrap();
|
||||
|
||||
// when
|
||||
let account = SafeAccount::create(&keypair, [0u8; 16], &password, *ITERATIONS, "Test".to_owned(), "{}".to_owned());
|
||||
let account = SafeAccount::create(
|
||||
&keypair,
|
||||
[0u8; 16],
|
||||
&password,
|
||||
*ITERATIONS,
|
||||
"Test".to_owned(),
|
||||
"{}".to_owned(),
|
||||
);
|
||||
let res = directory.insert(account.unwrap());
|
||||
|
||||
// then
|
||||
assert!(res.is_ok(), "Should save account succesfuly.");
|
||||
assert!(res.unwrap().filename.is_some(), "Filename has been assigned.");
|
||||
assert!(
|
||||
res.unwrap().filename.is_some(),
|
||||
"Filename has been assigned."
|
||||
);
|
||||
|
||||
// cleanup
|
||||
let _ = fs::remove_dir_all(dir);
|
||||
@ -397,14 +470,36 @@ mod test {
|
||||
let directory = RootDiskDirectory::create(dir.clone()).unwrap();
|
||||
|
||||
// when
|
||||
let account = SafeAccount::create(&keypair, [0u8; 16], &password, *ITERATIONS, "Test".to_owned(), "{}".to_owned()).unwrap();
|
||||
let account = SafeAccount::create(
|
||||
&keypair,
|
||||
[0u8; 16],
|
||||
&password,
|
||||
*ITERATIONS,
|
||||
"Test".to_owned(),
|
||||
"{}".to_owned(),
|
||||
)
|
||||
.unwrap();
|
||||
let filename = "test".to_string();
|
||||
let dedup = true;
|
||||
|
||||
directory.insert_with_filename(account.clone(), "foo".to_string(), dedup).unwrap();
|
||||
let file1 = directory.insert_with_filename(account.clone(), filename.clone(), dedup).unwrap().filename.unwrap();
|
||||
let file2 = directory.insert_with_filename(account.clone(), filename.clone(), dedup).unwrap().filename.unwrap();
|
||||
let file3 = directory.insert_with_filename(account.clone(), filename.clone(), dedup).unwrap().filename.unwrap();
|
||||
directory
|
||||
.insert_with_filename(account.clone(), "foo".to_string(), dedup)
|
||||
.unwrap();
|
||||
let file1 = directory
|
||||
.insert_with_filename(account.clone(), filename.clone(), dedup)
|
||||
.unwrap()
|
||||
.filename
|
||||
.unwrap();
|
||||
let file2 = directory
|
||||
.insert_with_filename(account.clone(), filename.clone(), dedup)
|
||||
.unwrap()
|
||||
.filename
|
||||
.unwrap();
|
||||
let file3 = directory
|
||||
.insert_with_filename(account.clone(), filename.clone(), dedup)
|
||||
.unwrap()
|
||||
.filename
|
||||
.unwrap();
|
||||
|
||||
// then
|
||||
// the first file should have the original names
|
||||
@ -433,7 +528,10 @@ mod test {
|
||||
|
||||
// and when
|
||||
let before_root_items_count = fs::read_dir(&dir).unwrap().count();
|
||||
let vault = directory.as_vault_provider().unwrap().create(vault_name, VaultKey::new(&password, *ITERATIONS));
|
||||
let vault = directory
|
||||
.as_vault_provider()
|
||||
.unwrap()
|
||||
.create(vault_name, VaultKey::new(&password, *ITERATIONS));
|
||||
|
||||
// then
|
||||
assert!(vault.is_ok());
|
||||
@ -441,7 +539,10 @@ mod test {
|
||||
assert!(after_root_items_count > before_root_items_count);
|
||||
|
||||
// and when
|
||||
let vault = directory.as_vault_provider().unwrap().open(vault_name, VaultKey::new(&password, *ITERATIONS));
|
||||
let vault = directory
|
||||
.as_vault_provider()
|
||||
.unwrap()
|
||||
.open(vault_name, VaultKey::new(&password, *ITERATIONS));
|
||||
|
||||
// then
|
||||
assert!(vault.is_ok());
|
||||
@ -459,8 +560,12 @@ mod test {
|
||||
let directory = RootDiskDirectory::create(&temp_path).unwrap();
|
||||
let vault_provider = directory.as_vault_provider().unwrap();
|
||||
let iter = NonZeroU32::new(1).expect("1 > 0; qed");
|
||||
vault_provider.create("vault1", VaultKey::new(&"password1".into(), iter)).unwrap();
|
||||
vault_provider.create("vault2", VaultKey::new(&"password2".into(), iter)).unwrap();
|
||||
vault_provider
|
||||
.create("vault1", VaultKey::new(&"password1".into(), iter))
|
||||
.unwrap();
|
||||
vault_provider
|
||||
.create("vault2", VaultKey::new(&"password2".into(), iter))
|
||||
.unwrap();
|
||||
|
||||
// then
|
||||
let vaults = vault_provider.list_vaults().unwrap();
|
||||
@ -474,19 +579,32 @@ mod test {
|
||||
let temp_path = TempDir::new("").unwrap();
|
||||
let directory = RootDiskDirectory::create(&temp_path).unwrap();
|
||||
|
||||
let hash = directory.files_hash().expect("Files hash should be calculated ok");
|
||||
assert_eq!(
|
||||
hash,
|
||||
15130871412783076140
|
||||
);
|
||||
let hash = directory
|
||||
.files_hash()
|
||||
.expect("Files hash should be calculated ok");
|
||||
assert_eq!(hash, 15130871412783076140);
|
||||
|
||||
let keypair = Random.generate().unwrap();
|
||||
let password = "test pass".into();
|
||||
let account = SafeAccount::create(&keypair, [0u8; 16], &password, *ITERATIONS, "Test".to_owned(), "{}".to_owned());
|
||||
directory.insert(account.unwrap()).expect("Account should be inserted ok");
|
||||
let account = SafeAccount::create(
|
||||
&keypair,
|
||||
[0u8; 16],
|
||||
&password,
|
||||
*ITERATIONS,
|
||||
"Test".to_owned(),
|
||||
"{}".to_owned(),
|
||||
);
|
||||
directory
|
||||
.insert(account.unwrap())
|
||||
.expect("Account should be inserted ok");
|
||||
|
||||
let new_hash = directory.files_hash().expect("New files hash should be calculated ok");
|
||||
let new_hash = directory
|
||||
.files_hash()
|
||||
.expect("New files hash should be calculated ok");
|
||||
|
||||
assert!(new_hash != hash, "hash of the file list should change once directory content changed");
|
||||
assert!(
|
||||
new_hash != hash,
|
||||
"hash of the file list should change once directory content changed"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -14,13 +14,14 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use parking_lot::RwLock;
|
||||
use itertools;
|
||||
use ethkey::Address;
|
||||
use itertools;
|
||||
use parking_lot::RwLock;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use {SafeAccount, Error};
|
||||
use super::KeyDirectory;
|
||||
use Error;
|
||||
use SafeAccount;
|
||||
|
||||
/// Accounts in-memory storage.
|
||||
#[derive(Default)]
|
||||
@ -68,7 +69,9 @@ impl KeyDirectory for MemoryDirectory {
|
||||
fn unique_repr(&self) -> Result<u64, Error> {
|
||||
let mut val = 0u64;
|
||||
let accounts = self.accounts.read();
|
||||
for acc in accounts.keys() { val = val ^ acc.low_u64() }
|
||||
for acc in accounts.keys() {
|
||||
val = val ^ acc.low_u64()
|
||||
}
|
||||
Ok(val)
|
||||
}
|
||||
}
|
||||
|
@ -17,9 +17,9 @@
|
||||
//! Accounts Directory
|
||||
|
||||
use ethkey::Password;
|
||||
use std::num::NonZeroU32;
|
||||
use std::path::{PathBuf};
|
||||
use {SafeAccount, Error};
|
||||
use std::{num::NonZeroU32, path::PathBuf};
|
||||
use Error;
|
||||
use SafeAccount;
|
||||
|
||||
mod disk;
|
||||
mod memory;
|
||||
@ -56,9 +56,13 @@ pub trait KeyDirectory: Send + Sync {
|
||||
/// Remove key from directory
|
||||
fn remove(&self, account: &SafeAccount) -> Result<(), Error>;
|
||||
/// Get directory filesystem path, if available
|
||||
fn path(&self) -> Option<&PathBuf> { None }
|
||||
fn path(&self) -> Option<&PathBuf> {
|
||||
None
|
||||
}
|
||||
/// Return vault provider, if available
|
||||
fn as_vault_provider(&self) -> Option<&VaultKeyDirectoryProvider> { None }
|
||||
fn as_vault_provider(&self) -> Option<&VaultKeyDirectoryProvider> {
|
||||
None
|
||||
}
|
||||
/// Unique representation of directory account collection
|
||||
fn unique_repr(&self) -> Result<u64, Error>;
|
||||
}
|
||||
@ -91,9 +95,11 @@ pub trait VaultKeyDirectory: KeyDirectory {
|
||||
fn set_meta(&self, meta: &str) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
pub use self::disk::{RootDiskDirectory, DiskKeyFileManager, KeyFileManager};
|
||||
pub use self::memory::MemoryDirectory;
|
||||
pub use self::vault::VaultDiskDirectory;
|
||||
pub use self::{
|
||||
disk::{DiskKeyFileManager, KeyFileManager, RootDiskDirectory},
|
||||
memory::MemoryDirectory,
|
||||
vault::VaultDiskDirectory,
|
||||
};
|
||||
|
||||
impl VaultKey {
|
||||
/// Create new vault key
|
||||
|
@ -14,14 +14,20 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::{fs, io};
|
||||
use std::path::{PathBuf, Path};
|
||||
use parking_lot::Mutex;
|
||||
use {json, SafeAccount, Error};
|
||||
use super::{
|
||||
super::account::Crypto,
|
||||
disk::{self, DiskDirectory, KeyFileManager},
|
||||
KeyDirectory, SetKeyError, VaultKey, VaultKeyDirectory,
|
||||
};
|
||||
use crypto::Keccak256;
|
||||
use super::super::account::Crypto;
|
||||
use super::{KeyDirectory, VaultKeyDirectory, VaultKey, SetKeyError};
|
||||
use super::disk::{self, DiskDirectory, KeyFileManager};
|
||||
use json;
|
||||
use parking_lot::Mutex;
|
||||
use std::{
|
||||
fs, io,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use Error;
|
||||
use SafeAccount;
|
||||
|
||||
/// Name of vault metadata file
|
||||
pub const VAULT_FILE_NAME: &'static str = "vault.json";
|
||||
@ -40,7 +46,10 @@ pub struct VaultKeyFileManager {
|
||||
|
||||
impl VaultDiskDirectory {
|
||||
/// Create new vault directory with given key
|
||||
pub fn create<P>(root: P, name: &str, key: VaultKey) -> Result<Self, Error> where P: AsRef<Path> {
|
||||
pub fn create<P>(root: P, name: &str, key: VaultKey) -> Result<Self, Error>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
// check that vault directory does not exists
|
||||
let vault_dir_path = make_vault_dir_path(root, name, true)?;
|
||||
if vault_dir_path.exists() {
|
||||
@ -55,11 +64,17 @@ impl VaultDiskDirectory {
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
Ok(DiskDirectory::new(vault_dir_path, VaultKeyFileManager::new(name, key, vault_meta)))
|
||||
Ok(DiskDirectory::new(
|
||||
vault_dir_path,
|
||||
VaultKeyFileManager::new(name, key, vault_meta),
|
||||
))
|
||||
}
|
||||
|
||||
/// Open existing vault directory with given key
|
||||
pub fn at<P>(root: P, name: &str, key: VaultKey) -> Result<Self, Error> where P: AsRef<Path> {
|
||||
pub fn at<P>(root: P, name: &str, key: VaultKey) -> Result<Self, Error>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
// check that vault directory exists
|
||||
let vault_dir_path = make_vault_dir_path(root, name, true)?;
|
||||
if !vault_dir_path.is_dir() {
|
||||
@ -69,11 +84,17 @@ impl VaultDiskDirectory {
|
||||
// check that passed key matches vault file
|
||||
let meta = read_vault_file(&vault_dir_path, Some(&key))?;
|
||||
|
||||
Ok(DiskDirectory::new(vault_dir_path, VaultKeyFileManager::new(name, key, &meta)))
|
||||
Ok(DiskDirectory::new(
|
||||
vault_dir_path,
|
||||
VaultKeyFileManager::new(name, key, &meta),
|
||||
))
|
||||
}
|
||||
|
||||
/// Read vault meta without actually opening the vault
|
||||
pub fn meta_at<P>(root: P, name: &str) -> Result<String, Error> where P: AsRef<Path> {
|
||||
pub fn meta_at<P>(root: P, name: &str) -> Result<String, Error>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
// check that vault directory exists
|
||||
let vault_dir_path = make_vault_dir_path(root, name, true)?;
|
||||
if !vault_dir_path.is_dir() {
|
||||
@ -85,7 +106,9 @@ impl VaultDiskDirectory {
|
||||
}
|
||||
|
||||
fn create_temp_vault(&self, key: VaultKey) -> Result<VaultDiskDirectory, Error> {
|
||||
let original_path = self.path().expect("self is instance of DiskDirectory; DiskDirectory always returns path; qed");
|
||||
let original_path = self
|
||||
.path()
|
||||
.expect("self is instance of DiskDirectory; DiskDirectory always returns path; qed");
|
||||
let mut path: PathBuf = original_path.clone();
|
||||
let name = self.name();
|
||||
|
||||
@ -105,7 +128,9 @@ impl VaultDiskDirectory {
|
||||
|
||||
fn copy_to_vault(&self, vault: &VaultDiskDirectory) -> Result<(), Error> {
|
||||
for account in self.load()? {
|
||||
let filename = account.filename.clone().expect("self is instance of DiskDirectory; DiskDirectory fills filename in load; qed");
|
||||
let filename = account.filename.clone().expect(
|
||||
"self is instance of DiskDirectory; DiskDirectory fills filename in load; qed",
|
||||
);
|
||||
vault.insert_with_filename(account, filename, true)?;
|
||||
}
|
||||
|
||||
@ -113,7 +138,9 @@ impl VaultDiskDirectory {
|
||||
}
|
||||
|
||||
fn delete(&self) -> Result<(), Error> {
|
||||
let path = self.path().expect("self is instance of DiskDirectory; DiskDirectory always returns path; qed");
|
||||
let path = self
|
||||
.path()
|
||||
.expect("self is instance of DiskDirectory; DiskDirectory always returns path; qed");
|
||||
fs::remove_dir_all(path).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
@ -132,18 +159,30 @@ impl VaultKeyDirectory for VaultDiskDirectory {
|
||||
}
|
||||
|
||||
fn set_key(&self, new_key: VaultKey) -> Result<(), SetKeyError> {
|
||||
let temp_vault = VaultDiskDirectory::create_temp_vault(self, new_key.clone()).map_err(|err| SetKeyError::NonFatalOld(err))?;
|
||||
let mut source_path = temp_vault.path().expect("temp_vault is instance of DiskDirectory; DiskDirectory always returns path; qed").clone();
|
||||
let mut target_path = self.path().expect("self is instance of DiskDirectory; DiskDirectory always returns path; qed").clone();
|
||||
let temp_vault = VaultDiskDirectory::create_temp_vault(self, new_key.clone())
|
||||
.map_err(|err| SetKeyError::NonFatalOld(err))?;
|
||||
let mut source_path = temp_vault
|
||||
.path()
|
||||
.expect(
|
||||
"temp_vault is instance of DiskDirectory; DiskDirectory always returns path; qed",
|
||||
)
|
||||
.clone();
|
||||
let mut target_path = self
|
||||
.path()
|
||||
.expect("self is instance of DiskDirectory; DiskDirectory always returns path; qed")
|
||||
.clone();
|
||||
|
||||
// preserve meta
|
||||
temp_vault.set_meta(&self.meta()).map_err(SetKeyError::NonFatalOld)?;
|
||||
temp_vault
|
||||
.set_meta(&self.meta())
|
||||
.map_err(SetKeyError::NonFatalOld)?;
|
||||
|
||||
// jump to next fs level
|
||||
source_path.push("next");
|
||||
target_path.push("next");
|
||||
|
||||
let temp_accounts = self.copy_to_vault(&temp_vault)
|
||||
let temp_accounts = self
|
||||
.copy_to_vault(&temp_vault)
|
||||
.and_then(|_| temp_vault.load())
|
||||
.map_err(|err| {
|
||||
// ignore error, as we already processing error
|
||||
@ -155,7 +194,9 @@ impl VaultKeyDirectory for VaultDiskDirectory {
|
||||
// original vault content has already been partially replaced
|
||||
// => when error or crash happens here, we can't do anything
|
||||
for temp_account in temp_accounts {
|
||||
let filename = temp_account.filename.expect("self is instance of DiskDirectory; DiskDirectory fills filename in load; qed");
|
||||
let filename = temp_account.filename.expect(
|
||||
"self is instance of DiskDirectory; DiskDirectory fills filename in load; qed",
|
||||
);
|
||||
source_path.set_file_name(&filename);
|
||||
target_path.set_file_name(&filename);
|
||||
fs::rename(&source_path, &target_path).map_err(|err| SetKeyError::Fatal(err.into()))?;
|
||||
@ -164,7 +205,9 @@ impl VaultKeyDirectory for VaultDiskDirectory {
|
||||
target_path.set_file_name(VAULT_FILE_NAME);
|
||||
fs::rename(source_path, target_path).map_err(|err| SetKeyError::Fatal(err.into()))?;
|
||||
|
||||
temp_vault.delete().map_err(|err| SetKeyError::NonFatalNew(err))
|
||||
temp_vault
|
||||
.delete()
|
||||
.map_err(|err| SetKeyError::NonFatalNew(err))
|
||||
}
|
||||
|
||||
fn meta(&self) -> String {
|
||||
@ -173,7 +216,9 @@ impl VaultKeyDirectory for VaultDiskDirectory {
|
||||
|
||||
fn set_meta(&self, meta: &str) -> Result<(), Error> {
|
||||
let key_manager = self.key_manager();
|
||||
let vault_path = self.path().expect("self is instance of DiskDirectory; DiskDirectory always returns path; qed");
|
||||
let vault_path = self
|
||||
.path()
|
||||
.expect("self is instance of DiskDirectory; DiskDirectory always returns path; qed");
|
||||
create_vault_file(vault_path, &key_manager.key, meta)?;
|
||||
*key_manager.meta.lock() = meta.to_owned();
|
||||
Ok(())
|
||||
@ -191,26 +236,40 @@ impl VaultKeyFileManager {
|
||||
}
|
||||
|
||||
impl KeyFileManager for VaultKeyFileManager {
|
||||
fn read<T>(&self, filename: Option<String>, reader: T) -> Result<SafeAccount, Error> where T: io::Read {
|
||||
let vault_file = json::VaultKeyFile::load(reader).map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
||||
let mut safe_account = SafeAccount::from_vault_file(&self.key.password, vault_file, filename.clone())?;
|
||||
fn read<T>(&self, filename: Option<String>, reader: T) -> Result<SafeAccount, Error>
|
||||
where
|
||||
T: io::Read,
|
||||
{
|
||||
let vault_file =
|
||||
json::VaultKeyFile::load(reader).map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
||||
let mut safe_account =
|
||||
SafeAccount::from_vault_file(&self.key.password, vault_file, filename.clone())?;
|
||||
|
||||
safe_account.meta = json::insert_vault_name_to_json_meta(&safe_account.meta, &self.name)
|
||||
.map_err(|err| Error::Custom(format!("{:?}", err)))?;
|
||||
Ok(safe_account)
|
||||
}
|
||||
|
||||
fn write<T>(&self, mut account: SafeAccount, writer: &mut T) -> Result<(), Error> where T: io::Write {
|
||||
fn write<T>(&self, mut account: SafeAccount, writer: &mut T) -> Result<(), Error>
|
||||
where
|
||||
T: io::Write,
|
||||
{
|
||||
account.meta = json::remove_vault_name_from_json_meta(&account.meta)
|
||||
.map_err(|err| Error::Custom(format!("{:?}", err)))?;
|
||||
|
||||
let vault_file: json::VaultKeyFile = account.into_vault_file(self.key.iterations, &self.key.password)?;
|
||||
vault_file.write(writer).map_err(|e| Error::Custom(format!("{:?}", e)))
|
||||
let vault_file: json::VaultKeyFile =
|
||||
account.into_vault_file(self.key.iterations, &self.key.password)?;
|
||||
vault_file
|
||||
.write(writer)
|
||||
.map_err(|e| Error::Custom(format!("{:?}", e)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes path to vault directory, checking that vault name is appropriate
|
||||
fn make_vault_dir_path<P>(root: P, name: &str, check_name: bool) -> Result<PathBuf, Error> where P: AsRef<Path> {
|
||||
fn make_vault_dir_path<P>(root: P, name: &str, check_name: bool) -> Result<PathBuf, Error>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
// check vault name
|
||||
if check_name && !check_vault_name(name) {
|
||||
return Err(Error::InvalidVaultName);
|
||||
@ -226,19 +285,24 @@ fn make_vault_dir_path<P>(root: P, name: &str, check_name: bool) -> Result<PathB
|
||||
/// => we only allow alphanumeric + separator characters in vault name.
|
||||
fn check_vault_name(name: &str) -> bool {
|
||||
!name.is_empty()
|
||||
&& name.chars()
|
||||
.all(|c| c.is_alphanumeric()
|
||||
|| c.is_whitespace()
|
||||
|| c == '-' || c == '_')
|
||||
&& name
|
||||
.chars()
|
||||
.all(|c| c.is_alphanumeric() || c.is_whitespace() || c == '-' || c == '_')
|
||||
}
|
||||
|
||||
/// Vault can be empty, but still must be pluggable => we store vault password in separate file
|
||||
fn create_vault_file<P>(vault_dir_path: P, key: &VaultKey, meta: &str) -> Result<(), Error> where P: AsRef<Path> {
|
||||
fn create_vault_file<P>(vault_dir_path: P, key: &VaultKey, meta: &str) -> Result<(), Error>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let password_hash = key.password.as_bytes().keccak256();
|
||||
let crypto = Crypto::with_plain(&password_hash, &key.password, key.iterations)?;
|
||||
|
||||
let vault_file_path = vault_dir_path.as_ref().join(VAULT_FILE_NAME);
|
||||
let temp_vault_file_name = disk::find_unique_filename_using_random_suffix(vault_dir_path.as_ref(), &VAULT_TEMP_FILE_NAME)?;
|
||||
let temp_vault_file_name = disk::find_unique_filename_using_random_suffix(
|
||||
vault_dir_path.as_ref(),
|
||||
&VAULT_TEMP_FILE_NAME,
|
||||
)?;
|
||||
let temp_vault_file_path = vault_dir_path.as_ref().join(&temp_vault_file_name);
|
||||
|
||||
// this method is used to rewrite existing vault file
|
||||
@ -248,7 +312,9 @@ fn create_vault_file<P>(vault_dir_path: P, key: &VaultKey, meta: &str) -> Result
|
||||
crypto: crypto.into(),
|
||||
meta: Some(meta.to_owned()),
|
||||
};
|
||||
vault_file_contents.write(&mut vault_file).map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
||||
vault_file_contents
|
||||
.write(&mut vault_file)
|
||||
.map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
||||
drop(vault_file);
|
||||
fs::rename(&temp_vault_file_path, &vault_file_path)?;
|
||||
|
||||
@ -256,12 +322,16 @@ fn create_vault_file<P>(vault_dir_path: P, key: &VaultKey, meta: &str) -> Result
|
||||
}
|
||||
|
||||
/// When vault is opened => we must check that password matches && read metadata
|
||||
fn read_vault_file<P>(vault_dir_path: P, key: Option<&VaultKey>) -> Result<String, Error> where P: AsRef<Path> {
|
||||
fn read_vault_file<P>(vault_dir_path: P, key: Option<&VaultKey>) -> Result<String, Error>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let mut vault_file_path: PathBuf = vault_dir_path.as_ref().into();
|
||||
vault_file_path.push(VAULT_FILE_NAME);
|
||||
|
||||
let vault_file = fs::File::open(vault_file_path)?;
|
||||
let vault_file_contents = json::VaultFile::load(vault_file).map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
||||
let vault_file_contents =
|
||||
json::VaultFile::load(vault_file).map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
||||
let vault_file_meta = vault_file_contents.meta.unwrap_or("{}".to_owned());
|
||||
let vault_file_crypto: Crypto = vault_file_contents.crypto.into();
|
||||
|
||||
@ -280,14 +350,12 @@ fn read_vault_file<P>(vault_dir_path: P, key: Option<&VaultKey>) -> Result<Strin
|
||||
mod test {
|
||||
extern crate tempdir;
|
||||
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::num::NonZeroU32;
|
||||
use std::path::PathBuf;
|
||||
use super::VaultKey;
|
||||
use super::{VAULT_FILE_NAME, check_vault_name, make_vault_dir_path, create_vault_file, read_vault_file, VaultDiskDirectory};
|
||||
use self::tempdir::TempDir;
|
||||
|
||||
use super::{
|
||||
check_vault_name, create_vault_file, make_vault_dir_path, read_vault_file,
|
||||
VaultDiskDirectory, VaultKey, VAULT_FILE_NAME,
|
||||
};
|
||||
use std::{fs, io::Write, num::NonZeroU32, path::PathBuf};
|
||||
|
||||
lazy_static! {
|
||||
static ref ITERATIONS: NonZeroU32 = NonZeroU32::new(1024).expect("1024 > 0; qed");
|
||||
@ -318,8 +386,14 @@ mod test {
|
||||
fn make_vault_dir_path_succeeds() {
|
||||
use std::path::Path;
|
||||
|
||||
assert_eq!(&make_vault_dir_path("/home/user/parity", "vault", true).unwrap(), &Path::new("/home/user/parity/vault"));
|
||||
assert_eq!(&make_vault_dir_path("/home/user/parity", "*bad-name*", false).unwrap(), &Path::new("/home/user/parity/*bad-name*"));
|
||||
assert_eq!(
|
||||
&make_vault_dir_path("/home/user/parity", "vault", true).unwrap(),
|
||||
&Path::new("/home/user/parity/vault")
|
||||
);
|
||||
assert_eq!(
|
||||
&make_vault_dir_path("/home/user/parity", "*bad-name*", false).unwrap(),
|
||||
&Path::new("/home/user/parity/*bad-name*")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -357,7 +431,9 @@ mod test {
|
||||
vault_file_path.push(VAULT_FILE_NAME);
|
||||
{
|
||||
let mut vault_file = fs::File::create(vault_file_path).unwrap();
|
||||
vault_file.write_all(vault_file_contents.as_bytes()).unwrap();
|
||||
vault_file
|
||||
.write_all(vault_file_contents.as_bytes())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// when
|
||||
@ -386,7 +462,9 @@ mod test {
|
||||
let vault_file_contents = r#"{"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"0155e3690be19fbfbecabcd440aa284b"},"ciphertext":"4d6938a1f49b7782","kdf":"pbkdf2","kdfparams":{"c":1024,"dklen":32,"prf":"hmac-sha256","salt":"b6a9338a7ccd39288a86dba73bfecd9101b4f3db9c9830e7c76afdbd4f6872e5"},"mac":"16381463ea11c6eb2239a9f339c2e780516d29d234ce30ac5f166f9080b5a262"}}"#;
|
||||
{
|
||||
let mut vault_file = fs::File::create(vault_file_path).unwrap();
|
||||
vault_file.write_all(vault_file_contents.as_bytes()).unwrap();
|
||||
vault_file
|
||||
.write_all(vault_file_contents.as_bytes())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// when
|
||||
|
@ -14,11 +14,9 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::fmt;
|
||||
use std::io::Error as IoError;
|
||||
use ethkey::{self, Error as EthKeyError};
|
||||
use crypto::{self, Error as EthCryptoError};
|
||||
use ethkey::DerivationError;
|
||||
use ethkey::{self, DerivationError, Error as EthKeyError};
|
||||
use std::{fmt, io::Error as IoError};
|
||||
|
||||
/// Account-related errors.
|
||||
#[derive(Debug)]
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -14,20 +14,25 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::path::Path;
|
||||
use std::fs;
|
||||
use std::{collections::HashSet, fs, path::Path};
|
||||
|
||||
use ethkey::Address;
|
||||
use accounts_dir::{KeyDirectory, RootDiskDirectory, DiskKeyFileManager, KeyFileManager};
|
||||
use accounts_dir::{DiskKeyFileManager, KeyDirectory, KeyFileManager, RootDiskDirectory};
|
||||
use dir;
|
||||
use ethkey::Address;
|
||||
use Error;
|
||||
|
||||
/// Import an account from a file.
|
||||
pub fn import_account(path: &Path, dst: &KeyDirectory) -> Result<Address, Error> {
|
||||
let key_manager = DiskKeyFileManager::default();
|
||||
let existing_accounts = dst.load()?.into_iter().map(|a| a.address).collect::<HashSet<_>>();
|
||||
let filename = path.file_name().and_then(|n| n.to_str()).map(|f| f.to_owned());
|
||||
let existing_accounts = dst
|
||||
.load()?
|
||||
.into_iter()
|
||||
.map(|a| a.address)
|
||||
.collect::<HashSet<_>>();
|
||||
let filename = path
|
||||
.file_name()
|
||||
.and_then(|n| n.to_str())
|
||||
.map(|f| f.to_owned());
|
||||
let account = fs::File::open(&path)
|
||||
.map_err(Into::into)
|
||||
.and_then(|file| key_manager.read(filename, file))?;
|
||||
@ -42,17 +47,21 @@ pub fn import_account(path: &Path, dst: &KeyDirectory) -> Result<Address, Error>
|
||||
/// Import all accounts from one directory to the other.
|
||||
pub fn import_accounts(src: &KeyDirectory, dst: &KeyDirectory) -> Result<Vec<Address>, Error> {
|
||||
let accounts = src.load()?;
|
||||
let existing_accounts = dst.load()?.into_iter()
|
||||
let existing_accounts = dst
|
||||
.load()?
|
||||
.into_iter()
|
||||
.map(|a| a.address)
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
accounts.into_iter()
|
||||
accounts
|
||||
.into_iter()
|
||||
.filter(|a| !existing_accounts.contains(&a.address))
|
||||
.map(|a| {
|
||||
let address = a.address.clone();
|
||||
dst.insert(a)?;
|
||||
Ok(address)
|
||||
}).collect()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Provide a `HashSet` of all accounts available for import from the Geth keystore.
|
||||
@ -64,17 +73,27 @@ pub fn read_geth_accounts(testnet: bool) -> Vec<Address> {
|
||||
}
|
||||
|
||||
/// Import specific `desired` accounts from the Geth keystore into `dst`.
|
||||
pub fn import_geth_accounts(dst: &KeyDirectory, desired: HashSet<Address>, testnet: bool) -> Result<Vec<Address>, Error> {
|
||||
pub fn import_geth_accounts(
|
||||
dst: &KeyDirectory,
|
||||
desired: HashSet<Address>,
|
||||
testnet: bool,
|
||||
) -> Result<Vec<Address>, Error> {
|
||||
let src = RootDiskDirectory::at(dir::geth(testnet));
|
||||
let accounts = src.load()?;
|
||||
let existing_accounts = dst.load()?.into_iter().map(|a| a.address).collect::<HashSet<_>>();
|
||||
let existing_accounts = dst
|
||||
.load()?
|
||||
.into_iter()
|
||||
.map(|a| a.address)
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
accounts.into_iter()
|
||||
accounts
|
||||
.into_iter()
|
||||
.filter(|a| !existing_accounts.contains(&a.address))
|
||||
.filter(|a| desired.contains(&a.address))
|
||||
.map(|a| {
|
||||
let address = a.address.clone();
|
||||
dst.insert(a)?;
|
||||
Ok(address)
|
||||
}).collect()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
@ -14,10 +14,9 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use rustc_hex::{FromHex, FromHexError, ToHex};
|
||||
use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::{ops, str};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use serde::de::Error;
|
||||
use rustc_hex::{ToHex, FromHex, FromHexError};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Bytes(Vec<u8>);
|
||||
@ -32,17 +31,22 @@ impl ops::Deref for Bytes {
|
||||
|
||||
impl<'a> Deserialize<'a> for Bytes {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where D: Deserializer<'a>
|
||||
where
|
||||
D: Deserializer<'a>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
let data = s.from_hex().map_err(|e| Error::custom(format!("Invalid hex value {}", e)))?;
|
||||
let data = s
|
||||
.from_hex()
|
||||
.map_err(|e| Error::custom(format!("Invalid hex value {}", e)))?;
|
||||
Ok(Bytes(data))
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Bytes {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where S: Serializer {
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.0.to_hex())
|
||||
}
|
||||
}
|
||||
@ -57,7 +61,11 @@ impl str::FromStr for Bytes {
|
||||
|
||||
impl From<&'static str> for Bytes {
|
||||
fn from(s: &'static str) -> Self {
|
||||
s.parse().expect(&format!("invalid string literal for {}: '{}'", stringify!(Self), s))
|
||||
s.parse().expect(&format!(
|
||||
"invalid string literal for {}: '{}'",
|
||||
stringify!(Self),
|
||||
s
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,10 +14,12 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::fmt;
|
||||
use serde::{Serialize, Serializer, Deserialize, Deserializer};
|
||||
use serde::de::{Visitor, Error as SerdeError};
|
||||
use super::{Error, H128};
|
||||
use serde::{
|
||||
de::{Error as SerdeError, Visitor},
|
||||
Deserialize, Deserializer, Serialize, Serializer,
|
||||
};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum CipherSer {
|
||||
@ -26,7 +28,9 @@ pub enum CipherSer {
|
||||
|
||||
impl Serialize for CipherSer {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where S: Serializer {
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match *self {
|
||||
CipherSer::Aes128Ctr => serializer.serialize_str("aes-128-ctr"),
|
||||
}
|
||||
@ -35,7 +39,9 @@ impl Serialize for CipherSer {
|
||||
|
||||
impl<'a> Deserialize<'a> for CipherSer {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where D: Deserializer<'a> {
|
||||
where
|
||||
D: Deserializer<'a>,
|
||||
{
|
||||
deserializer.deserialize_any(CipherSerVisitor)
|
||||
}
|
||||
}
|
||||
@ -49,14 +55,20 @@ impl<'a> Visitor<'a> for CipherSerVisitor {
|
||||
write!(formatter, "a valid cipher identifier")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> where E: SerdeError {
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: SerdeError,
|
||||
{
|
||||
match value {
|
||||
"aes-128-ctr" => Ok(CipherSer::Aes128Ctr),
|
||||
_ => Err(SerdeError::custom(Error::UnsupportedCipher))
|
||||
_ => Err(SerdeError::custom(Error::UnsupportedCipher)),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_string<E>(self, value: String) -> Result<Self::Value, E> where E: SerdeError {
|
||||
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
|
||||
where
|
||||
E: SerdeError,
|
||||
{
|
||||
self.visit_str(value.as_ref())
|
||||
}
|
||||
}
|
||||
@ -73,7 +85,9 @@ pub enum CipherSerParams {
|
||||
|
||||
impl Serialize for CipherSerParams {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where S: Serializer {
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match *self {
|
||||
CipherSerParams::Aes128Ctr(ref params) => params.serialize(serializer),
|
||||
}
|
||||
@ -82,7 +96,9 @@ impl Serialize for CipherSerParams {
|
||||
|
||||
impl<'a> Deserialize<'a> for CipherSerParams {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where D: Deserializer<'a> {
|
||||
where
|
||||
D: Deserializer<'a>,
|
||||
{
|
||||
Aes128Ctr::deserialize(deserializer)
|
||||
.map(CipherSerParams::Aes128Ctr)
|
||||
.map_err(|_| Error::InvalidCipherParams)
|
||||
|
@ -14,12 +14,14 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::{fmt, str};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use serde::ser::SerializeStruct;
|
||||
use serde::de::{Visitor, MapAccess, Error};
|
||||
use super::{Bytes, Cipher, CipherSer, CipherSerParams, Kdf, KdfSer, KdfSerParams, H256};
|
||||
use serde::{
|
||||
de::{Error, MapAccess, Visitor},
|
||||
ser::SerializeStruct,
|
||||
Deserialize, Deserializer, Serialize, Serializer,
|
||||
};
|
||||
use serde_json;
|
||||
use super::{Cipher, CipherSer, CipherSerParams, Kdf, KdfSer, KdfSerParams, H256, Bytes};
|
||||
use std::{fmt, str};
|
||||
|
||||
pub type CipherText = Bytes;
|
||||
|
||||
@ -41,7 +43,8 @@ impl str::FromStr for Crypto {
|
||||
|
||||
impl From<Crypto> for String {
|
||||
fn from(c: Crypto) -> Self {
|
||||
serde_json::to_string(&c).expect("serialization cannot fail, cause all crypto keys are strings")
|
||||
serde_json::to_string(&c)
|
||||
.expect("serialization cannot fail, cause all crypto keys are strings")
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,7 +60,8 @@ enum CryptoField {
|
||||
|
||||
impl<'a> Deserialize<'a> for CryptoField {
|
||||
fn deserialize<D>(deserializer: D) -> Result<CryptoField, D::Error>
|
||||
where D: Deserializer<'a>
|
||||
where
|
||||
D: Deserializer<'a>,
|
||||
{
|
||||
deserializer.deserialize_any(CryptoFieldVisitor)
|
||||
}
|
||||
@ -73,7 +77,8 @@ impl<'a> Visitor<'a> for CryptoFieldVisitor {
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||
where E: Error
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
match value {
|
||||
"cipher" => Ok(CryptoField::Cipher),
|
||||
@ -90,7 +95,8 @@ impl<'a> Visitor<'a> for CryptoFieldVisitor {
|
||||
|
||||
impl<'a> Deserialize<'a> for Crypto {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Crypto, D::Error>
|
||||
where D: Deserializer<'a>
|
||||
where
|
||||
D: Deserializer<'a>,
|
||||
{
|
||||
static FIELDS: &'static [&'static str] = &["id", "version", "crypto", "Crypto", "address"];
|
||||
deserializer.deserialize_struct("Crypto", FIELDS, CryptoVisitor)
|
||||
@ -107,7 +113,8 @@ impl<'a> Visitor<'a> for CryptoVisitor {
|
||||
}
|
||||
|
||||
fn visit_map<V>(self, mut visitor: V) -> Result<Self::Value, V::Error>
|
||||
where V: MapAccess<'a>
|
||||
where
|
||||
V: MapAccess<'a>,
|
||||
{
|
||||
let mut cipher = None;
|
||||
let mut cipherparams = None;
|
||||
@ -118,20 +125,36 @@ impl<'a> Visitor<'a> for CryptoVisitor {
|
||||
|
||||
loop {
|
||||
match visitor.next_key()? {
|
||||
Some(CryptoField::Cipher) => { cipher = Some(visitor.next_value()?); }
|
||||
Some(CryptoField::CipherParams) => { cipherparams = Some(visitor.next_value()?); }
|
||||
Some(CryptoField::CipherText) => { ciphertext = Some(visitor.next_value()?); }
|
||||
Some(CryptoField::Kdf) => { kdf = Some(visitor.next_value()?); }
|
||||
Some(CryptoField::KdfParams) => { kdfparams = Some(visitor.next_value()?); }
|
||||
Some(CryptoField::Mac) => { mac = Some(visitor.next_value()?); }
|
||||
Some(CryptoField::Cipher) => {
|
||||
cipher = Some(visitor.next_value()?);
|
||||
}
|
||||
Some(CryptoField::CipherParams) => {
|
||||
cipherparams = Some(visitor.next_value()?);
|
||||
}
|
||||
Some(CryptoField::CipherText) => {
|
||||
ciphertext = Some(visitor.next_value()?);
|
||||
}
|
||||
Some(CryptoField::Kdf) => {
|
||||
kdf = Some(visitor.next_value()?);
|
||||
}
|
||||
Some(CryptoField::KdfParams) => {
|
||||
kdfparams = Some(visitor.next_value()?);
|
||||
}
|
||||
Some(CryptoField::Mac) => {
|
||||
mac = Some(visitor.next_value()?);
|
||||
}
|
||||
// skip not required version field (it appears in pyethereum generated keystores)
|
||||
Some(CryptoField::Version) => { visitor.next_value().unwrap_or(()) }
|
||||
None => { break; }
|
||||
Some(CryptoField::Version) => visitor.next_value().unwrap_or(()),
|
||||
None => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let cipher = match (cipher, cipherparams) {
|
||||
(Some(CipherSer::Aes128Ctr), Some(CipherSerParams::Aes128Ctr(params))) => Cipher::Aes128Ctr(params),
|
||||
(Some(CipherSer::Aes128Ctr), Some(CipherSerParams::Aes128Ctr(params))) => {
|
||||
Cipher::Aes128Ctr(params)
|
||||
}
|
||||
(None, _) => return Err(V::Error::missing_field("cipher")),
|
||||
(Some(_), None) => return Err(V::Error::missing_field("cipherparams")),
|
||||
};
|
||||
@ -167,25 +190,26 @@ impl<'a> Visitor<'a> for CryptoVisitor {
|
||||
|
||||
impl Serialize for Crypto {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where S: Serializer
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut crypto = serializer.serialize_struct("Crypto", 6)?;
|
||||
match self.cipher {
|
||||
Cipher::Aes128Ctr(ref params) => {
|
||||
crypto.serialize_field("cipher", &CipherSer::Aes128Ctr)?;
|
||||
crypto.serialize_field("cipherparams", params)?;
|
||||
},
|
||||
}
|
||||
}
|
||||
crypto.serialize_field("ciphertext", &self.ciphertext)?;
|
||||
match self.kdf {
|
||||
Kdf::Pbkdf2(ref params) => {
|
||||
crypto.serialize_field("kdf", &KdfSer::Pbkdf2)?;
|
||||
crypto.serialize_field("kdfparams", params)?;
|
||||
},
|
||||
}
|
||||
Kdf::Scrypt(ref params) => {
|
||||
crypto.serialize_field("kdf", &KdfSer::Scrypt)?;
|
||||
crypto.serialize_field("kdfparams", params)?;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
crypto.serialize_field("mac", &self.mac)?;
|
||||
|
@ -14,11 +14,13 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::{ops, fmt, str};
|
||||
use rustc_hex::{FromHex, ToHex};
|
||||
use serde::{Serialize, Serializer, Deserialize, Deserializer};
|
||||
use serde::de::{Visitor, Error as SerdeError};
|
||||
use super::Error;
|
||||
use rustc_hex::{FromHex, ToHex};
|
||||
use serde::{
|
||||
de::{Error as SerdeError, Visitor},
|
||||
Deserialize, Deserializer, Serialize, Serializer,
|
||||
};
|
||||
use std::{fmt, ops, str};
|
||||
|
||||
macro_rules! impl_hash {
|
||||
($name: ident, $size: expr) => {
|
||||
@ -49,14 +51,18 @@ macro_rules! impl_hash {
|
||||
|
||||
impl Serialize for $name {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where S: Serializer {
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.0.to_hex())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deserialize<'a> for $name {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where D: Deserializer<'a> {
|
||||
where
|
||||
D: Deserializer<'a>,
|
||||
{
|
||||
struct HashVisitor;
|
||||
|
||||
impl<'b> Visitor<'b> for HashVisitor {
|
||||
@ -66,11 +72,17 @@ macro_rules! impl_hash {
|
||||
write!(formatter, "a hex-encoded {}", stringify!($name))
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> where E: SerdeError {
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: SerdeError,
|
||||
{
|
||||
value.parse().map_err(SerdeError::custom)
|
||||
}
|
||||
|
||||
fn visit_string<E>(self, value: String) -> Result<Self::Value, E> where E: SerdeError {
|
||||
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
|
||||
where
|
||||
E: SerdeError,
|
||||
{
|
||||
self.visit_str(value.as_ref())
|
||||
}
|
||||
}
|
||||
@ -96,7 +108,11 @@ macro_rules! impl_hash {
|
||||
|
||||
impl From<&'static str> for $name {
|
||||
fn from(s: &'static str) -> Self {
|
||||
s.parse().expect(&format!("invalid string literal for {}: '{}'", stringify!($name), s))
|
||||
s.parse().expect(&format!(
|
||||
"invalid string literal for {}: '{}'",
|
||||
stringify!($name),
|
||||
s
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,7 +127,7 @@ macro_rules! impl_hash {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_hash!(H128, 16);
|
||||
|
@ -15,11 +15,13 @@
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Universaly unique identifier.
|
||||
use std::{fmt, str};
|
||||
use rustc_hex::{ToHex, FromHex};
|
||||
use serde::{Deserialize, Serialize, Deserializer, Serializer};
|
||||
use serde::de::{Visitor, Error as SerdeError};
|
||||
use super::Error;
|
||||
use rustc_hex::{FromHex, ToHex};
|
||||
use serde::{
|
||||
de::{Error as SerdeError, Visitor},
|
||||
Deserialize, Deserializer, Serialize, Serializer,
|
||||
};
|
||||
use std::{fmt, str};
|
||||
|
||||
/// Universaly unique identifier.
|
||||
#[derive(Debug, PartialEq)]
|
||||
@ -38,7 +40,11 @@ impl<'a> Into<String> for &'a Uuid {
|
||||
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("-")
|
||||
[d1, d2, d3, d4, d5]
|
||||
.into_iter()
|
||||
.map(|d| d.to_hex())
|
||||
.collect::<Vec<String>>()
|
||||
.join("-")
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,13 +102,19 @@ impl str::FromStr for Uuid {
|
||||
|
||||
impl From<&'static str> for Uuid {
|
||||
fn from(s: &'static str) -> Self {
|
||||
s.parse().expect(&format!("invalid string literal for {}: '{}'", stringify!(Self), s))
|
||||
s.parse().expect(&format!(
|
||||
"invalid string literal for {}: '{}'",
|
||||
stringify!(Self),
|
||||
s
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Uuid {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where S: Serializer {
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let s: String = self.into();
|
||||
serializer.serialize_str(&s)
|
||||
}
|
||||
@ -110,7 +122,9 @@ impl Serialize for Uuid {
|
||||
|
||||
impl<'a> Deserialize<'a> for Uuid {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where D: Deserializer<'a> {
|
||||
where
|
||||
D: Deserializer<'a>,
|
||||
{
|
||||
deserializer.deserialize_any(UuidVisitor)
|
||||
}
|
||||
}
|
||||
@ -124,11 +138,17 @@ impl<'a> Visitor<'a> for UuidVisitor {
|
||||
write!(formatter, "a valid hex-encoded UUID")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> where E: SerdeError {
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: SerdeError,
|
||||
{
|
||||
value.parse().map_err(SerdeError::custom)
|
||||
}
|
||||
|
||||
fn visit_string<E>(self, value: String) -> Result<Self::Value, E> where E: SerdeError {
|
||||
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
|
||||
where
|
||||
E: SerdeError,
|
||||
{
|
||||
self.visit_str(value.as_ref())
|
||||
}
|
||||
}
|
||||
@ -140,7 +160,13 @@ mod tests {
|
||||
#[test]
|
||||
fn uuid_from_str() {
|
||||
let uuid: Uuid = "3198bc9c-6672-5ab3-d995-4942343ae5b6".into();
|
||||
assert_eq!(uuid, Uuid::from([0x31, 0x98, 0xbc, 0x9c, 0x66, 0x72, 0x5a, 0xb3, 0xd9, 0x95, 0x49, 0x42, 0x34, 0x3a, 0xe5, 0xb6]));
|
||||
assert_eq!(
|
||||
uuid,
|
||||
Uuid::from([
|
||||
0x31, 0x98, 0xbc, 0x9c, 0x66, 0x72, 0x5a, 0xb3, 0xd9, 0x95, 0x49, 0x42, 0x34, 0x3a,
|
||||
0xe5, 0xb6
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -14,11 +14,12 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::fmt;
|
||||
use std::num::NonZeroU32;
|
||||
use serde::{Serialize, Serializer, Deserialize, Deserializer};
|
||||
use serde::de::{Visitor, Error as SerdeError};
|
||||
use super::{Error, Bytes};
|
||||
use super::{Bytes, Error};
|
||||
use serde::{
|
||||
de::{Error as SerdeError, Visitor},
|
||||
Deserialize, Deserializer, Serialize, Serializer,
|
||||
};
|
||||
use std::{fmt, num::NonZeroU32};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum KdfSer {
|
||||
@ -28,7 +29,9 @@ pub enum KdfSer {
|
||||
|
||||
impl Serialize for KdfSer {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where S: Serializer {
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match *self {
|
||||
KdfSer::Pbkdf2 => serializer.serialize_str("pbkdf2"),
|
||||
KdfSer::Scrypt => serializer.serialize_str("scrypt"),
|
||||
@ -38,7 +41,9 @@ impl Serialize for KdfSer {
|
||||
|
||||
impl<'a> Deserialize<'a> for KdfSer {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where D: Deserializer<'a> {
|
||||
where
|
||||
D: Deserializer<'a>,
|
||||
{
|
||||
deserializer.deserialize_any(KdfSerVisitor)
|
||||
}
|
||||
}
|
||||
@ -52,15 +57,21 @@ impl<'a> Visitor<'a> for KdfSerVisitor {
|
||||
write!(formatter, "a kdf algorithm identifier")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> where E: SerdeError {
|
||||
fn visit_str<E>(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))
|
||||
_ => Err(SerdeError::custom(Error::UnsupportedKdf)),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_string<E>(self, value: String) -> Result<Self::Value, E> where E: SerdeError {
|
||||
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
|
||||
where
|
||||
E: SerdeError,
|
||||
{
|
||||
self.visit_str(value.as_ref())
|
||||
}
|
||||
}
|
||||
@ -72,7 +83,9 @@ pub enum Prf {
|
||||
|
||||
impl Serialize for Prf {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where S: Serializer {
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match *self {
|
||||
Prf::HmacSha256 => serializer.serialize_str("hmac-sha256"),
|
||||
}
|
||||
@ -81,7 +94,9 @@ impl Serialize for Prf {
|
||||
|
||||
impl<'a> Deserialize<'a> for Prf {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where D: Deserializer<'a> {
|
||||
where
|
||||
D: Deserializer<'a>,
|
||||
{
|
||||
deserializer.deserialize_any(PrfVisitor)
|
||||
}
|
||||
}
|
||||
@ -95,14 +110,20 @@ impl<'a> Visitor<'a> for PrfVisitor {
|
||||
write!(formatter, "a prf algorithm identifier")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> where E: SerdeError {
|
||||
fn visit_str<E>(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>(self, value: String) -> Result<Self::Value, E> where E: SerdeError {
|
||||
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
|
||||
where
|
||||
E: SerdeError,
|
||||
{
|
||||
self.visit_str(value.as_ref())
|
||||
}
|
||||
}
|
||||
@ -132,7 +153,9 @@ pub enum KdfSerParams {
|
||||
|
||||
impl Serialize for KdfSerParams {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where S: Serializer {
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match *self {
|
||||
KdfSerParams::Pbkdf2(ref params) => params.serialize(serializer),
|
||||
KdfSerParams::Scrypt(ref params) => params.serialize(serializer),
|
||||
@ -142,12 +165,15 @@ impl Serialize for KdfSerParams {
|
||||
|
||||
impl<'a> Deserialize<'a> for KdfSerParams {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where D: Deserializer<'a> {
|
||||
use serde_json::{Value, from_value};
|
||||
where
|
||||
D: Deserializer<'a>,
|
||||
{
|
||||
use serde_json::{from_value, Value};
|
||||
|
||||
let v: Value = Deserialize::deserialize(deserializer)?;
|
||||
|
||||
from_value(v.clone()).map(KdfSerParams::Pbkdf2)
|
||||
from_value(v.clone())
|
||||
.map(KdfSerParams::Pbkdf2)
|
||||
.or_else(|_| from_value(v).map(KdfSerParams::Scrypt))
|
||||
.map_err(|_| D::Error::custom("Invalid KDF algorithm"))
|
||||
}
|
||||
|
@ -14,30 +14,40 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::fmt;
|
||||
use std::io::{Read, Write};
|
||||
use serde::{Serialize, Serializer, Deserialize, Deserializer};
|
||||
use serde::de::{Error, Visitor, MapAccess, DeserializeOwned};
|
||||
use super::{Crypto, Uuid, Version, H160};
|
||||
use serde::{
|
||||
de::{DeserializeOwned, Error, MapAccess, Visitor},
|
||||
Deserialize, Deserializer, Serialize, Serializer,
|
||||
};
|
||||
use serde_json;
|
||||
use super::{Uuid, Version, Crypto, H160};
|
||||
use std::{
|
||||
fmt,
|
||||
io::{Read, Write},
|
||||
};
|
||||
|
||||
/// Public opaque type representing serializable `KeyFile`.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct OpaqueKeyFile {
|
||||
key_file: KeyFile
|
||||
key_file: KeyFile,
|
||||
}
|
||||
|
||||
impl Serialize for OpaqueKeyFile {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
self.key_file.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for OpaqueKeyFile where T: Into<KeyFile> {
|
||||
impl<T> From<T> for OpaqueKeyFile
|
||||
where
|
||||
T: Into<KeyFile>,
|
||||
{
|
||||
fn from(val: T) -> Self {
|
||||
OpaqueKeyFile { key_file: val.into() }
|
||||
OpaqueKeyFile {
|
||||
key_file: val.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,7 +72,8 @@ enum KeyFileField {
|
||||
|
||||
impl<'a> Deserialize<'a> for KeyFileField {
|
||||
fn deserialize<D>(deserializer: D) -> Result<KeyFileField, D::Error>
|
||||
where D: Deserializer<'a>
|
||||
where
|
||||
D: Deserializer<'a>,
|
||||
{
|
||||
deserializer.deserialize_any(KeyFileFieldVisitor)
|
||||
}
|
||||
@ -78,7 +89,8 @@ impl<'a> Visitor<'a> for KeyFileFieldVisitor {
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||
where E: Error
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
match value {
|
||||
"id" => Ok(KeyFileField::Id),
|
||||
@ -95,22 +107,25 @@ impl<'a> Visitor<'a> for KeyFileFieldVisitor {
|
||||
|
||||
impl<'a> Deserialize<'a> for KeyFile {
|
||||
fn deserialize<D>(deserializer: D) -> Result<KeyFile, D::Error>
|
||||
where D: Deserializer<'a>
|
||||
where
|
||||
D: Deserializer<'a>,
|
||||
{
|
||||
static FIELDS: &'static [&'static str] = &["id", "version", "crypto", "Crypto", "address"];
|
||||
deserializer.deserialize_struct("KeyFile", FIELDS, KeyFileVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
fn none_if_empty<'a, T>(v: Option<serde_json::Value>) -> Option<T> where
|
||||
T: DeserializeOwned
|
||||
fn none_if_empty<'a, T>(v: Option<serde_json::Value>) -> Option<T>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
v.and_then(|v| if v.is_null() {
|
||||
v.and_then(|v| {
|
||||
if v.is_null() {
|
||||
None
|
||||
} else {
|
||||
serde_json::from_value(v).ok()
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
struct KeyFileVisitor;
|
||||
@ -122,7 +137,8 @@ impl<'a> Visitor<'a> for KeyFileVisitor {
|
||||
}
|
||||
|
||||
fn visit_map<V>(self, mut visitor: V) -> Result<Self::Value, V::Error>
|
||||
where V: MapAccess<'a>
|
||||
where
|
||||
V: MapAccess<'a>,
|
||||
{
|
||||
let mut id = None;
|
||||
let mut version = None;
|
||||
@ -133,13 +149,23 @@ impl<'a> Visitor<'a> for KeyFileVisitor {
|
||||
|
||||
loop {
|
||||
match visitor.next_key()? {
|
||||
Some(KeyFileField::Id) => { id = Some(visitor.next_value()?); }
|
||||
Some(KeyFileField::Version) => { version = Some(visitor.next_value()?); }
|
||||
Some(KeyFileField::Crypto) => { crypto = Some(visitor.next_value()?); }
|
||||
Some(KeyFileField::Address) => { address = Some(visitor.next_value()?); }
|
||||
Some(KeyFileField::Name) => { name = none_if_empty(visitor.next_value().ok()) }
|
||||
Some(KeyFileField::Meta) => { meta = none_if_empty(visitor.next_value().ok()) }
|
||||
None => { break; }
|
||||
Some(KeyFileField::Id) => {
|
||||
id = Some(visitor.next_value()?);
|
||||
}
|
||||
Some(KeyFileField::Version) => {
|
||||
version = Some(visitor.next_value()?);
|
||||
}
|
||||
Some(KeyFileField::Crypto) => {
|
||||
crypto = Some(visitor.next_value()?);
|
||||
}
|
||||
Some(KeyFileField::Address) => {
|
||||
address = Some(visitor.next_value()?);
|
||||
}
|
||||
Some(KeyFileField::Name) => name = none_if_empty(visitor.next_value().ok()),
|
||||
Some(KeyFileField::Meta) => meta = none_if_empty(visitor.next_value().ok()),
|
||||
None => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -172,20 +198,26 @@ impl<'a> Visitor<'a> for KeyFileVisitor {
|
||||
}
|
||||
|
||||
impl KeyFile {
|
||||
pub fn load<R>(reader: R) -> Result<Self, serde_json::Error> where R: Read {
|
||||
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 {
|
||||
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 json::{Aes128Ctr, Cipher, Crypto, Kdf, KeyFile, Scrypt, Uuid, Version};
|
||||
use serde_json;
|
||||
use json::{KeyFile, Uuid, Version, Crypto, Cipher, Aes128Ctr, Kdf, Scrypt};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn basic_keyfile() {
|
||||
@ -222,7 +254,8 @@ mod tests {
|
||||
cipher: Cipher::Aes128Ctr(Aes128Ctr {
|
||||
iv: "b5a7ec855ec9e2c405371356855fec83".into(),
|
||||
}),
|
||||
ciphertext: "7203da0676d141b138cd7f8e1a4365f59cc1aa6978dc5443f364ca943d7cb4bc".into(),
|
||||
ciphertext: "7203da0676d141b138cd7f8e1a4365f59cc1aa6978dc5443f364ca943d7cb4bc"
|
||||
.into(),
|
||||
kdf: Kdf::Scrypt(Scrypt {
|
||||
n: 262144,
|
||||
dklen: 32,
|
||||
@ -273,7 +306,8 @@ mod tests {
|
||||
cipher: Cipher::Aes128Ctr(Aes128Ctr {
|
||||
iv: "b5a7ec855ec9e2c405371356855fec83".into(),
|
||||
}),
|
||||
ciphertext: "7203da0676d141b138cd7f8e1a4365f59cc1aa6978dc5443f364ca943d7cb4bc".into(),
|
||||
ciphertext: "7203da0676d141b138cd7f8e1a4365f59cc1aa6978dc5443f364ca943d7cb4bc"
|
||||
.into(),
|
||||
kdf: Kdf::Scrypt(Scrypt {
|
||||
n: 262144,
|
||||
dklen: 32,
|
||||
@ -301,7 +335,8 @@ mod tests {
|
||||
cipher: Cipher::Aes128Ctr(Aes128Ctr {
|
||||
iv: "b5a7ec855ec9e2c405371356855fec83".into(),
|
||||
}),
|
||||
ciphertext: "7203da0676d141b138cd7f8e1a4365f59cc1aa6978dc5443f364ca943d7cb4bc".into(),
|
||||
ciphertext: "7203da0676d141b138cd7f8e1a4365f59cc1aa6978dc5443f364ca943d7cb4bc"
|
||||
.into(),
|
||||
kdf: Kdf::Scrypt(Scrypt {
|
||||
n: 262144,
|
||||
dklen: 32,
|
||||
|
@ -29,15 +29,20 @@ mod vault_file;
|
||||
mod vault_key_file;
|
||||
mod version;
|
||||
|
||||
pub use self::bytes::Bytes;
|
||||
pub use self::cipher::{Cipher, CipherSer, CipherSerParams, Aes128Ctr};
|
||||
pub use self::crypto::{Crypto, CipherText};
|
||||
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, OpaqueKeyFile};
|
||||
pub use self::presale::{PresaleWallet, Encseed};
|
||||
pub use self::vault_file::VaultFile;
|
||||
pub use self::vault_key_file::{VaultKeyFile, VaultKeyMeta, insert_vault_name_to_json_meta, remove_vault_name_from_json_meta};
|
||||
pub use self::version::Version;
|
||||
pub use self::{
|
||||
bytes::Bytes,
|
||||
cipher::{Aes128Ctr, Cipher, CipherSer, CipherSerParams},
|
||||
crypto::{CipherText, Crypto},
|
||||
error::Error,
|
||||
hash::{H128, H160, H256},
|
||||
id::Uuid,
|
||||
kdf::{Kdf, KdfSer, KdfSerParams, Pbkdf2, Prf, Scrypt},
|
||||
key_file::{KeyFile, OpaqueKeyFile},
|
||||
presale::{Encseed, PresaleWallet},
|
||||
vault_file::VaultFile,
|
||||
vault_key_file::{
|
||||
insert_vault_name_to_json_meta, remove_vault_name_from_json_meta, VaultKeyFile,
|
||||
VaultKeyMeta,
|
||||
},
|
||||
version::Version,
|
||||
};
|
||||
|
@ -14,9 +14,9 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::io::Read;
|
||||
use super::{Bytes, H160};
|
||||
use serde_json;
|
||||
use super::{H160, Bytes};
|
||||
use std::io::Read;
|
||||
|
||||
pub type Encseed = Bytes;
|
||||
|
||||
@ -28,16 +28,19 @@ pub struct PresaleWallet {
|
||||
}
|
||||
|
||||
impl PresaleWallet {
|
||||
pub fn load<R>(reader: R) -> Result<Self, serde_json::Error> where R: Read {
|
||||
pub fn load<R>(reader: R) -> Result<Self, serde_json::Error>
|
||||
where
|
||||
R: Read,
|
||||
{
|
||||
serde_json::from_reader(reader)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::str::FromStr;
|
||||
use serde_json;
|
||||
use json::{PresaleWallet, H160};
|
||||
use serde_json;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn presale_wallet() {
|
||||
|
@ -14,9 +14,9 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::io::{Read, Write};
|
||||
use serde_json;
|
||||
use super::Crypto;
|
||||
use serde_json;
|
||||
use std::io::{Read, Write};
|
||||
|
||||
/// Vault meta file
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
@ -28,19 +28,25 @@ pub struct VaultFile {
|
||||
}
|
||||
|
||||
impl VaultFile {
|
||||
pub fn load<R>(reader: R) -> Result<Self, serde_json::Error> where R: Read {
|
||||
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 {
|
||||
pub fn write<W>(&self, writer: &mut W) -> Result<(), serde_json::Error>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
serde_json::to_writer(writer, self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use json::{Aes128Ctr, Cipher, Crypto, Kdf, Pbkdf2, Prf, VaultFile};
|
||||
use serde_json;
|
||||
use json::{VaultFile, Crypto, Cipher, Aes128Ctr, Kdf, Pbkdf2, Prf};
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
lazy_static! {
|
||||
|
@ -14,12 +14,10 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::io::{Read, Write};
|
||||
use super::{Crypto, Uuid, Version, H160};
|
||||
use serde::de::Error;
|
||||
use serde_json;
|
||||
use serde_json::value::Value;
|
||||
use serde_json::error;
|
||||
use super::{Uuid, Version, Crypto, H160};
|
||||
use serde_json::{self, error, value::Value};
|
||||
use std::io::{Read, Write};
|
||||
|
||||
/// Meta key name for vault field
|
||||
const VAULT_NAME_META_KEY: &'static str = "vault";
|
||||
@ -49,7 +47,10 @@ pub struct VaultKeyMeta {
|
||||
}
|
||||
|
||||
/// Insert vault name to the JSON meta field
|
||||
pub fn insert_vault_name_to_json_meta(meta: &str, vault_name: &str) -> Result<String, error::Error> {
|
||||
pub fn insert_vault_name_to_json_meta(
|
||||
meta: &str,
|
||||
vault_name: &str,
|
||||
) -> Result<String, error::Error> {
|
||||
let mut meta = if meta.is_empty() {
|
||||
Value::Object(serde_json::Map::new())
|
||||
} else {
|
||||
@ -57,10 +58,15 @@ pub fn insert_vault_name_to_json_meta(meta: &str, vault_name: &str) -> Result<St
|
||||
};
|
||||
|
||||
if let Some(meta_obj) = meta.as_object_mut() {
|
||||
meta_obj.insert(VAULT_NAME_META_KEY.to_owned(), Value::String(vault_name.to_owned()));
|
||||
meta_obj.insert(
|
||||
VAULT_NAME_META_KEY.to_owned(),
|
||||
Value::String(vault_name.to_owned()),
|
||||
);
|
||||
serde_json::to_string(meta_obj)
|
||||
} else {
|
||||
Err(error::Error::custom("Meta is expected to be a serialized JSON object"))
|
||||
Err(error::Error::custom(
|
||||
"Meta is expected to be a serialized JSON object",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,16 +82,24 @@ pub fn remove_vault_name_from_json_meta(meta: &str) -> Result<String, error::Err
|
||||
meta_obj.remove(VAULT_NAME_META_KEY);
|
||||
serde_json::to_string(meta_obj)
|
||||
} else {
|
||||
Err(error::Error::custom("Meta is expected to be a serialized JSON object"))
|
||||
Err(error::Error::custom(
|
||||
"Meta is expected to be a serialized JSON object",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl VaultKeyFile {
|
||||
pub fn load<R>(reader: R) -> Result<Self, serde_json::Error> where R: Read {
|
||||
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 {
|
||||
pub fn write<W>(&self, writer: &mut W) -> Result<(), serde_json::Error>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
serde_json::to_writer(writer, self)
|
||||
}
|
||||
}
|
||||
@ -103,9 +117,11 @@ impl VaultKeyMeta {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use json::{
|
||||
insert_vault_name_to_json_meta, remove_vault_name_from_json_meta, Aes128Ctr, Cipher,
|
||||
Crypto, Kdf, Pbkdf2, Prf, VaultKeyFile, Version,
|
||||
};
|
||||
use serde_json;
|
||||
use json::{VaultKeyFile, Version, Crypto, Cipher, Aes128Ctr, Kdf, Pbkdf2, Prf,
|
||||
insert_vault_name_to_json_meta, remove_vault_name_from_json_meta};
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
lazy_static! {
|
||||
@ -153,8 +169,14 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn vault_name_inserted_to_json_meta() {
|
||||
assert_eq!(insert_vault_name_to_json_meta(r#""#, "MyVault").unwrap(), r#"{"vault":"MyVault"}"#);
|
||||
assert_eq!(insert_vault_name_to_json_meta(r#"{"tags":["kalabala"]}"#, "MyVault").unwrap(), r#"{"tags":["kalabala"],"vault":"MyVault"}"#);
|
||||
assert_eq!(
|
||||
insert_vault_name_to_json_meta(r#""#, "MyVault").unwrap(),
|
||||
r#"{"vault":"MyVault"}"#
|
||||
);
|
||||
assert_eq!(
|
||||
insert_vault_name_to_json_meta(r#"{"tags":["kalabala"]}"#, "MyVault").unwrap(),
|
||||
r#"{"tags":["kalabala"],"vault":"MyVault"}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -165,8 +187,14 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn vault_name_removed_from_json_meta() {
|
||||
assert_eq!(remove_vault_name_from_json_meta(r#"{"vault":"MyVault"}"#).unwrap(), r#"{}"#);
|
||||
assert_eq!(remove_vault_name_from_json_meta(r#"{"tags":["kalabala"],"vault":"MyVault"}"#).unwrap(), r#"{"tags":["kalabala"]}"#);
|
||||
assert_eq!(
|
||||
remove_vault_name_from_json_meta(r#"{"vault":"MyVault"}"#).unwrap(),
|
||||
r#"{}"#
|
||||
);
|
||||
assert_eq!(
|
||||
remove_vault_name_from_json_meta(r#"{"tags":["kalabala"],"vault":"MyVault"}"#).unwrap(),
|
||||
r#"{"tags":["kalabala"]}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -14,10 +14,12 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::fmt;
|
||||
use serde::{Serialize, Serializer, Deserialize, Deserializer};
|
||||
use serde::de::{Error as SerdeError, Visitor};
|
||||
use super::Error;
|
||||
use serde::{
|
||||
de::{Error as SerdeError, Visitor},
|
||||
Deserialize, Deserializer, Serialize, Serializer,
|
||||
};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Version {
|
||||
@ -26,16 +28,20 @@ pub enum Version {
|
||||
|
||||
impl Serialize for Version {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where S: Serializer {
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match *self {
|
||||
Version::V3 => serializer.serialize_u64(3)
|
||||
Version::V3 => serializer.serialize_u64(3),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deserialize<'a> for Version {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Version, D::Error>
|
||||
where D: Deserializer<'a> {
|
||||
where
|
||||
D: Deserializer<'a>,
|
||||
{
|
||||
deserializer.deserialize_any(VersionVisitor)
|
||||
}
|
||||
}
|
||||
@ -49,10 +55,13 @@ impl<'a> Visitor<'a> for VersionVisitor {
|
||||
write!(formatter, "a valid key version identifier")
|
||||
}
|
||||
|
||||
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E> where E: SerdeError {
|
||||
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: SerdeError,
|
||||
{
|
||||
match value {
|
||||
3 => Ok(Version::V3),
|
||||
_ => Err(SerdeError::custom(Error::UnsupportedVersion))
|
||||
_ => Err(SerdeError::custom(Error::UnsupportedVersion)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,13 +27,13 @@ extern crate rustc_hex;
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
extern crate smallvec;
|
||||
extern crate tempdir;
|
||||
extern crate time;
|
||||
extern crate tiny_keccak;
|
||||
extern crate tempdir;
|
||||
|
||||
extern crate parity_crypto as crypto;
|
||||
extern crate ethereum_types;
|
||||
extern crate ethkey as _ethkey;
|
||||
extern crate parity_crypto as crypto;
|
||||
extern crate parity_wordlist;
|
||||
|
||||
#[macro_use]
|
||||
@ -60,18 +60,20 @@ mod presale;
|
||||
mod random;
|
||||
mod secret_store;
|
||||
|
||||
pub use self::account::{SafeAccount, Crypto};
|
||||
pub use self::error::Error;
|
||||
pub use self::ethstore::{EthStore, EthMultiStore};
|
||||
pub use self::import::{import_account, import_accounts, read_geth_accounts};
|
||||
pub use self::json::OpaqueKeyFile as KeyFile;
|
||||
pub use self::presale::PresaleWallet;
|
||||
pub use self::secret_store::{
|
||||
SecretVaultRef, StoreAccountRef, SimpleSecretStore, SecretStore,
|
||||
Derivation, IndexDerivation,
|
||||
pub use self::{
|
||||
account::{Crypto, SafeAccount},
|
||||
error::Error,
|
||||
ethstore::{EthMultiStore, EthStore},
|
||||
import::{import_account, import_accounts, read_geth_accounts},
|
||||
json::OpaqueKeyFile as KeyFile,
|
||||
parity_wordlist::random_phrase,
|
||||
presale::PresaleWallet,
|
||||
random::random_string,
|
||||
secret_store::{
|
||||
Derivation, IndexDerivation, SecretStore, SecretVaultRef, SimpleSecretStore,
|
||||
StoreAccountRef,
|
||||
},
|
||||
};
|
||||
pub use self::random::random_string;
|
||||
pub use self::parity_wordlist::random_phrase;
|
||||
|
||||
/// An opaque wrapper for secret.
|
||||
pub struct OpaqueSecret(::ethkey::Secret);
|
||||
|
@ -14,13 +14,11 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::fs;
|
||||
use std::num::NonZeroU32;
|
||||
use std::path::Path;
|
||||
use crypto::{self, pbkdf2, Keccak256};
|
||||
use ethkey::{Address, KeyPair, Password, Secret};
|
||||
use json;
|
||||
use ethkey::{Address, Secret, KeyPair, Password};
|
||||
use crypto::{Keccak256, pbkdf2};
|
||||
use {crypto, Error};
|
||||
use std::{fs, num::NonZeroU32, path::Path};
|
||||
use Error;
|
||||
|
||||
/// Pre-sale wallet.
|
||||
pub struct PresaleWallet {
|
||||
@ -47,10 +45,13 @@ impl From<json::PresaleWallet> for PresaleWallet {
|
||||
|
||||
impl PresaleWallet {
|
||||
/// Open a pre-sale wallet.
|
||||
pub fn open<P>(path: P) -> Result<Self, Error> where P: AsRef<Path> {
|
||||
pub fn open<P>(path: P) -> Result<Self, Error>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let file = fs::File::open(path)?;
|
||||
let presale = json::PresaleWallet::load(file)
|
||||
.map_err(|e| Error::InvalidKeyFile(format!("{}", e)))?;
|
||||
let presale =
|
||||
json::PresaleWallet::load(file).map_err(|e| Error::InvalidKeyFile(format!("{}", e)))?;
|
||||
Ok(PresaleWallet::from(presale))
|
||||
}
|
||||
|
||||
@ -63,14 +64,15 @@ impl PresaleWallet {
|
||||
pbkdf2::sha256(iter, salt, sec, &mut derived_key);
|
||||
|
||||
let mut key = vec![0; self.ciphertext.len()];
|
||||
let len = crypto::aes::decrypt_128_cbc(&derived_key[0..16], &self.iv, &self.ciphertext, &mut key)
|
||||
let len =
|
||||
crypto::aes::decrypt_128_cbc(&derived_key[0..16], &self.iv, &self.ciphertext, &mut key)
|
||||
.map_err(|_| Error::InvalidPassword)?;
|
||||
let unpadded = &key[..len];
|
||||
|
||||
let secret = Secret::from_unsafe_slice(&unpadded.keccak256())?;
|
||||
if let Ok(kp) = KeyPair::from_secret(secret) {
|
||||
if kp.address() == self.address {
|
||||
return Ok(kp)
|
||||
return Ok(kp);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,10 +14,12 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use rand::{Rng, OsRng};
|
||||
use rand::{OsRng, Rng};
|
||||
|
||||
pub trait Random {
|
||||
fn random() -> Self where Self: Sized;
|
||||
fn random() -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
impl Random for [u8; 16] {
|
||||
|
@ -14,13 +14,15 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::path::PathBuf;
|
||||
use std::cmp::Ordering;
|
||||
use ethkey::{Address, Message, Signature, Secret, Password, Public};
|
||||
use Error;
|
||||
use json::{Uuid, OpaqueKeyFile};
|
||||
use ethereum_types::H256;
|
||||
use ethkey::{Address, Message, Password, Public, Secret, Signature};
|
||||
use json::{OpaqueKeyFile, Uuid};
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
hash::{Hash, Hasher},
|
||||
path::PathBuf,
|
||||
};
|
||||
use Error;
|
||||
use OpaqueSecret;
|
||||
|
||||
/// Key directory reference
|
||||
@ -43,7 +45,11 @@ pub struct StoreAccountRef {
|
||||
|
||||
impl PartialOrd for StoreAccountRef {
|
||||
fn partial_cmp(&self, other: &StoreAccountRef) -> Option<Ordering> {
|
||||
Some(self.address.cmp(&other.address).then_with(|| self.vault.cmp(&other.vault)))
|
||||
Some(
|
||||
self.address
|
||||
.cmp(&other.address)
|
||||
.then_with(|| self.vault.cmp(&other.vault)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,25 +62,72 @@ impl ::std::borrow::Borrow<Address> for StoreAccountRef {
|
||||
/// Simple Secret Store API
|
||||
pub trait SimpleSecretStore: Send + Sync {
|
||||
/// Inserts new accounts to the store (or vault) with given password.
|
||||
fn insert_account(&self, vault: SecretVaultRef, secret: Secret, password: &Password) -> Result<StoreAccountRef, Error>;
|
||||
fn insert_account(
|
||||
&self,
|
||||
vault: SecretVaultRef,
|
||||
secret: Secret,
|
||||
password: &Password,
|
||||
) -> Result<StoreAccountRef, Error>;
|
||||
/// Inserts new derived account to the store (or vault) with given password.
|
||||
fn insert_derived(&self, vault: SecretVaultRef, account_ref: &StoreAccountRef, password: &Password, derivation: Derivation) -> Result<StoreAccountRef, Error>;
|
||||
fn insert_derived(
|
||||
&self,
|
||||
vault: SecretVaultRef,
|
||||
account_ref: &StoreAccountRef,
|
||||
password: &Password,
|
||||
derivation: Derivation,
|
||||
) -> Result<StoreAccountRef, Error>;
|
||||
/// Changes accounts password.
|
||||
fn change_password(&self, account: &StoreAccountRef, old_password: &Password, new_password: &Password) -> Result<(), Error>;
|
||||
fn change_password(
|
||||
&self,
|
||||
account: &StoreAccountRef,
|
||||
old_password: &Password,
|
||||
new_password: &Password,
|
||||
) -> Result<(), Error>;
|
||||
/// Exports key details for account.
|
||||
fn export_account(&self, account: &StoreAccountRef, password: &Password) -> Result<OpaqueKeyFile, Error>;
|
||||
fn export_account(
|
||||
&self,
|
||||
account: &StoreAccountRef,
|
||||
password: &Password,
|
||||
) -> Result<OpaqueKeyFile, Error>;
|
||||
/// Entirely removes account from the store and underlying storage.
|
||||
fn remove_account(&self, account: &StoreAccountRef, password: &Password) -> Result<(), Error>;
|
||||
/// Generates new derived account.
|
||||
fn generate_derived(&self, account_ref: &StoreAccountRef, password: &Password, derivation: Derivation) -> Result<Address, Error>;
|
||||
fn generate_derived(
|
||||
&self,
|
||||
account_ref: &StoreAccountRef,
|
||||
password: &Password,
|
||||
derivation: Derivation,
|
||||
) -> Result<Address, Error>;
|
||||
/// Sign a message with given account.
|
||||
fn sign(&self, account: &StoreAccountRef, password: &Password, message: &Message) -> Result<Signature, Error>;
|
||||
fn sign(
|
||||
&self,
|
||||
account: &StoreAccountRef,
|
||||
password: &Password,
|
||||
message: &Message,
|
||||
) -> Result<Signature, Error>;
|
||||
/// Sign a message with derived account.
|
||||
fn sign_derived(&self, account_ref: &StoreAccountRef, password: &Password, derivation: Derivation, message: &Message) -> Result<Signature, Error>;
|
||||
fn sign_derived(
|
||||
&self,
|
||||
account_ref: &StoreAccountRef,
|
||||
password: &Password,
|
||||
derivation: Derivation,
|
||||
message: &Message,
|
||||
) -> Result<Signature, Error>;
|
||||
/// Decrypt a messages with given account.
|
||||
fn decrypt(&self, account: &StoreAccountRef, password: &Password, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error>;
|
||||
fn decrypt(
|
||||
&self,
|
||||
account: &StoreAccountRef,
|
||||
password: &Password,
|
||||
shared_mac: &[u8],
|
||||
message: &[u8],
|
||||
) -> Result<Vec<u8>, Error>;
|
||||
/// Agree on shared key.
|
||||
fn agree(&self, account: &StoreAccountRef, password: &Password, other: &Public) -> Result<Secret, Error>;
|
||||
fn agree(
|
||||
&self,
|
||||
account: &StoreAccountRef,
|
||||
password: &Password,
|
||||
other: &Public,
|
||||
) -> Result<Secret, Error>;
|
||||
|
||||
/// Returns all accounts in this secret store.
|
||||
fn accounts(&self) -> Result<Vec<StoreAccountRef>, Error>;
|
||||
@ -95,7 +148,11 @@ pub trait SimpleSecretStore: Send + Sync {
|
||||
/// Change vault password
|
||||
fn change_vault_password(&self, name: &str, new_password: &Password) -> Result<(), Error>;
|
||||
/// Cnage account' vault
|
||||
fn change_account_vault(&self, vault: SecretVaultRef, account: StoreAccountRef) -> Result<StoreAccountRef, Error>;
|
||||
fn change_account_vault(
|
||||
&self,
|
||||
vault: SecretVaultRef,
|
||||
account: StoreAccountRef,
|
||||
) -> Result<StoreAccountRef, Error>;
|
||||
/// Get vault metadata string.
|
||||
fn get_vault_meta(&self, name: &str) -> Result<String, Error>;
|
||||
/// Set vault metadata string.
|
||||
@ -104,21 +161,46 @@ pub trait SimpleSecretStore: Send + Sync {
|
||||
|
||||
/// Secret Store API
|
||||
pub trait SecretStore: SimpleSecretStore {
|
||||
|
||||
/// Returns a raw opaque Secret that can be later used to sign a message.
|
||||
fn raw_secret(&self, account: &StoreAccountRef, password: &Password) -> Result<OpaqueSecret, Error>;
|
||||
fn raw_secret(
|
||||
&self,
|
||||
account: &StoreAccountRef,
|
||||
password: &Password,
|
||||
) -> Result<OpaqueSecret, Error>;
|
||||
|
||||
/// Signs a message with raw secret.
|
||||
fn sign_with_secret(&self, secret: &OpaqueSecret, message: &Message) -> Result<Signature, Error> {
|
||||
fn sign_with_secret(
|
||||
&self,
|
||||
secret: &OpaqueSecret,
|
||||
message: &Message,
|
||||
) -> Result<Signature, Error> {
|
||||
Ok(::ethkey::sign(&secret.0, message)?)
|
||||
}
|
||||
|
||||
/// Imports presale wallet
|
||||
fn import_presale(&self, vault: SecretVaultRef, json: &[u8], password: &Password) -> Result<StoreAccountRef, Error>;
|
||||
fn import_presale(
|
||||
&self,
|
||||
vault: SecretVaultRef,
|
||||
json: &[u8],
|
||||
password: &Password,
|
||||
) -> Result<StoreAccountRef, Error>;
|
||||
/// Imports existing JSON wallet
|
||||
fn import_wallet(&self, vault: SecretVaultRef, json: &[u8], password: &Password, gen_id: bool) -> Result<StoreAccountRef, Error>;
|
||||
fn import_wallet(
|
||||
&self,
|
||||
vault: SecretVaultRef,
|
||||
json: &[u8],
|
||||
password: &Password,
|
||||
gen_id: bool,
|
||||
) -> Result<StoreAccountRef, Error>;
|
||||
/// Copies account between stores and vaults.
|
||||
fn copy_account(&self, new_store: &SimpleSecretStore, new_vault: SecretVaultRef, account: &StoreAccountRef, password: &Password, new_password: &Password) -> Result<(), Error>;
|
||||
fn copy_account(
|
||||
&self,
|
||||
new_store: &SimpleSecretStore,
|
||||
new_vault: SecretVaultRef,
|
||||
account: &StoreAccountRef,
|
||||
password: &Password,
|
||||
new_password: &Password,
|
||||
) -> Result<(), Error>;
|
||||
/// Checks if password matches given account.
|
||||
fn test_password(&self, account: &StoreAccountRef, password: &Password) -> Result<bool, Error>;
|
||||
|
||||
@ -142,7 +224,12 @@ pub trait SecretStore: SimpleSecretStore {
|
||||
/// Lists all found geth accounts.
|
||||
fn list_geth_accounts(&self, testnet: bool) -> Vec<Address>;
|
||||
/// Imports geth accounts to the store/vault.
|
||||
fn import_geth_accounts(&self, vault: SecretVaultRef, desired: Vec<Address>, testnet: bool) -> Result<Vec<StoreAccountRef>, Error>;
|
||||
fn import_geth_accounts(
|
||||
&self,
|
||||
vault: SecretVaultRef,
|
||||
desired: Vec<Address>,
|
||||
testnet: bool,
|
||||
) -> Result<Vec<StoreAccountRef>, Error>;
|
||||
}
|
||||
|
||||
impl StoreAccountRef {
|
||||
|
@ -14,14 +14,16 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
extern crate rand;
|
||||
extern crate ethstore;
|
||||
extern crate rand;
|
||||
|
||||
mod util;
|
||||
|
||||
use ethstore::{EthStore, SimpleSecretStore, SecretVaultRef, StoreAccountRef};
|
||||
use ethstore::ethkey::{Random, Generator, Secret, KeyPair, verify_address};
|
||||
use ethstore::accounts_dir::RootDiskDirectory;
|
||||
use ethstore::{
|
||||
accounts_dir::RootDiskDirectory,
|
||||
ethkey::{verify_address, Generator, KeyPair, Random, Secret},
|
||||
EthStore, SecretVaultRef, SimpleSecretStore, StoreAccountRef,
|
||||
};
|
||||
use util::TransientDir;
|
||||
|
||||
#[test]
|
||||
@ -46,9 +48,13 @@ fn secret_store_create_account() {
|
||||
let dir = TransientDir::create().unwrap();
|
||||
let store = EthStore::open(Box::new(dir)).unwrap();
|
||||
assert_eq!(store.accounts().unwrap().len(), 0);
|
||||
assert!(store.insert_account(SecretVaultRef::Root, random_secret(), &"".into()).is_ok());
|
||||
assert!(store
|
||||
.insert_account(SecretVaultRef::Root, random_secret(), &"".into())
|
||||
.is_ok());
|
||||
assert_eq!(store.accounts().unwrap().len(), 1);
|
||||
assert!(store.insert_account(SecretVaultRef::Root, random_secret(), &"".into()).is_ok());
|
||||
assert!(store
|
||||
.insert_account(SecretVaultRef::Root, random_secret(), &"".into())
|
||||
.is_ok());
|
||||
assert_eq!(store.accounts().unwrap().len(), 2);
|
||||
}
|
||||
|
||||
@ -56,31 +62,49 @@ fn secret_store_create_account() {
|
||||
fn secret_store_sign() {
|
||||
let dir = TransientDir::create().unwrap();
|
||||
let store = EthStore::open(Box::new(dir)).unwrap();
|
||||
assert!(store.insert_account(SecretVaultRef::Root, random_secret(), &"".into()).is_ok());
|
||||
assert!(store
|
||||
.insert_account(SecretVaultRef::Root, random_secret(), &"".into())
|
||||
.is_ok());
|
||||
let accounts = store.accounts().unwrap();
|
||||
assert_eq!(accounts.len(), 1);
|
||||
assert!(store.sign(&accounts[0], &"".into(), &Default::default()).is_ok());
|
||||
assert!(store.sign(&accounts[0], &"1".into(), &Default::default()).is_err());
|
||||
assert!(store
|
||||
.sign(&accounts[0], &"".into(), &Default::default())
|
||||
.is_ok());
|
||||
assert!(store
|
||||
.sign(&accounts[0], &"1".into(), &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(SecretVaultRef::Root, random_secret(), &"".into()).is_ok());
|
||||
assert!(store
|
||||
.insert_account(SecretVaultRef::Root, random_secret(), &"".into())
|
||||
.is_ok());
|
||||
let accounts = store.accounts().unwrap();
|
||||
assert_eq!(accounts.len(), 1);
|
||||
assert!(store.sign(&accounts[0], &"".into(), &Default::default()).is_ok());
|
||||
assert!(store.change_password(&accounts[0], &"".into(), &"1".into()).is_ok());
|
||||
assert!(store.sign(&accounts[0], &"".into(), &Default::default()).is_err());
|
||||
assert!(store.sign(&accounts[0], &"1".into(), &Default::default()).is_ok());
|
||||
assert!(store
|
||||
.sign(&accounts[0], &"".into(), &Default::default())
|
||||
.is_ok());
|
||||
assert!(store
|
||||
.change_password(&accounts[0], &"".into(), &"1".into())
|
||||
.is_ok());
|
||||
assert!(store
|
||||
.sign(&accounts[0], &"".into(), &Default::default())
|
||||
.is_err());
|
||||
assert!(store
|
||||
.sign(&accounts[0], &"1".into(), &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(SecretVaultRef::Root, random_secret(), &"".into()).is_ok());
|
||||
assert!(store
|
||||
.insert_account(SecretVaultRef::Root, random_secret(), &"".into())
|
||||
.is_ok());
|
||||
let accounts = store.accounts().unwrap();
|
||||
assert_eq!(accounts.len(), 1);
|
||||
assert!(store.remove_account(&accounts[0], &"".into()).is_ok());
|
||||
@ -113,36 +137,55 @@ fn ciphertext_path() -> &'static str {
|
||||
fn secret_store_laod_geth_files() {
|
||||
let dir = RootDiskDirectory::at(test_path());
|
||||
let store = EthStore::open(Box::new(dir)).unwrap();
|
||||
assert_eq!(store.accounts().unwrap(), vec![
|
||||
assert_eq!(
|
||||
store.accounts().unwrap(),
|
||||
vec![
|
||||
StoreAccountRef::root("3f49624084b67849c7b4e805c5988c21a430f9d9".into()),
|
||||
StoreAccountRef::root("5ba4dcf897e97c2bdf8315b9ef26c13c085988cf".into()),
|
||||
StoreAccountRef::root("63121b431a52f8043c16fcf0d1df9cb7b5f66649".into()),
|
||||
]);
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn secret_store_load_pat_files() {
|
||||
let dir = RootDiskDirectory::at(pat_path());
|
||||
let store = EthStore::open(Box::new(dir)).unwrap();
|
||||
assert_eq!(store.accounts().unwrap(), vec![
|
||||
assert_eq!(
|
||||
store.accounts().unwrap(),
|
||||
vec![
|
||||
StoreAccountRef::root("3f49624084b67849c7b4e805c5988c21a430f9d9".into()),
|
||||
StoreAccountRef::root("5ba4dcf897e97c2bdf8315b9ef26c13c085988cf".into()),
|
||||
]);
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decrypting_files_with_short_ciphertext() {
|
||||
// 31e9d1e6d844bd3a536800ef8d8be6a9975db509, 30
|
||||
let kp1 = KeyPair::from_secret("000081c29e8142bb6a81bef5a92bda7a8328a5c85bb2f9542e76f9b0f94fc018".parse().unwrap()).unwrap();
|
||||
let kp1 = KeyPair::from_secret(
|
||||
"000081c29e8142bb6a81bef5a92bda7a8328a5c85bb2f9542e76f9b0f94fc018"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
// d1e64e5480bfaf733ba7d48712decb8227797a4e , 31
|
||||
let kp2 = KeyPair::from_secret("00fa7b3db73dc7dfdf8c5fbdb796d741e4488628c41fc4febd9160a866ba0f35".parse().unwrap()).unwrap();
|
||||
let kp2 = KeyPair::from_secret(
|
||||
"00fa7b3db73dc7dfdf8c5fbdb796d741e4488628c41fc4febd9160a866ba0f35"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let dir = RootDiskDirectory::at(ciphertext_path());
|
||||
let store = EthStore::open(Box::new(dir)).unwrap();
|
||||
let accounts = store.accounts().unwrap();
|
||||
assert_eq!(accounts, vec![
|
||||
assert_eq!(
|
||||
accounts,
|
||||
vec![
|
||||
StoreAccountRef::root("31e9d1e6d844bd3a536800ef8d8be6a9975db509".into()),
|
||||
StoreAccountRef::root("d1e64e5480bfaf733ba7d48712decb8227797a4e".into()),
|
||||
]);
|
||||
]
|
||||
);
|
||||
|
||||
let message = Default::default();
|
||||
|
||||
|
@ -14,11 +14,12 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::{env, fs};
|
||||
use rand::{Rng, OsRng};
|
||||
use ethstore::accounts_dir::{KeyDirectory, RootDiskDirectory};
|
||||
use ethstore::{Error, SafeAccount};
|
||||
use ethstore::{
|
||||
accounts_dir::{KeyDirectory, RootDiskDirectory},
|
||||
Error, SafeAccount,
|
||||
};
|
||||
use rand::{OsRng, Rng};
|
||||
use std::{env, fs, path::PathBuf};
|
||||
|
||||
pub fn random_dir() -> PathBuf {
|
||||
let mut rng = OsRng::new().unwrap();
|
||||
|
@ -19,9 +19,9 @@
|
||||
extern crate ethereum_types;
|
||||
extern crate ethkey;
|
||||
|
||||
use std::fmt;
|
||||
use ethereum_types::U256;
|
||||
use ethkey::{Address, Signature};
|
||||
use std::fmt;
|
||||
|
||||
pub struct WalletInfo {
|
||||
pub address: Address,
|
||||
@ -86,8 +86,14 @@ impl HardwareWalletManager {
|
||||
Err(Error::NoWallet)
|
||||
}
|
||||
|
||||
pub fn sign_transaction(&self, _address: &Address, _transaction: &TransactionInfo, _rlp_transaction: &[u8]) -> Result<Signature, Error> {
|
||||
Err(Error::NoWallet) }
|
||||
pub fn sign_transaction(
|
||||
&self,
|
||||
_address: &Address,
|
||||
_transaction: &TransactionInfo,
|
||||
_rlp_transaction: &[u8],
|
||||
) -> Result<Signature, Error> {
|
||||
Err(Error::NoWallet)
|
||||
}
|
||||
|
||||
pub fn sign_message(&self, _address: &Address, _msg: &[u8]) -> Result<Signature, Error> {
|
||||
Err(Error::NoWallet)
|
||||
|
@ -17,26 +17,31 @@
|
||||
//! Ledger hardware wallet module. Supports Ledger Blue and Nano S.
|
||||
//! See <https://github.com/LedgerHQ/blue-app-eth/blob/master/doc/ethapp.asc> for protocol details.
|
||||
|
||||
use std::cmp::min;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::fmt;
|
||||
use std::{
|
||||
cmp::min,
|
||||
fmt,
|
||||
str::FromStr,
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use ethereum_types::{H256, Address};
|
||||
use super::{is_valid_hid_device, Device, DeviceDirection, KeyPath, Wallet, WalletInfo};
|
||||
use ethereum_types::{Address, H256};
|
||||
use ethkey::Signature;
|
||||
use hidapi;
|
||||
use libusb;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use semver::Version as FirmwareVersion;
|
||||
use super::{WalletInfo, KeyPath, Device, DeviceDirection, Wallet, is_valid_hid_device};
|
||||
|
||||
const APDU_TAG: u8 = 0x05;
|
||||
const APDU_CLA: u8 = 0xe0;
|
||||
const APDU_PAYLOAD_HEADER_LEN: usize = 7;
|
||||
|
||||
const ETH_DERIVATION_PATH_BE: [u8; 17] = [4, 0x80, 0, 0, 44, 0x80, 0, 0, 60, 0x80, 0, 0, 0, 0, 0, 0, 0]; // 44'/60'/0'/0
|
||||
const ETC_DERIVATION_PATH_BE: [u8; 21] = [5, 0x80, 0, 0, 44, 0x80, 0, 0, 60, 0x80, 0x02, 0x73, 0xd0, 0x80, 0, 0, 0, 0, 0, 0, 0]; // 44'/60'/160720'/0'/0
|
||||
const ETH_DERIVATION_PATH_BE: [u8; 17] =
|
||||
[4, 0x80, 0, 0, 44, 0x80, 0, 0, 60, 0x80, 0, 0, 0, 0, 0, 0, 0]; // 44'/60'/0'/0
|
||||
const ETC_DERIVATION_PATH_BE: [u8; 21] = [
|
||||
5, 0x80, 0, 0, 44, 0x80, 0, 0, 60, 0x80, 0x02, 0x73, 0xd0, 0x80, 0, 0, 0, 0, 0, 0, 0,
|
||||
]; // 44'/60'/160720'/0'/0
|
||||
|
||||
/// Ledger vendor ID
|
||||
const LEDGER_VID: u16 = 0x2c97;
|
||||
@ -48,8 +53,10 @@ const MAX_CHUNK_SIZE: usize = 255;
|
||||
|
||||
const HID_PACKET_SIZE: usize = 64 + HID_PREFIX_ZERO;
|
||||
|
||||
#[cfg(windows)] const HID_PREFIX_ZERO: usize = 1;
|
||||
#[cfg(not(windows))] const HID_PREFIX_ZERO: usize = 0;
|
||||
#[cfg(windows)]
|
||||
const HID_PREFIX_ZERO: usize = 1;
|
||||
#[cfg(not(windows))]
|
||||
const HID_PREFIX_ZERO: usize = 0;
|
||||
|
||||
mod commands {
|
||||
pub const GET_APP_CONFIGURATION: u8 = 0x06;
|
||||
@ -91,8 +98,11 @@ impl fmt::Display for Error {
|
||||
Error::UserCancel => write!(f, "Operation has been cancelled"),
|
||||
Error::Impossible => write!(f, "Placeholder error"),
|
||||
Error::NoDeviceArrived => write!(f, "No device arrived"),
|
||||
Error::NoDeviceLeft=> write!(f, "No device left"),
|
||||
Error::InvalidDevice => write!(f, "Device with non-supported product ID or vendor ID was detected"),
|
||||
Error::NoDeviceLeft => write!(f, "No device left"),
|
||||
Error::InvalidDevice => write!(
|
||||
f,
|
||||
"Device with non-supported product ID or vendor ID was detected"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -141,22 +151,46 @@ impl Manager {
|
||||
// * APDU_LENGTH (1 byte)
|
||||
// * APDU_Payload (Variable)
|
||||
//
|
||||
fn write(handle: &hidapi::HidDevice, command: u8, p1: u8, p2: u8, data: &[u8]) -> Result<(), Error> {
|
||||
fn write(
|
||||
handle: &hidapi::HidDevice,
|
||||
command: u8,
|
||||
p1: u8,
|
||||
p2: u8,
|
||||
data: &[u8],
|
||||
) -> Result<(), Error> {
|
||||
let data_len = data.len();
|
||||
let mut offset = 0;
|
||||
let mut sequence_number = 0;
|
||||
let mut hid_chunk = [0_u8; HID_PACKET_SIZE];
|
||||
|
||||
while sequence_number == 0 || offset < data_len {
|
||||
let header = if sequence_number == 0 { LEDGER_TRANSPORT_HEADER_LEN + APDU_PAYLOAD_HEADER_LEN } else { LEDGER_TRANSPORT_HEADER_LEN };
|
||||
let header = if sequence_number == 0 {
|
||||
LEDGER_TRANSPORT_HEADER_LEN + APDU_PAYLOAD_HEADER_LEN
|
||||
} else {
|
||||
LEDGER_TRANSPORT_HEADER_LEN
|
||||
};
|
||||
let size = min(64 - header, data_len - offset);
|
||||
{
|
||||
let chunk = &mut hid_chunk[HID_PREFIX_ZERO..];
|
||||
chunk[0..5].copy_from_slice(&[0x01, 0x01, APDU_TAG, (sequence_number >> 8) as u8, (sequence_number & 0xff) as u8 ]);
|
||||
chunk[0..5].copy_from_slice(&[
|
||||
0x01,
|
||||
0x01,
|
||||
APDU_TAG,
|
||||
(sequence_number >> 8) as u8,
|
||||
(sequence_number & 0xff) as u8,
|
||||
]);
|
||||
|
||||
if sequence_number == 0 {
|
||||
let data_len = data.len() + 5;
|
||||
chunk[5..12].copy_from_slice(&[(data_len >> 8) as u8, (data_len & 0xff) as u8, APDU_CLA, command, p1, p2, data.len() as u8]);
|
||||
chunk[5..12].copy_from_slice(&[
|
||||
(data_len >> 8) as u8,
|
||||
(data_len & 0xff) as u8,
|
||||
APDU_CLA,
|
||||
command,
|
||||
p1,
|
||||
p2,
|
||||
data.len() as u8,
|
||||
]);
|
||||
}
|
||||
|
||||
chunk[header..header + size].copy_from_slice(&data[offset..offset + size]);
|
||||
@ -199,7 +233,11 @@ impl Manager {
|
||||
let mut chunk: [u8; HID_PACKET_SIZE] = [0; HID_PACKET_SIZE];
|
||||
let chunk_size = handle.read(&mut chunk)?;
|
||||
trace!(target: "hw", "Ledger read {:?}", &chunk[..]);
|
||||
if chunk_size < LEDGER_TRANSPORT_HEADER_LEN || chunk[0] != 0x01 || chunk[1] != 0x01 || chunk[2] != APDU_TAG {
|
||||
if chunk_size < LEDGER_TRANSPORT_HEADER_LEN
|
||||
|| chunk[0] != 0x01
|
||||
|| chunk[1] != 0x01
|
||||
|| chunk[2] != APDU_TAG
|
||||
{
|
||||
return Err(Error::Protocol("Unexpected chunk header"));
|
||||
}
|
||||
let seq = (chunk[3] as usize) << 8 | (chunk[4] as usize);
|
||||
@ -225,28 +263,38 @@ impl Manager {
|
||||
if message.len() < 2 {
|
||||
return Err(Error::Protocol("No status word"));
|
||||
}
|
||||
let status = (message[message.len() - 2] as usize) << 8 | (message[message.len() - 1] as usize);
|
||||
let status =
|
||||
(message[message.len() - 2] as usize) << 8 | (message[message.len() - 1] as usize);
|
||||
debug!(target: "hw", "Read status {:x}", status);
|
||||
match status {
|
||||
0x6700 => Err(Error::Protocol("Incorrect length")),
|
||||
0x6982 => Err(Error::Protocol("Security status not satisfied (Canceled by user)")),
|
||||
0x6982 => Err(Error::Protocol(
|
||||
"Security status not satisfied (Canceled by user)",
|
||||
)),
|
||||
0x6a80 => Err(Error::Protocol("Invalid data")),
|
||||
0x6a82 => Err(Error::Protocol("File not found")),
|
||||
0x6a85 => Err(Error::UserCancel),
|
||||
0x6b00 => Err(Error::Protocol("Incorrect parameters")),
|
||||
0x6d00 => Err(Error::Protocol("Not implemented. Make sure the Ledger Ethereum Wallet app is running.")),
|
||||
0x6d00 => Err(Error::Protocol(
|
||||
"Not implemented. Make sure the Ledger Ethereum Wallet app is running.",
|
||||
)),
|
||||
0x6faa => Err(Error::Protocol("Your Ledger need to be unplugged")),
|
||||
0x6f00...0x6fff => Err(Error::Protocol("Internal error")),
|
||||
0x9000 => Ok(()),
|
||||
_ => Err(Error::Protocol("Unknown error")),
|
||||
|
||||
}?;
|
||||
let new_len = message.len() - 2;
|
||||
message.truncate(new_len);
|
||||
Ok(message)
|
||||
}
|
||||
|
||||
fn send_apdu(handle: &hidapi::HidDevice, command: u8, p1: u8, p2: u8, data: &[u8]) -> Result<Vec<u8>, Error> {
|
||||
fn send_apdu(
|
||||
handle: &hidapi::HidDevice,
|
||||
command: u8,
|
||||
p1: u8,
|
||||
p2: u8,
|
||||
data: &[u8],
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
Self::write(&handle, command, p1, p2, data)?;
|
||||
Self::read(&handle)
|
||||
}
|
||||
@ -256,7 +304,11 @@ impl Manager {
|
||||
if ver.len() != 4 {
|
||||
return Err(Error::Protocol("Version packet size mismatch"));
|
||||
}
|
||||
Ok(FirmwareVersion::new(ver[1].into(), ver[2].into(), ver[3].into()))
|
||||
Ok(FirmwareVersion::new(
|
||||
ver[1].into(),
|
||||
ver[2].into(),
|
||||
ver[3].into(),
|
||||
))
|
||||
}
|
||||
|
||||
fn get_derivation_path(&self) -> &[u8] {
|
||||
@ -266,21 +318,31 @@ impl Manager {
|
||||
}
|
||||
}
|
||||
|
||||
fn signer_helper(&self, address: &Address, data: &[u8], command: u8) -> Result<Signature, Error> {
|
||||
fn signer_helper(
|
||||
&self,
|
||||
address: &Address,
|
||||
data: &[u8],
|
||||
command: u8,
|
||||
) -> Result<Signature, Error> {
|
||||
let usb = self.usb.lock();
|
||||
let devices = self.devices.read();
|
||||
let device = devices.iter().find(|d| &d.info.address == address).ok_or(Error::KeyNotFound)?;
|
||||
let device = devices
|
||||
.iter()
|
||||
.find(|d| &d.info.address == address)
|
||||
.ok_or(Error::KeyNotFound)?;
|
||||
let handle = self.open_path(|| usb.open_path(&device.path))?;
|
||||
|
||||
// Signing personal messages are only support by Ledger firmware version 1.0.8 or newer
|
||||
if command == commands::SIGN_ETH_PERSONAL_MESSAGE {
|
||||
let version = Self::get_firmware_version(&handle)?;
|
||||
if version < FirmwareVersion::new(1, 0, 8) {
|
||||
return Err(Error::Protocol("Signing personal messages with Ledger requires version 1.0.8"));
|
||||
return Err(Error::Protocol(
|
||||
"Signing personal messages with Ledger requires version 1.0.8",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let mut chunk= [0_u8; MAX_CHUNK_SIZE];
|
||||
let mut chunk = [0_u8; MAX_CHUNK_SIZE];
|
||||
let derivation_path = self.get_derivation_path();
|
||||
|
||||
// Copy the address of the key (only done once)
|
||||
@ -326,7 +388,11 @@ impl<'a> Wallet<'a> for Manager {
|
||||
type Error = Error;
|
||||
type Transaction = &'a [u8];
|
||||
|
||||
fn sign_transaction(&self, address: &Address, transaction: Self::Transaction) -> Result<Signature, Self::Error> {
|
||||
fn sign_transaction(
|
||||
&self,
|
||||
address: &Address,
|
||||
transaction: Self::Transaction,
|
||||
) -> Result<Signature, Self::Error> {
|
||||
self.signer_helper(address, transaction, commands::SIGN_ETH_TRANSACTION)
|
||||
}
|
||||
|
||||
@ -346,10 +412,12 @@ impl<'a> Wallet<'a> for Manager {
|
||||
return Err(Error::NoDeviceArrived);
|
||||
}
|
||||
|
||||
let detected_devices = devices.iter()
|
||||
.filter(|&d| is_valid_ledger(d.vendor_id, d.product_id) &&
|
||||
is_valid_hid_device(d.usage_page, d.interface_number)
|
||||
)
|
||||
let detected_devices = devices
|
||||
.iter()
|
||||
.filter(|&d| {
|
||||
is_valid_ledger(d.vendor_id, d.product_id)
|
||||
&& is_valid_hid_device(d.usage_page, d.interface_number)
|
||||
})
|
||||
.fold(Vec::new(), |mut v, d| {
|
||||
match self.read_device(&usb, &d) {
|
||||
Ok(info) => {
|
||||
@ -382,14 +450,26 @@ impl<'a> Wallet<'a> for Manager {
|
||||
}
|
||||
}
|
||||
|
||||
fn read_device(&self, usb: &hidapi::HidApi, dev_info: &hidapi::HidDeviceInfo) -> Result<Device, Self::Error> {
|
||||
fn read_device(
|
||||
&self,
|
||||
usb: &hidapi::HidApi,
|
||||
dev_info: &hidapi::HidDeviceInfo,
|
||||
) -> Result<Device, Self::Error> {
|
||||
let handle = self.open_path(|| usb.open_path(&dev_info.path))?;
|
||||
let manufacturer = dev_info.manufacturer_string.clone().unwrap_or_else(|| "Unknown".to_owned());
|
||||
let name = dev_info.product_string.clone().unwrap_or_else(|| "Unknown".to_owned());
|
||||
let serial = dev_info.serial_number.clone().unwrap_or_else(|| "Unknown".to_owned());
|
||||
let manufacturer = dev_info
|
||||
.manufacturer_string
|
||||
.clone()
|
||||
.unwrap_or_else(|| "Unknown".to_owned());
|
||||
let name = dev_info
|
||||
.product_string
|
||||
.clone()
|
||||
.unwrap_or_else(|| "Unknown".to_owned());
|
||||
let serial = dev_info
|
||||
.serial_number
|
||||
.clone()
|
||||
.unwrap_or_else(|| "Unknown".to_owned());
|
||||
match self.get_address(&handle) {
|
||||
Ok(Some(addr)) => {
|
||||
Ok(Device {
|
||||
Ok(Some(addr)) => Ok(Device {
|
||||
path: dev_info.path.clone(),
|
||||
info: WalletInfo {
|
||||
name,
|
||||
@ -397,8 +477,7 @@ impl<'a> Wallet<'a> for Manager {
|
||||
serial,
|
||||
address: addr,
|
||||
},
|
||||
})
|
||||
}
|
||||
}),
|
||||
// This variant is not possible, but the trait forces this return type
|
||||
Ok(None) => Err(Error::Impossible),
|
||||
Err(e) => Err(e),
|
||||
@ -415,7 +494,11 @@ impl<'a> Wallet<'a> for Manager {
|
||||
}
|
||||
|
||||
fn get_wallet(&self, address: &Address) -> Option<WalletInfo> {
|
||||
self.devices.read().iter().find(|d| &d.info.address == address).map(|d| d.info.clone())
|
||||
self.devices
|
||||
.read()
|
||||
.iter()
|
||||
.find(|d| &d.info.address == address)
|
||||
.map(|d| d.info.clone())
|
||||
}
|
||||
|
||||
fn get_address(&self, device: &hidapi::HidDevice) -> Result<Option<Address>, Self::Error> {
|
||||
@ -426,8 +509,15 @@ impl<'a> Wallet<'a> for Manager {
|
||||
|
||||
let derivation_path = self.get_derivation_path();
|
||||
|
||||
let key_and_address = Self::send_apdu(device, commands::GET_ETH_PUBLIC_ADDRESS, 0, 0, derivation_path)?;
|
||||
if key_and_address.len() != 107 { // 1 + 65 PK + 1 + 40 Addr (ascii-hex)
|
||||
let key_and_address = Self::send_apdu(
|
||||
device,
|
||||
commands::GET_ETH_PUBLIC_ADDRESS,
|
||||
0,
|
||||
0,
|
||||
derivation_path,
|
||||
)?;
|
||||
if key_and_address.len() != 107 {
|
||||
// 1 + 65 PK + 1 + 40 Addr (ascii-hex)
|
||||
return Err(Error::Protocol("Key packet size mismatch"));
|
||||
}
|
||||
let address_string = ::std::str::from_utf8(&key_and_address[67..107])
|
||||
@ -440,7 +530,8 @@ impl<'a> Wallet<'a> for Manager {
|
||||
}
|
||||
|
||||
fn open_path<R, F>(&self, f: F) -> Result<R, Self::Error>
|
||||
where F: Fn() -> Result<R, &'static str>
|
||||
where
|
||||
F: Fn() -> Result<R, &'static str>,
|
||||
{
|
||||
f().map_err(Into::into)
|
||||
}
|
||||
@ -452,7 +543,11 @@ pub fn is_valid_ledger(vendor_id: u16, product_id: u16) -> bool {
|
||||
}
|
||||
|
||||
/// Poll the device in maximum `max_polling_duration` if it doesn't succeed
|
||||
pub fn try_connect_polling(ledger: &Manager, max_polling_duration: &Duration, device_direction: DeviceDirection) -> bool {
|
||||
pub fn try_connect_polling(
|
||||
ledger: &Manager,
|
||||
max_polling_duration: &Duration,
|
||||
device_direction: DeviceDirection,
|
||||
) -> bool {
|
||||
let start_time = Instant::now();
|
||||
while start_time.elapsed() <= *max_polling_duration {
|
||||
if let Ok(num_devices) = ledger.update_devices(device_direction) {
|
||||
@ -465,9 +560,9 @@ pub fn try_connect_polling(ledger: &Manager, max_polling_duration: &Duration, de
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rustc_hex::FromHex;
|
||||
use super::*;
|
||||
use ::HardwareWalletManager;
|
||||
use rustc_hex::FromHex;
|
||||
use HardwareWalletManager;
|
||||
|
||||
/// This test can't be run without an actual ledger device connected with the `Ledger Wallet Ethereum application` running
|
||||
#[test]
|
||||
@ -481,7 +576,8 @@ mod tests {
|
||||
ledger.update_devices(DeviceDirection::Arrived).expect("No Ledger found, make sure you have a unlocked Ledger connected with the Ledger Wallet Ethereum running");
|
||||
|
||||
// Fetch the ethereum address of a connected ledger device
|
||||
let address = ledger.list_devices()
|
||||
let address = ledger
|
||||
.list_devices()
|
||||
.iter()
|
||||
.filter(|d| d.manufacturer == "Ledger".to_string())
|
||||
.nth(0)
|
||||
@ -505,7 +601,8 @@ mod tests {
|
||||
ledger.update_devices(DeviceDirection::Arrived).expect("No Ledger found, make sure you have a unlocked Ledger connected with the Ledger Wallet Ethereum running");
|
||||
|
||||
// Fetch the ethereum address of a connected ledger device
|
||||
let address = ledger.list_devices()
|
||||
let address = ledger
|
||||
.list_devices()
|
||||
.iter()
|
||||
.filter(|d| d.manufacturer == "Ledger".to_string())
|
||||
.nth(0)
|
||||
|
@ -28,15 +28,20 @@ extern crate protobuf;
|
||||
extern crate semver;
|
||||
extern crate trezor_sys;
|
||||
|
||||
#[macro_use] extern crate log;
|
||||
#[cfg(test)] extern crate rustc_hex;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[cfg(test)]
|
||||
extern crate rustc_hex;
|
||||
|
||||
mod ledger;
|
||||
mod trezor;
|
||||
|
||||
use std::sync::{Arc, atomic, atomic::AtomicBool, Weak};
|
||||
use std::{fmt, time::Duration};
|
||||
use std::thread;
|
||||
use std::{
|
||||
fmt,
|
||||
sync::{atomic, atomic::AtomicBool, Arc, Weak},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use ethereum_types::U256;
|
||||
use ethkey::{Address, Signature};
|
||||
@ -62,7 +67,11 @@ pub trait Wallet<'a> {
|
||||
type Transaction;
|
||||
|
||||
/// Sign transaction data with wallet managing `address`.
|
||||
fn sign_transaction(&self, address: &Address, transaction: Self::Transaction) -> Result<Signature, Self::Error>;
|
||||
fn sign_transaction(
|
||||
&self,
|
||||
address: &Address,
|
||||
transaction: Self::Transaction,
|
||||
) -> Result<Signature, Self::Error>;
|
||||
|
||||
/// Set key derivation path for a chain.
|
||||
fn set_key_path(&self, key_path: KeyPath);
|
||||
@ -72,7 +81,11 @@ pub trait Wallet<'a> {
|
||||
fn update_devices(&self, device_direction: DeviceDirection) -> Result<usize, Self::Error>;
|
||||
|
||||
/// Read device info
|
||||
fn read_device(&self, usb: &hidapi::HidApi, dev_info: &hidapi::HidDeviceInfo) -> Result<Device, Self::Error>;
|
||||
fn read_device(
|
||||
&self,
|
||||
usb: &hidapi::HidApi,
|
||||
dev_info: &hidapi::HidDeviceInfo,
|
||||
) -> Result<Device, Self::Error>;
|
||||
|
||||
/// List connected and acknowledged wallets
|
||||
fn list_devices(&self) -> Vec<WalletInfo>;
|
||||
@ -95,7 +108,8 @@ pub trait Wallet<'a> {
|
||||
/// * <https://github.com/paritytech/hidapi-rs>
|
||||
/// * <https://github.com/rust-lang/libc>
|
||||
fn open_path<R, F>(&self, f: F) -> Result<R, Self::Error>
|
||||
where F: Fn() -> Result<R, &'static str>;
|
||||
where
|
||||
F: Fn() -> Result<R, &'static str>;
|
||||
}
|
||||
|
||||
/// Hardware wallet error.
|
||||
@ -220,7 +234,9 @@ impl HardwareWalletManager {
|
||||
/// Hardware wallet constructor
|
||||
pub fn new() -> Result<Self, Error> {
|
||||
let exiting = Arc::new(AtomicBool::new(false));
|
||||
let hidapi = Arc::new(Mutex::new(hidapi::HidApi::new().map_err(|e| Error::Hid(e.to_string().clone()))?));
|
||||
let hidapi = Arc::new(Mutex::new(
|
||||
hidapi::HidApi::new().map_err(|e| Error::Hid(e.to_string().clone()))?,
|
||||
));
|
||||
let ledger = ledger::Manager::new(hidapi.clone());
|
||||
let trezor = trezor::Manager::new(hidapi.clone());
|
||||
let usb_context = Arc::new(libusb::Context::new()?);
|
||||
@ -232,11 +248,13 @@ impl HardwareWalletManager {
|
||||
// Subscribe to all vendor IDs (VIDs) and product IDs (PIDs)
|
||||
// This means that the `HardwareWalletManager` is responsible to validate the detected device
|
||||
usb_context.register_callback(
|
||||
None, None, Some(HID_USB_DEVICE_CLASS),
|
||||
None,
|
||||
None,
|
||||
Some(HID_USB_DEVICE_CLASS),
|
||||
Box::new(EventHandler::new(
|
||||
Arc::downgrade(&ledger),
|
||||
Arc::downgrade(&trezor)
|
||||
))
|
||||
Arc::downgrade(&trezor),
|
||||
)),
|
||||
)?;
|
||||
|
||||
// Hardware event subscriber thread
|
||||
@ -309,7 +327,12 @@ impl HardwareWalletManager {
|
||||
}
|
||||
|
||||
/// Sign transaction data with wallet managing `address`.
|
||||
pub fn sign_transaction(&self, address: &Address, t_info: &TransactionInfo, encoded_transaction: &[u8]) -> Result<Signature, Error> {
|
||||
pub fn sign_transaction(
|
||||
&self,
|
||||
address: &Address,
|
||||
t_info: &TransactionInfo,
|
||||
encoded_transaction: &[u8],
|
||||
) -> Result<Signature, Error> {
|
||||
if self.ledger.get_wallet(address).is_some() {
|
||||
Ok(self.ledger.sign_transaction(address, encoded_transaction)?)
|
||||
} else if self.trezor.get_wallet(address).is_some() {
|
||||
@ -323,7 +346,9 @@ impl HardwareWalletManager {
|
||||
/// This is only applicable to Trezor because Ledger only appears as
|
||||
/// a device when it is unlocked
|
||||
pub fn pin_matrix_ack(&self, path: &str, pin: &str) -> Result<bool, Error> {
|
||||
self.trezor.pin_matrix_ack(path, pin).map_err(Error::TrezorDevice)
|
||||
self.trezor
|
||||
.pin_matrix_ack(path, pin)
|
||||
.map_err(Error::TrezorDevice)
|
||||
}
|
||||
}
|
||||
|
||||
@ -365,11 +390,19 @@ impl libusb::Hotplug for EventHandler {
|
||||
// Version ID and Product ID are available
|
||||
if let Ok((vid, pid)) = Self::extract_device_info(&device) {
|
||||
if trezor::is_valid_trezor(vid, pid) {
|
||||
if !trezor::try_connect_polling(&trezor, &MAX_POLLING_DURATION, DeviceDirection::Arrived) {
|
||||
if !trezor::try_connect_polling(
|
||||
&trezor,
|
||||
&MAX_POLLING_DURATION,
|
||||
DeviceDirection::Arrived,
|
||||
) {
|
||||
trace!(target: "hw", "Trezor device was detected but connection failed");
|
||||
}
|
||||
} else if ledger::is_valid_ledger(vid, pid) {
|
||||
if !ledger::try_connect_polling(&ledger, &MAX_POLLING_DURATION, DeviceDirection::Arrived) {
|
||||
if !ledger::try_connect_polling(
|
||||
&ledger,
|
||||
&MAX_POLLING_DURATION,
|
||||
DeviceDirection::Arrived,
|
||||
) {
|
||||
trace!(target: "hw", "Ledger device was detected but connection failed");
|
||||
}
|
||||
}
|
||||
@ -383,11 +416,19 @@ impl libusb::Hotplug for EventHandler {
|
||||
// Version ID and Product ID are available
|
||||
if let Ok((vid, pid)) = Self::extract_device_info(&device) {
|
||||
if trezor::is_valid_trezor(vid, pid) {
|
||||
if !trezor::try_connect_polling(&trezor, &MAX_POLLING_DURATION, DeviceDirection::Left) {
|
||||
if !trezor::try_connect_polling(
|
||||
&trezor,
|
||||
&MAX_POLLING_DURATION,
|
||||
DeviceDirection::Left,
|
||||
) {
|
||||
trace!(target: "hw", "Trezor device was detected but disconnection failed");
|
||||
}
|
||||
} else if ledger::is_valid_ledger(vid, pid) {
|
||||
if !ledger::try_connect_polling(&ledger, &MAX_POLLING_DURATION, DeviceDirection::Left) {
|
||||
if !ledger::try_connect_polling(
|
||||
&ledger,
|
||||
&MAX_POLLING_DURATION,
|
||||
DeviceDirection::Left,
|
||||
) {
|
||||
trace!(target: "hw", "Ledger device was detected but disconnection failed");
|
||||
}
|
||||
}
|
||||
|
@ -19,19 +19,26 @@
|
||||
//! and <https://github.com/trezor/trezor-common/blob/master/protob/protocol.md>
|
||||
//! for protocol details.
|
||||
|
||||
use std::cmp::{min, max};
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::fmt;
|
||||
use std::{
|
||||
cmp::{max, min},
|
||||
fmt,
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use ethereum_types::{U256, H256, Address};
|
||||
use super::{
|
||||
is_valid_hid_device, Device, DeviceDirection, KeyPath, TransactionInfo, Wallet, WalletInfo,
|
||||
};
|
||||
use ethereum_types::{Address, H256, U256};
|
||||
use ethkey::Signature;
|
||||
use hidapi;
|
||||
use libusb;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use protobuf::{self, Message, ProtobufEnum};
|
||||
use super::{DeviceDirection, WalletInfo, TransactionInfo, KeyPath, Wallet, Device, is_valid_hid_device};
|
||||
use trezor_sys::messages::{EthereumAddress, PinMatrixAck, MessageType, EthereumTxRequest, EthereumSignTx, EthereumGetAddress, EthereumTxAck, ButtonAck};
|
||||
use trezor_sys::messages::{
|
||||
ButtonAck, EthereumAddress, EthereumGetAddress, EthereumSignTx, EthereumTxAck,
|
||||
EthereumTxRequest, MessageType, PinMatrixAck,
|
||||
};
|
||||
|
||||
/// Trezor v1 vendor ID
|
||||
const TREZOR_VID: u16 = 0x534c;
|
||||
@ -77,11 +84,18 @@ impl fmt::Display for Error {
|
||||
Error::KeyNotFound => write!(f, "Key not found"),
|
||||
Error::UserCancel => write!(f, "Operation has been cancelled"),
|
||||
Error::BadMessageType => write!(f, "Bad Message Type in RPC call"),
|
||||
Error::LockedDevice(ref s) => write!(f, "Device is locked, needs PIN to perform operations: {}", s),
|
||||
Error::NoSigningMessage=> write!(f, "Signing messages are not supported by Trezor"),
|
||||
Error::LockedDevice(ref s) => write!(
|
||||
f,
|
||||
"Device is locked, needs PIN to perform operations: {}",
|
||||
s
|
||||
),
|
||||
Error::NoSigningMessage => write!(f, "Signing messages are not supported by Trezor"),
|
||||
Error::NoDeviceArrived => write!(f, "No device arrived"),
|
||||
Error::NoDeviceLeft => write!(f, "No device left"),
|
||||
Error::InvalidDevice => write!(f, "Device with non-supported product ID or vendor ID was detected"),
|
||||
Error::InvalidDevice => write!(
|
||||
f,
|
||||
"Device with non-supported product ID or vendor ID was detected"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -143,7 +157,6 @@ impl Manager {
|
||||
MessageType::MessageType_EthereumAddress => Ok(true),
|
||||
// Getting anything else means we didn't unlock it
|
||||
_ => Ok(false),
|
||||
|
||||
}
|
||||
};
|
||||
self.update_devices(DeviceDirection::Arrived)?;
|
||||
@ -156,12 +169,21 @@ impl Manager {
|
||||
buf.iter().skip_while(|x| **x == 0).cloned().collect()
|
||||
}
|
||||
|
||||
fn signing_loop(&self, handle: &hidapi::HidDevice, chain_id: &Option<u64>, data: &[u8]) -> Result<Signature, Error> {
|
||||
fn signing_loop(
|
||||
&self,
|
||||
handle: &hidapi::HidDevice,
|
||||
chain_id: &Option<u64>,
|
||||
data: &[u8],
|
||||
) -> Result<Signature, Error> {
|
||||
let (resp_type, bytes) = self.read_device_response(&handle)?;
|
||||
match resp_type {
|
||||
MessageType::MessageType_Cancel => Err(Error::UserCancel),
|
||||
MessageType::MessageType_ButtonRequest => {
|
||||
self.send_device_message(handle, MessageType::MessageType_ButtonAck, &ButtonAck::new())?;
|
||||
self.send_device_message(
|
||||
handle,
|
||||
MessageType::MessageType_ButtonAck,
|
||||
&ButtonAck::new(),
|
||||
)?;
|
||||
// Signing loop goes back to the top and reading blocks
|
||||
// for up to 5 minutes waiting for response from the device
|
||||
// if the user doesn't click any button within 5 minutes you
|
||||
@ -186,7 +208,11 @@ impl Manager {
|
||||
// so v' = v + 2 * chain_id + 35, but code further down the
|
||||
// pipeline will already do this transformation, so remove it here
|
||||
let adjustment = 35 + 2 * c_id as u32;
|
||||
Ok(Signature::from_rsv(&r, &s, (max(v, adjustment) - adjustment) as u8))
|
||||
Ok(Signature::from_rsv(
|
||||
&r,
|
||||
&s,
|
||||
(max(v, adjustment) - adjustment) as u8,
|
||||
))
|
||||
} else {
|
||||
// If there isn't a chain_id, v will be returned as v + 27
|
||||
let adjusted_v = if v < 27 { v } else { v - 27 };
|
||||
@ -194,12 +220,19 @@ impl Manager {
|
||||
}
|
||||
}
|
||||
}
|
||||
MessageType::MessageType_Failure => Err(Error::Protocol("Last message sent to Trezor failed")),
|
||||
MessageType::MessageType_Failure => {
|
||||
Err(Error::Protocol("Last message sent to Trezor failed"))
|
||||
}
|
||||
_ => Err(Error::Protocol("Unexpected response from Trezor device.")),
|
||||
}
|
||||
}
|
||||
|
||||
fn send_device_message(&self, device: &hidapi::HidDevice, msg_type: MessageType, msg: &Message) -> Result<usize, Error> {
|
||||
fn send_device_message(
|
||||
&self,
|
||||
device: &hidapi::HidDevice,
|
||||
msg_type: MessageType,
|
||||
msg: &Message,
|
||||
) -> Result<usize, Error> {
|
||||
let msg_id = msg_type as u16;
|
||||
let mut message = msg.write_to_bytes()?;
|
||||
let msg_size = message.len();
|
||||
@ -247,7 +280,10 @@ impl Manager {
|
||||
}
|
||||
}
|
||||
|
||||
fn read_device_response(&self, device: &hidapi::HidDevice) -> Result<(MessageType, Vec<u8>), Error> {
|
||||
fn read_device_response(
|
||||
&self,
|
||||
device: &hidapi::HidDevice,
|
||||
) -> Result<(MessageType, Vec<u8>), Error> {
|
||||
let protocol_err = Error::Protocol(&"Unexpected wire response from Trezor Device");
|
||||
let mut buf = vec![0; 64];
|
||||
|
||||
@ -255,8 +291,13 @@ impl Manager {
|
||||
if first_chunk < 9 || buf[0] != b'?' || buf[1] != b'#' || buf[2] != b'#' {
|
||||
return Err(protocol_err);
|
||||
}
|
||||
let msg_type = MessageType::from_i32(((buf[3] as i32 & 0xFF) << 8) + (buf[4] as i32 & 0xFF)).ok_or(protocol_err)?;
|
||||
let msg_size = ((buf[5] as u32 & 0xFF) << 24) + ((buf[6] as u32 & 0xFF) << 16) + ((buf[7] as u32 & 0xFF) << 8) + (buf[8] as u32 & 0xFF);
|
||||
let msg_type =
|
||||
MessageType::from_i32(((buf[3] as i32 & 0xFF) << 8) + (buf[4] as i32 & 0xFF))
|
||||
.ok_or(protocol_err)?;
|
||||
let msg_size = ((buf[5] as u32 & 0xFF) << 24)
|
||||
+ ((buf[6] as u32 & 0xFF) << 16)
|
||||
+ ((buf[7] as u32 & 0xFF) << 8)
|
||||
+ (buf[8] as u32 & 0xFF);
|
||||
let mut data = Vec::new();
|
||||
data.extend_from_slice(&buf[9..]);
|
||||
while data.len() < (msg_size as usize) {
|
||||
@ -271,11 +312,17 @@ impl<'a> Wallet<'a> for Manager {
|
||||
type Error = Error;
|
||||
type Transaction = &'a TransactionInfo;
|
||||
|
||||
fn sign_transaction(&self, address: &Address, t_info: Self::Transaction) ->
|
||||
Result<Signature, Error> {
|
||||
fn sign_transaction(
|
||||
&self,
|
||||
address: &Address,
|
||||
t_info: Self::Transaction,
|
||||
) -> Result<Signature, Error> {
|
||||
let usb = self.usb.lock();
|
||||
let devices = self.devices.read();
|
||||
let device = devices.iter().find(|d| &d.info.address == address).ok_or(Error::KeyNotFound)?;
|
||||
let device = devices
|
||||
.iter()
|
||||
.find(|d| &d.info.address == address)
|
||||
.ok_or(Error::KeyNotFound)?;
|
||||
let handle = self.open_path(|| usb.open_path(&device.path))?;
|
||||
let msg_type = MessageType::MessageType_EthereumSignTx;
|
||||
let mut message = EthereumSignTx::new();
|
||||
@ -301,7 +348,11 @@ impl<'a> Wallet<'a> for Manager {
|
||||
|
||||
self.send_device_message(&handle, msg_type, &message)?;
|
||||
|
||||
self.signing_loop(&handle, &t_info.chain_id, &t_info.data[first_chunk_length..])
|
||||
self.signing_loop(
|
||||
&handle,
|
||||
&t_info.chain_id,
|
||||
&t_info.data[first_chunk_length..],
|
||||
)
|
||||
}
|
||||
|
||||
fn set_key_path(&self, key_path: KeyPath) {
|
||||
@ -314,10 +365,12 @@ impl<'a> Wallet<'a> for Manager {
|
||||
let devices = usb.devices();
|
||||
let num_prev_devices = self.devices.read().len();
|
||||
|
||||
let detected_devices = devices.iter()
|
||||
.filter(|&d| is_valid_trezor(d.vendor_id, d.product_id) &&
|
||||
is_valid_hid_device(d.usage_page, d.interface_number)
|
||||
)
|
||||
let detected_devices = devices
|
||||
.iter()
|
||||
.filter(|&d| {
|
||||
is_valid_trezor(d.vendor_id, d.product_id)
|
||||
&& is_valid_hid_device(d.usage_page, d.interface_number)
|
||||
})
|
||||
.fold(Vec::new(), |mut v, d| {
|
||||
match self.read_device(&usb, &d) {
|
||||
Ok(info) => {
|
||||
@ -350,14 +403,26 @@ impl<'a> Wallet<'a> for Manager {
|
||||
}
|
||||
}
|
||||
|
||||
fn read_device(&self, usb: &hidapi::HidApi, dev_info: &hidapi::HidDeviceInfo) -> Result<Device, Error> {
|
||||
fn read_device(
|
||||
&self,
|
||||
usb: &hidapi::HidApi,
|
||||
dev_info: &hidapi::HidDeviceInfo,
|
||||
) -> Result<Device, Error> {
|
||||
let handle = self.open_path(|| usb.open_path(&dev_info.path))?;
|
||||
let manufacturer = dev_info.manufacturer_string.clone().unwrap_or_else(|| "Unknown".to_owned());
|
||||
let name = dev_info.product_string.clone().unwrap_or_else(|| "Unknown".to_owned());
|
||||
let serial = dev_info.serial_number.clone().unwrap_or_else(|| "Unknown".to_owned());
|
||||
let manufacturer = dev_info
|
||||
.manufacturer_string
|
||||
.clone()
|
||||
.unwrap_or_else(|| "Unknown".to_owned());
|
||||
let name = dev_info
|
||||
.product_string
|
||||
.clone()
|
||||
.unwrap_or_else(|| "Unknown".to_owned());
|
||||
let serial = dev_info
|
||||
.serial_number
|
||||
.clone()
|
||||
.unwrap_or_else(|| "Unknown".to_owned());
|
||||
match self.get_address(&handle) {
|
||||
Ok(Some(addr)) => {
|
||||
Ok(Device {
|
||||
Ok(Some(addr)) => Ok(Device {
|
||||
path: dev_info.path.clone(),
|
||||
info: WalletInfo {
|
||||
name,
|
||||
@ -365,8 +430,7 @@ impl<'a> Wallet<'a> for Manager {
|
||||
serial,
|
||||
address: addr,
|
||||
},
|
||||
})
|
||||
}
|
||||
}),
|
||||
Ok(None) => Err(Error::LockedDevice(dev_info.path.clone())),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
@ -381,7 +445,11 @@ impl<'a> Wallet<'a> for Manager {
|
||||
}
|
||||
|
||||
fn get_wallet(&self, address: &Address) -> Option<WalletInfo> {
|
||||
self.devices.read().iter().find(|d| &d.info.address == address).map(|d| d.info.clone())
|
||||
self.devices
|
||||
.read()
|
||||
.iter()
|
||||
.find(|d| &d.info.address == address)
|
||||
.map(|d| d.info.clone())
|
||||
}
|
||||
|
||||
fn get_address(&self, device: &hidapi::HidDevice) -> Result<Option<Address>, Error> {
|
||||
@ -405,7 +473,8 @@ impl<'a> Wallet<'a> for Manager {
|
||||
}
|
||||
|
||||
fn open_path<R, F>(&self, f: F) -> Result<R, Error>
|
||||
where F: Fn() -> Result<R, &'static str>
|
||||
where
|
||||
F: Fn() -> Result<R, &'static str>,
|
||||
{
|
||||
f().map_err(Into::into)
|
||||
}
|
||||
@ -417,7 +486,7 @@ pub fn try_connect_polling(trezor: &Manager, duration: &Duration, dir: DeviceDir
|
||||
while start_time.elapsed() <= *duration {
|
||||
if let Ok(num_devices) = trezor.update_devices(dir) {
|
||||
trace!(target: "hw", "{} Trezor devices {}", num_devices, dir);
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
@ -433,15 +502,23 @@ pub fn is_valid_trezor(vid: u16, pid: u16) -> bool {
|
||||
/// This test can't be run without an actual trezor device connected
|
||||
/// (and unlocked) attached to the machine that's running the test
|
||||
fn test_signature() {
|
||||
use super::HardwareWalletManager;
|
||||
use ethereum_types::Address;
|
||||
use MAX_POLLING_DURATION;
|
||||
use super::HardwareWalletManager;
|
||||
|
||||
let manager = HardwareWalletManager::new().unwrap();
|
||||
|
||||
assert_eq!(try_connect_polling(&manager.trezor, &MAX_POLLING_DURATION, DeviceDirection::Arrived), true);
|
||||
assert_eq!(
|
||||
try_connect_polling(
|
||||
&manager.trezor,
|
||||
&MAX_POLLING_DURATION,
|
||||
DeviceDirection::Arrived
|
||||
),
|
||||
true
|
||||
);
|
||||
|
||||
let addr: Address = manager.list_wallets()
|
||||
let addr: Address = manager
|
||||
.list_wallets()
|
||||
.iter()
|
||||
.filter(|d| d.name == "TREZOR".to_string() && d.manufacturer == "SatoshiLabs".to_string())
|
||||
.nth(0)
|
||||
|
@ -16,13 +16,10 @@
|
||||
|
||||
//! Account Metadata
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
time::Instant,
|
||||
};
|
||||
use std::{collections::HashMap, time::Instant};
|
||||
|
||||
use ethkey::{Address, Password};
|
||||
use serde_derive::{Serialize, Deserialize};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use serde_json;
|
||||
|
||||
/// Type of unlock.
|
||||
@ -57,17 +54,18 @@ pub struct AccountMeta {
|
||||
|
||||
impl AccountMeta {
|
||||
/// Read a hash map of Address -> AccountMeta
|
||||
pub fn read<R>(reader: R) -> Result<HashMap<Address, Self>, serde_json::Error> where
|
||||
pub fn read<R>(reader: R) -> Result<HashMap<Address, Self>, serde_json::Error>
|
||||
where
|
||||
R: ::std::io::Read,
|
||||
{
|
||||
serde_json::from_reader(reader)
|
||||
}
|
||||
|
||||
/// Write a hash map of Address -> AccountMeta
|
||||
pub fn write<W>(m: &HashMap<Address, Self>, writer: &mut W) -> Result<(), serde_json::Error> where
|
||||
pub fn write<W>(m: &HashMap<Address, Self>, writer: &mut W) -> Result<(), serde_json::Error>
|
||||
where
|
||||
W: ::std::io::Write,
|
||||
{
|
||||
serde_json::to_writer(writer, m)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,8 +16,8 @@
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use ethstore::{Error as SSError};
|
||||
use hardware_wallet::{Error as HardwareError};
|
||||
use ethstore::Error as SSError;
|
||||
use hardware_wallet::Error as HardwareError;
|
||||
|
||||
/// Signing error
|
||||
#[derive(Debug)]
|
||||
|
@ -25,28 +25,32 @@ mod stores;
|
||||
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
|
||||
extern crate fake_hardware_wallet as hardware_wallet;
|
||||
|
||||
use self::account_data::{Unlock, AccountData};
|
||||
use self::stores::AddressBook;
|
||||
use self::{
|
||||
account_data::{AccountData, Unlock},
|
||||
stores::AddressBook,
|
||||
};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::time::{Instant, Duration};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use common_types::transaction::{Action, Transaction};
|
||||
use ethkey::{Address, Message, Public, Secret, Password, Random, Generator};
|
||||
use ethstore::accounts_dir::MemoryDirectory;
|
||||
use ethkey::{Address, Generator, Message, Password, Public, Random, Secret};
|
||||
use ethstore::{
|
||||
SimpleSecretStore, SecretStore, EthStore, EthMultiStore,
|
||||
random_string, SecretVaultRef, StoreAccountRef, OpaqueSecret,
|
||||
accounts_dir::MemoryDirectory, random_string, EthMultiStore, EthStore, OpaqueSecret,
|
||||
SecretStore, SecretVaultRef, SimpleSecretStore, StoreAccountRef,
|
||||
};
|
||||
use log::{warn, debug};
|
||||
use log::{debug, warn};
|
||||
use parking_lot::RwLock;
|
||||
|
||||
pub use ethkey::Signature;
|
||||
pub use ethstore::{Derivation, IndexDerivation, KeyFile, Error};
|
||||
pub use hardware_wallet::{Error as HardwareError, HardwareWalletManager, KeyPath, TransactionInfo};
|
||||
pub use ethstore::{Derivation, Error, IndexDerivation, KeyFile};
|
||||
pub use hardware_wallet::{
|
||||
Error as HardwareError, HardwareWalletManager, KeyPath, TransactionInfo,
|
||||
};
|
||||
|
||||
pub use self::account_data::AccountMeta;
|
||||
pub use self::error::SignError;
|
||||
pub use self::{account_data::AccountMeta, error::SignError};
|
||||
|
||||
type AccountToken = Password;
|
||||
|
||||
@ -86,7 +90,8 @@ pub struct AccountProvider {
|
||||
}
|
||||
|
||||
fn transient_sstore() -> EthMultiStore {
|
||||
EthMultiStore::open(Box::new(MemoryDirectory::default())).expect("MemoryDirectory load always succeeds; qed")
|
||||
EthMultiStore::open(Box::new(MemoryDirectory::default()))
|
||||
.expect("MemoryDirectory load always succeeds; qed")
|
||||
}
|
||||
|
||||
impl AccountProvider {
|
||||
@ -97,15 +102,22 @@ impl AccountProvider {
|
||||
if settings.enable_hardware_wallets {
|
||||
match HardwareWalletManager::new() {
|
||||
Ok(manager) => {
|
||||
manager.set_key_path(if settings.hardware_wallet_classic_key { KeyPath::EthereumClassic } else { KeyPath::Ethereum });
|
||||
manager.set_key_path(if settings.hardware_wallet_classic_key {
|
||||
KeyPath::EthereumClassic
|
||||
} else {
|
||||
KeyPath::Ethereum
|
||||
});
|
||||
hardware_store = Some(manager)
|
||||
},
|
||||
}
|
||||
Err(e) => debug!("Error initializing hardware wallets: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(accounts) = sstore.accounts() {
|
||||
for account in accounts.into_iter().filter(|a| settings.blacklisted_accounts.contains(&a.address)) {
|
||||
for account in accounts
|
||||
.into_iter()
|
||||
.filter(|a| settings.blacklisted_accounts.contains(&a.address))
|
||||
{
|
||||
warn!("Local Account {} has a blacklisted (known to be weak) address and will be ignored",
|
||||
account.address);
|
||||
}
|
||||
@ -135,7 +147,10 @@ impl AccountProvider {
|
||||
unlocked_secrets: RwLock::new(HashMap::new()),
|
||||
unlocked: RwLock::new(HashMap::new()),
|
||||
address_book: RwLock::new(AddressBook::transient()),
|
||||
sstore: Box::new(EthStore::open(Box::new(MemoryDirectory::default())).expect("MemoryDirectory load always succeeds; qed")),
|
||||
sstore: Box::new(
|
||||
EthStore::open(Box::new(MemoryDirectory::default()))
|
||||
.expect("MemoryDirectory load always succeeds; qed"),
|
||||
),
|
||||
transient_sstore: transient_sstore(),
|
||||
hardware_store: None,
|
||||
unlock_keep_secret: false,
|
||||
@ -150,17 +165,23 @@ impl AccountProvider {
|
||||
|
||||
/// Creates new random account and returns address and public key
|
||||
pub fn new_account_and_public(&self, password: &Password) -> Result<(Address, Public), Error> {
|
||||
let acc = Random.generate().expect("secp context has generation capabilities; qed");
|
||||
let acc = Random
|
||||
.generate()
|
||||
.expect("secp context has generation capabilities; qed");
|
||||
let public = acc.public().clone();
|
||||
let secret = acc.secret().clone();
|
||||
let account = self.sstore.insert_account(SecretVaultRef::Root, secret, password)?;
|
||||
let account = self
|
||||
.sstore
|
||||
.insert_account(SecretVaultRef::Root, secret, password)?;
|
||||
Ok((account.address, public))
|
||||
}
|
||||
|
||||
/// Inserts new account into underlying store.
|
||||
/// Does not unlock account!
|
||||
pub fn insert_account(&self, secret: Secret, password: &Password) -> Result<Address, Error> {
|
||||
let account = self.sstore.insert_account(SecretVaultRef::Root, secret, password)?;
|
||||
let account = self
|
||||
.sstore
|
||||
.insert_account(SecretVaultRef::Root, secret, password)?;
|
||||
if self.blacklisted_accounts.contains(&account.address) {
|
||||
self.sstore.remove_account(&account, password)?;
|
||||
return Err(Error::InvalidAccount.into());
|
||||
@ -171,26 +192,49 @@ impl AccountProvider {
|
||||
/// Generates new derived account based on the existing one
|
||||
/// If password is not provided, account must be unlocked
|
||||
/// New account will be created with the same password (if save: true)
|
||||
pub fn derive_account(&self, address: &Address, password: Option<Password>, derivation: Derivation, save: bool)
|
||||
-> Result<Address, SignError>
|
||||
{
|
||||
pub fn derive_account(
|
||||
&self,
|
||||
address: &Address,
|
||||
password: Option<Password>,
|
||||
derivation: Derivation,
|
||||
save: bool,
|
||||
) -> Result<Address, SignError> {
|
||||
let account = self.sstore.account_ref(&address)?;
|
||||
let password = password.map(Ok).unwrap_or_else(|| self.password(&account))?;
|
||||
Ok(
|
||||
if save { self.sstore.insert_derived(SecretVaultRef::Root, &account, &password, derivation)?.address }
|
||||
else { self.sstore.generate_derived(&account, &password, derivation)? }
|
||||
)
|
||||
let password = password
|
||||
.map(Ok)
|
||||
.unwrap_or_else(|| self.password(&account))?;
|
||||
Ok(if save {
|
||||
self.sstore
|
||||
.insert_derived(SecretVaultRef::Root, &account, &password, derivation)?
|
||||
.address
|
||||
} else {
|
||||
self.sstore
|
||||
.generate_derived(&account, &password, derivation)?
|
||||
})
|
||||
}
|
||||
|
||||
/// Import a new presale wallet.
|
||||
pub fn import_presale(&self, presale_json: &[u8], password: &Password) -> Result<Address, Error> {
|
||||
let account = self.sstore.import_presale(SecretVaultRef::Root, presale_json, password)?;
|
||||
pub fn import_presale(
|
||||
&self,
|
||||
presale_json: &[u8],
|
||||
password: &Password,
|
||||
) -> Result<Address, Error> {
|
||||
let account = self
|
||||
.sstore
|
||||
.import_presale(SecretVaultRef::Root, presale_json, password)?;
|
||||
Ok(Address::from(account.address).into())
|
||||
}
|
||||
|
||||
/// Import a new wallet.
|
||||
pub fn import_wallet(&self, json: &[u8], password: &Password, gen_id: bool) -> Result<Address, Error> {
|
||||
let account = self.sstore.import_wallet(SecretVaultRef::Root, json, password, gen_id)?;
|
||||
pub fn import_wallet(
|
||||
&self,
|
||||
json: &[u8],
|
||||
password: &Password,
|
||||
gen_id: bool,
|
||||
) -> Result<Address, Error> {
|
||||
let account = self
|
||||
.sstore
|
||||
.import_wallet(SecretVaultRef::Root, json, password, gen_id)?;
|
||||
if self.blacklisted_accounts.contains(&account.address) {
|
||||
self.sstore.remove_account(&account, password)?;
|
||||
return Err(Error::InvalidAccount.into());
|
||||
@ -210,8 +254,7 @@ impl AccountProvider {
|
||||
.into_iter()
|
||||
.map(|a| a.address)
|
||||
.filter(|address| !self.blacklisted_accounts.contains(address))
|
||||
.collect()
|
||||
)
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Returns the address of default account.
|
||||
@ -226,12 +269,18 @@ impl AccountProvider {
|
||||
return Ok(accounts.into_iter().map(|a| a.address).collect());
|
||||
}
|
||||
}
|
||||
Err(Error::Custom("No hardware wallet accounts were found".into()))
|
||||
Err(Error::Custom(
|
||||
"No hardware wallet accounts were found".into(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Get a list of paths to locked hardware wallets
|
||||
pub fn locked_hardware_accounts(&self) -> Result<Vec<String>, SignError> {
|
||||
match self.hardware_store.as_ref().map(|h| h.list_locked_wallets()) {
|
||||
match self
|
||||
.hardware_store
|
||||
.as_ref()
|
||||
.map(|h| h.list_locked_wallets())
|
||||
{
|
||||
None => Err(SignError::NotFound),
|
||||
Some(Err(e)) => Err(SignError::Hardware(e)),
|
||||
Some(Ok(s)) => Ok(s),
|
||||
@ -240,7 +289,11 @@ impl AccountProvider {
|
||||
|
||||
/// Provide a pin to a locked hardware wallet on USB path to unlock it
|
||||
pub fn hardware_pin_matrix_ack(&self, path: &str, pin: &str) -> Result<bool, SignError> {
|
||||
match self.hardware_store.as_ref().map(|h| h.pin_matrix_ack(path, pin)) {
|
||||
match self
|
||||
.hardware_store
|
||||
.as_ref()
|
||||
.map(|h| h.pin_matrix_ack(path, pin))
|
||||
{
|
||||
None => Err(SignError::NotFound),
|
||||
Some(Err(e)) => Err(SignError::Hardware(e)),
|
||||
Some(Ok(s)) => Ok(s),
|
||||
@ -269,31 +322,51 @@ impl AccountProvider {
|
||||
|
||||
/// Returns each account along with name and meta.
|
||||
pub fn accounts_info(&self) -> Result<HashMap<Address, AccountMeta>, Error> {
|
||||
let r = self.sstore.accounts()?
|
||||
let r = self
|
||||
.sstore
|
||||
.accounts()?
|
||||
.into_iter()
|
||||
.filter(|a| !self.blacklisted_accounts.contains(&a.address))
|
||||
.map(|a| (a.address.clone(), self.account_meta(a.address).ok().unwrap_or_default()))
|
||||
.map(|a| {
|
||||
(
|
||||
a.address.clone(),
|
||||
self.account_meta(a.address).ok().unwrap_or_default(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
/// Returns each hardware account along with name and meta.
|
||||
pub fn hardware_accounts_info(&self) -> Result<HashMap<Address, AccountMeta>, Error> {
|
||||
let r = self.hardware_accounts()?
|
||||
let r = self
|
||||
.hardware_accounts()?
|
||||
.into_iter()
|
||||
.map(|address| (address.clone(), self.account_meta(address).ok().unwrap_or_default()))
|
||||
.map(|address| {
|
||||
(
|
||||
address.clone(),
|
||||
self.account_meta(address).ok().unwrap_or_default(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
/// Returns each hardware account along with name and meta.
|
||||
pub fn is_hardware_address(&self, address: &Address) -> bool {
|
||||
self.hardware_store.as_ref().and_then(|s| s.wallet_info(address)).is_some()
|
||||
self.hardware_store
|
||||
.as_ref()
|
||||
.and_then(|s| s.wallet_info(address))
|
||||
.is_some()
|
||||
}
|
||||
|
||||
/// Returns each account along with name and meta.
|
||||
pub fn account_meta(&self, address: Address) -> Result<AccountMeta, Error> {
|
||||
if let Some(info) = self.hardware_store.as_ref().and_then(|s| s.wallet_info(&address)) {
|
||||
if let Some(info) = self
|
||||
.hardware_store
|
||||
.as_ref()
|
||||
.and_then(|s| s.wallet_info(&address))
|
||||
{
|
||||
Ok(AccountMeta {
|
||||
name: info.name,
|
||||
meta: info.manufacturer,
|
||||
@ -311,59 +384,78 @@ impl AccountProvider {
|
||||
|
||||
/// Returns account public key.
|
||||
pub fn account_public(&self, address: Address, password: &Password) -> Result<Public, Error> {
|
||||
self.sstore.public(&self.sstore.account_ref(&address)?, password)
|
||||
self.sstore
|
||||
.public(&self.sstore.account_ref(&address)?, password)
|
||||
}
|
||||
|
||||
/// Returns each account along with name and meta.
|
||||
pub fn set_account_name(&self, address: Address, name: String) -> Result<(), Error> {
|
||||
self.sstore.set_name(&self.sstore.account_ref(&address)?, name)?;
|
||||
self.sstore
|
||||
.set_name(&self.sstore.account_ref(&address)?, name)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns each account along with name and meta.
|
||||
pub fn set_account_meta(&self, address: Address, meta: String) -> Result<(), Error> {
|
||||
self.sstore.set_meta(&self.sstore.account_ref(&address)?, meta)?;
|
||||
self.sstore
|
||||
.set_meta(&self.sstore.account_ref(&address)?, meta)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns `true` if the password for `account` is `password`. `false` if not.
|
||||
pub fn test_password(&self, address: &Address, password: &Password) -> Result<bool, Error> {
|
||||
self.sstore.test_password(&self.sstore.account_ref(&address)?, password)
|
||||
self.sstore
|
||||
.test_password(&self.sstore.account_ref(&address)?, password)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Permanently removes an account.
|
||||
pub fn kill_account(&self, address: &Address, password: &Password) -> Result<(), Error> {
|
||||
self.sstore.remove_account(&self.sstore.account_ref(&address)?, &password)?;
|
||||
self.sstore
|
||||
.remove_account(&self.sstore.account_ref(&address)?, &password)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Changes the password of `account` from `password` to `new_password`. Fails if incorrect `password` given.
|
||||
pub fn change_password(&self, address: &Address, password: Password, new_password: Password) -> Result<(), Error> {
|
||||
self.sstore.change_password(&self.sstore.account_ref(address)?, &password, &new_password)
|
||||
pub fn change_password(
|
||||
&self,
|
||||
address: &Address,
|
||||
password: Password,
|
||||
new_password: Password,
|
||||
) -> Result<(), Error> {
|
||||
self.sstore
|
||||
.change_password(&self.sstore.account_ref(address)?, &password, &new_password)
|
||||
}
|
||||
|
||||
/// Exports an account for given address.
|
||||
pub fn export_account(&self, address: &Address, password: Password) -> Result<KeyFile, Error> {
|
||||
self.sstore.export_account(&self.sstore.account_ref(address)?, &password)
|
||||
self.sstore
|
||||
.export_account(&self.sstore.account_ref(address)?, &password)
|
||||
}
|
||||
|
||||
/// Helper method used for unlocking accounts.
|
||||
fn unlock_account(&self, address: Address, password: Password, unlock: Unlock) -> Result<(), Error> {
|
||||
fn unlock_account(
|
||||
&self,
|
||||
address: Address,
|
||||
password: Password,
|
||||
unlock: Unlock,
|
||||
) -> Result<(), Error> {
|
||||
let account = self.sstore.account_ref(&address)?;
|
||||
|
||||
// check if account is already unlocked permanently, if it is, do nothing
|
||||
let mut unlocked = self.unlocked.write();
|
||||
if let Some(data) = unlocked.get(&account) {
|
||||
if let Unlock::Perm = data.unlock {
|
||||
return Ok(())
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
if self.unlock_keep_secret && unlock == Unlock::Perm {
|
||||
// verify password and get the secret
|
||||
let secret = self.sstore.raw_secret(&account, &password)?;
|
||||
self.unlocked_secrets.write().insert(account.clone(), secret);
|
||||
self.unlocked_secrets
|
||||
.write()
|
||||
.insert(account.clone(), secret);
|
||||
} else {
|
||||
// verify password by signing dump message
|
||||
// result may be discarded
|
||||
@ -383,11 +475,15 @@ impl AccountProvider {
|
||||
let mut unlocked = self.unlocked.write();
|
||||
let data = unlocked.get(account).ok_or(SignError::NotUnlocked)?.clone();
|
||||
if let Unlock::OneTime = data.unlock {
|
||||
unlocked.remove(account).expect("data exists: so key must exist: qed");
|
||||
unlocked
|
||||
.remove(account)
|
||||
.expect("data exists: so key must exist: qed");
|
||||
}
|
||||
if let Unlock::Timed(ref end) = data.unlock {
|
||||
if Instant::now() > *end {
|
||||
unlocked.remove(account).expect("data exists: so key must exist: qed");
|
||||
unlocked
|
||||
.remove(account)
|
||||
.expect("data exists: so key must exist: qed");
|
||||
return Err(SignError::NotUnlocked);
|
||||
}
|
||||
}
|
||||
@ -395,17 +491,30 @@ impl AccountProvider {
|
||||
}
|
||||
|
||||
/// Unlocks account permanently.
|
||||
pub fn unlock_account_permanently(&self, account: Address, password: Password) -> Result<(), Error> {
|
||||
pub fn unlock_account_permanently(
|
||||
&self,
|
||||
account: Address,
|
||||
password: Password,
|
||||
) -> Result<(), Error> {
|
||||
self.unlock_account(account, password, Unlock::Perm)
|
||||
}
|
||||
|
||||
/// Unlocks account temporarily (for one signing).
|
||||
pub fn unlock_account_temporarily(&self, account: Address, password: Password) -> Result<(), Error> {
|
||||
pub fn unlock_account_temporarily(
|
||||
&self,
|
||||
account: Address,
|
||||
password: Password,
|
||||
) -> Result<(), Error> {
|
||||
self.unlock_account(account, password, Unlock::OneTime)
|
||||
}
|
||||
|
||||
/// Unlocks account temporarily with a timeout.
|
||||
pub fn unlock_account_timed(&self, account: Address, password: Password, duration: Duration) -> Result<(), Error> {
|
||||
pub fn unlock_account_timed(
|
||||
&self,
|
||||
account: Address,
|
||||
password: Password,
|
||||
duration: Duration,
|
||||
) -> Result<(), Error> {
|
||||
self.unlock_account(account, password, Unlock::Timed(Instant::now() + duration))
|
||||
}
|
||||
|
||||
@ -413,7 +522,8 @@ impl AccountProvider {
|
||||
pub fn is_unlocked(&self, address: &Address) -> bool {
|
||||
let unlocked = self.unlocked.read();
|
||||
let unlocked_secrets = self.unlocked_secrets.read();
|
||||
self.sstore.account_ref(address)
|
||||
self.sstore
|
||||
.account_ref(address)
|
||||
.map(|r| unlocked.get(&r).is_some() || unlocked_secrets.get(&r).is_some())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
@ -421,48 +531,78 @@ impl AccountProvider {
|
||||
/// Checks if given account is unlocked permanently
|
||||
pub fn is_unlocked_permanently(&self, address: &Address) -> bool {
|
||||
let unlocked = self.unlocked.read();
|
||||
self.sstore.account_ref(address)
|
||||
.map(|r| unlocked.get(&r).map_or(false, |account| account.unlock == Unlock::Perm))
|
||||
self.sstore
|
||||
.account_ref(address)
|
||||
.map(|r| {
|
||||
unlocked
|
||||
.get(&r)
|
||||
.map_or(false, |account| account.unlock == Unlock::Perm)
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Signs the message. If password is not provided the account must be unlocked.
|
||||
pub fn sign(&self, address: Address, password: Option<Password>, message: Message) -> Result<Signature, SignError> {
|
||||
pub fn sign(
|
||||
&self,
|
||||
address: Address,
|
||||
password: Option<Password>,
|
||||
message: Message,
|
||||
) -> Result<Signature, SignError> {
|
||||
let account = self.sstore.account_ref(&address)?;
|
||||
match self.unlocked_secrets.read().get(&account) {
|
||||
Some(secret) => {
|
||||
Ok(self.sstore.sign_with_secret(&secret, &message)?)
|
||||
},
|
||||
Some(secret) => Ok(self.sstore.sign_with_secret(&secret, &message)?),
|
||||
None => {
|
||||
let password = password.map(Ok).unwrap_or_else(|| self.password(&account))?;
|
||||
let password = password
|
||||
.map(Ok)
|
||||
.unwrap_or_else(|| self.password(&account))?;
|
||||
Ok(self.sstore.sign(&account, &password, &message)?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Signs message using the derived secret. If password is not provided the account must be unlocked.
|
||||
pub fn sign_derived(&self, address: &Address, password: Option<Password>, derivation: Derivation, message: Message)
|
||||
-> Result<Signature, SignError>
|
||||
{
|
||||
pub fn sign_derived(
|
||||
&self,
|
||||
address: &Address,
|
||||
password: Option<Password>,
|
||||
derivation: Derivation,
|
||||
message: Message,
|
||||
) -> Result<Signature, SignError> {
|
||||
let account = self.sstore.account_ref(address)?;
|
||||
let password = password.map(Ok).unwrap_or_else(|| self.password(&account))?;
|
||||
Ok(self.sstore.sign_derived(&account, &password, derivation, &message)?)
|
||||
let password = password
|
||||
.map(Ok)
|
||||
.unwrap_or_else(|| self.password(&account))?;
|
||||
Ok(self
|
||||
.sstore
|
||||
.sign_derived(&account, &password, derivation, &message)?)
|
||||
}
|
||||
|
||||
/// Signs given message with supplied token. Returns a token to use in next signing within this session.
|
||||
pub fn sign_with_token(&self, address: Address, token: AccountToken, message: Message) -> Result<(Signature, AccountToken), SignError> {
|
||||
pub fn sign_with_token(
|
||||
&self,
|
||||
address: Address,
|
||||
token: AccountToken,
|
||||
message: Message,
|
||||
) -> Result<(Signature, AccountToken), SignError> {
|
||||
let account = self.sstore.account_ref(&address)?;
|
||||
let is_std_password = self.sstore.test_password(&account, &token)?;
|
||||
|
||||
let new_token = Password::from(random_string(16));
|
||||
let signature = if is_std_password {
|
||||
// Insert to transient store
|
||||
self.sstore.copy_account(&self.transient_sstore, SecretVaultRef::Root, &account, &token, &new_token)?;
|
||||
self.sstore.copy_account(
|
||||
&self.transient_sstore,
|
||||
SecretVaultRef::Root,
|
||||
&account,
|
||||
&token,
|
||||
&new_token,
|
||||
)?;
|
||||
// sign
|
||||
self.sstore.sign(&account, &token, &message)?
|
||||
} else {
|
||||
// check transient store
|
||||
self.transient_sstore.change_password(&account, &token, &new_token)?;
|
||||
self.transient_sstore
|
||||
.change_password(&account, &token, &new_token)?;
|
||||
// and sign
|
||||
self.transient_sstore.sign(&account, &new_token, &message)?
|
||||
};
|
||||
@ -471,114 +611,159 @@ impl AccountProvider {
|
||||
}
|
||||
|
||||
/// Decrypts a message with given token. Returns a token to use in next operation for this account.
|
||||
pub fn decrypt_with_token(&self, address: Address, token: AccountToken, shared_mac: &[u8], message: &[u8])
|
||||
-> Result<(Vec<u8>, AccountToken), SignError>
|
||||
{
|
||||
pub fn decrypt_with_token(
|
||||
&self,
|
||||
address: Address,
|
||||
token: AccountToken,
|
||||
shared_mac: &[u8],
|
||||
message: &[u8],
|
||||
) -> Result<(Vec<u8>, AccountToken), SignError> {
|
||||
let account = self.sstore.account_ref(&address)?;
|
||||
let is_std_password = self.sstore.test_password(&account, &token)?;
|
||||
|
||||
let new_token = Password::from(random_string(16));
|
||||
let message = if is_std_password {
|
||||
// Insert to transient store
|
||||
self.sstore.copy_account(&self.transient_sstore, SecretVaultRef::Root, &account, &token, &new_token)?;
|
||||
self.sstore.copy_account(
|
||||
&self.transient_sstore,
|
||||
SecretVaultRef::Root,
|
||||
&account,
|
||||
&token,
|
||||
&new_token,
|
||||
)?;
|
||||
// decrypt
|
||||
self.sstore.decrypt(&account, &token, shared_mac, message)?
|
||||
} else {
|
||||
// check transient store
|
||||
self.transient_sstore.change_password(&account, &token, &new_token)?;
|
||||
self.transient_sstore
|
||||
.change_password(&account, &token, &new_token)?;
|
||||
// and decrypt
|
||||
self.transient_sstore.decrypt(&account, &token, shared_mac, message)?
|
||||
self.transient_sstore
|
||||
.decrypt(&account, &token, shared_mac, message)?
|
||||
};
|
||||
|
||||
Ok((message, new_token))
|
||||
}
|
||||
|
||||
/// Decrypts a message. If password is not provided the account must be unlocked.
|
||||
pub fn decrypt(&self, address: Address, password: Option<Password>, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, SignError> {
|
||||
pub fn decrypt(
|
||||
&self,
|
||||
address: Address,
|
||||
password: Option<Password>,
|
||||
shared_mac: &[u8],
|
||||
message: &[u8],
|
||||
) -> Result<Vec<u8>, SignError> {
|
||||
let account = self.sstore.account_ref(&address)?;
|
||||
let password = password.map(Ok).unwrap_or_else(|| self.password(&account))?;
|
||||
Ok(self.sstore.decrypt(&account, &password, shared_mac, message)?)
|
||||
let password = password
|
||||
.map(Ok)
|
||||
.unwrap_or_else(|| self.password(&account))?;
|
||||
Ok(self
|
||||
.sstore
|
||||
.decrypt(&account, &password, shared_mac, message)?)
|
||||
}
|
||||
|
||||
/// Agree on shared key.
|
||||
pub fn agree(&self, address: Address, password: Option<Password>, other_public: &Public) -> Result<Secret, SignError> {
|
||||
pub fn agree(
|
||||
&self,
|
||||
address: Address,
|
||||
password: Option<Password>,
|
||||
other_public: &Public,
|
||||
) -> Result<Secret, SignError> {
|
||||
let account = self.sstore.account_ref(&address)?;
|
||||
let password = password.map(Ok).unwrap_or_else(|| self.password(&account))?;
|
||||
let password = password
|
||||
.map(Ok)
|
||||
.unwrap_or_else(|| self.password(&account))?;
|
||||
Ok(self.sstore.agree(&account, &password, other_public)?)
|
||||
}
|
||||
|
||||
/// Returns the underlying `SecretStore` reference if one exists.
|
||||
pub fn list_geth_accounts(&self, testnet: bool) -> Vec<Address> {
|
||||
self.sstore.list_geth_accounts(testnet).into_iter().map(|a| Address::from(a).into()).collect()
|
||||
self.sstore
|
||||
.list_geth_accounts(testnet)
|
||||
.into_iter()
|
||||
.map(|a| Address::from(a).into())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns the underlying `SecretStore` reference if one exists.
|
||||
pub fn import_geth_accounts(&self, desired: Vec<Address>, testnet: bool) -> Result<Vec<Address>, Error> {
|
||||
self.sstore.import_geth_accounts(SecretVaultRef::Root, desired, testnet)
|
||||
pub fn import_geth_accounts(
|
||||
&self,
|
||||
desired: Vec<Address>,
|
||||
testnet: bool,
|
||||
) -> Result<Vec<Address>, Error> {
|
||||
self.sstore
|
||||
.import_geth_accounts(SecretVaultRef::Root, desired, testnet)
|
||||
.map(|a| a.into_iter().map(|a| a.address).collect())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Create new vault.
|
||||
pub fn create_vault(&self, name: &str, password: &Password) -> Result<(), Error> {
|
||||
self.sstore.create_vault(name, password)
|
||||
.map_err(Into::into)
|
||||
self.sstore.create_vault(name, password).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Open existing vault.
|
||||
pub fn open_vault(&self, name: &str, password: &Password) -> Result<(), Error> {
|
||||
self.sstore.open_vault(name, password)
|
||||
.map_err(Into::into)
|
||||
self.sstore.open_vault(name, password).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Close previously opened vault.
|
||||
pub fn close_vault(&self, name: &str) -> Result<(), Error> {
|
||||
self.sstore.close_vault(name)
|
||||
.map_err(Into::into)
|
||||
self.sstore.close_vault(name).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// List all vaults
|
||||
pub fn list_vaults(&self) -> Result<Vec<String>, Error> {
|
||||
self.sstore.list_vaults()
|
||||
.map_err(Into::into)
|
||||
self.sstore.list_vaults().map_err(Into::into)
|
||||
}
|
||||
|
||||
/// List all currently opened vaults
|
||||
pub fn list_opened_vaults(&self) -> Result<Vec<String>, Error> {
|
||||
self.sstore.list_opened_vaults()
|
||||
.map_err(Into::into)
|
||||
self.sstore.list_opened_vaults().map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Change vault password.
|
||||
pub fn change_vault_password(&self, name: &str, new_password: &Password) -> Result<(), Error> {
|
||||
self.sstore.change_vault_password(name, new_password)
|
||||
self.sstore
|
||||
.change_vault_password(name, new_password)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Change vault of the given address.
|
||||
pub fn change_vault(&self, address: Address, new_vault: &str) -> Result<(), Error> {
|
||||
let new_vault_ref = if new_vault.is_empty() { SecretVaultRef::Root } else { SecretVaultRef::Vault(new_vault.to_owned()) };
|
||||
let new_vault_ref = if new_vault.is_empty() {
|
||||
SecretVaultRef::Root
|
||||
} else {
|
||||
SecretVaultRef::Vault(new_vault.to_owned())
|
||||
};
|
||||
let old_account_ref = self.sstore.account_ref(&address)?;
|
||||
self.sstore.change_account_vault(new_vault_ref, old_account_ref)
|
||||
self.sstore
|
||||
.change_account_vault(new_vault_ref, old_account_ref)
|
||||
.map_err(Into::into)
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
/// Get vault metadata string.
|
||||
pub fn get_vault_meta(&self, name: &str) -> Result<String, Error> {
|
||||
self.sstore.get_vault_meta(name)
|
||||
.map_err(Into::into)
|
||||
self.sstore.get_vault_meta(name).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Set vault metadata string.
|
||||
pub fn set_vault_meta(&self, name: &str, meta: &str) -> Result<(), Error> {
|
||||
self.sstore.set_vault_meta(name, meta)
|
||||
.map_err(Into::into)
|
||||
self.sstore.set_vault_meta(name, meta).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Sign message with hardware wallet.
|
||||
pub fn sign_message_with_hardware(&self, address: &Address, message: &[u8]) -> Result<Signature, SignError> {
|
||||
match self.hardware_store.as_ref().map(|s| s.sign_message(address, message)) {
|
||||
pub fn sign_message_with_hardware(
|
||||
&self,
|
||||
address: &Address,
|
||||
message: &[u8],
|
||||
) -> Result<Signature, SignError> {
|
||||
match self
|
||||
.hardware_store
|
||||
.as_ref()
|
||||
.map(|s| s.sign_message(address, message))
|
||||
{
|
||||
None | Some(Err(HardwareError::KeyNotFound)) => Err(SignError::NotFound),
|
||||
Some(Err(e)) => Err(From::from(e)),
|
||||
Some(Ok(s)) => Ok(s),
|
||||
@ -586,7 +771,13 @@ impl AccountProvider {
|
||||
}
|
||||
|
||||
/// Sign transaction with hardware wallet.
|
||||
pub fn sign_transaction_with_hardware(&self, address: &Address, transaction: &Transaction, chain_id: Option<u64>, rlp_encoded_transaction: &[u8]) -> Result<Signature, SignError> {
|
||||
pub fn sign_transaction_with_hardware(
|
||||
&self,
|
||||
address: &Address,
|
||||
transaction: &Transaction,
|
||||
chain_id: Option<u64>,
|
||||
rlp_encoded_transaction: &[u8],
|
||||
) -> Result<Signature, SignError> {
|
||||
let t_info = TransactionInfo {
|
||||
nonce: transaction.nonce,
|
||||
gas_price: transaction.gas_price,
|
||||
@ -599,7 +790,11 @@ impl AccountProvider {
|
||||
data: transaction.data.to_vec(),
|
||||
chain_id: chain_id,
|
||||
};
|
||||
match self.hardware_store.as_ref().map(|s| s.sign_transaction(&address, &t_info, rlp_encoded_transaction)) {
|
||||
match self
|
||||
.hardware_store
|
||||
.as_ref()
|
||||
.map(|s| s.sign_transaction(&address, &t_info, rlp_encoded_transaction))
|
||||
{
|
||||
None | Some(Err(HardwareError::KeyNotFound)) => Err(SignError::NotFound),
|
||||
Some(Err(e)) => Err(From::from(e)),
|
||||
Some(Ok(s)) => Ok(s),
|
||||
@ -610,18 +805,24 @@ impl AccountProvider {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{AccountProvider, Unlock};
|
||||
use std::time::{Duration, Instant};
|
||||
use ethkey::{Generator, Random, Address};
|
||||
use ethstore::{StoreAccountRef, Derivation};
|
||||
use ethereum_types::H256;
|
||||
use ethkey::{Address, Generator, Random};
|
||||
use ethstore::{Derivation, StoreAccountRef};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
#[test]
|
||||
fn unlock_account_temp() {
|
||||
let kp = Random.generate().unwrap();
|
||||
let ap = AccountProvider::transient_provider();
|
||||
assert!(ap.insert_account(kp.secret().clone(), &"test".into()).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
|
||||
.insert_account(kp.secret().clone(), &"test".into())
|
||||
.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(), None, Default::default()).is_ok());
|
||||
assert!(ap.sign(kp.address(), None, Default::default()).is_err());
|
||||
}
|
||||
@ -630,81 +831,118 @@ mod tests {
|
||||
fn derived_account_nosave() {
|
||||
let kp = Random.generate().unwrap();
|
||||
let ap = AccountProvider::transient_provider();
|
||||
assert!(ap.insert_account(kp.secret().clone(), &"base".into()).is_ok());
|
||||
assert!(ap.unlock_account_permanently(kp.address(), "base".into()).is_ok());
|
||||
assert!(ap
|
||||
.insert_account(kp.secret().clone(), &"base".into())
|
||||
.is_ok());
|
||||
assert!(ap
|
||||
.unlock_account_permanently(kp.address(), "base".into())
|
||||
.is_ok());
|
||||
|
||||
let derived_addr = ap.derive_account(
|
||||
let derived_addr = ap
|
||||
.derive_account(
|
||||
&kp.address(),
|
||||
None,
|
||||
Derivation::SoftHash(H256::from(999)),
|
||||
false,
|
||||
).expect("Derivation should not fail");
|
||||
)
|
||||
.expect("Derivation should not fail");
|
||||
|
||||
assert!(ap.unlock_account_permanently(derived_addr, "base".into()).is_err(),
|
||||
"There should be an error because account is not supposed to be saved");
|
||||
assert!(
|
||||
ap.unlock_account_permanently(derived_addr, "base".into())
|
||||
.is_err(),
|
||||
"There should be an error because account is not supposed to be saved"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derived_account_save() {
|
||||
let kp = Random.generate().unwrap();
|
||||
let ap = AccountProvider::transient_provider();
|
||||
assert!(ap.insert_account(kp.secret().clone(), &"base".into()).is_ok());
|
||||
assert!(ap.unlock_account_permanently(kp.address(), "base".into()).is_ok());
|
||||
assert!(ap
|
||||
.insert_account(kp.secret().clone(), &"base".into())
|
||||
.is_ok());
|
||||
assert!(ap
|
||||
.unlock_account_permanently(kp.address(), "base".into())
|
||||
.is_ok());
|
||||
|
||||
let derived_addr = ap.derive_account(
|
||||
let derived_addr = ap
|
||||
.derive_account(
|
||||
&kp.address(),
|
||||
None,
|
||||
Derivation::SoftHash(H256::from(999)),
|
||||
true,
|
||||
).expect("Derivation should not fail");
|
||||
)
|
||||
.expect("Derivation should not fail");
|
||||
|
||||
assert!(ap.unlock_account_permanently(derived_addr, "base_wrong".into()).is_err(),
|
||||
"There should be an error because password is invalid");
|
||||
assert!(
|
||||
ap.unlock_account_permanently(derived_addr, "base_wrong".into())
|
||||
.is_err(),
|
||||
"There should be an error because password is invalid"
|
||||
);
|
||||
|
||||
assert!(ap.unlock_account_permanently(derived_addr, "base".into()).is_ok(),
|
||||
"Should be ok because account is saved and password is valid");
|
||||
assert!(
|
||||
ap.unlock_account_permanently(derived_addr, "base".into())
|
||||
.is_ok(),
|
||||
"Should be ok because account is saved and password is valid"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derived_account_sign() {
|
||||
let kp = Random.generate().unwrap();
|
||||
let ap = AccountProvider::transient_provider();
|
||||
assert!(ap.insert_account(kp.secret().clone(), &"base".into()).is_ok());
|
||||
assert!(ap.unlock_account_permanently(kp.address(), "base".into()).is_ok());
|
||||
assert!(ap
|
||||
.insert_account(kp.secret().clone(), &"base".into())
|
||||
.is_ok());
|
||||
assert!(ap
|
||||
.unlock_account_permanently(kp.address(), "base".into())
|
||||
.is_ok());
|
||||
|
||||
let derived_addr = ap.derive_account(
|
||||
let derived_addr = ap
|
||||
.derive_account(
|
||||
&kp.address(),
|
||||
None,
|
||||
Derivation::SoftHash(H256::from(1999)),
|
||||
true,
|
||||
).expect("Derivation should not fail");
|
||||
)
|
||||
.expect("Derivation should not fail");
|
||||
ap.unlock_account_permanently(derived_addr, "base".into())
|
||||
.expect("Should be ok because account is saved and password is valid");
|
||||
|
||||
let msg = Default::default();
|
||||
let signed_msg1 = ap.sign(derived_addr, None, msg)
|
||||
let signed_msg1 = ap
|
||||
.sign(derived_addr, None, msg)
|
||||
.expect("Signing with existing unlocked account should not fail");
|
||||
let signed_msg2 = ap.sign_derived(
|
||||
let signed_msg2 = ap
|
||||
.sign_derived(
|
||||
&kp.address(),
|
||||
None,
|
||||
Derivation::SoftHash(H256::from(1999)),
|
||||
msg,
|
||||
).expect("Derived signing with existing unlocked account should not fail");
|
||||
)
|
||||
.expect("Derived signing with existing unlocked account should not fail");
|
||||
|
||||
assert_eq!(signed_msg1, signed_msg2,
|
||||
"Signed messages should match");
|
||||
assert_eq!(signed_msg1, signed_msg2, "Signed messages should match");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unlock_account_perm() {
|
||||
let kp = Random.generate().unwrap();
|
||||
let ap = AccountProvider::transient_provider();
|
||||
assert!(ap.insert_account(kp.secret().clone(), &"test".into()).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
|
||||
.insert_account(kp.secret().clone(), &"test".into())
|
||||
.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(), None, Default::default()).is_ok());
|
||||
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
|
||||
assert!(ap.unlock_account_temporarily(kp.address(), "test".into()).is_ok());
|
||||
assert!(ap
|
||||
.unlock_account_temporarily(kp.address(), "test".into())
|
||||
.is_ok());
|
||||
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
|
||||
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
|
||||
}
|
||||
@ -713,11 +951,21 @@ mod tests {
|
||||
fn unlock_account_timer() {
|
||||
let kp = Random.generate().unwrap();
|
||||
let ap = AccountProvider::transient_provider();
|
||||
assert!(ap.insert_account(kp.secret().clone(), &"test".into()).is_ok());
|
||||
assert!(ap.unlock_account_timed(kp.address(), "test1".into(), Duration::from_secs(60)).is_err());
|
||||
assert!(ap.unlock_account_timed(kp.address(), "test".into(), Duration::from_secs(60)).is_ok());
|
||||
assert!(ap
|
||||
.insert_account(kp.secret().clone(), &"test".into())
|
||||
.is_ok());
|
||||
assert!(ap
|
||||
.unlock_account_timed(kp.address(), "test1".into(), Duration::from_secs(60))
|
||||
.is_err());
|
||||
assert!(ap
|
||||
.unlock_account_timed(kp.address(), "test".into(), Duration::from_secs(60))
|
||||
.is_ok());
|
||||
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
|
||||
ap.unlocked.write().get_mut(&StoreAccountRef::root(kp.address())).unwrap().unlock = Unlock::Timed(Instant::now());
|
||||
ap.unlocked
|
||||
.write()
|
||||
.get_mut(&StoreAccountRef::root(kp.address()))
|
||||
.unwrap()
|
||||
.unlock = Unlock::Timed(Instant::now());
|
||||
assert!(ap.sign(kp.address(), None, Default::default()).is_err());
|
||||
}
|
||||
|
||||
@ -726,15 +974,23 @@ mod tests {
|
||||
// given
|
||||
let kp = Random.generate().unwrap();
|
||||
let ap = AccountProvider::transient_provider();
|
||||
assert!(ap.insert_account(kp.secret().clone(), &"test".into()).is_ok());
|
||||
assert!(ap
|
||||
.insert_account(kp.secret().clone(), &"test".into())
|
||||
.is_ok());
|
||||
|
||||
// when
|
||||
let (_signature, token) = ap.sign_with_token(kp.address(), "test".into(), Default::default()).unwrap();
|
||||
let (_signature, token) = ap
|
||||
.sign_with_token(kp.address(), "test".into(), Default::default())
|
||||
.unwrap();
|
||||
|
||||
// then
|
||||
ap.sign_with_token(kp.address(), token.clone(), Default::default())
|
||||
.expect("First usage of token should be correct.");
|
||||
assert!(ap.sign_with_token(kp.address(), token, Default::default()).is_err(), "Second usage of the same token should fail.");
|
||||
assert!(
|
||||
ap.sign_with_token(kp.address(), token, Default::default())
|
||||
.is_err(),
|
||||
"Second usage of the same token should fail."
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -745,7 +1001,14 @@ mod tests {
|
||||
ap.blacklisted_accounts = vec![acc];
|
||||
|
||||
// then
|
||||
assert_eq!(ap.accounts_info().unwrap().keys().cloned().collect::<Vec<Address>>(), vec![]);
|
||||
assert_eq!(
|
||||
ap.accounts_info()
|
||||
.unwrap()
|
||||
.keys()
|
||||
.cloned()
|
||||
.collect::<Vec<Address>>(),
|
||||
vec![]
|
||||
);
|
||||
assert_eq!(ap.accounts().unwrap(), vec![]);
|
||||
}
|
||||
}
|
||||
|
@ -16,9 +16,11 @@
|
||||
|
||||
//! Address Book Store
|
||||
|
||||
use std::{fs, fmt, hash, ops};
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt, fs, hash, ops,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use ethkey::Address;
|
||||
use log::{trace, warn};
|
||||
@ -34,7 +36,7 @@ impl AddressBook {
|
||||
/// Creates new address book at given directory.
|
||||
pub fn new(path: &Path) -> Self {
|
||||
let mut r = AddressBook {
|
||||
cache: DiskMap::new(path, "address_book.json")
|
||||
cache: DiskMap::new(path, "address_book.json"),
|
||||
};
|
||||
r.cache.revert(AccountMeta::read);
|
||||
r
|
||||
@ -43,7 +45,7 @@ impl AddressBook {
|
||||
/// Creates transient address book (no changes are saved to disk).
|
||||
pub fn transient() -> Self {
|
||||
AddressBook {
|
||||
cache: DiskMap::transient()
|
||||
cache: DiskMap::transient(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,8 +61,11 @@ impl AddressBook {
|
||||
/// Sets new name for given address.
|
||||
pub fn set_name(&mut self, a: Address, name: String) {
|
||||
{
|
||||
let x = self.cache.entry(a)
|
||||
.or_insert_with(|| AccountMeta {name: Default::default(), meta: "{}".to_owned(), uuid: None});
|
||||
let x = self.cache.entry(a).or_insert_with(|| AccountMeta {
|
||||
name: Default::default(),
|
||||
meta: "{}".to_owned(),
|
||||
uuid: None,
|
||||
});
|
||||
x.name = name;
|
||||
}
|
||||
self.save();
|
||||
@ -69,8 +74,11 @@ impl AddressBook {
|
||||
/// Sets new meta for given address.
|
||||
pub fn set_meta(&mut self, a: Address, meta: String) {
|
||||
{
|
||||
let x = self.cache.entry(a)
|
||||
.or_insert_with(|| AccountMeta {name: "Anonymous".to_owned(), meta: Default::default(), uuid: None});
|
||||
let x = self.cache.entry(a).or_insert_with(|| AccountMeta {
|
||||
name: "Anonymous".to_owned(),
|
||||
meta: Default::default(),
|
||||
uuid: None,
|
||||
});
|
||||
x.meta = meta;
|
||||
}
|
||||
self.save();
|
||||
@ -122,31 +130,40 @@ impl<K: hash::Hash + Eq, V> DiskMap<K, V> {
|
||||
map
|
||||
}
|
||||
|
||||
fn revert<F, E>(&mut self, read: F) where
|
||||
fn revert<F, E>(&mut self, read: F)
|
||||
where
|
||||
F: Fn(fs::File) -> Result<HashMap<K, V>, E>,
|
||||
E: fmt::Display,
|
||||
{
|
||||
if self.transient { return; }
|
||||
if self.transient {
|
||||
return;
|
||||
}
|
||||
trace!(target: "diskmap", "revert {:?}", self.path);
|
||||
let _ = fs::File::open(self.path.clone())
|
||||
.map_err(|e| trace!(target: "diskmap", "Couldn't open disk map: {}", e))
|
||||
.and_then(|f| read(f).map_err(|e| warn!(target: "diskmap", "Couldn't read disk map: {}", e)))
|
||||
.and_then(|f| {
|
||||
read(f).map_err(|e| warn!(target: "diskmap", "Couldn't read disk map: {}", e))
|
||||
})
|
||||
.and_then(|m| {
|
||||
self.cache = m;
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
fn save<F, E>(&self, write: F) where
|
||||
fn save<F, E>(&self, write: F)
|
||||
where
|
||||
F: Fn(&HashMap<K, V>, &mut fs::File) -> Result<(), E>,
|
||||
E: fmt::Display,
|
||||
{
|
||||
if self.transient { return; }
|
||||
if self.transient {
|
||||
return;
|
||||
}
|
||||
trace!(target: "diskmap", "save {:?}", self.path);
|
||||
let _ = fs::File::create(self.path.clone())
|
||||
.map_err(|e| warn!(target: "diskmap", "Couldn't open disk map for writing: {}", e))
|
||||
.and_then(|mut f| {
|
||||
write(&self.cache, &mut f).map_err(|e| warn!(target: "diskmap", "Couldn't write to disk map: {}", e))
|
||||
write(&self.cache, &mut f)
|
||||
.map_err(|e| warn!(target: "diskmap", "Couldn't write to disk map: {}", e))
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -154,9 +171,9 @@ impl<K: hash::Hash + Eq, V> DiskMap<K, V> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::AddressBook;
|
||||
use crate::account_data::AccountMeta;
|
||||
use std::collections::HashMap;
|
||||
use tempdir::TempDir;
|
||||
use crate::account_data::AccountMeta;
|
||||
|
||||
#[test]
|
||||
fn should_save_and_reload_address_book() {
|
||||
@ -165,9 +182,20 @@ mod tests {
|
||||
b.set_name(1.into(), "One".to_owned());
|
||||
b.set_meta(1.into(), "{1:1}".to_owned());
|
||||
let b = AddressBook::new(tempdir.path());
|
||||
assert_eq!(b.get(), vec![
|
||||
(1, AccountMeta {name: "One".to_owned(), meta: "{1:1}".to_owned(), uuid: None})
|
||||
].into_iter().map(|(a, b)| (a.into(), b)).collect::<HashMap<_, _>>());
|
||||
assert_eq!(
|
||||
b.get(),
|
||||
vec![(
|
||||
1,
|
||||
AccountMeta {
|
||||
name: "One".to_owned(),
|
||||
meta: "{1:1}".to_owned(),
|
||||
uuid: None
|
||||
}
|
||||
)]
|
||||
.into_iter()
|
||||
.map(|(a, b)| (a.into(), b))
|
||||
.collect::<HashMap<_, _>>()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -181,9 +209,29 @@ mod tests {
|
||||
b.remove(2.into());
|
||||
|
||||
let b = AddressBook::new(tempdir.path());
|
||||
assert_eq!(b.get(), vec![
|
||||
(1, AccountMeta{name: "One".to_owned(), meta: "{}".to_owned(), uuid: None}),
|
||||
(3, AccountMeta{name: "Three".to_owned(), meta: "{}".to_owned(), uuid: None}),
|
||||
].into_iter().map(|(a, b)| (a.into(), b)).collect::<HashMap<_, _>>());
|
||||
assert_eq!(
|
||||
b.get(),
|
||||
vec![
|
||||
(
|
||||
1,
|
||||
AccountMeta {
|
||||
name: "One".to_owned(),
|
||||
meta: "{}".to_owned(),
|
||||
uuid: None
|
||||
}
|
||||
),
|
||||
(
|
||||
3,
|
||||
AccountMeta {
|
||||
name: "Three".to_owned(),
|
||||
meta: "{}".to_owned(),
|
||||
uuid: None
|
||||
}
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|(a, b)| (a.into(), b))
|
||||
.collect::<HashMap<_, _>>()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -14,11 +14,11 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
extern crate serde_json;
|
||||
extern crate ethjson;
|
||||
extern crate serde_json;
|
||||
|
||||
use std::{fs, env, process};
|
||||
use ethjson::spec::Spec;
|
||||
use std::{env, fs, process};
|
||||
|
||||
fn quit(s: &str) -> ! {
|
||||
println!("{}", s);
|
||||
@ -28,9 +28,11 @@ fn quit(s: &str) -> ! {
|
||||
fn main() {
|
||||
let mut args = env::args();
|
||||
if args.len() != 2 {
|
||||
quit("You need to specify chainspec.json\n\
|
||||
quit(
|
||||
"You need to specify chainspec.json\n\
|
||||
\n\
|
||||
./chainspec <chainspec.json>");
|
||||
./chainspec <chainspec.json>",
|
||||
);
|
||||
}
|
||||
|
||||
let path = args.nth(1).expect("args.len() == 2; qed");
|
||||
|
@ -14,45 +14,38 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::fmt::{Debug, Formatter, Error as FmtError};
|
||||
use std::io::{BufReader, BufRead};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::collections::BTreeMap;
|
||||
use std::thread;
|
||||
use std::time;
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
fmt::{Debug, Error as FmtError, Formatter},
|
||||
io::{BufRead, BufReader},
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc,
|
||||
},
|
||||
thread, time,
|
||||
};
|
||||
|
||||
use std::path::PathBuf;
|
||||
use hash::keccak;
|
||||
use parking_lot::Mutex;
|
||||
use std::{fs::File, path::PathBuf};
|
||||
use url::Url;
|
||||
use std::fs::File;
|
||||
|
||||
use ws::ws::{
|
||||
self,
|
||||
Request,
|
||||
Handler,
|
||||
Sender,
|
||||
Handshake,
|
||||
Error as WsError,
|
||||
ErrorKind as WsErrorKind,
|
||||
Message,
|
||||
Result as WsResult,
|
||||
self, Error as WsError, ErrorKind as WsErrorKind, Handler, Handshake, Message, Request,
|
||||
Result as WsResult, Sender,
|
||||
};
|
||||
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json::{
|
||||
self as json,
|
||||
Value as JsonValue,
|
||||
Error as JsonError,
|
||||
use serde_json::{self as json, Error as JsonError, Value as JsonValue};
|
||||
|
||||
use futures::{done, oneshot, Canceled, Complete, Future};
|
||||
|
||||
use jsonrpc_core::{
|
||||
request::MethodCall,
|
||||
response::{Failure, Output, Success},
|
||||
Error as JsonRpcError, Id, Params, Version,
|
||||
};
|
||||
|
||||
use futures::{Canceled, Complete, Future, oneshot, done};
|
||||
|
||||
use jsonrpc_core::{Id, Version, Params, Error as JsonRpcError};
|
||||
use jsonrpc_core::request::MethodCall;
|
||||
use jsonrpc_core::response::{Output, Success, Failure};
|
||||
|
||||
use BoxFuture;
|
||||
|
||||
/// The actual websocket connection handler, passed into the
|
||||
@ -67,11 +60,7 @@ struct RpcHandler {
|
||||
}
|
||||
|
||||
impl RpcHandler {
|
||||
fn new(
|
||||
out: Sender,
|
||||
auth_code: String,
|
||||
complete: Complete<Result<Rpc, RpcError>>
|
||||
) -> Self {
|
||||
fn new(out: Sender, auth_code: String, complete: Complete<Result<Rpc, RpcError>>) -> Self {
|
||||
RpcHandler {
|
||||
out: Some(out),
|
||||
auth_code: auth_code,
|
||||
@ -85,23 +74,22 @@ impl Handler for RpcHandler {
|
||||
fn build_request(&mut self, url: &Url) -> WsResult<Request> {
|
||||
match Request::from_url(url) {
|
||||
Ok(mut r) => {
|
||||
let timestamp = time::UNIX_EPOCH.elapsed().map_err(|err| {
|
||||
WsError::new(WsErrorKind::Internal, format!("{}", err))
|
||||
})?;
|
||||
let timestamp = time::UNIX_EPOCH
|
||||
.elapsed()
|
||||
.map_err(|err| WsError::new(WsErrorKind::Internal, format!("{}", err)))?;
|
||||
let secs = timestamp.as_secs();
|
||||
let hashed = keccak(format!("{}:{}", self.auth_code, secs));
|
||||
let proto = format!("{:x}_{}", hashed, secs);
|
||||
r.add_protocol(&proto);
|
||||
Ok(r)
|
||||
},
|
||||
Err(e) =>
|
||||
Err(WsError::new(WsErrorKind::Internal, format!("{}", e))),
|
||||
}
|
||||
Err(e) => Err(WsError::new(WsErrorKind::Internal, format!("{}", e))),
|
||||
}
|
||||
}
|
||||
fn on_error(&mut self, err: WsError) {
|
||||
match self.complete.take() {
|
||||
Some(c) => match c.send(Err(RpcError::WsError(err))) {
|
||||
Ok(_) => {},
|
||||
Ok(_) => {}
|
||||
Err(_) => warn!(target: "rpc-client", "Unable to notify about error."),
|
||||
},
|
||||
None => warn!(target: "rpc-client", "unexpected error: {}", err),
|
||||
@ -119,7 +107,7 @@ impl Handler for RpcHandler {
|
||||
warn!(target: "rpc-client", "Unable to open a connection.")
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
_ => {
|
||||
let msg = format!("on_open called twice");
|
||||
Err(WsError::new(WsErrorKind::Internal, msg))
|
||||
@ -131,12 +119,19 @@ impl Handler for RpcHandler {
|
||||
let response_id;
|
||||
let string = &msg.to_string();
|
||||
match json::from_str::<Output>(&string) {
|
||||
Ok(Output::Success(Success { result, id: Id::Num(id), .. })) =>
|
||||
{
|
||||
Ok(Output::Success(Success {
|
||||
result,
|
||||
id: Id::Num(id),
|
||||
..
|
||||
})) => {
|
||||
ret = Ok(result);
|
||||
response_id = id as usize;
|
||||
}
|
||||
Ok(Output::Failure(Failure { error, id: Id::Num(id), .. })) => {
|
||||
Ok(Output::Failure(Failure {
|
||||
error,
|
||||
id: Id::Num(id),
|
||||
..
|
||||
})) => {
|
||||
ret = Err(error);
|
||||
response_id = id as usize;
|
||||
}
|
||||
@ -147,22 +142,24 @@ impl Handler for RpcHandler {
|
||||
string,
|
||||
e
|
||||
);
|
||||
return Ok(())
|
||||
},
|
||||
return Ok(());
|
||||
}
|
||||
_ => {
|
||||
warn!(
|
||||
target: "rpc-client",
|
||||
"recieved invalid message: {}",
|
||||
string
|
||||
);
|
||||
return Ok(())
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
match self.pending.remove(response_id) {
|
||||
Some(c) => if let Err(_) = c.send(ret.map_err(|err| RpcError::JsonRpc(err))) {
|
||||
Some(c) => {
|
||||
if let Err(_) = c.send(ret.map_err(|err| RpcError::JsonRpc(err))) {
|
||||
warn!(target: "rpc-client", "Unable to send response.")
|
||||
},
|
||||
}
|
||||
}
|
||||
None => warn!(
|
||||
target: "rpc-client",
|
||||
"warning: unexpected id: {}",
|
||||
@ -175,9 +172,7 @@ impl Handler for RpcHandler {
|
||||
|
||||
/// Keeping track of issued requests to be matched up with responses
|
||||
#[derive(Clone)]
|
||||
struct Pending(
|
||||
Arc<Mutex<BTreeMap<usize, Complete<Result<JsonValue, RpcError>>>>>
|
||||
);
|
||||
struct Pending(Arc<Mutex<BTreeMap<usize, Complete<Result<JsonValue, RpcError>>>>>);
|
||||
|
||||
impl Pending {
|
||||
fn new() -> Self {
|
||||
@ -186,10 +181,7 @@ impl Pending {
|
||||
fn insert(&mut self, k: usize, v: Complete<Result<JsonValue, RpcError>>) {
|
||||
self.0.lock().insert(k, v);
|
||||
}
|
||||
fn remove(
|
||||
&mut self,
|
||||
k: usize
|
||||
) -> Option<Complete<Result<JsonValue, RpcError>>> {
|
||||
fn remove(&mut self, k: usize) -> Option<Complete<Result<JsonValue, RpcError>>> {
|
||||
self.0.lock().remove(&k)
|
||||
}
|
||||
}
|
||||
@ -223,9 +215,7 @@ impl Rpc {
|
||||
}
|
||||
|
||||
/// Non-blocking, returns a future
|
||||
pub fn connect(
|
||||
url: &str, authpath: &PathBuf
|
||||
) -> BoxFuture<Result<Self, RpcError>, Canceled> {
|
||||
pub fn connect(url: &str, authpath: &PathBuf) -> BoxFuture<Result<Self, RpcError>, Canceled> {
|
||||
let (c, p) = oneshot::<Result<Self, RpcError>>();
|
||||
match get_authcode(authpath) {
|
||||
Err(e) => return Box::new(done(Ok(Err(e)))),
|
||||
@ -239,20 +229,18 @@ impl Rpc {
|
||||
let conn = ws::connect(url, |out| {
|
||||
// this will panic if the closure is called twice,
|
||||
// which it should never be.
|
||||
let c = once.take()
|
||||
.expect("connection closure called only once");
|
||||
let c = once.take().expect("connection closure called only once");
|
||||
RpcHandler::new(out, code.clone(), c)
|
||||
});
|
||||
match conn {
|
||||
Err(err) => {
|
||||
// since ws::connect is only called once, it cannot
|
||||
// both fail and succeed.
|
||||
let c = once.take()
|
||||
.expect("connection closure called only once");
|
||||
let c = once.take().expect("connection closure called only once");
|
||||
let _ = c.send(Err(RpcError::WsError(err)));
|
||||
},
|
||||
}
|
||||
// c will complete on the `on_open` event in the Handler
|
||||
_ => ()
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
Box::new(p)
|
||||
@ -262,10 +250,13 @@ impl Rpc {
|
||||
|
||||
/// Non-blocking, returns a future of the request response
|
||||
pub fn request<T>(
|
||||
&mut self, method: &'static str, params: Vec<JsonValue>
|
||||
&mut self,
|
||||
method: &'static str,
|
||||
params: Vec<JsonValue>,
|
||||
) -> BoxFuture<Result<T, RpcError>, Canceled>
|
||||
where T: DeserializeOwned + Send + Sized {
|
||||
|
||||
where
|
||||
T: DeserializeOwned + Send + Sized,
|
||||
{
|
||||
let (c, p) = oneshot::<Result<JsonValue, RpcError>>();
|
||||
|
||||
let id = self.counter.fetch_add(1, Ordering::Relaxed);
|
||||
@ -278,18 +269,15 @@ impl Rpc {
|
||||
id: Id::Num(id as u64),
|
||||
};
|
||||
|
||||
let serialized = json::to_string(&request)
|
||||
.expect("request is serializable");
|
||||
let serialized = json::to_string(&request).expect("request is serializable");
|
||||
let _ = self.out.send(serialized);
|
||||
|
||||
Box::new(p.map(|result| {
|
||||
match result {
|
||||
Box::new(p.map(|result| match result {
|
||||
Ok(json) => {
|
||||
let t: T = json::from_value(json)?;
|
||||
Ok(t)
|
||||
},
|
||||
Err(err) => Err(err)
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
}))
|
||||
}
|
||||
}
|
||||
@ -308,22 +296,14 @@ pub enum RpcError {
|
||||
impl Debug for RpcError {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
|
||||
match *self {
|
||||
RpcError::WrongVersion(ref s)
|
||||
=> write!(f, "Expected version 2.0, got {}", s),
|
||||
RpcError::ParseError(ref err)
|
||||
=> write!(f, "ParseError: {}", err),
|
||||
RpcError::MalformedResponse(ref s)
|
||||
=> write!(f, "Malformed response: {}", s),
|
||||
RpcError::JsonRpc(ref json)
|
||||
=> write!(f, "JsonRpc error: {:?}", json),
|
||||
RpcError::WsError(ref s)
|
||||
=> write!(f, "Websocket error: {}", s),
|
||||
RpcError::Canceled(ref s)
|
||||
=> write!(f, "Futures error: {:?}", s),
|
||||
RpcError::UnexpectedId
|
||||
=> write!(f, "Unexpected response id"),
|
||||
RpcError::NoAuthCode
|
||||
=> write!(f, "No authcodes available"),
|
||||
RpcError::WrongVersion(ref s) => write!(f, "Expected version 2.0, got {}", s),
|
||||
RpcError::ParseError(ref err) => write!(f, "ParseError: {}", err),
|
||||
RpcError::MalformedResponse(ref s) => write!(f, "Malformed response: {}", s),
|
||||
RpcError::JsonRpc(ref json) => write!(f, "JsonRpc error: {:?}", json),
|
||||
RpcError::WsError(ref s) => write!(f, "Websocket error: {}", s),
|
||||
RpcError::Canceled(ref s) => write!(f, "Futures error: {:?}", s),
|
||||
RpcError::UnexpectedId => write!(f, "Unexpected response id"),
|
||||
RpcError::NoAuthCode => write!(f, "No authcodes available"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,12 +21,12 @@ extern crate ethereum_types;
|
||||
extern crate futures;
|
||||
extern crate jsonrpc_core;
|
||||
extern crate jsonrpc_ws_server as ws;
|
||||
extern crate keccak_hash as hash;
|
||||
extern crate parity_rpc as rpc;
|
||||
extern crate parking_lot;
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
extern crate url;
|
||||
extern crate keccak_hash as hash;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
@ -36,15 +36,15 @@ extern crate log;
|
||||
extern crate matches;
|
||||
|
||||
/// Boxed future response.
|
||||
pub type BoxFuture<T, E> = Box<futures::Future<Item=T, Error=E> + Send>;
|
||||
pub type BoxFuture<T, E> = Box<futures::Future<Item = T, Error = E> + Send>;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use futures::Future;
|
||||
use std::path::PathBuf;
|
||||
use client::{Rpc, RpcError};
|
||||
use futures::Future;
|
||||
use rpc;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn test_connection_refused() {
|
||||
@ -53,12 +53,13 @@ mod tests {
|
||||
let _ = authcodes.generate_new();
|
||||
authcodes.to_file(&authcodes.path).unwrap();
|
||||
|
||||
let connect = Rpc::connect(&format!("ws://127.0.0.1:{}", port - 1),
|
||||
&authcodes.path);
|
||||
let connect = Rpc::connect(&format!("ws://127.0.0.1:{}", port - 1), &authcodes.path);
|
||||
|
||||
let _ = connect.map(|conn| {
|
||||
let _ = connect
|
||||
.map(|conn| {
|
||||
assert!(matches!(&conn, &Err(RpcError::WsError(_))));
|
||||
}).wait();
|
||||
})
|
||||
.wait();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -68,9 +69,11 @@ mod tests {
|
||||
|
||||
let connect = Rpc::connect(&format!("ws://127.0.0.1:{}", port), &path);
|
||||
|
||||
let _ = connect.map(|conn| {
|
||||
let _ = connect
|
||||
.map(|conn| {
|
||||
assert!(matches!(&conn, &Err(RpcError::NoAuthCode)));
|
||||
}).wait();
|
||||
})
|
||||
.wait();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -80,12 +83,8 @@ mod tests {
|
||||
let _ = authcodes.generate_new();
|
||||
authcodes.to_file(&authcodes.path).unwrap();
|
||||
|
||||
let connect = Rpc::connect(&format!("ws://127.0.0.1:{}", port),
|
||||
&authcodes.path);
|
||||
let connect = Rpc::connect(&format!("ws://127.0.0.1:{}", port), &authcodes.path);
|
||||
|
||||
let _ = connect.map(|conn| {
|
||||
assert!(conn.is_ok())
|
||||
}).wait();
|
||||
let _ = connect.map(|conn| assert!(conn.is_ok())).wait();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,12 +16,12 @@
|
||||
|
||||
use client::{Rpc, RpcError};
|
||||
use ethereum_types::U256;
|
||||
use rpc::signer::{ConfirmationRequest, TransactionModification, TransactionCondition};
|
||||
use futures::Canceled;
|
||||
use rpc::signer::{ConfirmationRequest, TransactionCondition, TransactionModification};
|
||||
use serde;
|
||||
use serde_json::{Value as JsonValue, to_value};
|
||||
use serde_json::{to_value, Value as JsonValue};
|
||||
use std::path::PathBuf;
|
||||
use futures::{Canceled};
|
||||
use {BoxFuture};
|
||||
use BoxFuture;
|
||||
|
||||
pub struct SignerRpc {
|
||||
rpc: Rpc,
|
||||
@ -29,10 +29,14 @@ pub struct SignerRpc {
|
||||
|
||||
impl SignerRpc {
|
||||
pub fn new(url: &str, authfile: &PathBuf) -> Result<Self, RpcError> {
|
||||
Ok(SignerRpc { rpc: Rpc::new(&url, authfile)? })
|
||||
Ok(SignerRpc {
|
||||
rpc: Rpc::new(&url, authfile)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn requests_to_confirm(&mut self) -> BoxFuture<Result<Vec<ConfirmationRequest>, RpcError>, Canceled> {
|
||||
pub fn requests_to_confirm(
|
||||
&mut self,
|
||||
) -> BoxFuture<Result<Vec<ConfirmationRequest>, RpcError>, Canceled> {
|
||||
self.rpc.request("signer_requestsToConfirm", vec![])
|
||||
}
|
||||
|
||||
@ -42,19 +46,28 @@ impl SignerRpc {
|
||||
new_gas: Option<U256>,
|
||||
new_gas_price: Option<U256>,
|
||||
new_condition: Option<Option<TransactionCondition>>,
|
||||
pwd: &str
|
||||
pwd: &str,
|
||||
) -> BoxFuture<Result<U256, RpcError>, Canceled> {
|
||||
self.rpc.request("signer_confirmRequest", vec![
|
||||
self.rpc.request(
|
||||
"signer_confirmRequest",
|
||||
vec![
|
||||
Self::to_value(&format!("{:#x}", id)),
|
||||
Self::to_value(&TransactionModification { sender: None, gas_price: new_gas_price, gas: new_gas, condition: new_condition }),
|
||||
Self::to_value(&TransactionModification {
|
||||
sender: None,
|
||||
gas_price: new_gas_price,
|
||||
gas: new_gas,
|
||||
condition: new_condition,
|
||||
}),
|
||||
Self::to_value(&pwd),
|
||||
])
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn reject_request(&mut self, id: U256) -> BoxFuture<Result<bool, RpcError>, Canceled> {
|
||||
self.rpc.request("signer_rejectRequest", vec![
|
||||
JsonValue::String(format!("{:#x}", id))
|
||||
])
|
||||
self.rpc.request(
|
||||
"signer_rejectRequest",
|
||||
vec![JsonValue::String(format!("{:#x}", id))],
|
||||
)
|
||||
}
|
||||
|
||||
fn to_value<T: serde::Serialize>(v: &T) -> JsonValue {
|
||||
|
@ -21,135 +21,111 @@ extern crate rpassword;
|
||||
extern crate parity_rpc as rpc;
|
||||
extern crate parity_rpc_client as client;
|
||||
|
||||
use client::signer_client::SignerRpc;
|
||||
use ethereum_types::U256;
|
||||
use rpc::signer::ConfirmationRequest;
|
||||
use client::signer_client::SignerRpc;
|
||||
use std::io::{Write, BufRead, BufReader, stdout, stdin};
|
||||
use std::path::PathBuf;
|
||||
use std::fs::File;
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{stdin, stdout, BufRead, BufReader, Write},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use futures::Future;
|
||||
|
||||
fn sign_interactive(
|
||||
signer: &mut SignerRpc,
|
||||
password: &str,
|
||||
request: ConfirmationRequest
|
||||
) {
|
||||
print!("\n{}\nSign this transaction? (y)es/(N)o/(r)eject: ", request);
|
||||
fn sign_interactive(signer: &mut SignerRpc, password: &str, request: ConfirmationRequest) {
|
||||
print!(
|
||||
"\n{}\nSign this transaction? (y)es/(N)o/(r)eject: ",
|
||||
request
|
||||
);
|
||||
let _ = stdout().flush();
|
||||
match BufReader::new(stdin()).lines().next() {
|
||||
Some(Ok(line)) => {
|
||||
match line.to_lowercase().chars().nth(0) {
|
||||
Some('y') => {
|
||||
match sign_transaction(signer, request.id, password) {
|
||||
Some(Ok(line)) => match line.to_lowercase().chars().nth(0) {
|
||||
Some('y') => match sign_transaction(signer, request.id, password) {
|
||||
Ok(s) | Err(s) => println!("{}", s),
|
||||
}
|
||||
}
|
||||
Some('r') => {
|
||||
match reject_transaction(signer, request.id) {
|
||||
},
|
||||
Some('r') => match reject_transaction(signer, request.id) {
|
||||
Ok(s) | Err(s) => println!("{}", s),
|
||||
}
|
||||
}
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
_ => println!("Could not read from stdin")
|
||||
},
|
||||
_ => (),
|
||||
},
|
||||
_ => println!("Could not read from stdin"),
|
||||
}
|
||||
}
|
||||
|
||||
fn sign_transactions(
|
||||
signer: &mut SignerRpc,
|
||||
password: String
|
||||
) -> Result<String, String> {
|
||||
signer.requests_to_confirm().map(|reqs| {
|
||||
match reqs {
|
||||
Ok(ref reqs) if reqs.is_empty() => {
|
||||
Ok("No transactions in signing queue".to_owned())
|
||||
}
|
||||
fn sign_transactions(signer: &mut SignerRpc, password: String) -> Result<String, String> {
|
||||
signer
|
||||
.requests_to_confirm()
|
||||
.map(|reqs| match reqs {
|
||||
Ok(ref reqs) if reqs.is_empty() => Ok("No transactions in signing queue".to_owned()),
|
||||
Ok(reqs) => {
|
||||
for r in reqs {
|
||||
sign_interactive(signer, &password, r)
|
||||
}
|
||||
Ok("".to_owned())
|
||||
}
|
||||
Err(err) => {
|
||||
Err(format!("error: {:?}", err))
|
||||
}
|
||||
}
|
||||
}).map_err(|err| {
|
||||
format!("{:?}", err)
|
||||
}).wait()?
|
||||
Err(err) => Err(format!("error: {:?}", err)),
|
||||
})
|
||||
.map_err(|err| format!("{:?}", err))
|
||||
.wait()?
|
||||
}
|
||||
|
||||
fn list_transactions(signer: &mut SignerRpc) -> Result<String, String> {
|
||||
signer.requests_to_confirm().map(|reqs| {
|
||||
match reqs {
|
||||
Ok(ref reqs) if reqs.is_empty() => {
|
||||
Ok("No transactions in signing queue".to_owned())
|
||||
}
|
||||
Ok(ref reqs) => {
|
||||
Ok(format!("Transaction queue:\n{}", reqs
|
||||
.iter()
|
||||
signer
|
||||
.requests_to_confirm()
|
||||
.map(|reqs| match reqs {
|
||||
Ok(ref reqs) if reqs.is_empty() => Ok("No transactions in signing queue".to_owned()),
|
||||
Ok(ref reqs) => Ok(format!(
|
||||
"Transaction queue:\n{}",
|
||||
reqs.iter()
|
||||
.map(|r| format!("{}", r))
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n")))
|
||||
}
|
||||
Err(err) => {
|
||||
Err(format!("error: {:?}", err))
|
||||
}
|
||||
}
|
||||
}).map_err(|err| {
|
||||
format!("{:?}", err)
|
||||
}).wait()?
|
||||
.join("\n")
|
||||
)),
|
||||
Err(err) => Err(format!("error: {:?}", err)),
|
||||
})
|
||||
.map_err(|err| format!("{:?}", err))
|
||||
.wait()?
|
||||
}
|
||||
|
||||
fn sign_transaction(
|
||||
signer: &mut SignerRpc, id: U256, password: &str
|
||||
) -> Result<String, String> {
|
||||
signer.confirm_request(id, None, None, None, password).map(|res| {
|
||||
match res {
|
||||
fn sign_transaction(signer: &mut SignerRpc, id: U256, password: &str) -> Result<String, String> {
|
||||
signer
|
||||
.confirm_request(id, None, None, None, password)
|
||||
.map(|res| match res {
|
||||
Ok(u) => Ok(format!("Signed transaction id: {:#x}", u)),
|
||||
Err(e) => Err(format!("{:?}", e)),
|
||||
}
|
||||
}).map_err(|err| {
|
||||
format!("{:?}", err)
|
||||
}).wait()?
|
||||
})
|
||||
.map_err(|err| format!("{:?}", err))
|
||||
.wait()?
|
||||
}
|
||||
|
||||
fn reject_transaction(
|
||||
signer: &mut SignerRpc, id: U256) -> Result<String, String>
|
||||
{
|
||||
signer.reject_request(id).map(|res| {
|
||||
match res {
|
||||
fn reject_transaction(signer: &mut SignerRpc, id: U256) -> Result<String, String> {
|
||||
signer
|
||||
.reject_request(id)
|
||||
.map(|res| match res {
|
||||
Ok(true) => Ok(format!("Rejected transaction id {:#x}", id)),
|
||||
Ok(false) => Err(format!("No such request")),
|
||||
Err(e) => Err(format!("{:?}", e)),
|
||||
}
|
||||
}).map_err(|err| {
|
||||
format!("{:?}", err)
|
||||
}).wait()?
|
||||
})
|
||||
.map_err(|err| format!("{:?}", err))
|
||||
.wait()?
|
||||
}
|
||||
|
||||
// cmds
|
||||
|
||||
pub fn signer_list(
|
||||
signerport: u16, authfile: PathBuf
|
||||
) -> Result<String, String> {
|
||||
pub fn signer_list(signerport: u16, authfile: PathBuf) -> Result<String, String> {
|
||||
let addr = &format!("ws://127.0.0.1:{}", signerport);
|
||||
let mut signer = SignerRpc::new(addr, &authfile).map_err(|err| {
|
||||
format!("{:?}", err)
|
||||
})?;
|
||||
let mut signer = SignerRpc::new(addr, &authfile).map_err(|err| format!("{:?}", err))?;
|
||||
list_transactions(&mut signer)
|
||||
}
|
||||
|
||||
pub fn signer_reject(
|
||||
id: Option<usize>, signerport: u16, authfile: PathBuf
|
||||
id: Option<usize>,
|
||||
signerport: u16,
|
||||
authfile: PathBuf,
|
||||
) -> Result<String, String> {
|
||||
let id = id.ok_or(format!("id required for signer reject"))?;
|
||||
let addr = &format!("ws://127.0.0.1:{}", signerport);
|
||||
let mut signer = SignerRpc::new(addr, &authfile).map_err(|err| {
|
||||
format!("{:?}", err)
|
||||
})?;
|
||||
let mut signer = SignerRpc::new(addr, &authfile).map_err(|err| format!("{:?}", err))?;
|
||||
reject_transaction(&mut signer, U256::from(id))
|
||||
}
|
||||
|
||||
@ -157,22 +133,17 @@ pub fn signer_sign(
|
||||
id: Option<usize>,
|
||||
pwfile: Option<PathBuf>,
|
||||
signerport: u16,
|
||||
authfile: PathBuf
|
||||
authfile: PathBuf,
|
||||
) -> Result<String, String> {
|
||||
let password;
|
||||
match pwfile {
|
||||
Some(pwfile) => {
|
||||
match File::open(pwfile) {
|
||||
Ok(fd) => {
|
||||
match BufReader::new(fd).lines().next() {
|
||||
Some(pwfile) => match File::open(pwfile) {
|
||||
Ok(fd) => match BufReader::new(fd).lines().next() {
|
||||
Some(Ok(line)) => password = line,
|
||||
_ => return Err(format!("No password in file"))
|
||||
}
|
||||
_ => return Err(format!("No password in file")),
|
||||
},
|
||||
Err(e) => return Err(format!("Could not open password file: {}", e)),
|
||||
},
|
||||
Err(e) =>
|
||||
return Err(format!("Could not open password file: {}", e))
|
||||
}
|
||||
}
|
||||
None => {
|
||||
password = match rpassword::prompt_password_stdout("Password: ") {
|
||||
Ok(p) => p,
|
||||
@ -182,16 +153,10 @@ pub fn signer_sign(
|
||||
}
|
||||
|
||||
let addr = &format!("ws://127.0.0.1:{}", signerport);
|
||||
let mut signer = SignerRpc::new(addr, &authfile).map_err(|err| {
|
||||
format!("{:?}", err)
|
||||
})?;
|
||||
let mut signer = SignerRpc::new(addr, &authfile).map_err(|err| format!("{:?}", err))?;
|
||||
|
||||
match id {
|
||||
Some(id) => {
|
||||
sign_transaction(&mut signer, U256::from(id), &password)
|
||||
},
|
||||
None => {
|
||||
sign_transactions(&mut signer, password)
|
||||
}
|
||||
Some(id) => sign_transaction(&mut signer, U256::from(id), &password),
|
||||
None => sign_transactions(&mut signer, password),
|
||||
}
|
||||
}
|
||||
|
@ -21,9 +21,10 @@ extern crate ethash;
|
||||
use criterion::Criterion;
|
||||
use ethash::{NodeCacheBuilder, OptimizeFor};
|
||||
|
||||
const HASH: [u8; 32] = [0xf5, 0x7e, 0x6f, 0x3a, 0xcf, 0xc0, 0xdd, 0x4b, 0x5b, 0xf2, 0xbe,
|
||||
0xe4, 0x0a, 0xb3, 0x35, 0x8a, 0xa6, 0x87, 0x73, 0xa8, 0xd0, 0x9f,
|
||||
0x5e, 0x59, 0x5e, 0xab, 0x55, 0x94, 0x05, 0x52, 0x7d, 0x72];
|
||||
const HASH: [u8; 32] = [
|
||||
0xf5, 0x7e, 0x6f, 0x3a, 0xcf, 0xc0, 0xdd, 0x4b, 0x5b, 0xf2, 0xbe, 0xe4, 0x0a, 0xb3, 0x35, 0x8a,
|
||||
0xa6, 0x87, 0x73, 0xa8, 0xd0, 0x9f, 0x5e, 0x59, 0x5e, 0xab, 0x55, 0x94, 0x05, 0x52, 0x7d, 0x72,
|
||||
];
|
||||
const NONCE: u64 = 0xd7b3ac70a301a249;
|
||||
|
||||
criterion_group!(
|
||||
@ -43,7 +44,9 @@ fn bench_light_compute_memmap(b: &mut Criterion) {
|
||||
let builder = NodeCacheBuilder::new(OptimizeFor::Memory, u64::max_value());
|
||||
let light = builder.light(&env::temp_dir(), 486382);
|
||||
|
||||
b.bench_function("bench_light_compute_memmap", move |b| b.iter(|| light.compute(&HASH, NONCE, u64::max_value())));
|
||||
b.bench_function("bench_light_compute_memmap", move |b| {
|
||||
b.iter(|| light.compute(&HASH, NONCE, u64::max_value()))
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_light_compute_memory(b: &mut Criterion) {
|
||||
@ -52,27 +55,33 @@ fn bench_light_compute_memory(b: &mut Criterion) {
|
||||
let builder = NodeCacheBuilder::new(OptimizeFor::Cpu, u64::max_value());
|
||||
let light = builder.light(&env::temp_dir(), 486382);
|
||||
|
||||
b.bench_function("bench_light_compute_memmap", move |b| b.iter(|| light.compute(&HASH, NONCE, u64::max_value())));
|
||||
b.bench_function("bench_light_compute_memmap", move |b| {
|
||||
b.iter(|| light.compute(&HASH, NONCE, u64::max_value()))
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_light_new_round_trip_memmap(b: &mut Criterion) {
|
||||
use std::env;
|
||||
|
||||
b.bench_function("bench_light_compute_memmap", move |b| b.iter(|| {
|
||||
b.bench_function("bench_light_compute_memmap", move |b| {
|
||||
b.iter(|| {
|
||||
let builder = NodeCacheBuilder::new(OptimizeFor::Memory, u64::max_value());
|
||||
let light = builder.light(&env::temp_dir(), 486382);
|
||||
light.compute(&HASH, NONCE, u64::max_value());
|
||||
}));
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_light_new_round_trip_memory(b: &mut Criterion) {
|
||||
use std::env;
|
||||
|
||||
b.bench_function("bench_light_compute_memmap", move |b| b.iter(|| {
|
||||
b.bench_function("bench_light_compute_memmap", move |b| {
|
||||
b.iter(|| {
|
||||
let builder = NodeCacheBuilder::new(OptimizeFor::Cpu, u64::max_value());
|
||||
let light = builder.light(&env::temp_dir(), 486382);
|
||||
light.compute(&HASH, NONCE, u64::max_value());
|
||||
}));
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_light_from_file_round_trip_memory(b: &mut Criterion) {
|
||||
@ -86,11 +95,13 @@ fn bench_light_from_file_round_trip_memory(b: &mut Criterion) {
|
||||
dummy.to_file().unwrap();
|
||||
}
|
||||
|
||||
b.bench_function("bench_light_compute_memmap", move |b| b.iter(|| {
|
||||
b.bench_function("bench_light_compute_memmap", move |b| {
|
||||
b.iter(|| {
|
||||
let builder = NodeCacheBuilder::new(OptimizeFor::Cpu, u64::max_value());
|
||||
let light = builder.light_from_file(&dir, 486382).unwrap();
|
||||
light.compute(&HASH, NONCE, u64::max_value());
|
||||
}));
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_light_from_file_round_trip_memmap(b: &mut Criterion) {
|
||||
@ -105,9 +116,11 @@ fn bench_light_from_file_round_trip_memmap(b: &mut Criterion) {
|
||||
dummy.to_file().unwrap();
|
||||
}
|
||||
|
||||
b.bench_function("bench_light_compute_memmap", move |b| b.iter(|| {
|
||||
b.bench_function("bench_light_compute_memmap", move |b| {
|
||||
b.iter(|| {
|
||||
let builder = NodeCacheBuilder::new(OptimizeFor::Memory, u64::max_value());
|
||||
let light = builder.light_from_file(&dir, 486382).unwrap();
|
||||
light.compute(&HASH, NONCE, u64::max_value());
|
||||
}));
|
||||
})
|
||||
});
|
||||
}
|
||||
|
@ -7,16 +7,16 @@ extern crate tempdir;
|
||||
use criterion::Criterion;
|
||||
use ethash::progpow;
|
||||
|
||||
use tempdir::TempDir;
|
||||
use ethash::{compute::light_compute, NodeCacheBuilder, OptimizeFor};
|
||||
use rustc_hex::FromHex;
|
||||
use ethash::{NodeCacheBuilder, OptimizeFor};
|
||||
use ethash::compute::light_compute;
|
||||
use tempdir::TempDir;
|
||||
|
||||
fn bench_hashimoto_light(c: &mut Criterion) {
|
||||
let builder = NodeCacheBuilder::new(OptimizeFor::Memory, u64::max_value());
|
||||
let tempdir = TempDir::new("").unwrap();
|
||||
let light = builder.light(&tempdir.path(), 1);
|
||||
let h = FromHex::from_hex("c9149cc0386e689d789a1c2f3d5d169a61a6218ed30e74414dc736e442ef3d1f").unwrap();
|
||||
let h = FromHex::from_hex("c9149cc0386e689d789a1c2f3d5d169a61a6218ed30e74414dc736e442ef3d1f")
|
||||
.unwrap();
|
||||
let mut hash = [0; 32];
|
||||
hash.copy_from_slice(&h);
|
||||
|
||||
@ -30,20 +30,15 @@ fn bench_progpow_light(c: &mut Criterion) {
|
||||
let tempdir = TempDir::new("").unwrap();
|
||||
let cache = builder.new_cache(tempdir.into_path(), 0);
|
||||
|
||||
let h = FromHex::from_hex("c9149cc0386e689d789a1c2f3d5d169a61a6218ed30e74414dc736e442ef3d1f").unwrap();
|
||||
let h = FromHex::from_hex("c9149cc0386e689d789a1c2f3d5d169a61a6218ed30e74414dc736e442ef3d1f")
|
||||
.unwrap();
|
||||
let mut hash = [0; 32];
|
||||
hash.copy_from_slice(&h);
|
||||
|
||||
c.bench_function("progpow_light", move |b| {
|
||||
b.iter(|| {
|
||||
let c_dag = progpow::generate_cdag(cache.as_ref());
|
||||
progpow::progpow(
|
||||
hash,
|
||||
0,
|
||||
0,
|
||||
cache.as_ref(),
|
||||
&c_dag,
|
||||
);
|
||||
progpow::progpow(hash, 0, 0, cache.as_ref(), &c_dag);
|
||||
})
|
||||
});
|
||||
}
|
||||
@ -54,19 +49,14 @@ fn bench_progpow_optimal_light(c: &mut Criterion) {
|
||||
let cache = builder.new_cache(tempdir.into_path(), 0);
|
||||
let c_dag = progpow::generate_cdag(cache.as_ref());
|
||||
|
||||
let h = FromHex::from_hex("c9149cc0386e689d789a1c2f3d5d169a61a6218ed30e74414dc736e442ef3d1f").unwrap();
|
||||
let h = FromHex::from_hex("c9149cc0386e689d789a1c2f3d5d169a61a6218ed30e74414dc736e442ef3d1f")
|
||||
.unwrap();
|
||||
let mut hash = [0; 32];
|
||||
hash.copy_from_slice(&h);
|
||||
|
||||
c.bench_function("progpow_optimal_light", move |b| {
|
||||
b.iter(|| {
|
||||
progpow::progpow(
|
||||
hash,
|
||||
0,
|
||||
0,
|
||||
cache.as_ref(),
|
||||
&c_dag,
|
||||
);
|
||||
progpow::progpow(hash, 0, 0, cache.as_ref(), &c_dag);
|
||||
})
|
||||
});
|
||||
}
|
||||
@ -77,7 +67,8 @@ fn bench_keccak_f800_long(c: &mut Criterion) {
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches,
|
||||
criterion_group!(
|
||||
benches,
|
||||
bench_hashimoto_light,
|
||||
bench_progpow_light,
|
||||
bench_progpow_optimal_light,
|
||||
|
@ -16,19 +16,21 @@
|
||||
|
||||
use compute::Light;
|
||||
use either::Either;
|
||||
use keccak::{H256, keccak_512};
|
||||
use keccak::{keccak_512, H256};
|
||||
use memmap::MmapMut;
|
||||
use parking_lot::Mutex;
|
||||
use seed_compute::SeedHashCompute;
|
||||
|
||||
use shared::{ETHASH_CACHE_ROUNDS, NODE_BYTES, NODE_DWORDS, Node, epoch, get_cache_size, to_hex};
|
||||
use shared::{epoch, get_cache_size, to_hex, Node, ETHASH_CACHE_ROUNDS, NODE_BYTES, NODE_DWORDS};
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::fs;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::slice;
|
||||
use std::sync::Arc;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
fs,
|
||||
io::{self, Read, Write},
|
||||
path::{Path, PathBuf},
|
||||
slice,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
type Cache = Either<Vec<Node>, MmapMut>;
|
||||
|
||||
@ -59,9 +61,9 @@ fn new_buffer(path: &Path, num_nodes: usize, ident: &H256, optimize_for: Optimiz
|
||||
OptimizeFor::Memory => make_memmapped_cache(path, num_nodes, ident).ok(),
|
||||
};
|
||||
|
||||
memmap.map(Either::Right).unwrap_or_else(|| {
|
||||
Either::Left(make_memory_cache(num_nodes, ident))
|
||||
})
|
||||
memmap
|
||||
.map(Either::Right)
|
||||
.unwrap_or_else(|| Either::Left(make_memory_cache(num_nodes, ident)))
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -94,7 +96,7 @@ impl NodeCacheBuilder {
|
||||
NodeCacheBuilder {
|
||||
seedhash: Arc::new(Mutex::new(SeedHashCompute::default())),
|
||||
optimize_for: optimize_for.into().unwrap_or_default(),
|
||||
progpow_transition
|
||||
progpow_transition,
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,9 +171,10 @@ impl NodeCache {
|
||||
}
|
||||
|
||||
pub fn flush(&mut self) -> io::Result<()> {
|
||||
if let Some(last) = self.epoch.checked_sub(2).map(|ep| {
|
||||
cache_path(self.cache_dir.as_ref(), &self.builder.epoch_to_ident(ep))
|
||||
})
|
||||
if let Some(last) = self
|
||||
.epoch
|
||||
.checked_sub(2)
|
||||
.map(|ep| cache_path(self.cache_dir.as_ref(), &self.builder.epoch_to_ident(ep)))
|
||||
{
|
||||
fs::remove_file(last).unwrap_or_else(|error| match error.kind() {
|
||||
io::ErrorKind::NotFound => (),
|
||||
@ -234,9 +237,7 @@ fn consume_cache(cache: &mut Cache, path: &Path) -> io::Result<()> {
|
||||
|
||||
file.write_all(buf).map(|_| ())
|
||||
}
|
||||
Either::Right(ref mmap) => {
|
||||
mmap.flush()
|
||||
}
|
||||
Either::Right(ref mmap) => mmap.flush(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -244,25 +245,31 @@ fn cache_from_path(path: &Path, optimize_for: OptimizeFor) -> io::Result<Cache>
|
||||
let memmap = match optimize_for {
|
||||
OptimizeFor::Cpu => None,
|
||||
OptimizeFor::Memory => {
|
||||
let file = fs::OpenOptions::new().read(true).write(true).create(true).open(path)?;
|
||||
let file = fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(path)?;
|
||||
unsafe { MmapMut::map_mut(&file).ok() }
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
memmap.map(Either::Right).ok_or(()).or_else(|_| {
|
||||
read_from_path(path).map(Either::Left)
|
||||
})
|
||||
memmap
|
||||
.map(Either::Right)
|
||||
.ok_or(())
|
||||
.or_else(|_| read_from_path(path).map(Either::Left))
|
||||
}
|
||||
|
||||
fn read_from_path(path: &Path) -> io::Result<Vec<Node>> {
|
||||
use std::fs::File;
|
||||
use std::mem;
|
||||
use std::{fs::File, mem};
|
||||
|
||||
let mut file = File::open(path)?;
|
||||
|
||||
let mut nodes: Vec<u8> = Vec::with_capacity(file.metadata().map(|m| m.len() as _).unwrap_or(
|
||||
NODE_BYTES * 1_000_000,
|
||||
));
|
||||
let mut nodes: Vec<u8> = Vec::with_capacity(
|
||||
file.metadata()
|
||||
.map(|m| m.len() as _)
|
||||
.unwrap_or(NODE_BYTES * 1_000_000),
|
||||
);
|
||||
file.read_to_end(&mut nodes)?;
|
||||
|
||||
nodes.shrink_to_fit();
|
||||
|
@ -19,15 +19,14 @@
|
||||
|
||||
// TODO: fix endianess for big endian
|
||||
|
||||
use keccak::{keccak_512, keccak_256, H256};
|
||||
use cache::{NodeCache, NodeCacheBuilder};
|
||||
use progpow::{CDag, generate_cdag, progpow, keccak_f800_short, keccak_f800_long};
|
||||
use keccak::{keccak_256, keccak_512, H256};
|
||||
use progpow::{generate_cdag, keccak_f800_long, keccak_f800_short, progpow, CDag};
|
||||
use seed_compute::SeedHashCompute;
|
||||
use shared::*;
|
||||
use std::io;
|
||||
|
||||
use std::{mem, ptr};
|
||||
use std::path::Path;
|
||||
use std::{mem, path::Path, ptr};
|
||||
|
||||
const MIX_WORDS: usize = ETHASH_MIX_BYTES / 4;
|
||||
const MIX_NODES: usize = MIX_WORDS / NODE_WORDS;
|
||||
@ -68,7 +67,11 @@ impl Light {
|
||||
Algorithm::Hashimoto
|
||||
};
|
||||
|
||||
Light { block_number, cache, algorithm }
|
||||
Light {
|
||||
block_number,
|
||||
cache,
|
||||
algorithm,
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate the light boundary data
|
||||
@ -86,10 +89,9 @@ impl Light {
|
||||
);
|
||||
|
||||
ProofOfWork { value, mix_hash }
|
||||
},
|
||||
}
|
||||
Algorithm::Hashimoto => light_compute(self, header_hash, nonce),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub fn from_file_with_builder(
|
||||
@ -106,7 +108,11 @@ impl Light {
|
||||
Algorithm::Hashimoto
|
||||
};
|
||||
|
||||
Ok(Light { block_number, cache, algorithm })
|
||||
Ok(Light {
|
||||
block_number,
|
||||
cache,
|
||||
algorithm,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_file(&mut self) -> io::Result<&Path> {
|
||||
@ -129,7 +135,12 @@ fn fnv_hash(x: u32, y: u32) -> u32 {
|
||||
/// `nonce` The block's nonce
|
||||
/// `mix_hash` The mix digest hash
|
||||
/// Boundary recovered from mix hash
|
||||
pub fn quick_get_difficulty(header_hash: &H256, nonce: u64, mix_hash: &H256, progpow: bool) -> H256 {
|
||||
pub fn quick_get_difficulty(
|
||||
header_hash: &H256,
|
||||
nonce: u64,
|
||||
mix_hash: &H256,
|
||||
progpow: bool,
|
||||
) -> H256 {
|
||||
unsafe {
|
||||
if progpow {
|
||||
let seed = keccak_f800_short(*header_hash, nonce, [0u32; 8]);
|
||||
@ -173,14 +184,14 @@ fn hash_compute(light: &Light, full_size: usize, header_hash: &H256, nonce: u64)
|
||||
// We use explicit lifetimes to ensure that val's borrow is invalidated until the
|
||||
// transmuted val dies.
|
||||
unsafe fn make_const_array<T, U>(val: &mut [T]) -> &mut [U; $n] {
|
||||
use ::std::mem;
|
||||
use std::mem;
|
||||
|
||||
debug_assert_eq!(val.len() * mem::size_of::<T>(), $n * mem::size_of::<U>());
|
||||
&mut *(val.as_mut_ptr() as *mut [U; $n])
|
||||
}
|
||||
|
||||
make_const_array($value)
|
||||
}}
|
||||
}};
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
@ -318,7 +329,10 @@ fn hash_compute(light: &Light, full_size: usize, header_hash: &H256, nonce: u64)
|
||||
buf.compress_bytes
|
||||
};
|
||||
|
||||
ProofOfWork { mix_hash: mix_hash, value: value }
|
||||
ProofOfWork {
|
||||
mix_hash: mix_hash,
|
||||
value: value,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Use the `simd` crate
|
||||
@ -331,8 +345,8 @@ pub fn calculate_dag_item(node_index: u32, cache: &[Node]) -> Node {
|
||||
|
||||
debug_assert_eq!(NODE_WORDS, 16);
|
||||
for i in 0..ETHASH_DATASET_PARENTS as u32 {
|
||||
let parent_index = fnv_hash(node_index ^ i, ret.as_words()[i as usize % NODE_WORDS]) %
|
||||
num_parent_nodes as u32;
|
||||
let parent_index = fnv_hash(node_index ^ i, ret.as_words()[i as usize % NODE_WORDS])
|
||||
% num_parent_nodes as u32;
|
||||
let parent = &cache[parent_index as usize];
|
||||
|
||||
unroll! {
|
||||
@ -363,7 +377,10 @@ mod test {
|
||||
assert_eq!(16907456usize, get_cache_size(ETHASH_EPOCH_LENGTH + 1));
|
||||
assert_eq!(284950208usize, get_cache_size(2046 * ETHASH_EPOCH_LENGTH));
|
||||
assert_eq!(285081536usize, get_cache_size(2047 * ETHASH_EPOCH_LENGTH));
|
||||
assert_eq!(285081536usize, get_cache_size(2048 * ETHASH_EPOCH_LENGTH - 1));
|
||||
assert_eq!(
|
||||
285081536usize,
|
||||
get_cache_size(2048 * ETHASH_EPOCH_LENGTH - 1)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -396,7 +413,10 @@ mod test {
|
||||
0x4a, 0x8e, 0x95, 0x69, 0xef, 0xc7, 0xd7, 0x1b, 0x33, 0x35, 0xdf, 0x36, 0x8c, 0x9a,
|
||||
0xe9, 0x7e, 0x53, 0x84,
|
||||
];
|
||||
assert_eq!(quick_get_difficulty(&hash, nonce, &mix_hash, false)[..], boundary_good[..]);
|
||||
assert_eq!(
|
||||
quick_get_difficulty(&hash, nonce, &mix_hash, false)[..],
|
||||
boundary_good[..]
|
||||
);
|
||||
let boundary_bad = [
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x3a, 0x9b, 0x6c, 0x69, 0xbc, 0x2c, 0xe2, 0xa2,
|
||||
0x4a, 0x8e, 0x95, 0x69, 0xef, 0xc7, 0xd7, 0x1b, 0x33, 0x35, 0xdf, 0x36, 0x8c, 0x9a,
|
||||
@ -436,16 +456,28 @@ mod test {
|
||||
fn test_drop_old_data() {
|
||||
let tempdir = TempDir::new("").unwrap();
|
||||
let builder = NodeCacheBuilder::new(None, u64::max_value());
|
||||
let first = builder.light(tempdir.path(), 0).to_file().unwrap().to_owned();
|
||||
let first = builder
|
||||
.light(tempdir.path(), 0)
|
||||
.to_file()
|
||||
.unwrap()
|
||||
.to_owned();
|
||||
|
||||
let second = builder.light(tempdir.path(), ETHASH_EPOCH_LENGTH).to_file().unwrap().to_owned();
|
||||
let second = builder
|
||||
.light(tempdir.path(), ETHASH_EPOCH_LENGTH)
|
||||
.to_file()
|
||||
.unwrap()
|
||||
.to_owned();
|
||||
assert!(fs::metadata(&first).is_ok());
|
||||
|
||||
let _ = builder.light(tempdir.path(), ETHASH_EPOCH_LENGTH * 2).to_file();
|
||||
let _ = builder
|
||||
.light(tempdir.path(), ETHASH_EPOCH_LENGTH * 2)
|
||||
.to_file();
|
||||
assert!(fs::metadata(&first).is_err());
|
||||
assert!(fs::metadata(&second).is_ok());
|
||||
|
||||
let _ = builder.light(tempdir.path(), ETHASH_EPOCH_LENGTH * 3).to_file();
|
||||
let _ = builder
|
||||
.light(tempdir.path(), ETHASH_EPOCH_LENGTH * 3)
|
||||
.to_file();
|
||||
assert!(fs::metadata(&second).is_err());
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,12 @@ pub mod keccak_512 {
|
||||
// This is safe since `keccak_*` uses an internal buffer and copies the result to the output. This
|
||||
// means that we can reuse the input buffer for both input and output.
|
||||
unsafe {
|
||||
hash::keccak_512_unchecked(input.as_mut_ptr(), input.len(), input.as_ptr(), input.len());
|
||||
hash::keccak_512_unchecked(
|
||||
input.as_mut_ptr(),
|
||||
input.len(),
|
||||
input.as_ptr(),
|
||||
input.len(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -50,7 +55,12 @@ pub mod keccak_256 {
|
||||
// This is safe since `keccak_*` uses an internal buffer and copies the result to the output. This
|
||||
// means that we can reuse the input buffer for both input and output.
|
||||
unsafe {
|
||||
hash::keccak_256_unchecked(input.as_mut_ptr(), input.len(), input.as_ptr(), input.len());
|
||||
hash::keccak_256_unchecked(
|
||||
input.as_mut_ptr(),
|
||||
input.len(),
|
||||
input.as_ptr(),
|
||||
input.len(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,9 +39,9 @@ pub mod compute;
|
||||
#[cfg(not(feature = "bench"))]
|
||||
mod compute;
|
||||
|
||||
mod seed_compute;
|
||||
mod cache;
|
||||
mod keccak;
|
||||
mod seed_compute;
|
||||
mod shared;
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
@ -50,15 +50,17 @@ pub mod progpow;
|
||||
mod progpow;
|
||||
|
||||
pub use cache::{NodeCacheBuilder, OptimizeFor};
|
||||
pub use compute::{ProofOfWork, quick_get_difficulty, slow_hash_block_number};
|
||||
use compute::Light;
|
||||
pub use compute::{quick_get_difficulty, slow_hash_block_number, ProofOfWork};
|
||||
use ethereum_types::{U256, U512};
|
||||
use keccak::H256;
|
||||
use parking_lot::Mutex;
|
||||
pub use seed_compute::SeedHashCompute;
|
||||
pub use shared::ETHASH_EPOCH_LENGTH;
|
||||
use std::mem;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{
|
||||
mem,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@ -79,10 +81,17 @@ pub struct EthashManager {
|
||||
|
||||
impl EthashManager {
|
||||
/// Create a new new instance of ethash manager
|
||||
pub fn new<T: Into<Option<OptimizeFor>>>(cache_dir: &Path, optimize_for: T, progpow_transition: u64) -> EthashManager {
|
||||
pub fn new<T: Into<Option<OptimizeFor>>>(
|
||||
cache_dir: &Path,
|
||||
optimize_for: T,
|
||||
progpow_transition: u64,
|
||||
) -> EthashManager {
|
||||
EthashManager {
|
||||
cache_dir: cache_dir.to_path_buf(),
|
||||
nodecache_builder: NodeCacheBuilder::new(optimize_for.into().unwrap_or_default(), progpow_transition),
|
||||
nodecache_builder: NodeCacheBuilder::new(
|
||||
optimize_for.into().unwrap_or_default(),
|
||||
progpow_transition,
|
||||
),
|
||||
progpow_transition: progpow_transition,
|
||||
cache: Mutex::new(LightCache {
|
||||
recent_epoch: None,
|
||||
@ -131,17 +140,15 @@ impl EthashManager {
|
||||
|
||||
match light {
|
||||
None => {
|
||||
let light = match self.nodecache_builder.light_from_file(
|
||||
&self.cache_dir,
|
||||
block_number,
|
||||
) {
|
||||
let light = match self
|
||||
.nodecache_builder
|
||||
.light_from_file(&self.cache_dir, block_number)
|
||||
{
|
||||
Ok(light) => Arc::new(light),
|
||||
Err(e) => {
|
||||
debug!("Light cache file not found for {}:{}", block_number, e);
|
||||
let mut light = self.nodecache_builder.light(
|
||||
&self.cache_dir,
|
||||
block_number,
|
||||
);
|
||||
let mut light =
|
||||
self.nodecache_builder.light(&self.cache_dir, block_number);
|
||||
if let Err(e) = light.to_file() {
|
||||
warn!("Light cache file write error: {}", e);
|
||||
}
|
||||
@ -206,10 +213,22 @@ fn test_difficulty_to_boundary() {
|
||||
use ethereum_types::H256;
|
||||
use std::str::FromStr;
|
||||
|
||||
assert_eq!(difficulty_to_boundary(&U256::from(1)), H256::from(U256::max_value()));
|
||||
assert_eq!(difficulty_to_boundary(&U256::from(2)), H256::from_str("8000000000000000000000000000000000000000000000000000000000000000").unwrap());
|
||||
assert_eq!(difficulty_to_boundary(&U256::from(4)), H256::from_str("4000000000000000000000000000000000000000000000000000000000000000").unwrap());
|
||||
assert_eq!(difficulty_to_boundary(&U256::from(32)), H256::from_str("0800000000000000000000000000000000000000000000000000000000000000").unwrap());
|
||||
assert_eq!(
|
||||
difficulty_to_boundary(&U256::from(1)),
|
||||
H256::from(U256::max_value())
|
||||
);
|
||||
assert_eq!(
|
||||
difficulty_to_boundary(&U256::from(2)),
|
||||
H256::from_str("8000000000000000000000000000000000000000000000000000000000000000").unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
difficulty_to_boundary(&U256::from(4)),
|
||||
H256::from_str("4000000000000000000000000000000000000000000000000000000000000000").unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
difficulty_to_boundary(&U256::from(32)),
|
||||
H256::from_str("0800000000000000000000000000000000000000000000000000000000000000").unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -219,10 +238,22 @@ fn test_difficulty_to_boundary_regression() {
|
||||
// the last bit was originally being truncated when performing the conversion
|
||||
// https://github.com/paritytech/parity-ethereum/issues/8397
|
||||
for difficulty in 1..9 {
|
||||
assert_eq!(U256::from(difficulty), boundary_to_difficulty(&difficulty_to_boundary(&difficulty.into())));
|
||||
assert_eq!(H256::from(difficulty), difficulty_to_boundary(&boundary_to_difficulty(&difficulty.into())));
|
||||
assert_eq!(U256::from(difficulty), boundary_to_difficulty(&boundary_to_difficulty(&difficulty.into()).into()));
|
||||
assert_eq!(H256::from(difficulty), difficulty_to_boundary(&difficulty_to_boundary(&difficulty.into()).into()));
|
||||
assert_eq!(
|
||||
U256::from(difficulty),
|
||||
boundary_to_difficulty(&difficulty_to_boundary(&difficulty.into()))
|
||||
);
|
||||
assert_eq!(
|
||||
H256::from(difficulty),
|
||||
difficulty_to_boundary(&boundary_to_difficulty(&difficulty.into()))
|
||||
);
|
||||
assert_eq!(
|
||||
U256::from(difficulty),
|
||||
boundary_to_difficulty(&boundary_to_difficulty(&difficulty.into()).into())
|
||||
);
|
||||
assert_eq!(
|
||||
H256::from(difficulty),
|
||||
difficulty_to_boundary(&difficulty_to_boundary(&difficulty.into()).into())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,9 +14,9 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use compute::{FNV_PRIME, calculate_dag_item};
|
||||
use compute::{calculate_dag_item, FNV_PRIME};
|
||||
use keccak::H256;
|
||||
use shared::{ETHASH_ACCESSES, ETHASH_MIX_BYTES, Node, get_data_size};
|
||||
use shared::{get_data_size, Node, ETHASH_ACCESSES, ETHASH_MIX_BYTES};
|
||||
|
||||
const PROGPOW_CACHE_BYTES: usize = 16 * 1024;
|
||||
const PROGPOW_CACHE_WORDS: usize = PROGPOW_CACHE_BYTES / 4;
|
||||
@ -32,20 +32,17 @@ const PROGPOW_REGS: usize = 32;
|
||||
const FNV_HASH: u32 = 0x811c9dc5;
|
||||
|
||||
const KECCAKF_RNDC: [u32; 24] = [
|
||||
0x00000001, 0x00008082, 0x0000808a, 0x80008000, 0x0000808b, 0x80000001,
|
||||
0x80008081, 0x00008009, 0x0000008a, 0x00000088, 0x80008009, 0x8000000a,
|
||||
0x8000808b, 0x0000008b, 0x00008089, 0x00008003, 0x00008002, 0x00000080,
|
||||
0x0000800a, 0x8000000a, 0x80008081, 0x00008080, 0x80000001, 0x80008008
|
||||
0x00000001, 0x00008082, 0x0000808a, 0x80008000, 0x0000808b, 0x80000001, 0x80008081, 0x00008009,
|
||||
0x0000008a, 0x00000088, 0x80008009, 0x8000000a, 0x8000808b, 0x0000008b, 0x00008089, 0x00008003,
|
||||
0x00008002, 0x00000080, 0x0000800a, 0x8000000a, 0x80008081, 0x00008080, 0x80000001, 0x80008008,
|
||||
];
|
||||
|
||||
const KECCAKF_ROTC: [u32; 24] = [
|
||||
1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14,
|
||||
27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44
|
||||
1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44,
|
||||
];
|
||||
|
||||
const KECCAKF_PILN: [usize; 24] = [
|
||||
10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4,
|
||||
15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1
|
||||
10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1,
|
||||
];
|
||||
|
||||
fn keccak_f800_round(st: &mut [u32; 25], r: usize) {
|
||||
@ -90,10 +87,10 @@ fn keccak_f800_round(st: &mut [u32; 25], r: usize) {
|
||||
|
||||
fn keccak_f800(header_hash: H256, nonce: u64, result: [u32; 8], st: &mut [u32; 25]) {
|
||||
for i in 0..8 {
|
||||
st[i] = (header_hash[4 * i] as u32) +
|
||||
((header_hash[4 * i + 1] as u32) << 8) +
|
||||
((header_hash[4 * i + 2] as u32) << 16) +
|
||||
((header_hash[4 * i + 3] as u32) << 24);
|
||||
st[i] = (header_hash[4 * i] as u32)
|
||||
+ ((header_hash[4 * i + 1] as u32) << 8)
|
||||
+ ((header_hash[4 * i + 2] as u32) << 16)
|
||||
+ ((header_hash[4 * i + 3] as u32) << 24);
|
||||
}
|
||||
|
||||
st[8] = nonce as u32;
|
||||
@ -119,11 +116,7 @@ pub fn keccak_f800_long(header_hash: H256, nonce: u64, result: [u32; 8]) -> H256
|
||||
keccak_f800(header_hash, nonce, result, &mut st);
|
||||
|
||||
// NOTE: transmute from `[u32; 8]` to `[u8; 32]`
|
||||
unsafe {
|
||||
std::mem::transmute(
|
||||
[st[0], st[1], st[2], st[3], st[4], st[5], st[6], st[7]]
|
||||
)
|
||||
}
|
||||
unsafe { std::mem::transmute([st[0], st[1], st[2], st[3], st[4], st[5], st[6], st[7]]) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@ -146,8 +139,12 @@ impl Kiss99 {
|
||||
|
||||
#[inline]
|
||||
fn next_u32(&mut self) -> u32 {
|
||||
self.z = 36969u32.wrapping_mul(self.z & 65535).wrapping_add(self.z >> 16);
|
||||
self.w = 18000u32.wrapping_mul(self.w & 65535).wrapping_add(self.w >> 16);
|
||||
self.z = 36969u32
|
||||
.wrapping_mul(self.z & 65535)
|
||||
.wrapping_add(self.z >> 16);
|
||||
self.w = 18000u32
|
||||
.wrapping_mul(self.w & 65535)
|
||||
.wrapping_add(self.w >> 16);
|
||||
let mwc = (self.z << 16).wrapping_add(self.w);
|
||||
self.jsr ^= self.jsr << 17;
|
||||
self.jsr ^= self.jsr >> 13;
|
||||
@ -247,8 +244,8 @@ fn progpow_loop(
|
||||
) {
|
||||
// All lanes share a base address for the global load. Global offset uses
|
||||
// mix[0] to guarantee it depends on the load result.
|
||||
let g_offset = mix[loop_ % PROGPOW_LANES][0] as usize %
|
||||
(64 * data_size / (PROGPOW_LANES * PROGPOW_DAG_LOADS));
|
||||
let g_offset = mix[loop_ % PROGPOW_LANES][0] as usize
|
||||
% (64 * data_size / (PROGPOW_LANES * PROGPOW_DAG_LOADS));
|
||||
|
||||
// 256 bytes of dag data
|
||||
let mut dag_item = [0u32; 64];
|
||||
@ -354,14 +351,7 @@ pub fn progpow(
|
||||
// Execute the randomly generated inner loop
|
||||
let period = block_number / PROGPOW_PERIOD_LENGTH as u64;
|
||||
for i in 0..PROGPOW_CNT_DAG {
|
||||
progpow_loop(
|
||||
period,
|
||||
i,
|
||||
&mut mix,
|
||||
cache,
|
||||
c_dag,
|
||||
data_size,
|
||||
);
|
||||
progpow_loop(period, i, &mut mix, cache, c_dag, data_size);
|
||||
}
|
||||
|
||||
// Reduce mix data to a single per-lane result
|
||||
@ -403,12 +393,12 @@ pub fn generate_cdag(cache: &[Node]) -> CDag {
|
||||
mod test {
|
||||
use tempdir::TempDir;
|
||||
|
||||
use super::*;
|
||||
use cache::{NodeCacheBuilder, OptimizeFor};
|
||||
use keccak::H256;
|
||||
use rustc_hex::FromHex;
|
||||
use serde_json::{self, Value};
|
||||
use std::collections::VecDeque;
|
||||
use super::*;
|
||||
|
||||
fn h256(hex: &str) -> H256 {
|
||||
let bytes = FromHex::from_hex(hex).unwrap();
|
||||
@ -426,16 +416,29 @@ mod test {
|
||||
let c_dag = generate_cdag(cache.as_ref());
|
||||
|
||||
let expected = vec![
|
||||
690150178u32, 1181503948, 2248155602, 2118233073, 2193871115,
|
||||
1791778428, 1067701239, 724807309, 530799275, 3480325829, 3899029234,
|
||||
1998124059, 2541974622, 1100859971, 1297211151, 3268320000, 2217813733,
|
||||
2690422980, 3172863319, 2651064309
|
||||
690150178u32,
|
||||
1181503948,
|
||||
2248155602,
|
||||
2118233073,
|
||||
2193871115,
|
||||
1791778428,
|
||||
1067701239,
|
||||
724807309,
|
||||
530799275,
|
||||
3480325829,
|
||||
3899029234,
|
||||
1998124059,
|
||||
2541974622,
|
||||
1100859971,
|
||||
1297211151,
|
||||
3268320000,
|
||||
2217813733,
|
||||
2690422980,
|
||||
3172863319,
|
||||
2651064309,
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
c_dag.iter().take(20).cloned().collect::<Vec<_>>(),
|
||||
expected,
|
||||
);
|
||||
assert_eq!(c_dag.iter().take(20).cloned().collect::<Vec<_>>(), expected,);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -452,10 +455,7 @@ mod test {
|
||||
];
|
||||
|
||||
for (i, &(a, b, expected)) in tests.iter().enumerate() {
|
||||
assert_eq!(
|
||||
merge(a, b, i as u32),
|
||||
expected,
|
||||
);
|
||||
assert_eq!(merge(a, b, i as u32), expected,);
|
||||
}
|
||||
}
|
||||
|
||||
@ -487,29 +487,20 @@ mod test {
|
||||
];
|
||||
|
||||
for (i, &(a, b, expected)) in tests.iter().enumerate() {
|
||||
assert_eq!(
|
||||
math(a, b, i as u32),
|
||||
expected,
|
||||
);
|
||||
assert_eq!(math(a, b, i as u32), expected,);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keccak_256() {
|
||||
let expected = "5dd431e5fbc604f499bfa0232f45f8f142d0ff5178f539e5a7800bf0643697af";
|
||||
assert_eq!(
|
||||
keccak_f800_long([0; 32], 0, [0; 8]),
|
||||
h256(expected),
|
||||
);
|
||||
assert_eq!(keccak_f800_long([0; 32], 0, [0; 8]), h256(expected),);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keccak_64() {
|
||||
let expected: u64 = 0x5dd431e5fbc604f4;
|
||||
assert_eq!(
|
||||
keccak_f800_short([0; 32], 0, [0; 8]),
|
||||
expected,
|
||||
);
|
||||
assert_eq!(keccak_f800_short([0; 32], 0, [0; 8]), expected,);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -521,26 +512,18 @@ mod test {
|
||||
|
||||
let header_hash = [0; 32];
|
||||
|
||||
let (digest, result) = progpow(
|
||||
header_hash,
|
||||
0,
|
||||
0,
|
||||
cache.as_ref(),
|
||||
&c_dag,
|
||||
);
|
||||
let (digest, result) = progpow(header_hash, 0, 0, cache.as_ref(), &c_dag);
|
||||
|
||||
let expected_digest = FromHex::from_hex("63155f732f2bf556967f906155b510c917e48e99685ead76ea83f4eca03ab12b").unwrap();
|
||||
let expected_result = FromHex::from_hex("faeb1be51075b03a4ff44b335067951ead07a3b078539ace76fd56fc410557a3").unwrap();
|
||||
let expected_digest =
|
||||
FromHex::from_hex("63155f732f2bf556967f906155b510c917e48e99685ead76ea83f4eca03ab12b")
|
||||
.unwrap();
|
||||
let expected_result =
|
||||
FromHex::from_hex("faeb1be51075b03a4ff44b335067951ead07a3b078539ace76fd56fc410557a3")
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
digest.to_vec(),
|
||||
expected_digest,
|
||||
);
|
||||
assert_eq!(digest.to_vec(), expected_digest,);
|
||||
|
||||
assert_eq!(
|
||||
result.to_vec(),
|
||||
expected_result,
|
||||
);
|
||||
assert_eq!(result.to_vec(), expected_result,);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -556,11 +539,14 @@ mod test {
|
||||
let tests: Vec<VecDeque<Value>> =
|
||||
serde_json::from_slice(include_bytes!("../res/progpow_testvectors.json")).unwrap();
|
||||
|
||||
let tests: Vec<ProgpowTest> = tests.into_iter().map(|mut test: VecDeque<Value>| {
|
||||
let tests: Vec<ProgpowTest> = tests
|
||||
.into_iter()
|
||||
.map(|mut test: VecDeque<Value>| {
|
||||
assert!(test.len() == 5);
|
||||
|
||||
let block_number: u64 = serde_json::from_value(test.pop_front().unwrap()).unwrap();
|
||||
let header_hash: String = serde_json::from_value(test.pop_front().unwrap()).unwrap();
|
||||
let header_hash: String =
|
||||
serde_json::from_value(test.pop_front().unwrap()).unwrap();
|
||||
let nonce: String = serde_json::from_value(test.pop_front().unwrap()).unwrap();
|
||||
let mix_hash: String = serde_json::from_value(test.pop_front().unwrap()).unwrap();
|
||||
let final_hash: String = serde_json::from_value(test.pop_front().unwrap()).unwrap();
|
||||
@ -572,7 +558,8 @@ mod test {
|
||||
mix_hash: h256(&mix_hash),
|
||||
final_hash: h256(&final_hash),
|
||||
}
|
||||
}).collect();
|
||||
})
|
||||
.collect();
|
||||
|
||||
for test in tests {
|
||||
let builder = NodeCacheBuilder::new(OptimizeFor::Memory, u64::max_value());
|
||||
|
@ -14,8 +14,8 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use shared;
|
||||
use keccak::{keccak_256, H256};
|
||||
use shared;
|
||||
|
||||
use std::cell::Cell;
|
||||
|
||||
@ -71,7 +71,10 @@ mod tests {
|
||||
#[test]
|
||||
fn test_seed_compute_once() {
|
||||
let seed_compute = SeedHashCompute::default();
|
||||
let hash = [241, 175, 44, 134, 39, 121, 245, 239, 228, 236, 43, 160, 195, 152, 46, 7, 199, 5, 253, 147, 241, 206, 98, 43, 3, 104, 17, 40, 192, 79, 106, 162];
|
||||
let hash = [
|
||||
241, 175, 44, 134, 39, 121, 245, 239, 228, 236, 43, 160, 195, 152, 46, 7, 199, 5, 253,
|
||||
147, 241, 206, 98, 43, 3, 104, 17, 40, 192, 79, 106, 162,
|
||||
];
|
||||
assert_eq!(seed_compute.hash_block_number(486382), hash);
|
||||
}
|
||||
|
||||
@ -86,7 +89,10 @@ mod tests {
|
||||
let seed_compute = SeedHashCompute::default();
|
||||
// calculating an older value first shouldn't affect the result
|
||||
let _ = seed_compute.hash_block_number(50000);
|
||||
let hash = [241, 175, 44, 134, 39, 121, 245, 239, 228, 236, 43, 160, 195, 152, 46, 7, 199, 5, 253, 147, 241, 206, 98, 43, 3, 104, 17, 40, 192, 79, 106, 162];
|
||||
let hash = [
|
||||
241, 175, 44, 134, 39, 121, 245, 239, 228, 236, 43, 160, 195, 152, 46, 7, 199, 5, 253,
|
||||
147, 241, 206, 98, 43, 3, 104, 17, 40, 192, 79, 106, 162,
|
||||
];
|
||||
assert_eq!(seed_compute.hash_block_number(486382), hash);
|
||||
}
|
||||
|
||||
@ -95,8 +101,10 @@ mod tests {
|
||||
let seed_compute = SeedHashCompute::default();
|
||||
// calculating an newer value first shouldn't affect the result
|
||||
let _ = seed_compute.hash_block_number(972764);
|
||||
let hash = [241, 175, 44, 134, 39, 121, 245, 239, 228, 236, 43, 160, 195, 152, 46, 7, 199, 5, 253, 147, 241, 206, 98, 43, 3, 104, 17, 40, 192, 79, 106, 162];
|
||||
let hash = [
|
||||
241, 175, 44, 134, 39, 121, 245, 239, 228, 236, 43, 160, 195, 152, 46, 7, 199, 5, 253,
|
||||
147, 241, 206, 98, 43, 3, 104, 17, 40, 192, 79, 106, 162,
|
||||
];
|
||||
assert_eq!(seed_compute.hash_block_number(486382), hash);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -57,7 +57,8 @@ pub fn get_cache_size(block_number: u64) -> usize {
|
||||
|
||||
pub fn get_data_size(block_number: u64) -> usize {
|
||||
// TODO: Memoise
|
||||
let mut sz: u64 = DATASET_BYTES_INIT + DATASET_BYTES_GROWTH * (block_number / ETHASH_EPOCH_LENGTH);
|
||||
let mut sz: u64 =
|
||||
DATASET_BYTES_INIT + DATASET_BYTES_GROWTH * (block_number / ETHASH_EPOCH_LENGTH);
|
||||
sz = sz - ETHASH_MIX_BYTES as u64;
|
||||
while !is_prime(sz / ETHASH_MIX_BYTES as u64) {
|
||||
sz = sz - 2 * ETHASH_MIX_BYTES as u64;
|
||||
@ -107,7 +108,11 @@ pub union Node {
|
||||
|
||||
impl Clone for Node {
|
||||
fn clone(&self) -> Self {
|
||||
unsafe { Node { bytes: *&self.bytes } }
|
||||
unsafe {
|
||||
Node {
|
||||
bytes: *&self.bytes,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,18 +19,17 @@ extern crate criterion;
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
extern crate ethcore_builtin;
|
||||
extern crate ethcore;
|
||||
extern crate ethcore_builtin;
|
||||
extern crate ethereum_types;
|
||||
extern crate parity_bytes as bytes;
|
||||
extern crate rustc_hex;
|
||||
|
||||
use criterion::{Criterion, Bencher};
|
||||
use bytes::BytesRef;
|
||||
use criterion::{Bencher, Criterion};
|
||||
use ethcore::{ethereum::new_byzantium_test_machine, machine::EthereumMachine};
|
||||
use ethcore_builtin::Builtin;
|
||||
use ethcore::machine::EthereumMachine;
|
||||
use ethereum_types::U256;
|
||||
use ethcore::ethereum::new_byzantium_test_machine;
|
||||
use rustc_hex::FromHex;
|
||||
|
||||
lazy_static! {
|
||||
@ -52,7 +51,9 @@ impl<'a> BuiltinBenchmark<'a> {
|
||||
let expected = FromHex::from_hex(expected).unwrap();
|
||||
|
||||
BuiltinBenchmark {
|
||||
builtin, input, expected
|
||||
builtin,
|
||||
input,
|
||||
expected,
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,20 +61,16 @@ impl<'a> BuiltinBenchmark<'a> {
|
||||
let mut output = vec![0; self.expected.len()];
|
||||
|
||||
b.iter(|| {
|
||||
self.builtin.execute(&self.input, &mut BytesRef::Fixed(&mut output)).unwrap();
|
||||
self.builtin
|
||||
.execute(&self.input, &mut BytesRef::Fixed(&mut output))
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
assert_eq!(self.expected[..], output[..]);
|
||||
}
|
||||
}
|
||||
|
||||
fn bench(
|
||||
id: &str,
|
||||
builtin_address: &'static str,
|
||||
input: &str,
|
||||
expected: &str,
|
||||
b: &mut Criterion,
|
||||
) {
|
||||
fn bench(id: &str, builtin_address: &'static str, input: &str, expected: &str, b: &mut Criterion) {
|
||||
let bench = BuiltinBenchmark::new(builtin_address, input, expected);
|
||||
b.bench_function(id, move |b| bench.run(b));
|
||||
}
|
||||
|
@ -16,8 +16,7 @@
|
||||
|
||||
use ethereum_types::{H256, U256};
|
||||
|
||||
use common_types::{encoded, BlockNumber};
|
||||
use common_types::header::Header;
|
||||
use common_types::{encoded, header::Header, BlockNumber};
|
||||
|
||||
/// Contains information on a best block that is specific to the consensus engine.
|
||||
///
|
||||
|
@ -14,8 +14,8 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use ethereum_types::{H256, U256};
|
||||
use common_types::BlockNumber;
|
||||
use ethereum_types::{H256, U256};
|
||||
|
||||
/// Brief info about inserted block.
|
||||
#[derive(Clone)]
|
||||
@ -27,7 +27,7 @@ pub struct BlockInfo {
|
||||
/// Total block difficulty.
|
||||
pub total_difficulty: U256,
|
||||
/// Block location in blockchain.
|
||||
pub location: BlockLocation
|
||||
pub location: BlockLocation,
|
||||
}
|
||||
|
||||
/// Describes location of newly inserted block.
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -16,14 +16,16 @@
|
||||
|
||||
//! Blockchain generator for tests.
|
||||
|
||||
use ethereum_types::{Bloom, H256, U256};
|
||||
use std::collections::VecDeque;
|
||||
use ethereum_types::{U256, H256, Bloom};
|
||||
|
||||
use common_types::encoded;
|
||||
use common_types::header::Header;
|
||||
use common_types::transaction::{SignedTransaction, Transaction, Action};
|
||||
use common_types::view;
|
||||
use common_types::views::BlockView;
|
||||
use common_types::{
|
||||
encoded,
|
||||
header::Header,
|
||||
transaction::{Action, SignedTransaction, Transaction},
|
||||
view,
|
||||
views::BlockView,
|
||||
};
|
||||
use keccak_hash::keccak;
|
||||
use rlp::encode;
|
||||
use rlp_derive::RlpEncodable;
|
||||
@ -37,7 +39,7 @@ pub struct Block {
|
||||
/// Block transactions
|
||||
pub transactions: Vec<SignedTransaction>,
|
||||
/// Block uncles
|
||||
pub uncles: Vec<Header>
|
||||
pub uncles: Vec<Header>,
|
||||
}
|
||||
|
||||
impl Block {
|
||||
@ -105,9 +107,7 @@ impl BlockBuilder {
|
||||
let mut blocks = VecDeque::with_capacity(1);
|
||||
blocks.push_back(Block::default());
|
||||
|
||||
BlockBuilder {
|
||||
blocks,
|
||||
}
|
||||
BlockBuilder { blocks }
|
||||
}
|
||||
|
||||
/// Add new block with default options.
|
||||
@ -124,13 +124,19 @@ impl BlockBuilder {
|
||||
|
||||
/// Add block with specified options.
|
||||
#[inline]
|
||||
pub fn add_block_with<T>(&self, get_metadata: T) -> Self where T: Fn() -> BlockOptions {
|
||||
pub fn add_block_with<T>(&self, get_metadata: T) -> Self
|
||||
where
|
||||
T: Fn() -> BlockOptions,
|
||||
{
|
||||
self.add_blocks_with(1, get_metadata)
|
||||
}
|
||||
|
||||
/// Add a block with given difficulty
|
||||
#[inline]
|
||||
pub fn add_block_with_difficulty<T>(&self, difficulty: T) -> Self where T: Into<U256> {
|
||||
pub fn add_block_with_difficulty<T>(&self, difficulty: T) -> Self
|
||||
where
|
||||
T: Into<U256>,
|
||||
{
|
||||
let difficulty = difficulty.into();
|
||||
self.add_blocks_with(1, move || BlockOptions {
|
||||
difficulty,
|
||||
@ -155,8 +161,10 @@ impl BlockBuilder {
|
||||
action: Action::Create,
|
||||
value: 100.into(),
|
||||
data,
|
||||
}.sign(&keccak("").into(), None)
|
||||
}).take(count);
|
||||
}
|
||||
.sign(&keccak("").into(), None)
|
||||
})
|
||||
.take(count);
|
||||
|
||||
self.add_block_with_transactions(transactions)
|
||||
}
|
||||
@ -164,7 +172,9 @@ impl BlockBuilder {
|
||||
/// Add a block with given transactions.
|
||||
#[inline]
|
||||
pub fn add_block_with_transactions<T>(&self, transactions: T) -> Self
|
||||
where T: IntoIterator<Item = SignedTransaction> {
|
||||
where
|
||||
T: IntoIterator<Item = SignedTransaction>,
|
||||
{
|
||||
let transactions = transactions.into_iter().collect::<Vec<_>>();
|
||||
self.add_blocks_with(1, || BlockOptions {
|
||||
transactions: transactions.clone(),
|
||||
@ -182,7 +192,10 @@ impl BlockBuilder {
|
||||
}
|
||||
|
||||
/// Add a bunch of blocks with given metadata.
|
||||
pub fn add_blocks_with<T>(&self, count: usize, get_metadata: T) -> Self where T: Fn() -> BlockOptions {
|
||||
pub fn add_blocks_with<T>(&self, count: usize, get_metadata: T) -> Self
|
||||
where
|
||||
T: Fn() -> BlockOptions,
|
||||
{
|
||||
assert!(count > 0, "There must be at least 1 block");
|
||||
let mut parent_hash = self.last().hash();
|
||||
let mut parent_number = self.last().number();
|
||||
@ -207,15 +220,15 @@ impl BlockBuilder {
|
||||
blocks.push_back(block);
|
||||
}
|
||||
|
||||
BlockBuilder {
|
||||
blocks,
|
||||
}
|
||||
BlockBuilder { blocks }
|
||||
}
|
||||
|
||||
/// Get a reference to the last generated block.
|
||||
#[inline]
|
||||
pub fn last(&self) -> &Block {
|
||||
self.blocks.back().expect("There is always at least 1 block")
|
||||
self.blocks
|
||||
.back()
|
||||
.expect("There is always at least 1 block")
|
||||
}
|
||||
}
|
||||
|
||||
@ -227,7 +240,10 @@ pub struct BlockGenerator {
|
||||
|
||||
impl BlockGenerator {
|
||||
/// Create new block generator.
|
||||
pub fn new<T>(builders: T) -> Self where T: IntoIterator<Item = BlockBuilder> {
|
||||
pub fn new<T>(builders: T) -> Self
|
||||
where
|
||||
T: IntoIterator<Item = BlockBuilder>,
|
||||
{
|
||||
BlockGenerator {
|
||||
builders: builders.into_iter().collect(),
|
||||
}
|
||||
@ -244,18 +260,17 @@ impl Iterator for BlockGenerator {
|
||||
if let Some(block) = builder.blocks.pop_front() {
|
||||
return Some(block);
|
||||
}
|
||||
},
|
||||
}
|
||||
None => return None,
|
||||
}
|
||||
self.builders.pop_front();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{BlockBuilder, BlockOptions, BlockGenerator};
|
||||
use super::{BlockBuilder, BlockGenerator, BlockOptions};
|
||||
|
||||
#[test]
|
||||
fn test_block_builder() {
|
||||
|
@ -16,8 +16,8 @@
|
||||
|
||||
//! Import route.
|
||||
|
||||
use ethereum_types::H256;
|
||||
use crate::block_info::{BlockInfo, BlockLocation};
|
||||
use ethereum_types::H256;
|
||||
|
||||
/// Import route for newly inserted block.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
@ -68,17 +68,20 @@ impl From<BlockInfo> for ImportRoute {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ethereum_types::{H256, U256};
|
||||
use crate::block_info::{BlockInfo, BlockLocation, BranchBecomingCanonChainData};
|
||||
use super::ImportRoute;
|
||||
use crate::block_info::{BlockInfo, BlockLocation, BranchBecomingCanonChainData};
|
||||
use ethereum_types::{H256, U256};
|
||||
|
||||
#[test]
|
||||
fn import_route_none() {
|
||||
assert_eq!(ImportRoute::none(), ImportRoute {
|
||||
assert_eq!(
|
||||
ImportRoute::none(),
|
||||
ImportRoute {
|
||||
enacted: vec![],
|
||||
retracted: vec![],
|
||||
omitted: vec![],
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -90,11 +93,14 @@ mod tests {
|
||||
location: BlockLocation::Branch,
|
||||
};
|
||||
|
||||
assert_eq!(ImportRoute::from(info), ImportRoute {
|
||||
assert_eq!(
|
||||
ImportRoute::from(info),
|
||||
ImportRoute {
|
||||
retracted: vec![],
|
||||
enacted: vec![],
|
||||
omitted: vec![H256::from(U256::from(1))],
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -106,11 +112,14 @@ mod tests {
|
||||
location: BlockLocation::CanonChain,
|
||||
};
|
||||
|
||||
assert_eq!(ImportRoute::from(info), ImportRoute {
|
||||
assert_eq!(
|
||||
ImportRoute::from(info),
|
||||
ImportRoute {
|
||||
retracted: vec![],
|
||||
enacted: vec![H256::from(U256::from(1))],
|
||||
omitted: vec![],
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -123,13 +132,16 @@ mod tests {
|
||||
ancestor: H256::from(U256::from(0)),
|
||||
enacted: vec![H256::from(U256::from(1))],
|
||||
retracted: vec![H256::from(U256::from(3)), H256::from(U256::from(4))],
|
||||
})
|
||||
}),
|
||||
};
|
||||
|
||||
assert_eq!(ImportRoute::from(info), ImportRoute {
|
||||
assert_eq!(
|
||||
ImportRoute::from(info),
|
||||
ImportRoute {
|
||||
retracted: vec![H256::from(U256::from(3)), H256::from(U256::from(4))],
|
||||
enacted: vec![H256::from(U256::from(1)), H256::from(U256::from(2))],
|
||||
omitted: vec![],
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -28,10 +28,12 @@ mod update;
|
||||
|
||||
pub mod generator;
|
||||
|
||||
pub use self::blockchain::{BlockProvider, BlockChain, BlockChainDB, BlockChainDBHandler};
|
||||
pub use self::cache::CacheSize;
|
||||
pub use self::config::Config;
|
||||
pub use self::import_route::ImportRoute;
|
||||
pub use self::update::ExtrasInsert;
|
||||
pub use ethcore_db::keys::{BlockReceipts, BlockDetails, TransactionAddress, BlockNumberKey};
|
||||
pub use self::{
|
||||
blockchain::{BlockChain, BlockChainDB, BlockChainDBHandler, BlockProvider},
|
||||
cache::CacheSize,
|
||||
config::Config,
|
||||
import_route::ImportRoute,
|
||||
update::ExtrasInsert,
|
||||
};
|
||||
pub use common_types::tree_route::TreeRoute;
|
||||
pub use ethcore_db::keys::{BlockDetails, BlockNumberKey, BlockReceipts, TransactionAddress};
|
||||
|
@ -16,11 +16,9 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use common_types::BlockNumber;
|
||||
use common_types::encoded::Block;
|
||||
use common_types::engines::ForkChoice;
|
||||
use common_types::{encoded::Block, engines::ForkChoice, BlockNumber};
|
||||
use ethcore_db::keys::{BlockDetails, BlockReceipts, TransactionAddress};
|
||||
use ethereum_types::{H256, Bloom};
|
||||
use ethereum_types::{Bloom, H256};
|
||||
|
||||
use crate::block_info::BlockInfo;
|
||||
|
||||
|
@ -22,19 +22,19 @@ use std::{
|
||||
cmp::{max, min},
|
||||
collections::BTreeMap,
|
||||
convert::{TryFrom, TryInto},
|
||||
io::{self, Read, Cursor},
|
||||
io::{self, Cursor, Read},
|
||||
mem::size_of,
|
||||
str::FromStr
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use byteorder::{BigEndian, LittleEndian, ReadBytesExt};
|
||||
use eip_152::compress;
|
||||
use ethereum_types::{H256, U256};
|
||||
use ethjson;
|
||||
use ethkey::{Signature, recover as ec_recover};
|
||||
use eip_152::compress;
|
||||
use ethkey::{recover as ec_recover, Signature};
|
||||
use keccak_hash::keccak;
|
||||
use log::{warn, trace};
|
||||
use num::{BigUint, Zero, One};
|
||||
use log::{trace, warn};
|
||||
use num::{BigUint, One, Zero};
|
||||
use parity_bytes::BytesRef;
|
||||
use parity_crypto::digest;
|
||||
|
||||
@ -149,7 +149,9 @@ impl Pricer for ModexpPricer {
|
||||
|
||||
// read lengths as U256 here for accurate gas calculation.
|
||||
let mut read_len = || {
|
||||
reader.read_exact(&mut buf[..]).expect("reading from zero-extended memory cannot fail; qed");
|
||||
reader
|
||||
.read_exact(&mut buf[..])
|
||||
.expect("reading from zero-extended memory cannot fail; qed");
|
||||
U256::from(H256::from_slice(&buf[..]))
|
||||
};
|
||||
let base_len = read_len();
|
||||
@ -157,14 +159,15 @@ impl Pricer for ModexpPricer {
|
||||
let mod_len = read_len();
|
||||
|
||||
if mod_len.is_zero() && base_len.is_zero() {
|
||||
return U256::zero()
|
||||
return U256::zero();
|
||||
}
|
||||
|
||||
let max_len = U256::from(u32::max_value() / 2);
|
||||
if base_len > max_len || mod_len > max_len || exp_len > max_len {
|
||||
return U256::max_value();
|
||||
}
|
||||
let (base_len, exp_len, mod_len) = (base_len.low_u64(), exp_len.low_u64(), mod_len.low_u64());
|
||||
let (base_len, exp_len, mod_len) =
|
||||
(base_len.low_u64(), exp_len.low_u64(), mod_len.low_u64());
|
||||
|
||||
let m = max(mod_len, base_len);
|
||||
// read fist 32-byte word of the exponent.
|
||||
@ -174,7 +177,9 @@ impl Pricer for ModexpPricer {
|
||||
buf.iter_mut().for_each(|b| *b = 0);
|
||||
let mut reader = input[(96 + base_len as usize)..].chain(io::repeat(0));
|
||||
let len = min(exp_len, 32) as usize;
|
||||
reader.read_exact(&mut buf[(32 - len)..]).expect("reading from zero-extended memory cannot fail; qed");
|
||||
reader
|
||||
.read_exact(&mut buf[(32 - len)..])
|
||||
.expect("reading from zero-extended memory cannot fail; qed");
|
||||
U256::from(H256::from_slice(&buf[..]))
|
||||
};
|
||||
|
||||
@ -190,7 +195,11 @@ impl Pricer for ModexpPricer {
|
||||
|
||||
impl ModexpPricer {
|
||||
fn adjusted_exp_len(len: u64, exp_low: U256) -> u64 {
|
||||
let bit_index = if exp_low.is_zero() { 0 } else { (255 - exp_low.leading_zeros()) as u64 };
|
||||
let bit_index = if exp_low.is_zero() {
|
||||
0
|
||||
} else {
|
||||
(255 - exp_low.leading_zeros()) as u64
|
||||
};
|
||||
if len <= 32 {
|
||||
bit_index
|
||||
} else {
|
||||
@ -268,22 +277,18 @@ impl From<ethjson::spec::builtin::Pricing> for Pricing {
|
||||
ethjson::spec::builtin::Pricing::Blake2F { gas_per_round } => {
|
||||
Pricing::Blake2F(gas_per_round)
|
||||
}
|
||||
ethjson::spec::builtin::Pricing::Linear(linear) => {
|
||||
Pricing::Linear(Linear {
|
||||
ethjson::spec::builtin::Pricing::Linear(linear) => Pricing::Linear(Linear {
|
||||
base: linear.base,
|
||||
word: linear.word,
|
||||
})
|
||||
}
|
||||
ethjson::spec::builtin::Pricing::Modexp(exp) => {
|
||||
Pricing::Modexp(ModexpPricer {
|
||||
}),
|
||||
ethjson::spec::builtin::Pricing::Modexp(exp) => Pricing::Modexp(ModexpPricer {
|
||||
divisor: if exp.divisor == 0 {
|
||||
warn!(target: "builtin", "Zero modexp divisor specified. Falling back to default: 10.");
|
||||
10
|
||||
} else {
|
||||
exp.divisor
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
}),
|
||||
ethjson::spec::builtin::Pricing::AltBn128Pairing(pricer) => {
|
||||
Pricing::AltBn128Pairing(AltBn128PairingPricer {
|
||||
price: AltBn128PairingPrice {
|
||||
@ -294,7 +299,7 @@ impl From<ethjson::spec::builtin::Pricing> for Pricing {
|
||||
}
|
||||
ethjson::spec::builtin::Pricing::AltBn128ConstOperations(pricer) => {
|
||||
Pricing::AltBn128ConstOperations(AltBn128ConstOperations {
|
||||
price: pricer.price
|
||||
price: pricer.price,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -320,7 +325,7 @@ enum EthereumBuiltin {
|
||||
/// alt_bn128_pairing
|
||||
Bn128Pairing(Bn128Pairing),
|
||||
/// blake2_f (The Blake2 compression function F, EIP-152)
|
||||
Blake2F(Blake2F)
|
||||
Blake2F(Blake2F),
|
||||
}
|
||||
|
||||
impl FromStr for EthereumBuiltin {
|
||||
@ -406,7 +411,9 @@ impl Implementation for EcRecover {
|
||||
|
||||
let bit = match v[31] {
|
||||
27 | 28 if v.0[..31] == [0; 31] => v[31] - 27,
|
||||
_ => { return Ok(()); },
|
||||
_ => {
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let s = Signature::from_rsv(&r, &s, bit);
|
||||
@ -439,7 +446,7 @@ impl Implementation for Blake2F {
|
||||
|
||||
if input.len() != BLAKE2_F_ARG_LEN {
|
||||
trace!(target: "builtin", "input length for Blake2 F precompile should be exactly 213 bytes, was {}", input.len());
|
||||
return Err("input length for Blake2 F precompile should be exactly 213 bytes".into())
|
||||
return Err("input length for Blake2 F precompile should be exactly 213 bytes".into());
|
||||
}
|
||||
|
||||
let mut cursor = Cursor::new(input);
|
||||
@ -469,7 +476,7 @@ impl Implementation for Blake2F {
|
||||
Some(0) => false,
|
||||
_ => {
|
||||
trace!(target: "builtin", "incorrect final block indicator flag, was: {:?}", input.last());
|
||||
return Err("incorrect final block indicator flag".into())
|
||||
return Err("incorrect final block indicator flag".into());
|
||||
}
|
||||
};
|
||||
|
||||
@ -477,7 +484,7 @@ impl Implementation for Blake2F {
|
||||
|
||||
let mut output_buf = [0u8; 8 * size_of::<u64>()];
|
||||
for (i, state_word) in h.iter().enumerate() {
|
||||
output_buf[i*8..(i+1)*8].copy_from_slice(&state_word.to_le_bytes());
|
||||
output_buf[i * 8..(i + 1) * 8].copy_from_slice(&state_word.to_le_bytes());
|
||||
}
|
||||
output.write(0, &output_buf[..]);
|
||||
Ok(())
|
||||
@ -518,7 +525,9 @@ fn modexp(mut base: BigUint, exp: Vec<u8>, modulus: BigUint) -> BigUint {
|
||||
base %= &modulus;
|
||||
|
||||
// Fast path for base divisible by modulus.
|
||||
if base.is_zero() { return BigUint::zero() }
|
||||
if base.is_zero() {
|
||||
return BigUint::zero();
|
||||
}
|
||||
|
||||
// Left-to-right binary exponentiation (Handbook of Applied Cryptography - Algorithm 14.79).
|
||||
// http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf
|
||||
@ -550,7 +559,9 @@ impl Implementation for Modexp {
|
||||
// ignoring the first 24 bytes might technically lead us to fall out of consensus,
|
||||
// but so would running out of addressable memory!
|
||||
let mut read_len = |reader: &mut io::Chain<&[u8], io::Repeat>| {
|
||||
reader.read_exact(&mut buf[..]).expect("reading from zero-extended memory cannot fail; qed");
|
||||
reader
|
||||
.read_exact(&mut buf[..])
|
||||
.expect("reading from zero-extended memory cannot fail; qed");
|
||||
let mut len_bytes = [0u8; 8];
|
||||
len_bytes.copy_from_slice(&buf[24..]);
|
||||
u64::from_be_bytes(len_bytes) as usize
|
||||
@ -567,14 +578,18 @@ impl Implementation for Modexp {
|
||||
// read the numbers themselves.
|
||||
let mut buf = vec![0; max(mod_len, max(base_len, exp_len))];
|
||||
let mut read_num = |reader: &mut io::Chain<&[u8], io::Repeat>, len: usize| {
|
||||
reader.read_exact(&mut buf[..len]).expect("reading from zero-extended memory cannot fail; qed");
|
||||
reader
|
||||
.read_exact(&mut buf[..len])
|
||||
.expect("reading from zero-extended memory cannot fail; qed");
|
||||
BigUint::from_bytes_be(&buf[..len])
|
||||
};
|
||||
|
||||
let base = read_num(&mut reader, base_len);
|
||||
|
||||
let mut exp_buf = vec![0; exp_len];
|
||||
reader.read_exact(&mut exp_buf[..exp_len]).expect("reading from zero-extended memory cannot fail; qed");
|
||||
reader
|
||||
.read_exact(&mut exp_buf[..exp_len])
|
||||
.expect("reading from zero-extended memory cannot fail; qed");
|
||||
|
||||
let modulus = read_num(&mut reader, mod_len);
|
||||
|
||||
@ -598,27 +613,33 @@ impl Implementation for Modexp {
|
||||
fn read_fr(reader: &mut io::Chain<&[u8], io::Repeat>) -> Result<bn::Fr, &'static str> {
|
||||
let mut buf = [0u8; 32];
|
||||
|
||||
reader.read_exact(&mut buf[..]).expect("reading from zero-extended memory cannot fail; qed");
|
||||
reader
|
||||
.read_exact(&mut buf[..])
|
||||
.expect("reading from zero-extended memory cannot fail; qed");
|
||||
bn::Fr::from_slice(&buf[0..32]).map_err(|_| "Invalid field element")
|
||||
}
|
||||
|
||||
fn read_point(reader: &mut io::Chain<&[u8], io::Repeat>) -> Result<bn::G1, &'static str> {
|
||||
use bn::{Fq, AffineG1, G1, Group};
|
||||
use bn::{AffineG1, Fq, Group, G1};
|
||||
|
||||
let mut buf = [0u8; 32];
|
||||
|
||||
reader.read_exact(&mut buf[..]).expect("reading from zero-extended memory cannot fail; qed");
|
||||
reader
|
||||
.read_exact(&mut buf[..])
|
||||
.expect("reading from zero-extended memory cannot fail; qed");
|
||||
let px = Fq::from_slice(&buf[0..32]).map_err(|_| "Invalid point x coordinate")?;
|
||||
|
||||
reader.read_exact(&mut buf[..]).expect("reading from zero-extended memory cannot fail; qed");
|
||||
reader
|
||||
.read_exact(&mut buf[..])
|
||||
.expect("reading from zero-extended memory cannot fail; qed");
|
||||
let py = Fq::from_slice(&buf[0..32]).map_err(|_| "Invalid point y coordinate")?;
|
||||
Ok(
|
||||
if px == Fq::zero() && py == Fq::zero() {
|
||||
Ok(if px == Fq::zero() && py == Fq::zero() {
|
||||
G1::zero()
|
||||
} else {
|
||||
AffineG1::new(px, py).map_err(|_| "Invalid curve point")?.into()
|
||||
}
|
||||
)
|
||||
AffineG1::new(px, py)
|
||||
.map_err(|_| "Invalid curve point")?
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
impl Implementation for Bn128Add {
|
||||
@ -633,8 +654,12 @@ impl Implementation for Bn128Add {
|
||||
let mut write_buf = [0u8; 64];
|
||||
if let Some(sum) = AffineG1::from_jacobian(p1 + p2) {
|
||||
// point not at infinity
|
||||
sum.x().to_big_endian(&mut write_buf[0..32]).expect("Cannot fail since 0..32 is 32-byte length");
|
||||
sum.y().to_big_endian(&mut write_buf[32..64]).expect("Cannot fail since 32..64 is 32-byte length");
|
||||
sum.x()
|
||||
.to_big_endian(&mut write_buf[0..32])
|
||||
.expect("Cannot fail since 0..32 is 32-byte length");
|
||||
sum.y()
|
||||
.to_big_endian(&mut write_buf[32..64])
|
||||
.expect("Cannot fail since 32..64 is 32-byte length");
|
||||
}
|
||||
output.write(0, &write_buf);
|
||||
|
||||
@ -654,8 +679,12 @@ impl Implementation for Bn128Mul {
|
||||
let mut write_buf = [0u8; 64];
|
||||
if let Some(sum) = AffineG1::from_jacobian(p * fr) {
|
||||
// point not at infinity
|
||||
sum.x().to_big_endian(&mut write_buf[0..32]).expect("Cannot fail since 0..32 is 32-byte length");
|
||||
sum.y().to_big_endian(&mut write_buf[32..64]).expect("Cannot fail since 32..64 is 32-byte length");
|
||||
sum.x()
|
||||
.to_big_endian(&mut write_buf[0..32])
|
||||
.expect("Cannot fail since 0..32 is 32-byte length");
|
||||
sum.y()
|
||||
.to_big_endian(&mut write_buf[32..64])
|
||||
.expect("Cannot fail since 32..64 is 32-byte length");
|
||||
}
|
||||
output.write(0, &write_buf);
|
||||
Ok(())
|
||||
@ -669,12 +698,12 @@ impl Implementation for Bn128Pairing {
|
||||
/// - any of even points does not belong to the twisted bn128 curve over the field F_p^2 = F_p[i] / (i^2 + 1)
|
||||
fn execute(&self, input: &[u8], output: &mut BytesRef) -> Result<(), &'static str> {
|
||||
if input.len() % 192 != 0 {
|
||||
return Err("Invalid input length, must be multiple of 192 (3 * (32*2))".into())
|
||||
return Err("Invalid input length, must be multiple of 192 (3 * (32*2))".into());
|
||||
}
|
||||
|
||||
if let Err(err) = self.execute_with_error(input, output) {
|
||||
trace!(target: "builtin", "Pairing error: {:?}", err);
|
||||
return Err(err)
|
||||
return Err(err);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -682,7 +711,7 @@ impl Implementation for Bn128Pairing {
|
||||
|
||||
impl Bn128Pairing {
|
||||
fn execute_with_error(&self, input: &[u8], output: &mut BytesRef) -> Result<(), &'static str> {
|
||||
use bn::{AffineG1, AffineG2, Fq, Fq2, pairing, G1, G2, Gt, Group};
|
||||
use bn::{pairing, AffineG1, AffineG2, Fq, Fq2, Group, Gt, G1, G2};
|
||||
|
||||
let ret_val = if input.is_empty() {
|
||||
U256::one()
|
||||
@ -691,22 +720,22 @@ impl Bn128Pairing {
|
||||
let elements = input.len() / 192;
|
||||
let mut vals = Vec::new();
|
||||
for idx in 0..elements {
|
||||
let a_x = Fq::from_slice(&input[idx*192..idx*192+32])
|
||||
let a_x = Fq::from_slice(&input[idx * 192..idx * 192 + 32])
|
||||
.map_err(|_| "Invalid a argument x coordinate")?;
|
||||
|
||||
let a_y = Fq::from_slice(&input[idx*192+32..idx*192+64])
|
||||
let a_y = Fq::from_slice(&input[idx * 192 + 32..idx * 192 + 64])
|
||||
.map_err(|_| "Invalid a argument y coordinate")?;
|
||||
|
||||
let b_a_y = Fq::from_slice(&input[idx*192+64..idx*192+96])
|
||||
let b_a_y = Fq::from_slice(&input[idx * 192 + 64..idx * 192 + 96])
|
||||
.map_err(|_| "Invalid b argument imaginary coeff x coordinate")?;
|
||||
|
||||
let b_a_x = Fq::from_slice(&input[idx*192+96..idx*192+128])
|
||||
let b_a_x = Fq::from_slice(&input[idx * 192 + 96..idx * 192 + 128])
|
||||
.map_err(|_| "Invalid b argument imaginary coeff y coordinate")?;
|
||||
|
||||
let b_b_y = Fq::from_slice(&input[idx*192+128..idx*192+160])
|
||||
let b_b_y = Fq::from_slice(&input[idx * 192 + 128..idx * 192 + 160])
|
||||
.map_err(|_| "Invalid b argument real coeff x coordinate")?;
|
||||
|
||||
let b_b_x = Fq::from_slice(&input[idx*192+160..idx*192+192])
|
||||
let b_b_x = Fq::from_slice(&input[idx * 192 + 160..idx * 192 + 192])
|
||||
.map_err(|_| "Invalid b argument real coeff y coordinate")?;
|
||||
|
||||
let b_a = Fq2::new(b_a_x, b_a_y);
|
||||
@ -714,17 +743,23 @@ impl Bn128Pairing {
|
||||
let b = if b_a.is_zero() && b_b.is_zero() {
|
||||
G2::zero()
|
||||
} else {
|
||||
G2::from(AffineG2::new(b_a, b_b).map_err(|_| "Invalid b argument - not on curve")?)
|
||||
G2::from(
|
||||
AffineG2::new(b_a, b_b).map_err(|_| "Invalid b argument - not on curve")?,
|
||||
)
|
||||
};
|
||||
let a = if a_x.is_zero() && a_y.is_zero() {
|
||||
G1::zero()
|
||||
} else {
|
||||
G1::from(AffineG1::new(a_x, a_y).map_err(|_| "Invalid a argument - not on curve")?)
|
||||
G1::from(
|
||||
AffineG1::new(a_x, a_y).map_err(|_| "Invalid a argument - not on curve")?,
|
||||
)
|
||||
};
|
||||
vals.push((a, b));
|
||||
};
|
||||
}
|
||||
|
||||
let mul = vals.into_iter().fold(Gt::one(), |s, (a, b)| s * pairing(a, b));
|
||||
let mul = vals
|
||||
.into_iter()
|
||||
.fold(Gt::one(), |s, (a, b)| s * pairing(a, b));
|
||||
|
||||
if mul == Gt::one() {
|
||||
U256::one()
|
||||
@ -743,20 +778,20 @@ impl Bn128Pairing {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::convert::TryFrom;
|
||||
use super::{
|
||||
modexp as me, BTreeMap, Builtin, EthereumBuiltin, FromStr, Implementation, Linear,
|
||||
ModexpPricer, Pricing,
|
||||
};
|
||||
use ethereum_types::U256;
|
||||
use ethjson::spec::builtin::{
|
||||
Builtin as JsonBuiltin, Linear as JsonLinearPricing,
|
||||
PricingAt, AltBn128Pairing as JsonAltBn128PairingPricing, Pricing as JsonPricing,
|
||||
AltBn128Pairing as JsonAltBn128PairingPricing, Builtin as JsonBuiltin,
|
||||
Linear as JsonLinearPricing, Pricing as JsonPricing, PricingAt,
|
||||
};
|
||||
use hex_literal::hex;
|
||||
use macros::map;
|
||||
use num::{BigUint, Zero, One};
|
||||
use num::{BigUint, One, Zero};
|
||||
use parity_bytes::BytesRef;
|
||||
use super::{
|
||||
BTreeMap, Builtin, EthereumBuiltin, FromStr, Implementation, Linear,
|
||||
ModexpPricer, modexp as me, Pricing
|
||||
};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
#[test]
|
||||
fn blake2f_cost() {
|
||||
@ -767,9 +802,10 @@ mod tests {
|
||||
// 5 rounds
|
||||
let input = hex!("0000000548c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001");
|
||||
let mut output = [0u8; 64];
|
||||
f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..])).unwrap();
|
||||
f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..]))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(f.cost(&input[..], 0), U256::from(123*5));
|
||||
assert_eq!(f.cost(&input[..], 0), U256::from(123 * 5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -793,7 +829,10 @@ mod tests {
|
||||
|
||||
let result = blake2.execute(&input[..], &mut BytesRef::Fixed(&mut out[..]));
|
||||
assert!(result.is_err());
|
||||
assert_eq!(result.unwrap_err(), "input length for Blake2 F precompile should be exactly 213 bytes");
|
||||
assert_eq!(
|
||||
result.unwrap_err(),
|
||||
"input length for Blake2 F precompile should be exactly 213 bytes"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -805,7 +844,10 @@ mod tests {
|
||||
|
||||
let result = blake2.execute(&input[..], &mut BytesRef::Fixed(&mut out[..]));
|
||||
assert!(result.is_err());
|
||||
assert_eq!(result.unwrap_err(), "input length for Blake2 F precompile should be exactly 213 bytes");
|
||||
assert_eq!(
|
||||
result.unwrap_err(),
|
||||
"input length for Blake2 F precompile should be exactly 213 bytes"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -827,7 +869,9 @@ mod tests {
|
||||
let input = hex!("0000000048c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001");
|
||||
let expected = hex!("08c9bcf367e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d282e6ad7f520e511f6c3e2b8c68059b9442be0454267ce079217e1319cde05b");
|
||||
let mut output = [0u8; 64];
|
||||
blake2.execute(&input[..], &mut BytesRef::Fixed(&mut output[..])).unwrap();
|
||||
blake2
|
||||
.execute(&input[..], &mut BytesRef::Fixed(&mut output[..]))
|
||||
.unwrap();
|
||||
assert_eq!(&output[..], &expected[..]);
|
||||
}
|
||||
|
||||
@ -838,7 +882,9 @@ mod tests {
|
||||
let input = hex!("0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001");
|
||||
let expected = hex!("ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923");
|
||||
let mut out = [0u8; 64];
|
||||
blake2.execute(&input[..], &mut BytesRef::Fixed(&mut out[..])).unwrap();
|
||||
blake2
|
||||
.execute(&input[..], &mut BytesRef::Fixed(&mut out[..]))
|
||||
.unwrap();
|
||||
assert_eq!(&out[..], &expected[..]);
|
||||
}
|
||||
|
||||
@ -849,7 +895,9 @@ mod tests {
|
||||
let input = hex!("0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000");
|
||||
let expected = hex!("75ab69d3190a562c51aef8d88f1c2775876944407270c42c9844252c26d2875298743e7f6d5ea2f2d3e8d226039cd31b4e426ac4f2d3d666a610c2116fde4735");
|
||||
let mut out = [0u8; 64];
|
||||
blake2.execute(&input[..], &mut BytesRef::Fixed(&mut out[..])).unwrap();
|
||||
blake2
|
||||
.execute(&input[..], &mut BytesRef::Fixed(&mut out[..]))
|
||||
.unwrap();
|
||||
assert_eq!(&out[..], &expected[..]);
|
||||
}
|
||||
|
||||
@ -860,7 +908,9 @@ mod tests {
|
||||
let input = hex!("0000000148c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001");
|
||||
let expected = hex!("b63a380cb2897d521994a85234ee2c181b5f844d2c624c002677e9703449d2fba551b3a8333bcdf5f2f7e08993d53923de3d64fcc68c034e717b9293fed7a421");
|
||||
let mut out = [0u8; 64];
|
||||
blake2.execute(&input[..], &mut BytesRef::Fixed(&mut out[..])).unwrap();
|
||||
blake2
|
||||
.execute(&input[..], &mut BytesRef::Fixed(&mut out[..]))
|
||||
.unwrap();
|
||||
assert_eq!(&out[..], &expected[..]);
|
||||
}
|
||||
|
||||
@ -873,7 +923,9 @@ mod tests {
|
||||
let input = hex!("ffffffff48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001");
|
||||
let expected = hex!("fc59093aafa9ab43daae0e914c57635c5402d8e3d2130eb9b3cc181de7f0ecf9b22bf99a7815ce16419e200e01846e6b5df8cc7703041bbceb571de6631d2615");
|
||||
let mut out = [0u8; 64];
|
||||
blake2.execute(&input[..], &mut BytesRef::Fixed(&mut out[..])).unwrap();
|
||||
blake2
|
||||
.execute(&input[..], &mut BytesRef::Fixed(&mut out[..]))
|
||||
.unwrap();
|
||||
assert_eq!(&out[..], &expected[..]);
|
||||
}
|
||||
|
||||
@ -907,7 +959,10 @@ mod tests {
|
||||
base = BigUint::parse_bytes(b"12345", 10).unwrap();
|
||||
exp = BigUint::parse_bytes(b"789", 10).unwrap();
|
||||
modulus = BigUint::parse_bytes(b"97", 10).unwrap();
|
||||
assert_eq!(me(base, exp.to_bytes_be(), modulus), BigUint::parse_bytes(b"55", 10).unwrap());
|
||||
assert_eq!(
|
||||
me(base, exp.to_bytes_be(), modulus),
|
||||
BigUint::parse_bytes(b"55", 10).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -916,15 +971,18 @@ mod tests {
|
||||
let i = [0u8, 1, 2, 3];
|
||||
|
||||
let mut o2 = [255u8; 2];
|
||||
f.execute(&i[..], &mut BytesRef::Fixed(&mut o2[..])).expect("Builtin should not fail");
|
||||
f.execute(&i[..], &mut BytesRef::Fixed(&mut o2[..]))
|
||||
.expect("Builtin should not fail");
|
||||
assert_eq!(i[0..2], o2);
|
||||
|
||||
let mut o4 = [255u8; 4];
|
||||
f.execute(&i[..], &mut BytesRef::Fixed(&mut o4[..])).expect("Builtin should not fail");
|
||||
f.execute(&i[..], &mut BytesRef::Fixed(&mut o4[..]))
|
||||
.expect("Builtin should not fail");
|
||||
assert_eq!(i, o4);
|
||||
|
||||
let mut o8 = [255u8; 8];
|
||||
f.execute(&i[..], &mut BytesRef::Fixed(&mut o8[..])).expect("Builtin should not fail");
|
||||
f.execute(&i[..], &mut BytesRef::Fixed(&mut o8[..]))
|
||||
.expect("Builtin should not fail");
|
||||
assert_eq!(i, o8[..4]);
|
||||
assert_eq!([255u8; 4], o8[4..]);
|
||||
}
|
||||
@ -935,20 +993,33 @@ mod tests {
|
||||
let i = [0u8; 0];
|
||||
|
||||
let mut o = [255u8; 32];
|
||||
f.execute(&i[..], &mut BytesRef::Fixed(&mut o[..])).expect("Builtin should not fail");
|
||||
assert_eq!(&o[..], hex!("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"));
|
||||
f.execute(&i[..], &mut BytesRef::Fixed(&mut o[..]))
|
||||
.expect("Builtin should not fail");
|
||||
assert_eq!(
|
||||
&o[..],
|
||||
hex!("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
|
||||
);
|
||||
|
||||
let mut o8 = [255u8; 8];
|
||||
f.execute(&i[..], &mut BytesRef::Fixed(&mut o8[..])).expect("Builtin should not fail");
|
||||
f.execute(&i[..], &mut BytesRef::Fixed(&mut o8[..]))
|
||||
.expect("Builtin should not fail");
|
||||
assert_eq!(&o8[..], hex!("e3b0c44298fc1c14"));
|
||||
|
||||
let mut o34 = [255u8; 34];
|
||||
f.execute(&i[..], &mut BytesRef::Fixed(&mut o34[..])).expect("Builtin should not fail");
|
||||
assert_eq!(&o34[..], &hex!("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855ffff")[..]);
|
||||
f.execute(&i[..], &mut BytesRef::Fixed(&mut o34[..]))
|
||||
.expect("Builtin should not fail");
|
||||
assert_eq!(
|
||||
&o34[..],
|
||||
&hex!("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855ffff")[..]
|
||||
);
|
||||
|
||||
let mut ov = vec![];
|
||||
f.execute(&i[..], &mut BytesRef::Flexible(&mut ov)).expect("Builtin should not fail");
|
||||
assert_eq!(&ov[..], &hex!("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")[..]);
|
||||
f.execute(&i[..], &mut BytesRef::Flexible(&mut ov))
|
||||
.expect("Builtin should not fail");
|
||||
assert_eq!(
|
||||
&ov[..],
|
||||
&hex!("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")[..]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -957,16 +1028,25 @@ mod tests {
|
||||
let i = [0u8; 0];
|
||||
|
||||
let mut o = [255u8; 32];
|
||||
f.execute(&i[..], &mut BytesRef::Fixed(&mut o[..])).expect("Builtin should not fail");
|
||||
assert_eq!(&o[..], &hex!("0000000000000000000000009c1185a5c5e9fc54612808977ee8f548b2258d31")[..]);
|
||||
f.execute(&i[..], &mut BytesRef::Fixed(&mut o[..]))
|
||||
.expect("Builtin should not fail");
|
||||
assert_eq!(
|
||||
&o[..],
|
||||
&hex!("0000000000000000000000009c1185a5c5e9fc54612808977ee8f548b2258d31")[..]
|
||||
);
|
||||
|
||||
let mut o8 = [255u8; 8];
|
||||
f.execute(&i[..], &mut BytesRef::Fixed(&mut o8[..])).expect("Builtin should not fail");
|
||||
f.execute(&i[..], &mut BytesRef::Fixed(&mut o8[..]))
|
||||
.expect("Builtin should not fail");
|
||||
assert_eq!(&o8[..], &hex!("0000000000000000")[..]);
|
||||
|
||||
let mut o34 = [255u8; 34];
|
||||
f.execute(&i[..], &mut BytesRef::Fixed(&mut o34[..])).expect("Builtin should not fail");
|
||||
assert_eq!(&o34[..], &hex!("0000000000000000000000009c1185a5c5e9fc54612808977ee8f548b2258d31ffff")[..]);
|
||||
f.execute(&i[..], &mut BytesRef::Fixed(&mut o34[..]))
|
||||
.expect("Builtin should not fail");
|
||||
assert_eq!(
|
||||
&o34[..],
|
||||
&hex!("0000000000000000000000009c1185a5c5e9fc54612808977ee8f548b2258d31ffff")[..]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -976,41 +1056,70 @@ mod tests {
|
||||
let i = hex!("47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad000000000000000000000000000000000000000000000000000000000000001b650acf9d3f5f0a2c799776a1254355d5f4061762a237396a99a0e0e3fc2bcd6729514a0dacb2e623ac4abd157cb18163ff942280db4d5caad66ddf941ba12e03");
|
||||
|
||||
let mut o = [255u8; 32];
|
||||
f.execute(&i[..], &mut BytesRef::Fixed(&mut o[..])).expect("Builtin should not fail");
|
||||
assert_eq!(&o[..], &hex!("000000000000000000000000c08b5542d177ac6686946920409741463a15dddb")[..]);
|
||||
f.execute(&i[..], &mut BytesRef::Fixed(&mut o[..]))
|
||||
.expect("Builtin should not fail");
|
||||
assert_eq!(
|
||||
&o[..],
|
||||
&hex!("000000000000000000000000c08b5542d177ac6686946920409741463a15dddb")[..]
|
||||
);
|
||||
|
||||
let mut o8 = [255u8; 8];
|
||||
f.execute(&i[..], &mut BytesRef::Fixed(&mut o8[..])).expect("Builtin should not fail");
|
||||
f.execute(&i[..], &mut BytesRef::Fixed(&mut o8[..]))
|
||||
.expect("Builtin should not fail");
|
||||
assert_eq!(&o8[..], &hex!("0000000000000000")[..]);
|
||||
|
||||
let mut o34 = [255u8; 34];
|
||||
f.execute(&i[..], &mut BytesRef::Fixed(&mut o34[..])).expect("Builtin should not fail");
|
||||
assert_eq!(&o34[..], &hex!("000000000000000000000000c08b5542d177ac6686946920409741463a15dddbffff")[..]);
|
||||
f.execute(&i[..], &mut BytesRef::Fixed(&mut o34[..]))
|
||||
.expect("Builtin should not fail");
|
||||
assert_eq!(
|
||||
&o34[..],
|
||||
&hex!("000000000000000000000000c08b5542d177ac6686946920409741463a15dddbffff")[..]
|
||||
);
|
||||
|
||||
let i_bad = hex!("47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad000000000000000000000000000000000000000000000000000000000000001a650acf9d3f5f0a2c799776a1254355d5f4061762a237396a99a0e0e3fc2bcd6729514a0dacb2e623ac4abd157cb18163ff942280db4d5caad66ddf941ba12e03");
|
||||
let mut o = [255u8; 32];
|
||||
f.execute(&i_bad[..], &mut BytesRef::Fixed(&mut o[..])).expect("Builtin should not fail");
|
||||
assert_eq!(&o[..], &hex!("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")[..]);
|
||||
f.execute(&i_bad[..], &mut BytesRef::Fixed(&mut o[..]))
|
||||
.expect("Builtin should not fail");
|
||||
assert_eq!(
|
||||
&o[..],
|
||||
&hex!("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")[..]
|
||||
);
|
||||
|
||||
let i_bad = hex!("47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad000000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000000000001b0000000000000000000000000000000000000000000000000000000000000000");
|
||||
let mut o = [255u8; 32];
|
||||
f.execute(&i_bad[..], &mut BytesRef::Fixed(&mut o[..])).expect("Builtin should not fail");
|
||||
assert_eq!(&o[..], &hex!("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")[..]);
|
||||
f.execute(&i_bad[..], &mut BytesRef::Fixed(&mut o[..]))
|
||||
.expect("Builtin should not fail");
|
||||
assert_eq!(
|
||||
&o[..],
|
||||
&hex!("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")[..]
|
||||
);
|
||||
|
||||
let i_bad = hex!("47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad000000000000000000000000000000000000000000000000000000000000001b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b");
|
||||
let mut o = [255u8; 32];
|
||||
f.execute(&i_bad[..], &mut BytesRef::Fixed(&mut o[..])).expect("Builtin should not fail");
|
||||
assert_eq!(&o[..], &hex!("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")[..]);
|
||||
f.execute(&i_bad[..], &mut BytesRef::Fixed(&mut o[..]))
|
||||
.expect("Builtin should not fail");
|
||||
assert_eq!(
|
||||
&o[..],
|
||||
&hex!("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")[..]
|
||||
);
|
||||
|
||||
let i_bad = hex!("47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad000000000000000000000000000000000000000000000000000000000000001bffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000001b");
|
||||
let mut o = [255u8; 32];
|
||||
f.execute(&i_bad[..], &mut BytesRef::Fixed(&mut o[..])).expect("Builtin should not fail");
|
||||
assert_eq!(&o[..], &hex!("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")[..]);
|
||||
f.execute(&i_bad[..], &mut BytesRef::Fixed(&mut o[..]))
|
||||
.expect("Builtin should not fail");
|
||||
assert_eq!(
|
||||
&o[..],
|
||||
&hex!("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")[..]
|
||||
);
|
||||
|
||||
let i_bad = hex!("47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad000000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000000000001bffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
|
||||
let mut o = [255u8; 32];
|
||||
f.execute(&i_bad[..], &mut BytesRef::Fixed(&mut o[..])).expect("Builtin should not fail");
|
||||
assert_eq!(&o[..], &hex!("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")[..]);
|
||||
f.execute(&i_bad[..], &mut BytesRef::Fixed(&mut o[..]))
|
||||
.expect("Builtin should not fail");
|
||||
assert_eq!(
|
||||
&o[..],
|
||||
&hex!("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")[..]
|
||||
);
|
||||
|
||||
// TODO: Should this (corrupted version of the above) fail rather than returning some address?
|
||||
/* let i_bad = FromHex::from_hex("48173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad000000000000000000000000000000000000000000000000000000000000001b650acf9d3f5f0a2c799776a1254355d5f4061762a237396a99a0e0e3fc2bcd6729514a0dacb2e623ac4abd157cb18163ff942280db4d5caad66ddf941ba12e03").unwrap();
|
||||
@ -1035,7 +1144,8 @@ mod tests {
|
||||
|
||||
// test for potential exp len overflow
|
||||
{
|
||||
let input = hex!("
|
||||
let input = hex!(
|
||||
"
|
||||
00000000000000000000000000000000000000000000000000000000000000ff
|
||||
2a1e530000000000000000000000000000000000000000000000000000000000
|
||||
0000000000000000000000000000000000000000000000000000000000000000"
|
||||
@ -1045,14 +1155,16 @@ mod tests {
|
||||
let expected = hex!("0000000000000000000000000000000000000000000000000000000000000000");
|
||||
let expected_cost = U256::max_value();
|
||||
|
||||
f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..])).expect("Builtin should fail");
|
||||
f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..]))
|
||||
.expect("Builtin should fail");
|
||||
assert_eq!(output, expected);
|
||||
assert_eq!(f.cost(&input[..], 0), expected_cost);
|
||||
}
|
||||
|
||||
// fermat's little theorem example.
|
||||
{
|
||||
let input = hex!("
|
||||
let input = hex!(
|
||||
"
|
||||
0000000000000000000000000000000000000000000000000000000000000001
|
||||
0000000000000000000000000000000000000000000000000000000000000020
|
||||
0000000000000000000000000000000000000000000000000000000000000020
|
||||
@ -1065,14 +1177,16 @@ mod tests {
|
||||
let expected = hex!("0000000000000000000000000000000000000000000000000000000000000001");
|
||||
let expected_cost = 13056;
|
||||
|
||||
f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..])).expect("Builtin should not fail");
|
||||
f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..]))
|
||||
.expect("Builtin should not fail");
|
||||
assert_eq!(output, expected);
|
||||
assert_eq!(f.cost(&input[..], 0), expected_cost.into());
|
||||
}
|
||||
|
||||
// second example from EIP: zero base.
|
||||
{
|
||||
let input = hex!("
|
||||
let input = hex!(
|
||||
"
|
||||
0000000000000000000000000000000000000000000000000000000000000000
|
||||
0000000000000000000000000000000000000000000000000000000000000020
|
||||
0000000000000000000000000000000000000000000000000000000000000020
|
||||
@ -1084,14 +1198,16 @@ mod tests {
|
||||
let expected = hex!("0000000000000000000000000000000000000000000000000000000000000000");
|
||||
let expected_cost = 13056;
|
||||
|
||||
f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..])).expect("Builtin should not fail");
|
||||
f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..]))
|
||||
.expect("Builtin should not fail");
|
||||
assert_eq!(output, expected);
|
||||
assert_eq!(f.cost(&input[..], 0), expected_cost.into());
|
||||
}
|
||||
|
||||
// another example from EIP: zero-padding
|
||||
{
|
||||
let input = hex!("
|
||||
let input = hex!(
|
||||
"
|
||||
0000000000000000000000000000000000000000000000000000000000000001
|
||||
0000000000000000000000000000000000000000000000000000000000000002
|
||||
0000000000000000000000000000000000000000000000000000000000000020
|
||||
@ -1104,14 +1220,16 @@ mod tests {
|
||||
let expected = hex!("3b01b01ac41f2d6e917c6d6a221ce793802469026d9ab7578fa2e79e4da6aaab");
|
||||
let expected_cost = 768;
|
||||
|
||||
f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..])).expect("Builtin should not fail");
|
||||
f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..]))
|
||||
.expect("Builtin should not fail");
|
||||
assert_eq!(output, expected);
|
||||
assert_eq!(f.cost(&input[..], 0), expected_cost.into());
|
||||
}
|
||||
|
||||
// zero-length modulus.
|
||||
{
|
||||
let input = hex!("
|
||||
let input = hex!(
|
||||
"
|
||||
0000000000000000000000000000000000000000000000000000000000000001
|
||||
0000000000000000000000000000000000000000000000000000000000000002
|
||||
0000000000000000000000000000000000000000000000000000000000000000
|
||||
@ -1122,7 +1240,8 @@ mod tests {
|
||||
let mut output = vec![];
|
||||
let expected_cost = 0;
|
||||
|
||||
f.execute(&input[..], &mut BytesRef::Flexible(&mut output)).expect("Builtin should not fail");
|
||||
f.execute(&input[..], &mut BytesRef::Flexible(&mut output))
|
||||
.expect("Builtin should not fail");
|
||||
assert_eq!(output.len(), 0); // shouldn't have written any output.
|
||||
assert_eq!(f.cost(&input[..], 0), expected_cost.into());
|
||||
}
|
||||
@ -1130,7 +1249,6 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn bn128_add() {
|
||||
|
||||
let f = Builtin {
|
||||
pricer: map![0 => Pricing::Linear(Linear { base: 0, word: 0 })],
|
||||
native: EthereumBuiltin::from_str("alt_bn128_add").unwrap(),
|
||||
@ -1138,7 +1256,8 @@ mod tests {
|
||||
|
||||
// zero-points additions
|
||||
{
|
||||
let input = hex!("
|
||||
let input = hex!(
|
||||
"
|
||||
0000000000000000000000000000000000000000000000000000000000000000
|
||||
0000000000000000000000000000000000000000000000000000000000000000
|
||||
0000000000000000000000000000000000000000000000000000000000000000
|
||||
@ -1146,12 +1265,14 @@ mod tests {
|
||||
);
|
||||
|
||||
let mut output = vec![0u8; 64];
|
||||
let expected = hex!("
|
||||
let expected = hex!(
|
||||
"
|
||||
0000000000000000000000000000000000000000000000000000000000000000
|
||||
0000000000000000000000000000000000000000000000000000000000000000"
|
||||
);
|
||||
|
||||
f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..])).expect("Builtin should not fail");
|
||||
f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..]))
|
||||
.expect("Builtin should not fail");
|
||||
assert_eq!(output, &expected[..]);
|
||||
}
|
||||
|
||||
@ -1161,18 +1282,21 @@ mod tests {
|
||||
let input = BytesRef::Fixed(&mut empty);
|
||||
|
||||
let mut output = vec![0u8; 64];
|
||||
let expected = hex!("
|
||||
let expected = hex!(
|
||||
"
|
||||
0000000000000000000000000000000000000000000000000000000000000000
|
||||
0000000000000000000000000000000000000000000000000000000000000000"
|
||||
);
|
||||
|
||||
f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..])).expect("Builtin should not fail");
|
||||
f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..]))
|
||||
.expect("Builtin should not fail");
|
||||
assert_eq!(output, &expected[..]);
|
||||
}
|
||||
|
||||
// should fail - point not on curve
|
||||
{
|
||||
let input = hex!("
|
||||
let input = hex!(
|
||||
"
|
||||
1111111111111111111111111111111111111111111111111111111111111111
|
||||
1111111111111111111111111111111111111111111111111111111111111111
|
||||
1111111111111111111111111111111111111111111111111111111111111111
|
||||
@ -1195,25 +1319,29 @@ mod tests {
|
||||
|
||||
// zero-point multiplication
|
||||
{
|
||||
let input = hex!("
|
||||
let input = hex!(
|
||||
"
|
||||
0000000000000000000000000000000000000000000000000000000000000000
|
||||
0000000000000000000000000000000000000000000000000000000000000000
|
||||
0200000000000000000000000000000000000000000000000000000000000000"
|
||||
);
|
||||
|
||||
let mut output = vec![0u8; 64];
|
||||
let expected = hex!("
|
||||
let expected = hex!(
|
||||
"
|
||||
0000000000000000000000000000000000000000000000000000000000000000
|
||||
0000000000000000000000000000000000000000000000000000000000000000"
|
||||
);
|
||||
|
||||
f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..])).expect("Builtin should not fail");
|
||||
f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..]))
|
||||
.expect("Builtin should not fail");
|
||||
assert_eq!(output, &expected[..]);
|
||||
}
|
||||
|
||||
// should fail - point not on curve
|
||||
{
|
||||
let input = hex!("
|
||||
let input = hex!(
|
||||
"
|
||||
1111111111111111111111111111111111111111111111111111111111111111
|
||||
1111111111111111111111111111111111111111111111111111111111111111
|
||||
0f00000000000000000000000000000000000000000000000000000000000000"
|
||||
@ -1239,7 +1367,8 @@ mod tests {
|
||||
|
||||
let mut output = vec![0u8; expected.len()];
|
||||
|
||||
f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..])).expect("Builtin should not fail");
|
||||
f.execute(&input[..], &mut BytesRef::Fixed(&mut output[..]))
|
||||
.expect("Builtin should not fail");
|
||||
assert_eq!(output, expected);
|
||||
}
|
||||
|
||||
@ -1249,7 +1378,10 @@ mod tests {
|
||||
if let Some(msg) = msg_contains {
|
||||
if let Err(e) = res {
|
||||
if !e.contains(msg) {
|
||||
panic!("There should be error containing '{}' here, but got: '{}'", msg, e);
|
||||
panic!(
|
||||
"There should be error containing '{}' here, but got: '{}'",
|
||||
msg, e
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -1271,7 +1403,8 @@ mod tests {
|
||||
// should fail - point not on curve
|
||||
error_test(
|
||||
builtin_pairing(),
|
||||
&hex!("
|
||||
&hex!(
|
||||
"
|
||||
1111111111111111111111111111111111111111111111111111111111111111
|
||||
1111111111111111111111111111111111111111111111111111111111111111
|
||||
1111111111111111111111111111111111111111111111111111111111111111
|
||||
@ -1288,7 +1421,8 @@ mod tests {
|
||||
// should fail - input length is invalid
|
||||
error_test(
|
||||
builtin_pairing(),
|
||||
&hex!("
|
||||
&hex!(
|
||||
"
|
||||
1111111111111111111111111111111111111111111111111111111111111111
|
||||
1111111111111111111111111111111111111111111111111111111111111111
|
||||
111111111111111111111111111111"
|
||||
@ -1331,7 +1465,8 @@ mod tests {
|
||||
|
||||
let i = [0u8, 1, 2, 3];
|
||||
let mut o = [255u8; 4];
|
||||
b.execute(&i[..], &mut BytesRef::Fixed(&mut o[..])).expect("Builtin should not fail");
|
||||
b.execute(&i[..], &mut BytesRef::Fixed(&mut o[..]))
|
||||
.expect("Builtin should not fail");
|
||||
assert_eq!(i, o);
|
||||
}
|
||||
|
||||
@ -1344,8 +1479,9 @@ mod tests {
|
||||
info: None,
|
||||
price: JsonPricing::Linear(JsonLinearPricing { base: 10, word: 20 })
|
||||
}
|
||||
]
|
||||
}).expect("known builtin");
|
||||
],
|
||||
})
|
||||
.expect("known builtin");
|
||||
|
||||
assert_eq!(b.cost(&[0; 0], 0), U256::from(10));
|
||||
assert_eq!(b.cost(&[0; 1], 0), U256::from(30));
|
||||
@ -1354,7 +1490,8 @@ mod tests {
|
||||
|
||||
let i = [0u8, 1, 2, 3];
|
||||
let mut o = [255u8; 4];
|
||||
b.execute(&i[..], &mut BytesRef::Fixed(&mut o[..])).expect("Builtin should not fail");
|
||||
b.execute(&i[..], &mut BytesRef::Fixed(&mut o[..]))
|
||||
.expect("Builtin should not fail");
|
||||
assert_eq!(i, o);
|
||||
}
|
||||
|
||||
@ -1378,10 +1515,19 @@ mod tests {
|
||||
}),
|
||||
}
|
||||
],
|
||||
}).unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(b.cost(&[0; 192 * 3], 10), U256::from(340_000), "80 000 * 3 + 100 000 == 340 000");
|
||||
assert_eq!(b.cost(&[0; 192 * 7], 20), U256::from(283_000), "34 000 * 7 + 45 000 == 283 000");
|
||||
assert_eq!(
|
||||
b.cost(&[0; 192 * 3], 10),
|
||||
U256::from(340_000),
|
||||
"80 000 * 3 + 100 000 == 340 000"
|
||||
);
|
||||
assert_eq!(
|
||||
b.cost(&[0; 192 * 7], 20),
|
||||
U256::from(283_000),
|
||||
"34 000 * 7 + 45 000 == 283 000"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1404,10 +1550,15 @@ mod tests {
|
||||
}),
|
||||
}
|
||||
],
|
||||
}).unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(b.cost(&[0; 192], 10), U256::from(500));
|
||||
assert_eq!(b.cost(&[0; 10], 20), U256::from(150), "after istanbul hardfork gas cost for add should be 150");
|
||||
assert_eq!(
|
||||
b.cost(&[0; 10], 20),
|
||||
U256::from(150),
|
||||
"after istanbul hardfork gas cost for add should be 150"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1430,13 +1581,17 @@ mod tests {
|
||||
}),
|
||||
}
|
||||
],
|
||||
}).unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(b.cost(&[0; 192], 10), U256::from(40_000));
|
||||
assert_eq!(b.cost(&[0; 10], 20), U256::from(6_000), "after istanbul hardfork gas cost for mul should be 6 000");
|
||||
assert_eq!(
|
||||
b.cost(&[0; 10], 20),
|
||||
U256::from(6_000),
|
||||
"after istanbul hardfork gas cost for mul should be 6 000"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn multimap_use_most_recent_on_activate() {
|
||||
let b = Builtin::try_from(JsonBuiltin {
|
||||
@ -1463,18 +1618,26 @@ mod tests {
|
||||
word: 0,
|
||||
})
|
||||
}
|
||||
]
|
||||
}).unwrap();
|
||||
],
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(b.cost(&[0; 2], 0), U256::zero(), "not activated yet; should be zero");
|
||||
assert_eq!(
|
||||
b.cost(&[0; 2], 0),
|
||||
U256::zero(),
|
||||
"not activated yet; should be zero"
|
||||
);
|
||||
assert_eq!(b.cost(&[0; 3], 10), U256::from(40_000), "use price #1");
|
||||
assert_eq!(b.cost(&[0; 4], 20), U256::from(6_000), "use price #2");
|
||||
assert_eq!(b.cost(&[0; 1], 99), U256::from(6_000), "use price #2");
|
||||
assert_eq!(b.cost(&[0; 1], 100), U256::from(1_337), "use price #3");
|
||||
assert_eq!(b.cost(&[0; 1], u64::max_value()), U256::from(1_337), "use price #3 indefinitely");
|
||||
assert_eq!(
|
||||
b.cost(&[0; 1], u64::max_value()),
|
||||
U256::from(1_337),
|
||||
"use price #3 indefinitely"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn multimap_use_last_with_same_activate_at() {
|
||||
let b = Builtin::try_from(JsonBuiltin {
|
||||
@ -1502,7 +1665,8 @@ mod tests {
|
||||
}),
|
||||
}
|
||||
],
|
||||
}).unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(b.cost(&[0; 1], 0), U256::from(0), "not activated yet");
|
||||
assert_eq!(b.cost(&[0; 1], 1), U256::from(1_337));
|
||||
|
@ -16,8 +16,10 @@
|
||||
|
||||
//! Database cache manager
|
||||
|
||||
use std::collections::{VecDeque, HashSet};
|
||||
use std::hash::Hash;
|
||||
use std::{
|
||||
collections::{HashSet, VecDeque},
|
||||
hash::Hash,
|
||||
};
|
||||
|
||||
const COLLECTION_QUEUE_SIZE: usize = 8;
|
||||
|
||||
@ -26,24 +28,39 @@ pub struct CacheManager<T> {
|
||||
pref_cache_size: usize,
|
||||
max_cache_size: usize,
|
||||
bytes_per_cache_entry: usize,
|
||||
cache_usage: VecDeque<HashSet<T>>
|
||||
cache_usage: VecDeque<HashSet<T>>,
|
||||
}
|
||||
|
||||
impl<T> CacheManager<T> where T: Eq + Hash {
|
||||
impl<T> CacheManager<T>
|
||||
where
|
||||
T: Eq + Hash,
|
||||
{
|
||||
/// Create new cache manager with preferred (heap) sizes.
|
||||
pub fn new(pref_cache_size: usize, max_cache_size: usize, bytes_per_cache_entry: usize) -> Self {
|
||||
pub fn new(
|
||||
pref_cache_size: usize,
|
||||
max_cache_size: usize,
|
||||
bytes_per_cache_entry: usize,
|
||||
) -> Self {
|
||||
CacheManager {
|
||||
pref_cache_size: pref_cache_size,
|
||||
max_cache_size: max_cache_size,
|
||||
bytes_per_cache_entry: bytes_per_cache_entry,
|
||||
cache_usage: (0..COLLECTION_QUEUE_SIZE).into_iter().map(|_| Default::default()).collect(),
|
||||
cache_usage: (0..COLLECTION_QUEUE_SIZE)
|
||||
.into_iter()
|
||||
.map(|_| Default::default())
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark element as used.
|
||||
pub fn note_used(&mut self, id: T) {
|
||||
if !self.cache_usage[0].contains(&id) {
|
||||
if let Some(c) = self.cache_usage.iter_mut().skip(1).find(|e| e.contains(&id)) {
|
||||
if let Some(c) = self
|
||||
.cache_usage
|
||||
.iter_mut()
|
||||
.skip(1)
|
||||
.find(|e| e.contains(&id))
|
||||
{
|
||||
c.remove(&id);
|
||||
}
|
||||
self.cache_usage[0].insert(id);
|
||||
@ -53,7 +70,10 @@ impl<T> CacheManager<T> where T: Eq + Hash {
|
||||
/// Collects unused objects from cache.
|
||||
/// First params is the current size of the cache.
|
||||
/// Second one is an with objects to remove. It should also return new size of the cache.
|
||||
pub fn collect_garbage<F>(&mut self, current_size: usize, mut notify_unused: F) where F: FnMut(HashSet<T>) -> usize {
|
||||
pub fn collect_garbage<F>(&mut self, current_size: usize, mut notify_unused: F)
|
||||
where
|
||||
F: FnMut(HashSet<T>) -> usize,
|
||||
{
|
||||
if current_size < self.pref_cache_size {
|
||||
self.rotate_cache_if_needed();
|
||||
return;
|
||||
@ -64,16 +84,20 @@ impl<T> CacheManager<T> where T: Eq + Hash {
|
||||
let current_size = notify_unused(back);
|
||||
self.cache_usage.push_front(Default::default());
|
||||
if current_size < self.max_cache_size {
|
||||
break
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn rotate_cache_if_needed(&mut self) {
|
||||
if self.cache_usage.is_empty() { return }
|
||||
if self.cache_usage.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.cache_usage[0].len() * self.bytes_per_cache_entry > self.pref_cache_size / COLLECTION_QUEUE_SIZE {
|
||||
if self.cache_usage[0].len() * self.bytes_per_cache_entry
|
||||
> self.pref_cache_size / COLLECTION_QUEUE_SIZE
|
||||
{
|
||||
if let Some(cache) = self.cache_usage.pop_back() {
|
||||
self.cache_usage.push_front(cache);
|
||||
}
|
||||
|
@ -16,11 +16,9 @@
|
||||
|
||||
//! Database utilities and definitions.
|
||||
|
||||
use std::ops::Deref;
|
||||
use std::hash::Hash;
|
||||
use std::collections::HashMap;
|
||||
use parking_lot::RwLock;
|
||||
use kvdb::{DBTransaction, KeyValueDB};
|
||||
use parking_lot::RwLock;
|
||||
use std::{collections::HashMap, hash::Hash, ops::Deref};
|
||||
|
||||
use rlp;
|
||||
|
||||
@ -65,7 +63,10 @@ pub trait Cache<K, V> {
|
||||
fn get(&self, k: &K) -> Option<&V>;
|
||||
}
|
||||
|
||||
impl<K, V> Cache<K, V> for HashMap<K, V> where K: Hash + Eq {
|
||||
impl<K, V> Cache<K, V> for HashMap<K, V>
|
||||
where
|
||||
K: Hash + Eq,
|
||||
{
|
||||
fn insert(&mut self, k: K, v: V) -> Option<V> {
|
||||
HashMap::insert(self, k, v)
|
||||
}
|
||||
@ -91,21 +92,35 @@ pub trait Key<T> {
|
||||
/// Should be used to write value into database.
|
||||
pub trait Writable {
|
||||
/// Writes the value into the database.
|
||||
fn write<T, R>(&mut self, col: Option<u32>, key: &Key<T, Target = R>, value: &T) where T: rlp::Encodable, R: Deref<Target = [u8]>;
|
||||
fn write<T, R>(&mut self, col: Option<u32>, key: &Key<T, Target = R>, value: &T)
|
||||
where
|
||||
T: rlp::Encodable,
|
||||
R: Deref<Target = [u8]>;
|
||||
|
||||
/// Deletes key from the databse.
|
||||
fn delete<T, R>(&mut self, col: Option<u32>, key: &Key<T, Target = R>) where T: rlp::Encodable, R: Deref<Target = [u8]>;
|
||||
fn delete<T, R>(&mut self, col: Option<u32>, key: &Key<T, Target = R>)
|
||||
where
|
||||
T: rlp::Encodable,
|
||||
R: Deref<Target = [u8]>;
|
||||
|
||||
/// Writes the value into the database and updates the cache.
|
||||
fn write_with_cache<K, T, R>(&mut self, col: Option<u32>, cache: &mut Cache<K, T>, key: K, value: T, policy: CacheUpdatePolicy) where
|
||||
fn write_with_cache<K, T, R>(
|
||||
&mut self,
|
||||
col: Option<u32>,
|
||||
cache: &mut Cache<K, T>,
|
||||
key: K,
|
||||
value: T,
|
||||
policy: CacheUpdatePolicy,
|
||||
) where
|
||||
K: Key<T, Target = R> + Hash + Eq,
|
||||
T: rlp::Encodable,
|
||||
R: Deref<Target = [u8]> {
|
||||
R: Deref<Target = [u8]>,
|
||||
{
|
||||
self.write(col, &key, &value);
|
||||
match policy {
|
||||
CacheUpdatePolicy::Overwrite => {
|
||||
cache.insert(key, value);
|
||||
},
|
||||
}
|
||||
CacheUpdatePolicy::Remove => {
|
||||
cache.remove(&key);
|
||||
}
|
||||
@ -113,31 +128,45 @@ pub trait Writable {
|
||||
}
|
||||
|
||||
/// Writes the values into the database and updates the cache.
|
||||
fn extend_with_cache<K, T, R>(&mut self, col: Option<u32>, cache: &mut Cache<K, T>, values: HashMap<K, T>, policy: CacheUpdatePolicy) where
|
||||
fn extend_with_cache<K, T, R>(
|
||||
&mut self,
|
||||
col: Option<u32>,
|
||||
cache: &mut Cache<K, T>,
|
||||
values: HashMap<K, T>,
|
||||
policy: CacheUpdatePolicy,
|
||||
) where
|
||||
K: Key<T, Target = R> + Hash + Eq,
|
||||
T: rlp::Encodable,
|
||||
R: Deref<Target = [u8]> {
|
||||
R: Deref<Target = [u8]>,
|
||||
{
|
||||
match policy {
|
||||
CacheUpdatePolicy::Overwrite => {
|
||||
for (key, value) in values {
|
||||
self.write(col, &key, &value);
|
||||
cache.insert(key, value);
|
||||
}
|
||||
},
|
||||
}
|
||||
CacheUpdatePolicy::Remove => {
|
||||
for (key, value) in &values {
|
||||
self.write(col, key, value);
|
||||
cache.remove(key);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes and removes the values into the database and updates the cache.
|
||||
fn extend_with_option_cache<K, T, R>(&mut self, col: Option<u32>, cache: &mut Cache<K, Option<T>>, values: HashMap<K, Option<T>>, policy: CacheUpdatePolicy) where
|
||||
fn extend_with_option_cache<K, T, R>(
|
||||
&mut self,
|
||||
col: Option<u32>,
|
||||
cache: &mut Cache<K, Option<T>>,
|
||||
values: HashMap<K, Option<T>>,
|
||||
policy: CacheUpdatePolicy,
|
||||
) where
|
||||
K: Key<T, Target = R> + Hash + Eq,
|
||||
T: rlp::Encodable,
|
||||
R: Deref<Target = [u8]> {
|
||||
R: Deref<Target = [u8]>,
|
||||
{
|
||||
match policy {
|
||||
CacheUpdatePolicy::Overwrite => {
|
||||
for (key, value) in values {
|
||||
@ -147,7 +176,7 @@ pub trait Writable {
|
||||
}
|
||||
cache.insert(key, value);
|
||||
}
|
||||
},
|
||||
}
|
||||
CacheUpdatePolicy::Remove => {
|
||||
for (key, value) in values {
|
||||
match value {
|
||||
@ -156,24 +185,26 @@ pub trait Writable {
|
||||
}
|
||||
cache.remove(&key);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// Should be used to read values from database.
|
||||
pub trait Readable {
|
||||
/// Returns value for given key.
|
||||
fn read<T, R>(&self, col: Option<u32>, key: &Key<T, Target = R>) -> Option<T> where
|
||||
fn read<T, R>(&self, col: Option<u32>, key: &Key<T, Target = R>) -> Option<T>
|
||||
where
|
||||
T: rlp::Decodable,
|
||||
R: Deref<Target = [u8]>;
|
||||
|
||||
/// Returns value for given key either in cache or in database.
|
||||
fn read_with_cache<K, T, C>(&self, col: Option<u32>, cache: &RwLock<C>, key: &K) -> Option<T> where
|
||||
fn read_with_cache<K, T, C>(&self, col: Option<u32>, cache: &RwLock<C>, key: &K) -> Option<T>
|
||||
where
|
||||
K: Key<T> + Eq + Hash + Clone,
|
||||
T: Clone + rlp::Decodable,
|
||||
C: Cache<K, T> {
|
||||
C: Cache<K, T>,
|
||||
{
|
||||
{
|
||||
let read = cache.read();
|
||||
if let Some(v) = read.get(key) {
|
||||
@ -181,7 +212,7 @@ pub trait Readable {
|
||||
}
|
||||
}
|
||||
|
||||
self.read(col, key).map(|value: T|{
|
||||
self.read(col, key).map(|value: T| {
|
||||
let mut write = cache.write();
|
||||
write.insert(key.clone(), value.clone());
|
||||
value
|
||||
@ -189,10 +220,18 @@ pub trait Readable {
|
||||
}
|
||||
|
||||
/// Returns value for given key either in two-layered cache or in database.
|
||||
fn read_with_two_layer_cache<K, T, C>(&self, col: Option<u32>, l1_cache: &RwLock<C>, l2_cache: &RwLock<C>, key: &K) -> Option<T> where
|
||||
fn read_with_two_layer_cache<K, T, C>(
|
||||
&self,
|
||||
col: Option<u32>,
|
||||
l1_cache: &RwLock<C>,
|
||||
l2_cache: &RwLock<C>,
|
||||
key: &K,
|
||||
) -> Option<T>
|
||||
where
|
||||
K: Key<T> + Eq + Hash + Clone,
|
||||
T: Clone + rlp::Decodable,
|
||||
C: Cache<K, T> {
|
||||
C: Cache<K, T>,
|
||||
{
|
||||
{
|
||||
let read = l1_cache.read();
|
||||
if let Some(v) = read.get(key) {
|
||||
@ -204,13 +243,17 @@ pub trait Readable {
|
||||
}
|
||||
|
||||
/// Returns true if given value exists.
|
||||
fn exists<T, R>(&self, col: Option<u32>, key: &Key<T, Target = R>) -> bool where R: Deref<Target= [u8]>;
|
||||
fn exists<T, R>(&self, col: Option<u32>, key: &Key<T, Target = R>) -> bool
|
||||
where
|
||||
R: Deref<Target = [u8]>;
|
||||
|
||||
/// Returns true if given value exists either in cache or in database.
|
||||
fn exists_with_cache<K, T, R, C>(&self, col: Option<u32>, cache: &RwLock<C>, key: &K) -> bool where
|
||||
fn exists_with_cache<K, T, R, C>(&self, col: Option<u32>, cache: &RwLock<C>, key: &K) -> bool
|
||||
where
|
||||
K: Eq + Hash + Key<T, Target = R>,
|
||||
R: Deref<Target = [u8]>,
|
||||
C: Cache<K, T> {
|
||||
C: Cache<K, T>,
|
||||
{
|
||||
{
|
||||
let read = cache.read();
|
||||
if read.get(key).is_some() {
|
||||
@ -223,31 +266,48 @@ pub trait Readable {
|
||||
}
|
||||
|
||||
impl Writable for DBTransaction {
|
||||
fn write<T, R>(&mut self, col: Option<u32>, key: &Key<T, Target = R>, value: &T) where T: rlp::Encodable, R: Deref<Target = [u8]> {
|
||||
fn write<T, R>(&mut self, col: Option<u32>, key: &Key<T, Target = R>, value: &T)
|
||||
where
|
||||
T: rlp::Encodable,
|
||||
R: Deref<Target = [u8]>,
|
||||
{
|
||||
self.put(col, &key.key(), &rlp::encode(value));
|
||||
}
|
||||
|
||||
fn delete<T, R>(&mut self, col: Option<u32>, key: &Key<T, Target = R>) where T: rlp::Encodable, R: Deref<Target = [u8]> {
|
||||
fn delete<T, R>(&mut self, col: Option<u32>, key: &Key<T, Target = R>)
|
||||
where
|
||||
T: rlp::Encodable,
|
||||
R: Deref<Target = [u8]>,
|
||||
{
|
||||
self.delete(col, &key.key());
|
||||
}
|
||||
}
|
||||
|
||||
impl<KVDB: KeyValueDB + ?Sized> Readable for KVDB {
|
||||
fn read<T, R>(&self, col: Option<u32>, key: &Key<T, Target = R>) -> Option<T>
|
||||
where T: rlp::Decodable, R: Deref<Target = [u8]> {
|
||||
where
|
||||
T: rlp::Decodable,
|
||||
R: Deref<Target = [u8]>,
|
||||
{
|
||||
self.get(col, &key.key())
|
||||
.expect(&format!("db get failed, key: {:?}", &key.key() as &[u8]))
|
||||
.map(|v| rlp::decode(&v).expect("decode db value failed") )
|
||||
|
||||
.map(|v| rlp::decode(&v).expect("decode db value failed"))
|
||||
}
|
||||
|
||||
fn exists<T, R>(&self, col: Option<u32>, key: &Key<T, Target = R>) -> bool where R: Deref<Target = [u8]> {
|
||||
fn exists<T, R>(&self, col: Option<u32>, key: &Key<T, Target = R>) -> bool
|
||||
where
|
||||
R: Deref<Target = [u8]>,
|
||||
{
|
||||
let result = self.get(col, &key.key());
|
||||
|
||||
match result {
|
||||
Ok(v) => v.is_some(),
|
||||
Err(err) => {
|
||||
panic!("db get failed, key: {:?}, err: {:?}", &key.key() as &[u8], err);
|
||||
panic!(
|
||||
"db get failed, key: {:?}, err: {:?}",
|
||||
&key.key() as &[u8],
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,17 +16,14 @@
|
||||
|
||||
//! Blockchain DB extras.
|
||||
|
||||
use std::io::Write;
|
||||
use std::ops;
|
||||
use std::{io::Write, ops};
|
||||
|
||||
use common_types::BlockNumber;
|
||||
use common_types::engines::epoch::Transition as EpochTransition;
|
||||
use common_types::receipt::Receipt;
|
||||
use common_types::{engines::epoch::Transition as EpochTransition, receipt::Receipt, BlockNumber};
|
||||
use ethereum_types::{H256, H264, U256};
|
||||
use heapsize::HeapSizeOf;
|
||||
use kvdb::PREFIX_LEN as DB_PREFIX_LEN;
|
||||
use rlp;
|
||||
use rlp_derive::{RlpEncodableWrapper, RlpDecodableWrapper, RlpEncodable, RlpDecodable};
|
||||
use rlp_derive::{RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper};
|
||||
|
||||
use crate::db::Key;
|
||||
|
||||
@ -117,7 +114,18 @@ pub const EPOCH_KEY_LEN: usize = DB_PREFIX_LEN + 16;
|
||||
/// epoch key prefix.
|
||||
/// used to iterate over all epoch transitions in order from genesis.
|
||||
pub const EPOCH_KEY_PREFIX: &'static [u8; DB_PREFIX_LEN] = &[
|
||||
ExtrasIndex::EpochTransitions as u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
ExtrasIndex::EpochTransitions as u8,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
];
|
||||
|
||||
/// Epoch transitions key
|
||||
@ -126,7 +134,9 @@ pub struct EpochTransitionsKey([u8; EPOCH_KEY_LEN]);
|
||||
impl ops::Deref for EpochTransitionsKey {
|
||||
type Target = [u8];
|
||||
|
||||
fn deref(&self) -> &[u8] { &self.0[..] }
|
||||
fn deref(&self) -> &[u8] {
|
||||
&self.0[..]
|
||||
}
|
||||
}
|
||||
|
||||
impl Key<EpochTransitions> for u64 {
|
||||
@ -163,8 +173,12 @@ impl rlp::Encodable for BlockDetails {
|
||||
let use_short_version = !self.is_finalized;
|
||||
|
||||
match use_short_version {
|
||||
true => { stream.begin_list(4); },
|
||||
false => { stream.begin_list(5); },
|
||||
true => {
|
||||
stream.begin_list(4);
|
||||
}
|
||||
false => {
|
||||
stream.begin_list(5);
|
||||
}
|
||||
}
|
||||
|
||||
stream.append(&self.number);
|
||||
@ -211,11 +225,13 @@ pub struct TransactionAddress {
|
||||
/// Block hash
|
||||
pub block_hash: H256,
|
||||
/// Transaction index within the block
|
||||
pub index: usize
|
||||
pub index: usize,
|
||||
}
|
||||
|
||||
impl HeapSizeOf for TransactionAddress {
|
||||
fn heap_size_of_children(&self) -> usize { 0 }
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/// Contains all block receipts.
|
||||
@ -228,9 +244,7 @@ pub struct BlockReceipts {
|
||||
impl BlockReceipts {
|
||||
/// Create new block receipts wrapper.
|
||||
pub fn new(receipts: Vec<Receipt>) -> Self {
|
||||
BlockReceipts {
|
||||
receipts: receipts
|
||||
}
|
||||
BlockReceipts { receipts: receipts }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@
|
||||
|
||||
mod db;
|
||||
|
||||
pub mod keys;
|
||||
pub mod cache_manager;
|
||||
pub mod keys;
|
||||
|
||||
pub use self::db::*;
|
||||
|
@ -20,23 +20,21 @@
|
||||
extern crate criterion;
|
||||
extern crate bit_set;
|
||||
extern crate ethereum_types;
|
||||
extern crate parking_lot;
|
||||
extern crate heapsize;
|
||||
extern crate vm;
|
||||
extern crate evm;
|
||||
extern crate heapsize;
|
||||
extern crate keccak_hash as hash;
|
||||
extern crate memory_cache;
|
||||
extern crate parity_bytes as bytes;
|
||||
extern crate parking_lot;
|
||||
extern crate rustc_hex;
|
||||
extern crate vm;
|
||||
|
||||
use criterion::{Criterion, Bencher, black_box};
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use ethereum_types::{U256, Address};
|
||||
use vm::{ActionParams, Result, GasLeft, Ext};
|
||||
use vm::tests::FakeExt;
|
||||
use criterion::{black_box, Bencher, Criterion};
|
||||
use ethereum_types::{Address, U256};
|
||||
use evm::Factory;
|
||||
use rustc_hex::FromHex;
|
||||
use std::{str::FromStr, sync::Arc};
|
||||
use vm::{tests::FakeExt, ActionParams, Ext, GasLeft, Result};
|
||||
|
||||
criterion_group!(
|
||||
basic,
|
||||
@ -68,9 +66,7 @@ fn simple_loop_log0(gas: U256, b: &mut Bencher) {
|
||||
let mut ext = FakeExt::new();
|
||||
|
||||
let address = Address::from_str("0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6").unwrap();
|
||||
let code = black_box(
|
||||
"62ffffff5b600190036000600fa0600357".from_hex().unwrap()
|
||||
);
|
||||
let code = black_box("62ffffff5b600190036000600fa0600357".from_hex().unwrap());
|
||||
|
||||
b.iter(|| {
|
||||
let mut params = ActionParams::default();
|
||||
@ -104,7 +100,9 @@ fn mem_gas_calculation_same(gas: U256, b: &mut Bencher) {
|
||||
|
||||
b.iter(|| {
|
||||
let code = black_box(
|
||||
"6110006001556001546000555b610fff805560016000540380600055600c57".from_hex().unwrap()
|
||||
"6110006001556001546000555b610fff805560016000540380600055600c57"
|
||||
.from_hex()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let mut params = ActionParams::default();
|
||||
@ -138,7 +136,9 @@ fn mem_gas_calculation_increasing(gas: U256, b: &mut Bencher) {
|
||||
|
||||
b.iter(|| {
|
||||
let code = black_box(
|
||||
"6110006001556001546000555b610fff60005401805560016000540380600055600c57".from_hex().unwrap()
|
||||
"6110006001556001546000555b610fff60005401805560016000540380600055600c57"
|
||||
.from_hex()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let mut params = ActionParams::default();
|
||||
|
@ -16,9 +16,9 @@
|
||||
|
||||
//! Evm interface.
|
||||
|
||||
use std::{ops, cmp, fmt};
|
||||
use ethereum_types::{U128, U256, U512};
|
||||
use vm::{Ext, Result, ReturnData, GasLeft, Error};
|
||||
use std::{cmp, fmt, ops};
|
||||
use vm::{Error, Ext, GasLeft, Result, ReturnData};
|
||||
|
||||
/// Finalization result. Gas Left: either it is a known value, or it needs to be computed by processing
|
||||
/// a return instruction.
|
||||
@ -44,18 +44,22 @@ pub trait Finalize {
|
||||
impl Finalize for Result<GasLeft> {
|
||||
fn finalize<E: Ext>(self, ext: E) -> Result<FinalizationResult> {
|
||||
match self {
|
||||
Ok(GasLeft::Known(gas_left)) => {
|
||||
Ok(FinalizationResult {
|
||||
Ok(GasLeft::Known(gas_left)) => Ok(FinalizationResult {
|
||||
gas_left,
|
||||
apply_state: true,
|
||||
return_data: ReturnData::empty()
|
||||
})
|
||||
},
|
||||
Ok(GasLeft::NeedsReturn { gas_left, data, apply_state }) => {
|
||||
ext.ret(&gas_left, &data, apply_state).map(|gas_left|
|
||||
FinalizationResult { gas_left, apply_state, return_data: data }
|
||||
)
|
||||
},
|
||||
return_data: ReturnData::empty(),
|
||||
}),
|
||||
Ok(GasLeft::NeedsReturn {
|
||||
gas_left,
|
||||
data,
|
||||
apply_state,
|
||||
}) => ext
|
||||
.ret(&gas_left, &data, apply_state)
|
||||
.map(|gas_left| FinalizationResult {
|
||||
gas_left,
|
||||
apply_state,
|
||||
return_data: data,
|
||||
}),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
@ -68,10 +72,20 @@ impl Finalize for Error {
|
||||
}
|
||||
|
||||
/// Cost calculation type. For low-gas usage we calculate costs using usize instead of U256
|
||||
pub trait CostType: Sized + From<usize> + Copy + Send
|
||||
+ ops::Mul<Output=Self> + ops::Div<Output=Self> + ops::Add<Output=Self> + ops::Sub<Output=Self>
|
||||
+ ops::Shr<usize, Output=Self> + ops::Shl<usize, Output=Self>
|
||||
+ cmp::Ord + fmt::Debug {
|
||||
pub trait CostType:
|
||||
Sized
|
||||
+ From<usize>
|
||||
+ Copy
|
||||
+ Send
|
||||
+ ops::Mul<Output = Self>
|
||||
+ ops::Div<Output = Self>
|
||||
+ ops::Add<Output = Self>
|
||||
+ ops::Sub<Output = Self>
|
||||
+ ops::Shr<usize, Output = Self>
|
||||
+ ops::Shl<usize, Output = Self>
|
||||
+ cmp::Ord
|
||||
+ fmt::Debug
|
||||
{
|
||||
/// Converts this cost into `U256`
|
||||
fn as_u256(&self) -> U256;
|
||||
/// Tries to fit `U256` into this `Cost` type
|
||||
@ -113,10 +127,7 @@ impl CostType for U256 {
|
||||
let U512(parts) = x;
|
||||
let overflow = (parts[4] | parts[5] | parts[6] | parts[7]) > 0;
|
||||
let U512(parts) = x >> shr;
|
||||
(
|
||||
U256([parts[0], parts[1], parts[2], parts[3]]),
|
||||
overflow
|
||||
)
|
||||
(U256([parts[0], parts[1], parts[2], parts[3]]), overflow)
|
||||
}
|
||||
}
|
||||
|
||||
@ -161,8 +172,8 @@ impl CostType for usize {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ethereum_types::U256;
|
||||
use super::CostType;
|
||||
use ethereum_types::U256;
|
||||
|
||||
#[test]
|
||||
fn should_calculate_overflow_mul_shr_without_overflow() {
|
||||
|
@ -16,12 +16,10 @@
|
||||
|
||||
//! Evm factory.
|
||||
//!
|
||||
use super::{interpreter::SharedCache, vm::ActionParams, vmtype::VMType};
|
||||
use ethereum_types::U256;
|
||||
use std::sync::Arc;
|
||||
use vm::{Exec, Schedule};
|
||||
use ethereum_types::U256;
|
||||
use super::vm::ActionParams;
|
||||
use super::interpreter::SharedCache;
|
||||
use super::vmtype::VMType;
|
||||
|
||||
/// Evm factory. Creates appropriate Evm.
|
||||
#[derive(Clone)]
|
||||
@ -35,10 +33,22 @@ impl Factory {
|
||||
/// Might choose implementation depending on supplied gas.
|
||||
pub fn create(&self, params: ActionParams, schedule: &Schedule, depth: usize) -> Box<Exec> {
|
||||
match self.evm {
|
||||
VMType::Interpreter => if Self::can_fit_in_usize(¶ms.gas) {
|
||||
Box::new(super::interpreter::Interpreter::<usize>::new(params, self.evm_cache.clone(), schedule, depth))
|
||||
VMType::Interpreter => {
|
||||
if Self::can_fit_in_usize(¶ms.gas) {
|
||||
Box::new(super::interpreter::Interpreter::<usize>::new(
|
||||
params,
|
||||
self.evm_cache.clone(),
|
||||
schedule,
|
||||
depth,
|
||||
))
|
||||
} else {
|
||||
Box::new(super::interpreter::Interpreter::<U256>::new(params, self.evm_cache.clone(), schedule, depth))
|
||||
Box::new(super::interpreter::Interpreter::<U256>::new(
|
||||
params,
|
||||
self.evm_cache.clone(),
|
||||
schedule,
|
||||
depth,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -69,9 +79,8 @@ impl Default for Factory {
|
||||
|
||||
#[test]
|
||||
fn test_create_vm() {
|
||||
use vm::Ext;
|
||||
use vm::tests::FakeExt;
|
||||
use bytes::Bytes;
|
||||
use vm::{tests::FakeExt, Ext};
|
||||
|
||||
let mut params = ActionParams::default();
|
||||
params.code = Some(Arc::new(Bytes::default()));
|
||||
|
@ -440,13 +440,18 @@ pub struct InstructionInfo {
|
||||
/// Number of returned stack items.
|
||||
pub ret: usize,
|
||||
/// Gas price tier.
|
||||
pub tier: GasPriceTier
|
||||
pub tier: GasPriceTier,
|
||||
}
|
||||
|
||||
impl InstructionInfo {
|
||||
/// Create new instruction info.
|
||||
pub fn new(name: &'static str, args: usize, ret: usize, tier: GasPriceTier) -> Self {
|
||||
InstructionInfo { name, args, ret, tier }
|
||||
InstructionInfo {
|
||||
name,
|
||||
args,
|
||||
ret,
|
||||
tier,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,28 +14,30 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::cmp;
|
||||
use ethereum_types::{U256, H256};
|
||||
use super::u256_to_address;
|
||||
use ethereum_types::{H256, U256};
|
||||
use std::cmp;
|
||||
|
||||
use {evm, vm};
|
||||
use evm;
|
||||
use instructions::{self, Instruction, InstructionInfo};
|
||||
use interpreter::stack::Stack;
|
||||
use vm::Schedule;
|
||||
use vm::{self, Schedule};
|
||||
|
||||
macro_rules! overflowing {
|
||||
($x: expr) => {{
|
||||
let (v, overflow) = $x;
|
||||
if overflow { return Err(vm::Error::OutOfGas); }
|
||||
if overflow {
|
||||
return Err(vm::Error::OutOfGas);
|
||||
}
|
||||
v
|
||||
}}
|
||||
}};
|
||||
}
|
||||
|
||||
enum Request<Cost: ::evm::CostType> {
|
||||
Gas(Cost),
|
||||
GasMem(Cost, Cost),
|
||||
GasMemProvide(Cost, Cost, Option<U256>),
|
||||
GasMemCopy(Cost, Cost, Cost)
|
||||
GasMemCopy(Cost, Cost, Cost),
|
||||
}
|
||||
|
||||
pub struct InstructionRequirements<Cost> {
|
||||
@ -51,7 +53,6 @@ pub struct Gasometer<Gas> {
|
||||
}
|
||||
|
||||
impl<Gas: evm::CostType> Gasometer<Gas> {
|
||||
|
||||
pub fn new(current_gas: Gas) -> Self {
|
||||
Gasometer {
|
||||
current_gas: current_gas,
|
||||
@ -62,13 +63,18 @@ impl<Gas: evm::CostType> Gasometer<Gas> {
|
||||
pub fn verify_gas(&self, gas_cost: &Gas) -> vm::Result<()> {
|
||||
match &self.current_gas < gas_cost {
|
||||
true => Err(vm::Error::OutOfGas),
|
||||
false => Ok(())
|
||||
false => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// How much gas is provided to a CALL/CREATE, given that we need to deduct `needed` for this operation
|
||||
/// and that we `requested` some.
|
||||
pub fn gas_provided(&self, schedule: &Schedule, needed: Gas, requested: Option<U256>) -> vm::Result<Gas> {
|
||||
pub fn gas_provided(
|
||||
&self,
|
||||
schedule: &Schedule,
|
||||
needed: Gas,
|
||||
requested: Option<U256>,
|
||||
) -> vm::Result<Gas> {
|
||||
// Try converting requested gas to `Gas` (`U256/u64`)
|
||||
// but in EIP150 even if we request more we should never fail from OOG
|
||||
let requested = requested.map(Gas::from_u256);
|
||||
@ -86,7 +92,7 @@ impl<Gas: evm::CostType> Gasometer<Gas> {
|
||||
} else {
|
||||
Ok(max_gas_provided)
|
||||
}
|
||||
},
|
||||
}
|
||||
_ => {
|
||||
if let Some(r) = requested {
|
||||
r
|
||||
@ -95,7 +101,7 @@ impl<Gas: evm::CostType> Gasometer<Gas> {
|
||||
} else {
|
||||
Ok(0.into())
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -117,9 +123,7 @@ impl<Gas: evm::CostType> Gasometer<Gas> {
|
||||
let default_gas = Gas::from(schedule.tier_step_gas[tier]);
|
||||
|
||||
let cost = match instruction {
|
||||
instructions::JUMPDEST => {
|
||||
Request::Gas(Gas::from(1))
|
||||
},
|
||||
instructions::JUMPDEST => Request::Gas(Gas::from(1)),
|
||||
instructions::SSTORE => {
|
||||
if schedule.eip1706 && self.current_gas <= Gas::from(schedule.call_stipend) {
|
||||
return Err(vm::Error::OutOfGas);
|
||||
@ -141,77 +145,87 @@ impl<Gas: evm::CostType> Gasometer<Gas> {
|
||||
}
|
||||
};
|
||||
Request::Gas(Gas::from(gas))
|
||||
},
|
||||
instructions::SLOAD => {
|
||||
Request::Gas(Gas::from(schedule.sload_gas))
|
||||
},
|
||||
instructions::BALANCE => {
|
||||
Request::Gas(Gas::from(schedule.balance_gas))
|
||||
},
|
||||
instructions::EXTCODESIZE => {
|
||||
Request::Gas(Gas::from(schedule.extcodesize_gas))
|
||||
},
|
||||
instructions::EXTCODEHASH => {
|
||||
Request::Gas(Gas::from(schedule.extcodehash_gas))
|
||||
},
|
||||
}
|
||||
instructions::SLOAD => Request::Gas(Gas::from(schedule.sload_gas)),
|
||||
instructions::BALANCE => Request::Gas(Gas::from(schedule.balance_gas)),
|
||||
instructions::EXTCODESIZE => Request::Gas(Gas::from(schedule.extcodesize_gas)),
|
||||
instructions::EXTCODEHASH => Request::Gas(Gas::from(schedule.extcodehash_gas)),
|
||||
instructions::SUICIDE => {
|
||||
let mut gas = Gas::from(schedule.suicide_gas);
|
||||
|
||||
let is_value_transfer = !ext.origin_balance()?.is_zero();
|
||||
let address = u256_to_address(stack.peek(0));
|
||||
if (
|
||||
!schedule.no_empty && !ext.exists(&address)?
|
||||
) || (
|
||||
schedule.no_empty && is_value_transfer && !ext.exists_and_not_null(&address)?
|
||||
) {
|
||||
gas = overflowing!(gas.overflow_add(schedule.suicide_to_new_account_cost.into()));
|
||||
if (!schedule.no_empty && !ext.exists(&address)?)
|
||||
|| (schedule.no_empty
|
||||
&& is_value_transfer
|
||||
&& !ext.exists_and_not_null(&address)?)
|
||||
{
|
||||
gas =
|
||||
overflowing!(gas.overflow_add(schedule.suicide_to_new_account_cost.into()));
|
||||
}
|
||||
|
||||
Request::Gas(gas)
|
||||
},
|
||||
}
|
||||
instructions::MSTORE | instructions::MLOAD => {
|
||||
Request::GasMem(default_gas, mem_needed_const(stack.peek(0), 32)?)
|
||||
},
|
||||
}
|
||||
instructions::MSTORE8 => {
|
||||
Request::GasMem(default_gas, mem_needed_const(stack.peek(0), 1)?)
|
||||
},
|
||||
}
|
||||
instructions::RETURN | instructions::REVERT => {
|
||||
Request::GasMem(default_gas, mem_needed(stack.peek(0), stack.peek(1))?)
|
||||
},
|
||||
}
|
||||
instructions::SHA3 => {
|
||||
let words = overflowing!(to_word_size(Gas::from_u256(*stack.peek(1))?));
|
||||
let gas = overflowing!(Gas::from(schedule.sha3_gas).overflow_add(overflowing!(Gas::from(schedule.sha3_word_gas).overflow_mul(words))));
|
||||
let gas = overflowing!(Gas::from(schedule.sha3_gas).overflow_add(overflowing!(
|
||||
Gas::from(schedule.sha3_word_gas).overflow_mul(words)
|
||||
)));
|
||||
Request::GasMem(gas, mem_needed(stack.peek(0), stack.peek(1))?)
|
||||
},
|
||||
}
|
||||
instructions::CALLDATACOPY | instructions::CODECOPY | instructions::RETURNDATACOPY => {
|
||||
Request::GasMemCopy(default_gas, mem_needed(stack.peek(0), stack.peek(2))?, Gas::from_u256(*stack.peek(2))?)
|
||||
},
|
||||
instructions::EXTCODECOPY => {
|
||||
Request::GasMemCopy(schedule.extcodecopy_base_gas.into(), mem_needed(stack.peek(1), stack.peek(3))?, Gas::from_u256(*stack.peek(3))?)
|
||||
},
|
||||
instructions::LOG0 | instructions::LOG1 | instructions::LOG2 | instructions::LOG3 | instructions::LOG4 => {
|
||||
let no_of_topics = instruction.log_topics().expect("log_topics always return some for LOG* instructions; qed");
|
||||
Request::GasMemCopy(
|
||||
default_gas,
|
||||
mem_needed(stack.peek(0), stack.peek(2))?,
|
||||
Gas::from_u256(*stack.peek(2))?,
|
||||
)
|
||||
}
|
||||
instructions::EXTCODECOPY => Request::GasMemCopy(
|
||||
schedule.extcodecopy_base_gas.into(),
|
||||
mem_needed(stack.peek(1), stack.peek(3))?,
|
||||
Gas::from_u256(*stack.peek(3))?,
|
||||
),
|
||||
instructions::LOG0
|
||||
| instructions::LOG1
|
||||
| instructions::LOG2
|
||||
| instructions::LOG3
|
||||
| instructions::LOG4 => {
|
||||
let no_of_topics = instruction
|
||||
.log_topics()
|
||||
.expect("log_topics always return some for LOG* instructions; qed");
|
||||
let log_gas = schedule.log_gas + schedule.log_topic_gas * no_of_topics;
|
||||
|
||||
let data_gas = overflowing!(Gas::from_u256(*stack.peek(1))?.overflow_mul(Gas::from(schedule.log_data_gas)));
|
||||
let data_gas =
|
||||
overflowing!(Gas::from_u256(*stack.peek(1))?
|
||||
.overflow_mul(Gas::from(schedule.log_data_gas)));
|
||||
let gas = overflowing!(data_gas.overflow_add(Gas::from(log_gas)));
|
||||
Request::GasMem(gas, mem_needed(stack.peek(0), stack.peek(1))?)
|
||||
},
|
||||
}
|
||||
instructions::CALL | instructions::CALLCODE => {
|
||||
let mut gas = Gas::from(schedule.call_gas);
|
||||
let mem = cmp::max(
|
||||
mem_needed(stack.peek(5), stack.peek(6))?,
|
||||
mem_needed(stack.peek(3), stack.peek(4))?
|
||||
mem_needed(stack.peek(3), stack.peek(4))?,
|
||||
);
|
||||
|
||||
let address = u256_to_address(stack.peek(1));
|
||||
let is_value_transfer = !stack.peek(2).is_zero();
|
||||
|
||||
if instruction == instructions::CALL && (
|
||||
(!schedule.no_empty && !ext.exists(&address)?)
|
||||
||
|
||||
(schedule.no_empty && is_value_transfer && !ext.exists_and_not_null(&address)?)
|
||||
) {
|
||||
if instruction == instructions::CALL
|
||||
&& ((!schedule.no_empty && !ext.exists(&address)?)
|
||||
|| (schedule.no_empty
|
||||
&& is_value_transfer
|
||||
&& !ext.exists_and_not_null(&address)?))
|
||||
{
|
||||
gas = overflowing!(gas.overflow_add(schedule.call_new_account_gas.into()));
|
||||
}
|
||||
|
||||
@ -222,17 +236,17 @@ impl<Gas: evm::CostType> Gasometer<Gas> {
|
||||
let requested = *stack.peek(0);
|
||||
|
||||
Request::GasMemProvide(gas, mem, Some(requested))
|
||||
},
|
||||
}
|
||||
instructions::DELEGATECALL | instructions::STATICCALL => {
|
||||
let gas = Gas::from(schedule.call_gas);
|
||||
let mem = cmp::max(
|
||||
mem_needed(stack.peek(4), stack.peek(5))?,
|
||||
mem_needed(stack.peek(2), stack.peek(3))?
|
||||
mem_needed(stack.peek(2), stack.peek(3))?,
|
||||
);
|
||||
let requested = *stack.peek(0);
|
||||
|
||||
Request::GasMemProvide(gas, mem, Some(requested))
|
||||
},
|
||||
}
|
||||
instructions::CREATE => {
|
||||
let start = stack.peek(1);
|
||||
let len = stack.peek(2);
|
||||
@ -241,7 +255,7 @@ impl<Gas: evm::CostType> Gasometer<Gas> {
|
||||
let mem = mem_needed(start, len)?;
|
||||
|
||||
Request::GasMemProvide(gas, mem, None)
|
||||
},
|
||||
}
|
||||
instructions::CREATE2 => {
|
||||
let start = stack.peek(1);
|
||||
let len = stack.peek(2);
|
||||
@ -253,30 +267,27 @@ impl<Gas: evm::CostType> Gasometer<Gas> {
|
||||
let mem = mem_needed(start, len)?;
|
||||
|
||||
Request::GasMemProvide(gas, mem, None)
|
||||
},
|
||||
}
|
||||
instructions::EXP => {
|
||||
let expon = stack.peek(1);
|
||||
let bytes = ((expon.bits() + 7) / 8) as usize;
|
||||
let gas = Gas::from(schedule.exp_gas + schedule.exp_byte_gas * bytes);
|
||||
Request::Gas(gas)
|
||||
},
|
||||
instructions::BLOCKHASH => {
|
||||
Request::Gas(Gas::from(schedule.blockhash_gas))
|
||||
},
|
||||
}
|
||||
instructions::BLOCKHASH => Request::Gas(Gas::from(schedule.blockhash_gas)),
|
||||
_ => Request::Gas(default_gas),
|
||||
};
|
||||
|
||||
Ok(match cost {
|
||||
Request::Gas(gas) => {
|
||||
InstructionRequirements {
|
||||
Request::Gas(gas) => InstructionRequirements {
|
||||
gas_cost: gas,
|
||||
provide_gas: None,
|
||||
memory_required_size: 0,
|
||||
memory_total_gas: self.current_mem_gas,
|
||||
}
|
||||
},
|
||||
Request::GasMem(gas, mem_size) => {
|
||||
let (mem_gas_cost, new_mem_gas, new_mem_size) = self.mem_gas_cost(schedule, current_mem_size, &mem_size)?;
|
||||
let (mem_gas_cost, new_mem_gas, new_mem_size) =
|
||||
self.mem_gas_cost(schedule, current_mem_size, &mem_size)?;
|
||||
let gas = overflowing!(gas.overflow_add(mem_gas_cost));
|
||||
InstructionRequirements {
|
||||
gas_cost: gas,
|
||||
@ -284,9 +295,10 @@ impl<Gas: evm::CostType> Gasometer<Gas> {
|
||||
memory_required_size: new_mem_size,
|
||||
memory_total_gas: new_mem_gas,
|
||||
}
|
||||
},
|
||||
}
|
||||
Request::GasMemProvide(gas, mem_size, requested) => {
|
||||
let (mem_gas_cost, new_mem_gas, new_mem_size) = self.mem_gas_cost(schedule, current_mem_size, &mem_size)?;
|
||||
let (mem_gas_cost, new_mem_gas, new_mem_size) =
|
||||
self.mem_gas_cost(schedule, current_mem_size, &mem_size)?;
|
||||
let gas = overflowing!(gas.overflow_add(mem_gas_cost));
|
||||
let provided = self.gas_provided(schedule, gas, requested)?;
|
||||
let total_gas = overflowing!(gas.overflow_add(provided));
|
||||
@ -297,9 +309,10 @@ impl<Gas: evm::CostType> Gasometer<Gas> {
|
||||
memory_required_size: new_mem_size,
|
||||
memory_total_gas: new_mem_gas,
|
||||
}
|
||||
},
|
||||
}
|
||||
Request::GasMemCopy(gas, mem_size, copy) => {
|
||||
let (mem_gas_cost, new_mem_gas, new_mem_size) = self.mem_gas_cost(schedule, current_mem_size, &mem_size)?;
|
||||
let (mem_gas_cost, new_mem_gas, new_mem_size) =
|
||||
self.mem_gas_cost(schedule, current_mem_size, &mem_size)?;
|
||||
let copy = overflowing!(to_word_size(copy));
|
||||
let copy_gas = overflowing!(Gas::from(schedule.copy_gas).overflow_mul(copy));
|
||||
let gas = overflowing!(gas.overflow_add(copy_gas));
|
||||
@ -311,11 +324,16 @@ impl<Gas: evm::CostType> Gasometer<Gas> {
|
||||
memory_required_size: new_mem_size,
|
||||
memory_total_gas: new_mem_gas,
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn mem_gas_cost(&self, schedule: &Schedule, current_mem_size: usize, mem_size: &Gas) -> vm::Result<(Gas, Gas, usize)> {
|
||||
fn mem_gas_cost(
|
||||
&self,
|
||||
schedule: &Schedule,
|
||||
current_mem_size: usize,
|
||||
mem_size: &Gas,
|
||||
) -> vm::Result<(Gas, Gas, usize)> {
|
||||
let gas_for_mem = |mem_size: Gas| {
|
||||
let s = mem_size >> 5;
|
||||
// s * memory_gas + s * s / quad_coeff_div
|
||||
@ -371,9 +389,13 @@ fn to_word_size<Gas: evm::CostType>(value: Gas) -> (Gas, bool) {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn calculate_eip1283_sstore_gas<Gas: evm::CostType>(schedule: &Schedule, original: &U256, current: &U256, new: &U256) -> Gas {
|
||||
Gas::from(
|
||||
if current == new {
|
||||
fn calculate_eip1283_sstore_gas<Gas: evm::CostType>(
|
||||
schedule: &Schedule,
|
||||
original: &U256,
|
||||
current: &U256,
|
||||
new: &U256,
|
||||
) -> Gas {
|
||||
Gas::from(if current == new {
|
||||
// 1. If current value equals new value (this is a no-op), 200 gas is deducted.
|
||||
schedule.sload_gas
|
||||
} else {
|
||||
@ -401,11 +423,15 @@ fn calculate_eip1283_sstore_gas<Gas: evm::CostType>(schedule: &Schedule, origina
|
||||
// 2.2.2.1. If original value is 0, add 19800 gas to refund counter.
|
||||
// 2.2.2.2. Otherwise, add 4800 gas to refund counter.
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn handle_eip1283_sstore_clears_refund(ext: &mut vm::Ext, original: &U256, current: &U256, new: &U256) {
|
||||
pub fn handle_eip1283_sstore_clears_refund(
|
||||
ext: &mut vm::Ext,
|
||||
original: &U256,
|
||||
current: &U256,
|
||||
new: &U256,
|
||||
) {
|
||||
let sstore_clears_schedule = ext.schedule().sstore_refund_gas;
|
||||
|
||||
if current == new {
|
||||
@ -479,7 +505,9 @@ fn test_calculate_mem_cost() {
|
||||
let mem_size = 5;
|
||||
|
||||
// when
|
||||
let (mem_cost, new_mem_gas, mem_size) = gasometer.mem_gas_cost(&schedule, current_mem_size, &mem_size).unwrap();
|
||||
let (mem_cost, new_mem_gas, mem_size) = gasometer
|
||||
.mem_gas_cost(&schedule, current_mem_size, &mem_size)
|
||||
.unwrap();
|
||||
|
||||
// then
|
||||
assert_eq!(mem_cost, 3);
|
||||
|
@ -20,7 +20,7 @@ pub use self::inner::*;
|
||||
#[cfg(not(feature = "evm-debug"))]
|
||||
mod inner {
|
||||
macro_rules! evm_debug {
|
||||
($x: expr) => {}
|
||||
($x: expr) => {};
|
||||
}
|
||||
|
||||
pub struct EvmInformant;
|
||||
@ -35,20 +35,22 @@ mod inner {
|
||||
#[macro_use]
|
||||
#[cfg(feature = "evm-debug")]
|
||||
mod inner {
|
||||
use std::iter;
|
||||
use std::collections::HashMap;
|
||||
use std::time::{Instant, Duration};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
iter,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use ethereum_types::U256;
|
||||
|
||||
use interpreter::stack::Stack;
|
||||
use instructions::{Instruction, InstructionInfo};
|
||||
use interpreter::stack::Stack;
|
||||
use CostType;
|
||||
|
||||
macro_rules! evm_debug {
|
||||
($x: expr) => {
|
||||
$x
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn print(data: String) {
|
||||
@ -66,7 +68,6 @@ mod inner {
|
||||
}
|
||||
|
||||
impl EvmInformant {
|
||||
|
||||
fn color(instruction: Instruction, name: &str) -> String {
|
||||
let c = instruction as usize % 6;
|
||||
let colors = [31, 34, 33, 32, 35, 36];
|
||||
@ -89,11 +90,19 @@ mod inner {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn before_instruction<Cost: CostType>(&mut self, pc: usize, instruction: Instruction, info: &InstructionInfo, current_gas: &Cost, stack: &Stack<U256>) {
|
||||
pub fn before_instruction<Cost: CostType>(
|
||||
&mut self,
|
||||
pc: usize,
|
||||
instruction: Instruction,
|
||||
info: &InstructionInfo,
|
||||
current_gas: &Cost,
|
||||
stack: &Stack<U256>,
|
||||
) {
|
||||
let time = self.last_instruction.elapsed();
|
||||
self.last_instruction = Instant::now();
|
||||
|
||||
print(format!("{}[0x{:<3x}][{:>19}(0x{:<2x}) Gas Left: {:6?} (Previous took: {:10}μs)",
|
||||
print(format!(
|
||||
"{}[0x{:<3x}][{:>19}(0x{:<2x}) Gas Left: {:6?} (Previous took: {:10}μs)",
|
||||
&self.spacing,
|
||||
pc,
|
||||
Self::color(instruction, info.name),
|
||||
@ -110,20 +119,24 @@ mod inner {
|
||||
}
|
||||
|
||||
pub fn after_instruction(&mut self, instruction: Instruction) {
|
||||
let stats = self.stats.entry(instruction).or_insert_with(|| Stats::default());
|
||||
let stats = self
|
||||
.stats
|
||||
.entry(instruction)
|
||||
.or_insert_with(|| Stats::default());
|
||||
let took = self.last_instruction.elapsed();
|
||||
stats.note(took);
|
||||
}
|
||||
|
||||
pub fn done(&mut self) {
|
||||
// Print out stats
|
||||
let mut stats: Vec<(_,_)> = self.stats.drain().collect();
|
||||
let mut stats: Vec<(_, _)> = self.stats.drain().collect();
|
||||
stats.sort_by(|ref a, ref b| b.1.avg().cmp(&a.1.avg()));
|
||||
|
||||
print(format!("\n{}-------OPCODE STATS:", self.spacing));
|
||||
for (instruction, stats) in stats.into_iter() {
|
||||
let info = instruction.info();
|
||||
print(format!("{}-------{:>19}(0x{:<2x}) count: {:4}, avg: {:10}μs",
|
||||
print(format!(
|
||||
"{}-------{:>19}(0x{:<2x}) count: {:4}, avg: {:10}μs",
|
||||
self.spacing,
|
||||
Self::color(instruction, info.name),
|
||||
instruction as u8,
|
||||
@ -132,7 +145,6 @@ mod inner {
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct Stats {
|
||||
|
@ -37,7 +37,7 @@ pub trait Memory {
|
||||
/// Retrieve part of the memory between offset and offset + size
|
||||
fn read_slice(&self, offset: U256, size: U256) -> &[u8];
|
||||
/// Retrieve writeable part of memory
|
||||
fn writeable_slice(&mut self, offset: U256, size: U256) -> &mut[u8];
|
||||
fn writeable_slice(&mut self, offset: U256, size: U256) -> &mut [u8];
|
||||
/// Convert memory into return data.
|
||||
fn into_return_data(self, offset: U256, size: U256) -> ReturnData;
|
||||
}
|
||||
@ -60,13 +60,13 @@ impl Memory for Vec<u8> {
|
||||
if !is_valid_range(off, size) {
|
||||
&self[0..0]
|
||||
} else {
|
||||
&self[off..off+size]
|
||||
&self[off..off + size]
|
||||
}
|
||||
}
|
||||
|
||||
fn read(&self, offset: U256) -> U256 {
|
||||
let off = offset.low_u64() as usize;
|
||||
U256::from(&self[off..off+32])
|
||||
U256::from(&self[off..off + 32])
|
||||
}
|
||||
|
||||
fn writeable_slice(&mut self, offset: U256, size: U256) -> &mut [u8] {
|
||||
@ -75,20 +75,20 @@ impl Memory for Vec<u8> {
|
||||
if !is_valid_range(off, s) {
|
||||
&mut self[0..0]
|
||||
} else {
|
||||
&mut self[off..off+s]
|
||||
&mut self[off..off + s]
|
||||
}
|
||||
}
|
||||
|
||||
fn write_slice(&mut self, offset: U256, slice: &[u8]) {
|
||||
if !slice.is_empty() {
|
||||
let off = offset.low_u64() as usize;
|
||||
self[off..off+slice.len()].copy_from_slice(slice);
|
||||
self[off..off + slice.len()].copy_from_slice(slice);
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, offset: U256, value: U256) {
|
||||
let off = offset.low_u64() as usize;
|
||||
value.to_big_endian(&mut self[off..off+32]);
|
||||
value.to_big_endian(&mut self[off..off + 32]);
|
||||
}
|
||||
|
||||
fn write_byte(&mut self, offset: U256, value: U256) {
|
||||
@ -130,8 +130,8 @@ impl Memory for Vec<u8> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ethereum_types::U256;
|
||||
use super::Memory;
|
||||
use ethereum_types::U256;
|
||||
|
||||
#[test]
|
||||
fn test_memory_read_and_write() {
|
||||
@ -178,7 +178,10 @@ mod tests {
|
||||
let slice = "67890".as_bytes();
|
||||
mem.write_slice(U256::from(0x1), slice);
|
||||
|
||||
assert_eq!(mem.read_slice(U256::from(0), U256::from(7)), "a67890g".as_bytes());
|
||||
assert_eq!(
|
||||
mem.read_slice(U256::from(0), U256::from(7)),
|
||||
"a67890g".as_bytes()
|
||||
);
|
||||
}
|
||||
|
||||
// write empty slice out of bounds
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -14,14 +14,14 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::sync::Arc;
|
||||
use super::super::instructions::{self, Instruction};
|
||||
use bit_set::BitSet;
|
||||
use ethereum_types::H256;
|
||||
use hash::KECCAK_EMPTY;
|
||||
use heapsize::HeapSizeOf;
|
||||
use ethereum_types::H256;
|
||||
use parking_lot::Mutex;
|
||||
use memory_cache::MemoryLruCache;
|
||||
use bit_set::BitSet;
|
||||
use super::super::instructions::{self, Instruction};
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
||||
const DEFAULT_CACHE_SIZE: usize = 4 * 1024 * 1024;
|
||||
|
||||
@ -64,7 +64,9 @@ impl SharedCache {
|
||||
let d = Self::find_jump_destinations(code);
|
||||
|
||||
if let Some(ref code_hash) = code_hash {
|
||||
self.jump_destinations.lock().insert(*code_hash, Bits(d.clone()));
|
||||
self.jump_destinations
|
||||
.lock()
|
||||
.insert(*code_hash, Bits(d.clone()));
|
||||
}
|
||||
|
||||
d
|
||||
|
@ -14,8 +14,8 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::fmt;
|
||||
use instructions;
|
||||
use std::fmt;
|
||||
|
||||
/// Stack trait with VM-friendly API
|
||||
pub trait Stack<T> {
|
||||
@ -39,19 +39,19 @@ pub trait Stack<T> {
|
||||
|
||||
pub struct VecStack<S> {
|
||||
stack: Vec<S>,
|
||||
logs: [S; instructions::MAX_NO_OF_TOPICS]
|
||||
logs: [S; instructions::MAX_NO_OF_TOPICS],
|
||||
}
|
||||
|
||||
impl<S : Copy> VecStack<S> {
|
||||
impl<S: Copy> VecStack<S> {
|
||||
pub fn with_capacity(capacity: usize, zero: S) -> Self {
|
||||
VecStack {
|
||||
stack: Vec::with_capacity(capacity),
|
||||
logs: [zero; instructions::MAX_NO_OF_TOPICS]
|
||||
logs: [zero; instructions::MAX_NO_OF_TOPICS],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S : fmt::Display> Stack<S> for VecStack<S> {
|
||||
impl<S: fmt::Display> Stack<S> for VecStack<S> {
|
||||
fn peek(&self, no_from_top: usize) -> &S {
|
||||
&self.stack[self.stack.len() - no_from_top - 1]
|
||||
}
|
||||
@ -66,7 +66,9 @@ impl<S : fmt::Display> Stack<S> for VecStack<S> {
|
||||
}
|
||||
|
||||
fn pop_back(&mut self) -> S {
|
||||
self.stack.pop().expect("instruction validation prevents from popping too many items; qed")
|
||||
self.stack
|
||||
.pop()
|
||||
.expect("instruction validation prevents from popping too many items; qed")
|
||||
}
|
||||
|
||||
fn pop_n(&mut self, no_of_elems: usize) -> &[S] {
|
||||
@ -87,7 +89,10 @@ impl<S : fmt::Display> Stack<S> for VecStack<S> {
|
||||
}
|
||||
|
||||
fn peek_top(&self, no_from_top: usize) -> &[S] {
|
||||
assert!(self.stack.len() >= no_from_top, "peek_top asked for more items than exist.");
|
||||
&self.stack[self.stack.len() - no_from_top .. self.stack.len()]
|
||||
assert!(
|
||||
self.stack.len() >= no_from_top,
|
||||
"peek_top asked for more items than exist."
|
||||
);
|
||||
&self.stack[self.stack.len() - no_from_top..self.stack.len()]
|
||||
}
|
||||
}
|
||||
|
@ -18,13 +18,13 @@
|
||||
|
||||
extern crate bit_set;
|
||||
extern crate ethereum_types;
|
||||
extern crate parking_lot;
|
||||
extern crate heapsize;
|
||||
extern crate vm;
|
||||
extern crate keccak_hash as hash;
|
||||
extern crate memory_cache;
|
||||
extern crate parity_bytes as bytes;
|
||||
extern crate num_bigint;
|
||||
extern crate parity_bytes as bytes;
|
||||
extern crate parking_lot;
|
||||
extern crate vm;
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
@ -32,28 +32,29 @@ extern crate lazy_static;
|
||||
#[cfg_attr(feature = "evm-debug", macro_use)]
|
||||
extern crate log;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate rustc_hex;
|
||||
#[cfg(test)]
|
||||
extern crate hex_literal;
|
||||
#[cfg(test)]
|
||||
extern crate rustc_hex;
|
||||
|
||||
pub mod evm;
|
||||
pub mod interpreter;
|
||||
|
||||
#[macro_use]
|
||||
pub mod factory;
|
||||
mod vmtype;
|
||||
mod instructions;
|
||||
mod vmtype;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub use vm::{
|
||||
Schedule, CleanDustMode, EnvInfo, CallType, ActionParams, Ext,
|
||||
ContractCreateResult, MessageCallResult, CreateContractAddress,
|
||||
GasLeft, ReturnData
|
||||
pub use self::{
|
||||
evm::{CostType, FinalizationResult, Finalize},
|
||||
factory::Factory,
|
||||
instructions::{Instruction, InstructionInfo},
|
||||
vmtype::VMType,
|
||||
};
|
||||
pub use vm::{
|
||||
ActionParams, CallType, CleanDustMode, ContractCreateResult, CreateContractAddress, EnvInfo,
|
||||
Ext, GasLeft, MessageCallResult, ReturnData, Schedule,
|
||||
};
|
||||
pub use self::evm::{Finalize, FinalizationResult, CostType};
|
||||
pub use self::instructions::{InstructionInfo, Instruction};
|
||||
pub use self::vmtype::VMType;
|
||||
pub use self::factory::Factory;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -20,14 +20,18 @@ use std::fmt;
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum VMType {
|
||||
/// RUST EVM
|
||||
Interpreter
|
||||
Interpreter,
|
||||
}
|
||||
|
||||
impl fmt::Display for VMType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", match *self {
|
||||
VMType::Interpreter => "INT"
|
||||
})
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match *self {
|
||||
VMType::Interpreter => "INT",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,11 +20,9 @@
|
||||
//! Furthermore, stores a "gas price corpus" of relative recency, which is a sorted
|
||||
//! vector of all gas prices from a recent range of blocks.
|
||||
|
||||
use std::time::{Instant, Duration};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use common_types::encoded;
|
||||
use common_types::BlockNumber;
|
||||
use common_types::receipt::Receipt;
|
||||
use common_types::{encoded, receipt::Receipt, BlockNumber};
|
||||
use ethereum_types::{H256, U256};
|
||||
use heapsize::HeapSizeOf;
|
||||
use memory_cache::MemoryLruCache;
|
||||
|
@ -23,21 +23,23 @@
|
||||
//! root has. A correct proof implies that the claimed block is identical to the one
|
||||
//! we discarded.
|
||||
|
||||
use bytes::Bytes;
|
||||
use common_types::ids::BlockId;
|
||||
use ethereum_types::{H256, U256};
|
||||
use ethtrie::{self, TrieDB, TrieDBMut};
|
||||
use hash_db::HashDB;
|
||||
use journaldb::new_memory_db;
|
||||
use keccak_hasher::KeccakHasher;
|
||||
use kvdb::DBValue;
|
||||
use memory_db::MemoryDB;
|
||||
use journaldb::new_memory_db;
|
||||
use bytes::Bytes;
|
||||
use trie::{TrieMut, Trie, Recorder};
|
||||
use ethtrie::{self, TrieDB, TrieDBMut};
|
||||
use rlp::{RlpStream, Rlp};
|
||||
use rlp::{Rlp, RlpStream};
|
||||
use trie::{Recorder, Trie, TrieMut};
|
||||
|
||||
// encode a key.
|
||||
macro_rules! key {
|
||||
($num: expr) => { ::rlp::encode(&$num) }
|
||||
($num: expr) => {
|
||||
::rlp::encode(&$num)
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! val {
|
||||
@ -45,7 +47,7 @@ macro_rules! val {
|
||||
let mut stream = RlpStream::new_list(2);
|
||||
stream.append(&$hash).append(&$td);
|
||||
stream.drain()
|
||||
}}
|
||||
}};
|
||||
}
|
||||
|
||||
/// The size of each CHT.
|
||||
@ -62,19 +64,25 @@ pub struct CHT<DB: HashDB<KeccakHasher, DBValue>> {
|
||||
|
||||
impl<DB: HashDB<KeccakHasher, DBValue>> CHT<DB> {
|
||||
/// Query the root of the CHT.
|
||||
pub fn root(&self) -> H256 { self.root }
|
||||
pub fn root(&self) -> H256 {
|
||||
self.root
|
||||
}
|
||||
|
||||
/// Query the number of the CHT.
|
||||
pub fn number(&self) -> u64 { self.number }
|
||||
pub fn number(&self) -> u64 {
|
||||
self.number
|
||||
}
|
||||
|
||||
/// Generate an inclusion proof for the entry at a specific block.
|
||||
/// Nodes before level `from_level` will be omitted.
|
||||
/// Returns an error on an incomplete trie, and `Ok(None)` on an unprovable request.
|
||||
pub fn prove(&self, num: u64, from_level: u32) -> ethtrie::Result<Option<Vec<Bytes>>> {
|
||||
if block_to_cht_number(num) != Some(self.number) { return Ok(None) }
|
||||
if block_to_cht_number(num) != Some(self.number) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut recorder = Recorder::with_depth(from_level);
|
||||
let db: &HashDB<_,_> = &self.db;
|
||||
let db: &HashDB<_, _> = &self.db;
|
||||
let t = TrieDB::new(&db, &self.root)?;
|
||||
t.get_with(&key!(num), &mut recorder)?;
|
||||
|
||||
@ -96,7 +104,8 @@ pub struct BlockInfo {
|
||||
/// about blocks. If the fetcher ever fails to provide the info, the CHT
|
||||
/// will not be generated.
|
||||
pub fn build<F>(cht_num: u64, mut fetcher: F) -> Option<CHT<MemoryDB<KeccakHasher, DBValue>>>
|
||||
where F: FnMut(BlockId) -> Option<BlockInfo>
|
||||
where
|
||||
F: FnMut(BlockId) -> Option<BlockInfo>,
|
||||
{
|
||||
let mut db = new_memory_db();
|
||||
|
||||
@ -131,7 +140,8 @@ pub fn build<F>(cht_num: u64, mut fetcher: F) -> Option<CHT<MemoryDB<KeccakHashe
|
||||
/// SIZE items. The items are assumed to proceed sequentially from `start_number(cht_num)`.
|
||||
/// Discards the trie's nodes.
|
||||
pub fn compute_root<I>(cht_num: u64, iterable: I) -> Option<H256>
|
||||
where I: IntoIterator<Item=(H256, U256)>
|
||||
where
|
||||
I: IntoIterator<Item = (H256, U256)>,
|
||||
{
|
||||
let mut v = Vec::with_capacity(SIZE as usize);
|
||||
let start_num = start_number(cht_num) as usize;
|
||||
@ -154,7 +164,9 @@ pub fn compute_root<I>(cht_num: u64, iterable: I) -> Option<H256>
|
||||
pub fn check_proof(proof: &[Bytes], num: u64, root: H256) -> Option<(H256, U256)> {
|
||||
let mut db = new_memory_db();
|
||||
|
||||
for node in proof { db.insert(&node[..]); }
|
||||
for node in proof {
|
||||
db.insert(&node[..]);
|
||||
}
|
||||
let res = match TrieDB::new(&db, &root) {
|
||||
Err(_) => return None,
|
||||
Ok(trie) => trie.get_with(&key!(num), |val: &[u8]| {
|
||||
@ -162,7 +174,7 @@ pub fn check_proof(proof: &[Bytes], num: u64, root: H256) -> Option<(H256, U256)
|
||||
rlp.val_at::<H256>(0)
|
||||
.and_then(|h| rlp.val_at::<U256>(1).map(|td| (h, td)))
|
||||
.ok()
|
||||
})
|
||||
}),
|
||||
};
|
||||
|
||||
match res {
|
||||
|
@ -18,11 +18,11 @@
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use common_types::encoded;
|
||||
use common_types::header::Header;
|
||||
use common_types::receipt::Receipt;
|
||||
use ethcore::engines::{EthEngine, StateDependentProof};
|
||||
use ethcore::machine::EthereumMachine;
|
||||
use common_types::{encoded, header::Header, receipt::Receipt};
|
||||
use ethcore::{
|
||||
engines::{EthEngine, StateDependentProof},
|
||||
machine::EthereumMachine,
|
||||
};
|
||||
use ethereum_types::H256;
|
||||
use futures::future::IntoFuture;
|
||||
|
||||
@ -32,11 +32,11 @@ pub trait ChainDataFetcher: Send + Sync + 'static {
|
||||
type Error: ::std::fmt::Debug;
|
||||
|
||||
/// Future for fetching block body.
|
||||
type Body: IntoFuture<Item=encoded::Block, Error=Self::Error>;
|
||||
type Body: IntoFuture<Item = encoded::Block, Error = Self::Error>;
|
||||
/// Future for fetching block receipts.
|
||||
type Receipts: IntoFuture<Item=Vec<Receipt>, Error=Self::Error>;
|
||||
type Receipts: IntoFuture<Item = Vec<Receipt>, Error = Self::Error>;
|
||||
/// Future for fetching epoch transition
|
||||
type Transition: IntoFuture<Item=Vec<u8>, Error=Self::Error>;
|
||||
type Transition: IntoFuture<Item = Vec<u8>, Error = Self::Error>;
|
||||
|
||||
/// Fetch a block body.
|
||||
fn block_body(&self, header: &Header) -> Self::Body;
|
||||
@ -49,7 +49,7 @@ pub trait ChainDataFetcher: Send + Sync + 'static {
|
||||
&self,
|
||||
_hash: H256,
|
||||
_engine: Arc<EthEngine>,
|
||||
_checker: Arc<StateDependentProof<EthereumMachine>>
|
||||
_checker: Arc<StateDependentProof<EthereumMachine>>,
|
||||
) -> Self::Transition;
|
||||
}
|
||||
|
||||
@ -57,7 +57,9 @@ pub trait ChainDataFetcher: Send + Sync + 'static {
|
||||
pub struct Unavailable;
|
||||
|
||||
/// Create a fetcher which has all data unavailable.
|
||||
pub fn unavailable() -> Unavailable { Unavailable }
|
||||
pub fn unavailable() -> Unavailable {
|
||||
Unavailable
|
||||
}
|
||||
|
||||
impl ChainDataFetcher for Unavailable {
|
||||
type Error = &'static str;
|
||||
@ -78,7 +80,7 @@ impl ChainDataFetcher for Unavailable {
|
||||
&self,
|
||||
_hash: H256,
|
||||
_engine: Arc<EthEngine>,
|
||||
_checker: Arc<StateDependentProof<EthereumMachine>>
|
||||
_checker: Arc<StateDependentProof<EthereumMachine>>,
|
||||
) -> Self::Transition {
|
||||
Err("fetching epoch transition proofs unavailable")
|
||||
}
|
||||
|
@ -25,24 +25,22 @@
|
||||
//! - It stores only headers (and a pruned subset of them)
|
||||
//! - To allow for flexibility in the database layout..
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
use std::{collections::BTreeMap, sync::Arc};
|
||||
|
||||
use cache::Cache;
|
||||
use cht;
|
||||
use common_types::block_status::BlockStatus;
|
||||
use common_types::encoded;
|
||||
use common_types::header::Header;
|
||||
use common_types::ids::BlockId;
|
||||
use ethcore::engines::epoch::{Transition as EpochTransition, PendingTransition as PendingEpochTransition};
|
||||
use ethcore::error::{Error, EthcoreResult, ErrorKind as EthcoreErrorKind, BlockError};
|
||||
use ethcore::spec::{Spec, SpecHardcodedSync};
|
||||
use common_types::{block_status::BlockStatus, encoded, header::Header, ids::BlockId};
|
||||
use ethcore::{
|
||||
engines::epoch::{PendingTransition as PendingEpochTransition, Transition as EpochTransition},
|
||||
error::{BlockError, Error, ErrorKind as EthcoreErrorKind, EthcoreResult},
|
||||
spec::{Spec, SpecHardcodedSync},
|
||||
};
|
||||
use ethereum_types::{H256, H264, U256};
|
||||
use fastmap::H256FastMap;
|
||||
use heapsize::HeapSizeOf;
|
||||
use kvdb::{DBTransaction, KeyValueDB};
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use fastmap::H256FastMap;
|
||||
use rlp::{Encodable, Decodable, DecoderError, RlpStream, Rlp};
|
||||
use rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
/// Store at least this many candidate headers at all times.
|
||||
@ -71,7 +69,7 @@ pub struct BlockDescriptor {
|
||||
#[derive(RlpEncodable, RlpDecodable)]
|
||||
struct BestAndLatest {
|
||||
best_num: u64,
|
||||
latest_num: u64
|
||||
latest_num: u64,
|
||||
}
|
||||
|
||||
impl BestAndLatest {
|
||||
@ -130,7 +128,9 @@ impl Decodable for Entry {
|
||||
})
|
||||
}
|
||||
|
||||
if candidates.is_empty() { return Err(DecoderError::Custom("Empty candidates vector submitted.")) }
|
||||
if candidates.is_empty() {
|
||||
return Err(DecoderError::Custom("Empty candidates vector submitted."));
|
||||
}
|
||||
|
||||
// rely on the invariant that the canonical entry is always first.
|
||||
let canon_hash = candidates[0].hash;
|
||||
@ -222,7 +222,7 @@ impl HeaderChain {
|
||||
let decoded_header = spec.genesis_header();
|
||||
|
||||
let chain = if let Some(current) = db.get(col, CURRENT_KEY)? {
|
||||
let curr : BestAndLatest = ::rlp::decode(¤t).expect("decoding db value failed");
|
||||
let curr: BestAndLatest = ::rlp::decode(¤t).expect("decoding db value failed");
|
||||
|
||||
let mut cur_number = curr.latest_num;
|
||||
let mut candidates = BTreeMap::new();
|
||||
@ -238,11 +238,14 @@ impl HeaderChain {
|
||||
let key = transition_key(c.hash);
|
||||
|
||||
if let Some(proof) = db.get(col, &*key)? {
|
||||
live_epoch_proofs.insert(c.hash, EpochTransition {
|
||||
live_epoch_proofs.insert(
|
||||
c.hash,
|
||||
EpochTransition {
|
||||
block_hash: c.hash,
|
||||
block_number: cur_number,
|
||||
proof: proof.into_vec(),
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
candidates.insert(cur_number, entry);
|
||||
@ -274,7 +277,6 @@ impl HeaderChain {
|
||||
col,
|
||||
cache,
|
||||
}
|
||||
|
||||
} else {
|
||||
let chain = HeaderChain {
|
||||
genesis_header: encoded::Header::new(genesis),
|
||||
@ -291,12 +293,18 @@ impl HeaderChain {
|
||||
};
|
||||
|
||||
// insert the hardcoded sync into the database.
|
||||
if let (&Some(ref hardcoded_sync), HardcodedSync::Allow) = (&spec.hardcoded_sync, allow_hs) {
|
||||
if let (&Some(ref hardcoded_sync), HardcodedSync::Allow) =
|
||||
(&spec.hardcoded_sync, allow_hs)
|
||||
{
|
||||
let mut batch = db.transaction();
|
||||
|
||||
// insert the hardcoded CHT roots into the database.
|
||||
for (cht_num, cht_root) in hardcoded_sync.chts.iter().enumerate() {
|
||||
batch.put(col, cht_key(cht_num as u64).as_bytes(), &::rlp::encode(cht_root));
|
||||
batch.put(
|
||||
col,
|
||||
cht_key(cht_num as u64).as_bytes(),
|
||||
&::rlp::encode(cht_root),
|
||||
);
|
||||
}
|
||||
|
||||
let decoded_header = hardcoded_sync.header.decode()?;
|
||||
@ -304,8 +312,12 @@ impl HeaderChain {
|
||||
|
||||
// write the block in the DB.
|
||||
info!(target: "chain", "Inserting hardcoded block #{} in chain", decoded_header_num);
|
||||
let pending = chain.insert_with_td(&mut batch, &decoded_header,
|
||||
hardcoded_sync.total_difficulty, None)?;
|
||||
let pending = chain.insert_with_td(
|
||||
&mut batch,
|
||||
&decoded_header,
|
||||
hardcoded_sync.total_difficulty,
|
||||
None,
|
||||
)?;
|
||||
|
||||
// check that we have enough hardcoded CHT roots. avoids panicking later.
|
||||
let cht_num = cht::block_to_cht_number(decoded_header_num - 1)
|
||||
@ -365,7 +377,12 @@ impl HeaderChain {
|
||||
total_difficulty: U256,
|
||||
transition_proof: Option<Vec<u8>>,
|
||||
) -> EthcoreResult<PendingChanges> {
|
||||
self.insert_inner(transaction, header, Some(total_difficulty), transition_proof)
|
||||
self.insert_inner(
|
||||
transaction,
|
||||
header,
|
||||
Some(total_difficulty),
|
||||
transition_proof,
|
||||
)
|
||||
}
|
||||
|
||||
fn insert_inner(
|
||||
@ -384,9 +401,7 @@ impl HeaderChain {
|
||||
proof,
|
||||
});
|
||||
|
||||
let mut pending = PendingChanges {
|
||||
best_block: None,
|
||||
};
|
||||
let mut pending = PendingChanges { best_block: None };
|
||||
|
||||
// hold candidates the whole time to guard import order.
|
||||
let mut candidates = self.candidates.write();
|
||||
@ -395,11 +410,11 @@ impl HeaderChain {
|
||||
let total_difficulty = match total_difficulty {
|
||||
Some(td) => td,
|
||||
None => {
|
||||
let parent_td =
|
||||
if number == 1 {
|
||||
let parent_td = if number == 1 {
|
||||
self.genesis_header.difficulty()
|
||||
} else {
|
||||
candidates.get(&(number - 1))
|
||||
candidates
|
||||
.get(&(number - 1))
|
||||
.and_then(|entry| entry.candidates.iter().find(|c| c.hash == parent_hash))
|
||||
.map(|c| c.total_difficulty)
|
||||
.ok_or_else(|| BlockError::UnknownParent(parent_hash))
|
||||
@ -407,13 +422,15 @@ impl HeaderChain {
|
||||
};
|
||||
|
||||
parent_td + *header.difficulty()
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
// insert headers and candidates entries and write era to disk.
|
||||
{
|
||||
let cur_era = candidates.entry(number)
|
||||
.or_insert_with(|| Entry { candidates: SmallVec::new(), canonical_hash: hash });
|
||||
let cur_era = candidates.entry(number).or_insert_with(|| Entry {
|
||||
candidates: SmallVec::new(),
|
||||
canonical_hash: hash,
|
||||
});
|
||||
cur_era.candidates.push(Candidate {
|
||||
hash,
|
||||
parent_hash,
|
||||
@ -427,7 +444,11 @@ impl HeaderChain {
|
||||
cur_era.canonical_hash = hash;
|
||||
}
|
||||
|
||||
transaction.put(self.col, era_key(number).as_bytes(), &::rlp::encode(&*cur_era))
|
||||
transaction.put(
|
||||
self.col,
|
||||
era_key(number).as_bytes(),
|
||||
&::rlp::encode(&*cur_era),
|
||||
)
|
||||
}
|
||||
|
||||
if let Some(transition) = transition {
|
||||
@ -452,8 +473,14 @@ impl HeaderChain {
|
||||
// respective candidates vectors.
|
||||
if is_new_best {
|
||||
let mut canon_hash = hash;
|
||||
for (&height, entry) in candidates.iter_mut().rev().skip_while(|&(height, _)| *height > number) {
|
||||
if height != number && entry.canonical_hash == canon_hash { break; }
|
||||
for (&height, entry) in candidates
|
||||
.iter_mut()
|
||||
.rev()
|
||||
.skip_while(|&(height, _)| *height > number)
|
||||
{
|
||||
if height != number && entry.canonical_hash == canon_hash {
|
||||
break;
|
||||
}
|
||||
|
||||
trace!(target: "chain", "Setting new canonical block {} for block height {}",
|
||||
canon_hash, height);
|
||||
@ -485,7 +512,10 @@ impl HeaderChain {
|
||||
});
|
||||
|
||||
// produce next CHT root if it's time.
|
||||
let earliest_era = *candidates.keys().next().expect("at least one era just created; qed");
|
||||
let earliest_era = *candidates
|
||||
.keys()
|
||||
.next()
|
||||
.expect("at least one era just created; qed");
|
||||
if earliest_era + HISTORY + cht::SIZE <= number {
|
||||
let cht_num = cht::block_to_cht_number(earliest_era)
|
||||
.expect("fails only for number == 0; genesis never imported; qed");
|
||||
@ -498,7 +528,8 @@ impl HeaderChain {
|
||||
// iterable function which removes the candidates as it goes
|
||||
// along. this will only be called until the CHT is complete.
|
||||
let iter = || {
|
||||
let era_entry = candidates.remove(&i)
|
||||
let era_entry = candidates
|
||||
.remove(&i)
|
||||
.expect("all eras are sequential with no gaps; qed");
|
||||
transaction.delete(self.col, era_key(i).as_bytes());
|
||||
|
||||
@ -538,7 +569,11 @@ impl HeaderChain {
|
||||
|
||||
// write the CHT root to the database.
|
||||
debug!(target: "chain", "Produced CHT {} root: {:?}", cht_num, cht_root);
|
||||
transaction.put(self.col, cht_key(cht_num).as_bytes(), &::rlp::encode(&cht_root));
|
||||
transaction.put(
|
||||
self.col,
|
||||
cht_key(cht_num).as_bytes(),
|
||||
&::rlp::encode(&cht_root),
|
||||
);
|
||||
|
||||
// update the last canonical transition proof
|
||||
if let Some((epoch_transition, header)) = last_canonical_transition {
|
||||
@ -550,7 +585,12 @@ impl HeaderChain {
|
||||
|
||||
// write the best and latest eras to the database.
|
||||
{
|
||||
let latest_num = *candidates.iter().rev().next().expect("at least one era just inserted; qed").0;
|
||||
let latest_num = *candidates
|
||||
.iter()
|
||||
.rev()
|
||||
.next()
|
||||
.expect("at least one era just inserted; qed")
|
||||
.0;
|
||||
let curr = BestAndLatest::new(best_num, latest_num);
|
||||
transaction.put(self.col, CURRENT_KEY, &::rlp::encode(&curr))
|
||||
}
|
||||
@ -574,23 +614,33 @@ impl HeaderChain {
|
||||
let header = if let Some(header) = self.block_header(BlockId::Number(h_num)) {
|
||||
header
|
||||
} else {
|
||||
let msg = format!("header of block #{} not found in DB ; database in an \
|
||||
inconsistent state", h_num);
|
||||
let msg = format!(
|
||||
"header of block #{} not found in DB ; database in an \
|
||||
inconsistent state",
|
||||
h_num
|
||||
);
|
||||
bail!(msg);
|
||||
};
|
||||
|
||||
let decoded = header.decode().expect("decoding db value failed");
|
||||
|
||||
let entry: Entry = {
|
||||
let bytes = self.db.get(self.col, era_key(h_num).as_bytes())?
|
||||
let bytes = self
|
||||
.db
|
||||
.get(self.col, era_key(h_num).as_bytes())?
|
||||
.ok_or_else(|| {
|
||||
format!("entry for era #{} not found in DB ; database \
|
||||
in an inconsistent state", h_num)
|
||||
format!(
|
||||
"entry for era #{} not found in DB ; database \
|
||||
in an inconsistent state",
|
||||
h_num
|
||||
)
|
||||
})?;
|
||||
::rlp::decode(&bytes).expect("decoding db value failed")
|
||||
};
|
||||
|
||||
let total_difficulty = entry.candidates.iter()
|
||||
let total_difficulty = entry
|
||||
.candidates
|
||||
.iter()
|
||||
.find(|c| c.hash == decoded.hash())
|
||||
.ok_or_else(|| {
|
||||
"no candidate matching block found in DB ; database in an \
|
||||
@ -603,10 +653,10 @@ impl HeaderChain {
|
||||
total_difficulty,
|
||||
chts,
|
||||
}));
|
||||
},
|
||||
}
|
||||
None => {
|
||||
break Ok(None);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
chts.push(cht);
|
||||
@ -629,12 +679,15 @@ impl HeaderChain {
|
||||
BlockId::Earliest | BlockId::Number(0) => Some(self.genesis_hash()),
|
||||
BlockId::Hash(hash) => Some(hash),
|
||||
BlockId::Number(num) => {
|
||||
if self.best_block.read().number < num { return None }
|
||||
self.candidates.read().get(&num).map(|entry| entry.canonical_hash)
|
||||
if self.best_block.read().number < num {
|
||||
return None;
|
||||
}
|
||||
BlockId::Latest => {
|
||||
Some(self.best_block.read().hash)
|
||||
self.candidates
|
||||
.read()
|
||||
.get(&num)
|
||||
.map(|entry| entry.canonical_hash)
|
||||
}
|
||||
BlockId::Latest => Some(self.best_block.read().hash),
|
||||
}
|
||||
}
|
||||
|
||||
@ -646,32 +699,35 @@ impl HeaderChain {
|
||||
|
||||
match cache.block_header(&hash) {
|
||||
Some(header) => Some(header),
|
||||
None => {
|
||||
match self.db.get(self.col, &hash) {
|
||||
Ok(db_value) => {
|
||||
db_value.map(|x| x.into_vec()).map(encoded::Header::new)
|
||||
None => match self.db.get(self.col, &hash) {
|
||||
Ok(db_value) => db_value
|
||||
.map(|x| x.into_vec())
|
||||
.map(encoded::Header::new)
|
||||
.and_then(|header| {
|
||||
cache.insert_block_header(hash, header.clone());
|
||||
Some(header)
|
||||
})
|
||||
},
|
||||
}),
|
||||
Err(e) => {
|
||||
warn!(target: "chain", "Failed to read from database: {}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
match id {
|
||||
BlockId::Earliest | BlockId::Number(0) => Some(self.genesis_header.clone()),
|
||||
BlockId::Hash(hash) if hash == self.genesis_hash() => { Some(self.genesis_header.clone()) }
|
||||
BlockId::Hash(hash) if hash == self.genesis_hash() => Some(self.genesis_header.clone()),
|
||||
BlockId::Hash(hash) => load_from_db(hash),
|
||||
BlockId::Number(num) => {
|
||||
if self.best_block.read().number < num { return None }
|
||||
if self.best_block.read().number < num {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.candidates.read().get(&num).map(|entry| entry.canonical_hash)
|
||||
self.candidates
|
||||
.read()
|
||||
.get(&num)
|
||||
.map(|entry| entry.canonical_hash)
|
||||
.and_then(load_from_db)
|
||||
}
|
||||
BlockId::Latest => {
|
||||
@ -681,7 +737,7 @@ impl HeaderChain {
|
||||
let hash = {
|
||||
let best = self.best_block.read();
|
||||
if best.number == 0 {
|
||||
return Some(self.genesis_header.clone())
|
||||
return Some(self.genesis_header.clone());
|
||||
}
|
||||
|
||||
best.hash
|
||||
@ -700,23 +756,31 @@ impl HeaderChain {
|
||||
BlockId::Earliest | BlockId::Number(0) => Some(self.genesis_header.difficulty()),
|
||||
BlockId::Hash(hash) if hash == genesis_hash => Some(self.genesis_header.difficulty()),
|
||||
BlockId::Hash(hash) => match self.block_header(BlockId::Hash(hash)) {
|
||||
Some(header) => self.candidates.read().get(&header.number())
|
||||
Some(header) => self
|
||||
.candidates
|
||||
.read()
|
||||
.get(&header.number())
|
||||
.and_then(|era| era.candidates.iter().find(|e| e.hash == hash))
|
||||
.map(|c| c.total_difficulty),
|
||||
None => None,
|
||||
},
|
||||
BlockId::Number(num) => {
|
||||
let candidates = self.candidates.read();
|
||||
if self.best_block.read().number < num { return None }
|
||||
candidates.get(&num).map(|era| era.candidates[0].total_difficulty)
|
||||
if self.best_block.read().number < num {
|
||||
return None;
|
||||
}
|
||||
BlockId::Latest => Some(self.best_block.read().total_difficulty)
|
||||
candidates
|
||||
.get(&num)
|
||||
.map(|era| era.candidates[0].total_difficulty)
|
||||
}
|
||||
BlockId::Latest => Some(self.best_block.read().total_difficulty),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the best block's header.
|
||||
pub fn best_header(&self) -> encoded::Header {
|
||||
self.block_header(BlockId::Latest).expect("Header for best block always stored; qed")
|
||||
self.block_header(BlockId::Latest)
|
||||
.expect("Header for best block always stored; qed")
|
||||
}
|
||||
|
||||
/// Get an iterator over a block and its ancestry.
|
||||
@ -737,7 +801,9 @@ impl HeaderChain {
|
||||
/// so including it within a CHT would be redundant.
|
||||
pub fn cht_root(&self, n: usize) -> Option<H256> {
|
||||
match self.db.get(self.col, cht_key(n as u64).as_bytes()) {
|
||||
Ok(db_fetch) => db_fetch.map(|bytes| ::rlp::decode(&bytes).expect("decoding value from db failed")),
|
||||
Ok(db_fetch) => {
|
||||
db_fetch.map(|bytes| ::rlp::decode(&bytes).expect("decoding value from db failed"))
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(target: "chain", "Error reading from database: {}", e);
|
||||
None
|
||||
@ -764,15 +830,24 @@ impl HeaderChain {
|
||||
Some((&height, entry)) => Some(BlockDescriptor {
|
||||
number: height,
|
||||
hash: entry.canonical_hash,
|
||||
total_difficulty: entry.candidates.iter().find(|x| x.hash == entry.canonical_hash)
|
||||
.expect("entry always stores canonical candidate; qed").total_difficulty,
|
||||
})
|
||||
total_difficulty: entry
|
||||
.candidates
|
||||
.iter()
|
||||
.find(|x| x.hash == entry.canonical_hash)
|
||||
.expect("entry always stores canonical candidate; qed")
|
||||
.total_difficulty,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get block status.
|
||||
pub fn status(&self, hash: &H256) -> BlockStatus {
|
||||
if self.db.get(self.col, hash).ok().map_or(false, |x| x.is_some()) {
|
||||
if self
|
||||
.db
|
||||
.get(self.col, hash)
|
||||
.ok()
|
||||
.map_or(false, |x| x.is_some())
|
||||
{
|
||||
BlockStatus::InChain
|
||||
} else {
|
||||
BlockStatus::Unknown
|
||||
@ -780,7 +855,12 @@ impl HeaderChain {
|
||||
}
|
||||
|
||||
/// Insert a pending transition.
|
||||
pub fn insert_pending_transition(&self, batch: &mut DBTransaction, hash: H256, t: &PendingEpochTransition) {
|
||||
pub fn insert_pending_transition(
|
||||
&self,
|
||||
batch: &mut DBTransaction,
|
||||
hash: H256,
|
||||
t: &PendingEpochTransition,
|
||||
) {
|
||||
let key = pending_transition_key(hash);
|
||||
batch.put(self.col, &*key, &*::rlp::encode(t));
|
||||
}
|
||||
@ -789,7 +869,9 @@ impl HeaderChain {
|
||||
pub fn pending_transition(&self, hash: H256) -> Option<PendingEpochTransition> {
|
||||
let key = pending_transition_key(hash);
|
||||
match self.db.get(self.col, &*key) {
|
||||
Ok(db_fetch) => db_fetch.map(|bytes| ::rlp::decode(&bytes).expect("decoding value from db failed")),
|
||||
Ok(db_fetch) => {
|
||||
db_fetch.map(|bytes| ::rlp::decode(&bytes).expect("decoding value from db failed"))
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(target: "chain", "Error reading from database: {}", e);
|
||||
None
|
||||
@ -808,9 +890,10 @@ impl HeaderChain {
|
||||
|
||||
for hdr in self.ancestry_iter(BlockId::Hash(parent_hash)) {
|
||||
if let Some(transition) = live_proofs.get(&hdr.hash()).cloned() {
|
||||
return hdr.decode().map(|decoded_hdr| {
|
||||
(decoded_hdr, transition.proof)
|
||||
}).ok();
|
||||
return hdr
|
||||
.decode()
|
||||
.map(|decoded_hdr| (decoded_hdr, transition.proof))
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
@ -859,19 +942,18 @@ impl<'a> Iterator for AncestryIter<'a> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{HeaderChain, HardcodedSync};
|
||||
use super::{HardcodedSync, HeaderChain};
|
||||
use std::sync::Arc;
|
||||
|
||||
use cache::Cache;
|
||||
use common_types::header::Header;
|
||||
use common_types::ids::BlockId;
|
||||
use common_types::{header::Header, ids::BlockId};
|
||||
use ethcore::spec::Spec;
|
||||
use ethereum_types::U256;
|
||||
use kvdb::KeyValueDB;
|
||||
use kvdb_memorydb;
|
||||
|
||||
use std::time::Duration;
|
||||
use parking_lot::Mutex;
|
||||
use std::time::Duration;
|
||||
|
||||
fn make_db() -> Arc<KeyValueDB> {
|
||||
Arc::new(kvdb_memorydb::create(0))
|
||||
@ -883,7 +965,10 @@ mod tests {
|
||||
let genesis_header = spec.genesis_header();
|
||||
let db = make_db();
|
||||
|
||||
let cache = Arc::new(Mutex::new(Cache::new(Default::default(), Duration::from_secs(6 * 3600))));
|
||||
let cache = Arc::new(Mutex::new(Cache::new(
|
||||
Default::default(),
|
||||
Duration::from_secs(6 * 3600),
|
||||
)));
|
||||
|
||||
let chain = HeaderChain::new(db.clone(), None, &spec, cache, HardcodedSync::Allow).unwrap();
|
||||
|
||||
@ -916,7 +1001,10 @@ mod tests {
|
||||
let spec = Spec::new_test();
|
||||
let genesis_header = spec.genesis_header();
|
||||
let db = make_db();
|
||||
let cache = Arc::new(Mutex::new(Cache::new(Default::default(), Duration::from_secs(6 * 3600))));
|
||||
let cache = Arc::new(Mutex::new(Cache::new(
|
||||
Default::default(),
|
||||
Duration::from_secs(6 * 3600),
|
||||
)));
|
||||
|
||||
let chain = HeaderChain::new(db.clone(), None, &spec, cache, HardcodedSync::Allow).unwrap();
|
||||
|
||||
@ -998,7 +1086,10 @@ mod tests {
|
||||
fn earliest_is_latest() {
|
||||
let spec = Spec::new_test();
|
||||
let db = make_db();
|
||||
let cache = Arc::new(Mutex::new(Cache::new(Default::default(), Duration::from_secs(6 * 3600))));
|
||||
let cache = Arc::new(Mutex::new(Cache::new(
|
||||
Default::default(),
|
||||
Duration::from_secs(6 * 3600),
|
||||
)));
|
||||
|
||||
let chain = HeaderChain::new(db.clone(), None, &spec, cache, HardcodedSync::Allow).unwrap();
|
||||
|
||||
@ -1011,11 +1102,15 @@ mod tests {
|
||||
let spec = Spec::new_test();
|
||||
let genesis_header = spec.genesis_header();
|
||||
let db = make_db();
|
||||
let cache = Arc::new(Mutex::new(Cache::new(Default::default(), Duration::from_secs(6 * 3600))));
|
||||
let cache = Arc::new(Mutex::new(Cache::new(
|
||||
Default::default(),
|
||||
Duration::from_secs(6 * 3600),
|
||||
)));
|
||||
|
||||
{
|
||||
let chain = HeaderChain::new(db.clone(), None, &spec, cache.clone(),
|
||||
HardcodedSync::Allow).unwrap();
|
||||
let chain =
|
||||
HeaderChain::new(db.clone(), None, &spec, cache.clone(), HardcodedSync::Allow)
|
||||
.unwrap();
|
||||
let mut parent_hash = genesis_header.hash();
|
||||
let mut rolling_timestamp = genesis_header.timestamp();
|
||||
for i in 1..10000 {
|
||||
@ -1035,8 +1130,8 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
let chain = HeaderChain::new(db.clone(), None, &spec, cache.clone(),
|
||||
HardcodedSync::Allow).unwrap();
|
||||
let chain =
|
||||
HeaderChain::new(db.clone(), None, &spec, cache.clone(), HardcodedSync::Allow).unwrap();
|
||||
assert!(chain.block_header(BlockId::Number(10)).is_none());
|
||||
assert!(chain.block_header(BlockId::Number(9000)).is_some());
|
||||
assert!(chain.cht_root(2).is_some());
|
||||
@ -1049,11 +1144,15 @@ mod tests {
|
||||
let spec = Spec::new_test();
|
||||
let genesis_header = spec.genesis_header();
|
||||
let db = make_db();
|
||||
let cache = Arc::new(Mutex::new(Cache::new(Default::default(), Duration::from_secs(6 * 3600))));
|
||||
let cache = Arc::new(Mutex::new(Cache::new(
|
||||
Default::default(),
|
||||
Duration::from_secs(6 * 3600),
|
||||
)));
|
||||
|
||||
{
|
||||
let chain = HeaderChain::new(db.clone(), None, &spec, cache.clone(),
|
||||
HardcodedSync::Allow).unwrap();
|
||||
let chain =
|
||||
HeaderChain::new(db.clone(), None, &spec, cache.clone(), HardcodedSync::Allow)
|
||||
.unwrap();
|
||||
let mut parent_hash = genesis_header.hash();
|
||||
let mut rolling_timestamp = genesis_header.timestamp();
|
||||
|
||||
@ -1080,7 +1179,8 @@ mod tests {
|
||||
header.set_parent_hash(parent_hash);
|
||||
header.set_number(i);
|
||||
header.set_timestamp(rolling_timestamp);
|
||||
header.set_difficulty(*genesis_header.difficulty() * U256::from(i as u32 * 1000u32));
|
||||
header
|
||||
.set_difficulty(*genesis_header.difficulty() * U256::from(i as u32 * 1000u32));
|
||||
parent_hash = header.hash();
|
||||
|
||||
let mut tx = db.transaction();
|
||||
@ -1095,8 +1195,8 @@ mod tests {
|
||||
}
|
||||
|
||||
// after restoration, non-canonical eras should still be loaded.
|
||||
let chain = HeaderChain::new(db.clone(), None, &spec, cache.clone(),
|
||||
HardcodedSync::Allow).unwrap();
|
||||
let chain =
|
||||
HeaderChain::new(db.clone(), None, &spec, cache.clone(), HardcodedSync::Allow).unwrap();
|
||||
assert_eq!(chain.block_header(BlockId::Latest).unwrap().number(), 10);
|
||||
assert!(chain.candidates.read().get(&100).is_some())
|
||||
}
|
||||
@ -1106,14 +1206,19 @@ mod tests {
|
||||
let spec = Spec::new_test();
|
||||
let genesis_header = spec.genesis_header();
|
||||
let db = make_db();
|
||||
let cache = Arc::new(Mutex::new(Cache::new(Default::default(), Duration::from_secs(6 * 3600))));
|
||||
let cache = Arc::new(Mutex::new(Cache::new(
|
||||
Default::default(),
|
||||
Duration::from_secs(6 * 3600),
|
||||
)));
|
||||
|
||||
let chain = HeaderChain::new(db.clone(), None, &spec, cache.clone(),
|
||||
HardcodedSync::Allow).unwrap();
|
||||
let chain =
|
||||
HeaderChain::new(db.clone(), None, &spec, cache.clone(), HardcodedSync::Allow).unwrap();
|
||||
|
||||
assert!(chain.block_header(BlockId::Earliest).is_some());
|
||||
assert!(chain.block_header(BlockId::Number(0)).is_some());
|
||||
assert!(chain.block_header(BlockId::Hash(genesis_header.hash())).is_some());
|
||||
assert!(chain
|
||||
.block_header(BlockId::Hash(genesis_header.hash()))
|
||||
.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1121,7 +1226,10 @@ mod tests {
|
||||
let spec = Spec::new_test();
|
||||
let genesis_header = spec.genesis_header();
|
||||
let db = make_db();
|
||||
let cache = Arc::new(Mutex::new(Cache::new(Default::default(), Duration::from_secs(6 * 3600))));
|
||||
let cache = Arc::new(Mutex::new(Cache::new(
|
||||
Default::default(),
|
||||
Duration::from_secs(6 * 3600),
|
||||
)));
|
||||
|
||||
let chain = HeaderChain::new(db.clone(), None, &spec, cache, HardcodedSync::Allow).unwrap();
|
||||
|
||||
@ -1136,11 +1244,7 @@ mod tests {
|
||||
parent_hash = header.hash();
|
||||
|
||||
let mut tx = db.transaction();
|
||||
let epoch_proof = if i == 3 {
|
||||
Some(vec![1, 2, 3, 4])
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let epoch_proof = if i == 3 { Some(vec![1, 2, 3, 4]) } else { None };
|
||||
|
||||
let pending = chain.insert(&mut tx, &header, epoch_proof).unwrap();
|
||||
db.write(tx).unwrap();
|
||||
@ -1152,13 +1256,19 @@ mod tests {
|
||||
// these 3 should end up falling back to the genesis epoch proof in DB
|
||||
for i in 0..3 {
|
||||
let hash = chain.block_hash(BlockId::Number(i)).unwrap();
|
||||
assert_eq!(chain.epoch_transition_for(hash).unwrap().1, Vec::<u8>::new());
|
||||
assert_eq!(
|
||||
chain.epoch_transition_for(hash).unwrap().1,
|
||||
Vec::<u8>::new()
|
||||
);
|
||||
}
|
||||
|
||||
// these are live.
|
||||
for i in 3..6 {
|
||||
let hash = chain.block_hash(BlockId::Number(i)).unwrap();
|
||||
assert_eq!(chain.epoch_transition_for(hash).unwrap().1, vec![1, 2, 3, 4]);
|
||||
assert_eq!(
|
||||
chain.epoch_transition_for(hash).unwrap().1,
|
||||
vec![1, 2, 3, 4]
|
||||
);
|
||||
}
|
||||
|
||||
for i in 6..10000 {
|
||||
@ -1179,7 +1289,10 @@ mod tests {
|
||||
|
||||
// no live blocks have associated epoch proofs -- make sure we aren't leaking memory.
|
||||
assert!(chain.live_epoch_proofs.read().is_empty());
|
||||
assert_eq!(chain.epoch_transition_for(parent_hash).unwrap().1, vec![1, 2, 3, 4]);
|
||||
assert_eq!(
|
||||
chain.epoch_transition_for(parent_hash).unwrap().1,
|
||||
vec![1, 2, 3, 4]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1188,9 +1301,13 @@ mod tests {
|
||||
let genesis_header = spec.genesis_header();
|
||||
let db = make_db();
|
||||
|
||||
let cache = Arc::new(Mutex::new(Cache::new(Default::default(), Duration::from_secs(6 * 3600))));
|
||||
let cache = Arc::new(Mutex::new(Cache::new(
|
||||
Default::default(),
|
||||
Duration::from_secs(6 * 3600),
|
||||
)));
|
||||
|
||||
let chain = HeaderChain::new(db.clone(), None, &spec, cache, HardcodedSync::Allow).expect("failed to instantiate a new HeaderChain");
|
||||
let chain = HeaderChain::new(db.clone(), None, &spec, cache, HardcodedSync::Allow)
|
||||
.expect("failed to instantiate a new HeaderChain");
|
||||
|
||||
let mut parent_hash = genesis_header.hash();
|
||||
let mut rolling_timestamp = genesis_header.timestamp();
|
||||
@ -1209,14 +1326,19 @@ mod tests {
|
||||
parent_hash = header.hash();
|
||||
|
||||
let mut tx = db.transaction();
|
||||
let pending = chain.insert(&mut tx, &header, None).expect("failed inserting a transaction");
|
||||
let pending = chain
|
||||
.insert(&mut tx, &header, None)
|
||||
.expect("failed inserting a transaction");
|
||||
db.write(tx).unwrap();
|
||||
chain.apply_pending(pending);
|
||||
|
||||
rolling_timestamp += 10;
|
||||
}
|
||||
|
||||
let hardcoded_sync = chain.read_hardcoded_sync().expect("failed reading hardcoded sync").expect("failed unwrapping hardcoded sync");
|
||||
let hardcoded_sync = chain
|
||||
.read_hardcoded_sync()
|
||||
.expect("failed reading hardcoded sync")
|
||||
.expect("failed unwrapping hardcoded sync");
|
||||
assert_eq!(hardcoded_sync.chts.len(), 3);
|
||||
assert_eq!(hardcoded_sync.total_difficulty, total_difficulty);
|
||||
let decoded: Header = hardcoded_sync.header.decode().expect("decoding failed");
|
||||
|
@ -16,29 +16,31 @@
|
||||
|
||||
//! Light client implementation. Stores data from light sync
|
||||
|
||||
use std::sync::{Weak, Arc};
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use ethcore::client::{ClientReport, EnvInfo, ClientIoMessage, traits::ForceUpdateSealing};
|
||||
use ethcore::engines::{epoch, EthEngine, EpochChange, EpochTransition, Proof};
|
||||
use ethcore::machine::EthereumMachine;
|
||||
use ethcore::error::{Error, EthcoreResult};
|
||||
use ethcore::verification::queue::{self, HeaderQueue};
|
||||
use ethcore::spec::{Spec, SpecHardcodedSync};
|
||||
use common_types::{
|
||||
block_status::BlockStatus, blockchain_info::BlockChainInfo, encoded, header::Header,
|
||||
ids::BlockId, BlockNumber,
|
||||
};
|
||||
use ethcore::{
|
||||
client::{traits::ForceUpdateSealing, ClientIoMessage, ClientReport, EnvInfo},
|
||||
engines::{epoch, EpochChange, EpochTransition, EthEngine, Proof},
|
||||
error::{Error, EthcoreResult},
|
||||
machine::EthereumMachine,
|
||||
spec::{Spec, SpecHardcodedSync},
|
||||
verification::queue::{self, HeaderQueue},
|
||||
};
|
||||
use ethereum_types::{H256, U256};
|
||||
use futures::{Future, IntoFuture};
|
||||
use io::IoChannel;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use ethereum_types::{H256, U256};
|
||||
use futures::{IntoFuture, Future};
|
||||
use common_types::BlockNumber;
|
||||
use common_types::block_status::BlockStatus;
|
||||
use common_types::blockchain_info::BlockChainInfo;
|
||||
use common_types::encoded;
|
||||
use common_types::header::Header;
|
||||
use common_types::ids::BlockId;
|
||||
|
||||
use kvdb::KeyValueDB;
|
||||
|
||||
use self::fetch::ChainDataFetcher;
|
||||
use self::header_chain::{AncestryIter, HeaderChain, HardcodedSync};
|
||||
use self::{
|
||||
fetch::ChainDataFetcher,
|
||||
header_chain::{AncestryIter, HardcodedSync, HeaderChain},
|
||||
};
|
||||
|
||||
use cache::Cache;
|
||||
|
||||
@ -101,7 +103,7 @@ pub trait LightChainClient: Send + Sync {
|
||||
fn score(&self, id: BlockId) -> Option<U256>;
|
||||
|
||||
/// Get an iterator over a block and its ancestry.
|
||||
fn ancestry_iter<'a>(&'a self, start: BlockId) -> Box<Iterator<Item=encoded::Header> + 'a>;
|
||||
fn ancestry_iter<'a>(&'a self, start: BlockId) -> Box<Iterator<Item = encoded::Header> + 'a>;
|
||||
|
||||
/// Get the signing chain ID.
|
||||
fn signing_chain_id(&self) -> Option<u64>;
|
||||
@ -153,7 +155,9 @@ pub trait AsLightClient {
|
||||
impl<T: LightChainClient> AsLightClient for T {
|
||||
type Client = Self;
|
||||
|
||||
fn as_light_client(&self) -> &Self { self }
|
||||
fn as_light_client(&self) -> &Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Light client implementation.
|
||||
@ -180,13 +184,22 @@ impl<T: ChainDataFetcher> Client<T> {
|
||||
spec: &Spec,
|
||||
fetcher: T,
|
||||
io_channel: IoChannel<ClientIoMessage>,
|
||||
cache: Arc<Mutex<Cache>>
|
||||
cache: Arc<Mutex<Cache>>,
|
||||
) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
queue: HeaderQueue::new(config.queue, spec.engine.clone(), io_channel, config.check_seal),
|
||||
queue: HeaderQueue::new(
|
||||
config.queue,
|
||||
spec.engine.clone(),
|
||||
io_channel,
|
||||
config.check_seal,
|
||||
),
|
||||
engine: spec.engine.clone(),
|
||||
chain: {
|
||||
let hs_cfg = if config.no_hardcoded_sync { HardcodedSync::Deny } else { HardcodedSync::Allow };
|
||||
let hs_cfg = if config.no_hardcoded_sync {
|
||||
HardcodedSync::Deny
|
||||
} else {
|
||||
HardcodedSync::Allow
|
||||
};
|
||||
HeaderChain::new(db.clone(), chain_col, &spec, cache, hs_cfg)?
|
||||
},
|
||||
report: RwLock::new(ClientReport::default()),
|
||||
@ -240,7 +253,11 @@ impl<T: ChainDataFetcher> Client<T> {
|
||||
best_block_hash: best_hdr.hash(),
|
||||
best_block_number: best_hdr.number(),
|
||||
best_block_timestamp: best_hdr.timestamp(),
|
||||
ancient_block_hash: if first_block.is_some() { Some(genesis_hash) } else { None },
|
||||
ancient_block_hash: if first_block.is_some() {
|
||||
Some(genesis_hash)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
ancient_block_number: if first_block.is_some() { Some(0) } else { None },
|
||||
first_block_hash: first_block.as_ref().map(|first| first.hash),
|
||||
first_block_number: first_block.as_ref().map(|first| first.number),
|
||||
@ -305,14 +322,13 @@ impl<T: ChainDataFetcher> Client<T> {
|
||||
trace!(target: "client", "importing block {}", num);
|
||||
|
||||
if self.verify_full && !self.check_header(&mut bad, &verified_header) {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
|
||||
let write_proof_result = match self.check_epoch_signal(&verified_header) {
|
||||
Ok(Some(proof)) => self.write_pending_proof(&verified_header, proof),
|
||||
Ok(None) => Ok(()),
|
||||
Err(e) =>
|
||||
panic!("Unable to fetch epoch transition proof: {:?}", e),
|
||||
Err(e) => panic!("Unable to fetch epoch transition proof: {:?}", e),
|
||||
};
|
||||
|
||||
if let Err(e) = write_proof_result {
|
||||
@ -322,7 +338,11 @@ impl<T: ChainDataFetcher> Client<T> {
|
||||
|
||||
let epoch_proof = self.engine.is_epoch_end_light(
|
||||
&verified_header,
|
||||
&|h| self.chain.block_header(BlockId::Hash(h)).and_then(|hdr| hdr.decode().ok()),
|
||||
&|h| {
|
||||
self.chain
|
||||
.block_header(BlockId::Hash(h))
|
||||
.and_then(|hdr| hdr.decode().ok())
|
||||
},
|
||||
&|h| self.chain.pending_transition(h),
|
||||
);
|
||||
|
||||
@ -370,7 +390,10 @@ impl<T: ChainDataFetcher> Client<T> {
|
||||
///
|
||||
/// The parameter passed to the callback is the name of the new chain spec to use after
|
||||
/// the restart.
|
||||
pub fn set_exit_handler<F>(&self, f: F) where F: Fn(String) + 'static + Send {
|
||||
pub fn set_exit_handler<F>(&self, f: F)
|
||||
where
|
||||
F: Fn(String) + 'static + Send,
|
||||
{
|
||||
*self.exit_handler.lock() = Some(Box::new(f));
|
||||
}
|
||||
|
||||
@ -428,24 +451,25 @@ impl<T: ChainDataFetcher> Client<T> {
|
||||
// should skip.
|
||||
fn check_header(&self, bad: &mut Vec<H256>, verified_header: &Header) -> bool {
|
||||
let hash = verified_header.hash();
|
||||
let parent_header = match self.chain.block_header(BlockId::Hash(*verified_header.parent_hash())) {
|
||||
let parent_header = match self
|
||||
.chain
|
||||
.block_header(BlockId::Hash(*verified_header.parent_hash()))
|
||||
{
|
||||
Some(header) => header,
|
||||
None => {
|
||||
trace!(target: "client", "No parent for block ({}, {})",
|
||||
verified_header.number(), hash);
|
||||
return false // skip import of block with missing parent.
|
||||
return false; // skip import of block with missing parent.
|
||||
}
|
||||
};
|
||||
|
||||
// Verify Block Family
|
||||
|
||||
let verify_family_result = {
|
||||
parent_header.decode()
|
||||
parent_header
|
||||
.decode()
|
||||
.map_err(|dec_err| dec_err.into())
|
||||
.and_then(|decoded| {
|
||||
self.engine.verify_block_family(&verified_header, &decoded)
|
||||
})
|
||||
|
||||
.and_then(|decoded| self.engine.verify_block_family(&verified_header, &decoded))
|
||||
};
|
||||
if let Err(e) = verify_family_result {
|
||||
warn!(target: "client", "Stage 3 block verification failed for #{} ({})\nError: {:?}",
|
||||
@ -467,14 +491,16 @@ impl<T: ChainDataFetcher> Client<T> {
|
||||
true
|
||||
}
|
||||
|
||||
fn check_epoch_signal(&self, verified_header: &Header) -> Result<Option<Proof<EthereumMachine>>, T::Error> {
|
||||
use ethcore::machine::{AuxiliaryRequest, AuxiliaryData};
|
||||
fn check_epoch_signal(
|
||||
&self,
|
||||
verified_header: &Header,
|
||||
) -> Result<Option<Proof<EthereumMachine>>, T::Error> {
|
||||
use ethcore::machine::{AuxiliaryData, AuxiliaryRequest};
|
||||
|
||||
let mut block: Option<Vec<u8>> = None;
|
||||
let mut receipts: Option<Vec<_>> = None;
|
||||
|
||||
loop {
|
||||
|
||||
let is_signal = {
|
||||
let auxiliary = AuxiliaryData {
|
||||
bytes: block.as_ref().map(|x| &x[..]),
|
||||
@ -490,10 +516,12 @@ impl<T: ChainDataFetcher> Client<T> {
|
||||
EpochChange::Yes(proof) => return Ok(Some(proof)),
|
||||
EpochChange::Unsure(unsure) => {
|
||||
let (b, r) = match unsure {
|
||||
AuxiliaryRequest::Body =>
|
||||
(Some(self.fetcher.block_body(verified_header)), None),
|
||||
AuxiliaryRequest::Receipts =>
|
||||
(None, Some(self.fetcher.block_receipts(verified_header))),
|
||||
AuxiliaryRequest::Body => {
|
||||
(Some(self.fetcher.block_body(verified_header)), None)
|
||||
}
|
||||
AuxiliaryRequest::Receipts => {
|
||||
(None, Some(self.fetcher.block_receipts(verified_header)))
|
||||
}
|
||||
AuxiliaryRequest::Both => (
|
||||
Some(self.fetcher.block_body(verified_header)),
|
||||
Some(self.fetcher.block_receipts(verified_header)),
|
||||
@ -513,22 +541,26 @@ impl<T: ChainDataFetcher> Client<T> {
|
||||
}
|
||||
|
||||
// attempts to fetch the epoch proof from the network until successful.
|
||||
fn write_pending_proof(&self, header: &Header, proof: Proof<EthereumMachine>) -> Result<(), T::Error> {
|
||||
fn write_pending_proof(
|
||||
&self,
|
||||
header: &Header,
|
||||
proof: Proof<EthereumMachine>,
|
||||
) -> Result<(), T::Error> {
|
||||
let proof = match proof {
|
||||
Proof::Known(known) => known,
|
||||
Proof::WithState(state_dependent) => {
|
||||
self.fetcher.epoch_transition(
|
||||
header.hash(),
|
||||
self.engine.clone(),
|
||||
state_dependent
|
||||
).into_future().wait()?
|
||||
}
|
||||
Proof::WithState(state_dependent) => self
|
||||
.fetcher
|
||||
.epoch_transition(header.hash(), self.engine.clone(), state_dependent)
|
||||
.into_future()
|
||||
.wait()?,
|
||||
};
|
||||
|
||||
let mut batch = self.db.transaction();
|
||||
self.chain.insert_pending_transition(&mut batch, header.hash(), &epoch::PendingTransition {
|
||||
proof,
|
||||
});
|
||||
self.chain.insert_pending_transition(
|
||||
&mut batch,
|
||||
header.hash(),
|
||||
&epoch::PendingTransition { proof },
|
||||
);
|
||||
self.db.write_buffered(batch);
|
||||
Ok(())
|
||||
}
|
||||
@ -539,7 +571,9 @@ impl<T: ChainDataFetcher> LightChainClient for Client<T> {
|
||||
Client::add_listener(self, listener)
|
||||
}
|
||||
|
||||
fn chain_info(&self) -> BlockChainInfo { Client::chain_info(self) }
|
||||
fn chain_info(&self) -> BlockChainInfo {
|
||||
Client::chain_info(self)
|
||||
}
|
||||
|
||||
fn queue_header(&self, header: Header) -> EthcoreResult<H256> {
|
||||
self.import_header(header)
|
||||
@ -561,7 +595,7 @@ impl<T: ChainDataFetcher> LightChainClient for Client<T> {
|
||||
Client::score(self, id)
|
||||
}
|
||||
|
||||
fn ancestry_iter<'a>(&'a self, start: BlockId) -> Box<Iterator<Item=encoded::Header> + 'a> {
|
||||
fn ancestry_iter<'a>(&'a self, start: BlockId) -> Box<Iterator<Item = encoded::Header> + 'a> {
|
||||
Box::new(Client::ancestry_iter(self, start))
|
||||
}
|
||||
|
||||
@ -621,11 +655,13 @@ impl<T: ChainDataFetcher> ::ethcore::client::ChainInfo for Client<T> {
|
||||
|
||||
impl<T: ChainDataFetcher> ::ethcore::client::EngineClient for Client<T> {
|
||||
fn update_sealing(&self, _force: ForceUpdateSealing) {}
|
||||
fn submit_seal(&self, _block_hash: H256, _seal: Vec<Vec<u8>>) { }
|
||||
fn broadcast_consensus_message(&self, _message: Vec<u8>) { }
|
||||
fn submit_seal(&self, _block_hash: H256, _seal: Vec<Vec<u8>>) {}
|
||||
fn broadcast_consensus_message(&self, _message: Vec<u8>) {}
|
||||
|
||||
fn epoch_transition_for(&self, parent_hash: H256) -> Option<EpochTransition> {
|
||||
self.chain.epoch_transition_for(parent_hash).map(|(hdr, proof)| EpochTransition {
|
||||
self.chain
|
||||
.epoch_transition_for(parent_hash)
|
||||
.map(|(hdr, proof)| EpochTransition {
|
||||
block_hash: hdr.hash(),
|
||||
block_number: hdr.number(),
|
||||
proof,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user