diff --git a/ethcore/native_contracts/build.rs b/ethcore/native_contracts/build.rs index 979535057..72250dba5 100644 --- a/ethcore/native_contracts/build.rs +++ b/ethcore/native_contracts/build.rs @@ -26,6 +26,7 @@ const REGISTRY_ABI: &'static str = include_str!("res/registrar.json"); const URLHINT_ABI: &'static str = include_str!("res/urlhint.json"); const SERVICE_TRANSACTION_ABI: &'static str = include_str!("res/service_transaction.json"); const SECRETSTORE_ACL_STORAGE_ABI: &'static str = include_str!("res/secretstore_acl_storage.json"); +const SECRETSTORE_SERVICE_ABI: &'static str = include_str!("res/secretstore_service.json"); const VALIDATOR_SET_ABI: &'static str = include_str!("res/validator_set.json"); const VALIDATOR_REPORT_ABI: &'static str = include_str!("res/validator_report.json"); const PEER_SET_ABI: &'static str = include_str!("res/peer_set.json"); @@ -53,6 +54,7 @@ fn main() { build_file("Urlhint", URLHINT_ABI, "urlhint.rs"); build_file("ServiceTransactionChecker", SERVICE_TRANSACTION_ABI, "service_transaction.rs"); build_file("SecretStoreAclStorage", SECRETSTORE_ACL_STORAGE_ABI, "secretstore_acl_storage.rs"); + build_file("SecretStoreService", SECRETSTORE_SERVICE_ABI, "secretstore_service.rs"); build_file("ValidatorSet", VALIDATOR_SET_ABI, "validator_set.rs"); build_file("ValidatorReport", VALIDATOR_REPORT_ABI, "validator_report.rs"); build_file("PeerSet", PEER_SET_ABI, "peer_set.rs"); diff --git a/ethcore/native_contracts/generator/src/lib.rs b/ethcore/native_contracts/generator/src/lib.rs index a8848503e..0c545abaf 100644 --- a/ethcore/native_contracts/generator/src/lib.rs +++ b/ethcore/native_contracts/generator/src/lib.rs @@ -46,7 +46,7 @@ pub fn generate_module(struct_name: &str, abi: &str) -> Result { Ok(format!(r##" use byteorder::{{BigEndian, ByteOrder}}; use futures::{{future, Future, IntoFuture}}; -use ethabi::{{Contract, Token, Event}}; +use ethabi::{{Bytes, Contract, Token, Event}}; use bigint; type BoxFuture = Box + Send>; @@ -96,7 +96,7 @@ fn generate_functions(contract: &Contract) -> Result { let inputs: Vec<_> = function.inputs.iter().map(|i| i.kind.clone()).collect(); let outputs: Vec<_> = function.outputs.iter().map(|i| i.kind.clone()).collect(); - let (input_params, to_tokens) = input_params_codegen(&inputs) + let (input_params, input_names, to_tokens) = input_params_codegen(&inputs) .map_err(|bad_type| Error::UnsupportedType(name.clone(), bad_type))?; let (output_type, decode_outputs) = output_params_codegen(&outputs) @@ -113,14 +113,14 @@ pub fn {snake_name}(&self, call: F, {params}) -> BoxFuture<{output_type}, U: IntoFuture, Error=String>, U::Future: Send + 'static {{ + let call_addr = self.address; + let call_future = match self.encode_{snake_name}_input({params_names}) {{ + Ok(call_data) => (call)(call_addr, call_data), + Err(e) => return Box::new(future::err(e)), + }}; + let function = self.contract.function(r#"{abi_name}"#) .expect("function existence checked at compile-time; qed").clone(); - let call_addr = self.address; - - let call_future = match function.encode_input(&{to_tokens}) {{ - Ok(call_data) => (call)(call_addr, call_data), - Err(e) => return Box::new(future::err(format!("Error encoding call: {{:?}}", e))), - }}; Box::new(call_future .into_future() @@ -128,12 +128,22 @@ pub fn {snake_name}(&self, call: F, {params}) -> BoxFuture<{output_type}, .map(Vec::into_iter) .and_then(|mut outputs| {decode_outputs})) }} + +/// Encode "{abi_name}" function arguments. +/// Arguments: {abi_inputs:?} +pub fn encode_{snake_name}_input(&self, {params}) -> Result {{ + self.contract.function(r#"{abi_name}"#) + .expect("function existence checked at compile-time; qed") + .encode_input(&{to_tokens}) + .map_err(|e| format!("Error encoding call: {{:?}}", e)) +}} "##, abi_name = name, abi_inputs = inputs, abi_outputs = outputs, snake_name = snake_name, params = input_params, + params_names = input_names, output_type = output_type, to_tokens = to_tokens, decode_outputs = decode_outputs, @@ -145,15 +155,17 @@ pub fn {snake_name}(&self, call: F, {params}) -> BoxFuture<{output_type}, // generate code for params in function signature and turning them into tokens. // -// two pieces of code are generated: the first gives input types for the function signature, -// and the second gives code to tokenize those inputs. +// three pieces of code are generated: the first gives input types for the function signature, +// the second one gives input parameter names to pass to another method, +// and the third gives code to tokenize those inputs. // // params of form `param_0: type_0, param_1: type_1, ...` // tokenizing code of form `{let mut tokens = Vec::new(); tokens.push({param_X}); tokens }` // // returns any unsupported param type encountered. -fn input_params_codegen(inputs: &[ParamType]) -> Result<(String, String), ParamType> { +fn input_params_codegen(inputs: &[ParamType]) -> Result<(String, String, String), ParamType> { let mut params = String::new(); + let mut params_names = String::new(); let mut to_tokens = "{ let mut tokens = Vec::new();".to_string(); for (index, param_type) in inputs.iter().enumerate() { @@ -164,11 +176,13 @@ fn input_params_codegen(inputs: &[ParamType]) -> Result<(String, String), ParamT params.push_str(&format!("{}{}: {}, ", if needs_mut { "mut " } else { "" }, param_name, rust_type)); + params_names.push_str(&format!("{}, ", param_name)); + to_tokens.push_str(&format!("tokens.push({{ {} }});", tokenize_code)); } to_tokens.push_str(" tokens }"); - Ok((params, to_tokens)) + Ok((params, params_names, to_tokens)) } // generate code for outputs of the function and detokenizing them. diff --git a/ethcore/native_contracts/res/secretstore_service.json b/ethcore/native_contracts/res/secretstore_service.json new file mode 100644 index 000000000..3c9510bb5 --- /dev/null +++ b/ethcore/native_contracts/res/secretstore_service.json @@ -0,0 +1,3 @@ +[ + {"constant":false,"inputs":[{"name":"serverKeyId","type":"bytes32"},{"name":"serverKeyPublic","type":"bytes"},{"name":"v","type":"uint8"},{"name":"r","type":"bytes32"},{"name":"s","type":"bytes32"}],"name":"serverKeyGenerated","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"} +] \ No newline at end of file diff --git a/ethcore/native_contracts/src/lib.rs b/ethcore/native_contracts/src/lib.rs index c37a13504..2138c1976 100644 --- a/ethcore/native_contracts/src/lib.rs +++ b/ethcore/native_contracts/src/lib.rs @@ -28,6 +28,7 @@ mod registry; mod urlhint; mod service_transaction; mod secretstore_acl_storage; +mod secretstore_service; mod validator_set; mod validator_report; mod peer_set; @@ -40,6 +41,7 @@ pub use self::registry::Registry; pub use self::urlhint::Urlhint; pub use self::service_transaction::ServiceTransactionChecker; pub use self::secretstore_acl_storage::SecretStoreAclStorage; +pub use self::secretstore_service::SecretStoreService; pub use self::validator_set::ValidatorSet; pub use self::validator_report::ValidatorReport; pub use self::peer_set::PeerSet; diff --git a/ethcore/native_contracts/src/secretstore_service.rs b/ethcore/native_contracts/src/secretstore_service.rs new file mode 100644 index 000000000..508cfa13b --- /dev/null +++ b/ethcore/native_contracts/src/secretstore_service.rs @@ -0,0 +1,21 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +#![allow(unused_mut, unused_variables, unused_imports)] + +//! Secret store service contract. + +include!(concat!(env!("OUT_DIR"), "/secretstore_service.rs")); diff --git a/secret_store/src/lib.rs b/secret_store/src/lib.rs index 5951be508..31a93f012 100644 --- a/secret_store/src/lib.rs +++ b/secret_store/src/lib.rs @@ -54,12 +54,12 @@ mod types; mod traits; mod acl_storage; -mod http_listener; mod key_server; mod key_storage; mod serialization; mod key_server_set; mod node_key_pair; +mod listener; use std::sync::Arc; use ethcore::client::Client; @@ -71,8 +71,6 @@ pub use self::node_key_pair::{PlainNodeKeyPair, KeyStoreNodeKeyPair}; /// Start new key server instance pub fn start(client: Arc, self_key_pair: Arc, config: ServiceConfiguration) -> Result, Error> { - use std::sync::Arc; - let acl_storage: Arc = if config.acl_check_enabled { acl_storage::OnChainAclStorage::new(&client) } else { @@ -80,7 +78,12 @@ pub fn start(client: Arc, self_key_pair: Arc, config: Servi }; let key_server_set = key_server_set::OnChainKeyServerSet::new(&client, config.cluster_config.nodes.clone())?; let key_storage = Arc::new(key_storage::PersistentKeyStorage::new(&config)?); - let key_server = key_server::KeyServerImpl::new(&config.cluster_config, key_server_set, self_key_pair, acl_storage, key_storage)?; - let listener = http_listener::KeyServerHttpListener::start(config.listener_address, key_server)?; + let key_server = Arc::new(key_server::KeyServerImpl::new(&config.cluster_config, key_server_set, self_key_pair.clone(), acl_storage, key_storage)?); + let http_listener = match config.listener_address { + Some(listener_address) => Some(listener::http_listener::KeyServerHttpListener::start(listener_address, key_server.clone())?), + None => None, + }; + let contract_listener = listener::service_contract_listener::ServiceContractListener::new(&client, key_server.clone(), self_key_pair); + let listener = listener::Listener::new(key_server, Some(http_listener), Some(contract_listener)); Ok(Box::new(listener)) } diff --git a/secret_store/src/http_listener.rs b/secret_store/src/listener/http_listener.rs similarity index 85% rename from secret_store/src/http_listener.rs rename to secret_store/src/listener/http_listener.rs index 883389365..c9f2acf16 100644 --- a/secret_store/src/http_listener.rs +++ b/secret_store/src/listener/http_listener.rs @@ -25,9 +25,9 @@ use serde::Serialize; use serde_json; use url::percent_encoding::percent_decode; -use traits::{ServerKeyGenerator, DocumentKeyServer, MessageSigner, KeyServer}; +use traits::KeyServer; use serialization::{SerializableEncryptedDocumentKeyShadow, SerializableBytes, SerializablePublic}; -use types::all::{Error, Public, MessageHash, EncryptedMessageSignature, NodeAddress, RequestSignature, ServerKeyId, +use types::all::{Error, Public, MessageHash, NodeAddress, RequestSignature, ServerKeyId, EncryptedDocumentKey, EncryptedDocumentKeyShadow}; /// Key server http-requests listener. Available requests: @@ -38,9 +38,9 @@ use types::all::{Error, Public, MessageHash, EncryptedMessageSignature, NodeAddr /// To get document key shadow: GET /shadow/{server_key_id}/{signature} /// To sign message with server key: GET /{server_key_id}/{signature}/{message_hash} -pub struct KeyServerHttpListener { - http_server: Option, - handler: Arc>, +pub struct KeyServerHttpListener { + http_server: HttpListening, + _handler: Arc, } /// Parsed http request @@ -63,77 +63,44 @@ enum Request { } /// Cloneable http handler -struct KeyServerHttpHandler { - handler: Arc>, +struct KeyServerHttpHandler { + handler: Arc, } /// Shared http handler -struct KeyServerSharedHttpHandler { - key_server: T, +struct KeyServerSharedHttpHandler { + key_server: Arc, } -impl KeyServerHttpListener where T: KeyServer + 'static { +impl KeyServerHttpListener { /// Start KeyServer http listener - pub fn start(listener_address: Option, key_server: T) -> Result { + pub fn start(listener_address: NodeAddress, key_server: Arc) -> Result { let shared_handler = Arc::new(KeyServerSharedHttpHandler { key_server: key_server, }); - let http_server = listener_address - .map(|listener_address| format!("{}:{}", listener_address.address, listener_address.port)) - .map(|listener_address| HttpServer::http(&listener_address).expect("cannot start HttpServer")) - .map(|http_server| http_server.handle(KeyServerHttpHandler { + let listener_address = format!("{}:{}", listener_address.address, listener_address.port); + let http_server = HttpServer::http(&listener_address).expect("cannot start HttpServer"); + let http_server = http_server.handle(KeyServerHttpHandler { handler: shared_handler.clone(), - }).expect("cannot start HttpServer")); + }).expect("cannot start HttpServer"); let listener = KeyServerHttpListener { http_server: http_server, - handler: shared_handler, + _handler: shared_handler, }; Ok(listener) } } -impl KeyServer for KeyServerHttpListener where T: KeyServer + 'static {} - -impl ServerKeyGenerator for KeyServerHttpListener where T: KeyServer + 'static { - fn generate_key(&self, key_id: &ServerKeyId, signature: &RequestSignature, threshold: usize) -> Result { - self.handler.key_server.generate_key(key_id, signature, threshold) - } -} - -impl DocumentKeyServer for KeyServerHttpListener where T: KeyServer + 'static { - fn store_document_key(&self, key_id: &ServerKeyId, signature: &RequestSignature, common_point: Public, encrypted_document_key: Public) -> Result<(), Error> { - self.handler.key_server.store_document_key(key_id, signature, common_point, encrypted_document_key) - } - - fn generate_document_key(&self, key_id: &ServerKeyId, signature: &RequestSignature, threshold: usize) -> Result { - self.handler.key_server.generate_document_key(key_id, signature, threshold) - } - - fn restore_document_key(&self, key_id: &ServerKeyId, signature: &RequestSignature) -> Result { - self.handler.key_server.restore_document_key(key_id, signature) - } - - fn restore_document_key_shadow(&self, key_id: &ServerKeyId, signature: &RequestSignature) -> Result { - self.handler.key_server.restore_document_key_shadow(key_id, signature) - } -} - -impl MessageSigner for KeyServerHttpListener where T: KeyServer + 'static { - fn sign_message(&self, key_id: &ServerKeyId, signature: &RequestSignature, message: MessageHash) -> Result { - self.handler.key_server.sign_message(key_id, signature, message) - } -} - -impl Drop for KeyServerHttpListener where T: KeyServer + 'static { +impl Drop for KeyServerHttpListener { fn drop(&mut self) { // ignore error as we are dropping anyway self.http_server.take().map(|mut s| { let _ = s.close(); }); } } -impl HttpHandler for KeyServerHttpHandler where T: KeyServer + 'static { +impl HttpHandler for KeyServerHttpHandler { fn handle(&self, req: HttpRequest, mut res: HttpResponse) { if req.headers.has::() { warn!(target: "secretstore", "Ignoring {}-request {} with Origin header", req.method, req.uri); @@ -310,6 +277,7 @@ fn parse_request(method: &HttpMethod, uri_path: &str) -> Request { #[cfg(test)] mod tests { + use std::sync::Arc; use hyper::method::Method as HttpMethod; use key_server::tests::DummyKeyServer; use types::all::NodeAddress; @@ -317,12 +285,12 @@ mod tests { #[test] fn http_listener_successfully_drops() { - let key_server = DummyKeyServer; + let key_server = Arc::new(DummyKeyServer); let address = NodeAddress { address: "127.0.0.1".into(), port: 9000 }; let listener = KeyServerHttpListener::start(Some(address), key_server).unwrap(); drop(listener); } - + #[test] fn parse_request_successful() { // POST /shadow/{server_key_id}/{signature}/{threshold} => generate server key diff --git a/secret_store/src/listener/mod.rs b/secret_store/src/listener/mod.rs new file mode 100644 index 000000000..858f01ee0 --- /dev/null +++ b/secret_store/src/listener/mod.rs @@ -0,0 +1,55 @@ +pub mod http_listener; +pub mod service_contract_listener; + +use std::sync::Arc; +use traits::{ServerKeyGenerator, DocumentKeyServer, MessageSigner, KeyServer}; +use types::all::{Error, Public, MessageHash, EncryptedMessageSignature, RequestSignature, ServerKeyId, + EncryptedDocumentKey, EncryptedDocumentKeyShadow}; + +pub struct Listener { + key_server: Arc, + _http: Option, + _contract: Option>, +} + +impl Listener { + pub fn new(key_server: Arc, http: Option, contract: Option>) -> Self { + Self { + key_server: key_server, + _http: http, + _contract: contract, + } + } +} + +impl KeyServer for Listener {} + +impl ServerKeyGenerator for Listener { + fn generate_key(&self, key_id: &ServerKeyId, signature: &RequestSignature, threshold: usize) -> Result { + self.key_server.generate_key(key_id, signature, threshold) + } +} + +impl DocumentKeyServer for Listener { + fn store_document_key(&self, key_id: &ServerKeyId, signature: &RequestSignature, common_point: Public, encrypted_document_key: Public) -> Result<(), Error> { + self.key_server.store_document_key(key_id, signature, common_point, encrypted_document_key) + } + + fn generate_document_key(&self, key_id: &ServerKeyId, signature: &RequestSignature, threshold: usize) -> Result { + self.key_server.generate_document_key(key_id, signature, threshold) + } + + fn restore_document_key(&self, key_id: &ServerKeyId, signature: &RequestSignature) -> Result { + self.key_server.restore_document_key(key_id, signature) + } + + fn restore_document_key_shadow(&self, key_id: &ServerKeyId, signature: &RequestSignature) -> Result { + self.key_server.restore_document_key_shadow(key_id, signature) + } +} + +impl MessageSigner for Listener { + fn sign_message(&self, key_id: &ServerKeyId, signature: &RequestSignature, message: MessageHash) -> Result { + self.key_server.sign_message(key_id, signature, message) + } +} diff --git a/secret_store/src/listener/service_contract_listener.rs b/secret_store/src/listener/service_contract_listener.rs new file mode 100644 index 000000000..d30e0100c --- /dev/null +++ b/secret_store/src/listener/service_contract_listener.rs @@ -0,0 +1,138 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +use std::sync::{Arc, Weak}; +use parking_lot::Mutex; +use ethcore::filter::Filter; +use ethcore::client::{Client, BlockChainClient, BlockId, ChainNotify}; +use native_contracts::SecretStoreService; +use ethkey::{Random, Generator, sign}; +use bytes::Bytes; +use hash::keccak; +use bigint::hash::H256; +use util::Address; +use {NodeKeyPair, KeyServer}; + +/// Name of the SecretStore contract in the registry. +const SERVICE_CONTRACT_REGISTRY_NAME: &'static str = "secretstore_service"; + +/// Key server has been added to the set. +const SERVER_KEY_REQUESTED_EVENT_NAME: &'static [u8] = &*b"ServerKeyRequested(bytes32)"; + +lazy_static! { + static ref SERVER_KEY_REQUESTED_EVENT_NAME_HASH: H256 = keccak(SERVER_KEY_REQUESTED_EVENT_NAME); +} + +/// SecretStore <-> Authority connector. Duties: +/// 1. Listen for new requests on SecretStore contract +/// 2. Redirects requests for key server +/// 3. Publishes response on SecretStore contract +pub struct ServiceContractListener { + /// Cached on-chain contract. + contract: Mutex, +} + +/// Cached on-chain Key Server set contract. +struct CachedContract { + /// Blockchain client. + client: Weak, + /// Contract. + contract: SecretStoreService, + /// Contract address. + contract_addr: Option
, + /// Key server reference. + key_server: Arc, + /// This node key pair. + self_key_pair: Arc, +} + +impl ServiceContractListener { + pub fn new(client: &Arc, key_server: Arc, self_key_pair: Arc) -> Arc { + let contract = Arc::new(ServiceContractListener { + contract: Mutex::new(CachedContract::new(client, key_server, self_key_pair)), + }); + client.add_notify(contract.clone()); + contract + } +} + +impl ChainNotify for ServiceContractListener { + fn new_blocks(&self, _imported: Vec, _invalid: Vec, enacted: Vec, _retracted: Vec, _sealed: Vec, _proposed: Vec, _duration: u64) { + if !enacted.is_empty() { + self.contract.lock().update(enacted) + } + } +} + +impl CachedContract { + pub fn new(client: &Arc, key_server: Arc, self_key_pair: Arc) -> Self { + CachedContract { + client: Arc::downgrade(client), + contract: SecretStoreService::new(Default::default()), // we aren't going to call contract => could use default address + contract_addr: client.registry_address(SERVICE_CONTRACT_REGISTRY_NAME.to_owned()), + key_server: key_server, + self_key_pair: self_key_pair, + } + } + + pub fn update(&mut self, enacted: Vec) { + if let Some(client) = self.client.upgrade() { + // update contract address + self.contract_addr = client.registry_address(SERVICE_CONTRACT_REGISTRY_NAME.to_owned()); + + // check for new key requests. + // NOTE: If contract is changed, or unregistered && there are several enacted blocks + // in single update call, some requests in old contract can be abandoned (we get contract_address from latest block) + // && check for requests in this contract for every enacted block. + // The opposite is also true (we can process requests of contract, before it actually becames a SS contract). + if let Some(contract_addr) = self.contract_addr.as_ref() { + // TODO: in case of reorgs we might process requests for free (maybe wait for several confirmations???) && publish keys without request + // TODO: in case of reorgs we might publish keys to forked branch (re-submit transaction???) + for block in enacted { + let request_logs = client.logs(Filter { + from_block: BlockId::Hash(block.clone()), + to_block: BlockId::Hash(block), + address: Some(vec![contract_addr.clone()]), + topics: vec![ + Some(vec![*SERVER_KEY_REQUESTED_EVENT_NAME_HASH]), + None, + None, + None, + ], + limit: None, + }); + + // TODO: it actually should queue tasks to separate thread + // + separate thread at the beginning should read all requests from contract + // and then start processing logs + for request in request_logs { + // TODO: check if we are selected to process this request + let key_id = request.entry.topics[1]; + let key = Random.generate().unwrap(); + let signature = sign(key.secret(), &key_id).unwrap(); + let server_key = self.key_server.generate_key(&key_id, &signature, 0).unwrap(); +println!("=== generated key: {:?}", server_key); + // publish generated key + let server_key_hash = keccak(server_key); + let signed_key = self.self_key_pair.sign(&server_key_hash).unwrap(); + let transaction_data = self.contract.encode_server_key_generated_input(key_id, server_key.to_vec(), signed_key.v(), signed_key.r().into(), signed_key.s().into()).unwrap(); + client.transact_contract(contract_addr.clone(), transaction_data).unwrap(); + } + } + } + } + } +}