Encryption, decryption and public key RPCs. (#1946)

* Fix up pending receipts details.

* Add support for additional params and registry over RPC.

* Fix tests.

* Add test, additional fix.

Fixes #1932.

* Fix up tests.

* Fix test.

* Fix test.

* Remove unused use.

* Add encryption, decryption and public-key RPCs.

* Remove &
This commit is contained in:
Gav Wood 2016-09-22 14:48:22 +02:00 committed by GitHub
parent 5e0dcd0892
commit 07b5e9a5c7
23 changed files with 122 additions and 29 deletions

1
Cargo.lock generated
View File

@ -458,6 +458,7 @@ dependencies = [
"ethcore-io 1.4.0",
"ethcore-ipc 1.4.0",
"ethcore-util 1.4.0",
"ethcrypto 0.1.0",
"ethjson 0.1.0",
"ethkey 0.2.0",
"ethstore 0.1.0",

View File

@ -322,6 +322,26 @@ impl AccountProvider {
Ok(signature)
}
/// Decrypts a message. Account must be unlocked.
pub fn decrypt(&self, account: Address, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error> {
let data = {
let mut unlocked = self.unlocked.lock();
let data = try!(unlocked.get(&account).ok_or(Error::NotUnlocked)).clone();
if let Unlock::Temp = data.unlock {
unlocked.remove(&account).expect("data exists: so key must exist: qed");
}
if let Unlock::Timed((ref start, ref duration)) = data.unlock {
if start.elapsed() > Duration::from_millis(*duration as u64) {
unlocked.remove(&account).expect("data exists: so key must exist: qed");
return Err(Error::NotUnlocked);
}
}
data
};
Ok(try!(self.sstore.decrypt(&account, &data.password, shared_mac, message)))
}
/// Unlocks an account, signs the message, and locks it again.
pub fn sign_with_password(&self, account: Address, password: String, message: Message) -> Result<Signature, Error> {
let signature = try!(self.sstore.sign(&account, &password, &message));

View File

@ -215,8 +215,11 @@ pub trait BlockChainClient : Sync + Send {
/// Extended client interface used for mining
pub trait MiningBlockChainClient : BlockChainClient {
/// Returns OpenBlock prepared for closing.
fn prepare_open_block(&self, author: Address, gas_range_target: (U256, U256), extra_data: Bytes)
-> OpenBlock;
fn prepare_open_block(&self,
author: Address,
gas_range_target: (U256, U256),
extra_data: Bytes
) -> OpenBlock;
/// Returns EvmFactory.
fn vm_factory(&self) -> &EvmFactory;

View File

@ -21,7 +21,7 @@ use std::cell::*;
use rlp::*;
use util::sha3::Hashable;
use util::{H256, Address, U256, Bytes};
use ethkey::{Signature, sign, Secret, recover, public_to_address, Error as EthkeyError};
use ethkey::{Signature, sign, Secret, Public, recover, public_to_address, Error as EthkeyError};
use error::*;
use evm::Schedule;
use header::BlockNumber;
@ -305,13 +305,18 @@ impl SignedTransaction {
match sender {
Some(s) => Ok(s),
None => {
let s = public_to_address(&try!(recover(&self.signature(), &self.unsigned.hash())));
let s = public_to_address(&try!(self.public_key()));
self.sender.set(Some(s));
Ok(s)
}
}
}
/// Returns the public key of the sender.
pub fn public_key(&self) -> Result<Public, Error> {
Ok(try!(recover(&self.signature(), &self.unsigned.hash())))
}
/// Do basic validation, checking for valid signature and minimum gas,
// TODO: consider use in block validation.
#[cfg(test)]

View File

@ -22,6 +22,7 @@ extern crate crypto as rcrypto;
extern crate secp256k1;
extern crate ethkey;
use std::fmt;
use tiny_keccak::Keccak;
use rcrypto::pbkdf2::pbkdf2;
use rcrypto::scrypt::{scrypt, ScryptParams};
@ -39,6 +40,17 @@ pub enum Error {
InvalidMessage,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
let s = match *self {
Error::Secp(ref err) => err.to_string(),
Error::InvalidMessage => "Invalid message".into(),
};
write!(f, "{}", s)
}
}
impl From<SecpError> for Error {
fn from(e: SecpError) -> Self {
Error::Secp(e)

View File

@ -1,7 +1,7 @@
[package]
name = "ethkey"
version = "0.2.0"
authors = ["debris <marek.kotewicz@gmail.com>"]
authors = ["Ethcore <admin@ethcore.io>"]
[dependencies]
rand = "0.3.14"

View File

@ -16,7 +16,7 @@
use ethkey::{KeyPair, sign, Address, Secret, Signature, Message};
use {json, Error, crypto};
use crypto::Keccak256;
use crypto::{Keccak256};
use random::Random;
use account::{Version, Cipher, Kdf, Aes128Ctr, Pbkdf2, Prf};
@ -170,6 +170,11 @@ impl SafeAccount {
sign(&secret, message).map_err(From::from)
}
pub fn decrypt(&self, password: &str, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error> {
let secret = try!(self.crypto.secret(password));
crypto::ecies::decrypt(&secret, shared_mac, message).map_err(From::from)
}
pub fn change_password(&self, old_password: &str, new_password: &str, iterations: u32) -> Result<Self, Error> {
let secret = try!(self.crypto.secret(old_password));
let result = SafeAccount {

View File

@ -17,6 +17,7 @@
use std::fmt;
use std::io::Error as IoError;
use ethkey::Error as EthKeyError;
use crypto::Error as EthCryptoError;
#[derive(Debug)]
pub enum Error {
@ -28,6 +29,7 @@ pub enum Error {
InvalidKeyFile(String),
CreationFailed,
EthKey(EthKeyError),
EthCrypto(EthCryptoError),
Custom(String),
}
@ -42,6 +44,7 @@ impl fmt::Display for Error {
Error::InvalidKeyFile(ref reason) => format!("Invalid key file: {}", reason),
Error::CreationFailed => "Account creation failed".into(),
Error::EthKey(ref err) => err.to_string(),
Error::EthCrypto(ref err) => err.to_string(),
Error::Custom(ref s) => s.clone(),
};
@ -60,3 +63,9 @@ impl From<EthKeyError> for Error {
Error::EthKey(err)
}
}
impl From<EthCryptoError> for Error {
fn from(err: EthCryptoError) -> Self {
Error::EthCrypto(err)
}
}

View File

@ -144,6 +144,11 @@ impl SecretStore for EthStore {
account.sign(password, message)
}
fn decrypt(&self, account: &Address, password: &str, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error> {
let account = try!(self.get(account));
account.decrypt(password, shared_mac, message)
}
fn uuid(&self, address: &Address) -> Result<UUID, Error> {
let account = try!(self.get(address));
Ok(account.id.into())

View File

@ -20,33 +20,24 @@ use json::UUID;
pub trait SecretStore: Send + Sync {
fn insert_account(&self, secret: Secret, password: &str) -> Result<Address, Error>;
fn import_presale(&self, json: &[u8], password: &str) -> Result<Address, Error>;
fn import_wallet(&self, json: &[u8], password: &str) -> Result<Address, Error>;
fn accounts(&self) -> Result<Vec<Address>, Error>;
fn change_password(&self, account: &Address, old_password: &str, new_password: &str) -> Result<(), Error>;
fn remove_account(&self, account: &Address, password: &str) -> Result<(), Error>;
fn sign(&self, account: &Address, password: &str, message: &Message) -> Result<Signature, Error>;
fn decrypt(&self, account: &Address, password: &str, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error>;
fn accounts(&self) -> Result<Vec<Address>, Error>;
fn uuid(&self, account: &Address) -> Result<UUID, Error>;
fn name(&self, account: &Address) -> Result<String, Error>;
fn meta(&self, account: &Address) -> Result<String, Error>;
fn set_name(&self, address: &Address, name: String) -> Result<(), Error>;
fn set_meta(&self, address: &Address, meta: String) -> Result<(), Error>;
fn local_path(&self) -> String;
fn list_geth_accounts(&self, testnet: bool) -> Vec<Address>;
fn import_geth_accounts(&self, desired: Vec<Address>, testnet: bool) -> Result<Vec<Address>, Error>;
}

View File

@ -17,6 +17,7 @@ jsonrpc-http-server = { git = "https://github.com/ethcore/jsonrpc-http-server.gi
ethcore-io = { path = "../util/io" }
ethcore-util = { path = "../util" }
ethcore = { path = "../ethcore" }
ethcrypto = { path = "../ethcrypto" }
ethkey = { path = "../ethkey" }
ethstore = { path = "../ethstore" }
ethash = { path = "../ethash" }

View File

@ -28,6 +28,7 @@ extern crate jsonrpc_http_server;
extern crate ethcore_io as io;
extern crate ethcore;
extern crate ethkey;
extern crate ethcrypto as crypto;
extern crate ethstore;
extern crate ethsync;
extern crate transient_hashmap;

View File

@ -45,7 +45,7 @@ use v1::traits::Eth;
use v1::types::{Block, BlockTransactions, BlockNumber, Bytes, SyncStatus, SyncInfo, Transaction, CallRequest, Index, Filter, Log, Receipt, H64 as RpcH64, H256 as RpcH256, H160 as RpcH160, U256 as RpcU256};
use v1::helpers::{CallRequest as CRequest, errors, limit_logs};
use v1::helpers::dispatch::{default_gas_price, dispatch_transaction};
use v1::helpers::params::{expect_no_params, params_len, from_params_default_second, from_params_default_third};
use v1::helpers::params::{expect_no_params, from_params_default_second, from_params_default_third};
/// Eth RPC options
pub struct EthClientOptions {

View File

@ -26,7 +26,7 @@ use util::Mutex;
use v1::traits::EthFilter;
use v1::types::{BlockNumber, Index, Filter, Log, H256 as RpcH256, U256 as RpcU256};
use v1::helpers::{PollFilter, PollManager, limit_logs};
use v1::helpers::params::{expect_no_params, params_len};
use v1::helpers::params::{expect_no_params};
use v1::impls::eth::pending_logs;
/// Eth filter rpc implementation.

View File

@ -26,7 +26,7 @@ use ethcore::account_provider::AccountProvider;
use v1::helpers::{errors, SigningQueue, ConfirmationPromise, ConfirmationResult, ConfirmationPayload, TransactionRequest as TRequest, FilledTransactionRequest as FilledRequest, SignerService};
use v1::helpers::dispatch::{default_gas_price, sign_and_dispatch};
use v1::traits::EthSigning;
use v1::types::{TransactionRequest, H160 as RpcH160, H256 as RpcH256, H520 as RpcH520, U256 as RpcU256};
use v1::types::{TransactionRequest, H160 as RpcH160, H256 as RpcH256, H520 as RpcH520, U256 as RpcU256, Bytes as RpcBytes};
fn fill_optional_fields<C, M>(request: TRequest, client: &C, miner: &M) -> FilledRequest
where C: MiningBlockChainClient, M: MinerService {
@ -168,6 +168,13 @@ impl<C, M> EthSigning for EthSigningQueueClient<C, M>
})
}
fn decrypt_message(&self, params: Params) -> Result<Value, Error> {
try!(self.active());
from_params::<(RpcH160, RpcBytes)>(params).and_then(|(_account, _ciphertext)| {
Err(errors::unimplemented())
})
}
fn check_request(&self, params: Params) -> Result<Value, Error> {
try!(self.active());
let mut pending = self.pending.lock();
@ -241,6 +248,14 @@ impl<C, M> EthSigning for EthSigningUnsafeClient<C, M> where
}))
}
fn decrypt_message(&self, params: Params) -> Result<Value, Error> {
try!(self.active());
from_params::<(RpcH160, RpcBytes)>(params).and_then(|(address, ciphertext)| {
let s = try!(take_weak!(self.accounts).decrypt(address.into(), &[0; 0], &ciphertext.0).map_err(|_| Error::internal_error()));
Ok(to_value(RpcBytes::from(s)))
})
}
fn post_sign(&self, _: Params) -> Result<Value, Error> {
// We don't support this in non-signer mode.
Err(errors::signer_disabled())

View File

@ -21,6 +21,7 @@ use std::collections::{BTreeMap};
use util::{RotatingLogger, Address};
use util::misc::version_data;
use crypto::ecies;
use ethkey::{Brain, Generator};
use ethstore::random_phrase;
use ethsync::{SyncProvider, ManageNetwork};
@ -29,7 +30,7 @@ use ethcore::client::{MiningBlockChainClient};
use jsonrpc_core::*;
use v1::traits::Ethcore;
use v1::types::{Bytes, U256, H160, Peers};
use v1::types::{Bytes, U256, H160, H512, Peers};
use v1::helpers::{errors, SigningQueue, SignerService, NetworkSettings};
use v1::helpers::params::expect_no_params;
@ -217,4 +218,12 @@ impl<C, M, S: ?Sized> Ethcore for EthcoreClient<C, M, S> where M: MinerService +
to_value(&H160::from(Brain::new(phrase).generate().unwrap().address()))
)
}
fn encrypt_message(&self, params: Params) -> Result<Value, Error> {
try!(self.active());
from_params::<(H512, Bytes)>(params).and_then(|(key, phrase)| {
let s = try!(ecies::encrypt(&key.into(), &[0; 0], &phrase.0).map_err(|_| Error::internal_error()));
Ok(to_value(&Bytes::from(s)))
})
}
}

View File

@ -457,7 +457,7 @@ fn rpc_eth_pending_transaction_by_hash() {
tester.miner.pending_transactions.lock().insert(H256::zero(), tx);
}
let response = r#"{"jsonrpc":"2.0","result":{"blockHash":null,"blockNumber":null,"creates":null,"from":"0x0f65fe9276bc9a24ae7083ae28e2660ef72df99e","gas":"0x5208","gasPrice":"0x1","hash":"0x41df922fd0d4766fcc02e161f8295ec28522f329ae487f14d811e4b64c8d6e31","input":"0x","nonce":"0x0","raw":"0xf85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804","to":"0x095e7baea6a6c7c4c2dfeb977efac326af552d87","transactionIndex":null,"value":"0xa"},"id":1}"#;
let response = r#"{"jsonrpc":"2.0","result":{"blockHash":null,"blockNumber":null,"creates":null,"from":"0x0f65fe9276bc9a24ae7083ae28e2660ef72df99e","gas":"0x5208","gasPrice":"0x1","hash":"0x41df922fd0d4766fcc02e161f8295ec28522f329ae487f14d811e4b64c8d6e31","input":"0x","nonce":"0x0","publicKey":"0x7ae46da747962c2ee46825839c1ef9298e3bd2e70ca2938495c3693a485ec3eaa8f196327881090ff64cf4fbb0a48485d4f83098e189ed3b7a87d5941b59f789","raw":"0xf85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804","to":"0x095e7baea6a6c7c4c2dfeb977efac326af552d87","transactionIndex":null,"value":"0xa"},"id":1}"#;
let request = r#"{
"jsonrpc": "2.0",
"method": "eth_getTransactionByHash",

View File

@ -227,6 +227,10 @@ pub trait EthSigning: Sized + Send + Sync + 'static {
/// or an error.
fn check_request(&self, _: Params) -> Result<Value, Error>;
/// Decrypt some ECIES-encrypted message.
/// First parameter is the address with which it is encrypted, second is the ciphertext.
fn decrypt_message(&self, _: Params) -> Result<Value, Error>;
/// Should be used to convert object to io delegate.
fn to_delegate(self) -> IoDelegate<Self> {
let mut delegate = IoDelegate::new(Arc::new(self));
@ -235,6 +239,7 @@ pub trait EthSigning: Sized + Send + Sync + 'static {
delegate.add_method("eth_postSign", EthSigning::post_sign);
delegate.add_method("eth_postTransaction", EthSigning::post_transaction);
delegate.add_method("eth_checkRequest", EthSigning::check_request);
delegate.add_method("ethcore_decryptMessage", EthSigning::decrypt_message);
delegate
}
}

View File

@ -76,6 +76,10 @@ pub trait Ethcore: Sized + Send + Sync + 'static {
/// Returns the value of the registrar for this network.
fn registry_address(&self, _: Params) -> Result<Value, Error>;
/// Encrypt some data with a public key under ECIES.
/// First parameter is the 512-byte destination public key, second is the message.
fn encrypt_message(&self, _: Params) -> Result<Value, Error>;
/// Should be used to convert object to io delegate.
fn to_delegate(self) -> IoDelegate<Self> {
let mut delegate = IoDelegate::new(Arc::new(self));
@ -98,6 +102,7 @@ pub trait Ethcore: Sized + Send + Sync + 'static {
delegate.add_method("ethcore_generateSecretPhrase", Ethcore::generate_secret_phrase);
delegate.add_method("ethcore_phraseToAddress", Ethcore::phrase_to_address);
delegate.add_method("ethcore_registryAddress", Ethcore::registry_address);
delegate.add_method("ethcore_encryptMessage", Ethcore::encrypt_message);
delegate
}

View File

@ -103,7 +103,7 @@ mod tests {
fn test_serialize_block_transactions() {
let t = BlockTransactions::Full(vec![Transaction::default()]);
let serialized = serde_json::to_string(&t).unwrap();
assert_eq!(serialized, r#"[{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":"0x","creates":null,"raw":"0x"}]"#);
assert_eq!(serialized, r#"[{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":"0x","creates":null,"raw":"0x","publicKey":null}]"#);
let t = BlockTransactions::Hashes(vec![H256::default().into()]);
let serialized = serde_json::to_string(&t).unwrap();

View File

@ -20,7 +20,7 @@ use std::cmp::Ordering;
use std::hash::{Hash, Hasher};
use serde;
use rustc_serialize::hex::{ToHex, FromHex};
use util::{H64 as Eth64, H256 as EthH256, H520 as EthH520, H2048 as Eth2048, H160 as Eth160};
use util::{H64 as Eth64, H160 as Eth160, H256 as Eth256, H520 as Eth520, H512 as Eth512, H2048 as Eth2048};
macro_rules! impl_hash {
($name: ident, $other: ident, $size: expr) => {
@ -144,6 +144,7 @@ macro_rules! impl_hash {
impl_hash!(H64, Eth64, 8);
impl_hash!(H160, Eth160, 20);
impl_hash!(H256, EthH256, 32);
impl_hash!(H520, EthH520, 65);
impl_hash!(H256, Eth256, 32);
impl_hash!(H512, Eth512, 64);
impl_hash!(H520, Eth520, 65);
impl_hash!(H2048, Eth2048, 256);

View File

@ -37,7 +37,7 @@ pub use self::block_number::BlockNumber;
pub use self::call_request::CallRequest;
pub use self::confirmations::{ConfirmationPayload, ConfirmationRequest, TransactionModification};
pub use self::filter::Filter;
pub use self::hash::{H64, H160, H256, H520, H2048};
pub use self::hash::{H64, H160, H256, H512, H520, H2048};
pub use self::index::Index;
pub use self::log::Log;
pub use self::sync::{SyncStatus, SyncInfo, Peers};

View File

@ -16,7 +16,7 @@
use ethcore::contract_address;
use ethcore::transaction::{LocalizedTransaction, Action, SignedTransaction};
use v1::types::{Bytes, H160, H256, U256};
use v1::types::{Bytes, H160, H256, U256, H512};
/// Transaction
#[derive(Debug, Default, Serialize)]
@ -51,6 +51,9 @@ pub struct Transaction {
pub creates: Option<H160>,
/// Raw transaction data
pub raw: Bytes,
/// Public key of the signer.
#[serde(rename="publicKey")]
pub public_key: Option<H512>,
}
impl From<LocalizedTransaction> for Transaction {
@ -75,6 +78,7 @@ impl From<LocalizedTransaction> for Transaction {
Action::Call(_) => None,
},
raw: ::rlp::encode(&t.signed).to_vec().into(),
public_key: t.public_key().ok().map(Into::into),
}
}
}
@ -101,6 +105,7 @@ impl From<SignedTransaction> for Transaction {
Action::Call(_) => None,
},
raw: ::rlp::encode(&t).to_vec().into(),
public_key: t.public_key().ok().map(Into::into),
}
}
}
@ -114,7 +119,7 @@ mod tests {
fn test_transaction_serialize() {
let t = Transaction::default();
let serialized = serde_json::to_string(&t).unwrap();
assert_eq!(serialized, r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":"0x","creates":null,"raw":"0x"}"#);
assert_eq!(serialized, r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":"0x","creates":null,"raw":"0x","publicKey":null}"#);
}
}