SecretStore: generating and retrieving decryption keys via service contract (#8029)
* SecretStore: started document keys generation via contract * fixed Cargo.lock * SecretStore: doc key contract gen tests * SecretStore: fixed log parsing * SecretStore: flush * SecretStore: secretstore_generateDocumentKey RPC * SecretStore: return encrypted_key from secretstore_generateDocumentKey * prepare to GenerateDocKey -> StoreDocKey * SecretStore: ability to identify requester via Public/Address * SecretStore: store author address instead of public in db * flush * SecretStore: flush * SecretStore: fixed test * SecretStore: flush * SecretStore: flush * SecretStore: flush * SecretStore: flush * SecretStore: start async generation session * SecretStore: process StoreDocumentKey service tasks * SecretStore: flush * SecretStore: update service contact ABI * SecretStore: flush * SecretStore: flush * SecretStore: fixed event * SecretStore: flush * SecretStore: fixed tests * SecretStore: fix broadcast shadows decryption * SecretStore: finally decryption via service contract works * SecretStore: fix for updated contract * SecretStore: restored pending requests reqding * SecretStore: fixed some TODOs * SecretStore: OnChainServiceContractAggregate * SecretStore: different names for different contracts types * SecretStore: updated contracts interfaces * SecretStore: utilize aggregate service contract * fixed compilation * SecretStore: fixes for updated contract * SecretStore: service fixes after testing * fixed cli test compilation * SecretStore: decryption_session_origin_is_known_to_all_initialized_nodes * SecretStore: added new contract listener tests * SecretStore: session_listener_works * removed optional TODO * SecretStore: fixed KeyServer shutdown * fixed warn + grumble * const durations
This commit is contained in:
parent
0a535bf485
commit
ec96091369
@ -573,7 +573,23 @@ usage! {
|
||||
|
||||
ARG arg_secretstore_contract: (String) = "none", or |c: &Config| c.secretstore.as_ref()?.service_contract.clone(),
|
||||
"--secretstore-contract=[SOURCE]",
|
||||
"Secret Store Service contract address source: none, registry (contract address is read from registry) or address.",
|
||||
"Secret Store Service contract address source: none, registry (contract address is read from secretstore_service entry in registry) or address.",
|
||||
|
||||
ARG arg_secretstore_srv_gen_contract: (String) = "none", or |c: &Config| c.secretstore.as_ref()?.service_contract_srv_gen.clone(),
|
||||
"--secretstore-srv-gen-contract=[SOURCE]",
|
||||
"Secret Store Service server key generation contract address source: none, registry (contract address is read from secretstore_service_srv_gen entry in registry) or address.",
|
||||
|
||||
ARG arg_secretstore_srv_retr_contract: (String) = "none", or |c: &Config| c.secretstore.as_ref()?.service_contract_srv_retr.clone(),
|
||||
"--secretstore-srv-retr-contract=[SOURCE]",
|
||||
"Secret Store Service server key retrieval contract address source: none, registry (contract address is read from secretstore_service_srv_retr entry in registry) or address.",
|
||||
|
||||
ARG arg_secretstore_doc_store_contract: (String) = "none", or |c: &Config| c.secretstore.as_ref()?.service_contract_doc_store.clone(),
|
||||
"--secretstore-doc-store-contract=[SOURCE]",
|
||||
"Secret Store Service document key store contract address source: none, registry (contract address is read from secretstore_service_doc_store entry in registry) or address.",
|
||||
|
||||
ARG arg_secretstore_doc_sretr_contract: (String) = "none", or |c: &Config| c.secretstore.as_ref()?.service_contract_doc_sretr.clone(),
|
||||
"--secretstore-doc-sretr-contract=[SOURCE]",
|
||||
"Secret Store Service document key shadow retrieval contract address source: none, registry (contract address is read from secretstore_service_doc_sretr entry in registry) or address.",
|
||||
|
||||
ARG arg_secretstore_nodes: (String) = "", or |c: &Config| c.secretstore.as_ref()?.nodes.as_ref().map(|vec| vec.join(",")),
|
||||
"--secretstore-nodes=[NODES]",
|
||||
@ -1133,6 +1149,10 @@ struct SecretStore {
|
||||
disable_acl_check: Option<bool>,
|
||||
disable_auto_migrate: Option<bool>,
|
||||
service_contract: Option<String>,
|
||||
service_contract_srv_gen: Option<String>,
|
||||
service_contract_srv_retr: Option<String>,
|
||||
service_contract_doc_store: Option<String>,
|
||||
service_contract_doc_sretr: Option<String>,
|
||||
self_secret: Option<String>,
|
||||
admin_public: Option<String>,
|
||||
nodes: Option<Vec<String>>,
|
||||
@ -1554,6 +1574,10 @@ mod tests {
|
||||
flag_no_secretstore_acl_check: false,
|
||||
flag_no_secretstore_auto_migrate: false,
|
||||
arg_secretstore_contract: "none".into(),
|
||||
arg_secretstore_srv_gen_contract: "none".into(),
|
||||
arg_secretstore_srv_retr_contract: "none".into(),
|
||||
arg_secretstore_doc_store_contract: "none".into(),
|
||||
arg_secretstore_doc_sretr_contract: "none".into(),
|
||||
arg_secretstore_secret: None,
|
||||
arg_secretstore_admin_public: None,
|
||||
arg_secretstore_nodes: "".into(),
|
||||
@ -1812,6 +1836,10 @@ mod tests {
|
||||
disable_acl_check: None,
|
||||
disable_auto_migrate: None,
|
||||
service_contract: None,
|
||||
service_contract_srv_gen: None,
|
||||
service_contract_srv_retr: None,
|
||||
service_contract_doc_store: None,
|
||||
service_contract_doc_sretr: None,
|
||||
self_secret: None,
|
||||
admin_public: None,
|
||||
nodes: None,
|
||||
|
@ -83,6 +83,10 @@ disable = false
|
||||
disable_http = false
|
||||
disable_acl_check = false
|
||||
service_contract = "none"
|
||||
service_contract_srv_gen = "none"
|
||||
service_contract_srv_retr = "none"
|
||||
service_contract_doc_store = "none"
|
||||
service_contract_doc_sretr = "none"
|
||||
nodes = []
|
||||
http_interface = "local"
|
||||
http_port = 8082
|
||||
|
@ -635,6 +635,10 @@ impl Configuration {
|
||||
acl_check_enabled: self.secretstore_acl_check_enabled(),
|
||||
auto_migrate_enabled: self.secretstore_auto_migrate_enabled(),
|
||||
service_contract_address: self.secretstore_service_contract_address()?,
|
||||
service_contract_srv_gen_address: self.secretstore_service_contract_srv_gen_address()?,
|
||||
service_contract_srv_retr_address: self.secretstore_service_contract_srv_retr_address()?,
|
||||
service_contract_doc_store_address: self.secretstore_service_contract_doc_store_address()?,
|
||||
service_contract_doc_sretr_address: self.secretstore_service_contract_doc_sretr_address()?,
|
||||
self_secret: self.secretstore_self_secret()?,
|
||||
nodes: self.secretstore_nodes()?,
|
||||
interface: self.secretstore_interface(),
|
||||
@ -1127,11 +1131,23 @@ impl Configuration {
|
||||
}
|
||||
|
||||
fn secretstore_service_contract_address(&self) -> Result<Option<SecretStoreContractAddress>, String> {
|
||||
Ok(match self.args.arg_secretstore_contract.as_ref() {
|
||||
"none" => None,
|
||||
"registry" => Some(SecretStoreContractAddress::Registry),
|
||||
a => Some(SecretStoreContractAddress::Address(a.parse().map_err(|e| format!("{}", e))?)),
|
||||
})
|
||||
into_secretstore_service_contract_address(self.args.arg_secretstore_contract.as_ref())
|
||||
}
|
||||
|
||||
fn secretstore_service_contract_srv_gen_address(&self) -> Result<Option<SecretStoreContractAddress>, String> {
|
||||
into_secretstore_service_contract_address(self.args.arg_secretstore_srv_gen_contract.as_ref())
|
||||
}
|
||||
|
||||
fn secretstore_service_contract_srv_retr_address(&self) -> Result<Option<SecretStoreContractAddress>, String> {
|
||||
into_secretstore_service_contract_address(self.args.arg_secretstore_srv_retr_contract.as_ref())
|
||||
}
|
||||
|
||||
fn secretstore_service_contract_doc_store_address(&self) -> Result<Option<SecretStoreContractAddress>, String> {
|
||||
into_secretstore_service_contract_address(self.args.arg_secretstore_doc_store_contract.as_ref())
|
||||
}
|
||||
|
||||
fn secretstore_service_contract_doc_sretr_address(&self) -> Result<Option<SecretStoreContractAddress>, String> {
|
||||
into_secretstore_service_contract_address(self.args.arg_secretstore_doc_sretr_contract.as_ref())
|
||||
}
|
||||
|
||||
fn ui_enabled(&self) -> bool {
|
||||
@ -1164,6 +1180,14 @@ impl Configuration {
|
||||
}
|
||||
}
|
||||
|
||||
fn into_secretstore_service_contract_address(s: &str) -> Result<Option<SecretStoreContractAddress>, String> {
|
||||
match s {
|
||||
"none" => Ok(None),
|
||||
"registry" => Ok(Some(SecretStoreContractAddress::Registry)),
|
||||
a => Ok(Some(SecretStoreContractAddress::Address(a.parse().map_err(|e| format!("{}", e))?))),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::io::Write;
|
||||
|
@ -55,6 +55,14 @@ pub struct Configuration {
|
||||
pub auto_migrate_enabled: bool,
|
||||
/// Service contract address.
|
||||
pub service_contract_address: Option<ContractAddress>,
|
||||
/// Server key generation service contract address.
|
||||
pub service_contract_srv_gen_address: Option<ContractAddress>,
|
||||
/// Server key retrieval service contract address.
|
||||
pub service_contract_srv_retr_address: Option<ContractAddress>,
|
||||
/// Document key store service contract address.
|
||||
pub service_contract_doc_store_address: Option<ContractAddress>,
|
||||
/// Document key shadow retrieval service contract address.
|
||||
pub service_contract_doc_sretr_address: Option<ContractAddress>,
|
||||
/// This node secret.
|
||||
pub self_secret: Option<NodeSecretKey>,
|
||||
/// Other nodes IDs + addresses.
|
||||
@ -108,6 +116,13 @@ mod server {
|
||||
use ansi_term::Colour::Red;
|
||||
use super::{Configuration, Dependencies, NodeSecretKey, ContractAddress};
|
||||
|
||||
fn into_service_contract_address(address: ContractAddress) -> ethcore_secretstore::ContractAddress {
|
||||
match address {
|
||||
ContractAddress::Registry => ethcore_secretstore::ContractAddress::Registry,
|
||||
ContractAddress::Address(address) => ethcore_secretstore::ContractAddress::Address(address),
|
||||
}
|
||||
}
|
||||
|
||||
/// Key server
|
||||
pub struct KeyServer {
|
||||
_key_server: Box<ethcore_secretstore::KeyServer>,
|
||||
@ -150,10 +165,11 @@ mod server {
|
||||
address: conf.http_interface.clone(),
|
||||
port: conf.http_port,
|
||||
}) } else { None },
|
||||
service_contract_address: conf.service_contract_address.map(|c| match c {
|
||||
ContractAddress::Registry => ethcore_secretstore::ContractAddress::Registry,
|
||||
ContractAddress::Address(address) => ethcore_secretstore::ContractAddress::Address(address),
|
||||
}),
|
||||
service_contract_address: conf.service_contract_address.map(into_service_contract_address),
|
||||
service_contract_srv_gen_address: conf.service_contract_srv_gen_address.map(into_service_contract_address),
|
||||
service_contract_srv_retr_address: conf.service_contract_srv_retr_address.map(into_service_contract_address),
|
||||
service_contract_doc_store_address: conf.service_contract_doc_store_address.map(into_service_contract_address),
|
||||
service_contract_doc_sretr_address: conf.service_contract_doc_sretr_address.map(into_service_contract_address),
|
||||
data_path: conf.data_path.clone(),
|
||||
acl_check_enabled: conf.acl_check_enabled,
|
||||
cluster_config: ethcore_secretstore::ClusterConfiguration {
|
||||
@ -195,6 +211,10 @@ impl Default for Configuration {
|
||||
acl_check_enabled: true,
|
||||
auto_migrate_enabled: true,
|
||||
service_contract_address: None,
|
||||
service_contract_srv_gen_address: None,
|
||||
service_contract_srv_retr_address: None,
|
||||
service_contract_doc_store_address: None,
|
||||
service_contract_doc_sretr_address: None,
|
||||
self_secret: None,
|
||||
admin_public: None,
|
||||
nodes: BTreeMap::new(),
|
||||
|
@ -1 +1,24 @@
|
||||
[{"constant":true,"inputs":[],"name":"getMigrationMaster","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"keyServer","type":"address"}],"name":"getMigrationKeyServerPublic","outputs":[{"name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"id","type":"bytes32"}],"name":"startMigration","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"keyServer","type":"address"}],"name":"getMigrationKeyServerAddress","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getMigrationId","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getNewKeyServers","outputs":[{"name":"","type":"address[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"id","type":"bytes32"}],"name":"confirmMigration","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getMigrationKeyServers","outputs":[{"name":"","type":"address[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"keyServer","type":"address"}],"name":"isMigrationConfirmed","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getCurrentKeyServers","outputs":[{"name":"","type":"address[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"keyServer","type":"address"}],"name":"getCurrentKeyServerPublic","outputs":[{"name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"keyServer","type":"address"}],"name":"getNewKeyServerAddress","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"keyServer","type":"address"}],"name":"getCurrentKeyServerAddress","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"keyServer","type":"address"}],"name":"getNewKeyServerPublic","outputs":[{"name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"keyServer","type":"address"}],"name":"KeyServerAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"keyServer","type":"address"}],"name":"KeyServerRemoved","type":"event"},{"anonymous":false,"inputs":[],"name":"MigrationStarted","type":"event"},{"anonymous":false,"inputs":[],"name":"MigrationCompleted","type":"event"}]
|
||||
[
|
||||
{"constant":true,"inputs":[],"name":"getMigrationMaster","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},
|
||||
{"constant":true,"inputs":[{"name":"keyServer","type":"address"}],"name":"getMigrationKeyServerPublic","outputs":[{"name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},
|
||||
{"constant":false,"inputs":[{"name":"id","type":"bytes32"}],"name":"startMigration","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
|
||||
{"constant":true,"inputs":[{"name":"keyServer","type":"address"}],"name":"getCurrentKeyServerIndex","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},
|
||||
{"constant":true,"inputs":[{"name":"keyServer","type":"address"}],"name":"getMigrationKeyServerAddress","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},
|
||||
{"constant":true,"inputs":[],"name":"getMigrationId","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},
|
||||
{"constant":true,"inputs":[],"name":"getNewKeyServers","outputs":[{"name":"","type":"address[]"}],"payable":false,"stateMutability":"view","type":"function"},
|
||||
{"constant":false,"inputs":[{"name":"id","type":"bytes32"}],"name":"confirmMigration","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
|
||||
{"constant":true,"inputs":[],"name":"getMigrationKeyServers","outputs":[{"name":"","type":"address[]"}],"payable":false,"stateMutability":"view","type":"function"},
|
||||
{"constant":true,"inputs":[{"name":"keyServer","type":"address"}],"name":"isMigrationConfirmed","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},
|
||||
{"constant":true,"inputs":[],"name":"getCurrentKeyServersCount","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},
|
||||
{"constant":true,"inputs":[],"name":"getCurrentKeyServers","outputs":[{"name":"","type":"address[]"}],"payable":false,"stateMutability":"view","type":"function"},
|
||||
{"constant":true,"inputs":[],"name":"getCurrentLastChange","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},
|
||||
{"constant":true,"inputs":[{"name":"keyServer","type":"address"}],"name":"getCurrentKeyServerPublic","outputs":[{"name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},
|
||||
{"constant":true,"inputs":[{"name":"keyServer","type":"address"}],"name":"getNewKeyServerAddress","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},
|
||||
{"constant":true,"inputs":[{"name":"keyServer","type":"address"}],"name":"getCurrentKeyServerAddress","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},
|
||||
{"constant":true,"inputs":[{"name":"keyServer","type":"address"}],"name":"getNewKeyServerPublic","outputs":[{"name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},
|
||||
{"constant":true,"inputs":[{"name":"index","type":"uint8"}],"name":"getCurrentKeyServer","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},
|
||||
{"anonymous":false,"inputs":[{"indexed":false,"name":"keyServer","type":"address"}],"name":"KeyServerAdded","type":"event"},
|
||||
{"anonymous":false,"inputs":[{"indexed":false,"name":"keyServer","type":"address"}],"name":"KeyServerRemoved","type":"event"},
|
||||
{"anonymous":false,"inputs":[],"name":"MigrationStarted","type":"event"},
|
||||
{"anonymous":false,"inputs":[],"name":"MigrationCompleted","type":"event"}
|
||||
]
|
@ -1,8 +1,33 @@
|
||||
[
|
||||
{"constant":true,"inputs":[{"name":"keyServer","type":"address"}],"name":"requireKeyServer","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},
|
||||
|
||||
{"constant":true,"inputs":[],"name":"serverKeyGenerationRequestsCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},
|
||||
{"constant":true,"inputs":[{"name":"index","type":"uint256"}],"name":"getServerKeyId","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},
|
||||
{"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"},
|
||||
{"constant":true,"inputs":[{"name":"serverKeyId","type":"bytes32"}],"name":"getServerKeyThreshold","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},
|
||||
{"constant":true,"inputs":[{"name":"serverKeyId","type":"bytes32"},{"name":"authority","type":"address"}],"name":"getServerKeyConfirmationStatus","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},
|
||||
{"anonymous":false,"inputs":[{"indexed":true,"name":"serverKeyId","type":"bytes32"},{"indexed":true,"name":"threshold","type":"uint256"}],"name":"ServerKeyRequested","type":"event"}
|
||||
{"constant":false,"inputs":[{"name":"serverKeyId","type":"bytes32"}],"name":"serverKeyGenerationError","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
|
||||
{"constant":true,"inputs":[{"name":"index","type":"uint256"}],"name":"getServerKeyGenerationRequest","outputs":[{"name":"","type":"bytes32"},{"name":"","type":"address"},{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},
|
||||
{"constant":false,"inputs":[{"name":"serverKeyId","type":"bytes32"},{"name":"serverKeyPublic","type":"bytes"}],"name":"serverKeyGenerated","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
|
||||
{"constant":true,"inputs":[{"name":"serverKeyId","type":"bytes32"},{"name":"keyServer","type":"address"}],"name":"isServerKeyGenerationResponseRequired","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},
|
||||
{"anonymous":false,"inputs":[{"indexed":false,"name":"serverKeyId","type":"bytes32"},{"indexed":false,"name":"author","type":"address"},{"indexed":false,"name":"threshold","type":"uint8"}],"name":"ServerKeyGenerationRequested","type":"event"},
|
||||
|
||||
{"constant":false,"inputs":[{"name":"serverKeyId","type":"bytes32"}],"name":"serverKeyRetrievalError","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
|
||||
{"constant":true,"inputs":[],"name":"serverKeyRetrievalRequestsCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},
|
||||
{"constant":true,"inputs":[{"name":"serverKeyId","type":"bytes32"},{"name":"keyServer","type":"address"}],"name":"isServerKeyRetrievalResponseRequired","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},
|
||||
{"constant":true,"inputs":[{"name":"index","type":"uint256"}],"name":"getServerKeyRetrievalRequest","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},
|
||||
{"constant":false,"inputs":[{"name":"serverKeyId","type":"bytes32"},{"name":"serverKeyPublic","type":"bytes"},{"name":"threshold","type":"uint8"}],"name":"serverKeyRetrieved","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
|
||||
{"anonymous":false,"inputs":[{"indexed":false,"name":"serverKeyId","type":"bytes32"}],"name":"ServerKeyRetrievalRequested","type":"event"},
|
||||
|
||||
{"constant":true,"inputs":[],"name":"documentKeyStoreRequestsCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},
|
||||
{"constant":false,"inputs":[{"name":"serverKeyId","type":"bytes32"}],"name":"documentKeyStoreError","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
|
||||
{"constant":false,"inputs":[{"name":"serverKeyId","type":"bytes32"}],"name":"documentKeyStored","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
|
||||
{"constant":true,"inputs":[{"name":"serverKeyId","type":"bytes32"},{"name":"keyServer","type":"address"}],"name":"isDocumentKeyStoreResponseRequired","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},
|
||||
{"constant":true,"inputs":[{"name":"index","type":"uint256"}],"name":"getDocumentKeyStoreRequest","outputs":[{"name":"","type":"bytes32"},{"name":"","type":"address"},{"name":"","type":"bytes"},{"name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},
|
||||
{"anonymous":false,"inputs":[{"indexed":false,"name":"serverKeyId","type":"bytes32"},{"indexed":false,"name":"author","type":"address"},{"indexed":false,"name":"commonPoint","type":"bytes"},{"indexed":false,"name":"encryptedPoint","type":"bytes"}],"name":"DocumentKeyStoreRequested","type":"event"},
|
||||
|
||||
{"constant":false,"inputs":[{"name":"serverKeyId","type":"bytes32"},{"name":"requester","type":"address"},{"name":"commonPoint","type":"bytes"},{"name":"threshold","type":"uint8"}],"name":"documentKeyCommonRetrieved","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
|
||||
{"constant":true,"inputs":[{"name":"serverKeyId","type":"bytes32"},{"name":"keyServer","type":"address"},{"name":"requester","type":"address"}],"name":"isDocumentKeyShadowRetrievalResponseRequired","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},
|
||||
{"constant":false,"inputs":[{"name":"serverKeyId","type":"bytes32"},{"name":"requester","type":"address"},{"name":"participants","type":"uint256"},{"name":"decryptedSecret","type":"bytes"},{"name":"shadow","type":"bytes"}],"name":"documentKeyPersonalRetrieved","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
|
||||
{"constant":false,"inputs":[{"name":"serverKeyId","type":"bytes32"},{"name":"requester","type":"address"}],"name":"documentKeyShadowRetrievalError","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
|
||||
{"constant":true,"inputs":[],"name":"documentKeyShadowRetrievalRequestsCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},
|
||||
{"constant":true,"inputs":[{"name":"index","type":"uint256"}],"name":"getDocumentKeyShadowRetrievalRequest","outputs":[{"name":"","type":"bytes32"},{"name":"","type":"bytes"},{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},
|
||||
{"anonymous":false,"inputs":[{"indexed":false,"name":"serverKeyId","type":"bytes32"},{"indexed":false,"name":"requester","type":"address"}],"name":"DocumentKeyCommonRetrievalRequested","type":"event"},
|
||||
{"anonymous":false,"inputs":[{"indexed":false,"name":"serverKeyId","type":"bytes32"},{"indexed":false,"name":"requesterPublic","type":"bytes"}],"name":"DocumentKeyPersonalRetrievalRequested","type":"event"}
|
||||
]
|
@ -17,12 +17,11 @@
|
||||
use std::sync::Arc;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use ethkey::public_to_address;
|
||||
use ethcore::client::{BlockId, ChainNotify, CallContract, RegistryInfo};
|
||||
use ethereum_types::{H256, Address};
|
||||
use bytes::Bytes;
|
||||
use trusted_client::TrustedClient;
|
||||
use types::all::{Error, ServerKeyId, Public};
|
||||
use types::all::{Error, ServerKeyId};
|
||||
|
||||
use_contract!(acl_storage, "AclStorage", "res/acl_storage.json");
|
||||
|
||||
@ -30,8 +29,8 @@ const ACL_CHECKER_CONTRACT_REGISTRY_NAME: &'static str = "secretstore_acl_checke
|
||||
|
||||
/// ACL storage of Secret Store
|
||||
pub trait AclStorage: Send + Sync {
|
||||
/// Check if requestor with `public` key can access document with hash `document`
|
||||
fn check(&self, public: &Public, document: &ServerKeyId) -> Result<bool, Error>;
|
||||
/// Check if requestor can access document with hash `document`
|
||||
fn check(&self, requester: Address, document: &ServerKeyId) -> Result<bool, Error>;
|
||||
}
|
||||
|
||||
/// On-chain ACL storage implementation.
|
||||
@ -53,7 +52,7 @@ struct CachedContract {
|
||||
/// Dummy ACL storage implementation (check always passed).
|
||||
#[derive(Default, Debug)]
|
||||
pub struct DummyAclStorage {
|
||||
prohibited: RwLock<HashMap<Public, HashSet<ServerKeyId>>>,
|
||||
prohibited: RwLock<HashMap<Address, HashSet<ServerKeyId>>>,
|
||||
}
|
||||
|
||||
impl OnChainAclStorage {
|
||||
@ -70,8 +69,8 @@ impl OnChainAclStorage {
|
||||
}
|
||||
|
||||
impl AclStorage for OnChainAclStorage {
|
||||
fn check(&self, public: &Public, document: &ServerKeyId) -> Result<bool, Error> {
|
||||
self.contract.lock().check(public, document)
|
||||
fn check(&self, requester: Address, document: &ServerKeyId) -> Result<bool, Error> {
|
||||
self.contract.lock().check(requester, document)
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,16 +103,15 @@ impl CachedContract {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check(&mut self, public: &Public, document: &ServerKeyId) -> Result<bool, Error> {
|
||||
pub fn check(&mut self, requester: Address, document: &ServerKeyId) -> Result<bool, Error> {
|
||||
if let Some(client) = self.client.get() {
|
||||
// call contract to check accesss
|
||||
match self.contract_addr {
|
||||
Some(contract_address) => {
|
||||
let address = public_to_address(&public);
|
||||
let do_call = |data| client.call_contract(BlockId::Latest, contract_address, data);
|
||||
self.contract.functions()
|
||||
.check_permissions()
|
||||
.call(address, document.clone(), &do_call)
|
||||
.call(requester, document.clone(), &do_call)
|
||||
.map_err(|e| Error::Internal(e.to_string()))
|
||||
},
|
||||
None => Err(Error::Internal("ACL checker contract is not configured".to_owned())),
|
||||
@ -127,18 +125,18 @@ impl CachedContract {
|
||||
impl DummyAclStorage {
|
||||
/// Prohibit given requestor access to given documents
|
||||
#[cfg(test)]
|
||||
pub fn prohibit(&self, public: Public, document: ServerKeyId) {
|
||||
pub fn prohibit(&self, requester: Address, document: ServerKeyId) {
|
||||
self.prohibited.write()
|
||||
.entry(public)
|
||||
.entry(requester)
|
||||
.or_insert_with(Default::default)
|
||||
.insert(document);
|
||||
}
|
||||
}
|
||||
|
||||
impl AclStorage for DummyAclStorage {
|
||||
fn check(&self, public: &Public, document: &ServerKeyId) -> Result<bool, Error> {
|
||||
fn check(&self, requester: Address, document: &ServerKeyId) -> Result<bool, Error> {
|
||||
Ok(self.prohibited.read()
|
||||
.get(public)
|
||||
.get(&requester)
|
||||
.map(|docs| !docs.contains(document))
|
||||
.unwrap_or(true))
|
||||
}
|
||||
|
@ -22,13 +22,12 @@ use futures::{self, Future};
|
||||
use parking_lot::Mutex;
|
||||
use tokio_core::reactor::Core;
|
||||
use ethcrypto;
|
||||
use ethkey;
|
||||
use super::acl_storage::AclStorage;
|
||||
use super::key_storage::KeyStorage;
|
||||
use super::key_server_set::KeyServerSet;
|
||||
use key_server_cluster::{math, ClusterCore};
|
||||
use traits::{AdminSessionsServer, ServerKeyGenerator, DocumentKeyServer, MessageSigner, KeyServer, NodeKeyPair};
|
||||
use types::all::{Error, Public, RequestSignature, ServerKeyId, EncryptedDocumentKey, EncryptedDocumentKeyShadow,
|
||||
use types::all::{Error, Public, RequestSignature, Requester, ServerKeyId, EncryptedDocumentKey, EncryptedDocumentKeyShadow,
|
||||
ClusterConfiguration, MessageHash, EncryptedMessageSignature, NodeId};
|
||||
use key_server_cluster::{ClusterClient, ClusterConfiguration as NetClusterConfiguration};
|
||||
|
||||
@ -71,39 +70,39 @@ impl AdminSessionsServer for KeyServerImpl {
|
||||
}
|
||||
|
||||
impl ServerKeyGenerator for KeyServerImpl {
|
||||
fn generate_key(&self, key_id: &ServerKeyId, signature: &RequestSignature, threshold: usize) -> Result<Public, Error> {
|
||||
fn generate_key(&self, key_id: &ServerKeyId, author: &Requester, threshold: usize) -> Result<Public, Error> {
|
||||
// recover requestor' public key from signature
|
||||
let public = ethkey::recover(signature, key_id)
|
||||
.map_err(|_| Error::BadSignature)?;
|
||||
let address = author.address(key_id).map_err(Error::InsufficientRequesterData)?;
|
||||
|
||||
// generate server key
|
||||
let generation_session = self.data.lock().cluster.new_generation_session(key_id.clone(), public, threshold)?;
|
||||
generation_session.wait(None).map_err(Into::into)
|
||||
let generation_session = self.data.lock().cluster.new_generation_session(key_id.clone(), None, address, threshold)?;
|
||||
generation_session.wait(None)
|
||||
.expect("when wait is called without timeout it always returns Some; qed")
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl DocumentKeyServer for KeyServerImpl {
|
||||
fn store_document_key(&self, key_id: &ServerKeyId, signature: &RequestSignature, common_point: Public, encrypted_document_key: Public) -> Result<(), Error> {
|
||||
fn store_document_key(&self, key_id: &ServerKeyId, author: &Requester, common_point: Public, encrypted_document_key: Public) -> Result<(), Error> {
|
||||
// store encrypted key
|
||||
let encryption_session = self.data.lock().cluster.new_encryption_session(key_id.clone(),
|
||||
signature.clone().into(), common_point, encrypted_document_key)?;
|
||||
author.clone(), common_point, encrypted_document_key)?;
|
||||
encryption_session.wait(None).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn generate_document_key(&self, key_id: &ServerKeyId, signature: &RequestSignature, threshold: usize) -> Result<EncryptedDocumentKey, Error> {
|
||||
fn generate_document_key(&self, key_id: &ServerKeyId, author: &Requester, threshold: usize) -> Result<EncryptedDocumentKey, Error> {
|
||||
// recover requestor' public key from signature
|
||||
let public = ethkey::recover(signature, key_id)
|
||||
.map_err(|_| Error::BadSignature)?;
|
||||
let public = author.public(key_id).map_err(Error::InsufficientRequesterData)?;
|
||||
|
||||
// generate server key
|
||||
let server_key = self.generate_key(key_id, signature, threshold)?;
|
||||
let server_key = self.generate_key(key_id, author, threshold)?;
|
||||
|
||||
// generate random document key
|
||||
let document_key = math::generate_random_point()?;
|
||||
let encrypted_document_key = math::encrypt_secret(&document_key, &server_key)?;
|
||||
|
||||
// store document key in the storage
|
||||
self.store_document_key(key_id, signature, encrypted_document_key.common_point, encrypted_document_key.encrypted_point)?;
|
||||
self.store_document_key(key_id, author, encrypted_document_key.common_point, encrypted_document_key.encrypted_point)?;
|
||||
|
||||
// encrypt document key with requestor public key
|
||||
let document_key = ethcrypto::ecies::encrypt(&public, ðcrypto::DEFAULT_MAC, &document_key)
|
||||
@ -111,15 +110,16 @@ impl DocumentKeyServer for KeyServerImpl {
|
||||
Ok(document_key)
|
||||
}
|
||||
|
||||
fn restore_document_key(&self, key_id: &ServerKeyId, signature: &RequestSignature) -> Result<EncryptedDocumentKey, Error> {
|
||||
fn restore_document_key(&self, key_id: &ServerKeyId, requester: &Requester) -> Result<EncryptedDocumentKey, Error> {
|
||||
// recover requestor' public key from signature
|
||||
let public = ethkey::recover(signature, key_id)
|
||||
.map_err(|_| Error::BadSignature)?;
|
||||
let public = requester.public(key_id).map_err(Error::InsufficientRequesterData)?;
|
||||
|
||||
// decrypt document key
|
||||
let decryption_session = self.data.lock().cluster.new_decryption_session(key_id.clone(),
|
||||
signature.clone().into(), None, false)?;
|
||||
let document_key = decryption_session.wait()?.decrypted_secret;
|
||||
None, requester.clone(), None, false, false)?;
|
||||
let document_key = decryption_session.wait(None)
|
||||
.expect("when wait is called without timeout it always returns Some; qed")?
|
||||
.decrypted_secret;
|
||||
|
||||
// encrypt document key with requestor public key
|
||||
let document_key = ethcrypto::ecies::encrypt(&public, ðcrypto::DEFAULT_MAC, &document_key)
|
||||
@ -127,22 +127,23 @@ impl DocumentKeyServer for KeyServerImpl {
|
||||
Ok(document_key)
|
||||
}
|
||||
|
||||
fn restore_document_key_shadow(&self, key_id: &ServerKeyId, signature: &RequestSignature) -> Result<EncryptedDocumentKeyShadow, Error> {
|
||||
fn restore_document_key_shadow(&self, key_id: &ServerKeyId, requester: &Requester) -> Result<EncryptedDocumentKeyShadow, Error> {
|
||||
let decryption_session = self.data.lock().cluster.new_decryption_session(key_id.clone(),
|
||||
signature.clone().into(), None, true)?;
|
||||
decryption_session.wait().map_err(Into::into)
|
||||
None, requester.clone(), None, true, false)?;
|
||||
decryption_session.wait(None)
|
||||
.expect("when wait is called without timeout it always returns Some; qed")
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageSigner for KeyServerImpl {
|
||||
fn sign_message_schnorr(&self, key_id: &ServerKeyId, signature: &RequestSignature, message: MessageHash) -> Result<EncryptedMessageSignature, Error> {
|
||||
fn sign_message_schnorr(&self, key_id: &ServerKeyId, requester: &Requester, message: MessageHash) -> Result<EncryptedMessageSignature, Error> {
|
||||
// recover requestor' public key from signature
|
||||
let public = ethkey::recover(signature, key_id)
|
||||
.map_err(|_| Error::BadSignature)?;
|
||||
let public = requester.public(key_id).map_err(Error::InsufficientRequesterData)?;
|
||||
|
||||
// sign message
|
||||
let signing_session = self.data.lock().cluster.new_schnorr_signing_session(key_id.clone(),
|
||||
signature.clone().into(), None, message)?;
|
||||
requester.clone().into(), None, message)?;
|
||||
let message_signature = signing_session.wait()?;
|
||||
|
||||
// compose two message signature components into single one
|
||||
@ -156,14 +157,13 @@ impl MessageSigner for KeyServerImpl {
|
||||
Ok(message_signature)
|
||||
}
|
||||
|
||||
fn sign_message_ecdsa(&self, key_id: &ServerKeyId, signature: &RequestSignature, message: MessageHash) -> Result<EncryptedMessageSignature, Error> {
|
||||
fn sign_message_ecdsa(&self, key_id: &ServerKeyId, requester: &Requester, message: MessageHash) -> Result<EncryptedMessageSignature, Error> {
|
||||
// recover requestor' public key from signature
|
||||
let public = ethkey::recover(signature, key_id)
|
||||
.map_err(|_| Error::BadSignature)?;
|
||||
let public = requester.public(key_id).map_err(Error::InsufficientRequesterData)?;
|
||||
|
||||
// sign message
|
||||
let signing_session = self.data.lock().cluster.new_ecdsa_signing_session(key_id.clone(),
|
||||
signature.clone().into(), None, message)?;
|
||||
requester.clone().into(), None, message)?;
|
||||
let message_signature = signing_session.wait()?;
|
||||
|
||||
// encrypt combined signature with requestor public key
|
||||
@ -177,7 +177,7 @@ impl KeyServerCore {
|
||||
pub fn new(config: &ClusterConfiguration, key_server_set: Arc<KeyServerSet>, self_key_pair: Arc<NodeKeyPair>, acl_storage: Arc<AclStorage>, key_storage: Arc<KeyStorage>) -> Result<Self, Error> {
|
||||
let config = NetClusterConfiguration {
|
||||
threads: config.threads,
|
||||
self_key_pair: self_key_pair,
|
||||
self_key_pair: self_key_pair.clone(),
|
||||
listen_address: (config.listener_address.address.clone(), config.listener_address.port),
|
||||
key_server_set: key_server_set,
|
||||
allow_connecting_to_higher_nodes: config.allow_connecting_to_higher_nodes,
|
||||
@ -189,7 +189,7 @@ impl KeyServerCore {
|
||||
|
||||
let (stop, stopped) = futures::oneshot();
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let handle = thread::spawn(move || {
|
||||
let handle = thread::Builder::new().name("KeyServerLoop".into()).spawn(move || {
|
||||
let mut el = match Core::new() {
|
||||
Ok(el) => el,
|
||||
Err(e) => {
|
||||
@ -202,7 +202,9 @@ impl KeyServerCore {
|
||||
let cluster_client = cluster.and_then(|c| c.run().map(|_| c.client()));
|
||||
tx.send(cluster_client.map_err(Into::into)).expect("Rx is blocking upper thread.");
|
||||
let _ = el.run(futures::empty().select(stopped));
|
||||
});
|
||||
|
||||
trace!(target: "secretstore_net", "{}: KeyServerLoop thread stopped", self_key_pair.public());
|
||||
}).map_err(|e| Error::Internal(format!("{}", e)))?;
|
||||
let cluster = rx.recv().map_err(|e| Error::Internal(format!("error initializing event loop: {}", e)))??;
|
||||
|
||||
Ok(KeyServerCore {
|
||||
@ -225,26 +227,25 @@ pub mod tests {
|
||||
use std::collections::BTreeSet;
|
||||
use std::time;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::net::SocketAddr;
|
||||
use std::collections::BTreeMap;
|
||||
use ethcrypto;
|
||||
use ethkey::{self, Secret, Random, Generator, verify_public};
|
||||
use acl_storage::DummyAclStorage;
|
||||
use key_storage::KeyStorage;
|
||||
use key_storage::tests::DummyKeyStorage;
|
||||
use node_key_pair::PlainNodeKeyPair;
|
||||
use key_server_set::tests::MapKeyServerSet;
|
||||
use key_server_cluster::math;
|
||||
use ethereum_types::{H256, H520};
|
||||
use types::all::{Error, Public, ClusterConfiguration, NodeAddress, RequestSignature, ServerKeyId,
|
||||
EncryptedDocumentKey, EncryptedDocumentKeyShadow, MessageHash, EncryptedMessageSignature, NodeId};
|
||||
EncryptedDocumentKey, EncryptedDocumentKeyShadow, MessageHash, EncryptedMessageSignature,
|
||||
Requester, NodeId};
|
||||
use traits::{AdminSessionsServer, ServerKeyGenerator, DocumentKeyServer, MessageSigner, KeyServer};
|
||||
use super::KeyServerImpl;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DummyKeyServer {
|
||||
pub generation_requests_count: AtomicUsize,
|
||||
}
|
||||
pub struct DummyKeyServer;
|
||||
|
||||
impl KeyServer for DummyKeyServer {}
|
||||
|
||||
@ -255,41 +256,40 @@ pub mod tests {
|
||||
}
|
||||
|
||||
impl ServerKeyGenerator for DummyKeyServer {
|
||||
fn generate_key(&self, _key_id: &ServerKeyId, _signature: &RequestSignature, _threshold: usize) -> Result<Public, Error> {
|
||||
self.generation_requests_count.fetch_add(1, Ordering::Relaxed);
|
||||
Err(Error::Internal("test error".into()))
|
||||
fn generate_key(&self, _key_id: &ServerKeyId, _author: &Requester, _threshold: usize) -> Result<Public, Error> {
|
||||
unimplemented!("test-only")
|
||||
}
|
||||
}
|
||||
|
||||
impl DocumentKeyServer for DummyKeyServer {
|
||||
fn store_document_key(&self, _key_id: &ServerKeyId, _signature: &RequestSignature, _common_point: Public, _encrypted_document_key: Public) -> Result<(), Error> {
|
||||
fn store_document_key(&self, _key_id: &ServerKeyId, _author: &Requester, _common_point: Public, _encrypted_document_key: Public) -> Result<(), Error> {
|
||||
unimplemented!("test-only")
|
||||
}
|
||||
|
||||
fn generate_document_key(&self, _key_id: &ServerKeyId, _signature: &RequestSignature, _threshold: usize) -> Result<EncryptedDocumentKey, Error> {
|
||||
fn generate_document_key(&self, _key_id: &ServerKeyId, _author: &Requester, _threshold: usize) -> Result<EncryptedDocumentKey, Error> {
|
||||
unimplemented!("test-only")
|
||||
}
|
||||
|
||||
fn restore_document_key(&self, _key_id: &ServerKeyId, _signature: &RequestSignature) -> Result<EncryptedDocumentKey, Error> {
|
||||
fn restore_document_key(&self, _key_id: &ServerKeyId, _requester: &Requester) -> Result<EncryptedDocumentKey, Error> {
|
||||
unimplemented!("test-only")
|
||||
}
|
||||
|
||||
fn restore_document_key_shadow(&self, _key_id: &ServerKeyId, _signature: &RequestSignature) -> Result<EncryptedDocumentKeyShadow, Error> {
|
||||
fn restore_document_key_shadow(&self, _key_id: &ServerKeyId, _requester: &Requester) -> Result<EncryptedDocumentKeyShadow, Error> {
|
||||
unimplemented!("test-only")
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageSigner for DummyKeyServer {
|
||||
fn sign_message_schnorr(&self, _key_id: &ServerKeyId, _signature: &RequestSignature, _message: MessageHash) -> Result<EncryptedMessageSignature, Error> {
|
||||
fn sign_message_schnorr(&self, _key_id: &ServerKeyId, _requester: &Requester, _message: MessageHash) -> Result<EncryptedMessageSignature, Error> {
|
||||
unimplemented!("test-only")
|
||||
}
|
||||
|
||||
fn sign_message_ecdsa(&self, _key_id: &ServerKeyId, _signature: &RequestSignature, _message: MessageHash) -> Result<EncryptedMessageSignature, Error> {
|
||||
fn sign_message_ecdsa(&self, _key_id: &ServerKeyId, _requester: &Requester, _message: MessageHash) -> Result<EncryptedMessageSignature, Error> {
|
||||
unimplemented!("test-only")
|
||||
}
|
||||
}
|
||||
|
||||
fn make_key_servers(start_port: u16, num_nodes: usize) -> Vec<KeyServerImpl> {
|
||||
fn make_key_servers(start_port: u16, num_nodes: usize) -> (Vec<KeyServerImpl>, Vec<Arc<DummyKeyStorage>>) {
|
||||
let key_pairs: Vec<_> = (0..num_nodes).map(|_| Random.generate().unwrap()).collect();
|
||||
let configs: Vec<_> = (0..num_nodes).map(|i| ClusterConfiguration {
|
||||
threads: 1,
|
||||
@ -309,11 +309,12 @@ pub mod tests {
|
||||
let key_servers_set: BTreeMap<Public, SocketAddr> = configs[0].nodes.iter()
|
||||
.map(|(k, a)| (k.clone(), format!("{}:{}", a.address, a.port).parse().unwrap()))
|
||||
.collect();
|
||||
let key_storages = (0..num_nodes).map(|_| Arc::new(DummyKeyStorage::default())).collect::<Vec<_>>();
|
||||
let key_servers: Vec<_> = configs.into_iter().enumerate().map(|(i, cfg)|
|
||||
KeyServerImpl::new(&cfg, Arc::new(MapKeyServerSet::new(key_servers_set.clone())),
|
||||
Arc::new(PlainNodeKeyPair::new(key_pairs[i].clone())),
|
||||
Arc::new(DummyAclStorage::default()),
|
||||
Arc::new(DummyKeyStorage::default())).unwrap()
|
||||
key_storages[i].clone()).unwrap()
|
||||
).collect();
|
||||
|
||||
// wait until connections are established. It is fast => do not bother with events here
|
||||
@ -343,25 +344,25 @@ pub mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
key_servers
|
||||
(key_servers, key_storages)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn document_key_generation_and_retrievement_works_over_network_with_single_node() {
|
||||
//::logger::init_log();
|
||||
let key_servers = make_key_servers(6070, 1);
|
||||
let (key_servers, _) = make_key_servers(6070, 1);
|
||||
|
||||
// generate document key
|
||||
let threshold = 0;
|
||||
let document = Random.generate().unwrap().secret().clone();
|
||||
let secret = Random.generate().unwrap().secret().clone();
|
||||
let signature = ethkey::sign(&secret, &document).unwrap();
|
||||
let generated_key = key_servers[0].generate_document_key(&document, &signature, threshold).unwrap();
|
||||
let generated_key = key_servers[0].generate_document_key(&document, &signature.clone().into(), threshold).unwrap();
|
||||
let generated_key = ethcrypto::ecies::decrypt(&secret, ðcrypto::DEFAULT_MAC, &generated_key).unwrap();
|
||||
|
||||
// now let's try to retrieve key back
|
||||
for key_server in key_servers.iter() {
|
||||
let retrieved_key = key_server.restore_document_key(&document, &signature).unwrap();
|
||||
let retrieved_key = key_server.restore_document_key(&document, &signature.clone().into()).unwrap();
|
||||
let retrieved_key = ethcrypto::ecies::decrypt(&secret, ðcrypto::DEFAULT_MAC, &retrieved_key).unwrap();
|
||||
assert_eq!(retrieved_key, generated_key);
|
||||
}
|
||||
@ -370,7 +371,7 @@ pub mod tests {
|
||||
#[test]
|
||||
fn document_key_generation_and_retrievement_works_over_network_with_3_nodes() {
|
||||
//::logger::init_log();
|
||||
let key_servers = make_key_servers(6080, 3);
|
||||
let (key_servers, key_storages) = make_key_servers(6080, 3);
|
||||
|
||||
let test_cases = [0, 1, 2];
|
||||
for threshold in &test_cases {
|
||||
@ -378,14 +379,18 @@ pub mod tests {
|
||||
let document = Random.generate().unwrap().secret().clone();
|
||||
let secret = Random.generate().unwrap().secret().clone();
|
||||
let signature = ethkey::sign(&secret, &document).unwrap();
|
||||
let generated_key = key_servers[0].generate_document_key(&document, &signature, *threshold).unwrap();
|
||||
let generated_key = key_servers[0].generate_document_key(&document, &signature.clone().into(), *threshold).unwrap();
|
||||
let generated_key = ethcrypto::ecies::decrypt(&secret, ðcrypto::DEFAULT_MAC, &generated_key).unwrap();
|
||||
|
||||
// now let's try to retrieve key back
|
||||
for key_server in key_servers.iter() {
|
||||
let retrieved_key = key_server.restore_document_key(&document, &signature).unwrap();
|
||||
for (i, key_server) in key_servers.iter().enumerate() {
|
||||
let retrieved_key = key_server.restore_document_key(&document, &signature.clone().into()).unwrap();
|
||||
let retrieved_key = ethcrypto::ecies::decrypt(&secret, ðcrypto::DEFAULT_MAC, &retrieved_key).unwrap();
|
||||
assert_eq!(retrieved_key, generated_key);
|
||||
|
||||
let key_share = key_storages[i].get(&document).unwrap().unwrap();
|
||||
assert!(key_share.common_point.is_some());
|
||||
assert!(key_share.encrypted_point.is_some());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -393,7 +398,7 @@ pub mod tests {
|
||||
#[test]
|
||||
fn server_key_generation_and_storing_document_key_works_over_network_with_3_nodes() {
|
||||
//::logger::init_log();
|
||||
let key_servers = make_key_servers(6090, 3);
|
||||
let (key_servers, _) = make_key_servers(6090, 3);
|
||||
|
||||
let test_cases = [0, 1, 2];
|
||||
for threshold in &test_cases {
|
||||
@ -401,18 +406,19 @@ pub mod tests {
|
||||
let server_key_id = Random.generate().unwrap().secret().clone();
|
||||
let requestor_secret = Random.generate().unwrap().secret().clone();
|
||||
let signature = ethkey::sign(&requestor_secret, &server_key_id).unwrap();
|
||||
let server_public = key_servers[0].generate_key(&server_key_id, &signature, *threshold).unwrap();
|
||||
let server_public = key_servers[0].generate_key(&server_key_id, &signature.clone().into(), *threshold).unwrap();
|
||||
|
||||
// generate document key (this is done by KS client so that document key is unknown to any KS)
|
||||
let generated_key = Random.generate().unwrap().public().clone();
|
||||
let encrypted_document_key = math::encrypt_secret(&generated_key, &server_public).unwrap();
|
||||
|
||||
// store document key
|
||||
key_servers[0].store_document_key(&server_key_id, &signature, encrypted_document_key.common_point, encrypted_document_key.encrypted_point).unwrap();
|
||||
key_servers[0].store_document_key(&server_key_id, &signature.clone().into(),
|
||||
encrypted_document_key.common_point, encrypted_document_key.encrypted_point).unwrap();
|
||||
|
||||
// now let's try to retrieve key back
|
||||
for key_server in key_servers.iter() {
|
||||
let retrieved_key = key_server.restore_document_key(&server_key_id, &signature).unwrap();
|
||||
let retrieved_key = key_server.restore_document_key(&server_key_id, &signature.clone().into()).unwrap();
|
||||
let retrieved_key = ethcrypto::ecies::decrypt(&requestor_secret, ðcrypto::DEFAULT_MAC, &retrieved_key).unwrap();
|
||||
let retrieved_key = Public::from_slice(&retrieved_key);
|
||||
assert_eq!(retrieved_key, generated_key);
|
||||
@ -423,7 +429,7 @@ pub mod tests {
|
||||
#[test]
|
||||
fn server_key_generation_and_message_signing_works_over_network_with_3_nodes() {
|
||||
//::logger::init_log();
|
||||
let key_servers = make_key_servers(6100, 3);
|
||||
let (key_servers, _) = make_key_servers(6100, 3);
|
||||
|
||||
let test_cases = [0, 1, 2];
|
||||
for threshold in &test_cases {
|
||||
@ -431,11 +437,11 @@ pub mod tests {
|
||||
let server_key_id = Random.generate().unwrap().secret().clone();
|
||||
let requestor_secret = Random.generate().unwrap().secret().clone();
|
||||
let signature = ethkey::sign(&requestor_secret, &server_key_id).unwrap();
|
||||
let server_public = key_servers[0].generate_key(&server_key_id, &signature, *threshold).unwrap();
|
||||
let server_public = key_servers[0].generate_key(&server_key_id, &signature.clone().into(), *threshold).unwrap();
|
||||
|
||||
// sign message
|
||||
let message_hash = H256::from(42);
|
||||
let combined_signature = key_servers[0].sign_message_schnorr(&server_key_id, &signature, message_hash.clone()).unwrap();
|
||||
let combined_signature = key_servers[0].sign_message_schnorr(&server_key_id, &signature.into(), message_hash.clone()).unwrap();
|
||||
let combined_signature = ethcrypto::ecies::decrypt(&requestor_secret, ðcrypto::DEFAULT_MAC, &combined_signature).unwrap();
|
||||
let signature_c = Secret::from_slice(&combined_signature[..32]);
|
||||
let signature_s = Secret::from_slice(&combined_signature[32..]);
|
||||
@ -448,21 +454,21 @@ pub mod tests {
|
||||
#[test]
|
||||
fn decryption_session_is_delegated_when_node_does_not_have_key_share() {
|
||||
//::logger::init_log();
|
||||
let key_servers = make_key_servers(6110, 3);
|
||||
let (key_servers, _) = make_key_servers(6110, 3);
|
||||
|
||||
// generate document key
|
||||
let threshold = 0;
|
||||
let document = Random.generate().unwrap().secret().clone();
|
||||
let secret = Random.generate().unwrap().secret().clone();
|
||||
let signature = ethkey::sign(&secret, &document).unwrap();
|
||||
let generated_key = key_servers[0].generate_document_key(&document, &signature, threshold).unwrap();
|
||||
let generated_key = key_servers[0].generate_document_key(&document, &signature.clone().into(), threshold).unwrap();
|
||||
let generated_key = ethcrypto::ecies::decrypt(&secret, ðcrypto::DEFAULT_MAC, &generated_key).unwrap();
|
||||
|
||||
// remove key from node0
|
||||
key_servers[0].cluster().key_storage().remove(&document).unwrap();
|
||||
|
||||
// now let's try to retrieve key back by requesting it from node0, so that session must be delegated
|
||||
let retrieved_key = key_servers[0].restore_document_key(&document, &signature).unwrap();
|
||||
let retrieved_key = key_servers[0].restore_document_key(&document, &signature.into()).unwrap();
|
||||
let retrieved_key = ethcrypto::ecies::decrypt(&secret, ðcrypto::DEFAULT_MAC, &retrieved_key).unwrap();
|
||||
assert_eq!(retrieved_key, generated_key);
|
||||
}
|
||||
@ -470,21 +476,21 @@ pub mod tests {
|
||||
#[test]
|
||||
fn schnorr_signing_session_is_delegated_when_node_does_not_have_key_share() {
|
||||
//::logger::init_log();
|
||||
let key_servers = make_key_servers(6114, 3);
|
||||
let (key_servers, _) = make_key_servers(6114, 3);
|
||||
let threshold = 1;
|
||||
|
||||
// generate server key
|
||||
let server_key_id = Random.generate().unwrap().secret().clone();
|
||||
let requestor_secret = Random.generate().unwrap().secret().clone();
|
||||
let signature = ethkey::sign(&requestor_secret, &server_key_id).unwrap();
|
||||
let server_public = key_servers[0].generate_key(&server_key_id, &signature, threshold).unwrap();
|
||||
let server_public = key_servers[0].generate_key(&server_key_id, &signature.clone().into(), threshold).unwrap();
|
||||
|
||||
// remove key from node0
|
||||
key_servers[0].cluster().key_storage().remove(&server_key_id).unwrap();
|
||||
|
||||
// sign message
|
||||
let message_hash = H256::from(42);
|
||||
let combined_signature = key_servers[0].sign_message_schnorr(&server_key_id, &signature, message_hash.clone()).unwrap();
|
||||
let combined_signature = key_servers[0].sign_message_schnorr(&server_key_id, &signature.into(), message_hash.clone()).unwrap();
|
||||
let combined_signature = ethcrypto::ecies::decrypt(&requestor_secret, ðcrypto::DEFAULT_MAC, &combined_signature).unwrap();
|
||||
let signature_c = Secret::from_slice(&combined_signature[..32]);
|
||||
let signature_s = Secret::from_slice(&combined_signature[32..]);
|
||||
@ -496,21 +502,21 @@ pub mod tests {
|
||||
#[test]
|
||||
fn ecdsa_signing_session_is_delegated_when_node_does_not_have_key_share() {
|
||||
//::logger::init_log();
|
||||
let key_servers = make_key_servers(6117, 4);
|
||||
let (key_servers, _) = make_key_servers(6117, 4);
|
||||
let threshold = 1;
|
||||
|
||||
// generate server key
|
||||
let server_key_id = Random.generate().unwrap().secret().clone();
|
||||
let requestor_secret = Random.generate().unwrap().secret().clone();
|
||||
let signature = ethkey::sign(&requestor_secret, &server_key_id).unwrap();
|
||||
let server_public = key_servers[0].generate_key(&server_key_id, &signature, threshold).unwrap();
|
||||
let server_public = key_servers[0].generate_key(&server_key_id, &signature.clone().into(), threshold).unwrap();
|
||||
|
||||
// remove key from node0
|
||||
key_servers[0].cluster().key_storage().remove(&server_key_id).unwrap();
|
||||
|
||||
// sign message
|
||||
let message_hash = H256::random();
|
||||
let signature = key_servers[0].sign_message_ecdsa(&server_key_id, &signature, message_hash.clone()).unwrap();
|
||||
let signature = key_servers[0].sign_message_ecdsa(&server_key_id, &signature.into(), message_hash.clone()).unwrap();
|
||||
let signature = ethcrypto::ecies::decrypt(&requestor_secret, ðcrypto::DEFAULT_MAC, &signature).unwrap();
|
||||
let signature: H520 = signature[0..65].into();
|
||||
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::collections::{BTreeSet, BTreeMap};
|
||||
use ethereum_types::H256;
|
||||
use ethereum_types::{Address, H256};
|
||||
use ethkey::Secret;
|
||||
use parking_lot::{Mutex, Condvar};
|
||||
use key_server_cluster::{Error, SessionId, NodeId, DocumentKeyShare};
|
||||
@ -55,8 +55,8 @@ pub struct SessionImpl<T: SessionTransport> {
|
||||
/// Action after key version is negotiated.
|
||||
#[derive(Clone)]
|
||||
pub enum ContinueAction {
|
||||
/// Decryption session + is_shadow_decryption.
|
||||
Decrypt(Arc<DecryptionSession>, bool),
|
||||
/// Decryption session + origin + is_shadow_decryption + is_broadcast_decryption.
|
||||
Decrypt(Arc<DecryptionSession>, Option<Address>, bool, bool),
|
||||
/// Schnorr signing session + message hash.
|
||||
SchnorrSign(Arc<SchnorrSigningSession>, H256),
|
||||
/// ECDSA signing session + message hash.
|
||||
@ -202,6 +202,7 @@ impl<T> SessionImpl<T> where T: SessionTransport {
|
||||
/// Wait for session completion.
|
||||
pub fn wait(&self) -> Result<(H256, NodeId), Error> {
|
||||
Self::wait_session(&self.core.completed, &self.data, None, |data| data.result.clone())
|
||||
.expect("wait_session returns Some if called without timeout; qed")
|
||||
}
|
||||
|
||||
/// Initialize session.
|
||||
|
@ -221,6 +221,7 @@ impl SessionImpl {
|
||||
/// Wait for session completion.
|
||||
pub fn wait(&self) -> Result<(), Error> {
|
||||
Self::wait_session(&self.core.completed, &self.data, None, |data| data.result.clone())
|
||||
.expect("wait_session returns Some if called without timeout; qed")
|
||||
}
|
||||
|
||||
/// Initialize servers set change session on master node.
|
||||
@ -337,7 +338,7 @@ impl SessionImpl {
|
||||
}
|
||||
|
||||
let unknown_sessions_job = UnknownSessionsJob::new_on_master(self.core.key_storage.clone(), self.core.meta.self_node_id.clone());
|
||||
consensus_session.disseminate_jobs(unknown_sessions_job, self.unknown_sessions_transport(), false)
|
||||
consensus_session.disseminate_jobs(unknown_sessions_job, self.unknown_sessions_transport(), false).map(|_| ())
|
||||
}
|
||||
|
||||
/// When unknown sessions are requested.
|
||||
@ -1166,7 +1167,7 @@ pub mod tests {
|
||||
|
||||
pub fn generate_key(threshold: usize, nodes_ids: BTreeSet<NodeId>) -> GenerationMessageLoop {
|
||||
let mut gml = GenerationMessageLoop::with_nodes_ids(nodes_ids);
|
||||
gml.master().initialize(Default::default(), false, threshold, gml.nodes.keys().cloned().collect::<BTreeSet<_>>().into()).unwrap();
|
||||
gml.master().initialize(Default::default(), Default::default(), false, threshold, gml.nodes.keys().cloned().collect::<BTreeSet<_>>().into()).unwrap();
|
||||
while let Some((from, to, message)) = gml.take_message() {
|
||||
gml.process_message((from, to, message)).unwrap();
|
||||
}
|
||||
|
@ -14,10 +14,11 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
use std::collections::{BTreeSet, BTreeMap};
|
||||
use std::sync::Arc;
|
||||
use std::time;
|
||||
use parking_lot::{Mutex, Condvar};
|
||||
use ethereum_types::H256;
|
||||
use ethereum_types::{Address, H256};
|
||||
use ethkey::Secret;
|
||||
use key_server_cluster::{Error, AclStorage, DocumentKeyShare, NodeId, SessionId, Requester,
|
||||
EncryptedDocumentKeyShadow, SessionMeta};
|
||||
@ -26,7 +27,7 @@ use key_server_cluster::cluster_sessions::{SessionIdWithSubSession, ClusterSessi
|
||||
use key_server_cluster::message::{Message, DecryptionMessage, DecryptionConsensusMessage, RequestPartialDecryption,
|
||||
PartialDecryption, DecryptionSessionError, DecryptionSessionCompleted, ConsensusMessage, InitializeConsensusSession,
|
||||
ConfirmConsensusInitialization, DecryptionSessionDelegation, DecryptionSessionDelegationCompleted};
|
||||
use key_server_cluster::jobs::job_session::{JobSession, JobTransport};
|
||||
use key_server_cluster::jobs::job_session::{JobSession, JobSessionState, JobTransport};
|
||||
use key_server_cluster::jobs::key_access_job::KeyAccessJob;
|
||||
use key_server_cluster::jobs::decryption_job::{PartialDecryptionRequest, PartialDecryptionResponse, DecryptionJob};
|
||||
use key_server_cluster::jobs::consensus_session::{ConsensusSessionParams, ConsensusSessionState, ConsensusSession};
|
||||
@ -71,6 +72,8 @@ type BroadcastDecryptionJobSession = JobSession<DecryptionJob, DecryptionJobTran
|
||||
struct SessionData {
|
||||
/// Key version to use for decryption.
|
||||
pub version: Option<H256>,
|
||||
/// Session origin (if any).
|
||||
pub origin: Option<Address>,
|
||||
/// Consensus-based decryption session.
|
||||
pub consensus_session: DecryptionConsensusSession,
|
||||
/// Broadcast decryption job.
|
||||
@ -110,6 +113,8 @@ struct DecryptionConsensusTransport {
|
||||
access_key: Secret,
|
||||
/// Session-level nonce.
|
||||
nonce: u64,
|
||||
/// Session origin (if any).
|
||||
origin: Option<Address>,
|
||||
/// Selected key version (on master node).
|
||||
version: Option<H256>,
|
||||
/// Cluster.
|
||||
@ -157,6 +162,7 @@ impl SessionImpl {
|
||||
id: params.meta.id.clone(),
|
||||
access_key: params.access_key.clone(),
|
||||
nonce: params.nonce,
|
||||
origin: None,
|
||||
version: None,
|
||||
cluster: params.cluster.clone(),
|
||||
};
|
||||
@ -180,6 +186,7 @@ impl SessionImpl {
|
||||
},
|
||||
data: Mutex::new(SessionData {
|
||||
version: None,
|
||||
origin: None,
|
||||
consensus_session: consensus_session,
|
||||
broadcast_job_session: None,
|
||||
is_shadow_decryption: None,
|
||||
@ -214,13 +221,42 @@ impl SessionImpl {
|
||||
self.data.lock().result.clone()
|
||||
}
|
||||
|
||||
/// Get key requester.
|
||||
pub fn requester(&self) -> Option<Requester> {
|
||||
self.data.lock().consensus_session.consensus_job().executor().requester().cloned()
|
||||
}
|
||||
|
||||
/// Get session origin.
|
||||
pub fn origin(&self) -> Option<Address> {
|
||||
self.data.lock().origin.clone()
|
||||
}
|
||||
|
||||
/// Wait for session completion.
|
||||
pub fn wait(&self) -> Result<EncryptedDocumentKeyShadow, Error> {
|
||||
Self::wait_session(&self.core.completed, &self.data, None, |data| data.result.clone())
|
||||
pub fn wait(&self, timeout: Option<time::Duration>) -> Option<Result<EncryptedDocumentKeyShadow, Error>> {
|
||||
Self::wait_session(&self.core.completed, &self.data, timeout, |data| data.result.clone())
|
||||
}
|
||||
|
||||
/// Get broadcasted shadows.
|
||||
pub fn broadcast_shadows(&self) -> Option<BTreeMap<NodeId, Vec<u8>>> {
|
||||
let data = self.data.lock();
|
||||
|
||||
if data.result.is_none() || (data.is_broadcast_session, data.is_shadow_decryption) != (Some(true), Some(true)) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let proof = "data.is_shadow_decryption is true; decrypt_shadow.is_some() is checked in DecryptionJob::check_partial_response; qed";
|
||||
Some(match self.core.meta.master_node_id == self.core.meta.self_node_id {
|
||||
true => data.consensus_session.computation_job().responses().iter()
|
||||
.map(|(n, r)| (n.clone(), r.decrypt_shadow.clone().expect(proof)))
|
||||
.collect(),
|
||||
false => data.broadcast_job_session.as_ref().expect("session completed; is_shadow_decryption == true; we're on non-master node; qed").responses().iter()
|
||||
.map(|(n, r)| (n.clone(), r.decrypt_shadow.clone().expect(proof)))
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Delegate session to other node.
|
||||
pub fn delegate(&self, master: NodeId, version: H256, is_shadow_decryption: bool, is_broadcast_session: bool) -> Result<(), Error> {
|
||||
pub fn delegate(&self, master: NodeId, origin: Option<Address>, version: H256, is_shadow_decryption: bool, is_broadcast_session: bool) -> Result<(), Error> {
|
||||
if self.core.meta.master_node_id != self.core.meta.self_node_id {
|
||||
return Err(Error::InvalidStateForRequest);
|
||||
}
|
||||
@ -235,6 +271,7 @@ impl SessionImpl {
|
||||
session: self.core.meta.id.clone().into(),
|
||||
sub_session: self.core.access_key.clone().into(),
|
||||
session_nonce: self.core.nonce,
|
||||
origin: origin.map(Into::into),
|
||||
requester: data.consensus_session.consensus_job().executor().requester()
|
||||
.expect("signature is passed to master node on creation; session can be delegated from master node only; qed")
|
||||
.clone().into(),
|
||||
@ -247,7 +284,7 @@ impl SessionImpl {
|
||||
}
|
||||
|
||||
/// Initialize decryption session on master node.
|
||||
pub fn initialize(&self, version: H256, is_shadow_decryption: bool, is_broadcast_session: bool) -> Result<(), Error> {
|
||||
pub fn initialize(&self, origin: Option<Address>, version: H256, is_shadow_decryption: bool, is_broadcast_session: bool) -> Result<(), Error> {
|
||||
debug_assert_eq!(self.core.meta.self_node_id, self.core.meta.master_node_id);
|
||||
|
||||
// check if version exists
|
||||
@ -268,6 +305,8 @@ impl SessionImpl {
|
||||
}
|
||||
|
||||
data.consensus_session.consensus_job_mut().transport_mut().version = Some(version.clone());
|
||||
data.consensus_session.consensus_job_mut().transport_mut().origin = origin.clone();
|
||||
data.origin = origin;
|
||||
data.version = Some(version.clone());
|
||||
data.is_shadow_decryption = Some(is_shadow_decryption);
|
||||
data.is_broadcast_session = Some(is_broadcast_session);
|
||||
@ -323,7 +362,7 @@ impl SessionImpl {
|
||||
data.delegation_status = Some(DelegationStatus::DelegatedFrom(sender.clone(), message.session_nonce));
|
||||
}
|
||||
|
||||
self.initialize(message.version.clone().into(), message.is_shadow_decryption, message.is_broadcast_session)
|
||||
self.initialize(message.origin.clone().map(Into::into), message.version.clone().into(), message.is_shadow_decryption, message.is_broadcast_session)
|
||||
}
|
||||
|
||||
/// When delegated session is completed on other node.
|
||||
@ -364,6 +403,7 @@ impl SessionImpl {
|
||||
.unwrap_or(false);
|
||||
data.consensus_session.consensus_job_mut().executor_mut().set_has_key_share(has_key_share);
|
||||
data.version = Some(version);
|
||||
data.origin = message.origin.clone().map(Into::into);
|
||||
}
|
||||
data.consensus_session.on_consensus_message(&sender, &message.message)?;
|
||||
|
||||
@ -397,13 +437,19 @@ impl SessionImpl {
|
||||
let requester_public = data.consensus_session.consensus_job().executor().requester()
|
||||
.ok_or(Error::InvalidStateForRequest)?
|
||||
.public(&self.core.meta.id)
|
||||
.ok_or(Error::InsufficientRequesterData)?;
|
||||
.map_err(Error::InsufficientRequesterData)?;
|
||||
let decryption_job = DecryptionJob::new_on_slave(self.core.meta.self_node_id.clone(), self.core.access_key.clone(),
|
||||
requester_public.clone(), key_share.clone(), key_version)?;
|
||||
let decryption_transport = self.core.decryption_transport(false);
|
||||
|
||||
// update flags if not on master
|
||||
if self.core.meta.self_node_id != self.core.meta.master_node_id {
|
||||
data.is_shadow_decryption = Some(message.is_shadow_decryption);
|
||||
data.is_broadcast_session = Some(message.is_broadcast_session);
|
||||
}
|
||||
|
||||
// respond to request
|
||||
data.consensus_session.on_job_request(sender, PartialDecryptionRequest {
|
||||
let partial_decryption = data.consensus_session.on_job_request(sender, PartialDecryptionRequest {
|
||||
id: message.request_id.clone().into(),
|
||||
is_shadow_decryption: message.is_shadow_decryption,
|
||||
is_broadcast_session: message.is_broadcast_session,
|
||||
@ -417,7 +463,7 @@ impl SessionImpl {
|
||||
self.core.access_key.clone(), requester_public, key_share.clone(), key_version,
|
||||
message.is_shadow_decryption, message.is_broadcast_session)?;
|
||||
Self::create_broadcast_decryption_job(&self.core, &mut *data, consensus_group, broadcast_decryption_job,
|
||||
message.request_id.clone().into())?;
|
||||
message.request_id.clone().into(), Some(partial_decryption.take_response()))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -430,38 +476,52 @@ impl SessionImpl {
|
||||
debug_assert!(sender != &self.core.meta.self_node_id);
|
||||
|
||||
let mut data = self.data.lock();
|
||||
if self.core.meta.self_node_id == self.core.meta.master_node_id {
|
||||
let is_master_node = self.core.meta.self_node_id == self.core.meta.master_node_id;
|
||||
let result = if is_master_node {
|
||||
data.consensus_session.on_job_response(sender, PartialDecryptionResponse {
|
||||
request_id: message.request_id.clone().into(),
|
||||
shadow_point: message.shadow_point.clone().into(),
|
||||
decrypt_shadow: message.decrypt_shadow.clone(),
|
||||
})?;
|
||||
|
||||
if data.consensus_session.state() != ConsensusSessionState::Finished &&
|
||||
data.consensus_session.state() != ConsensusSessionState::Failed {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// send completion signal to all nodes, except for rejected nodes
|
||||
if is_master_node {
|
||||
for node in data.consensus_session.consensus_non_rejected_nodes() {
|
||||
self.core.cluster.send(&node, Message::Decryption(DecryptionMessage::DecryptionSessionCompleted(DecryptionSessionCompleted {
|
||||
session: self.core.meta.id.clone().into(),
|
||||
sub_session: self.core.access_key.clone().into(),
|
||||
session_nonce: self.core.nonce,
|
||||
})))?;
|
||||
}
|
||||
}
|
||||
|
||||
data.consensus_session.result()
|
||||
} else {
|
||||
match data.broadcast_job_session.as_mut() {
|
||||
Some(broadcast_job_session) => broadcast_job_session.on_partial_response(sender, PartialDecryptionResponse {
|
||||
request_id: message.request_id.clone().into(),
|
||||
shadow_point: message.shadow_point.clone().into(),
|
||||
decrypt_shadow: message.decrypt_shadow.clone(),
|
||||
})?,
|
||||
None => return Err(Error::TooEarlyForRequest),
|
||||
Some(broadcast_job_session) => {
|
||||
broadcast_job_session.on_partial_response(sender, PartialDecryptionResponse {
|
||||
request_id: message.request_id.clone().into(),
|
||||
shadow_point: message.shadow_point.clone().into(),
|
||||
decrypt_shadow: message.decrypt_shadow.clone(),
|
||||
})?;
|
||||
|
||||
if broadcast_job_session.state() != JobSessionState::Finished &&
|
||||
broadcast_job_session.state() != JobSessionState::Failed {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
broadcast_job_session.result()
|
||||
},
|
||||
None => return Err(Error::InvalidMessage),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if data.consensus_session.state() != ConsensusSessionState::Finished {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// send compeltion signal to all nodes, except for rejected nodes
|
||||
for node in data.consensus_session.consensus_non_rejected_nodes() {
|
||||
self.core.cluster.send(&node, Message::Decryption(DecryptionMessage::DecryptionSessionCompleted(DecryptionSessionCompleted {
|
||||
session: self.core.meta.id.clone().into(),
|
||||
sub_session: self.core.access_key.clone().into(),
|
||||
session_nonce: self.core.nonce,
|
||||
})))?;
|
||||
}
|
||||
|
||||
let result = data.consensus_session.result()?;
|
||||
Self::set_decryption_result(&self.core, &mut *data, Ok(result));
|
||||
Self::set_decryption_result(&self.core, &mut *data, result);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -543,28 +603,31 @@ impl SessionImpl {
|
||||
|
||||
let key_version = key_share.version(version).map_err(|e| Error::KeyStorage(e.into()))?.hash.clone();
|
||||
let requester = data.consensus_session.consensus_job().executor().requester().ok_or(Error::InvalidStateForRequest)?.clone();
|
||||
let requester_public = requester.public(&core.meta.id).ok_or(Error::InsufficientRequesterData)?;
|
||||
let requester_public = requester.public(&core.meta.id).map_err(Error::InsufficientRequesterData)?;
|
||||
let consensus_group = data.consensus_session.select_consensus_group()?.clone();
|
||||
let decryption_job = DecryptionJob::new_on_master(core.meta.self_node_id.clone(),
|
||||
core.access_key.clone(), requester_public.clone(), key_share.clone(), key_version,
|
||||
is_shadow_decryption, is_broadcast_session)?;
|
||||
let decryption_request_id = decryption_job.request_id().clone().expect("TODO");
|
||||
let decryption_request_id = decryption_job.request_id().clone()
|
||||
.expect("DecryptionJob always have request_id when created on master; it is created using new_on_master above; qed");
|
||||
let decryption_transport = core.decryption_transport(false);
|
||||
data.consensus_session.disseminate_jobs(decryption_job, decryption_transport, data.is_broadcast_session.expect("TODO"))?;
|
||||
let is_broadcast_session = data.is_broadcast_session
|
||||
.expect("disseminate_jobs is called on master node only; on master node is_broadcast_session is filled during initialization; qed");
|
||||
let self_response = data.consensus_session.disseminate_jobs(decryption_job, decryption_transport, is_broadcast_session)?;
|
||||
|
||||
// ...and prepare decryption job session if we need to broadcast result
|
||||
if data.is_broadcast_session.expect("TODO") {
|
||||
if is_broadcast_session {
|
||||
let broadcast_decryption_job = DecryptionJob::new_on_master(core.meta.self_node_id.clone(),
|
||||
core.access_key.clone(), requester_public, key_share.clone(), key_version, is_shadow_decryption, is_broadcast_session)?;
|
||||
Self::create_broadcast_decryption_job(&core, data, consensus_group, broadcast_decryption_job,
|
||||
decryption_request_id)?;
|
||||
decryption_request_id, self_response)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create broadcast decryption job.
|
||||
fn create_broadcast_decryption_job(core: &SessionCore, data: &mut SessionData, mut consensus_group: BTreeSet<NodeId>, mut job: DecryptionJob, request_id: Secret) -> Result<(), Error> {
|
||||
fn create_broadcast_decryption_job(core: &SessionCore, data: &mut SessionData, mut consensus_group: BTreeSet<NodeId>, mut job: DecryptionJob, request_id: Secret, self_response: Option<PartialDecryptionResponse>) -> Result<(), Error> {
|
||||
consensus_group.insert(core.meta.self_node_id.clone());
|
||||
job.set_request_id(request_id.clone().into());
|
||||
|
||||
@ -575,7 +638,7 @@ impl SessionImpl {
|
||||
self_node_id: core.meta.self_node_id.clone(),
|
||||
threshold: core.meta.threshold,
|
||||
}, job, transport);
|
||||
job_session.initialize(consensus_group, core.meta.self_node_id != core.meta.master_node_id)?;
|
||||
job_session.initialize(consensus_group, self_response, core.meta.self_node_id != core.meta.master_node_id)?;
|
||||
data.broadcast_job_session = Some(job_session);
|
||||
|
||||
Ok(())
|
||||
@ -691,6 +754,7 @@ impl JobTransport for DecryptionConsensusTransport {
|
||||
session: self.id.clone().into(),
|
||||
sub_session: self.access_key.clone().into(),
|
||||
session_nonce: self.nonce,
|
||||
origin: self.origin.clone().map(Into::into),
|
||||
message: ConsensusMessage::InitializeConsensusSession(InitializeConsensusSession {
|
||||
requester: request.into(),
|
||||
version: version.clone().into(),
|
||||
@ -703,6 +767,7 @@ impl JobTransport for DecryptionConsensusTransport {
|
||||
session: self.id.clone().into(),
|
||||
sub_session: self.access_key.clone().into(),
|
||||
session_nonce: self.nonce,
|
||||
origin: None,
|
||||
message: ConsensusMessage::ConfirmConsensusInitialization(ConfirmConsensusInitialization {
|
||||
is_confirmed: response,
|
||||
})
|
||||
@ -751,7 +816,7 @@ mod tests {
|
||||
use std::sync::Arc;
|
||||
use std::collections::{BTreeMap, VecDeque};
|
||||
use acl_storage::DummyAclStorage;
|
||||
use ethkey::{self, KeyPair, Random, Generator, Public, Secret};
|
||||
use ethkey::{self, KeyPair, Random, Generator, Public, Secret, public_to_address};
|
||||
use key_server_cluster::{NodeId, DocumentKeyShare, DocumentKeyShareVersion, SessionId, Requester,
|
||||
Error, EncryptedDocumentKeyShadow, SessionMeta};
|
||||
use key_server_cluster::cluster::tests::DummyCluster;
|
||||
@ -918,7 +983,7 @@ mod tests {
|
||||
cluster: Arc::new(DummyCluster::new(self_node_id.clone())),
|
||||
nonce: 0,
|
||||
}, Some(Requester::Signature(ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap()))).unwrap();
|
||||
assert_eq!(session.initialize(Default::default(), false, false), Err(Error::InvalidMessage));
|
||||
assert_eq!(session.initialize(Default::default(), Default::default(), false, false), Err(Error::InvalidMessage));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -951,24 +1016,25 @@ mod tests {
|
||||
cluster: Arc::new(DummyCluster::new(self_node_id.clone())),
|
||||
nonce: 0,
|
||||
}, Some(Requester::Signature(ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap()))).unwrap();
|
||||
assert_eq!(session.initialize(Default::default(), false, false), Err(Error::ConsensusUnreachable));
|
||||
assert_eq!(session.initialize(Default::default(), Default::default(), false, false), Err(Error::ConsensusUnreachable));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_to_initialize_when_already_initialized() {
|
||||
let (_, _, _, sessions) = prepare_decryption_sessions();
|
||||
assert_eq!(sessions[0].initialize(Default::default(), false, false).unwrap(), ());
|
||||
assert_eq!(sessions[0].initialize(Default::default(), false, false).unwrap_err(), Error::InvalidStateForRequest);
|
||||
assert_eq!(sessions[0].initialize(Default::default(), Default::default(), false, false).unwrap(), ());
|
||||
assert_eq!(sessions[0].initialize(Default::default(), Default::default(), false, false).unwrap_err(), Error::InvalidStateForRequest);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_to_accept_initialization_when_already_initialized() {
|
||||
let (_, _, _, sessions) = prepare_decryption_sessions();
|
||||
assert_eq!(sessions[0].initialize(Default::default(), false, false).unwrap(), ());
|
||||
assert_eq!(sessions[0].initialize(Default::default(), Default::default(), false, false).unwrap(), ());
|
||||
assert_eq!(sessions[0].on_consensus_message(sessions[1].node(), &message::DecryptionConsensusMessage {
|
||||
session: SessionId::default().into(),
|
||||
sub_session: sessions[0].access_key().clone().into(),
|
||||
session_nonce: 0,
|
||||
origin: None,
|
||||
message: message::ConsensusMessage::InitializeConsensusSession(message::InitializeConsensusSession {
|
||||
requester: Requester::Signature(ethkey::sign(
|
||||
Random.generate().unwrap().secret(), &SessionId::default()).unwrap()).into(),
|
||||
@ -984,6 +1050,7 @@ mod tests {
|
||||
session: SessionId::default().into(),
|
||||
sub_session: sessions[0].access_key().clone().into(),
|
||||
session_nonce: 0,
|
||||
origin: None,
|
||||
message: message::ConsensusMessage::InitializeConsensusSession(message::InitializeConsensusSession {
|
||||
requester: Requester::Signature(ethkey::sign(Random.generate().unwrap().secret(),
|
||||
&SessionId::default()).unwrap()).into(),
|
||||
@ -1008,6 +1075,7 @@ mod tests {
|
||||
session: SessionId::default().into(),
|
||||
sub_session: sessions[0].access_key().clone().into(),
|
||||
session_nonce: 0,
|
||||
origin: None,
|
||||
message: message::ConsensusMessage::InitializeConsensusSession(message::InitializeConsensusSession {
|
||||
requester: Requester::Signature(ethkey::sign(Random.generate().unwrap().secret(),
|
||||
&SessionId::default()).unwrap()).into(),
|
||||
@ -1041,7 +1109,7 @@ mod tests {
|
||||
#[test]
|
||||
fn fails_to_accept_partial_decrypt_twice() {
|
||||
let (_, clusters, _, sessions) = prepare_decryption_sessions();
|
||||
sessions[0].initialize(Default::default(), false, false).unwrap();
|
||||
sessions[0].initialize(Default::default(), Default::default(), false, false).unwrap();
|
||||
|
||||
let mut pd_from = None;
|
||||
let mut pd_msg = None;
|
||||
@ -1069,7 +1137,7 @@ mod tests {
|
||||
#[test]
|
||||
fn node_is_marked_rejected_when_timed_out_during_initialization_confirmation() {
|
||||
let (_, _, _, sessions) = prepare_decryption_sessions();
|
||||
sessions[0].initialize(Default::default(), false, false).unwrap();
|
||||
sessions[0].initialize(Default::default(), Default::default(), false, false).unwrap();
|
||||
|
||||
// 1 node disconnects => we still can recover secret
|
||||
sessions[0].on_node_timeout(sessions[1].node());
|
||||
@ -1086,8 +1154,8 @@ mod tests {
|
||||
let (_, clusters, acl_storages, sessions) = prepare_decryption_sessions();
|
||||
let key_pair = Random.generate().unwrap();
|
||||
|
||||
acl_storages[1].prohibit(key_pair.public().clone(), SessionId::default());
|
||||
sessions[0].initialize(Default::default(), false, false).unwrap();
|
||||
acl_storages[1].prohibit(public_to_address(key_pair.public()), SessionId::default());
|
||||
sessions[0].initialize(Default::default(), Default::default(), false, false).unwrap();
|
||||
|
||||
do_messages_exchange_until(&clusters, &sessions, |_, _, _| sessions[0].state() == ConsensusSessionState::WaitingForPartialResults).unwrap();
|
||||
|
||||
@ -1099,7 +1167,7 @@ mod tests {
|
||||
#[test]
|
||||
fn session_does_not_fail_if_requested_node_disconnects() {
|
||||
let (_, clusters, _, sessions) = prepare_decryption_sessions();
|
||||
sessions[0].initialize(Default::default(), false, false).unwrap();
|
||||
sessions[0].initialize(Default::default(), Default::default(), false, false).unwrap();
|
||||
|
||||
do_messages_exchange_until(&clusters, &sessions, |_, _, _| sessions[0].state() == ConsensusSessionState::WaitingForPartialResults).unwrap();
|
||||
|
||||
@ -1115,7 +1183,7 @@ mod tests {
|
||||
#[test]
|
||||
fn session_does_not_fail_if_node_with_shadow_point_disconnects() {
|
||||
let (_, clusters, _, sessions) = prepare_decryption_sessions();
|
||||
sessions[0].initialize(Default::default(), false, false).unwrap();
|
||||
sessions[0].initialize(Default::default(), Default::default(), false, false).unwrap();
|
||||
|
||||
do_messages_exchange_until(&clusters, &sessions, |_, _, _| sessions[0].state() == ConsensusSessionState::WaitingForPartialResults
|
||||
&& sessions[0].data.lock().consensus_session.computation_job().responses().len() == 2).unwrap();
|
||||
@ -1132,7 +1200,7 @@ mod tests {
|
||||
#[test]
|
||||
fn session_restarts_if_confirmed_node_disconnects() {
|
||||
let (_, clusters, _, sessions) = prepare_decryption_sessions();
|
||||
sessions[0].initialize(Default::default(), false, false).unwrap();
|
||||
sessions[0].initialize(Default::default(), Default::default(), false, false).unwrap();
|
||||
|
||||
do_messages_exchange_until(&clusters, &sessions, |_, _, _| sessions[0].state() == ConsensusSessionState::WaitingForPartialResults).unwrap();
|
||||
|
||||
@ -1147,7 +1215,7 @@ mod tests {
|
||||
#[test]
|
||||
fn session_does_not_fail_if_non_master_node_disconnects_from_non_master_node() {
|
||||
let (_, clusters, _, sessions) = prepare_decryption_sessions();
|
||||
sessions[0].initialize(Default::default(), false, false).unwrap();
|
||||
sessions[0].initialize(Default::default(), Default::default(), false, false).unwrap();
|
||||
|
||||
do_messages_exchange_until(&clusters, &sessions, |_, _, _| sessions[0].state() == ConsensusSessionState::WaitingForPartialResults).unwrap();
|
||||
|
||||
@ -1162,7 +1230,7 @@ mod tests {
|
||||
let (_, clusters, _, sessions) = prepare_decryption_sessions();
|
||||
|
||||
// now let's try to do a decryption
|
||||
sessions[0].initialize(Default::default(), false, false).unwrap();
|
||||
sessions[0].initialize(Default::default(), Default::default(), false, false).unwrap();
|
||||
|
||||
do_messages_exchange(&clusters, &sessions).unwrap();
|
||||
|
||||
@ -1184,7 +1252,7 @@ mod tests {
|
||||
let (key_pair, clusters, _, sessions) = prepare_decryption_sessions();
|
||||
|
||||
// now let's try to do a decryption
|
||||
sessions[0].initialize(Default::default(), true, false).unwrap();
|
||||
sessions[0].initialize(Default::default(), Default::default(), true, false).unwrap();
|
||||
|
||||
do_messages_exchange(&clusters, &sessions).unwrap();
|
||||
|
||||
@ -1215,12 +1283,12 @@ mod tests {
|
||||
let (key_pair, clusters, acl_storages, sessions) = prepare_decryption_sessions();
|
||||
|
||||
// now let's try to do a decryption
|
||||
sessions[0].initialize(Default::default(), false, false).unwrap();
|
||||
sessions[0].initialize(Default::default(), Default::default(), false, false).unwrap();
|
||||
|
||||
// we need 4 out of 5 nodes to agree to do a decryption
|
||||
// let's say that 2 of these nodes are disagree
|
||||
acl_storages[1].prohibit(key_pair.public().clone(), SessionId::default());
|
||||
acl_storages[2].prohibit(key_pair.public().clone(), SessionId::default());
|
||||
acl_storages[1].prohibit(public_to_address(key_pair.public()), SessionId::default());
|
||||
acl_storages[2].prohibit(public_to_address(key_pair.public()), SessionId::default());
|
||||
|
||||
assert_eq!(do_messages_exchange(&clusters, &sessions).unwrap_err(), Error::ConsensusUnreachable);
|
||||
|
||||
@ -1235,10 +1303,10 @@ mod tests {
|
||||
|
||||
// we need 4 out of 5 nodes to agree to do a decryption
|
||||
// let's say that 1 of these nodes (master) is disagree
|
||||
acl_storages[0].prohibit(key_pair.public().clone(), SessionId::default());
|
||||
acl_storages[0].prohibit(public_to_address(key_pair.public()), SessionId::default());
|
||||
|
||||
// now let's try to do a decryption
|
||||
sessions[0].initialize(Default::default(), false, false).unwrap();
|
||||
sessions[0].initialize(Default::default(), Default::default(), false, false).unwrap();
|
||||
|
||||
do_messages_exchange(&clusters, &sessions).unwrap();
|
||||
|
||||
@ -1278,7 +1346,7 @@ mod tests {
|
||||
);
|
||||
|
||||
// now let's try to do a decryption
|
||||
sessions[1].delegate(sessions[0].core.meta.self_node_id.clone(), Default::default(), false, false).unwrap();
|
||||
sessions[1].delegate(sessions[0].core.meta.self_node_id.clone(), Default::default(), Default::default(), false, false).unwrap();
|
||||
do_messages_exchange(&clusters, &sessions).unwrap();
|
||||
|
||||
// now check that:
|
||||
@ -1304,7 +1372,7 @@ mod tests {
|
||||
}
|
||||
|
||||
// now let's try to do a decryption
|
||||
sessions[0].initialize(Default::default(), false, false).unwrap();
|
||||
sessions[0].initialize(Default::default(), Default::default(), false, false).unwrap();
|
||||
do_messages_exchange(&clusters, &sessions).unwrap();
|
||||
|
||||
assert_eq!(sessions[0].decrypted_secret().unwrap().unwrap(), EncryptedDocumentKeyShadow {
|
||||
@ -1317,13 +1385,52 @@ mod tests {
|
||||
#[test]
|
||||
fn decryption_result_restored_on_all_nodes_if_broadcast_session_is_completed() {
|
||||
let (_, clusters, _, sessions) = prepare_decryption_sessions();
|
||||
sessions[0].initialize(Default::default(), false, true).unwrap();
|
||||
sessions[0].initialize(Default::default(), Default::default(), false, true).unwrap();
|
||||
do_messages_exchange(&clusters, &sessions).unwrap();
|
||||
|
||||
// decryption result must be the same and available on 4 nodes
|
||||
let result = sessions[0].decrypted_secret();
|
||||
assert!(result.clone().unwrap().is_ok());
|
||||
assert_eq!(result.clone().unwrap().unwrap(), EncryptedDocumentKeyShadow {
|
||||
decrypted_secret: SECRET_PLAIN.into(),
|
||||
common_point: None,
|
||||
decrypt_shadows: None,
|
||||
});
|
||||
assert_eq!(3, sessions.iter().skip(1).filter(|s| s.decrypted_secret() == result).count());
|
||||
assert_eq!(1, sessions.iter().skip(1).filter(|s| s.decrypted_secret().is_none()).count());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decryption_shadows_restored_on_all_nodes_if_shadow_broadcast_session_is_completed() {
|
||||
let (key_pair, clusters, _, sessions) = prepare_decryption_sessions();
|
||||
sessions[0].initialize(Default::default(), Default::default(), true, true).unwrap();
|
||||
do_messages_exchange(&clusters, &sessions).unwrap();
|
||||
|
||||
// decryption shadows must be the same and available on 4 nodes
|
||||
let broadcast_shadows = sessions[0].broadcast_shadows();
|
||||
assert!(broadcast_shadows.is_some());
|
||||
assert_eq!(3, sessions.iter().skip(1).filter(|s| s.broadcast_shadows() == broadcast_shadows).count());
|
||||
assert_eq!(1, sessions.iter().skip(1).filter(|s| s.broadcast_shadows().is_none()).count());
|
||||
|
||||
// 4 nodes must be able to recover original secret
|
||||
use ethcrypto::DEFAULT_MAC;
|
||||
use ethcrypto::ecies::decrypt;
|
||||
let result = sessions[0].decrypted_secret().unwrap().unwrap();
|
||||
assert_eq!(3, sessions.iter().skip(1).filter(|s| s.decrypted_secret() == Some(Ok(result.clone()))).count());
|
||||
let decrypt_shadows: Vec<_> = result.decrypt_shadows.unwrap().into_iter()
|
||||
.map(|c| Secret::from_slice(&decrypt(key_pair.secret(), &DEFAULT_MAC, &c).unwrap()))
|
||||
.collect();
|
||||
let decrypted_secret = math::decrypt_with_shadow_coefficients(result.decrypted_secret, result.common_point.unwrap(), decrypt_shadows).unwrap();
|
||||
assert_eq!(decrypted_secret, SECRET_PLAIN.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decryption_session_origin_is_known_to_all_initialized_nodes() {
|
||||
let (_, clusters, _, sessions) = prepare_decryption_sessions();
|
||||
sessions[0].initialize(Some(1.into()), Default::default(), true, true).unwrap();
|
||||
do_messages_exchange(&clusters, &sessions).unwrap();
|
||||
|
||||
// all session must have origin set
|
||||
assert_eq!(5, sessions.iter().filter(|s| s.origin() == Some(1.into())).count());
|
||||
}
|
||||
}
|
||||
|
@ -19,8 +19,10 @@ use std::fmt::{Debug, Formatter, Error as FmtError};
|
||||
use std::time;
|
||||
use std::sync::Arc;
|
||||
use parking_lot::{Condvar, Mutex};
|
||||
use ethereum_types::Address;
|
||||
use ethkey::Public;
|
||||
use key_server_cluster::{Error, NodeId, SessionId, Requester, KeyStorage, DocumentKeyShare};
|
||||
use key_server_cluster::{Error, NodeId, SessionId, Requester, KeyStorage,
|
||||
DocumentKeyShare, ServerKeyId};
|
||||
use key_server_cluster::cluster::Cluster;
|
||||
use key_server_cluster::cluster_sessions::ClusterSession;
|
||||
use key_server_cluster::message::{Message, EncryptionMessage, InitializeEncryptionSession,
|
||||
@ -107,7 +109,7 @@ pub enum SessionState {
|
||||
impl SessionImpl {
|
||||
/// Create new encryption session.
|
||||
pub fn new(params: SessionParams) -> Result<Self, Error> {
|
||||
check_encrypted_data(¶ms.encrypted_data)?;
|
||||
check_encrypted_data(params.encrypted_data.as_ref())?;
|
||||
|
||||
Ok(SessionImpl {
|
||||
id: params.id,
|
||||
@ -133,9 +135,9 @@ impl SessionImpl {
|
||||
/// Wait for session completion.
|
||||
pub fn wait(&self, timeout: Option<time::Duration>) -> Result<(), Error> {
|
||||
Self::wait_session(&self.completed, &self.data, timeout, |data| data.result.clone())
|
||||
.expect("wait_session returns Some if called without timeout; qed")
|
||||
}
|
||||
|
||||
|
||||
/// Start new session initialization. This must be called on master node.
|
||||
pub fn initialize(&self, requester: Requester, common_point: Public, encrypted_point: Public) -> Result<(), Error> {
|
||||
let mut data = self.data.lock();
|
||||
@ -155,17 +157,10 @@ impl SessionImpl {
|
||||
// TODO [Reliability]: there could be situation when some nodes have failed to store encrypted data
|
||||
// => potential problems during restore. some confirmation step is needed (2pc)?
|
||||
// save encryption data
|
||||
if let Some(mut encrypted_data) = self.encrypted_data.clone() {
|
||||
// check that the requester is the author of the encrypted data
|
||||
let requester_address = requester.address(&self.id).ok_or(Error::InsufficientRequesterData)?;
|
||||
if encrypted_data.author != requester_address {
|
||||
return Err(Error::AccessDenied);
|
||||
}
|
||||
|
||||
encrypted_data.common_point = Some(common_point.clone());
|
||||
encrypted_data.encrypted_point = Some(encrypted_point.clone());
|
||||
self.key_storage.update(self.id.clone(), encrypted_data)
|
||||
.map_err(|e| Error::KeyStorage(e.into()))?;
|
||||
if let Some(encrypted_data) = self.encrypted_data.clone() {
|
||||
let requester_address = requester.address(&self.id).map_err(Error::InsufficientRequesterData)?;
|
||||
update_encrypted_data(&self.key_storage, self.id.clone(),
|
||||
encrypted_data, requester_address, common_point.clone(), encrypted_point.clone())?;
|
||||
}
|
||||
|
||||
// start initialization
|
||||
@ -199,18 +194,11 @@ impl SessionImpl {
|
||||
}
|
||||
|
||||
// check that the requester is the author of the encrypted data
|
||||
if let Some(mut encrypted_data) = self.encrypted_data.clone() {
|
||||
if let Some(encrypted_data) = self.encrypted_data.clone() {
|
||||
let requester: Requester = message.requester.clone().into();
|
||||
let requestor_address = requester.address(&self.id).ok_or(Error::InsufficientRequesterData)?;
|
||||
if encrypted_data.author != requestor_address {
|
||||
return Err(Error::AccessDenied);
|
||||
}
|
||||
|
||||
// save encryption data
|
||||
encrypted_data.common_point = Some(message.common_point.clone().into());
|
||||
encrypted_data.encrypted_point = Some(message.encrypted_point.clone().into());
|
||||
self.key_storage.update(self.id.clone(), encrypted_data)
|
||||
.map_err(|e| Error::KeyStorage(e.into()))?;
|
||||
let requester_address = requester.address(&self.id).map_err(Error::InsufficientRequesterData)?;
|
||||
update_encrypted_data(&self.key_storage, self.id.clone(),
|
||||
encrypted_data, requester_address, message.common_point.clone().into(), message.encrypted_point.clone().into())?;
|
||||
}
|
||||
|
||||
// update state
|
||||
@ -333,13 +321,28 @@ impl Debug for SessionImpl {
|
||||
}
|
||||
}
|
||||
|
||||
fn check_encrypted_data(encrypted_data: &Option<DocumentKeyShare>) -> Result<(), Error> {
|
||||
if let &Some(ref encrypted_data) = encrypted_data {
|
||||
/// Check that common_point and encrypted point are not yet set in key share.
|
||||
pub fn check_encrypted_data(key_share: Option<&DocumentKeyShare>) -> Result<(), Error> {
|
||||
if let Some(key_share) = key_share {
|
||||
// check that common_point and encrypted_point are still not set yet
|
||||
if encrypted_data.common_point.is_some() || encrypted_data.encrypted_point.is_some() {
|
||||
if key_share.common_point.is_some() || key_share.encrypted_point.is_some() {
|
||||
return Err(Error::CompletedSessionId);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update key share with encrypted document key.
|
||||
pub fn update_encrypted_data(key_storage: &Arc<KeyStorage>, key_id: ServerKeyId, mut key_share: DocumentKeyShare, author: Address, common_point: Public, encrypted_point: Public) -> Result<(), Error> {
|
||||
// author must be the same
|
||||
if key_share.author != author {
|
||||
return Err(Error::AccessDenied);
|
||||
}
|
||||
|
||||
// save encryption data
|
||||
key_share.common_point = Some(common_point);
|
||||
key_share.encrypted_point = Some(encrypted_point);
|
||||
key_storage.update(key_id, key_share)
|
||||
.map_err(|e| Error::KeyStorage(e.into()))
|
||||
}
|
||||
|
@ -82,6 +82,8 @@ struct SessionData {
|
||||
author: Option<Address>,
|
||||
|
||||
// === Values, filled when session initialization is completed ===
|
||||
/// Session origin (if any).
|
||||
origin: Option<Address>,
|
||||
/// Is zero secret generation session?
|
||||
is_zero: Option<bool>,
|
||||
/// Threshold value for this DKG. Only `threshold + 1` will be able to collectively recreate joint secret,
|
||||
@ -217,6 +219,7 @@ impl SessionImpl {
|
||||
simulate_faulty_behaviour: false,
|
||||
master: None,
|
||||
author: None,
|
||||
origin: None,
|
||||
is_zero: None,
|
||||
threshold: None,
|
||||
derived_point: None,
|
||||
@ -251,8 +254,13 @@ impl SessionImpl {
|
||||
self.data.lock().state.clone()
|
||||
}
|
||||
|
||||
/// Get session origin.
|
||||
pub fn origin(&self) -> Option<Address> {
|
||||
self.data.lock().origin.clone()
|
||||
}
|
||||
|
||||
/// Wait for session completion.
|
||||
pub fn wait(&self, timeout: Option<Duration>) -> Result<Public, Error> {
|
||||
pub fn wait(&self, timeout: Option<Duration>) -> Option<Result<Public, Error>> {
|
||||
Self::wait_session(&self.completed, &self.data, timeout, |data| data.joint_public_and_secret.clone()
|
||||
.map(|r| r.map(|r| r.0.clone())))
|
||||
}
|
||||
@ -263,7 +271,7 @@ impl SessionImpl {
|
||||
}
|
||||
|
||||
/// Start new session initialization. This must be called on master node.
|
||||
pub fn initialize(&self, author: Address, is_zero: bool, threshold: usize, nodes: InitializationNodes) -> Result<(), Error> {
|
||||
pub fn initialize(&self, origin: Option<Address>, author: Address, is_zero: bool, threshold: usize, nodes: InitializationNodes) -> Result<(), Error> {
|
||||
check_cluster_nodes(self.node(), &nodes.set())?;
|
||||
check_threshold(threshold, &nodes.set())?;
|
||||
|
||||
@ -277,6 +285,7 @@ impl SessionImpl {
|
||||
// update state
|
||||
data.master = Some(self.node().clone());
|
||||
data.author = Some(author.clone());
|
||||
data.origin = origin.clone();
|
||||
data.is_zero = Some(is_zero);
|
||||
data.threshold = Some(threshold);
|
||||
match nodes {
|
||||
@ -304,6 +313,7 @@ impl SessionImpl {
|
||||
self.cluster.send(&next_node, Message::Generation(GenerationMessage::InitializeSession(InitializeSession {
|
||||
session: self.id.clone().into(),
|
||||
session_nonce: self.nonce,
|
||||
origin: origin.map(Into::into),
|
||||
author: author.into(),
|
||||
nodes: data.nodes.iter().map(|(k, v)| (k.clone().into(), v.id_number.clone().into())).collect(),
|
||||
is_zero: data.is_zero.expect("is_zero is filled in initialization phase; KD phase follows initialization phase; qed"),
|
||||
@ -380,6 +390,7 @@ impl SessionImpl {
|
||||
data.author = Some(message.author.clone().into());
|
||||
data.state = SessionState::WaitingForInitializationComplete;
|
||||
data.nodes = message.nodes.iter().map(|(id, number)| (id.clone().into(), NodeData::with_id_number(number.clone().into()))).collect();
|
||||
data.origin = message.origin.clone().map(Into::into);
|
||||
data.is_zero = Some(message.is_zero);
|
||||
data.threshold = Some(message.threshold);
|
||||
|
||||
@ -411,6 +422,7 @@ impl SessionImpl {
|
||||
return self.cluster.send(&next_receiver, Message::Generation(GenerationMessage::InitializeSession(InitializeSession {
|
||||
session: self.id.clone().into(),
|
||||
session_nonce: self.nonce,
|
||||
origin: data.origin.clone().map(Into::into),
|
||||
author: data.author.as_ref().expect("author is filled on initialization step; confrm initialization follows initialization; qed").clone().into(),
|
||||
nodes: data.nodes.iter().map(|(k, v)| (k.clone().into(), v.id_number.clone().into())).collect(),
|
||||
is_zero: data.is_zero.expect("is_zero is filled in initialization phase; KD phase follows initialization phase; qed"),
|
||||
@ -937,7 +949,7 @@ pub mod tests {
|
||||
use std::time::Duration;
|
||||
use tokio_core::reactor::Core;
|
||||
use ethereum_types::Address;
|
||||
use ethkey::{Random, Generator, Public, KeyPair};
|
||||
use ethkey::{Random, Generator, KeyPair};
|
||||
use key_server_cluster::{NodeId, SessionId, Error, KeyStorage, DummyKeyStorage};
|
||||
use key_server_cluster::message::{self, Message, GenerationMessage};
|
||||
use key_server_cluster::cluster::tests::{DummyCluster, make_clusters, run_clusters, loop_until, all_connections_established};
|
||||
@ -1065,7 +1077,7 @@ pub mod tests {
|
||||
|
||||
fn make_simple_cluster(threshold: usize, num_nodes: usize) -> Result<(SessionId, NodeId, NodeId, MessageLoop), Error> {
|
||||
let l = MessageLoop::new(num_nodes);
|
||||
l.master().initialize(Default::default(), false, threshold, l.nodes.keys().cloned().collect::<BTreeSet<_>>().into())?;
|
||||
l.master().initialize(Default::default(), Default::default(), false, threshold, l.nodes.keys().cloned().collect::<BTreeSet<_>>().into())?;
|
||||
|
||||
let session_id = l.session_id.clone();
|
||||
let master_id = l.master().node().clone();
|
||||
@ -1076,7 +1088,7 @@ pub mod tests {
|
||||
#[test]
|
||||
fn initializes_in_cluster_of_single_node() {
|
||||
let l = MessageLoop::new(1);
|
||||
assert!(l.master().initialize(Default::default(), false, 0, l.nodes.keys().cloned().collect::<BTreeSet<_>>().into()).is_ok());
|
||||
assert!(l.master().initialize(Default::default(), Default::default(), false, 0, l.nodes.keys().cloned().collect::<BTreeSet<_>>().into()).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1091,7 +1103,7 @@ pub mod tests {
|
||||
nonce: Some(0),
|
||||
});
|
||||
let cluster_nodes: BTreeSet<_> = (0..2).map(|_| math::generate_random_point().unwrap()).collect();
|
||||
assert_eq!(session.initialize(Default::default(), false, 0, cluster_nodes.into()).unwrap_err(), Error::InvalidNodesConfiguration);
|
||||
assert_eq!(session.initialize(Default::default(), Default::default(), false, 0, cluster_nodes.into()).unwrap_err(), Error::InvalidNodesConfiguration);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1105,7 +1117,7 @@ pub mod tests {
|
||||
#[test]
|
||||
fn fails_to_initialize_when_already_initialized() {
|
||||
let (_, _, _, l) = make_simple_cluster(0, 2).unwrap();
|
||||
assert_eq!(l.master().initialize(Default::default(), false, 0, l.nodes.keys().cloned().collect::<BTreeSet<_>>().into()).unwrap_err(),
|
||||
assert_eq!(l.master().initialize(Default::default(), Default::default(), false, 0, l.nodes.keys().cloned().collect::<BTreeSet<_>>().into()).unwrap_err(),
|
||||
Error::InvalidStateForRequest);
|
||||
}
|
||||
|
||||
@ -1185,6 +1197,7 @@ pub mod tests {
|
||||
assert_eq!(l.first_slave().on_initialize_session(m, &message::InitializeSession {
|
||||
session: sid.into(),
|
||||
session_nonce: 0,
|
||||
origin: None,
|
||||
author: Address::default().into(),
|
||||
nodes: nodes.into_iter().map(|(k, v)| (k.into(), v.into())).collect(),
|
||||
is_zero: false,
|
||||
@ -1202,6 +1215,7 @@ pub mod tests {
|
||||
assert_eq!(l.first_slave().on_initialize_session(m, &message::InitializeSession {
|
||||
session: sid.into(),
|
||||
session_nonce: 0,
|
||||
origin: None,
|
||||
author: Address::default().into(),
|
||||
nodes: nodes.into_iter().map(|(k, v)| (k.into(), v.into())).collect(),
|
||||
is_zero: false,
|
||||
@ -1345,7 +1359,7 @@ pub mod tests {
|
||||
let test_cases = [(0, 5), (2, 5), (3, 5)];
|
||||
for &(threshold, num_nodes) in &test_cases {
|
||||
let mut l = MessageLoop::new(num_nodes);
|
||||
l.master().initialize(Default::default(), false, threshold, l.nodes.keys().cloned().collect::<BTreeSet<_>>().into()).unwrap();
|
||||
l.master().initialize(Default::default(), Default::default(), false, threshold, l.nodes.keys().cloned().collect::<BTreeSet<_>>().into()).unwrap();
|
||||
assert_eq!(l.nodes.len(), num_nodes);
|
||||
|
||||
// let nodes do initialization + keys dissemination
|
||||
@ -1377,6 +1391,9 @@ pub mod tests {
|
||||
|
||||
#[test]
|
||||
fn encryption_session_works_over_network() {
|
||||
const CONN_TIMEOUT: Duration = Duration::from_millis(300);
|
||||
const SESSION_TIMEOUT: Duration = Duration::from_millis(1000);
|
||||
|
||||
let test_cases = [(1, 3)];
|
||||
for &(threshold, num_nodes) in &test_cases {
|
||||
let mut core = Core::new().unwrap();
|
||||
@ -1386,12 +1403,12 @@ pub mod tests {
|
||||
run_clusters(&clusters);
|
||||
|
||||
// establish connections
|
||||
loop_until(&mut core, Duration::from_millis(300), || clusters.iter().all(all_connections_established));
|
||||
loop_until(&mut core, CONN_TIMEOUT, || clusters.iter().all(all_connections_established));
|
||||
|
||||
// run session to completion
|
||||
let session_id = SessionId::default();
|
||||
let session = clusters[0].client().new_generation_session(session_id, Public::default(), threshold).unwrap();
|
||||
loop_until(&mut core, Duration::from_millis(1000), || session.joint_public_and_secret().is_some());
|
||||
let session = clusters[0].client().new_generation_session(session_id, Default::default(), Default::default(), threshold).unwrap();
|
||||
loop_until(&mut core, SESSION_TIMEOUT, || session.joint_public_and_secret().is_some());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -222,6 +222,7 @@ impl SessionImpl {
|
||||
/// Wait for session completion.
|
||||
pub fn wait(&self) -> Result<Signature, Error> {
|
||||
Self::wait_session(&self.core.completed, &self.data, None, |data| data.result.clone())
|
||||
.expect("wait_session returns Some if called without timeout; qed")
|
||||
}
|
||||
|
||||
/// Delegate session to other node.
|
||||
@ -402,7 +403,7 @@ impl SessionImpl {
|
||||
session_nonce: n,
|
||||
message: m,
|
||||
}));
|
||||
sig_nonce_generation_session.initialize(Default::default(), false, key_share.threshold, consensus_group_map.clone().into())?;
|
||||
sig_nonce_generation_session.initialize(Default::default(), Default::default(), false, key_share.threshold, consensus_group_map.clone().into())?;
|
||||
data.sig_nonce_generation_session = Some(sig_nonce_generation_session);
|
||||
|
||||
// start generation of inversed nonce computation session
|
||||
@ -414,7 +415,7 @@ impl SessionImpl {
|
||||
session_nonce: n,
|
||||
message: m,
|
||||
}));
|
||||
inv_nonce_generation_session.initialize(Default::default(), false, key_share.threshold, consensus_group_map.clone().into())?;
|
||||
inv_nonce_generation_session.initialize(Default::default(), Default::default(), false, key_share.threshold, consensus_group_map.clone().into())?;
|
||||
data.inv_nonce_generation_session = Some(inv_nonce_generation_session);
|
||||
|
||||
// start generation of zero-secret shares for inversed nonce computation session
|
||||
@ -426,7 +427,7 @@ impl SessionImpl {
|
||||
session_nonce: n,
|
||||
message: m,
|
||||
}));
|
||||
inv_zero_generation_session.initialize(Default::default(), true, key_share.threshold * 2, consensus_group_map.clone().into())?;
|
||||
inv_zero_generation_session.initialize(Default::default(), Default::default(), true, key_share.threshold * 2, consensus_group_map.clone().into())?;
|
||||
data.inv_zero_generation_session = Some(inv_zero_generation_session);
|
||||
|
||||
data.state = SessionState::NoncesGenerating;
|
||||
@ -688,7 +689,7 @@ impl SessionImpl {
|
||||
id: message.request_id.clone().into(),
|
||||
inversed_nonce_coeff: message.inversed_nonce_coeff.clone().into(),
|
||||
message_hash: message.message_hash.clone().into(),
|
||||
}, signing_job, signing_transport)
|
||||
}, signing_job, signing_transport).map(|_| ())
|
||||
}
|
||||
|
||||
/// When partial signature is received.
|
||||
@ -989,7 +990,7 @@ impl SessionCore {
|
||||
|
||||
let key_version = key_share.version(version).map_err(|e| Error::KeyStorage(e.into()))?.hash.clone();
|
||||
let signing_job = EcdsaSigningJob::new_on_master(key_share.clone(), key_version, nonce_public, inv_nonce_share, inversed_nonce_coeff, message_hash)?;
|
||||
consensus_session.disseminate_jobs(signing_job, self.signing_transport(), false)
|
||||
consensus_session.disseminate_jobs(signing_job, self.signing_transport(), false).map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -1054,7 +1055,7 @@ mod tests {
|
||||
use std::sync::Arc;
|
||||
use std::collections::{BTreeSet, BTreeMap, VecDeque};
|
||||
use ethereum_types::H256;
|
||||
use ethkey::{self, Random, Generator, KeyPair, verify_public};
|
||||
use ethkey::{self, Random, Generator, KeyPair, verify_public, public_to_address};
|
||||
use acl_storage::DummyAclStorage;
|
||||
use key_server_cluster::{NodeId, DummyKeyStorage, SessionId, SessionMeta, Error, KeyStorage};
|
||||
use key_server_cluster::cluster_sessions::ClusterSession;
|
||||
@ -1165,7 +1166,7 @@ mod tests {
|
||||
fn prepare_signing_sessions(threshold: usize, num_nodes: usize) -> (KeyGenerationMessageLoop, MessageLoop) {
|
||||
// run key generation sessions
|
||||
let mut gl = KeyGenerationMessageLoop::new(num_nodes);
|
||||
gl.master().initialize(Default::default(), false, threshold, gl.nodes.keys().cloned().collect::<BTreeSet<_>>().into()).unwrap();
|
||||
gl.master().initialize(Default::default(), Default::default(), false, threshold, gl.nodes.keys().cloned().collect::<BTreeSet<_>>().into()).unwrap();
|
||||
while let Some((from, to, message)) = gl.take_message() {
|
||||
gl.process_message((from, to, message)).unwrap();
|
||||
}
|
||||
@ -1214,7 +1215,7 @@ mod tests {
|
||||
|
||||
// we need at least 3-of-4 nodes to agree to reach consensus
|
||||
// let's say 1 of 4 nodes disagee
|
||||
sl.acl_storages[1].prohibit(sl.requester.public().clone(), SessionId::default());
|
||||
sl.acl_storages[1].prohibit(public_to_address(sl.requester.public()), SessionId::default());
|
||||
|
||||
// then consensus reachable, but single node will disagree
|
||||
while let Some((from, to, message)) = sl.take_message() {
|
||||
@ -1235,7 +1236,7 @@ mod tests {
|
||||
|
||||
// we need at least 3-of-4 nodes to agree to reach consensus
|
||||
// let's say 1 of 4 nodes disagee
|
||||
sl.acl_storages[0].prohibit(sl.requester.public().clone(), SessionId::default());
|
||||
sl.acl_storages[0].prohibit(public_to_address(sl.requester.public()), SessionId::default());
|
||||
|
||||
// then consensus reachable, but single node will disagree
|
||||
while let Some((from, to, message)) = sl.take_message() {
|
||||
|
@ -209,6 +209,7 @@ impl SessionImpl {
|
||||
/// Wait for session completion.
|
||||
pub fn wait(&self) -> Result<(Secret, Secret), Error> {
|
||||
Self::wait_session(&self.core.completed, &self.data, None, |data| data.result.clone())
|
||||
.expect("wait_session returns Some if called without timeout; qed")
|
||||
}
|
||||
|
||||
/// Delegate session to other node.
|
||||
@ -277,7 +278,7 @@ impl SessionImpl {
|
||||
}),
|
||||
nonce: None,
|
||||
});
|
||||
generation_session.initialize(Default::default(), false, 0, vec![self.core.meta.self_node_id.clone()].into_iter().collect::<BTreeSet<_>>().into())?;
|
||||
generation_session.initialize(Default::default(), Default::default(), false, 0, vec![self.core.meta.self_node_id.clone()].into_iter().collect::<BTreeSet<_>>().into())?;
|
||||
|
||||
debug_assert_eq!(generation_session.state(), GenerationSessionState::WaitingForGenerationConfirmation);
|
||||
let joint_public_and_secret = generation_session
|
||||
@ -406,7 +407,7 @@ impl SessionImpl {
|
||||
nonce: None,
|
||||
});
|
||||
|
||||
generation_session.initialize(Default::default(), false, key_share.threshold, consensus_group.into())?;
|
||||
generation_session.initialize(Default::default(), Default::default(), false, key_share.threshold, consensus_group.into())?;
|
||||
data.generation_session = Some(generation_session);
|
||||
data.state = SessionState::SessionKeyGeneration;
|
||||
|
||||
@ -508,7 +509,7 @@ impl SessionImpl {
|
||||
id: message.request_id.clone().into(),
|
||||
message_hash: message.message_hash.clone().into(),
|
||||
other_nodes_ids: message.nodes.iter().cloned().map(Into::into).collect(),
|
||||
}, signing_job, signing_transport)
|
||||
}, signing_job, signing_transport).map(|_| ())
|
||||
}
|
||||
|
||||
/// When partial signature is received.
|
||||
@ -735,8 +736,9 @@ impl SessionCore {
|
||||
};
|
||||
|
||||
let key_version = key_share.version(version).map_err(|e| Error::KeyStorage(e.into()))?.hash.clone();
|
||||
let signing_job = SchnorrSigningJob::new_on_master(self.meta.self_node_id.clone(), key_share.clone(), key_version, session_public, session_secret_share, message_hash)?;
|
||||
consensus_session.disseminate_jobs(signing_job, self.signing_transport(), false)
|
||||
let signing_job = SchnorrSigningJob::new_on_master(self.meta.self_node_id.clone(), key_share.clone(), key_version,
|
||||
session_public, session_secret_share, message_hash)?;
|
||||
consensus_session.disseminate_jobs(signing_job, self.signing_transport(), false).map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -802,7 +804,7 @@ mod tests {
|
||||
use std::str::FromStr;
|
||||
use std::collections::{BTreeSet, BTreeMap, VecDeque};
|
||||
use ethereum_types::{Address, H256};
|
||||
use ethkey::{self, Random, Generator, Public, Secret, KeyPair};
|
||||
use ethkey::{self, Random, Generator, Public, Secret, KeyPair, public_to_address};
|
||||
use acl_storage::DummyAclStorage;
|
||||
use key_server_cluster::{NodeId, DummyKeyStorage, DocumentKeyShare, DocumentKeyShareVersion, SessionId,
|
||||
Requester, SessionMeta, Error, KeyStorage};
|
||||
@ -928,7 +930,7 @@ mod tests {
|
||||
fn prepare_signing_sessions(threshold: usize, num_nodes: usize) -> (KeyGenerationMessageLoop, MessageLoop) {
|
||||
// run key generation sessions
|
||||
let mut gl = KeyGenerationMessageLoop::new(num_nodes);
|
||||
gl.master().initialize(Default::default(), false, threshold, gl.nodes.keys().cloned().collect::<BTreeSet<_>>().into()).unwrap();
|
||||
gl.master().initialize(Default::default(), Default::default(), false, threshold, gl.nodes.keys().cloned().collect::<BTreeSet<_>>().into()).unwrap();
|
||||
while let Some((from, to, message)) = gl.take_message() {
|
||||
gl.process_message((from, to, message)).unwrap();
|
||||
}
|
||||
@ -1114,6 +1116,7 @@ mod tests {
|
||||
message: GenerationMessage::InitializeSession(InitializeSession {
|
||||
session: SessionId::default().into(),
|
||||
session_nonce: 0,
|
||||
origin: None,
|
||||
author: Address::default().into(),
|
||||
nodes: BTreeMap::new(),
|
||||
is_zero: false,
|
||||
@ -1157,8 +1160,8 @@ mod tests {
|
||||
|
||||
// we need at least 2-of-3 nodes to agree to reach consensus
|
||||
// let's say 2 of 3 nodes disagee
|
||||
sl.acl_storages[1].prohibit(sl.requester.public().clone(), SessionId::default());
|
||||
sl.acl_storages[2].prohibit(sl.requester.public().clone(), SessionId::default());
|
||||
sl.acl_storages[1].prohibit(public_to_address(sl.requester.public()), SessionId::default());
|
||||
sl.acl_storages[2].prohibit(public_to_address(sl.requester.public()), SessionId::default());
|
||||
|
||||
// then consensus is unreachable
|
||||
assert_eq!(sl.run_until(|_| false), Err(Error::ConsensusUnreachable));
|
||||
@ -1171,7 +1174,7 @@ mod tests {
|
||||
|
||||
// we need at least 2-of-3 nodes to agree to reach consensus
|
||||
// let's say 1 of 3 nodes disagee
|
||||
sl.acl_storages[1].prohibit(sl.requester.public().clone(), SessionId::default());
|
||||
sl.acl_storages[1].prohibit(public_to_address(sl.requester.public()), SessionId::default());
|
||||
|
||||
// then consensus reachable, but single node will disagree
|
||||
while let Some((from, to, message)) = sl.take_message() {
|
||||
@ -1192,7 +1195,7 @@ mod tests {
|
||||
|
||||
// we need at least 2-of-3 nodes to agree to reach consensus
|
||||
// let's say 1 of 3 nodes disagee
|
||||
sl.acl_storages[0].prohibit(sl.requester.public().clone(), SessionId::default());
|
||||
sl.acl_storages[0].prohibit(public_to_address(sl.requester.public()), SessionId::default());
|
||||
|
||||
// then consensus reachable, but single node will disagree
|
||||
while let Some((from, to, message)) = sl.take_message() {
|
||||
|
@ -26,8 +26,8 @@ use parking_lot::{RwLock, Mutex};
|
||||
use tokio_io::IoFuture;
|
||||
use tokio_core::reactor::{Handle, Remote, Interval};
|
||||
use tokio_core::net::{TcpListener, TcpStream};
|
||||
use ethkey::{Public, KeyPair, Signature, Random, Generator, public_to_address};
|
||||
use ethereum_types::H256;
|
||||
use ethkey::{Public, KeyPair, Signature, Random, Generator};
|
||||
use ethereum_types::{Address, H256};
|
||||
use key_server_cluster::{Error, NodeId, SessionId, Requester, AclStorage, KeyStorage, KeyServerSet, NodeKeyPair};
|
||||
use key_server_cluster::cluster_sessions::{ClusterSession, AdminSession, ClusterSessions, SessionIdWithSubSession,
|
||||
ClusterSessionsContainer, SERVERS_SET_CHANGE_SESSION_ID, create_cluster_view, AdminSessionCreationData, ClusterSessionsListener};
|
||||
@ -66,11 +66,11 @@ pub trait ClusterClient: Send + Sync {
|
||||
/// Get cluster state.
|
||||
fn cluster_state(&self) -> ClusterState;
|
||||
/// Start new generation session.
|
||||
fn new_generation_session(&self, session_id: SessionId, author: Public, threshold: usize) -> Result<Arc<GenerationSession>, Error>;
|
||||
fn new_generation_session(&self, session_id: SessionId, origin: Option<Address>, author: Address, threshold: usize) -> Result<Arc<GenerationSession>, Error>;
|
||||
/// Start new encryption session.
|
||||
fn new_encryption_session(&self, session_id: SessionId, requester: Requester, common_point: Public, encrypted_point: Public) -> Result<Arc<EncryptionSession>, Error>;
|
||||
fn new_encryption_session(&self, session_id: SessionId, author: Requester, common_point: Public, encrypted_point: Public) -> Result<Arc<EncryptionSession>, Error>;
|
||||
/// Start new decryption session.
|
||||
fn new_decryption_session(&self, session_id: SessionId, requester: Requester, version: Option<H256>, is_shadow_decryption: bool) -> Result<Arc<DecryptionSession>, Error>;
|
||||
fn new_decryption_session(&self, session_id: SessionId, origin: Option<Address>, requester: Requester, version: Option<H256>, is_shadow_decryption: bool, is_broadcast_decryption: bool) -> Result<Arc<DecryptionSession>, Error>;
|
||||
/// Start new Schnorr signing session.
|
||||
fn new_schnorr_signing_session(&self, session_id: SessionId, requester: Requester, version: Option<H256>, message_hash: H256) -> Result<Arc<SchnorrSigningSession>, Error>;
|
||||
/// Start new ECDSA session.
|
||||
@ -82,6 +82,8 @@ pub trait ClusterClient: Send + Sync {
|
||||
|
||||
/// Listen for new generation sessions.
|
||||
fn add_generation_listener(&self, listener: Arc<ClusterSessionsListener<GenerationSession>>);
|
||||
/// Listen for new decryption sessions.
|
||||
fn add_decryption_listener(&self, listener: Arc<ClusterSessionsListener<DecryptionSession>>);
|
||||
|
||||
/// Ask node to make 'faulty' generation sessions.
|
||||
#[cfg(test)]
|
||||
@ -477,11 +479,11 @@ impl ClusterCore {
|
||||
data.sessions.negotiation_sessions.remove(&session.id());
|
||||
match session.wait() {
|
||||
Ok((version, master)) => match session.take_continue_action() {
|
||||
Some(ContinueAction::Decrypt(session, is_shadow_decryption)) => {
|
||||
Some(ContinueAction::Decrypt(session, origin, is_shadow_decryption, is_broadcast_decryption)) => {
|
||||
let initialization_error = if data.self_key_pair.public() == &master {
|
||||
session.initialize(version, is_shadow_decryption, false)
|
||||
session.initialize(origin, version, is_shadow_decryption, is_broadcast_decryption)
|
||||
} else {
|
||||
session.delegate(master, version, is_shadow_decryption, false)
|
||||
session.delegate(master, origin, version, is_shadow_decryption, is_broadcast_decryption)
|
||||
};
|
||||
|
||||
if let Err(error) = initialization_error {
|
||||
@ -516,7 +518,7 @@ impl ClusterCore {
|
||||
None => (),
|
||||
},
|
||||
Err(error) => match session.take_continue_action() {
|
||||
Some(ContinueAction::Decrypt(session, _)) => {
|
||||
Some(ContinueAction::Decrypt(session, _, _, _)) => {
|
||||
data.sessions.decryption_sessions.remove(&session.id());
|
||||
session.on_session_error(&meta.self_node_id, error);
|
||||
},
|
||||
@ -901,13 +903,13 @@ impl ClusterClient for ClusterClientImpl {
|
||||
self.data.connections.cluster_state()
|
||||
}
|
||||
|
||||
fn new_generation_session(&self, session_id: SessionId, author: Public, threshold: usize) -> Result<Arc<GenerationSession>, Error> {
|
||||
fn new_generation_session(&self, session_id: SessionId, origin: Option<Address>, author: Address, threshold: usize) -> Result<Arc<GenerationSession>, Error> {
|
||||
let mut connected_nodes = self.data.connections.connected_nodes();
|
||||
connected_nodes.insert(self.data.self_key_pair.public().clone());
|
||||
|
||||
let cluster = create_cluster_view(&self.data, true)?;
|
||||
let session = self.data.sessions.generation_sessions.insert(cluster, self.data.self_key_pair.public().clone(), session_id, None, false, None)?;
|
||||
match session.initialize(public_to_address(&author), false, threshold, connected_nodes.into()) {
|
||||
match session.initialize(origin, author, false, threshold, connected_nodes.into()) {
|
||||
Ok(()) => Ok(session),
|
||||
Err(error) => {
|
||||
self.data.sessions.generation_sessions.remove(&session.id());
|
||||
@ -931,7 +933,7 @@ impl ClusterClient for ClusterClientImpl {
|
||||
}
|
||||
}
|
||||
|
||||
fn new_decryption_session(&self, session_id: SessionId, requester: Requester, version: Option<H256>, is_shadow_decryption: bool) -> Result<Arc<DecryptionSession>, Error> {
|
||||
fn new_decryption_session(&self, session_id: SessionId, origin: Option<Address>, requester: Requester, version: Option<H256>, is_shadow_decryption: bool, is_broadcast_decryption: bool) -> Result<Arc<DecryptionSession>, Error> {
|
||||
let mut connected_nodes = self.data.connections.connected_nodes();
|
||||
connected_nodes.insert(self.data.self_key_pair.public().clone());
|
||||
|
||||
@ -942,11 +944,11 @@ impl ClusterClient for ClusterClientImpl {
|
||||
session_id.clone(), None, false, Some(requester))?;
|
||||
|
||||
let initialization_result = match version {
|
||||
Some(version) => session.initialize(version, is_shadow_decryption, false),
|
||||
Some(version) => session.initialize(origin, version, is_shadow_decryption, is_broadcast_decryption),
|
||||
None => {
|
||||
self.create_key_version_negotiation_session(session_id.id.clone())
|
||||
.map(|version_session| {
|
||||
version_session.set_continue_action(ContinueAction::Decrypt(session.clone(), is_shadow_decryption));
|
||||
version_session.set_continue_action(ContinueAction::Decrypt(session.clone(), origin, is_shadow_decryption, is_broadcast_decryption));
|
||||
ClusterCore::try_continue_session(&self.data, Some(version_session));
|
||||
})
|
||||
},
|
||||
@ -1056,6 +1058,10 @@ impl ClusterClient for ClusterClientImpl {
|
||||
self.data.sessions.generation_sessions.add_listener(listener);
|
||||
}
|
||||
|
||||
fn add_decryption_listener(&self, listener: Arc<ClusterSessionsListener<DecryptionSession>>) {
|
||||
self.data.sessions.decryption_sessions.add_listener(listener);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn connect(&self) {
|
||||
ClusterCore::connect_disconnected_nodes(self.data.clone());
|
||||
@ -1085,11 +1091,12 @@ fn make_socket_address(address: &str, port: u16) -> Result<SocketAddr, Error> {
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::time::{Duration, Instant};
|
||||
use std::collections::{BTreeSet, VecDeque};
|
||||
use parking_lot::Mutex;
|
||||
use tokio_core::reactor::Core;
|
||||
use ethereum_types::H256;
|
||||
use ethereum_types::{Address, H256};
|
||||
use ethkey::{Random, Generator, Public, Signature, sign};
|
||||
use key_server_cluster::{NodeId, SessionId, Requester, Error, DummyAclStorage, DummyKeyStorage,
|
||||
MapKeyServerSet, PlainNodeKeyPair, KeyStorage};
|
||||
@ -1107,7 +1114,9 @@ pub mod tests {
|
||||
const TIMEOUT: Duration = Duration::from_millis(300);
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DummyClusterClient;
|
||||
pub struct DummyClusterClient {
|
||||
pub generation_requests_count: AtomicUsize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DummyCluster {
|
||||
@ -1123,15 +1132,20 @@ pub mod tests {
|
||||
|
||||
impl ClusterClient for DummyClusterClient {
|
||||
fn cluster_state(&self) -> ClusterState { unimplemented!("test-only") }
|
||||
fn new_generation_session(&self, _session_id: SessionId, _author: Public, _threshold: usize) -> Result<Arc<GenerationSession>, Error> { unimplemented!("test-only") }
|
||||
fn new_generation_session(&self, _session_id: SessionId, _origin: Option<Address>, _author: Address, _threshold: usize) -> Result<Arc<GenerationSession>, Error> {
|
||||
self.generation_requests_count.fetch_add(1, Ordering::Relaxed);
|
||||
Err(Error::Io("test-errror".into()))
|
||||
}
|
||||
fn new_encryption_session(&self, _session_id: SessionId, _requester: Requester, _common_point: Public, _encrypted_point: Public) -> Result<Arc<EncryptionSession>, Error> { unimplemented!("test-only") }
|
||||
fn new_decryption_session(&self, _session_id: SessionId, _requester: Requester, _version: Option<H256>, _is_shadow_decryption: bool) -> Result<Arc<DecryptionSession>, Error> { unimplemented!("test-only") }
|
||||
fn new_decryption_session(&self, _session_id: SessionId, _origin: Option<Address>, _requester: Requester, _version: Option<H256>, _is_shadow_decryption: bool, _is_broadcast_session: bool) -> Result<Arc<DecryptionSession>, Error> { unimplemented!("test-only") }
|
||||
fn new_schnorr_signing_session(&self, _session_id: SessionId, _requester: Requester, _version: Option<H256>, _message_hash: H256) -> Result<Arc<SchnorrSigningSession>, Error> { unimplemented!("test-only") }
|
||||
fn new_ecdsa_signing_session(&self, _session_id: SessionId, _requester: Requester, _version: Option<H256>, _message_hash: H256) -> Result<Arc<EcdsaSigningSession>, Error> { unimplemented!("test-only") }
|
||||
|
||||
fn new_key_version_negotiation_session(&self, _session_id: SessionId) -> Result<Arc<KeyVersionNegotiationSession<KeyVersionNegotiationSessionTransport>>, Error> { unimplemented!("test-only") }
|
||||
fn new_servers_set_change_session(&self, _session_id: Option<SessionId>, _migration_id: Option<H256>, _new_nodes_set: BTreeSet<NodeId>, _old_set_signature: Signature, _new_set_signature: Signature) -> Result<Arc<AdminSession>, Error> { unimplemented!("test-only") }
|
||||
|
||||
fn add_generation_listener(&self, _listener: Arc<ClusterSessionsListener<GenerationSession>>) {}
|
||||
fn add_decryption_listener(&self, _listener: Arc<ClusterSessionsListener<DecryptionSession>>) {}
|
||||
|
||||
fn make_faulty_generation_sessions(&self) { unimplemented!("test-only") }
|
||||
fn generation_session(&self, _session_id: &SessionId) -> Option<Arc<GenerationSession>> { unimplemented!("test-only") }
|
||||
@ -1258,7 +1272,7 @@ pub mod tests {
|
||||
let core = Core::new().unwrap();
|
||||
let clusters = make_clusters(&core, 6013, 3);
|
||||
clusters[0].run().unwrap();
|
||||
match clusters[0].client().new_generation_session(SessionId::default(), Public::default(), 1) {
|
||||
match clusters[0].client().new_generation_session(SessionId::default(), Default::default(), Default::default(), 1) {
|
||||
Err(Error::NodeDisconnected) => (),
|
||||
Err(e) => panic!("unexpected error {:?}", e),
|
||||
_ => panic!("unexpected success"),
|
||||
@ -1277,7 +1291,7 @@ pub mod tests {
|
||||
clusters[1].client().make_faulty_generation_sessions();
|
||||
|
||||
// start && wait for generation session to fail
|
||||
let session = clusters[0].client().new_generation_session(SessionId::default(), Public::default(), 1).unwrap();
|
||||
let session = clusters[0].client().new_generation_session(SessionId::default(), Default::default(), Default::default(), 1).unwrap();
|
||||
loop_until(&mut core, TIMEOUT, || session.joint_public_and_secret().is_some()
|
||||
&& clusters[0].client().generation_session(&SessionId::default()).is_none());
|
||||
assert!(session.joint_public_and_secret().unwrap().is_err());
|
||||
@ -1306,7 +1320,7 @@ pub mod tests {
|
||||
clusters[0].client().make_faulty_generation_sessions();
|
||||
|
||||
// start && wait for generation session to fail
|
||||
let session = clusters[0].client().new_generation_session(SessionId::default(), Public::default(), 1).unwrap();
|
||||
let session = clusters[0].client().new_generation_session(SessionId::default(), Default::default(), Default::default(), 1).unwrap();
|
||||
loop_until(&mut core, TIMEOUT, || session.joint_public_and_secret().is_some()
|
||||
&& clusters[0].client().generation_session(&SessionId::default()).is_none());
|
||||
assert!(session.joint_public_and_secret().unwrap().is_err());
|
||||
@ -1332,7 +1346,7 @@ pub mod tests {
|
||||
loop_until(&mut core, TIMEOUT, || clusters.iter().all(all_connections_established));
|
||||
|
||||
// start && wait for generation session to complete
|
||||
let session = clusters[0].client().new_generation_session(SessionId::default(), Public::default(), 1).unwrap();
|
||||
let session = clusters[0].client().new_generation_session(SessionId::default(), Default::default(), Default::default(), 1).unwrap();
|
||||
loop_until(&mut core, TIMEOUT, || (session.state() == GenerationSessionState::Finished
|
||||
|| session.state() == GenerationSessionState::Failed)
|
||||
&& clusters[0].client().generation_session(&SessionId::default()).is_none());
|
||||
@ -1359,11 +1373,11 @@ pub mod tests {
|
||||
// generation session
|
||||
{
|
||||
// try to start generation session => fail in initialization
|
||||
assert_eq!(clusters[0].client().new_generation_session(SessionId::default(), Public::default(), 100).map(|_| ()),
|
||||
assert_eq!(clusters[0].client().new_generation_session(SessionId::default(), Default::default(), Default::default(), 100).map(|_| ()),
|
||||
Err(Error::InvalidThreshold));
|
||||
|
||||
// try to start generation session => fails in initialization
|
||||
assert_eq!(clusters[0].client().new_generation_session(SessionId::default(), Public::default(), 100).map(|_| ()),
|
||||
assert_eq!(clusters[0].client().new_generation_session(SessionId::default(), Default::default(), Default::default(), 100).map(|_| ()),
|
||||
Err(Error::InvalidThreshold));
|
||||
|
||||
assert!(clusters[0].data.sessions.generation_sessions.is_empty());
|
||||
@ -1372,11 +1386,11 @@ pub mod tests {
|
||||
// decryption session
|
||||
{
|
||||
// try to start decryption session => fails in initialization
|
||||
assert_eq!(clusters[0].client().new_decryption_session(Default::default(), Default::default(), Some(Default::default()), false).map(|_| ()),
|
||||
assert_eq!(clusters[0].client().new_decryption_session(Default::default(), Default::default(), Default::default(), Some(Default::default()), false, false).map(|_| ()),
|
||||
Err(Error::InvalidMessage));
|
||||
|
||||
// try to start generation session => fails in initialization
|
||||
assert_eq!(clusters[0].client().new_decryption_session(Default::default(), Default::default(), Some(Default::default()), false).map(|_| ()),
|
||||
assert_eq!(clusters[0].client().new_decryption_session(Default::default(), Default::default(), Default::default(), Some(Default::default()), false, false).map(|_| ()),
|
||||
Err(Error::InvalidMessage));
|
||||
|
||||
assert!(clusters[0].data.sessions.decryption_sessions.is_empty());
|
||||
@ -1393,7 +1407,7 @@ pub mod tests {
|
||||
loop_until(&mut core, TIMEOUT, || clusters.iter().all(all_connections_established));
|
||||
|
||||
// start && wait for generation session to complete
|
||||
let session = clusters[0].client().new_generation_session(SessionId::default(), Public::default(), 1).unwrap();
|
||||
let session = clusters[0].client().new_generation_session(SessionId::default(), Default::default(), Default::default(), 1).unwrap();
|
||||
loop_until(&mut core, TIMEOUT, || (session.state() == GenerationSessionState::Finished
|
||||
|| session.state() == GenerationSessionState::Failed)
|
||||
&& clusters[0].client().generation_session(&SessionId::default()).is_none());
|
||||
@ -1442,7 +1456,7 @@ pub mod tests {
|
||||
loop_until(&mut core, TIMEOUT, || clusters.iter().all(all_connections_established));
|
||||
|
||||
// start && wait for generation session to complete
|
||||
let session = clusters[0].client().new_generation_session(SessionId::default(), Public::default(), 1).unwrap();
|
||||
let session = clusters[0].client().new_generation_session(SessionId::default(), Default::default(), Default::default(), 1).unwrap();
|
||||
loop_until(&mut core, TIMEOUT, || (session.state() == GenerationSessionState::Finished
|
||||
|| session.state() == GenerationSessionState::Failed)
|
||||
&& clusters[0].client().generation_session(&SessionId::default()).is_none());
|
||||
|
@ -84,10 +84,10 @@ pub trait ClusterSession {
|
||||
fn on_message(&self, sender: &NodeId, message: &Message) -> Result<(), Error>;
|
||||
|
||||
/// 'Wait for session completion' helper.
|
||||
fn wait_session<T, U, F: Fn(&U) -> Option<Result<T, Error>>>(completion_event: &Condvar, session_data: &Mutex<U>, timeout: Option<Duration>, result_reader: F) -> Result<T, Error> {
|
||||
fn wait_session<T, U, F: Fn(&U) -> Option<Result<T, Error>>>(completion_event: &Condvar, session_data: &Mutex<U>, timeout: Option<Duration>, result_reader: F) -> Option<Result<T, Error>> {
|
||||
let mut locked_data = session_data.lock();
|
||||
match result_reader(&locked_data) {
|
||||
Some(result) => result,
|
||||
Some(result) => Some(result),
|
||||
None => {
|
||||
match timeout {
|
||||
None => completion_event.wait(&mut locked_data),
|
||||
@ -97,7 +97,6 @@ pub trait ClusterSession {
|
||||
}
|
||||
|
||||
result_reader(&locked_data)
|
||||
.expect("waited for completion; completion is only signaled when result.is_some(); qed")
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -563,12 +562,14 @@ pub fn create_cluster_view(data: &Arc<ClusterData>, requires_all_connections: bo
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use ethkey::{Random, Generator};
|
||||
use key_server_cluster::{Error, DummyAclStorage, DummyKeyStorage, MapKeyServerSet, PlainNodeKeyPair};
|
||||
use key_server_cluster::cluster::ClusterConfiguration;
|
||||
use key_server_cluster::connection_trigger::SimpleServersSetChangeSessionCreatorConnector;
|
||||
use key_server_cluster::cluster::tests::DummyCluster;
|
||||
use super::{ClusterSessions, AdminSessionCreationData};
|
||||
use key_server_cluster::generation_session::{SessionImpl as GenerationSession};
|
||||
use super::{ClusterSessions, AdminSessionCreationData, ClusterSessionsListener};
|
||||
|
||||
pub fn make_cluster_sessions() -> ClusterSessions {
|
||||
let key_pair = Random.generate().unwrap();
|
||||
@ -610,4 +611,35 @@ mod tests {
|
||||
Ok(_) => unreachable!("OK"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_listener_works() {
|
||||
#[derive(Default)]
|
||||
struct GenerationSessionListener {
|
||||
inserted: AtomicUsize,
|
||||
removed: AtomicUsize,
|
||||
}
|
||||
|
||||
impl ClusterSessionsListener<GenerationSession> for GenerationSessionListener {
|
||||
fn on_session_inserted(&self, _session: Arc<GenerationSession>) {
|
||||
self.inserted.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn on_session_removed(&self, _session: Arc<GenerationSession>) {
|
||||
self.removed.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
let listener = Arc::new(GenerationSessionListener::default());
|
||||
let sessions = make_cluster_sessions();
|
||||
sessions.generation_sessions.add_listener(listener.clone());
|
||||
|
||||
sessions.generation_sessions.insert(Arc::new(DummyCluster::new(Default::default())), Default::default(), Default::default(), None, false, None).unwrap();
|
||||
assert_eq!(listener.inserted.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(listener.removed.load(Ordering::Relaxed), 0);
|
||||
|
||||
sessions.generation_sessions.remove(&Default::default());
|
||||
assert_eq!(listener.inserted.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(listener.removed.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@
|
||||
use std::collections::BTreeSet;
|
||||
use key_server_cluster::{Error, NodeId, SessionMeta, Requester};
|
||||
use key_server_cluster::message::ConsensusMessage;
|
||||
use key_server_cluster::jobs::job_session::{JobSession, JobSessionState, JobTransport, JobExecutor};
|
||||
use key_server_cluster::jobs::job_session::{JobSession, JobSessionState, JobTransport, JobExecutor, JobPartialRequestAction};
|
||||
|
||||
/// Consensus session state.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
@ -114,7 +114,6 @@ impl<ConsensusExecutor, ConsensusTransport, ComputationExecutor, ComputationTran
|
||||
}
|
||||
|
||||
/// Get computation job reference.
|
||||
#[cfg(test)]
|
||||
pub fn computation_job(&self) -> &JobSession<ComputationExecutor, ComputationTransport> {
|
||||
self.computation_job.as_ref()
|
||||
.expect("computation_job must only be called on master nodes")
|
||||
@ -140,15 +139,15 @@ impl<ConsensusExecutor, ConsensusTransport, ComputationExecutor, ComputationTran
|
||||
/// Initialize session on master node.
|
||||
pub fn initialize(&mut self, nodes: BTreeSet<NodeId>) -> Result<(), Error> {
|
||||
debug_assert!(self.meta.self_node_id == self.meta.master_node_id);
|
||||
let initialization_result = self.consensus_job.initialize(nodes, false);
|
||||
let initialization_result = self.consensus_job.initialize(nodes, None, false);
|
||||
self.state = ConsensusSessionState::EstablishingConsensus;
|
||||
self.process_result(initialization_result)
|
||||
self.process_result(initialization_result.map(|_| ()))
|
||||
}
|
||||
|
||||
/// Process consensus request message.
|
||||
pub fn on_consensus_partial_request(&mut self, sender: &NodeId, request: ConsensusExecutor::PartialJobRequest) -> Result<(), Error> {
|
||||
let consensus_result = self.consensus_job.on_partial_request(sender, request);
|
||||
self.process_result(consensus_result)
|
||||
self.process_result(consensus_result.map(|_| ()))
|
||||
}
|
||||
|
||||
/// Process consensus message response.
|
||||
@ -179,19 +178,22 @@ impl<ConsensusExecutor, ConsensusTransport, ComputationExecutor, ComputationTran
|
||||
}
|
||||
|
||||
/// Disseminate jobs from master node.
|
||||
pub fn disseminate_jobs(&mut self, executor: ComputationExecutor, transport: ComputationTransport, broadcast_self_response: bool) -> Result<(), Error> {
|
||||
pub fn disseminate_jobs(&mut self, executor: ComputationExecutor, transport: ComputationTransport, broadcast_self_response: bool) -> Result<Option<ComputationExecutor::PartialJobResponse>, Error> {
|
||||
let consensus_group = self.select_consensus_group()?.clone();
|
||||
self.consensus_group.clear();
|
||||
|
||||
let mut computation_job = JobSession::new(self.meta.clone(), executor, transport);
|
||||
let computation_result = computation_job.initialize(consensus_group, broadcast_self_response);
|
||||
let computation_result = computation_job.initialize(consensus_group, None, broadcast_self_response);
|
||||
self.computation_job = Some(computation_job);
|
||||
self.state = ConsensusSessionState::WaitingForPartialResults;
|
||||
self.process_result(computation_result)
|
||||
match computation_result {
|
||||
Ok(computation_result) => self.process_result(Ok(())).map(|_| computation_result),
|
||||
Err(error) => Err(self.process_result(Err(error)).unwrap_err()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Process job request on slave node.
|
||||
pub fn on_job_request(&mut self, node: &NodeId, request: ComputationExecutor::PartialJobRequest, executor: ComputationExecutor, transport: ComputationTransport) -> Result<(), Error> {
|
||||
pub fn on_job_request(&mut self, node: &NodeId, request: ComputationExecutor::PartialJobRequest, executor: ComputationExecutor, transport: ComputationTransport) -> Result<JobPartialRequestAction<ComputationExecutor::PartialJobResponse>, Error> {
|
||||
if &self.meta.master_node_id != node {
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
@ -350,7 +352,7 @@ impl<ConsensusExecutor, ConsensusTransport, ComputationExecutor, ComputationTran
|
||||
let consensus_result = match message {
|
||||
|
||||
&ConsensusMessage::InitializeConsensusSession(ref message) =>
|
||||
self.consensus_job.on_partial_request(sender, message.requester.clone().into()),
|
||||
self.consensus_job.on_partial_request(sender, message.requester.clone().into()).map(|_| ()),
|
||||
&ConsensusMessage::ConfirmConsensusInitialization(ref message) =>
|
||||
self.consensus_job.on_partial_response(sender, message.is_confirmed),
|
||||
};
|
||||
@ -361,7 +363,7 @@ impl<ConsensusExecutor, ConsensusTransport, ComputationExecutor, ComputationTran
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
use ethkey::{KeyPair, Random, Generator, sign};
|
||||
use ethkey::{KeyPair, Random, Generator, sign, public_to_address};
|
||||
use key_server_cluster::{Error, NodeId, SessionId, Requester, DummyAclStorage};
|
||||
use key_server_cluster::message::{ConsensusMessage, InitializeConsensusSession, ConfirmConsensusInitialization};
|
||||
use key_server_cluster::jobs::job_session::tests::{make_master_session_meta, make_slave_session_meta, SquaredSumJobExecutor, DummyJobTransport};
|
||||
@ -414,7 +416,7 @@ mod tests {
|
||||
fn consensus_session_consensus_is_not_reached_when_initializes_with_zero_threshold_and_master_rejects() {
|
||||
let requester = Random.generate().unwrap();
|
||||
let acl_storage = DummyAclStorage::default();
|
||||
acl_storage.prohibit(requester.public().clone(), SessionId::default());
|
||||
acl_storage.prohibit(public_to_address(requester.public()), SessionId::default());
|
||||
|
||||
let mut session = make_master_consensus_session(0, Some(requester), Some(acl_storage));
|
||||
session.initialize(vec![NodeId::from(1), NodeId::from(2)].into_iter().collect()).unwrap();
|
||||
@ -429,7 +431,7 @@ mod tests {
|
||||
fn consensus_session_consensus_is_failed_by_master_node() {
|
||||
let requester = Random.generate().unwrap();
|
||||
let acl_storage = DummyAclStorage::default();
|
||||
acl_storage.prohibit(requester.public().clone(), SessionId::default());
|
||||
acl_storage.prohibit(public_to_address(requester.public()), SessionId::default());
|
||||
|
||||
let mut session = make_master_consensus_session(1, Some(requester), Some(acl_storage));
|
||||
assert_eq!(session.initialize(vec![NodeId::from(1), NodeId::from(2)].into_iter().collect()).unwrap_err(), Error::ConsensusUnreachable);
|
||||
@ -471,7 +473,7 @@ mod tests {
|
||||
fn consensus_session_job_dissemination_does_not_select_master_node_if_rejected() {
|
||||
let requester = Random.generate().unwrap();
|
||||
let acl_storage = DummyAclStorage::default();
|
||||
acl_storage.prohibit(requester.public().clone(), SessionId::default());
|
||||
acl_storage.prohibit(public_to_address(requester.public()), SessionId::default());
|
||||
|
||||
let mut session = make_master_consensus_session(0, Some(requester), Some(acl_storage));
|
||||
session.initialize(vec![NodeId::from(1), NodeId::from(2)].into_iter().collect()).unwrap();
|
||||
|
@ -44,6 +44,7 @@ pub struct DecryptionJob {
|
||||
}
|
||||
|
||||
/// Decryption job partial request.
|
||||
#[derive(Debug)]
|
||||
pub struct PartialDecryptionRequest {
|
||||
/// Request id.
|
||||
pub id: Secret,
|
||||
@ -143,10 +144,11 @@ impl JobExecutor for DecryptionJob {
|
||||
let decrypt_shadow = if partial_request.is_shadow_decryption { Some(math::generate_random_scalar()?) } else { None };
|
||||
let common_point = self.key_share.common_point.as_ref().expect("DecryptionJob is only created when common_point is known; qed");
|
||||
let (shadow_point, decrypt_shadow) = math::compute_node_shadow_point(&self.access_key, &common_point, &node_shadow, decrypt_shadow)?;
|
||||
|
||||
Ok(JobPartialRequestAction::Respond(PartialDecryptionResponse {
|
||||
request_id: partial_request.id,
|
||||
shadow_point: shadow_point,
|
||||
decrypt_shadow: match decrypt_shadow {
|
||||
decrypt_shadow: match decrypt_shadow.clone() {
|
||||
None => None,
|
||||
Some(decrypt_shadow) => Some(encrypt(&self.requester, &DEFAULT_MAC, &**decrypt_shadow)?),
|
||||
},
|
||||
|
@ -197,7 +197,7 @@ impl<Executor, Transport> JobSession<Executor, Transport> where Executor: JobExe
|
||||
}
|
||||
|
||||
/// Initialize.
|
||||
pub fn initialize(&mut self, nodes: BTreeSet<NodeId>, broadcast_self_response: bool) -> Result<(), Error> {
|
||||
pub fn initialize(&mut self, nodes: BTreeSet<NodeId>, self_response: Option<Executor::PartialJobResponse>, broadcast_self_response: bool) -> Result<Option<Executor::PartialJobResponse>, Error> {
|
||||
debug_assert!(self.meta.self_node_id == self.meta.master_node_id);
|
||||
|
||||
if nodes.len() < self.meta.threshold + 1 {
|
||||
@ -215,15 +215,13 @@ impl<Executor, Transport> JobSession<Executor, Transport> where Executor: JobExe
|
||||
responses: BTreeMap::new(),
|
||||
};
|
||||
let waits_for_self = active_data.requests.contains(&self.meta.self_node_id);
|
||||
let self_response = if waits_for_self {
|
||||
let partial_request = self.executor.prepare_partial_request(&self.meta.self_node_id, &active_data.requests)?;
|
||||
Some(self.executor.process_partial_request(partial_request)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let self_response = match self_response {
|
||||
Some(JobPartialRequestAction::Respond(self_response)) => Some(self_response),
|
||||
Some(JobPartialRequestAction::Reject(self_response)) => Some(self_response),
|
||||
Some(self_response) => Some(self_response),
|
||||
None if waits_for_self => {
|
||||
let partial_request = self.executor.prepare_partial_request(&self.meta.self_node_id, &active_data.requests)?;
|
||||
let self_response = self.executor.process_partial_request(partial_request)?;
|
||||
Some(self_response.take_response())
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
|
||||
@ -249,11 +247,11 @@ impl<Executor, Transport> JobSession<Executor, Transport> where Executor: JobExe
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(self_response)
|
||||
}
|
||||
|
||||
/// When partial request is received by slave node.
|
||||
pub fn on_partial_request(&mut self, node: &NodeId, request: Executor::PartialJobRequest) -> Result<(), Error> {
|
||||
pub fn on_partial_request(&mut self, node: &NodeId, request: Executor::PartialJobRequest) -> Result<JobPartialRequestAction<Executor::PartialJobResponse>, Error> {
|
||||
if node != &self.meta.master_node_id {
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
@ -264,17 +262,19 @@ impl<Executor, Transport> JobSession<Executor, Transport> where Executor: JobExe
|
||||
return Err(Error::InvalidStateForRequest);
|
||||
}
|
||||
|
||||
let partial_response = match self.executor.process_partial_request(request)? {
|
||||
JobPartialRequestAction::Respond(partial_response) => {
|
||||
let partial_request_action = self.executor.process_partial_request(request)?;
|
||||
let partial_response = match partial_request_action {
|
||||
JobPartialRequestAction::Respond(ref partial_response) => {
|
||||
self.data.state = JobSessionState::Finished;
|
||||
partial_response
|
||||
partial_response.clone()
|
||||
},
|
||||
JobPartialRequestAction::Reject(partial_response) => {
|
||||
JobPartialRequestAction::Reject(ref partial_response) => {
|
||||
self.data.state = JobSessionState::Failed;
|
||||
partial_response
|
||||
partial_response.clone()
|
||||
},
|
||||
};
|
||||
self.transport.send_partial_response(node, partial_response)
|
||||
self.transport.send_partial_response(node, partial_response)?;
|
||||
Ok(partial_request_action)
|
||||
}
|
||||
|
||||
/// When partial request is received by master node.
|
||||
@ -291,7 +291,7 @@ impl<Executor, Transport> JobSession<Executor, Transport> where Executor: JobExe
|
||||
if !active_data.requests.remove(node) {
|
||||
return Err(Error::InvalidNodeForRequest);
|
||||
}
|
||||
|
||||
|
||||
match self.executor.check_partial_response(node, &response)? {
|
||||
JobPartialResponseAction::Ignore => Ok(()),
|
||||
JobPartialResponseAction::Reject => {
|
||||
@ -358,6 +358,15 @@ impl<Executor, Transport> JobSession<Executor, Transport> where Executor: JobExe
|
||||
}
|
||||
}
|
||||
|
||||
impl<PartialJobResponse> JobPartialRequestAction<PartialJobResponse> {
|
||||
/// Take actual response.
|
||||
pub fn take_response(self) -> PartialJobResponse {
|
||||
match self {
|
||||
JobPartialRequestAction::Respond(response) => response,
|
||||
JobPartialRequestAction::Reject(response) => response,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
@ -415,14 +424,14 @@ pub mod tests {
|
||||
#[test]
|
||||
fn job_initialize_fails_if_not_inactive() {
|
||||
let mut job = JobSession::new(make_master_session_meta(0), SquaredSumJobExecutor, DummyJobTransport::default());
|
||||
job.initialize(vec![Public::from(1)].into_iter().collect(), false).unwrap();
|
||||
assert_eq!(job.initialize(vec![Public::from(1)].into_iter().collect(), false).unwrap_err(), Error::InvalidStateForRequest);
|
||||
job.initialize(vec![Public::from(1)].into_iter().collect(), None, false).unwrap();
|
||||
assert_eq!(job.initialize(vec![Public::from(1)].into_iter().collect(), None, false).unwrap_err(), Error::InvalidStateForRequest);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn job_initialization_leads_to_finish_if_single_node_is_required() {
|
||||
let mut job = JobSession::new(make_master_session_meta(0), SquaredSumJobExecutor, DummyJobTransport::default());
|
||||
job.initialize(vec![Public::from(1)].into_iter().collect(), false).unwrap();
|
||||
job.initialize(vec![Public::from(1)].into_iter().collect(), None, false).unwrap();
|
||||
assert_eq!(job.state(), JobSessionState::Finished);
|
||||
assert!(job.is_result_ready());
|
||||
assert_eq!(job.result(), Ok(4));
|
||||
@ -431,7 +440,7 @@ pub mod tests {
|
||||
#[test]
|
||||
fn job_initialization_does_not_leads_to_finish_if_single_other_node_is_required() {
|
||||
let mut job = JobSession::new(make_master_session_meta(0), SquaredSumJobExecutor, DummyJobTransport::default());
|
||||
job.initialize(vec![Public::from(2)].into_iter().collect(), false).unwrap();
|
||||
job.initialize(vec![Public::from(2)].into_iter().collect(), None, false).unwrap();
|
||||
assert_eq!(job.state(), JobSessionState::Active);
|
||||
}
|
||||
|
||||
@ -474,7 +483,7 @@ pub mod tests {
|
||||
#[test]
|
||||
fn job_response_fails_if_comes_to_failed_state() {
|
||||
let mut job = JobSession::new(make_master_session_meta(0), SquaredSumJobExecutor, DummyJobTransport::default());
|
||||
job.initialize(vec![Public::from(2)].into_iter().collect(), false).unwrap();
|
||||
job.initialize(vec![Public::from(2)].into_iter().collect(), None, false).unwrap();
|
||||
job.on_session_timeout().unwrap_err();
|
||||
assert_eq!(job.on_partial_response(&NodeId::from(2), 2).unwrap_err(), Error::InvalidStateForRequest);
|
||||
}
|
||||
@ -482,14 +491,14 @@ pub mod tests {
|
||||
#[test]
|
||||
fn job_response_fails_if_comes_from_unknown_node() {
|
||||
let mut job = JobSession::new(make_master_session_meta(0), SquaredSumJobExecutor, DummyJobTransport::default());
|
||||
job.initialize(vec![Public::from(2)].into_iter().collect(), false).unwrap();
|
||||
job.initialize(vec![Public::from(2)].into_iter().collect(), None, false).unwrap();
|
||||
assert_eq!(job.on_partial_response(&NodeId::from(3), 2).unwrap_err(), Error::InvalidNodeForRequest);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn job_response_leads_to_failure_if_too_few_nodes_left() {
|
||||
let mut job = JobSession::new(make_master_session_meta(1), SquaredSumJobExecutor, DummyJobTransport::default());
|
||||
job.initialize(vec![Public::from(1), Public::from(2)].into_iter().collect(), false).unwrap();
|
||||
job.initialize(vec![Public::from(1), Public::from(2)].into_iter().collect(), None, false).unwrap();
|
||||
assert_eq!(job.state(), JobSessionState::Active);
|
||||
assert_eq!(job.on_partial_response(&NodeId::from(2), 3).unwrap_err(), Error::ConsensusUnreachable);
|
||||
assert_eq!(job.state(), JobSessionState::Failed);
|
||||
@ -498,7 +507,7 @@ pub mod tests {
|
||||
#[test]
|
||||
fn job_response_succeeds() {
|
||||
let mut job = JobSession::new(make_master_session_meta(2), SquaredSumJobExecutor, DummyJobTransport::default());
|
||||
job.initialize(vec![Public::from(1), Public::from(2), Public::from(3)].into_iter().collect(), false).unwrap();
|
||||
job.initialize(vec![Public::from(1), Public::from(2), Public::from(3)].into_iter().collect(), None, false).unwrap();
|
||||
assert_eq!(job.state(), JobSessionState::Active);
|
||||
assert!(!job.is_result_ready());
|
||||
job.on_partial_response(&NodeId::from(2), 2).unwrap();
|
||||
@ -509,7 +518,7 @@ pub mod tests {
|
||||
#[test]
|
||||
fn job_response_leads_to_finish() {
|
||||
let mut job = JobSession::new(make_master_session_meta(1), SquaredSumJobExecutor, DummyJobTransport::default());
|
||||
job.initialize(vec![Public::from(1), Public::from(2)].into_iter().collect(), false).unwrap();
|
||||
job.initialize(vec![Public::from(1), Public::from(2)].into_iter().collect(), None, false).unwrap();
|
||||
assert_eq!(job.state(), JobSessionState::Active);
|
||||
job.on_partial_response(&NodeId::from(2), 2).unwrap();
|
||||
assert_eq!(job.state(), JobSessionState::Finished);
|
||||
@ -534,7 +543,7 @@ pub mod tests {
|
||||
#[test]
|
||||
fn job_node_error_ignored_when_disconnects_from_rejected() {
|
||||
let mut job = JobSession::new(make_master_session_meta(1), SquaredSumJobExecutor, DummyJobTransport::default());
|
||||
job.initialize(vec![Public::from(1), Public::from(2), Public::from(3)].into_iter().collect(), false).unwrap();
|
||||
job.initialize(vec![Public::from(1), Public::from(2), Public::from(3)].into_iter().collect(), None, false).unwrap();
|
||||
assert_eq!(job.state(), JobSessionState::Active);
|
||||
job.on_partial_response(&NodeId::from(2), 3).unwrap();
|
||||
job.on_node_error(&NodeId::from(2)).unwrap();
|
||||
@ -544,7 +553,7 @@ pub mod tests {
|
||||
#[test]
|
||||
fn job_node_error_ignored_when_disconnects_from_unknown() {
|
||||
let mut job = JobSession::new(make_master_session_meta(1), SquaredSumJobExecutor, DummyJobTransport::default());
|
||||
job.initialize(vec![Public::from(1), Public::from(2)].into_iter().collect(), false).unwrap();
|
||||
job.initialize(vec![Public::from(1), Public::from(2)].into_iter().collect(), None, false).unwrap();
|
||||
assert_eq!(job.state(), JobSessionState::Active);
|
||||
job.on_node_error(&NodeId::from(3)).unwrap();
|
||||
assert_eq!(job.state(), JobSessionState::Active);
|
||||
@ -553,7 +562,7 @@ pub mod tests {
|
||||
#[test]
|
||||
fn job_node_error_ignored_when_disconnects_from_requested_and_enough_nodes_left() {
|
||||
let mut job = JobSession::new(make_master_session_meta(1), SquaredSumJobExecutor, DummyJobTransport::default());
|
||||
job.initialize(vec![Public::from(1), Public::from(2), Public::from(3)].into_iter().collect(), false).unwrap();
|
||||
job.initialize(vec![Public::from(1), Public::from(2), Public::from(3)].into_iter().collect(), None, false).unwrap();
|
||||
assert_eq!(job.state(), JobSessionState::Active);
|
||||
job.on_node_error(&NodeId::from(3)).unwrap();
|
||||
assert_eq!(job.state(), JobSessionState::Active);
|
||||
@ -562,7 +571,7 @@ pub mod tests {
|
||||
#[test]
|
||||
fn job_node_error_leads_to_fail_when_disconnects_from_requested_and_not_enough_nodes_left() {
|
||||
let mut job = JobSession::new(make_master_session_meta(1), SquaredSumJobExecutor, DummyJobTransport::default());
|
||||
job.initialize(vec![Public::from(1), Public::from(2)].into_iter().collect(), false).unwrap();
|
||||
job.initialize(vec![Public::from(1), Public::from(2)].into_iter().collect(), None, false).unwrap();
|
||||
assert_eq!(job.state(), JobSessionState::Active);
|
||||
assert_eq!(job.on_node_error(&NodeId::from(2)).unwrap_err(), Error::ConsensusUnreachable);
|
||||
assert_eq!(job.state(), JobSessionState::Failed);
|
||||
@ -571,7 +580,7 @@ pub mod tests {
|
||||
#[test]
|
||||
fn job_broadcasts_self_response() {
|
||||
let mut job = JobSession::new(make_master_session_meta(1), SquaredSumJobExecutor, DummyJobTransport::default());
|
||||
job.initialize(vec![Public::from(1), Public::from(2)].into_iter().collect(), true).unwrap();
|
||||
job.initialize(vec![Public::from(1), Public::from(2)].into_iter().collect(), None, true).unwrap();
|
||||
assert_eq!(job.state(), JobSessionState::Active);
|
||||
assert_eq!(job.transport().response(), (NodeId::from(2), 4));
|
||||
}
|
||||
@ -579,7 +588,7 @@ pub mod tests {
|
||||
#[test]
|
||||
fn job_does_not_broadcasts_self_response() {
|
||||
let mut job = JobSession::new(make_master_session_meta(1), SquaredSumJobExecutor, DummyJobTransport::default());
|
||||
job.initialize(vec![Public::from(1), Public::from(2)].into_iter().collect(), false).unwrap();
|
||||
job.initialize(vec![Public::from(1), Public::from(2)].into_iter().collect(), None, false).unwrap();
|
||||
assert_eq!(job.state(), JobSessionState::Active);
|
||||
assert!(job.transport().is_empty_response());
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ impl JobExecutor for KeyAccessJob {
|
||||
}
|
||||
|
||||
self.requester = Some(partial_request.clone());
|
||||
self.acl_storage.check(&partial_request.public(&self.id).ok_or(Error::InsufficientRequesterData)?, &self.id)
|
||||
self.acl_storage.check(partial_request.address(&self.id).map_err(Error::InsufficientRequesterData)?, &self.id)
|
||||
.map_err(|_| Error::AccessDenied)
|
||||
.map(|is_confirmed| if is_confirmed { JobPartialRequestAction::Respond(true) } else { JobPartialRequestAction::Reject(false) })
|
||||
}
|
||||
|
@ -272,6 +272,8 @@ pub struct InitializeSession {
|
||||
pub session: MessageSessionId,
|
||||
/// Session-level nonce.
|
||||
pub session_nonce: u64,
|
||||
/// Session origin address (if any).
|
||||
pub origin: Option<SerializableAddress>,
|
||||
/// Session author.
|
||||
pub author: SerializableAddress,
|
||||
/// All session participants along with their identification numbers.
|
||||
@ -713,6 +715,8 @@ pub struct DecryptionConsensusMessage {
|
||||
pub sub_session: SerializableSecret,
|
||||
/// Session-level nonce.
|
||||
pub session_nonce: u64,
|
||||
/// Session origin (in consensus initialization message).
|
||||
pub origin: Option<SerializableAddress>,
|
||||
/// Consensus message.
|
||||
pub message: ConsensusMessage,
|
||||
}
|
||||
@ -788,6 +792,8 @@ pub struct DecryptionSessionDelegation {
|
||||
pub sub_session: SerializableSecret,
|
||||
/// Session-level nonce.
|
||||
pub session_nonce: u64,
|
||||
/// Session origin.
|
||||
pub origin: Option<SerializableAddress>,
|
||||
/// Requester.
|
||||
pub requester: SerializableRequester,
|
||||
/// Key version.
|
||||
|
@ -115,7 +115,7 @@ pub enum Error {
|
||||
/// Can't start exclusive session, because there are other active sessions.
|
||||
HasActiveSessions,
|
||||
/// Insufficient requester data.
|
||||
InsufficientRequesterData,
|
||||
InsufficientRequesterData(String),
|
||||
}
|
||||
|
||||
impl From<ethkey::Error> for Error {
|
||||
@ -164,7 +164,7 @@ impl fmt::Display for Error {
|
||||
Error::AccessDenied => write!(f, "Access denied"),
|
||||
Error::ExclusiveSessionActive => write!(f, "Exclusive session active"),
|
||||
Error::HasActiveSessions => write!(f, "Unable to start exclusive session"),
|
||||
Error::InsufficientRequesterData => write!(f, "Insufficient requester data"),
|
||||
Error::InsufficientRequesterData(ref e) => write!(f, "Insufficient requester data: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -475,6 +475,10 @@ pub mod tests {
|
||||
let config = ServiceConfiguration {
|
||||
listener_address: None,
|
||||
service_contract_address: None,
|
||||
service_contract_srv_gen_address: None,
|
||||
service_contract_srv_retr_address: None,
|
||||
service_contract_doc_store_address: None,
|
||||
service_contract_doc_sretr_address: None,
|
||||
acl_check_enabled: true,
|
||||
data_path: tempdir.path().display().to_string(),
|
||||
cluster_config: ClusterConfiguration {
|
||||
|
@ -86,26 +86,75 @@ pub fn start(client: Arc<Client>, sync: Arc<SyncProvider>, self_key_pair: Arc<No
|
||||
let key_server_set = key_server_set::OnChainKeyServerSet::new(trusted_client.clone(), self_key_pair.clone(),
|
||||
config.cluster_config.auto_migrate_enabled, config.cluster_config.nodes.clone())?;
|
||||
let key_storage = Arc::new(key_storage::PersistentKeyStorage::new(&config)?);
|
||||
let key_server = Arc::new(key_server::KeyServerImpl::new(&config.cluster_config, key_server_set.clone(), self_key_pair.clone(), acl_storage, key_storage.clone())?);
|
||||
let key_server = Arc::new(key_server::KeyServerImpl::new(&config.cluster_config, key_server_set.clone(), self_key_pair.clone(), acl_storage.clone(), key_storage.clone())?);
|
||||
let cluster = key_server.cluster();
|
||||
let key_server: Arc<KeyServer> = key_server;
|
||||
|
||||
// prepare listeners
|
||||
// prepare HTTP listener
|
||||
let http_listener = match config.listener_address {
|
||||
Some(listener_address) => Some(listener::http_listener::KeyServerHttpListener::start(listener_address, key_server.clone())?),
|
||||
Some(listener_address) => Some(listener::http_listener::KeyServerHttpListener::start(listener_address, Arc::downgrade(&key_server))?),
|
||||
None => None,
|
||||
};
|
||||
let contract_listener = config.service_contract_address.map(|service_contract_address| {
|
||||
let service_contract = Arc::new(listener::service_contract::OnChainServiceContract::new(trusted_client, service_contract_address, self_key_pair.clone()));
|
||||
let contract_listener = listener::service_contract_listener::ServiceContractListener::new(listener::service_contract_listener::ServiceContractListenerParams {
|
||||
contract: service_contract,
|
||||
key_server: key_server.clone(),
|
||||
self_key_pair: self_key_pair,
|
||||
key_server_set: key_server_set,
|
||||
cluster: cluster,
|
||||
key_storage: key_storage,
|
||||
});
|
||||
client.add_notify(contract_listener.clone());
|
||||
contract_listener
|
||||
});
|
||||
|
||||
// prepare service contract listeners
|
||||
let create_service_contract = |address, name, api_mask|
|
||||
Arc::new(listener::service_contract::OnChainServiceContract::new(
|
||||
api_mask,
|
||||
trusted_client.clone(),
|
||||
name,
|
||||
address,
|
||||
self_key_pair.clone()));
|
||||
|
||||
let mut contracts: Vec<Arc<listener::service_contract::ServiceContract>> = Vec::new();
|
||||
config.service_contract_address.map(|address|
|
||||
create_service_contract(address,
|
||||
listener::service_contract::SERVICE_CONTRACT_REGISTRY_NAME.to_owned(),
|
||||
listener::ApiMask::all()))
|
||||
.map(|l| contracts.push(l));
|
||||
config.service_contract_srv_gen_address.map(|address|
|
||||
create_service_contract(address,
|
||||
listener::service_contract::SRV_KEY_GEN_SERVICE_CONTRACT_REGISTRY_NAME.to_owned(),
|
||||
listener::ApiMask { server_key_generation_requests: true, ..Default::default() }))
|
||||
.map(|l| contracts.push(l));
|
||||
config.service_contract_srv_retr_address.map(|address|
|
||||
create_service_contract(address,
|
||||
listener::service_contract::SRV_KEY_RETR_SERVICE_CONTRACT_REGISTRY_NAME.to_owned(),
|
||||
listener::ApiMask { server_key_retrieval_requests: true, ..Default::default() }))
|
||||
.map(|l| contracts.push(l));
|
||||
config.service_contract_doc_store_address.map(|address|
|
||||
create_service_contract(address,
|
||||
listener::service_contract::DOC_KEY_STORE_SERVICE_CONTRACT_REGISTRY_NAME.to_owned(),
|
||||
listener::ApiMask { document_key_store_requests: true, ..Default::default() }))
|
||||
.map(|l| contracts.push(l));
|
||||
config.service_contract_doc_sretr_address.map(|address|
|
||||
create_service_contract(address,
|
||||
listener::service_contract::DOC_KEY_SRETR_SERVICE_CONTRACT_REGISTRY_NAME.to_owned(),
|
||||
listener::ApiMask { document_key_shadow_retrieval_requests: true, ..Default::default() }))
|
||||
.map(|l| contracts.push(l));
|
||||
|
||||
let contract: Option<Arc<listener::service_contract::ServiceContract>> = match contracts.len() {
|
||||
0 => None,
|
||||
1 => Some(contracts.pop().expect("contract.len() is 1; qed")),
|
||||
_ => Some(Arc::new(listener::service_contract_aggregate::OnChainServiceContractAggregate::new(contracts))),
|
||||
};
|
||||
|
||||
let contract_listener = match contract {
|
||||
Some(contract) => Some({
|
||||
let listener = listener::service_contract_listener::ServiceContractListener::new(
|
||||
listener::service_contract_listener::ServiceContractListenerParams {
|
||||
contract: contract,
|
||||
self_key_pair: self_key_pair.clone(),
|
||||
key_server_set: key_server_set,
|
||||
acl_storage: acl_storage,
|
||||
cluster: cluster,
|
||||
key_storage: key_storage,
|
||||
}
|
||||
)?;
|
||||
client.add_notify(listener.clone());
|
||||
listener
|
||||
}),
|
||||
None => None,
|
||||
};
|
||||
|
||||
Ok(Box::new(listener::Listener::new(key_server, http_listener, contract_listener)))
|
||||
}
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
use std::io::Read;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, Weak};
|
||||
use hyper::header;
|
||||
use hyper::uri::RequestUri;
|
||||
use hyper::method::Method as HttpMethod;
|
||||
@ -77,12 +77,12 @@ struct KeyServerHttpHandler {
|
||||
|
||||
/// Shared http handler
|
||||
struct KeyServerSharedHttpHandler {
|
||||
key_server: Arc<KeyServer>,
|
||||
key_server: Weak<KeyServer>,
|
||||
}
|
||||
|
||||
impl KeyServerHttpListener {
|
||||
/// Start KeyServer http listener
|
||||
pub fn start(listener_address: NodeAddress, key_server: Arc<KeyServer>) -> Result<Self, Error> {
|
||||
pub fn start(listener_address: NodeAddress, key_server: Weak<KeyServer>) -> Result<Self, Error> {
|
||||
let shared_handler = Arc::new(KeyServerSharedHttpHandler {
|
||||
key_server: key_server,
|
||||
});
|
||||
@ -128,56 +128,72 @@ impl HttpHandler for KeyServerHttpHandler {
|
||||
match &req_uri {
|
||||
&RequestUri::AbsolutePath(ref path) => match parse_request(&req_method, &path, &req_body) {
|
||||
Request::GenerateServerKey(document, signature, threshold) => {
|
||||
return_server_public_key(req, res, self.handler.key_server.generate_key(&document, &signature, threshold)
|
||||
return_server_public_key(req, res, self.handler.key_server.upgrade()
|
||||
.map(|key_server| key_server.generate_key(&document, &signature.into(), threshold))
|
||||
.unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into())))
|
||||
.map_err(|err| {
|
||||
warn!(target: "secretstore", "GenerateServerKey request {} has failed with: {}", req_uri, err);
|
||||
err
|
||||
}));
|
||||
},
|
||||
Request::StoreDocumentKey(document, signature, common_point, encrypted_document_key) => {
|
||||
return_empty(req, res, self.handler.key_server.store_document_key(&document, &signature, common_point, encrypted_document_key)
|
||||
return_empty(req, res, self.handler.key_server.upgrade()
|
||||
.map(|key_server| key_server.store_document_key(&document, &signature.into(), common_point, encrypted_document_key))
|
||||
.unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into())))
|
||||
.map_err(|err| {
|
||||
warn!(target: "secretstore", "StoreDocumentKey request {} has failed with: {}", req_uri, err);
|
||||
err
|
||||
}));
|
||||
},
|
||||
Request::GenerateDocumentKey(document, signature, threshold) => {
|
||||
return_document_key(req, res, self.handler.key_server.generate_document_key(&document, &signature, threshold)
|
||||
return_document_key(req, res, self.handler.key_server.upgrade()
|
||||
.map(|key_server| key_server.generate_document_key(&document, &signature.into(), threshold))
|
||||
.unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into())))
|
||||
.map_err(|err| {
|
||||
warn!(target: "secretstore", "GenerateDocumentKey request {} has failed with: {}", req_uri, err);
|
||||
err
|
||||
}));
|
||||
},
|
||||
Request::GetDocumentKey(document, signature) => {
|
||||
return_document_key(req, res, self.handler.key_server.restore_document_key(&document, &signature)
|
||||
return_document_key(req, res, self.handler.key_server.upgrade()
|
||||
.map(|key_server| key_server.restore_document_key(&document, &signature.into()))
|
||||
.unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into())))
|
||||
.map_err(|err| {
|
||||
warn!(target: "secretstore", "GetDocumentKey request {} has failed with: {}", req_uri, err);
|
||||
err
|
||||
}));
|
||||
},
|
||||
Request::GetDocumentKeyShadow(document, signature) => {
|
||||
return_document_key_shadow(req, res, self.handler.key_server.restore_document_key_shadow(&document, &signature)
|
||||
return_document_key_shadow(req, res, self.handler.key_server.upgrade()
|
||||
.map(|key_server| key_server.restore_document_key_shadow(&document, &signature.into()))
|
||||
.unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into())))
|
||||
.map_err(|err| {
|
||||
warn!(target: "secretstore", "GetDocumentKeyShadow request {} has failed with: {}", req_uri, err);
|
||||
err
|
||||
}));
|
||||
},
|
||||
Request::SchnorrSignMessage(document, signature, message_hash) => {
|
||||
return_message_signature(req, res, self.handler.key_server.sign_message_schnorr(&document, &signature, message_hash)
|
||||
return_message_signature(req, res, self.handler.key_server.upgrade()
|
||||
.map(|key_server| key_server.sign_message_schnorr(&document, &signature.into(), message_hash))
|
||||
.unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into())))
|
||||
.map_err(|err| {
|
||||
warn!(target: "secretstore", "SchnorrSignMessage request {} has failed with: {}", req_uri, err);
|
||||
err
|
||||
}));
|
||||
},
|
||||
Request::EcdsaSignMessage(document, signature, message_hash) => {
|
||||
return_message_signature(req, res, self.handler.key_server.sign_message_ecdsa(&document, &signature, message_hash)
|
||||
return_message_signature(req, res, self.handler.key_server.upgrade()
|
||||
.map(|key_server| key_server.sign_message_ecdsa(&document, &signature.into(), message_hash))
|
||||
.unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into())))
|
||||
.map_err(|err| {
|
||||
warn!(target: "secretstore", "EcdsaSignMessage request {} has failed with: {}", req_uri, err);
|
||||
err
|
||||
}));
|
||||
},
|
||||
Request::ChangeServersSet(old_set_signature, new_set_signature, new_servers_set) => {
|
||||
return_empty(req, res, self.handler.key_server.change_servers_set(old_set_signature, new_set_signature, new_servers_set)
|
||||
return_empty(req, res, self.handler.key_server.upgrade()
|
||||
.map(|key_server| key_server.change_servers_set(old_set_signature, new_set_signature, new_servers_set))
|
||||
.unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into())))
|
||||
.map_err(|err| {
|
||||
warn!(target: "secretstore", "ChangeServersSet request {} has failed with: {}", req_uri, err);
|
||||
err
|
||||
@ -241,7 +257,7 @@ fn return_bytes<T: Serialize>(req: HttpRequest, mut res: HttpResponse, result: R
|
||||
|
||||
fn return_error(mut res: HttpResponse, err: Error) {
|
||||
match err {
|
||||
Error::BadSignature => *res.status_mut() = HttpStatusCode::BadRequest,
|
||||
Error::InsufficientRequesterData(_) => *res.status_mut() = HttpStatusCode::BadRequest,
|
||||
Error::AccessDenied => *res.status_mut() = HttpStatusCode::Forbidden,
|
||||
Error::DocumentNotFound => *res.status_mut() = HttpStatusCode::NotFound,
|
||||
Error::Hyper(_) => *res.status_mut() = HttpStatusCode::BadRequest,
|
||||
@ -342,15 +358,16 @@ mod tests {
|
||||
use std::sync::Arc;
|
||||
use hyper::method::Method as HttpMethod;
|
||||
use ethkey::Public;
|
||||
use traits::KeyServer;
|
||||
use key_server::tests::DummyKeyServer;
|
||||
use types::all::NodeAddress;
|
||||
use super::{parse_request, Request, KeyServerHttpListener};
|
||||
|
||||
#[test]
|
||||
fn http_listener_successfully_drops() {
|
||||
let key_server = Arc::new(DummyKeyServer::default());
|
||||
let key_server: Arc<KeyServer> = Arc::new(DummyKeyServer::default());
|
||||
let address = NodeAddress { address: "127.0.0.1".into(), port: 9000 };
|
||||
let listener = KeyServerHttpListener::start(address, key_server).unwrap();
|
||||
let listener = KeyServerHttpListener::start(address, Arc::downgrade(&key_server)).unwrap();
|
||||
drop(listener);
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
pub mod http_listener;
|
||||
pub mod service_contract;
|
||||
pub mod service_contract_aggregate;
|
||||
pub mod service_contract_listener;
|
||||
mod tasks_queue;
|
||||
|
||||
@ -23,15 +24,42 @@ use std::collections::BTreeSet;
|
||||
use std::sync::Arc;
|
||||
use traits::{ServerKeyGenerator, DocumentKeyServer, MessageSigner, AdminSessionsServer, KeyServer};
|
||||
use types::all::{Error, Public, MessageHash, EncryptedMessageSignature, RequestSignature, ServerKeyId,
|
||||
EncryptedDocumentKey, EncryptedDocumentKeyShadow, NodeId};
|
||||
EncryptedDocumentKey, EncryptedDocumentKeyShadow, NodeId, Requester};
|
||||
|
||||
/// Available API mask.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ApiMask {
|
||||
/// Accept server key generation requests.
|
||||
pub server_key_generation_requests: bool,
|
||||
/// Accept server key retrieval requests.
|
||||
pub server_key_retrieval_requests: bool,
|
||||
/// Accept document key store requests.
|
||||
pub document_key_store_requests: bool,
|
||||
/// Accept document key shadow retrieval requests.
|
||||
pub document_key_shadow_retrieval_requests: bool,
|
||||
}
|
||||
|
||||
/// Combined HTTP + service contract listener.
|
||||
pub struct Listener {
|
||||
key_server: Arc<KeyServer>,
|
||||
_http: Option<http_listener::KeyServerHttpListener>,
|
||||
_contract: Option<Arc<service_contract_listener::ServiceContractListener>>,
|
||||
}
|
||||
|
||||
impl ApiMask {
|
||||
/// Create mask that accepts all requests.
|
||||
pub fn all() -> Self {
|
||||
ApiMask {
|
||||
server_key_generation_requests: true,
|
||||
server_key_retrieval_requests: true,
|
||||
document_key_store_requests: true,
|
||||
document_key_shadow_retrieval_requests: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Listener {
|
||||
/// Create new listener.
|
||||
pub fn new(key_server: Arc<KeyServer>, http: Option<http_listener::KeyServerHttpListener>, contract: Option<Arc<service_contract_listener::ServiceContractListener>>) -> Self {
|
||||
Self {
|
||||
key_server: key_server,
|
||||
@ -44,36 +72,36 @@ impl Listener {
|
||||
impl KeyServer for Listener {}
|
||||
|
||||
impl ServerKeyGenerator for Listener {
|
||||
fn generate_key(&self, key_id: &ServerKeyId, signature: &RequestSignature, threshold: usize) -> Result<Public, Error> {
|
||||
self.key_server.generate_key(key_id, signature, threshold)
|
||||
fn generate_key(&self, key_id: &ServerKeyId, author: &Requester, threshold: usize) -> Result<Public, Error> {
|
||||
self.key_server.generate_key(key_id, author, 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 store_document_key(&self, key_id: &ServerKeyId, author: &Requester, common_point: Public, encrypted_document_key: Public) -> Result<(), Error> {
|
||||
self.key_server.store_document_key(key_id, author, common_point, encrypted_document_key)
|
||||
}
|
||||
|
||||
fn generate_document_key(&self, key_id: &ServerKeyId, signature: &RequestSignature, threshold: usize) -> Result<EncryptedDocumentKey, Error> {
|
||||
self.key_server.generate_document_key(key_id, signature, threshold)
|
||||
fn generate_document_key(&self, key_id: &ServerKeyId, author: &Requester, threshold: usize) -> Result<EncryptedDocumentKey, Error> {
|
||||
self.key_server.generate_document_key(key_id, author, threshold)
|
||||
}
|
||||
|
||||
fn restore_document_key(&self, key_id: &ServerKeyId, signature: &RequestSignature) -> Result<EncryptedDocumentKey, Error> {
|
||||
self.key_server.restore_document_key(key_id, signature)
|
||||
fn restore_document_key(&self, key_id: &ServerKeyId, requester: &Requester) -> Result<EncryptedDocumentKey, Error> {
|
||||
self.key_server.restore_document_key(key_id, requester)
|
||||
}
|
||||
|
||||
fn restore_document_key_shadow(&self, key_id: &ServerKeyId, signature: &RequestSignature) -> Result<EncryptedDocumentKeyShadow, Error> {
|
||||
self.key_server.restore_document_key_shadow(key_id, signature)
|
||||
fn restore_document_key_shadow(&self, key_id: &ServerKeyId, requester: &Requester) -> Result<EncryptedDocumentKeyShadow, Error> {
|
||||
self.key_server.restore_document_key_shadow(key_id, requester)
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageSigner for Listener {
|
||||
fn sign_message_schnorr(&self, key_id: &ServerKeyId, signature: &RequestSignature, message: MessageHash) -> Result<EncryptedMessageSignature, Error> {
|
||||
self.key_server.sign_message_schnorr(key_id, signature, message)
|
||||
fn sign_message_schnorr(&self, key_id: &ServerKeyId, requester: &Requester, message: MessageHash) -> Result<EncryptedMessageSignature, Error> {
|
||||
self.key_server.sign_message_schnorr(key_id, requester, message)
|
||||
}
|
||||
|
||||
fn sign_message_ecdsa(&self, key_id: &ServerKeyId, signature: &RequestSignature, message: MessageHash) -> Result<EncryptedMessageSignature, Error> {
|
||||
self.key_server.sign_message_ecdsa(key_id, signature, message)
|
||||
fn sign_message_ecdsa(&self, key_id: &ServerKeyId, requester: &Requester, message: MessageHash) -> Result<EncryptedMessageSignature, Error> {
|
||||
self.key_server.sign_message_ecdsa(key_id, requester, message)
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,4 +109,4 @@ impl AdminSessionsServer for Listener {
|
||||
fn change_servers_set(&self, old_set_signature: RequestSignature, new_set_signature: RequestSignature, new_servers_set: BTreeSet<NodeId>) -> Result<(), Error> {
|
||||
self.key_server.change_servers_set(old_set_signature, new_set_signature, new_servers_set)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,28 +16,51 @@
|
||||
|
||||
use std::sync::Arc;
|
||||
use parking_lot::RwLock;
|
||||
use ethabi::RawLog;
|
||||
use ethcore::filter::Filter;
|
||||
use ethcore::client::{Client, BlockChainClient, BlockId, RegistryInfo, CallContract};
|
||||
use ethkey::{Public, Signature, public_to_address};
|
||||
use ethkey::{Public, public_to_address};
|
||||
use hash::keccak;
|
||||
use bytes::Bytes;
|
||||
use ethereum_types::{H256, U256, Address};
|
||||
use listener::ApiMask;
|
||||
use listener::service_contract_listener::ServiceTask;
|
||||
use trusted_client::TrustedClient;
|
||||
use {ServerKeyId, NodeKeyPair, ContractAddress};
|
||||
|
||||
use_contract!(service, "Service", "res/service.json");
|
||||
|
||||
/// Name of the SecretStore contract in the registry.
|
||||
const SERVICE_CONTRACT_REGISTRY_NAME: &'static str = "secretstore_service";
|
||||
/// Name of the general SecretStore contract in the registry.
|
||||
pub const SERVICE_CONTRACT_REGISTRY_NAME: &'static str = "secretstore_service";
|
||||
/// Name of the server key generation SecretStore contract in the registry.
|
||||
pub const SRV_KEY_GEN_SERVICE_CONTRACT_REGISTRY_NAME: &'static str = "secretstore_service_srv_gen";
|
||||
/// Name of the server key retrieval SecretStore contract in the registry.
|
||||
pub const SRV_KEY_RETR_SERVICE_CONTRACT_REGISTRY_NAME: &'static str = "secretstore_service_srv_retr";
|
||||
/// Name of the document key store SecretStore contract in the registry.
|
||||
pub const DOC_KEY_STORE_SERVICE_CONTRACT_REGISTRY_NAME: &'static str = "secretstore_service_doc_store";
|
||||
/// Name of the document key retrieval SecretStore contract in the registry.
|
||||
pub const DOC_KEY_SRETR_SERVICE_CONTRACT_REGISTRY_NAME: &'static str = "secretstore_service_doc_sretr";
|
||||
|
||||
/// Key server has been added to the set.
|
||||
const SERVER_KEY_REQUESTED_EVENT_NAME: &'static [u8] = &*b"ServerKeyRequested(bytes32,uint256)";
|
||||
/// Server key generation has been requested.
|
||||
const SERVER_KEY_GENERATION_REQUESTED_EVENT_NAME: &'static [u8] = &*b"ServerKeyGenerationRequested(bytes32,address,uint8)";
|
||||
/// Server key retrieval has been requested.
|
||||
const SERVER_KEY_RETRIEVAL_REQUESTED_EVENT_NAME: &'static [u8] = &*b"ServerKeyRetrievalRequested(bytes32)";
|
||||
/// Document key store has been requested.
|
||||
const DOCUMENT_KEY_STORE_REQUESTED_EVENT_NAME: &'static [u8] = &*b"DocumentKeyStoreRequested(bytes32,address,bytes,bytes)";
|
||||
/// Document key common part retrieval has been requested.
|
||||
const DOCUMENT_KEY_COMMON_PART_RETRIEVAL_REQUESTED_EVENT_NAME: &'static [u8] = &*b"DocumentKeyCommonRetrievalRequested(bytes32,address)";
|
||||
/// Document key personal part retrieval has been requested.
|
||||
const DOCUMENT_KEY_PERSONAL_PART_RETRIEVAL_REQUESTED_EVENT_NAME: &'static [u8] = &*b"DocumentKeyPersonalRetrievalRequested(bytes32,bytes)";
|
||||
|
||||
/// Number of confirmations required before request can be processed.
|
||||
const REQUEST_CONFIRMATIONS_REQUIRED: u64 = 3;
|
||||
|
||||
lazy_static! {
|
||||
static ref SERVER_KEY_REQUESTED_EVENT_NAME_HASH: H256 = keccak(SERVER_KEY_REQUESTED_EVENT_NAME);
|
||||
pub static ref SERVER_KEY_GENERATION_REQUESTED_EVENT_NAME_HASH: H256 = keccak(SERVER_KEY_GENERATION_REQUESTED_EVENT_NAME);
|
||||
pub static ref SERVER_KEY_RETRIEVAL_REQUESTED_EVENT_NAME_HASH: H256 = keccak(SERVER_KEY_RETRIEVAL_REQUESTED_EVENT_NAME);
|
||||
pub static ref DOCUMENT_KEY_STORE_REQUESTED_EVENT_NAME_HASH: H256 = keccak(DOCUMENT_KEY_STORE_REQUESTED_EVENT_NAME);
|
||||
pub static ref DOCUMENT_KEY_COMMON_PART_RETRIEVAL_REQUESTED_EVENT_NAME_HASH: H256 = keccak(DOCUMENT_KEY_COMMON_PART_RETRIEVAL_REQUESTED_EVENT_NAME);
|
||||
pub static ref DOCUMENT_KEY_PERSONAL_PART_RETRIEVAL_REQUESTED_EVENT_NAME_HASH: H256 = keccak(DOCUMENT_KEY_PERSONAL_PART_RETRIEVAL_REQUESTED_EVENT_NAME);
|
||||
}
|
||||
|
||||
/// Service contract trait.
|
||||
@ -45,61 +68,82 @@ pub trait ServiceContract: Send + Sync {
|
||||
/// Update contract when new blocks are enacted. Returns true if contract is installed && up-to-date (i.e. chain is synced).
|
||||
fn update(&self) -> bool;
|
||||
/// Read recent contract logs. Returns topics of every entry.
|
||||
fn read_logs(&self) -> Box<Iterator<Item=Vec<H256>>>;
|
||||
fn read_logs(&self) -> Box<Iterator<Item=ServiceTask>>;
|
||||
/// Publish generated key.
|
||||
fn read_pending_requests(&self) -> Box<Iterator<Item=(bool, ServiceTask)>>;
|
||||
/// Publish server key.
|
||||
fn publish_server_key(&self, server_key_id: &ServerKeyId, server_key: &Public) -> Result<(), String>;
|
||||
/// Publish generated server key.
|
||||
fn publish_generated_server_key(&self, origin: &Address, server_key_id: &ServerKeyId, server_key: Public) -> Result<(), String>;
|
||||
/// Publish server key generation error.
|
||||
fn publish_server_key_generation_error(&self, origin: &Address, server_key_id: &ServerKeyId) -> Result<(), String>;
|
||||
/// Publish retrieved server key.
|
||||
fn publish_retrieved_server_key(&self, origin: &Address, server_key_id: &ServerKeyId, server_key: Public, threshold: usize) -> Result<(), String>;
|
||||
/// Publish server key retrieval error.
|
||||
fn publish_server_key_retrieval_error(&self, origin: &Address, server_key_id: &ServerKeyId) -> Result<(), String>;
|
||||
/// Publish stored document key.
|
||||
fn publish_stored_document_key(&self, origin: &Address, server_key_id: &ServerKeyId) -> Result<(), String>;
|
||||
/// Publish document key store error.
|
||||
fn publish_document_key_store_error(&self, origin: &Address, server_key_id: &ServerKeyId) -> Result<(), String>;
|
||||
/// Publish retrieved document key common.
|
||||
fn publish_retrieved_document_key_common(&self, origin: &Address, server_key_id: &ServerKeyId, requester: &Address, common_point: Public, threshold: usize) -> Result<(), String>;
|
||||
/// Publish retrieved document key personal.
|
||||
fn publish_retrieved_document_key_personal(&self, origin: &Address, server_key_id: &ServerKeyId, requester: &Address, participants: &[Address], decrypted_secret: Public, shadow: Bytes) -> Result<(), String>;
|
||||
/// Publish document key store error.
|
||||
fn publish_document_key_retrieval_error(&self, origin: &Address, server_key_id: &ServerKeyId, requester: &Address) -> Result<(), String>;
|
||||
}
|
||||
|
||||
/// On-chain service contract.
|
||||
pub struct OnChainServiceContract {
|
||||
/// Requests mask.
|
||||
mask: ApiMask,
|
||||
/// Blockchain client.
|
||||
client: TrustedClient,
|
||||
/// This node key pair.
|
||||
self_key_pair: Arc<NodeKeyPair>,
|
||||
/// Contract addresss.
|
||||
/// Contract registry name (if any).
|
||||
name: String,
|
||||
/// Contract address.
|
||||
address: ContractAddress,
|
||||
/// Contract.
|
||||
contract: service::Service,
|
||||
/// Contract.
|
||||
data: RwLock<ServiceData>,
|
||||
}
|
||||
|
||||
/// On-chain service contract data.
|
||||
struct ServiceData {
|
||||
/// Contract.
|
||||
pub contract: service::Service,
|
||||
/// Contract address.
|
||||
/// Actual contract address.
|
||||
pub contract_address: Address,
|
||||
/// Last block we have read logs from.
|
||||
pub last_log_block: Option<H256>,
|
||||
}
|
||||
|
||||
/// Pending requests iterator.
|
||||
struct PendingRequestsIterator {
|
||||
/// Blockchain client.
|
||||
client: Arc<Client>,
|
||||
/// Contract.
|
||||
contract: service::Service,
|
||||
/// Contract address.
|
||||
contract_address: Address,
|
||||
/// This node key pair.
|
||||
self_key_pair: Arc<NodeKeyPair>,
|
||||
/// Block, this iterator is created for.
|
||||
block: H256,
|
||||
struct PendingRequestsIterator<F: Fn(U256) -> Option<(bool, ServiceTask)>> {
|
||||
/// Pending request read function.
|
||||
read_request: F,
|
||||
/// Current request index.
|
||||
index: U256,
|
||||
/// Requests length.
|
||||
length: U256,
|
||||
}
|
||||
|
||||
/// Server key generation related functions.
|
||||
struct ServerKeyGenerationService;
|
||||
/// Server key retrieval related functions.
|
||||
struct ServerKeyRetrievalService;
|
||||
/// Document key store related functions.
|
||||
struct DocumentKeyStoreService;
|
||||
/// Document key shadow retrievalrelated functions.
|
||||
struct DocumentKeyShadowRetrievalService;
|
||||
|
||||
impl OnChainServiceContract {
|
||||
/// Create new on-chain service contract.
|
||||
pub fn new(client: TrustedClient, address: ContractAddress, self_key_pair: Arc<NodeKeyPair>) -> Self {
|
||||
pub fn new(mask: ApiMask, client: TrustedClient, name: String, address: ContractAddress, self_key_pair: Arc<NodeKeyPair>) -> Self {
|
||||
let contract_addr = match address {
|
||||
ContractAddress::Registry => client.get().and_then(|c| c.registry_address(SERVICE_CONTRACT_REGISTRY_NAME.to_owned(), BlockId::Latest)
|
||||
ContractAddress::Registry => client.get().and_then(|c| c.registry_address(name.clone(), BlockId::Latest)
|
||||
.map(|address| {
|
||||
trace!(target: "secretstore", "{}: installing service contract from address {}",
|
||||
self_key_pair.public(), address);
|
||||
trace!(target: "secretstore", "{}: installing {} service contract from address {}",
|
||||
self_key_pair.public(), name, address);
|
||||
address
|
||||
}))
|
||||
.unwrap_or_default(),
|
||||
@ -111,16 +155,81 @@ impl OnChainServiceContract {
|
||||
};
|
||||
|
||||
OnChainServiceContract {
|
||||
mask: mask,
|
||||
client: client,
|
||||
self_key_pair: self_key_pair,
|
||||
name: name,
|
||||
address: address,
|
||||
contract: service::Service::default(),
|
||||
data: RwLock::new(ServiceData {
|
||||
contract: service::Service::default(),
|
||||
contract_address: contract_addr,
|
||||
last_log_block: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Send transaction to the service contract.
|
||||
fn send_contract_transaction<C, P>(&self, origin: &Address, server_key_id: &ServerKeyId, is_response_required: C, prepare_tx: P) -> Result<(), String>
|
||||
where C: FnOnce(&Client, &Address, &service::Service, &ServerKeyId, &Address) -> bool,
|
||||
P: FnOnce(&Client, &Address, &service::Service) -> Result<Bytes, String> {
|
||||
// only publish if contract address is set && client is online
|
||||
let client = match self.client.get() {
|
||||
Some(client) => client,
|
||||
None => return Err("trusted client is required to publish key".into()),
|
||||
};
|
||||
|
||||
// only publish key if contract waits for publication
|
||||
// failing is ok here - it could be that enough confirmations have been recevied
|
||||
// or key has been requested using HTTP API
|
||||
let self_address = public_to_address(self.self_key_pair.public());
|
||||
if !is_response_required(&*client, origin, &self.contract, server_key_id, &self_address) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// prepare transaction data
|
||||
let transaction_data = prepare_tx(&*client, origin, &self.contract)?;
|
||||
|
||||
// send transaction
|
||||
client.transact_contract(
|
||||
origin.clone(),
|
||||
transaction_data
|
||||
).map_err(|e| format!("{}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create task-specific pending requests iterator.
|
||||
fn create_pending_requests_iterator<
|
||||
C: 'static + Fn(&Client, &Address, &service::Service, &BlockId) -> Result<U256, String>,
|
||||
R: 'static + Fn(&NodeKeyPair, &Client, &Address, &service::Service, &BlockId, U256) -> Result<(bool, ServiceTask), String>
|
||||
>(&self, client: Arc<Client>, contract_address: &Address, block: &BlockId, get_count: C, read_item: R) -> Box<Iterator<Item=(bool, ServiceTask)>> {
|
||||
let contract = service::Service::default();
|
||||
get_count(&*client, contract_address, &contract, block)
|
||||
.map(|count| {
|
||||
let client = client.clone();
|
||||
let self_key_pair = self.self_key_pair.clone();
|
||||
let contract_address = contract_address.clone();
|
||||
let block = block.clone();
|
||||
Box::new(PendingRequestsIterator {
|
||||
read_request: move |index| read_item(&*self_key_pair, &*client, &contract_address, &contract, &block, index)
|
||||
.map_err(|error| {
|
||||
warn!(target: "secretstore", "{}: reading pending request failed: {}",
|
||||
self_key_pair.public(), error);
|
||||
error
|
||||
})
|
||||
.ok(),
|
||||
index: 0.into(),
|
||||
length: count,
|
||||
}) as Box<Iterator<Item=(bool, ServiceTask)>>
|
||||
})
|
||||
.map_err(|error| {
|
||||
warn!(target: "secretstore", "{}: creating pending requests iterator failed: {}",
|
||||
self.self_key_pair.public(), error);
|
||||
error
|
||||
})
|
||||
.ok()
|
||||
.unwrap_or_else(|| Box::new(::std::iter::empty()))
|
||||
}
|
||||
}
|
||||
|
||||
impl ServiceContract for OnChainServiceContract {
|
||||
@ -130,10 +239,10 @@ impl ServiceContract for OnChainServiceContract {
|
||||
if let &ContractAddress::Registry = &self.address {
|
||||
if let Some(client) = self.client.get() {
|
||||
// update contract address from registry
|
||||
let service_contract_addr = client.registry_address(SERVICE_CONTRACT_REGISTRY_NAME.to_owned(), BlockId::Latest).unwrap_or_default();
|
||||
let service_contract_addr = client.registry_address(self.name.clone(), BlockId::Latest).unwrap_or_default();
|
||||
if self.data.read().contract_address != service_contract_addr {
|
||||
trace!(target: "secretstore", "{}: installing service contract from address {}",
|
||||
self.self_key_pair.public(), service_contract_addr);
|
||||
trace!(target: "secretstore", "{}: installing {} service contract from address {}",
|
||||
self.self_key_pair.public(), self.name, service_contract_addr);
|
||||
self.data.write().contract_address = service_contract_addr;
|
||||
}
|
||||
}
|
||||
@ -143,7 +252,7 @@ impl ServiceContract for OnChainServiceContract {
|
||||
&& self.client.get().is_some()
|
||||
}
|
||||
|
||||
fn read_logs(&self) -> Box<Iterator<Item=Vec<H256>>> {
|
||||
fn read_logs(&self) -> Box<Iterator<Item=ServiceTask>> {
|
||||
let client = match self.client.get() {
|
||||
Some(client) => client,
|
||||
None => {
|
||||
@ -181,16 +290,33 @@ impl ServiceContract for OnChainServiceContract {
|
||||
from_block: BlockId::Hash(first_block),
|
||||
to_block: BlockId::Hash(last_block),
|
||||
address: Some(vec![address]),
|
||||
topics: vec![
|
||||
Some(vec![*SERVER_KEY_REQUESTED_EVENT_NAME_HASH]),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
],
|
||||
topics: vec![Some(mask_topics(&self.mask))],
|
||||
limit: None,
|
||||
});
|
||||
|
||||
Box::new(request_logs.into_iter().map(|log| log.entry.topics))
|
||||
Box::new(request_logs.into_iter()
|
||||
.filter_map(|log| {
|
||||
let raw_log: RawLog = (log.entry.topics.into_iter().map(|t| t.0.into()).collect(), log.entry.data).into();
|
||||
if raw_log.topics[0] == *SERVER_KEY_GENERATION_REQUESTED_EVENT_NAME_HASH {
|
||||
ServerKeyGenerationService::parse_log(&address, &self.contract, raw_log)
|
||||
} else if raw_log.topics[0] == *SERVER_KEY_RETRIEVAL_REQUESTED_EVENT_NAME_HASH {
|
||||
ServerKeyRetrievalService::parse_log(&address, &self.contract, raw_log)
|
||||
} else if raw_log.topics[0] == *DOCUMENT_KEY_STORE_REQUESTED_EVENT_NAME_HASH {
|
||||
DocumentKeyStoreService::parse_log(&address, &self.contract, raw_log)
|
||||
} else if raw_log.topics[0] == *DOCUMENT_KEY_COMMON_PART_RETRIEVAL_REQUESTED_EVENT_NAME_HASH {
|
||||
DocumentKeyShadowRetrievalService::parse_common_request_log(&address, &self.contract, raw_log)
|
||||
} else if raw_log.topics[0] == *DOCUMENT_KEY_PERSONAL_PART_RETRIEVAL_REQUESTED_EVENT_NAME_HASH {
|
||||
DocumentKeyShadowRetrievalService::parse_personal_request_log(&address, &self.contract, raw_log)
|
||||
} else {
|
||||
Err("unknown type of log entry".into())
|
||||
}
|
||||
.map_err(|error| {
|
||||
warn!(target: "secretstore", "{}: error parsing log entry from service contract: {}",
|
||||
self.self_key_pair.public(), error);
|
||||
error
|
||||
})
|
||||
.ok()
|
||||
}).collect::<Vec<_>>().into_iter())
|
||||
}
|
||||
|
||||
fn read_pending_requests(&self) -> Box<Iterator<Item=(bool, ServiceTask)>> {
|
||||
@ -205,81 +331,102 @@ impl ServiceContract for OnChainServiceContract {
|
||||
match data.contract_address == Default::default() {
|
||||
true => Box::new(::std::iter::empty()),
|
||||
false => get_confirmed_block_hash(&*client, REQUEST_CONFIRMATIONS_REQUIRED + 1)
|
||||
.and_then(|b| {
|
||||
let contract_address = data.contract_address;
|
||||
let do_call = |data| client.call_contract(BlockId::Hash(b), contract_address, data);
|
||||
data.contract.functions().server_key_generation_requests_count().call(&do_call)
|
||||
.map_err(|error| {
|
||||
warn!(target: "secretstore", "{}: call to server_key_generation_requests_count failed: {}",
|
||||
self.self_key_pair.public(), error);
|
||||
error
|
||||
})
|
||||
.map(|l| (b, l))
|
||||
.ok()
|
||||
.map(|b| {
|
||||
let block = BlockId::Hash(b);
|
||||
let iter = match self.mask.server_key_generation_requests {
|
||||
true => Box::new(self.create_pending_requests_iterator(client.clone(), &data.contract_address, &block,
|
||||
&ServerKeyGenerationService::read_pending_requests_count,
|
||||
&ServerKeyGenerationService::read_pending_request)) as Box<Iterator<Item=(bool, ServiceTask)>>,
|
||||
false => Box::new(::std::iter::empty()),
|
||||
};
|
||||
let iter = match self.mask.server_key_retrieval_requests {
|
||||
true => Box::new(iter.chain(self.create_pending_requests_iterator(client.clone(), &data.contract_address, &block,
|
||||
&ServerKeyRetrievalService::read_pending_requests_count,
|
||||
&ServerKeyRetrievalService::read_pending_request))),
|
||||
false => iter,
|
||||
};
|
||||
let iter = match self.mask.document_key_store_requests {
|
||||
true => Box::new(iter.chain(self.create_pending_requests_iterator(client.clone(), &data.contract_address, &block,
|
||||
&DocumentKeyStoreService::read_pending_requests_count,
|
||||
&DocumentKeyStoreService::read_pending_request))),
|
||||
false => iter,
|
||||
};
|
||||
let iter = match self.mask.document_key_shadow_retrieval_requests {
|
||||
true => Box::new(iter.chain(self.create_pending_requests_iterator(client, &data.contract_address, &block,
|
||||
&DocumentKeyShadowRetrievalService::read_pending_requests_count,
|
||||
&DocumentKeyShadowRetrievalService::read_pending_request))),
|
||||
false => iter
|
||||
};
|
||||
|
||||
iter
|
||||
})
|
||||
.map(|(b, l)| Box::new(PendingRequestsIterator {
|
||||
client: client,
|
||||
contract: service::Service::default(),
|
||||
contract_address: data.contract_address,
|
||||
self_key_pair: self.self_key_pair.clone(),
|
||||
block: b,
|
||||
index: 0.into(),
|
||||
length: l,
|
||||
}) as Box<Iterator<Item=(bool, ServiceTask)>>)
|
||||
.unwrap_or_else(|| Box::new(::std::iter::empty()))
|
||||
}
|
||||
}
|
||||
|
||||
fn publish_server_key(&self, server_key_id: &ServerKeyId, server_key: &Public) -> Result<(), String> {
|
||||
// only publish if contract address is set && client is online
|
||||
let data = self.data.read();
|
||||
if data.contract_address == Default::default() {
|
||||
// it is not an error, because key could be generated even without contract
|
||||
return Ok(());
|
||||
}
|
||||
fn publish_generated_server_key(&self, origin: &Address, server_key_id: &ServerKeyId, server_key: Public) -> Result<(), String> {
|
||||
self.send_contract_transaction(origin, server_key_id, ServerKeyGenerationService::is_response_required, |_, _, service|
|
||||
Ok(ServerKeyGenerationService::prepare_pubish_tx_data(service, server_key_id, &server_key))
|
||||
)
|
||||
}
|
||||
|
||||
let client = match self.client.get() {
|
||||
Some(client) => client,
|
||||
None => return Err("trusted client is required to publish key".into()),
|
||||
};
|
||||
fn publish_server_key_generation_error(&self, origin: &Address, server_key_id: &ServerKeyId) -> Result<(), String> {
|
||||
self.send_contract_transaction(origin, server_key_id, ServerKeyGenerationService::is_response_required, |_, _, service|
|
||||
Ok(ServerKeyGenerationService::prepare_error_tx_data(service, server_key_id))
|
||||
)
|
||||
}
|
||||
|
||||
// only publish key if contract waits for publication
|
||||
// failing is ok here - it could be that enough confirmations have been recevied
|
||||
// or key has been requested using HTTP API
|
||||
let contract_address = data.contract_address;
|
||||
let do_call = |data| client.call_contract(BlockId::Latest, contract_address, data);
|
||||
let self_address = public_to_address(self.self_key_pair.public());
|
||||
if data.contract.functions()
|
||||
.get_server_key_confirmation_status()
|
||||
.call(*server_key_id, self_address, &do_call)
|
||||
.unwrap_or(false) {
|
||||
return Ok(());
|
||||
}
|
||||
fn publish_retrieved_server_key(&self, origin: &Address, server_key_id: &ServerKeyId, server_key: Public, threshold: usize) -> Result<(), String> {
|
||||
let threshold = serialize_threshold(threshold)?;
|
||||
self.send_contract_transaction(origin, server_key_id, ServerKeyRetrievalService::is_response_required, |_, _, service|
|
||||
Ok(ServerKeyRetrievalService::prepare_pubish_tx_data(service, server_key_id, server_key, threshold))
|
||||
)
|
||||
}
|
||||
|
||||
// prepare transaction data
|
||||
let server_key_hash = keccak(server_key);
|
||||
let signed_server_key = self.self_key_pair.sign(&server_key_hash).map_err(|e| format!("{}", e))?;
|
||||
let signed_server_key: Signature = signed_server_key.into_electrum().into();
|
||||
let transaction_data = data.contract.functions()
|
||||
.server_key_generated()
|
||||
.input(*server_key_id,
|
||||
server_key.to_vec(),
|
||||
signed_server_key.v(),
|
||||
signed_server_key.r(),
|
||||
signed_server_key.s(),
|
||||
);
|
||||
fn publish_server_key_retrieval_error(&self, origin: &Address, server_key_id: &ServerKeyId) -> Result<(), String> {
|
||||
self.send_contract_transaction(origin, server_key_id, ServerKeyRetrievalService::is_response_required, |_, _, service|
|
||||
Ok(ServerKeyRetrievalService::prepare_error_tx_data(service, server_key_id))
|
||||
)
|
||||
}
|
||||
|
||||
// send transaction
|
||||
client.transact_contract(
|
||||
data.contract_address,
|
||||
transaction_data
|
||||
).map_err(|e| format!("{}", e))?;
|
||||
fn publish_stored_document_key(&self, origin: &Address, server_key_id: &ServerKeyId) -> Result<(), String> {
|
||||
self.send_contract_transaction(origin, server_key_id, DocumentKeyStoreService::is_response_required, |_, _, service|
|
||||
Ok(DocumentKeyStoreService::prepare_pubish_tx_data(service, server_key_id))
|
||||
)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
fn publish_document_key_store_error(&self, origin: &Address, server_key_id: &ServerKeyId) -> Result<(), String> {
|
||||
self.send_contract_transaction(origin, server_key_id, DocumentKeyStoreService::is_response_required, |_, _, service|
|
||||
Ok(DocumentKeyStoreService::prepare_error_tx_data(service, server_key_id))
|
||||
)
|
||||
}
|
||||
|
||||
fn publish_retrieved_document_key_common(&self, origin: &Address, server_key_id: &ServerKeyId, requester: &Address, common_point: Public, threshold: usize) -> Result<(), String> {
|
||||
let threshold = serialize_threshold(threshold)?;
|
||||
self.send_contract_transaction(origin, server_key_id, |client, contract_address, contract, server_key_id, key_server|
|
||||
DocumentKeyShadowRetrievalService::is_response_required(client, contract_address, contract, server_key_id, requester, key_server),
|
||||
|_, _, service|
|
||||
Ok(DocumentKeyShadowRetrievalService::prepare_pubish_common_tx_data(service, server_key_id, requester, common_point, threshold))
|
||||
)
|
||||
}
|
||||
|
||||
fn publish_retrieved_document_key_personal(&self, origin: &Address, server_key_id: &ServerKeyId, requester: &Address, participants: &[Address], decrypted_secret: Public, shadow: Bytes) -> Result<(), String> {
|
||||
self.send_contract_transaction(origin, server_key_id, |_, _, _, _, _| true,
|
||||
move |client, address, service|
|
||||
DocumentKeyShadowRetrievalService::prepare_pubish_personal_tx_data(client, address, service, server_key_id, requester, participants, decrypted_secret, shadow)
|
||||
)
|
||||
}
|
||||
|
||||
fn publish_document_key_retrieval_error(&self, origin: &Address, server_key_id: &ServerKeyId, requester: &Address) -> Result<(), String> {
|
||||
self.send_contract_transaction(origin, server_key_id, |client, contract_address, contract, server_key_id, key_server|
|
||||
DocumentKeyShadowRetrievalService::is_response_required(client, contract_address, contract, server_key_id, requester, key_server),
|
||||
|_, _, service|
|
||||
Ok(DocumentKeyShadowRetrievalService::prepare_error_tx_data(service, server_key_id, requester))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for PendingRequestsIterator {
|
||||
impl<F> Iterator for PendingRequestsIterator<F> where F: Fn(U256) -> Option<(bool, ServiceTask)> {
|
||||
type Item = (bool, ServiceTask);
|
||||
|
||||
fn next(&mut self) -> Option<(bool, ServiceTask)> {
|
||||
@ -290,27 +437,29 @@ impl Iterator for PendingRequestsIterator {
|
||||
let index = self.index.clone();
|
||||
self.index = self.index + 1.into();
|
||||
|
||||
let self_address = public_to_address(self.self_key_pair.public());
|
||||
let contract_address = self.contract_address;
|
||||
let do_call = |data| self.client.call_contract(BlockId::Hash(self.block.clone()), contract_address, data);
|
||||
self.contract.functions().get_server_key_id().call(index, &do_call)
|
||||
.and_then(|server_key_id|
|
||||
self.contract.functions().get_server_key_threshold().call(server_key_id, &do_call)
|
||||
.map(|threshold| (server_key_id, threshold)))
|
||||
.and_then(|(server_key_id, threshold)|
|
||||
self.contract.functions().get_server_key_confirmation_status().call(server_key_id, self_address, &do_call)
|
||||
.map(|is_confirmed| (server_key_id, threshold, is_confirmed)))
|
||||
.map(|(server_key_id, threshold, is_confirmed)|
|
||||
Some((is_confirmed, ServiceTask::GenerateServerKey(server_key_id, threshold.into()))))
|
||||
.map_err(|error| {
|
||||
warn!(target: "secretstore", "{}: reading service contract request failed: {}",
|
||||
self.self_key_pair.public(), error);
|
||||
()
|
||||
})
|
||||
.unwrap_or(None)
|
||||
(self.read_request)(index)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns vector of logs topics to listen to.
|
||||
pub fn mask_topics(mask: &ApiMask) -> Vec<H256> {
|
||||
let mut topics = Vec::new();
|
||||
if mask.server_key_generation_requests {
|
||||
topics.push(*SERVER_KEY_GENERATION_REQUESTED_EVENT_NAME_HASH);
|
||||
}
|
||||
if mask.server_key_retrieval_requests {
|
||||
topics.push(*SERVER_KEY_RETRIEVAL_REQUESTED_EVENT_NAME_HASH);
|
||||
}
|
||||
if mask.document_key_store_requests {
|
||||
topics.push(*DOCUMENT_KEY_STORE_REQUESTED_EVENT_NAME_HASH);
|
||||
}
|
||||
if mask.document_key_shadow_retrieval_requests {
|
||||
topics.push(*DOCUMENT_KEY_COMMON_PART_RETRIEVAL_REQUESTED_EVENT_NAME_HASH);
|
||||
topics.push(*DOCUMENT_KEY_PERSONAL_PART_RETRIEVAL_REQUESTED_EVENT_NAME_HASH);
|
||||
}
|
||||
topics
|
||||
}
|
||||
|
||||
/// Get hash of the last block with at least n confirmations.
|
||||
fn get_confirmed_block_hash(client: &Client, confirmations: u64) -> Option<H256> {
|
||||
client.block_number(BlockId::Latest)
|
||||
@ -318,21 +467,365 @@ fn get_confirmed_block_hash(client: &Client, confirmations: u64) -> Option<H256>
|
||||
.and_then(|b| client.block_hash(BlockId::Number(b)))
|
||||
}
|
||||
|
||||
impl ServerKeyGenerationService {
|
||||
/// Parse request log entry.
|
||||
pub fn parse_log(origin: &Address, contract: &service::Service, raw_log: RawLog) -> Result<ServiceTask, String> {
|
||||
let event = contract.events().server_key_generation_requested();
|
||||
match event.parse_log(raw_log) {
|
||||
Ok(l) => Ok(ServiceTask::GenerateServerKey(origin.clone(), l.server_key_id, l.author, parse_threshold(l.threshold)?)),
|
||||
Err(e) => Err(format!("{}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if response from key server is required.
|
||||
pub fn is_response_required(client: &Client, contract_address: &Address, contract: &service::Service, server_key_id: &ServerKeyId, key_server: &Address) -> bool {
|
||||
// we're checking confirmation in Latest block, because we're interested in latest contract state here
|
||||
let do_call = |data| client.call_contract(BlockId::Latest, *contract_address, data);
|
||||
contract.functions()
|
||||
.is_server_key_generation_response_required()
|
||||
.call(*server_key_id, key_server.clone(), &do_call)
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
/// Prepare publish key transaction data.
|
||||
pub fn prepare_pubish_tx_data(contract: &service::Service, server_key_id: &ServerKeyId, server_key_public: &Public) -> Bytes {
|
||||
contract.functions()
|
||||
.server_key_generated()
|
||||
.input(*server_key_id, server_key_public.to_vec())
|
||||
}
|
||||
|
||||
/// Prepare error transaction data.
|
||||
pub fn prepare_error_tx_data(contract: &service::Service, server_key_id: &ServerKeyId) -> Bytes {
|
||||
contract.functions()
|
||||
.server_key_generation_error()
|
||||
.input(*server_key_id)
|
||||
}
|
||||
|
||||
/// Read pending requests count.
|
||||
fn read_pending_requests_count(client: &Client, contract_address: &Address, _contract: &service::Service, block: &BlockId) -> Result<U256, String> {
|
||||
let do_call = |data| client.call_contract(block.clone(), contract_address.clone(), data);
|
||||
let contract = service::Service::default();
|
||||
contract.functions()
|
||||
.server_key_generation_requests_count()
|
||||
.call(&do_call)
|
||||
.map_err(|error| format!("{}", error))
|
||||
}
|
||||
|
||||
/// Read pending request.
|
||||
fn read_pending_request(self_key_pair: &NodeKeyPair, client: &Client, contract_address: &Address, contract: &service::Service, block: &BlockId, index: U256) -> Result<(bool, ServiceTask), String> {
|
||||
let self_address = public_to_address(self_key_pair.public());
|
||||
let do_call = |d| client.call_contract(block.clone(), contract_address.clone(), d);
|
||||
contract.functions()
|
||||
.get_server_key_generation_request()
|
||||
.call(index, &do_call)
|
||||
.map_err(|error| format!("{}", error))
|
||||
.and_then(|(server_key_id, author, threshold)| parse_threshold(threshold)
|
||||
.map(|threshold| (server_key_id, author, threshold)))
|
||||
.and_then(|(server_key_id, author, threshold)| contract.functions()
|
||||
.is_server_key_generation_response_required()
|
||||
.call(server_key_id.clone(), self_address, &do_call)
|
||||
.map(|not_confirmed| (
|
||||
not_confirmed,
|
||||
ServiceTask::GenerateServerKey(
|
||||
contract_address.clone(),
|
||||
server_key_id,
|
||||
author,
|
||||
threshold,
|
||||
)))
|
||||
.map_err(|error| format!("{}", error)))
|
||||
}
|
||||
}
|
||||
|
||||
impl ServerKeyRetrievalService {
|
||||
/// Parse request log entry.
|
||||
pub fn parse_log(origin: &Address, contract: &service::Service, raw_log: RawLog) -> Result<ServiceTask, String> {
|
||||
let event = contract.events().server_key_retrieval_requested();
|
||||
match event.parse_log(raw_log) {
|
||||
Ok(l) => Ok(ServiceTask::RetrieveServerKey(origin.clone(), l.server_key_id)),
|
||||
Err(e) => Err(format!("{}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if response from key server is required.
|
||||
pub fn is_response_required(client: &Client, contract_address: &Address, contract: &service::Service, server_key_id: &ServerKeyId, key_server: &Address) -> bool {
|
||||
// we're checking confirmation in Latest block, because we're interested in latest contract state here
|
||||
let do_call = |data| client.call_contract(BlockId::Latest, *contract_address, data);
|
||||
contract.functions()
|
||||
.is_server_key_retrieval_response_required()
|
||||
.call(*server_key_id, key_server.clone(), &do_call)
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
/// Prepare publish key transaction data.
|
||||
pub fn prepare_pubish_tx_data(contract: &service::Service, server_key_id: &ServerKeyId, server_key_public: Public, threshold: U256) -> Bytes {
|
||||
contract.functions()
|
||||
.server_key_retrieved()
|
||||
.input(*server_key_id, server_key_public.to_vec(), threshold)
|
||||
}
|
||||
|
||||
/// Prepare error transaction data.
|
||||
pub fn prepare_error_tx_data(contract: &service::Service, server_key_id: &ServerKeyId) -> Bytes {
|
||||
contract.functions()
|
||||
.server_key_retrieval_error()
|
||||
.input(*server_key_id)
|
||||
}
|
||||
|
||||
/// Read pending requests count.
|
||||
fn read_pending_requests_count(client: &Client, contract_address: &Address, _contract: &service::Service, block: &BlockId) -> Result<U256, String> {
|
||||
let do_call = |data| client.call_contract(block.clone(), contract_address.clone(), data);
|
||||
let contract = service::Service::default();
|
||||
contract.functions()
|
||||
.server_key_retrieval_requests_count()
|
||||
.call(&do_call)
|
||||
.map_err(|error| format!("{}", error))
|
||||
}
|
||||
|
||||
/// Read pending request.
|
||||
fn read_pending_request(self_key_pair: &NodeKeyPair, client: &Client, contract_address: &Address, contract: &service::Service, block: &BlockId, index: U256) -> Result<(bool, ServiceTask), String> {
|
||||
let self_address = public_to_address(self_key_pair.public());
|
||||
let do_call = |d| client.call_contract(block.clone(), contract_address.clone(), d);
|
||||
contract.functions()
|
||||
.get_server_key_retrieval_request()
|
||||
.call(index, &do_call)
|
||||
.map_err(|error| format!("{}", error))
|
||||
.and_then(|server_key_id| contract.functions()
|
||||
.is_server_key_retrieval_response_required()
|
||||
.call(server_key_id.clone(), self_address, &do_call)
|
||||
.map(|not_confirmed| (
|
||||
not_confirmed,
|
||||
ServiceTask::RetrieveServerKey(
|
||||
contract_address.clone(),
|
||||
server_key_id,
|
||||
)))
|
||||
.map_err(|error| format!("{}", error)))
|
||||
}
|
||||
}
|
||||
|
||||
impl DocumentKeyStoreService {
|
||||
/// Parse request log entry.
|
||||
pub fn parse_log(origin: &Address, contract: &service::Service, raw_log: RawLog) -> Result<ServiceTask, String> {
|
||||
let event = contract.events().document_key_store_requested();
|
||||
match event.parse_log(raw_log) {
|
||||
Ok(l) => Ok(ServiceTask::StoreDocumentKey(origin.clone(), l.server_key_id, l.author, (*l.common_point).into(), (*l.encrypted_point).into())),
|
||||
Err(e) => Err(format!("{}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if response from key server is required.
|
||||
pub fn is_response_required(client: &Client, contract_address: &Address, contract: &service::Service, server_key_id: &ServerKeyId, key_server: &Address) -> bool {
|
||||
// we're checking confirmation in Latest block, because we're interested in latest contract state here
|
||||
let do_call = |data| client.call_contract(BlockId::Latest, *contract_address, data);
|
||||
contract.functions()
|
||||
.is_document_key_store_response_required()
|
||||
.call(*server_key_id, key_server.clone(), &do_call)
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
/// Prepare publish key transaction data.
|
||||
pub fn prepare_pubish_tx_data(contract: &service::Service, server_key_id: &ServerKeyId) -> Bytes {
|
||||
contract.functions()
|
||||
.document_key_stored()
|
||||
.input(*server_key_id)
|
||||
}
|
||||
|
||||
/// Prepare error transaction data.
|
||||
pub fn prepare_error_tx_data(contract: &service::Service, server_key_id: &ServerKeyId) -> Bytes {
|
||||
contract.functions()
|
||||
.document_key_store_error()
|
||||
.input(*server_key_id)
|
||||
}
|
||||
|
||||
/// Read pending requests count.
|
||||
fn read_pending_requests_count(client: &Client, contract_address: &Address, _contract: &service::Service, block: &BlockId) -> Result<U256, String> {
|
||||
let do_call = |data| client.call_contract(block.clone(), contract_address.clone(), data);
|
||||
let contract = service::Service::default();
|
||||
contract.functions()
|
||||
.document_key_store_requests_count()
|
||||
.call(&do_call)
|
||||
.map_err(|error| format!("{}", error))
|
||||
}
|
||||
|
||||
/// Read pending request.
|
||||
fn read_pending_request(self_key_pair: &NodeKeyPair, client: &Client, contract_address: &Address, contract: &service::Service, block: &BlockId, index: U256) -> Result<(bool, ServiceTask), String> {
|
||||
let self_address = public_to_address(self_key_pair.public());
|
||||
let do_call = |d| client.call_contract(block.clone(), contract_address.clone(), d);
|
||||
contract.functions()
|
||||
.get_document_key_store_request()
|
||||
.call(index, &do_call)
|
||||
.map_err(|error| format!("{}", error))
|
||||
.and_then(|(server_key_id, author, common_point, encrypted_point)| contract.functions()
|
||||
.is_document_key_store_response_required()
|
||||
.call(server_key_id.clone(), self_address, &do_call)
|
||||
.map(|not_confirmed| (
|
||||
not_confirmed,
|
||||
ServiceTask::StoreDocumentKey(
|
||||
contract_address.clone(),
|
||||
server_key_id,
|
||||
author,
|
||||
Public::from_slice(&common_point),
|
||||
Public::from_slice(&encrypted_point),
|
||||
)))
|
||||
.map_err(|error| format!("{}", error)))
|
||||
}
|
||||
}
|
||||
|
||||
impl DocumentKeyShadowRetrievalService {
|
||||
/// Parse common request log entry.
|
||||
pub fn parse_common_request_log(origin: &Address, contract: &service::Service, raw_log: RawLog) -> Result<ServiceTask, String> {
|
||||
let event = contract.events().document_key_common_retrieval_requested();
|
||||
match event.parse_log(raw_log) {
|
||||
Ok(l) => Ok(ServiceTask::RetrieveShadowDocumentKeyCommon(origin.clone(), l.server_key_id, l.requester)),
|
||||
Err(e) => Err(format!("{}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse personal request log entry.
|
||||
pub fn parse_personal_request_log(origin: &Address, contract: &service::Service, raw_log: RawLog) -> Result<ServiceTask, String> {
|
||||
let event = contract.events().document_key_personal_retrieval_requested();
|
||||
match event.parse_log(raw_log) {
|
||||
Ok(l) => Ok(ServiceTask::RetrieveShadowDocumentKeyPersonal(origin.clone(), l.server_key_id, (*l.requester_public).into())),
|
||||
Err(e) => Err(format!("{}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if response from key server is required.
|
||||
pub fn is_response_required(client: &Client, contract_address: &Address, contract: &service::Service, server_key_id: &ServerKeyId, requester: &Address, key_server: &Address) -> bool {
|
||||
// we're checking confirmation in Latest block, because we're interested in latest contract state here
|
||||
let do_call = |data| client.call_contract(BlockId::Latest, *contract_address, data);
|
||||
contract.functions()
|
||||
.is_document_key_shadow_retrieval_response_required()
|
||||
.call(*server_key_id, *requester, key_server.clone(), &do_call)
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
/// Prepare publish common key transaction data.
|
||||
pub fn prepare_pubish_common_tx_data(contract: &service::Service, server_key_id: &ServerKeyId, requester: &Address, common_point: Public, threshold: U256) -> Bytes {
|
||||
contract.functions()
|
||||
.document_key_common_retrieved()
|
||||
.input(*server_key_id, *requester, common_point.to_vec(), threshold)
|
||||
}
|
||||
|
||||
/// Prepare publish personal key transaction data.
|
||||
pub fn prepare_pubish_personal_tx_data(client: &Client, contract_address: &Address, contract: &service::Service, server_key_id: &ServerKeyId, requester: &Address, participants: &[Address], decrypted_secret: Public, shadow: Bytes) -> Result<Bytes, String> {
|
||||
let mut participants_mask = U256::default();
|
||||
for participant in participants {
|
||||
let participant_index = Self::map_key_server_address(client, contract_address, contract, participant.clone())
|
||||
.map_err(|e| format!("Error searching for {} participant: {}", participant, e))?;
|
||||
participants_mask = participants_mask | (U256::one() << participant_index.into());
|
||||
}
|
||||
Ok(contract.functions()
|
||||
.document_key_personal_retrieved()
|
||||
.input(*server_key_id, *requester, participants_mask, decrypted_secret.to_vec(), shadow))
|
||||
}
|
||||
|
||||
/// Prepare error transaction data.
|
||||
pub fn prepare_error_tx_data(contract: &service::Service, server_key_id: &ServerKeyId, requester: &Address) -> Bytes {
|
||||
contract.functions()
|
||||
.document_key_shadow_retrieval_error()
|
||||
.input(*server_key_id, *requester)
|
||||
}
|
||||
|
||||
/// Read pending requests count.
|
||||
fn read_pending_requests_count(client: &Client, contract_address: &Address, _contract: &service::Service, block: &BlockId) -> Result<U256, String> {
|
||||
let do_call = |data| client.call_contract(block.clone(), contract_address.clone(), data);
|
||||
let contract = service::Service::default();
|
||||
contract.functions()
|
||||
.document_key_shadow_retrieval_requests_count()
|
||||
.call(&do_call)
|
||||
.map_err(|error| format!("{}", error))
|
||||
}
|
||||
|
||||
/// Read pending request.
|
||||
fn read_pending_request(self_key_pair: &NodeKeyPair, client: &Client, contract_address: &Address, contract: &service::Service, block: &BlockId, index: U256) -> Result<(bool, ServiceTask), String> {
|
||||
let self_address = public_to_address(self_key_pair.public());
|
||||
let do_call = |d| client.call_contract(block.clone(), contract_address.clone(), d);
|
||||
contract.functions()
|
||||
.get_document_key_shadow_retrieval_request()
|
||||
.call(index, &do_call)
|
||||
.map_err(|error| format!("{}", error))
|
||||
.and_then(|(server_key_id, requester, is_common_retrieval_completed)| {
|
||||
let requester = Public::from_slice(&requester);
|
||||
contract.functions()
|
||||
.is_document_key_shadow_retrieval_response_required()
|
||||
.call(server_key_id.clone(), public_to_address(&requester), self_address, &do_call)
|
||||
.map(|not_confirmed| (
|
||||
not_confirmed,
|
||||
match is_common_retrieval_completed {
|
||||
true => ServiceTask::RetrieveShadowDocumentKeyCommon(
|
||||
contract_address.clone(),
|
||||
server_key_id,
|
||||
public_to_address(&requester),
|
||||
),
|
||||
false => ServiceTask::RetrieveShadowDocumentKeyPersonal(
|
||||
contract_address.clone(),
|
||||
server_key_id,
|
||||
requester,
|
||||
)
|
||||
},
|
||||
))
|
||||
.map_err(|error| format!("{}", error))
|
||||
})
|
||||
}
|
||||
|
||||
/// Map from key server address to key server index.
|
||||
fn map_key_server_address(client: &Client, contract_address: &Address, contract: &service::Service, key_server: Address) -> Result<u8, String> {
|
||||
// we're checking confirmation in Latest block, because tx ,ust be appended to the latest state
|
||||
let do_call = |data| client.call_contract(BlockId::Latest, *contract_address, data);
|
||||
contract.functions()
|
||||
.require_key_server()
|
||||
.call(key_server, &do_call)
|
||||
.map_err(|e| format!("{}", e))
|
||||
.and_then(|index| if index > ::std::u8::MAX.into() {
|
||||
Err(format!("key server index is too big: {}", index))
|
||||
} else {
|
||||
let index: u32 = index.into();
|
||||
Ok(index as u8)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse threshold (we only supposrt 256 KS at max).
|
||||
fn parse_threshold(threshold: U256) -> Result<usize, String> {
|
||||
let threshold_num = threshold.low_u64();
|
||||
if threshold != threshold_num.into() || threshold_num >= ::std::u8::MAX as u64 {
|
||||
return Err(format!("invalid threshold to use in service contract: {}", threshold));
|
||||
}
|
||||
|
||||
Ok(threshold_num as usize)
|
||||
}
|
||||
|
||||
/// Serialize threshold (we only support 256 KS at max).
|
||||
fn serialize_threshold(threshold: usize) -> Result<U256, String> {
|
||||
if threshold > ::std::u8::MAX as usize {
|
||||
return Err(format!("invalid threshold to use in service contract: {}", threshold));
|
||||
}
|
||||
Ok(threshold.into())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use parking_lot::Mutex;
|
||||
use bytes::Bytes;
|
||||
use ethkey::Public;
|
||||
use ethereum_types::H256;
|
||||
use ethereum_types::Address;
|
||||
use listener::service_contract_listener::ServiceTask;
|
||||
use ServerKeyId;
|
||||
use {ServerKeyId};
|
||||
use super::ServiceContract;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DummyServiceContract {
|
||||
pub is_actual: bool,
|
||||
pub logs: Vec<Vec<H256>>,
|
||||
pub logs: Vec<ServiceTask>,
|
||||
pub pending_requests: Vec<(bool, ServiceTask)>,
|
||||
pub published_keys: Mutex<Vec<(ServerKeyId, Public)>>,
|
||||
pub generated_server_keys: Mutex<Vec<(ServerKeyId, Public)>>,
|
||||
pub server_keys_generation_failures: Mutex<Vec<ServerKeyId>>,
|
||||
pub retrieved_server_keys: Mutex<Vec<(ServerKeyId, Public, usize)>>,
|
||||
pub server_keys_retrieval_failures: Mutex<Vec<ServerKeyId>>,
|
||||
pub stored_document_keys: Mutex<Vec<ServerKeyId>>,
|
||||
pub document_keys_store_failures: Mutex<Vec<ServerKeyId>>,
|
||||
pub common_shadow_retrieved_document_keys: Mutex<Vec<(ServerKeyId, Address, Public, usize)>>,
|
||||
pub personal_shadow_retrieved_document_keys: Mutex<Vec<(ServerKeyId, Address, Vec<Address>, Public, Bytes)>>,
|
||||
pub document_keys_shadow_retrieval_failures: Mutex<Vec<(ServerKeyId, Address)>>,
|
||||
}
|
||||
|
||||
impl ServiceContract for DummyServiceContract {
|
||||
@ -340,7 +833,7 @@ pub mod tests {
|
||||
true
|
||||
}
|
||||
|
||||
fn read_logs(&self) -> Box<Iterator<Item=Vec<H256>>> {
|
||||
fn read_logs(&self) -> Box<Iterator<Item=ServiceTask>> {
|
||||
Box::new(self.logs.clone().into_iter())
|
||||
}
|
||||
|
||||
@ -348,8 +841,48 @@ pub mod tests {
|
||||
Box::new(self.pending_requests.clone().into_iter())
|
||||
}
|
||||
|
||||
fn publish_server_key(&self, server_key_id: &ServerKeyId, server_key: &Public) -> Result<(), String> {
|
||||
self.published_keys.lock().push((server_key_id.clone(), server_key.clone()));
|
||||
fn publish_generated_server_key(&self, _origin: &Address, server_key_id: &ServerKeyId, server_key: Public) -> Result<(), String> {
|
||||
self.generated_server_keys.lock().push((server_key_id.clone(), server_key.clone()));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn publish_server_key_generation_error(&self, _origin: &Address, server_key_id: &ServerKeyId) -> Result<(), String> {
|
||||
self.server_keys_generation_failures.lock().push(server_key_id.clone());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn publish_retrieved_server_key(&self, _origin: &Address, server_key_id: &ServerKeyId, server_key: Public, threshold: usize) -> Result<(), String> {
|
||||
self.retrieved_server_keys.lock().push((server_key_id.clone(), server_key.clone(), threshold));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn publish_server_key_retrieval_error(&self, _origin: &Address, server_key_id: &ServerKeyId) -> Result<(), String> {
|
||||
self.server_keys_retrieval_failures.lock().push(server_key_id.clone());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn publish_stored_document_key(&self, _origin: &Address, server_key_id: &ServerKeyId) -> Result<(), String> {
|
||||
self.stored_document_keys.lock().push(server_key_id.clone());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn publish_document_key_store_error(&self, _origin: &Address, server_key_id: &ServerKeyId) -> Result<(), String> {
|
||||
self.document_keys_store_failures.lock().push(server_key_id.clone());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn publish_retrieved_document_key_common(&self, _origin: &Address, server_key_id: &ServerKeyId, requester: &Address, common_point: Public, threshold: usize) -> Result<(), String> {
|
||||
self.common_shadow_retrieved_document_keys.lock().push((server_key_id.clone(), requester.clone(), common_point.clone(), threshold));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn publish_retrieved_document_key_personal(&self, _origin: &Address, server_key_id: &ServerKeyId, requester: &Address, participants: &[Address], decrypted_secret: Public, shadow: Bytes) -> Result<(), String> {
|
||||
self.personal_shadow_retrieved_document_keys.lock().push((server_key_id.clone(), requester.clone(), participants.iter().cloned().collect(), decrypted_secret, shadow));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn publish_document_key_retrieval_error(&self, _origin: &Address, server_key_id: &ServerKeyId, requester: &Address) -> Result<(), String> {
|
||||
self.document_keys_shadow_retrieval_failures.lock().push((server_key_id.clone(), requester.clone()));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
100
secret_store/src/listener/service_contract_aggregate.rs
Normal file
100
secret_store/src/listener/service_contract_aggregate.rs
Normal file
@ -0,0 +1,100 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::sync::Arc;
|
||||
use bytes::Bytes;
|
||||
use ethereum_types::Address;
|
||||
use ethkey::Public;
|
||||
use listener::service_contract::ServiceContract;
|
||||
use listener::service_contract_listener::ServiceTask;
|
||||
use {ServerKeyId};
|
||||
|
||||
/// Aggregated on-chain service contract.
|
||||
pub struct OnChainServiceContractAggregate {
|
||||
/// All hosted service contracts.
|
||||
contracts: Vec<Arc<ServiceContract>>,
|
||||
}
|
||||
|
||||
impl OnChainServiceContractAggregate {
|
||||
/// Create new aggregated service contract listener.
|
||||
pub fn new(contracts: Vec<Arc<ServiceContract>>) -> Self {
|
||||
debug_assert!(contracts.len() > 1);
|
||||
OnChainServiceContractAggregate {
|
||||
contracts: contracts,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ServiceContract for OnChainServiceContractAggregate {
|
||||
fn update(&self) -> bool {
|
||||
let mut result = false;
|
||||
for contract in &self.contracts {
|
||||
result = contract.update() || result;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn read_logs(&self) -> Box<Iterator<Item=ServiceTask>> {
|
||||
self.contracts.iter()
|
||||
.fold(Box::new(::std::iter::empty()) as Box<Iterator<Item=ServiceTask>>, |i, c|
|
||||
Box::new(i.chain(c.read_logs())))
|
||||
}
|
||||
|
||||
fn read_pending_requests(&self) -> Box<Iterator<Item=(bool, ServiceTask)>> {
|
||||
self.contracts.iter()
|
||||
.fold(Box::new(::std::iter::empty()) as Box<Iterator<Item=(bool, ServiceTask)>>, |i, c|
|
||||
Box::new(i.chain(c.read_pending_requests())))
|
||||
}
|
||||
|
||||
// in current implementation all publish methods are independent of actual contract adddress
|
||||
// (tx is sent to origin) => we do not care which contract to use for publish data in methods below
|
||||
|
||||
fn publish_generated_server_key(&self, origin: &Address, server_key_id: &ServerKeyId, server_key: Public) -> Result<(), String> {
|
||||
self.contracts[0].publish_generated_server_key(origin, server_key_id, server_key)
|
||||
}
|
||||
|
||||
fn publish_server_key_generation_error(&self, origin: &Address, server_key_id: &ServerKeyId) -> Result<(), String> {
|
||||
self.contracts[0].publish_server_key_generation_error(origin, server_key_id)
|
||||
}
|
||||
|
||||
fn publish_retrieved_server_key(&self, origin: &Address, server_key_id: &ServerKeyId, server_key: Public, threshold: usize) -> Result<(), String> {
|
||||
self.contracts[0].publish_retrieved_server_key(origin, server_key_id, server_key, threshold)
|
||||
}
|
||||
|
||||
fn publish_server_key_retrieval_error(&self, origin: &Address, server_key_id: &ServerKeyId) -> Result<(), String> {
|
||||
self.contracts[0].publish_server_key_retrieval_error(origin, server_key_id)
|
||||
}
|
||||
|
||||
fn publish_stored_document_key(&self, origin: &Address, server_key_id: &ServerKeyId) -> Result<(), String> {
|
||||
self.contracts[0].publish_stored_document_key(origin, server_key_id)
|
||||
}
|
||||
|
||||
fn publish_document_key_store_error(&self, origin: &Address, server_key_id: &ServerKeyId) -> Result<(), String> {
|
||||
self.contracts[0].publish_document_key_store_error(origin, server_key_id)
|
||||
}
|
||||
|
||||
fn publish_retrieved_document_key_common(&self, origin: &Address, server_key_id: &ServerKeyId, requester: &Address, common_point: Public, threshold: usize) -> Result<(), String> {
|
||||
self.contracts[0].publish_retrieved_document_key_common(origin, server_key_id, requester, common_point, threshold)
|
||||
}
|
||||
|
||||
fn publish_retrieved_document_key_personal(&self, origin: &Address, server_key_id: &ServerKeyId, requester: &Address, participants: &[Address], decrypted_secret: Public, shadow: Bytes) -> Result<(), String> {
|
||||
self.contracts[0].publish_retrieved_document_key_personal(origin, server_key_id, requester, participants, decrypted_secret, shadow)
|
||||
}
|
||||
|
||||
fn publish_document_key_retrieval_error(&self, origin: &Address, server_key_id: &ServerKeyId, requester: &Address) -> Result<(), String> {
|
||||
self.contracts[0].publish_document_key_retrieval_error(origin, server_key_id, requester)
|
||||
}
|
||||
}
|
@ -20,16 +20,20 @@ use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::thread;
|
||||
use parking_lot::Mutex;
|
||||
use ethcore::client::ChainNotify;
|
||||
use ethkey::{Random, Generator, Public, sign};
|
||||
use ethkey::{Public, public_to_address};
|
||||
use bytes::Bytes;
|
||||
use ethereum_types::{H256, U256};
|
||||
use ethereum_types::{H256, U256, Address};
|
||||
use key_server_set::KeyServerSet;
|
||||
use key_server_cluster::{ClusterClient, ClusterSessionsListener, ClusterSession};
|
||||
use key_server_cluster::math;
|
||||
use key_server_cluster::generation_session::SessionImpl as GenerationSession;
|
||||
use key_server_cluster::encryption_session::{check_encrypted_data, update_encrypted_data};
|
||||
use key_server_cluster::decryption_session::SessionImpl as DecryptionSession;
|
||||
use key_storage::KeyStorage;
|
||||
use acl_storage::AclStorage;
|
||||
use listener::service_contract::ServiceContract;
|
||||
use listener::tasks_queue::TasksQueue;
|
||||
use {ServerKeyId, NodeKeyPair, KeyServer};
|
||||
use {ServerKeyId, NodeKeyPair, Error};
|
||||
|
||||
/// Retry interval (in blocks). Every RETRY_INTERVAL_BLOCKS blocks each KeyServer reads pending requests from
|
||||
/// service contract && tries to re-execute. The reason to have this mechanism is primarily because keys
|
||||
@ -56,12 +60,12 @@ pub struct ServiceContractListener {
|
||||
pub struct ServiceContractListenerParams {
|
||||
/// Service contract.
|
||||
pub contract: Arc<ServiceContract>,
|
||||
/// Key server reference.
|
||||
pub key_server: Arc<KeyServer>,
|
||||
/// This node key pair.
|
||||
pub self_key_pair: Arc<NodeKeyPair>,
|
||||
/// Key servers set.
|
||||
pub key_server_set: Arc<KeyServerSet>,
|
||||
/// ACL storage reference.
|
||||
pub acl_storage: Arc<AclStorage>,
|
||||
/// Cluster reference.
|
||||
pub cluster: Arc<ClusterClient>,
|
||||
/// Key storage reference.
|
||||
@ -78,8 +82,10 @@ struct ServiceContractListenerData {
|
||||
pub tasks_queue: Arc<TasksQueue<ServiceTask>>,
|
||||
/// Service contract.
|
||||
pub contract: Arc<ServiceContract>,
|
||||
/// Key server reference.
|
||||
pub key_server: Arc<KeyServer>,
|
||||
/// ACL storage reference.
|
||||
pub acl_storage: Arc<AclStorage>,
|
||||
/// Cluster client reference.
|
||||
pub cluster: Arc<ClusterClient>,
|
||||
/// This node key pair.
|
||||
pub self_key_pair: Arc<NodeKeyPair>,
|
||||
/// Key servers set.
|
||||
@ -92,8 +98,10 @@ struct ServiceContractListenerData {
|
||||
/// Retry-related data.
|
||||
#[derive(Default)]
|
||||
struct ServiceContractRetryData {
|
||||
/// Server keys, which we have generated (or tried to generate) since last retry moment.
|
||||
pub generated_keys: HashSet<ServerKeyId>,
|
||||
/// Server keys, which we have 'touched' since last retry.
|
||||
pub affected_server_keys: HashSet<ServerKeyId>,
|
||||
/// Document keys + requesters, which we have 'touched' since last retry.
|
||||
pub affected_document_keys: HashSet<(ServerKeyId, Address)>,
|
||||
}
|
||||
|
||||
/// Service task.
|
||||
@ -101,23 +109,30 @@ struct ServiceContractRetryData {
|
||||
pub enum ServiceTask {
|
||||
/// Retry all 'stalled' tasks.
|
||||
Retry,
|
||||
/// Generate server key (server_key_id, threshold).
|
||||
GenerateServerKey(H256, H256),
|
||||
/// Confirm server key (server_key_id).
|
||||
RestoreServerKey(H256),
|
||||
/// Generate server key (origin, server_key_id, author, threshold).
|
||||
GenerateServerKey(Address, ServerKeyId, Address, usize),
|
||||
/// Retrieve server key (origin, server_key_id).
|
||||
RetrieveServerKey(Address, ServerKeyId),
|
||||
/// Store document key (origin, server_key_id, author, common_point, encrypted_point).
|
||||
StoreDocumentKey(Address, ServerKeyId, Address, Public, Public),
|
||||
/// Retrieve common data of document key (origin, server_key_id, requester).
|
||||
RetrieveShadowDocumentKeyCommon(Address, ServerKeyId, Address),
|
||||
/// Retrieve personal data of document key (origin, server_key_id, requester).
|
||||
RetrieveShadowDocumentKeyPersonal(Address, ServerKeyId, Public),
|
||||
/// Shutdown listener.
|
||||
Shutdown,
|
||||
}
|
||||
|
||||
impl ServiceContractListener {
|
||||
/// Create new service contract listener.
|
||||
pub fn new(params: ServiceContractListenerParams) -> Arc<ServiceContractListener> {
|
||||
pub fn new(params: ServiceContractListenerParams) -> Result<Arc<ServiceContractListener>, Error> {
|
||||
let data = Arc::new(ServiceContractListenerData {
|
||||
last_retry: AtomicUsize::new(0),
|
||||
retry_data: Default::default(),
|
||||
tasks_queue: Arc::new(TasksQueue::new()),
|
||||
contract: params.contract,
|
||||
key_server: params.key_server,
|
||||
acl_storage: params.acl_storage,
|
||||
cluster: params.cluster,
|
||||
self_key_pair: params.self_key_pair,
|
||||
key_server_set: params.key_server_set,
|
||||
key_storage: params.key_storage,
|
||||
@ -129,39 +144,55 @@ impl ServiceContractListener {
|
||||
None
|
||||
} else {
|
||||
let service_thread_data = data.clone();
|
||||
Some(thread::spawn(move || Self::run_service_thread(service_thread_data)))
|
||||
Some(thread::Builder::new().name("ServiceContractListener".into()).spawn(move ||
|
||||
Self::run_service_thread(service_thread_data)).map_err(|e| Error::Internal(format!("{}", e)))?)
|
||||
};
|
||||
let contract = Arc::new(ServiceContractListener {
|
||||
data: data,
|
||||
service_handle: service_handle,
|
||||
});
|
||||
params.cluster.add_generation_listener(contract.clone());
|
||||
contract
|
||||
contract.data.cluster.add_generation_listener(contract.clone());
|
||||
contract.data.cluster.add_decryption_listener(contract.clone());
|
||||
Ok(contract)
|
||||
}
|
||||
|
||||
/// Process incoming events of service contract.
|
||||
fn process_service_contract_events(&self) {
|
||||
self.data.tasks_queue.push_many(self.data.contract.read_logs()
|
||||
.filter_map(|topics| match topics.len() {
|
||||
// when key is already generated && we have this key
|
||||
3 if self.data.key_storage.get(&topics[1]).map(|k| k.is_some()).unwrap_or_default() => {
|
||||
Some(ServiceTask::RestoreServerKey(
|
||||
topics[1],
|
||||
))
|
||||
}
|
||||
// when key is not yet generated && this node should be master of this key generation session
|
||||
3 if is_processed_by_this_key_server(&*self.data.key_server_set, &*self.data.self_key_pair, &topics[1]) => {
|
||||
Some(ServiceTask::GenerateServerKey(
|
||||
topics[1],
|
||||
topics[2],
|
||||
))
|
||||
},
|
||||
3 => None,
|
||||
l @ _ => {
|
||||
warn!(target: "secretstore", "Ignoring ServerKeyRequested event with wrong number of params {}", l);
|
||||
None
|
||||
},
|
||||
}));
|
||||
.filter_map(|task| Self::filter_task(&self.data, task)));
|
||||
}
|
||||
|
||||
/// Filter service task. Only returns Some if task must be executed by this server.
|
||||
fn filter_task(data: &Arc<ServiceContractListenerData>, task: ServiceTask) -> Option<ServiceTask> {
|
||||
match task {
|
||||
// when this node should be master of this server key generation session
|
||||
ServiceTask::GenerateServerKey(origin, server_key_id, author, threshold) if is_processed_by_this_key_server(
|
||||
&*data.key_server_set, &*data.self_key_pair, &server_key_id) =>
|
||||
Some(ServiceTask::GenerateServerKey(origin, server_key_id, author, threshold)),
|
||||
// when server key is not yet generated and generation must be initiated by other node
|
||||
ServiceTask::GenerateServerKey(_, _, _, _) => None,
|
||||
|
||||
// when server key retrieval is requested
|
||||
ServiceTask::RetrieveServerKey(origin, server_key_id) =>
|
||||
Some(ServiceTask::RetrieveServerKey(origin, server_key_id)),
|
||||
|
||||
// when document key store is requested
|
||||
ServiceTask::StoreDocumentKey(origin, server_key_id, author, common_point, encrypted_point) =>
|
||||
Some(ServiceTask::StoreDocumentKey(origin, server_key_id, author, common_point, encrypted_point)),
|
||||
|
||||
// when common document key data retrieval is requested
|
||||
ServiceTask::RetrieveShadowDocumentKeyCommon(origin, server_key_id, requester) =>
|
||||
Some(ServiceTask::RetrieveShadowDocumentKeyCommon(origin, server_key_id, requester)),
|
||||
|
||||
// when this node should be master of this document key decryption session
|
||||
ServiceTask::RetrieveShadowDocumentKeyPersonal(origin, server_key_id, requester) if is_processed_by_this_key_server(
|
||||
&*data.key_server_set, &*data.self_key_pair, &server_key_id) =>
|
||||
Some(ServiceTask::RetrieveShadowDocumentKeyPersonal(origin, server_key_id, requester)),
|
||||
// when server key is not yet generated and generation must be initiated by other node
|
||||
ServiceTask::RetrieveShadowDocumentKeyPersonal(_, _, _) => None,
|
||||
|
||||
ServiceTask::Retry | ServiceTask::Shutdown => unreachable!("must be filtered outside"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Service thread procedure.
|
||||
@ -172,18 +203,45 @@ impl ServiceContractListener {
|
||||
|
||||
match task {
|
||||
ServiceTask::Shutdown => break,
|
||||
task @ _ => {
|
||||
// the only possible reaction to an error is a trace && it is already happened
|
||||
task => {
|
||||
// the only possible reaction to an error is a tx+trace && it is already happened
|
||||
let _ = Self::process_service_task(&data, task);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
trace!(target: "secretstore_net", "{}: ServiceContractListener thread stopped", data.self_key_pair.public());
|
||||
}
|
||||
|
||||
/// Process single service task.
|
||||
fn process_service_task(data: &Arc<ServiceContractListenerData>, task: ServiceTask) -> Result<(), String> {
|
||||
match task {
|
||||
ServiceTask::Retry =>
|
||||
match &task {
|
||||
&ServiceTask::GenerateServerKey(origin, server_key_id, author, threshold) => {
|
||||
data.retry_data.lock().affected_server_keys.insert(server_key_id.clone());
|
||||
log_service_task_result(&task, data.self_key_pair.public(),
|
||||
Self::generate_server_key(&data, origin, &server_key_id, author, threshold))
|
||||
},
|
||||
&ServiceTask::RetrieveServerKey(origin, server_key_id) => {
|
||||
data.retry_data.lock().affected_server_keys.insert(server_key_id.clone());
|
||||
log_service_task_result(&task, data.self_key_pair.public(),
|
||||
Self::retrieve_server_key(&data, origin, &server_key_id))
|
||||
},
|
||||
&ServiceTask::StoreDocumentKey(origin, server_key_id, author, common_point, encrypted_point) => {
|
||||
data.retry_data.lock().affected_document_keys.insert((server_key_id.clone(), author.clone()));
|
||||
log_service_task_result(&task, data.self_key_pair.public(),
|
||||
Self::store_document_key(&data, origin, &server_key_id, &author, &common_point, &encrypted_point))
|
||||
},
|
||||
&ServiceTask::RetrieveShadowDocumentKeyCommon(origin, server_key_id, requester) => {
|
||||
data.retry_data.lock().affected_document_keys.insert((server_key_id.clone(), requester.clone()));
|
||||
log_service_task_result(&task, data.self_key_pair.public(),
|
||||
Self::retrieve_document_key_common(&data, origin, &server_key_id, &requester))
|
||||
},
|
||||
&ServiceTask::RetrieveShadowDocumentKeyPersonal(origin, server_key_id, requester) => {
|
||||
data.retry_data.lock().affected_server_keys.insert(server_key_id.clone());
|
||||
log_service_task_result(&task, data.self_key_pair.public(),
|
||||
Self::retrieve_document_key_personal(&data, origin, &server_key_id, requester))
|
||||
},
|
||||
&ServiceTask::Retry => {
|
||||
Self::retry_pending_requests(&data)
|
||||
.map(|processed_requests| {
|
||||
if processed_requests != 0 {
|
||||
@ -196,38 +254,9 @@ impl ServiceContractListener {
|
||||
warn!(target: "secretstore", "{}: retrying pending requests has failed with: {}",
|
||||
data.self_key_pair.public(), error);
|
||||
error
|
||||
}),
|
||||
ServiceTask::RestoreServerKey(server_key_id) => {
|
||||
data.retry_data.lock().generated_keys.insert(server_key_id.clone());
|
||||
Self::restore_server_key(&data, &server_key_id)
|
||||
.and_then(|server_key| Self::publish_server_key(&data, &server_key_id, &server_key))
|
||||
.map(|_| {
|
||||
trace!(target: "secretstore", "{}: processed RestoreServerKey({}) request",
|
||||
data.self_key_pair.public(), server_key_id);
|
||||
()
|
||||
})
|
||||
.map_err(|error| {
|
||||
warn!(target: "secretstore", "{}: failed to process RestoreServerKey({}) request with: {}",
|
||||
data.self_key_pair.public(), server_key_id, error);
|
||||
error
|
||||
})
|
||||
},
|
||||
ServiceTask::GenerateServerKey(server_key_id, threshold) => {
|
||||
data.retry_data.lock().generated_keys.insert(server_key_id.clone());
|
||||
Self::generate_server_key(&data, &server_key_id, &threshold)
|
||||
.and_then(|server_key| Self::publish_server_key(&data, &server_key_id, &server_key))
|
||||
.map(|_| {
|
||||
trace!(target: "secretstore", "{}: processed GenerateServerKey({}, {}) request",
|
||||
data.self_key_pair.public(), server_key_id, threshold);
|
||||
()
|
||||
})
|
||||
.map_err(|error| {
|
||||
warn!(target: "secretstore", "{}: failed to process GenerateServerKey({}, {}) request with: {}",
|
||||
data.self_key_pair.public(), server_key_id, threshold, error);
|
||||
error
|
||||
})
|
||||
},
|
||||
ServiceTask::Shutdown => unreachable!("it must be checked outside"),
|
||||
&ServiceTask::Shutdown => unreachable!("must be filtered outside"),
|
||||
}
|
||||
}
|
||||
|
||||
@ -236,32 +265,28 @@ impl ServiceContractListener {
|
||||
let mut failed_requests = 0;
|
||||
let mut processed_requests = 0;
|
||||
let retry_data = ::std::mem::replace(&mut *data.retry_data.lock(), Default::default());
|
||||
for (is_confirmed, task) in data.contract.read_pending_requests() {
|
||||
let pending_tasks = data.contract.read_pending_requests()
|
||||
.filter_map(|(is_confirmed, task)| Self::filter_task(data, task)
|
||||
.map(|t| (is_confirmed, t)));
|
||||
for (is_confirmed, task) in pending_tasks {
|
||||
// only process requests, which we haven't confirmed yet
|
||||
if is_confirmed {
|
||||
continue;
|
||||
}
|
||||
|
||||
let request_result = match task {
|
||||
ServiceTask::GenerateServerKey(server_key_id, threshold) => {
|
||||
// only process request, which haven't been processed recently
|
||||
// there could be a lag when we've just generated server key && retrying on the same block
|
||||
// (or before our tx is mined) - state is not updated yet
|
||||
if retry_data.generated_keys.contains(&server_key_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// process request
|
||||
let is_own_request = is_processed_by_this_key_server(&*data.key_server_set, &*data.self_key_pair, &server_key_id);
|
||||
Self::process_service_task(data, match is_own_request {
|
||||
true => ServiceTask::GenerateServerKey(server_key_id, threshold.into()),
|
||||
false => ServiceTask::RestoreServerKey(server_key_id),
|
||||
})
|
||||
},
|
||||
_ => Err("not supported".into()),
|
||||
};
|
||||
match task {
|
||||
ServiceTask::GenerateServerKey(_, ref key, _, _) | ServiceTask::RetrieveServerKey(_, ref key)
|
||||
if retry_data.affected_server_keys.contains(key) => continue,
|
||||
ServiceTask::StoreDocumentKey(_, ref key, ref author, _, _) |
|
||||
ServiceTask::RetrieveShadowDocumentKeyCommon(_, ref key, ref author)
|
||||
if retry_data.affected_document_keys.contains(&(key.clone(), author.clone())) => continue,
|
||||
ServiceTask::RetrieveShadowDocumentKeyPersonal(_, ref key, ref requester)
|
||||
if retry_data.affected_document_keys.contains(&(key.clone(), public_to_address(requester))) => continue,
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// process request result
|
||||
let request_result = Self::process_service_task(data, task);
|
||||
match request_result {
|
||||
Ok(_) => processed_requests += 1,
|
||||
Err(_) => {
|
||||
@ -276,33 +301,119 @@ impl ServiceContractListener {
|
||||
Ok(processed_requests)
|
||||
}
|
||||
|
||||
/// Generate server key.
|
||||
fn generate_server_key(data: &Arc<ServiceContractListenerData>, server_key_id: &ServerKeyId, threshold: &H256) -> Result<Public, String> {
|
||||
let threshold_num = threshold.low_u64();
|
||||
if threshold != &threshold_num.into() || threshold_num >= ::std::usize::MAX as u64 {
|
||||
return Err(format!("invalid threshold {:?}", threshold));
|
||||
/// Generate server key (start generation session).
|
||||
fn generate_server_key(data: &Arc<ServiceContractListenerData>, origin: Address, server_key_id: &ServerKeyId, author: Address, threshold: usize) -> Result<(), String> {
|
||||
Self::process_server_key_generation_result(data, origin, server_key_id, data.cluster.new_generation_session(
|
||||
server_key_id.clone(), Some(origin), author, threshold).map(|_| None).map_err(Into::into))
|
||||
}
|
||||
|
||||
/// Process server key generation result.
|
||||
fn process_server_key_generation_result(data: &Arc<ServiceContractListenerData>, origin: Address, server_key_id: &ServerKeyId, result: Result<Option<Public>, Error>) -> Result<(), String> {
|
||||
match result {
|
||||
Ok(None) => Ok(()),
|
||||
Ok(Some(server_key)) => {
|
||||
data.contract.publish_generated_server_key(&origin, server_key_id, server_key)
|
||||
},
|
||||
Err(ref error) if is_internal_error(error) => Err(format!("{}", error)),
|
||||
Err(ref error) => {
|
||||
// ignore error as we're already processing an error
|
||||
let _ = data.contract.publish_server_key_generation_error(&origin, server_key_id)
|
||||
.map_err(|error| warn!(target: "secretstore", "{}: failed to publish GenerateServerKey({}) error: {}",
|
||||
data.self_key_pair.public(), server_key_id, error));
|
||||
Err(format!("{}", error))
|
||||
}
|
||||
}
|
||||
|
||||
// key server expects signed server_key_id in server_key_generation procedure
|
||||
// only signer could store document key for this server key later
|
||||
// => this API (server key generation) is not suitable for usage in encryption via contract endpoint
|
||||
let author_key = Random.generate().map_err(|e| format!("{}", e))?;
|
||||
let server_key_id_signature = sign(author_key.secret(), server_key_id).map_err(|e| format!("{}", e))?;
|
||||
data.key_server.generate_key(server_key_id, &server_key_id_signature, threshold_num as usize)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Restore server key.
|
||||
fn restore_server_key(data: &Arc<ServiceContractListenerData>, server_key_id: &ServerKeyId) -> Result<Public, String> {
|
||||
data.key_storage.get(server_key_id)
|
||||
.map_err(|e| format!("{}", e))
|
||||
.and_then(|ks| ks.ok_or("missing key".to_owned()))
|
||||
.map(|ks| ks.public)
|
||||
/// Retrieve server key.
|
||||
fn retrieve_server_key(data: &Arc<ServiceContractListenerData>, origin: Address, server_key_id: &ServerKeyId) -> Result<(), String> {
|
||||
match data.key_storage.get(server_key_id) {
|
||||
Ok(Some(server_key_share)) => {
|
||||
data.contract.publish_retrieved_server_key(&origin, server_key_id, server_key_share.public, server_key_share.threshold)
|
||||
},
|
||||
Ok(None) => {
|
||||
data.contract.publish_server_key_retrieval_error(&origin, server_key_id)
|
||||
}
|
||||
Err(ref error) if is_internal_error(error) => Err(format!("{}", error)),
|
||||
Err(ref error) => {
|
||||
// ignore error as we're already processing an error
|
||||
let _ = data.contract.publish_server_key_retrieval_error(&origin, server_key_id)
|
||||
.map_err(|error| warn!(target: "secretstore", "{}: failed to publish RetrieveServerKey({}) error: {}",
|
||||
data.self_key_pair.public(), server_key_id, error));
|
||||
Err(format!("{}", error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Publish server key.
|
||||
fn publish_server_key(data: &Arc<ServiceContractListenerData>, server_key_id: &ServerKeyId, server_key: &Public) -> Result<(), String> {
|
||||
data.contract.publish_server_key(server_key_id, server_key)
|
||||
/// Store document key.
|
||||
fn store_document_key(data: &Arc<ServiceContractListenerData>, origin: Address, server_key_id: &ServerKeyId, author: &Address, common_point: &Public, encrypted_point: &Public) -> Result<(), String> {
|
||||
let store_result = data.key_storage.get(server_key_id)
|
||||
.and_then(|key_share| key_share.ok_or(Error::DocumentNotFound))
|
||||
.and_then(|key_share| check_encrypted_data(Some(&key_share)).map(|_| key_share).map_err(Into::into))
|
||||
.and_then(|key_share| update_encrypted_data(&data.key_storage, server_key_id.clone(), key_share,
|
||||
author.clone(), common_point.clone(), encrypted_point.clone()).map_err(Into::into));
|
||||
match store_result {
|
||||
Ok(()) => {
|
||||
data.contract.publish_stored_document_key(&origin, server_key_id)
|
||||
},
|
||||
Err(ref error) if is_internal_error(&error) => Err(format!("{}", error)),
|
||||
Err(ref error) => {
|
||||
// ignore error as we're already processing an error
|
||||
let _ = data.contract.publish_document_key_store_error(&origin, server_key_id)
|
||||
.map_err(|error| warn!(target: "secretstore", "{}: failed to publish StoreDocumentKey({}) error: {}",
|
||||
data.self_key_pair.public(), server_key_id, error));
|
||||
Err(format!("{}", error))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieve common part of document key.
|
||||
fn retrieve_document_key_common(data: &Arc<ServiceContractListenerData>, origin: Address, server_key_id: &ServerKeyId, requester: &Address) -> Result<(), String> {
|
||||
let retrieval_result = data.acl_storage.check(requester.clone(), server_key_id)
|
||||
.and_then(|is_allowed| if !is_allowed { Err(Error::AccessDenied) } else { Ok(()) })
|
||||
.and_then(|_| data.key_storage.get(server_key_id).and_then(|key_share| key_share.ok_or(Error::DocumentNotFound)))
|
||||
.and_then(|key_share| key_share.common_point
|
||||
.ok_or(Error::DocumentNotFound)
|
||||
.and_then(|common_point| math::make_common_shadow_point(key_share.threshold, common_point)
|
||||
.map_err(|e| Error::Internal(e.into())))
|
||||
.map(|common_point| (common_point, key_share.threshold)));
|
||||
match retrieval_result {
|
||||
Ok((common_point, threshold)) => {
|
||||
data.contract.publish_retrieved_document_key_common(&origin, server_key_id, requester, common_point, threshold)
|
||||
},
|
||||
Err(ref error) if is_internal_error(&error) => Err(format!("{}", error)),
|
||||
Err(ref error) => {
|
||||
// ignore error as we're already processing an error
|
||||
let _ = data.contract.publish_document_key_retrieval_error(&origin, server_key_id, requester)
|
||||
.map_err(|error| warn!(target: "secretstore", "{}: failed to publish RetrieveDocumentKey({}) error: {}",
|
||||
data.self_key_pair.public(), server_key_id, error));
|
||||
Err(format!("{}", error))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieve personal part of document key (start decryption session).
|
||||
fn retrieve_document_key_personal(data: &Arc<ServiceContractListenerData>, origin: Address, server_key_id: &ServerKeyId, requester: Public) -> Result<(), String> {
|
||||
Self::process_document_key_retrieval_result(data, origin, server_key_id, &public_to_address(&requester), data.cluster.new_decryption_session(
|
||||
server_key_id.clone(), Some(origin), requester.clone().into(), None, true, true).map(|_| None).map_err(Into::into))
|
||||
}
|
||||
|
||||
/// Process document key retrieval result.
|
||||
fn process_document_key_retrieval_result(data: &Arc<ServiceContractListenerData>, origin: Address, server_key_id: &ServerKeyId, requester: &Address, result: Result<Option<(Vec<Address>, Public, Bytes)>, Error>) -> Result<(), String> {
|
||||
match result {
|
||||
Ok(None) => Ok(()),
|
||||
Ok(Some((participants, decrypted_secret, shadow))) => {
|
||||
data.contract.publish_retrieved_document_key_personal(&origin, server_key_id, &requester, &participants, decrypted_secret, shadow)
|
||||
},
|
||||
Err(ref error) if is_internal_error(error) => Err(format!("{}", error)),
|
||||
Err(ref error) => {
|
||||
// ignore error as we're already processing an error
|
||||
let _ = data.contract.publish_document_key_retrieval_error(&origin, server_key_id, &requester)
|
||||
.map_err(|error| warn!(target: "secretstore", "{}: failed to publish RetrieveDocumentKey({}) error: {}",
|
||||
data.self_key_pair.public(), server_key_id, error));
|
||||
Err(format!("{}", error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -340,25 +451,84 @@ impl ChainNotify for ServiceContractListener {
|
||||
|
||||
impl ClusterSessionsListener<GenerationSession> for ServiceContractListener {
|
||||
fn on_session_removed(&self, session: Arc<GenerationSession>) {
|
||||
// only publish when the session is started by another node
|
||||
// when it is started by this node, it is published from process_service_task
|
||||
if !is_processed_by_this_key_server(&*self.data.key_server_set, &*self.data.self_key_pair, &session.id()) {
|
||||
// by this time sesion must already be completed - either successfully, or not
|
||||
assert!(session.is_finished());
|
||||
// by this time sesion must already be completed - either successfully, or not
|
||||
assert!(session.is_finished());
|
||||
|
||||
// ignore result - the only thing that we can do is to log the error
|
||||
match session.wait(Some(Default::default()))
|
||||
.map_err(|e| format!("{}", e))
|
||||
.and_then(|server_key| Self::publish_server_key(&self.data, &session.id(), &server_key)) {
|
||||
Ok(_) => trace!(target: "secretstore", "{}: completed foreign GenerateServerKey({}) request",
|
||||
self.data.self_key_pair.public(), session.id()),
|
||||
Err(error) => warn!(target: "secretstore", "{}: failed to process GenerateServerKey({}) request with: {}",
|
||||
self.data.self_key_pair.public(), session.id(), error),
|
||||
// ignore result - the only thing that we can do is to log the error
|
||||
let server_key_id = session.id();
|
||||
if let Some(origin) = session.origin() {
|
||||
if let Some(generation_result) = session.wait(Some(Default::default())) {
|
||||
let generation_result = generation_result.map(Some).map_err(Into::into);
|
||||
let _ = Self::process_server_key_generation_result(&self.data, origin, &server_key_id, generation_result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ClusterSessionsListener<DecryptionSession> for ServiceContractListener {
|
||||
fn on_session_removed(&self, session: Arc<DecryptionSession>) {
|
||||
// by this time sesion must already be completed - either successfully, or not
|
||||
assert!(session.is_finished());
|
||||
|
||||
// ignore result - the only thing that we can do is to log the error
|
||||
let session_id = session.id();
|
||||
let server_key_id = session_id.id;
|
||||
if let (Some(requester), Some(origin)) = (session.requester().and_then(|r| r.address(&server_key_id).ok()), session.origin()) {
|
||||
if let Some(retrieval_result) = session.wait(Some(Default::default())) {
|
||||
let retrieval_result = retrieval_result.map(|key_shadow|
|
||||
session.broadcast_shadows()
|
||||
.and_then(|broadcast_shadows|
|
||||
broadcast_shadows.get(self.data.self_key_pair.public())
|
||||
.map(|self_shadow| (
|
||||
broadcast_shadows.keys().map(public_to_address).collect(),
|
||||
key_shadow.decrypted_secret,
|
||||
self_shadow.clone()
|
||||
)))
|
||||
).map_err(Into::into);
|
||||
let _ = Self::process_document_key_retrieval_result(&self.data, origin, &server_key_id, &requester, retrieval_result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::fmt::Display for ServiceTask {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
match *self {
|
||||
ServiceTask::Retry => write!(f, "Retry"),
|
||||
ServiceTask::GenerateServerKey(_, ref server_key_id, ref author, ref threshold) =>
|
||||
write!(f, "GenerateServerKey({}, {}, {})", server_key_id, author, threshold),
|
||||
ServiceTask::RetrieveServerKey(_, ref server_key_id) =>
|
||||
write!(f, "RetrieveServerKey({})", server_key_id),
|
||||
ServiceTask::StoreDocumentKey(_, ref server_key_id, ref author, _, _) =>
|
||||
write!(f, "StoreDocumentKey({}, {})", server_key_id, author),
|
||||
ServiceTask::RetrieveShadowDocumentKeyCommon(_, ref server_key_id, ref requester) =>
|
||||
write!(f, "RetrieveShadowDocumentKeyCommon({}, {})", server_key_id, requester),
|
||||
ServiceTask::RetrieveShadowDocumentKeyPersonal(_, ref server_key_id, ref requester) =>
|
||||
write!(f, "RetrieveShadowDocumentKeyPersonal({}, {})", server_key_id, public_to_address(requester)),
|
||||
ServiceTask::Shutdown => write!(f, "Shutdown"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Is internal error? Internal error means that it is SS who's responsible for it, like: connectivity, db failure, ...
|
||||
/// External error is caused by SS misuse, like: trying to generate duplicated key, access denied, ...
|
||||
/// When internal error occurs, we just ignore request for now and will retry later.
|
||||
/// When external error occurs, we reject request.
|
||||
fn is_internal_error(_error: &Error) -> bool {
|
||||
// TODO [Reliability]: implement me after proper is passed through network
|
||||
false
|
||||
}
|
||||
|
||||
/// Log service task result.
|
||||
fn log_service_task_result(task: &ServiceTask, self_id: &Public, result: Result<(), String>) -> Result<(), String> {
|
||||
match result {
|
||||
Ok(_) => trace!(target: "secretstore", "{}: processed {} request", self_id, task),
|
||||
Err(ref error) => warn!(target: "secretstore", "{}: failed to process {} request with: {}", self_id, task, error),
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Returns true when session, related to `server_key_id` must be started on this KeyServer.
|
||||
fn is_processed_by_this_key_server(key_server_set: &KeyServerSet, self_key_pair: &NodeKeyPair, server_key_id: &H256) -> bool {
|
||||
let servers = key_server_set.snapshot().current_set;
|
||||
@ -390,17 +560,31 @@ mod tests {
|
||||
use listener::service_contract::ServiceContract;
|
||||
use listener::service_contract::tests::DummyServiceContract;
|
||||
use key_server_cluster::DummyClusterClient;
|
||||
use key_server::tests::DummyKeyServer;
|
||||
use acl_storage::{AclStorage, DummyAclStorage};
|
||||
use key_storage::{KeyStorage, DocumentKeyShare};
|
||||
use key_storage::tests::DummyKeyStorage;
|
||||
use key_server_set::tests::MapKeyServerSet;
|
||||
use PlainNodeKeyPair;
|
||||
use {PlainNodeKeyPair, ServerKeyId};
|
||||
use super::{ServiceTask, ServiceContractListener, ServiceContractListenerParams, is_processed_by_this_key_server};
|
||||
|
||||
fn make_service_contract_listener(contract: Option<Arc<ServiceContract>>, key_server: Option<Arc<DummyKeyServer>>, key_storage: Option<Arc<KeyStorage>>) -> Arc<ServiceContractListener> {
|
||||
fn create_non_empty_key_storage(has_doc_key: bool) -> Arc<DummyKeyStorage> {
|
||||
let key_storage = Arc::new(DummyKeyStorage::default());
|
||||
let mut key_share = DocumentKeyShare::default();
|
||||
key_share.public = KeyPair::from_secret("0000000000000000000000000000000000000000000000000000000000000001"
|
||||
.parse().unwrap()).unwrap().public().clone();
|
||||
if has_doc_key {
|
||||
key_share.common_point = Some(Default::default());
|
||||
key_share.encrypted_point = Some(Default::default());
|
||||
}
|
||||
key_storage.insert(Default::default(), key_share.clone()).unwrap();
|
||||
key_storage
|
||||
}
|
||||
|
||||
fn make_service_contract_listener(contract: Option<Arc<ServiceContract>>, cluster: Option<Arc<DummyClusterClient>>, key_storage: Option<Arc<KeyStorage>>, acl_storage: Option<Arc<AclStorage>>) -> Arc<ServiceContractListener> {
|
||||
let contract = contract.unwrap_or_else(|| Arc::new(DummyServiceContract::default()));
|
||||
let key_server = key_server.unwrap_or_else(|| Arc::new(DummyKeyServer::default()));
|
||||
let cluster = cluster.unwrap_or_else(|| Arc::new(DummyClusterClient::default()));
|
||||
let key_storage = key_storage.unwrap_or_else(|| Arc::new(DummyKeyStorage::default()));
|
||||
let acl_storage = acl_storage.unwrap_or_else(|| Arc::new(DummyAclStorage::default()));
|
||||
let servers_set = Arc::new(MapKeyServerSet::new(vec![
|
||||
("79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8".parse().unwrap(),
|
||||
"127.0.0.1:8080".parse().unwrap()),
|
||||
@ -412,12 +596,12 @@ mod tests {
|
||||
let self_key_pair = Arc::new(PlainNodeKeyPair::new(KeyPair::from_secret("0000000000000000000000000000000000000000000000000000000000000001".parse().unwrap()).unwrap()));
|
||||
ServiceContractListener::new(ServiceContractListenerParams {
|
||||
contract: contract,
|
||||
key_server: key_server,
|
||||
self_key_pair: self_key_pair,
|
||||
key_server_set: servers_set,
|
||||
cluster: Arc::new(DummyClusterClient::default()),
|
||||
acl_storage: acl_storage,
|
||||
cluster: cluster,
|
||||
key_storage: key_storage,
|
||||
})
|
||||
}).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -576,51 +760,32 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn no_tasks_scheduled_when_no_contract_events() {
|
||||
let listener = make_service_contract_listener(None, None, None);
|
||||
let listener = make_service_contract_listener(None, None, None, None);
|
||||
assert_eq!(listener.data.tasks_queue.snapshot().len(), 1);
|
||||
listener.process_service_contract_events();
|
||||
assert_eq!(listener.data.tasks_queue.snapshot().len(), 1);
|
||||
}
|
||||
|
||||
// server key generation tests
|
||||
|
||||
#[test]
|
||||
fn server_key_generation_is_scheduled_when_requested_key_is_unknown() {
|
||||
fn server_key_generation_is_scheduled_when_requested() {
|
||||
let mut contract = DummyServiceContract::default();
|
||||
contract.logs.push(vec![Default::default(), Default::default(), Default::default()]);
|
||||
let listener = make_service_contract_listener(Some(Arc::new(contract)), None, None);
|
||||
contract.logs.push(ServiceTask::GenerateServerKey(Default::default(), Default::default(), Default::default(), 0));
|
||||
let listener = make_service_contract_listener(Some(Arc::new(contract)), None, None, None);
|
||||
assert_eq!(listener.data.tasks_queue.snapshot().len(), 1);
|
||||
listener.process_service_contract_events();
|
||||
assert_eq!(listener.data.tasks_queue.snapshot().len(), 2);
|
||||
assert_eq!(listener.data.tasks_queue.snapshot().pop_back(), Some(ServiceTask::GenerateServerKey(Default::default(), Default::default())));
|
||||
assert_eq!(listener.data.tasks_queue.snapshot().pop_back(), Some(ServiceTask::GenerateServerKey(
|
||||
Default::default(), Default::default(), Default::default(), 0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_new_tasks_scheduled_when_requested_key_is_unknown_and_request_belongs_to_other_key_server() {
|
||||
fn no_new_tasks_scheduled_when_server_key_generation_requested_and_request_belongs_to_other_key_server() {
|
||||
let server_key_id = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".parse().unwrap();
|
||||
let mut contract = DummyServiceContract::default();
|
||||
contract.logs.push(vec![Default::default(), server_key_id, Default::default()]);
|
||||
let listener = make_service_contract_listener(Some(Arc::new(contract)), None, None);
|
||||
assert_eq!(listener.data.tasks_queue.snapshot().len(), 1);
|
||||
listener.process_service_contract_events();
|
||||
assert_eq!(listener.data.tasks_queue.snapshot().len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn server_key_restore_is_scheduled_when_requested_key_is_known() {
|
||||
let mut contract = DummyServiceContract::default();
|
||||
contract.logs.push(vec![Default::default(), Default::default(), Default::default()]);
|
||||
let listener = make_service_contract_listener(Some(Arc::new(contract)), None, None);
|
||||
listener.data.key_storage.insert(Default::default(), Default::default()).unwrap();
|
||||
assert_eq!(listener.data.tasks_queue.snapshot().len(), 1);
|
||||
listener.process_service_contract_events();
|
||||
assert_eq!(listener.data.tasks_queue.snapshot().len(), 2);
|
||||
assert_eq!(listener.data.tasks_queue.snapshot().pop_back(), Some(ServiceTask::RestoreServerKey(Default::default())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_new_tasks_scheduled_when_wrong_number_of_topics_in_log() {
|
||||
let mut contract = DummyServiceContract::default();
|
||||
contract.logs.push(vec![Default::default(), Default::default()]);
|
||||
let listener = make_service_contract_listener(Some(Arc::new(contract)), None, None);
|
||||
contract.logs.push(ServiceTask::GenerateServerKey(Default::default(), server_key_id, Default::default(), 0));
|
||||
let listener = make_service_contract_listener(Some(Arc::new(contract)), None, None, None);
|
||||
assert_eq!(listener.data.tasks_queue.snapshot().len(), 1);
|
||||
listener.process_service_contract_events();
|
||||
assert_eq!(listener.data.tasks_queue.snapshot().len(), 1);
|
||||
@ -628,32 +793,221 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn generation_session_is_created_when_processing_generate_server_key_task() {
|
||||
let key_server = Arc::new(DummyKeyServer::default());
|
||||
let listener = make_service_contract_listener(None, Some(key_server.clone()), None);
|
||||
ServiceContractListener::process_service_task(&listener.data, ServiceTask::GenerateServerKey(Default::default(), Default::default())).unwrap_err();
|
||||
assert_eq!(key_server.generation_requests_count.load(Ordering::Relaxed), 1);
|
||||
let cluster = Arc::new(DummyClusterClient::default());
|
||||
let listener = make_service_contract_listener(None, Some(cluster.clone()), None, None);
|
||||
ServiceContractListener::process_service_task(&listener.data, ServiceTask::GenerateServerKey(
|
||||
Default::default(), Default::default(), Default::default(), Default::default())).unwrap_err();
|
||||
assert_eq!(cluster.generation_requests_count.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_is_read_and_published_when_processing_restore_server_key_task() {
|
||||
let contract = Arc::new(DummyServiceContract::default());
|
||||
let key_storage = Arc::new(DummyKeyStorage::default());
|
||||
let mut key_share = DocumentKeyShare::default();
|
||||
key_share.public = KeyPair::from_secret("0000000000000000000000000000000000000000000000000000000000000001".parse().unwrap()).unwrap().public().clone();
|
||||
key_storage.insert(Default::default(), key_share.clone()).unwrap();
|
||||
let listener = make_service_contract_listener(Some(contract.clone()), None, Some(key_storage));
|
||||
ServiceContractListener::process_service_task(&listener.data, ServiceTask::RestoreServerKey(Default::default())).unwrap();
|
||||
assert_eq!(*contract.published_keys.lock(), vec![(Default::default(), key_share.public)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generation_is_not_retried_if_tried_in_the_same_cycle() {
|
||||
fn server_key_generation_is_not_retried_if_tried_in_the_same_cycle() {
|
||||
let mut contract = DummyServiceContract::default();
|
||||
contract.pending_requests.push((false, ServiceTask::GenerateServerKey(Default::default(), Default::default())));
|
||||
let key_server = Arc::new(DummyKeyServer::default());
|
||||
let listener = make_service_contract_listener(Some(Arc::new(contract)), Some(key_server.clone()), None);
|
||||
listener.data.retry_data.lock().generated_keys.insert(Default::default());
|
||||
contract.pending_requests.push((false, ServiceTask::GenerateServerKey(Default::default(),
|
||||
Default::default(), Default::default(), Default::default())));
|
||||
let cluster = Arc::new(DummyClusterClient::default());
|
||||
let listener = make_service_contract_listener(Some(Arc::new(contract)), Some(cluster.clone()), None, None);
|
||||
listener.data.retry_data.lock().affected_server_keys.insert(Default::default());
|
||||
ServiceContractListener::retry_pending_requests(&listener.data).unwrap();
|
||||
assert_eq!(key_server.generation_requests_count.load(Ordering::Relaxed), 0);
|
||||
assert_eq!(cluster.generation_requests_count.load(Ordering::Relaxed), 0);
|
||||
}
|
||||
|
||||
// server key retrieval tests
|
||||
|
||||
#[test]
|
||||
fn server_key_retrieval_is_scheduled_when_requested() {
|
||||
let mut contract = DummyServiceContract::default();
|
||||
contract.logs.push(ServiceTask::RetrieveServerKey(Default::default(), Default::default()));
|
||||
let listener = make_service_contract_listener(Some(Arc::new(contract)), None, None, None);
|
||||
assert_eq!(listener.data.tasks_queue.snapshot().len(), 1);
|
||||
listener.process_service_contract_events();
|
||||
assert_eq!(listener.data.tasks_queue.snapshot().len(), 2);
|
||||
assert_eq!(listener.data.tasks_queue.snapshot().pop_back(), Some(ServiceTask::RetrieveServerKey(
|
||||
Default::default(), Default::default())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn server_key_retrieval_is_scheduled_when_requested_and_request_belongs_to_other_key_server() {
|
||||
let server_key_id: ServerKeyId = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".parse().unwrap();
|
||||
let mut contract = DummyServiceContract::default();
|
||||
contract.logs.push(ServiceTask::RetrieveServerKey(Default::default(), server_key_id.clone()));
|
||||
let listener = make_service_contract_listener(Some(Arc::new(contract)), None, None, None);
|
||||
assert_eq!(listener.data.tasks_queue.snapshot().len(), 1);
|
||||
listener.process_service_contract_events();
|
||||
assert_eq!(listener.data.tasks_queue.snapshot().len(), 2);
|
||||
assert_eq!(listener.data.tasks_queue.snapshot().pop_back(), Some(ServiceTask::RetrieveServerKey(
|
||||
Default::default(), server_key_id)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn server_key_is_retrieved_when_processing_retrieve_server_key_task() {
|
||||
let contract = Arc::new(DummyServiceContract::default());
|
||||
let key_storage = create_non_empty_key_storage(false);
|
||||
let listener = make_service_contract_listener(Some(contract.clone()), None, Some(key_storage), None);
|
||||
ServiceContractListener::process_service_task(&listener.data, ServiceTask::RetrieveServerKey(
|
||||
Default::default(), Default::default())).unwrap();
|
||||
assert_eq!(*contract.retrieved_server_keys.lock(), vec![(Default::default(),
|
||||
KeyPair::from_secret("0000000000000000000000000000000000000000000000000000000000000001".parse().unwrap()).unwrap().public().clone(), 0)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn server_key_retrieval_failure_is_reported_when_processing_retrieve_server_key_task_and_key_is_unknown() {
|
||||
let contract = Arc::new(DummyServiceContract::default());
|
||||
let listener = make_service_contract_listener(Some(contract.clone()), None, None, None);
|
||||
ServiceContractListener::process_service_task(&listener.data, ServiceTask::RetrieveServerKey(
|
||||
Default::default(), Default::default())).unwrap();
|
||||
assert_eq!(*contract.server_keys_retrieval_failures.lock(), vec![Default::default()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn server_key_retrieval_is_not_retried_if_tried_in_the_same_cycle() {
|
||||
let mut contract = DummyServiceContract::default();
|
||||
contract.pending_requests.push((false, ServiceTask::RetrieveServerKey(Default::default(), Default::default())));
|
||||
let cluster = Arc::new(DummyClusterClient::default());
|
||||
let listener = make_service_contract_listener(Some(Arc::new(contract)), Some(cluster.clone()), None, None);
|
||||
listener.data.retry_data.lock().affected_server_keys.insert(Default::default());
|
||||
ServiceContractListener::retry_pending_requests(&listener.data).unwrap();
|
||||
assert_eq!(cluster.generation_requests_count.load(Ordering::Relaxed), 0);
|
||||
}
|
||||
|
||||
// document key store tests
|
||||
|
||||
#[test]
|
||||
fn document_key_store_is_scheduled_when_requested() {
|
||||
let mut contract = DummyServiceContract::default();
|
||||
contract.logs.push(ServiceTask::StoreDocumentKey(Default::default(), Default::default(),
|
||||
Default::default(), Default::default(), Default::default()));
|
||||
let listener = make_service_contract_listener(Some(Arc::new(contract)), None, None, None);
|
||||
assert_eq!(listener.data.tasks_queue.snapshot().len(), 1);
|
||||
listener.process_service_contract_events();
|
||||
assert_eq!(listener.data.tasks_queue.snapshot().len(), 2);
|
||||
assert_eq!(listener.data.tasks_queue.snapshot().pop_back(), Some(ServiceTask::StoreDocumentKey(
|
||||
Default::default(), Default::default(), Default::default(), Default::default(), Default::default())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn document_key_store_is_scheduled_when_requested_and_request_belongs_to_other_key_server() {
|
||||
let server_key_id: ServerKeyId = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".parse().unwrap();
|
||||
let mut contract = DummyServiceContract::default();
|
||||
contract.logs.push(ServiceTask::StoreDocumentKey(Default::default(), server_key_id.clone(),
|
||||
Default::default(), Default::default(), Default::default()));
|
||||
let listener = make_service_contract_listener(Some(Arc::new(contract)), None, None, None);
|
||||
assert_eq!(listener.data.tasks_queue.snapshot().len(), 1);
|
||||
listener.process_service_contract_events();
|
||||
assert_eq!(listener.data.tasks_queue.snapshot().len(), 2);
|
||||
assert_eq!(listener.data.tasks_queue.snapshot().pop_back(), Some(ServiceTask::StoreDocumentKey(
|
||||
Default::default(), server_key_id, Default::default(), Default::default(), Default::default())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn document_key_is_stored_when_processing_store_document_key_task() {
|
||||
let contract = Arc::new(DummyServiceContract::default());
|
||||
let key_storage = create_non_empty_key_storage(false);
|
||||
let listener = make_service_contract_listener(Some(contract.clone()), None, Some(key_storage.clone()), None);
|
||||
ServiceContractListener::process_service_task(&listener.data, ServiceTask::StoreDocumentKey(
|
||||
Default::default(), Default::default(), Default::default(), Default::default(), Default::default())).unwrap();
|
||||
assert_eq!(*contract.stored_document_keys.lock(), vec![Default::default()]);
|
||||
|
||||
let key_share = key_storage.get(&Default::default()).unwrap().unwrap();
|
||||
assert_eq!(key_share.common_point, Some(Default::default()));
|
||||
assert_eq!(key_share.encrypted_point, Some(Default::default()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn document_key_store_failure_reported_when_no_server_key() {
|
||||
let contract = Arc::new(DummyServiceContract::default());
|
||||
let listener = make_service_contract_listener(Some(contract.clone()), None, None, None);
|
||||
ServiceContractListener::process_service_task(&listener.data, ServiceTask::StoreDocumentKey(
|
||||
Default::default(), Default::default(), Default::default(), Default::default(), Default::default())).unwrap_err();
|
||||
assert_eq!(*contract.document_keys_store_failures.lock(), vec![Default::default()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn document_key_store_failure_reported_when_document_key_already_set() {
|
||||
let contract = Arc::new(DummyServiceContract::default());
|
||||
let key_storage = create_non_empty_key_storage(true);
|
||||
let listener = make_service_contract_listener(Some(contract.clone()), None, Some(key_storage), None);
|
||||
ServiceContractListener::process_service_task(&listener.data, ServiceTask::StoreDocumentKey(
|
||||
Default::default(), Default::default(), Default::default(), Default::default(), Default::default())).unwrap_err();
|
||||
assert_eq!(*contract.document_keys_store_failures.lock(), vec![Default::default()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn document_key_store_failure_reported_when_author_differs() {
|
||||
let contract = Arc::new(DummyServiceContract::default());
|
||||
let key_storage = create_non_empty_key_storage(false);
|
||||
let listener = make_service_contract_listener(Some(contract.clone()), None, Some(key_storage), None);
|
||||
ServiceContractListener::process_service_task(&listener.data, ServiceTask::StoreDocumentKey(
|
||||
Default::default(), Default::default(), 1.into(), Default::default(), Default::default())).unwrap_err();
|
||||
assert_eq!(*contract.document_keys_store_failures.lock(), vec![Default::default()]);
|
||||
}
|
||||
|
||||
// document key shadow common retrieval tests
|
||||
|
||||
#[test]
|
||||
fn document_key_shadow_common_retrieval_is_scheduled_when_requested() {
|
||||
let mut contract = DummyServiceContract::default();
|
||||
contract.logs.push(ServiceTask::RetrieveShadowDocumentKeyCommon(Default::default(), Default::default(), Default::default()));
|
||||
let listener = make_service_contract_listener(Some(Arc::new(contract)), None, None, None);
|
||||
assert_eq!(listener.data.tasks_queue.snapshot().len(), 1);
|
||||
listener.process_service_contract_events();
|
||||
assert_eq!(listener.data.tasks_queue.snapshot().len(), 2);
|
||||
assert_eq!(listener.data.tasks_queue.snapshot().pop_back(), Some(ServiceTask::RetrieveShadowDocumentKeyCommon(
|
||||
Default::default(), Default::default(), Default::default())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn document_key_shadow_common_retrieval_is_scheduled_when_requested_and_request_belongs_to_other_key_server() {
|
||||
let server_key_id: ServerKeyId = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".parse().unwrap();
|
||||
let mut contract = DummyServiceContract::default();
|
||||
contract.logs.push(ServiceTask::RetrieveShadowDocumentKeyCommon(Default::default(), server_key_id.clone(), Default::default()));
|
||||
let listener = make_service_contract_listener(Some(Arc::new(contract)), None, None, None);
|
||||
assert_eq!(listener.data.tasks_queue.snapshot().len(), 1);
|
||||
listener.process_service_contract_events();
|
||||
assert_eq!(listener.data.tasks_queue.snapshot().len(), 2);
|
||||
assert_eq!(listener.data.tasks_queue.snapshot().pop_back(), Some(ServiceTask::RetrieveShadowDocumentKeyCommon(
|
||||
Default::default(), server_key_id, Default::default())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn document_key_shadow_common_is_retrieved_when_processing_document_key_shadow_common_retrieval_task() {
|
||||
let contract = Arc::new(DummyServiceContract::default());
|
||||
let key_storage = create_non_empty_key_storage(true);
|
||||
let listener = make_service_contract_listener(Some(contract.clone()), None, Some(key_storage.clone()), None);
|
||||
ServiceContractListener::process_service_task(&listener.data, ServiceTask::RetrieveShadowDocumentKeyCommon(
|
||||
Default::default(), Default::default(), Default::default())).unwrap();
|
||||
assert_eq!(*contract.common_shadow_retrieved_document_keys.lock(), vec![(Default::default(), Default::default(),
|
||||
Default::default(), 0)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn document_key_shadow_common_retrieval_failure_reported_when_access_denied() {
|
||||
let acl_storage = DummyAclStorage::default();
|
||||
acl_storage.prohibit(Default::default(), Default::default());
|
||||
let contract = Arc::new(DummyServiceContract::default());
|
||||
let key_storage = create_non_empty_key_storage(true);
|
||||
let listener = make_service_contract_listener(Some(contract.clone()), None, Some(key_storage.clone()), Some(Arc::new(acl_storage)));
|
||||
ServiceContractListener::process_service_task(&listener.data, ServiceTask::RetrieveShadowDocumentKeyCommon(
|
||||
Default::default(), Default::default(), Default::default())).unwrap_err();
|
||||
assert_eq!(*contract.document_keys_shadow_retrieval_failures.lock(), vec![(Default::default(), Default::default())]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn document_key_shadow_common_retrieval_failure_reported_when_no_server_key() {
|
||||
let contract = Arc::new(DummyServiceContract::default());
|
||||
let listener = make_service_contract_listener(Some(contract.clone()), None, None, None);
|
||||
ServiceContractListener::process_service_task(&listener.data, ServiceTask::RetrieveShadowDocumentKeyCommon(
|
||||
Default::default(), Default::default(), Default::default())).unwrap_err();
|
||||
assert_eq!(*contract.document_keys_shadow_retrieval_failures.lock(), vec![(Default::default(), Default::default())]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn document_key_shadow_common_retrieval_failure_reported_when_no_document_key() {
|
||||
let contract = Arc::new(DummyServiceContract::default());
|
||||
let key_storage = create_non_empty_key_storage(false);
|
||||
let listener = make_service_contract_listener(Some(contract.clone()), None, Some(key_storage.clone()), None);
|
||||
ServiceContractListener::process_service_task(&listener.data, ServiceTask::RetrieveShadowDocumentKeyCommon(
|
||||
Default::default(), Default::default(), Default::default())).unwrap_err();
|
||||
assert_eq!(*contract.document_keys_shadow_retrieval_failures.lock(), vec![(Default::default(), Default::default())]);
|
||||
}
|
||||
}
|
||||
|
@ -17,8 +17,8 @@
|
||||
use std::collections::BTreeSet;
|
||||
use ethkey::{KeyPair, Signature, Error as EthKeyError};
|
||||
use ethereum_types::{H256, Address};
|
||||
use types::all::{Error, Public, ServerKeyId, MessageHash, EncryptedMessageSignature, RequestSignature, EncryptedDocumentKey,
|
||||
EncryptedDocumentKeyShadow, NodeId};
|
||||
use types::all::{Error, Public, ServerKeyId, MessageHash, EncryptedMessageSignature, RequestSignature, Requester,
|
||||
EncryptedDocumentKey, EncryptedDocumentKeyShadow, NodeId};
|
||||
|
||||
/// Node key pair.
|
||||
pub trait NodeKeyPair: Send + Sync {
|
||||
@ -36,34 +36,34 @@ pub trait NodeKeyPair: Send + Sync {
|
||||
pub trait ServerKeyGenerator {
|
||||
/// Generate new SK.
|
||||
/// `key_id` is the caller-provided identifier of generated SK.
|
||||
/// `signature` is `key_id`, signed with caller public key.
|
||||
/// `author` is the author of key entry.
|
||||
/// `threshold + 1` is the minimal number of nodes, required to restore private key.
|
||||
/// Result is a public portion of SK.
|
||||
fn generate_key(&self, key_id: &ServerKeyId, signature: &RequestSignature, threshold: usize) -> Result<Public, Error>;
|
||||
fn generate_key(&self, key_id: &ServerKeyId, author: &Requester, threshold: usize) -> Result<Public, Error>;
|
||||
}
|
||||
|
||||
/// Document key (DK) server.
|
||||
pub trait DocumentKeyServer: ServerKeyGenerator {
|
||||
/// Store externally generated DK.
|
||||
/// `key_id` is identifier of previously generated SK.
|
||||
/// `signature` is key_id, signed with caller public key. Caller must be the same as in the `generate_key` call.
|
||||
/// `author` is the same author, that has created the server key.
|
||||
/// `common_point` is a result of `k * T` expression, where `T` is generation point and `k` is random scalar in EC field.
|
||||
/// `encrypted_document_key` is a result of `M + k * y` expression, where `M` is unencrypted document key (point on EC),
|
||||
/// `k` is the same scalar used in `common_point` calculation and `y` is previously generated public part of SK.
|
||||
fn store_document_key(&self, key_id: &ServerKeyId, signature: &RequestSignature, common_point: Public, encrypted_document_key: Public) -> Result<(), Error>;
|
||||
fn store_document_key(&self, key_id: &ServerKeyId, author: &Requester, common_point: Public, encrypted_document_key: Public) -> Result<(), Error>;
|
||||
/// Generate and store both SK and DK. This is a shortcut for consequent calls of `generate_key` and `store_document_key`.
|
||||
/// The only difference is that DK is generated by DocumentKeyServer (which might be considered unsafe).
|
||||
/// `key_id` is the caller-provided identifier of generated SK.
|
||||
/// `signature` is `key_id`, signed with caller public key.
|
||||
/// `author` is the author of server && document key entry.
|
||||
/// `threshold + 1` is the minimal number of nodes, required to restore private key.
|
||||
/// Result is a DK, encrypted with caller public key.
|
||||
fn generate_document_key(&self, key_id: &ServerKeyId, signature: &RequestSignature, threshold: usize) -> Result<EncryptedDocumentKey, Error>;
|
||||
fn generate_document_key(&self, key_id: &ServerKeyId, author: &Requester, threshold: usize) -> Result<EncryptedDocumentKey, Error>;
|
||||
/// Restore previously stored DK.
|
||||
/// DK is decrypted on the key server (which might be considered unsafe), and then encrypted with caller public key.
|
||||
/// `key_id` is identifier of previously generated SK.
|
||||
/// `signature` is key_id, signed with caller public key. Caller must be on ACL for this function to succeed.
|
||||
/// `requester` is the one who requests access to document key. Caller must be on ACL for this function to succeed.
|
||||
/// Result is a DK, encrypted with caller public key.
|
||||
fn restore_document_key(&self, key_id: &ServerKeyId, signature: &RequestSignature) -> Result<EncryptedDocumentKey, Error>;
|
||||
fn restore_document_key(&self, key_id: &ServerKeyId, requester: &Requester) -> Result<EncryptedDocumentKey, Error>;
|
||||
/// Restore previously stored DK.
|
||||
/// To decrypt DK on client:
|
||||
/// 1) use requestor secret key to decrypt secret coefficients from result.decrypt_shadows
|
||||
@ -71,24 +71,24 @@ pub trait DocumentKeyServer: ServerKeyGenerator {
|
||||
/// 3) calculate decrypt_shadow_point: decrypt_shadows_sum * result.common_point
|
||||
/// 4) calculate decrypted_secret: result.decrypted_secret + decrypt_shadow_point
|
||||
/// Result is a DK shadow.
|
||||
fn restore_document_key_shadow(&self, key_id: &ServerKeyId, signature: &RequestSignature) -> Result<EncryptedDocumentKeyShadow, Error>;
|
||||
fn restore_document_key_shadow(&self, key_id: &ServerKeyId, requester: &Requester) -> Result<EncryptedDocumentKeyShadow, Error>;
|
||||
}
|
||||
|
||||
/// Message signer.
|
||||
pub trait MessageSigner: ServerKeyGenerator {
|
||||
/// Generate Schnorr signature for message with previously generated SK.
|
||||
/// `key_id` is the caller-provided identifier of generated SK.
|
||||
/// `signature` is `key_id`, signed with caller public key.
|
||||
/// `requester` is the one who requests access to server key private.
|
||||
/// `message` is the message to be signed.
|
||||
/// Result is a signed message, encrypted with caller public key.
|
||||
fn sign_message_schnorr(&self, key_id: &ServerKeyId, signature: &RequestSignature, message: MessageHash) -> Result<EncryptedMessageSignature, Error>;
|
||||
fn sign_message_schnorr(&self, key_id: &ServerKeyId, requester: &Requester, message: MessageHash) -> Result<EncryptedMessageSignature, Error>;
|
||||
/// Generate ECDSA signature for message with previously generated SK.
|
||||
/// WARNING: only possible when SK was generated using t <= 2 * N.
|
||||
/// `key_id` is the caller-provided identifier of generated SK.
|
||||
/// `signature` is `key_id`, signed with caller public key.
|
||||
/// `message` is the message to be signed.
|
||||
/// Result is a signed message, encrypted with caller public key.
|
||||
fn sign_message_ecdsa(&self, key_id: &ServerKeyId, signature: &RequestSignature, message: MessageHash) -> Result<EncryptedMessageSignature, Error>;
|
||||
fn sign_message_ecdsa(&self, key_id: &ServerKeyId, signature: &Requester, message: MessageHash) -> Result<EncryptedMessageSignature, Error>;
|
||||
}
|
||||
|
||||
/// Administrative sessions server.
|
||||
|
@ -38,8 +38,8 @@ pub use ethkey::Public;
|
||||
/// Secret store error
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
/// Bad signature is passed
|
||||
BadSignature,
|
||||
/// Insufficient requester data
|
||||
InsufficientRequesterData(String),
|
||||
/// Access to resource is denied
|
||||
AccessDenied,
|
||||
/// Requested document not found
|
||||
@ -77,8 +77,16 @@ pub enum ContractAddress {
|
||||
pub struct ServiceConfiguration {
|
||||
/// HTTP listener address. If None, HTTP API is disabled.
|
||||
pub listener_address: Option<NodeAddress>,
|
||||
/// Service contract address. If None, service contract API is disabled.
|
||||
/// Service contract address.
|
||||
pub service_contract_address: Option<ContractAddress>,
|
||||
/// Server key generation service contract address.
|
||||
pub service_contract_srv_gen_address: Option<ContractAddress>,
|
||||
/// Server key retrieval service contract address.
|
||||
pub service_contract_srv_retr_address: Option<ContractAddress>,
|
||||
/// Document key store service contract address.
|
||||
pub service_contract_doc_store_address: Option<ContractAddress>,
|
||||
/// Document key shadow retrieval service contract address.
|
||||
pub service_contract_doc_sretr_address: Option<ContractAddress>,
|
||||
/// Is ACL check enabled. If false, everyone has access to all keys. Useful for tests only.
|
||||
pub acl_check_enabled: bool,
|
||||
/// Data directory path for secret store
|
||||
@ -131,7 +139,7 @@ pub enum Requester {
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
match *self {
|
||||
Error::BadSignature => write!(f, "Bad signature"),
|
||||
Error::InsufficientRequesterData(ref e) => write!(f, "Insufficient requester data: {}", e),
|
||||
Error::AccessDenied => write!(f, "Access dened"),
|
||||
Error::DocumentNotFound => write!(f, "Document not found"),
|
||||
Error::Hyper(ref msg) => write!(f, "Hyper error: {}", msg),
|
||||
@ -163,6 +171,8 @@ impl From<kvdb::Error> for Error {
|
||||
impl From<key_server_cluster::Error> for Error {
|
||||
fn from(err: key_server_cluster::Error) -> Self {
|
||||
match err {
|
||||
key_server_cluster::Error::InsufficientRequesterData(err)
|
||||
=> Error::InsufficientRequesterData(err),
|
||||
key_server_cluster::Error::ConsensusUnreachable
|
||||
| key_server_cluster::Error::AccessDenied => Error::AccessDenied,
|
||||
key_server_cluster::Error::MissingKeyShare => Error::DocumentNotFound,
|
||||
@ -184,16 +194,18 @@ impl Default for Requester {
|
||||
}
|
||||
|
||||
impl Requester {
|
||||
pub fn public(&self, server_key_id: &ServerKeyId) -> Option<Public> {
|
||||
pub fn public(&self, server_key_id: &ServerKeyId) -> Result<Public, String> {
|
||||
match *self {
|
||||
Requester::Signature(ref signature) => ethkey::recover(signature, server_key_id).ok(),
|
||||
Requester::Public(ref public) => Some(public.clone()),
|
||||
Requester::Address(_) => None,
|
||||
Requester::Signature(ref signature) => ethkey::recover(signature, server_key_id)
|
||||
.map_err(|e| format!("bad signature: {}", e)),
|
||||
Requester::Public(ref public) => Ok(public.clone()),
|
||||
Requester::Address(_) => Err("cannot recover public from address".into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn address(&self, server_key_id: &ServerKeyId) -> Option<ethkey::Address> {
|
||||
self.public(server_key_id).map(|p| ethkey::public_to_address(&p))
|
||||
pub fn address(&self, server_key_id: &ServerKeyId) -> Result<ethkey::Address, String> {
|
||||
self.public(server_key_id)
|
||||
.map(|p| ethkey::public_to_address(&p))
|
||||
}
|
||||
}
|
||||
|
||||
@ -202,3 +214,15 @@ impl From<ethkey::Signature> for Requester {
|
||||
Requester::Signature(signature)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ethereum_types::Public> for Requester {
|
||||
fn from(public: ethereum_types::Public) -> Requester {
|
||||
Requester::Public(public)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ethereum_types::Address> for Requester {
|
||||
fn from(address: ethereum_types::Address) -> Requester {
|
||||
Requester::Address(address)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user